刘学玺 4 сар өмнө
parent
commit
cae13835fb

+ 16 - 0
app/Enums/OrderRecordStatus.php

@@ -62,6 +62,16 @@ enum OrderRecordStatus: int
      */
     case LEFT = 11;
 
+    /**
+     * 记录状态:退款中
+     */
+    case REFUNDING = 12;
+
+    /**
+     * 记录状态:退款完成
+     */
+    case REFUNDED = 13;
+
     /**
      * 获取状态的显示文本
      *
@@ -81,6 +91,8 @@ enum OrderRecordStatus: int
             self::REJECTED => '已拒单',
             self::DEPARTED => '技师出发',
             self::LEFT => '技师撤离',
+            self::REFUNDING => '退款中',
+            self::REFUNDED => '退款完成',
         };
     }
 
@@ -125,6 +137,8 @@ enum OrderRecordStatus: int
             self::REJECTED->value => self::REJECTED,
             self::DEPARTED->value => self::DEPARTED,
             self::LEFT->value => self::LEFT,
+            self::REFUNDING->value => self::REFUNDING,
+            self::REFUNDED->value => self::REFUNDED,
             default => null
         };
     }
@@ -158,6 +172,8 @@ enum OrderRecordStatus: int
             self::REJECTED->value => self::REJECTED->label(),
             self::DEPARTED->value => self::DEPARTED->label(),
             self::LEFT->value => self::LEFT->label(),
+            self::REFUNDING->value => self::REFUNDING->label(),
+            self::REFUNDED->value => self::REFUNDED->label(),
         ];
     }
 }

+ 1 - 1
app/Http/Controllers/Client/AccountController.php

@@ -26,7 +26,7 @@ class AccountController extends Controller
      *
      * 向指定手机号发送验证码
      *
-     * @bodyParam mobile string required 手机号码. Example: 13800138000
+     * @queryParam mobile string required 手机号码. Example: 13800138000
      *
      * @response {
      *   "code": 200,

+ 6 - 2
app/Http/Controllers/Client/OrderController.php

@@ -62,6 +62,7 @@ class OrderController extends Controller
      * @bodyParam order_id int 订单ID. Example: null
      * @bodyParam payment_type number required 支付类型. Example: 1 (1:余额,2:微信,3:支付宝)
      * @bodyParam order_type int required 订单类型. Example: 1
+     * @bodyParam distance float 距离. Example: 10
      *
      * @response {
      *   "status": "success",
@@ -71,7 +72,7 @@ class OrderController extends Controller
     public function create(Request $request)
     {
 
-        $data = $request->only(['project_id', 'address_id', 'coach_id', 'use_balance', 'order_id', 'service_time', 'payment_type', 'order_type']);
+        $data = $request->only(['project_id', 'address_id', 'coach_id', 'use_balance', 'order_id', 'service_time', 'payment_type', 'order_type', 'distance']);
 
         return $this->service->createOrder(Auth::user()->id, $data);
     }
@@ -92,6 +93,7 @@ class OrderController extends Controller
      */
     public function finish(Request $request)
     {
+
         $userId = Auth::user()->id;
         $orderId = $request->input('order_id');
 
@@ -128,6 +130,7 @@ class OrderController extends Controller
      * @authenticated
      *
      * @bodyParam order_id int required 订单ID. Example: 123
+     * @bodyParam reason string required 取消原因. Example: 123
      *
      * @response {
      *   "status": "success",
@@ -138,8 +141,9 @@ class OrderController extends Controller
     {
         $userId = Auth::user()->id;
         $orderId = $request->input('order_id');
+        $reason = $request->input('reason');
 
-        return $this->service->cancelOrder($userId, $orderId);
+        return $this->service->cancelOrder($userId, $orderId, $reason);
     }
 
     /**

+ 25 - 1
app/Http/Middleware/Coach.php

@@ -255,7 +255,7 @@ class CoachService
     {
         try {
             // 默认使用当天日期
-            $date = $date ?: now()->toDateString();
+            $date = $date ? Carbon::parse($date)->format('Y-m-d') : now()->toDateString();
             $targetDate = Carbon::parse($date);
 
             // 验证技师信息
@@ -447,4 +447,28 @@ class CoachService
 
         return false;
     }
+
+    /**
+     * 检查是否有时间冲突
+     *
+     * @param  int  $coachId  技师ID
+     * @param  string  $serviceTime  服务时间
+     * @return bool 是否在可服务时间段中
+     */
+    public function validateServiceTimeWithinCoachAvailability(int $coachId, string $serviceTime): bool
+    {
+        $serviceDateTime = Carbon::parse($serviceTime);
+        $coachSchedule = $this->getSchedule($coachId, $serviceDateTime->format('Y-m-d'));
+
+        foreach ($coachSchedule['time_slots'] as $slot) {
+            $slotStart = Carbon::parse($serviceDateTime->format('Y-m-d').' '.$slot['start_time']);
+            $slotEnd = Carbon::parse($serviceDateTime->format('Y-m-d').' '.$slot['end_time']);
+
+            if ($serviceDateTime->between($slotStart, $slotEnd)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
 }

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 539 - 341
app/Services/Client/OrderService.php


+ 40 - 0
app/Services/Client/Traits/CalculatesOrderAmounts.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Services\Client\Traits;
+
+use App\Models\MemberUser;
+
+trait CalculatesOrderAmounts
+{
+    private function calculateTotalAmount(float $projectPrice, float $deliveryFee, float $discountAmount): float
+    {
+        $totalAmount = bcadd($projectPrice, $deliveryFee, 2);
+        $totalAmount = bcsub($totalAmount, $discountAmount, 2);
+
+        return max(0, $totalAmount);
+    }
+
+    private function calculatePaymentDistribution(MemberUser $user, float $totalAmount, bool $useBalance): array
+    {
+        if (! $useBalance || $totalAmount <= 0) {
+            return [
+                'balance_amount' => 0,
+                'pay_amount' => $totalAmount,
+            ];
+        }
+
+        $availableBalance = $user->wallet?->available_balance ?? 0;
+
+        if ($availableBalance >= $totalAmount) {
+            return [
+                'balance_amount' => $totalAmount,
+                'pay_amount' => 0,
+            ];
+        }
+
+        return [
+            'balance_amount' => $availableBalance,
+            'pay_amount' => bcsub($totalAmount, $availableBalance, 2),
+        ];
+    }
+}

+ 20 - 0
app/Services/Client/Traits/HandlesOrderRecords.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Services\Client\Traits;
+
+use App\Models\Order;
+use App\Models\OrderRecord;
+
+trait HandlesOrderRecords
+{
+    private function createOrderRecord(Order $order, int $objectId, string $objectType, string $status, string $remark): OrderRecord
+    {
+        return OrderRecord::create([
+            'order_id' => $order->id,
+            'object_id' => $objectId,
+            'object_type' => $objectType,
+            'state' => $status,
+            'remark' => $remark,
+        ]);
+    }
+}

+ 36 - 0
app/Services/Client/Traits/HandlesPayments.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace App\Services\Client\Traits;
+
+use App\Models\Order;
+use App\Models\WalletPaymentRecord;
+
+trait HandlesPayments
+{
+    private function processBalancePayment(Order $order): void
+    {
+        $wallet = $order->user->wallet;
+
+        // 扣减钱包余额
+        $wallet->decrement('total_balance', $order->balance_amount);
+        $wallet->decrement('available_balance', $order->balance_amount);
+
+        // 创建钱包交易记录
+        $this->createWalletPaymentRecord($order, $wallet);
+    }
+
+    private function createWalletPaymentRecord(Order $order, $wallet): void
+    {
+        WalletPaymentRecord::create([
+            'order_id' => $order->id,
+            'wallet_id' => $wallet->id,
+            'payment_no' => 'B_'.$order->id,
+            'payment_method' => 'balance',
+            'total_amount' => $order->balance_amount,
+            'actual_amount' => 0,
+            'used_wallet_balance' => $order->balance_amount,
+            'used_recharge_balance' => 0,
+            'state' => 'success',
+        ]);
+    }
+}

+ 76 - 0
app/Services/Client/Traits/HandlesRefunds.php

@@ -0,0 +1,76 @@
+<?php
+
+namespace App\Services\Client\Traits;
+
+use App\Enums\OrderStatus;
+use App\Models\MemberUser;
+use App\Models\Order;
+use App\Models\WalletRefundRecord;
+use Illuminate\Support\Facades\Log;
+
+trait HandlesRefunds
+{
+    /**
+     * 处理订单取消退款
+     */
+    protected function handleCancelRefund(Order $order): void
+    {
+        $user = $order->user;
+
+        $deductAmount = $this->calculateDeductAmount($order);
+        $this->processRefund($user, $order, $deductAmount);
+    }
+
+    /**
+     * 计算扣除金额
+     */
+    private function calculateDeductAmount(Order $order): float
+    {
+        $baseAmount = $order->payment_amount + $order->balance_amount - $order->traffic_amount;
+
+        return match ($order->state) {
+            OrderStatus::ACCEPTED->value => $baseAmount * 0.2, // 已接单扣除20%
+            OrderStatus::DEPARTED->value => $baseAmount * 0.5, // 已出发扣除50%
+            default => 0
+        };
+    }
+
+    /**
+     * 处理退款流程
+     */
+    private function processRefund(MemberUser $user, Order $order, float $deductAmount): void
+    {
+        try {
+            $refundAmount = $this->calculateRefundAmount($order, $deductAmount);
+
+            if ($refundAmount > 0) {
+                // 退还余额
+                $user->wallet->increment('total_balance', $refundAmount);
+                $user->wallet->increment('available_balance', $refundAmount);
+
+                // 创建退款记录
+                $this->createRefundRecord($order, $refundAmount);
+            }
+        } catch (\Exception $e) {
+            Log::error('处理退款失败', [
+                'order_id' => $order->id,
+                'user_id' => $user->id,
+                'error' => $e->getMessage(),
+            ]);
+            throw $e;
+        }
+    }
+
+    /**
+     * 创建退款记录
+     */
+    private function createRefundRecord(Order $order, float $refundAmount): void
+    {
+        WalletRefundRecord::create([
+            'order_id' => $order->id,
+            'user_id' => $order->user_id,
+            'amount' => $refundAmount,
+            'state' => 'success',
+        ]);
+    }
+}

+ 90 - 0
app/Services/Client/Traits/ValidatesOrders.php

@@ -0,0 +1,90 @@
+<?php
+
+namespace App\Services\Client\Traits;
+
+use App\Enums\OrderType;
+use App\Enums\ProjectStatus;
+use App\Enums\UserStatus;
+use App\Models\MemberUser;
+use App\Models\Project;
+
+trait ValidatesOrders
+{
+    /**
+     * 验证用户
+     */
+    protected function validateUser(int $userId): MemberUser
+    {
+        return MemberUser::where('id', $userId)
+            ->where('state', UserStatus::OPEN->value)
+            ->firstOrFail();
+    }
+
+    /**
+     * 验证项目
+     */
+    protected function validateProject(int $projectId): Project
+    {
+        return Project::where('id', $projectId)
+            ->where('state', ProjectStatus::OPEN->value)
+            ->firstOrFail();
+    }
+
+    /**
+     * 验证订单类型数据
+     */
+    protected function validateOrderTypeData(OrderType $type, array $data): void
+    {
+        match ($type) {
+            OrderType::VISIT => $this->validateVisitOrder($data),
+            OrderType::GRAB => $this->validateGrabOrder($data),
+            OrderType::OVERTIME => $this->validateOvertimeOrder($data),
+            OrderType::SHOP => $this->validateShopOrder($data),
+            OrderType::EMERGENCY => $this->validateEmergencyOrder($data),
+        };
+    }
+
+    /**
+     * 验证上门订单
+     */
+    private function validateVisitOrder(array $data): void
+    {
+        abort_if(empty($data['coach_id']), 400, '技师ID不能为空');
+        abort_if(empty($data['address_id']), 400, '地址ID不能为空');
+
+        $this->validateCoach($data['coach_id']);
+        $this->validateServiceTime($data['coach_id'], $data['service_time']);
+    }
+
+    /**
+     * 验证抢单订单
+     */
+    private function validateGrabOrder(array $data): void
+    {
+        abort_if(empty($data['address_id']), 400, '地址ID不能为空');
+    }
+
+    /**
+     * 验证加钟订单
+     */
+    private function validateOvertimeOrder(array $data): void
+    {
+        abort_if(empty($data['order_id']), 400, '原订单ID不能为空');
+    }
+
+    /**
+     * 验证到店订单
+     */
+    private function validateShopOrder(array $data): void
+    {
+        abort_if(empty($data['shop_id']), 400, '店铺ID不能为空');
+    }
+
+    /**
+     * 验证应急订单
+     */
+    private function validateEmergencyOrder(array $data): void
+    {
+        abort_if(empty($data['address_id']), 400, '地址ID不能为空');
+    }
+}

+ 147 - 0
app/Services/Client/Traits/ValidatesServiceTime.php

@@ -0,0 +1,147 @@
+<?php
+
+namespace App\Services\Client\Traits;
+
+use App\Enums\OrderStatus;
+use App\Enums\TechnicianAuthStatus;
+use App\Enums\TechnicianStatus;
+use App\Models\CoachSchedule;
+use App\Models\CoachUser;
+use App\Models\Order;
+use Carbon\Carbon;
+
+trait ValidatesServiceTime
+{
+    private function validateServiceTimeParams(int $coachId, string $serviceTime): void
+    {
+        // 验证技师状态
+        $coach = CoachUser::where('id', $coachId)
+            ->where('state', TechnicianStatus::ACTIVE->value)
+            ->where('auth_state', TechnicianAuthStatus::PASSED->value)
+            ->firstOrFail();
+
+        // 验证时间格式
+        $serviceDateTime = Carbon::parse($serviceTime);
+        abort_if(
+            $serviceDateTime->isPast(),
+            400,
+            '服务时间不能早于当前时间'
+        );
+
+        // 验证预约提前时间(至少提前2小时)
+        $minAdvanceHours = config('business.min_advance_hours', 2);
+        abort_if(
+            $serviceDateTime->diffInHours(now()) < $minAdvanceHours,
+            400,
+            "需要至少提前{$minAdvanceHours}小时预约"
+        );
+
+        // 验证预约时间范围(最多提前7天)
+        $maxAdvanceDays = config('business.max_advance_days', 7);
+        abort_if(
+            $serviceDateTime->diffInDays(now()) > $maxAdvanceDays,
+            400,
+            "最多只能提前{$maxAdvanceDays}天预约"
+        );
+    }
+
+    private function getCoachWorkSchedule(int $coachId): array
+    {
+        $schedule = CoachSchedule::where('coach_id', $coachId)
+            ->where('state', 1)
+            ->first();
+
+        if (! $schedule) {
+            return [
+                'work_days' => range(1, 7),
+                'work_hours' => [
+                    'start' => '09:00',
+                    'end' => '21:00',
+                ],
+                'rest_dates' => [],
+            ];
+        }
+
+        return [
+            'work_days' => json_decode($schedule->work_days, true),
+            'work_hours' => [
+                'start' => $schedule->work_start_time,
+                'end' => $schedule->work_end_time,
+            ],
+            'rest_dates' => json_decode($schedule->rest_dates, true) ?? [],
+        ];
+    }
+
+    private function validateWorkingHours(string $serviceTime, array $workSchedule): void
+    {
+        $serviceDateTime = Carbon::parse($serviceTime);
+
+        // 检查工作日
+        $dayOfWeek = $serviceDateTime->dayOfWeek;
+        abort_if(
+            ! in_array($dayOfWeek, $workSchedule['work_days']),
+            400,
+            '该时间不在技师工作日内'
+        );
+
+        // 检查特殊休息日
+        $dateStr = $serviceDateTime->format('Y-m-d');
+        abort_if(
+            in_array($dateStr, $workSchedule['rest_dates']),
+            400,
+            '技师该日期休息'
+        );
+
+        // 检查工作时间
+        $timeStr = $serviceDateTime->format('H:i');
+        $startTime = Carbon::parse($workSchedule['work_hours']['start']);
+        $endTime = Carbon::parse($workSchedule['work_hours']['end']);
+
+        abort_if(
+            $timeStr < $startTime->format('H:i') || $timeStr > $endTime->format('H:i'),
+            400,
+            sprintf(
+                '服务时间需在%s-%s之间',
+                $startTime->format('H:i'),
+                $endTime->format('H:i')
+            )
+        );
+    }
+
+    private function checkTimeConflicts(int $coachId, string $serviceTime): void
+    {
+        $serviceDateTime = Carbon::parse($serviceTime);
+
+        // 获取服务时长(默认2小时)
+        $serviceDuration = config('business.default_service_duration', 120);
+
+        // 计算服务结束时间
+        $serviceEndTime = $serviceDateTime->copy()->addMinutes($serviceDuration);
+
+        // 检查是否有其他订单在这个时间段
+        $conflictOrder = Order::where('coach_id', $coachId)
+            ->whereIn('state', [
+                OrderStatus::PAID->value,
+                OrderStatus::ACCEPTED->value,
+                OrderStatus::SERVING->value,
+            ])
+            ->where(function ($query) use ($serviceDateTime, $serviceEndTime) {
+                $query->where(function ($q) use ($serviceDateTime) {
+                    // 新订单开始时间在其他订单的服务时间段内
+                    $q->where('service_time', '<=', $serviceDateTime)
+                        ->where('service_end_time', '>', $serviceDateTime);
+                })->orWhere(function ($q) use ($serviceEndTime) {
+                    // 新订单结束时间在其他订单的服务时间段内
+                    $q->where('service_time', '<', $serviceEndTime)
+                        ->where('service_end_time', '>=', $serviceEndTime);
+                });
+            })
+            ->first();
+
+        abort_if(
+            $conflictOrder,
+            400,
+            '该时间段技师已有其他订单'
+        );
+    }
+}

+ 6 - 6
app/Services/Coach/OrderService.php

@@ -118,7 +118,7 @@ class OrderService
             // 验证技师信息
             [$coach, $location] = $this->validateCoach($user);
 
-            // 获取技师项目信���
+            // 获取技师项目信
             $coachProjects = $coach->projects;
             $coachProjectIds = $coachProjects->pluck('project_id')->toArray();
 
@@ -159,7 +159,7 @@ class OrderService
     private function validateCoach(MemberUser $user): array
     {
         $coach = $user->coach;
-        abort_if(! $coach, 404, '技师存在');
+        abort_if(! $coach, 404, '技师��存在');
         abort_if(! $coach->info, 404, '技师信息不存在');
         abort_if($coach->info->state != TechnicianStatus::ACTIVE->value, 404, '技师状异常');
         abort_if($coach->real->state != TechnicianAuthStatus::PASSED->value, 404, '技师实名认证未通过');
@@ -321,7 +321,7 @@ class OrderService
 
         $order->project->traffic_fee = round($trafficFee, 2);
 
-        // 程收费
+        // ��程收费
         if ($coachProject->is_round_trip) {
             $order->project->traffic_fee *= 2;
         }
@@ -361,7 +361,7 @@ class OrderService
                 // 验证订单状态
                 abort_if($order->state !== OrderStatus::CREATED->value, 400, '订单状态异常,无法抢单');
 
-                // 验订单类型
+                // 验���订单类型
                 abort_if($order->type !== OrderType::GRAB->value, 400, '该订单不是抢单类型');
 
                 // 检查技师是否已参与抢单
@@ -466,7 +466,7 @@ class OrderService
                 ]);
 
                 return [
-                    'message' => '单成功',
+                    'message' => '��单成功',
                     'order_id' => $orderId,
                     'order_no' => $order->order_no,
                 ];
@@ -859,7 +859,7 @@ class OrderService
     /**
      * 技师撤离
      *
-     * @param  int  $userId  技师���户ID
+     * @param  int  $userId  技师户ID
      * @param  int  $orderId  订单ID
      *
      * @throws \Exception

+ 2 - 1
config/cors.php

@@ -1,6 +1,7 @@
 <?php
+
 return [
-    'paths' => ['api/*'],
+    'paths' => ['api/*', 'sanctum/csrf-cookie'],
     'allowed_methods' => ['*'],
     'allowed_origins' => ['*'],
     'allowed_origins_patterns' => [],

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно