input('keyword', '')); $status = trim((string)$request->input('status', '')); $serviceProvider = trim((string)$request->input('service_provider', '')); $sourceChannel = $this->normalizeOrderSourceChannel((string)$request->input('source_channel', '')); $paginationEnabled = $request->input('page', null) !== null || $request->input('page_size', null) !== null; $page = max(1, (int)$request->input('page', 1)); $pageSize = max(1, min(100, (int)$request->input('page_size', 20))); $query = Db::name('orders') ->alias('o') ->leftJoin('order_products p', 'p.order_id = o.id') ->field([ 'o.id', 'o.order_no', 'o.appraisal_no', 'o.service_provider', 'o.order_status', 'o.display_status', 'o.estimated_finish_time', 'o.source_channel', 'o.source_customer_id', 'o.pay_amount', 'o.created_at', 'p.product_name', 'p.category_name', 'p.brand_name', ]) ->order('o.id', 'desc'); if ($keyword !== '') { $query->where(function ($builder) use ($keyword) { $builder->whereRaw( '(o.order_no LIKE :keyword_order OR o.appraisal_no LIKE :keyword_appraisal OR p.product_name LIKE :keyword_product)', [ 'keyword_order' => "%{$keyword}%", 'keyword_appraisal' => "%{$keyword}%", 'keyword_product' => "%{$keyword}%", ] ); }); } $warehouseStatusFilters = [ 'warehouse_active', 'warehouse_in_transit', 'warehouse_received', 'warehouse_pending_return', ]; $specialStatusFilters = array_merge(['returning', 'completed_signed'], $warehouseStatusFilters); if ($status !== '' && !in_array($status, $specialStatusFilters, true)) { $query->where('o.order_status', $status); } if (in_array($status, $warehouseStatusFilters, true)) { $warehouseActiveStatuses = [ 'pending_shipping', 'received', 'in_first_review', 'pending_supplement', 'in_final_review', 'generating_report', 'report_published', ]; if ($status === 'warehouse_in_transit') { $query->where('o.order_status', 'pending_shipping'); } elseif ($status === 'warehouse_received') { $query->whereIn('o.order_status', array_values(array_diff($warehouseActiveStatuses, ['pending_shipping', 'report_published']))); } elseif ($status === 'warehouse_pending_return') { $query->where('o.order_status', 'report_published'); } else { $query->whereIn('o.order_status', $warehouseActiveStatuses); } } if ($serviceProvider !== '') { $query->where('o.service_provider', $serviceProvider); } if ($sourceChannel !== '') { $query->where('o.source_channel', $sourceChannel); } $rows = $query->select()->toArray(); $orderIds = array_map('intval', array_column($rows, 'id')); $sendTrackingMap = $this->latestLogisticsMap($orderIds, 'send_to_center'); $returnTrackingMap = $this->latestLogisticsMap($orderIds, 'return_to_user'); $list = array_map(function (array $item) use ($sendTrackingMap, $returnTrackingMap) { $orderId = (int)$item['id']; $sendTrackingNo = $sendTrackingMap[$orderId]['tracking_no'] ?? ''; $sendTrackingStatus = $sendTrackingMap[$orderId]['tracking_status'] ?? ''; $warehouseBucket = $this->warehouseOrderBucket( (string)$item['order_status'], $sendTrackingNo, $sendTrackingStatus, (string)($item['display_status'] ?? '') ); return [ 'id' => $orderId, 'order_no' => $item['order_no'], 'appraisal_no' => $item['appraisal_no'], 'product_name' => $item['product_name'] ?: '待完善物品信息', 'category_name' => $item['category_name'] ?: '', 'brand_name' => $item['brand_name'] ?: '', 'service_provider' => $item['service_provider'], 'service_provider_text' => $item['service_provider'] === 'zhongjian' ? '中检鉴定' : '实物鉴定', 'source_channel' => $this->normalizeOrderSourceChannel((string)($item['source_channel'] ?? '')), 'source_channel_text' => $this->sourceChannelText((string)($item['source_channel'] ?? '')), 'source_customer_id' => (string)($item['source_customer_id'] ?? ''), 'order_status' => $item['order_status'], 'display_status' => $this->displayStatus( (string)$item['order_status'], (string)$item['display_status'], $returnTrackingMap[$orderId]['tracking_no'] ?? '', $returnTrackingMap[$orderId]['tracking_status'] ?? '', ), 'warehouse_bucket' => $warehouseBucket, 'warehouse_bucket_text' => $this->warehouseOrderBucketText($warehouseBucket), 'estimated_finish_time' => $item['estimated_finish_time'], 'pay_amount' => (float)$item['pay_amount'], 'created_at' => $item['created_at'], ]; }, $rows); if ($status === 'returning') { $list = array_values(array_filter($list, function (array $item) { return $item['order_status'] === 'completed' && $item['display_status'] === '物品已寄回'; })); } if ($status === 'completed_signed') { $list = array_values(array_filter($list, function (array $item) { return $item['order_status'] === 'completed' && $item['display_status'] === '已完成'; })); } if (in_array($status, $warehouseStatusFilters, true)) { $list = array_values(array_filter($list, function (array $item) use ($status) { if ($status === 'warehouse_active') { return in_array($item['warehouse_bucket'], [ 'warehouse_in_transit', 'warehouse_received', 'warehouse_pending_return', ], true); } return $item['warehouse_bucket'] === $status; })); } $total = count($list); if ($paginationEnabled) { $offset = ($page - 1) * $pageSize; $list = array_slice($list, $offset, $pageSize); return api_success([ 'list' => $list, 'total' => $total, 'page' => $page, 'page_size' => $pageSize, ]); } return api_success([ 'list' => $list, ]); } public function detail(Request $request) { $id = (int)$request->input('id', 0); if (!$id) { return api_error('订单 ID 不能为空', 422); } $order = Db::name('orders')->where('id', $id)->find(); if (!$order) { return api_error('订单不存在', 404); } $product = Db::name('order_products')->where('order_id', $id)->find(); $extra = Db::name('order_extras')->where('order_id', $id)->find(); $sendLogistics = Db::name('order_logistics') ->where('order_id', $id) ->where('logistics_type', 'send_to_center') ->order('id', 'desc') ->find(); $returnLogistics = Db::name('order_logistics') ->where('order_id', $id) ->where('logistics_type', 'return_to_user') ->order('id', 'desc') ->find(); $timeline = Db::name('order_timelines') ->where('order_id', $id) ->order('occurred_at', 'asc') ->select() ->toArray(); $timeline = array_map(fn (array $item) => [ 'node_text' => $item['node_text'], 'node_desc' => $item['node_desc'], 'occurred_at' => $item['occurred_at'], ], $timeline); $supplement = Db::name('order_supplement_tasks')->where('order_id', $id)->order('id', 'desc')->find(); $supplementItems = []; if ($supplement) { $supplementItems = Db::name('order_supplement_task_items') ->where('task_id', $supplement['id']) ->select() ->toArray(); $supplementItems = array_map(fn (array $item) => [ 'item_name' => $item['item_name'], 'guide_text' => $item['guide_text'], ], $supplementItems); } $report = Db::name('reports')->where('order_id', $id)->order('id', 'desc')->find(); $hasPublishedOrderReport = $report && ($report['report_status'] ?? '') === 'published'; $canAttemptReturnLogistics = in_array($order['order_status'], ['report_published', 'completed'], true) && (($returnLogistics['tracking_status'] ?? '') !== 'received'); $shippingTarget = Db::name('order_shipping_targets')->where('order_id', $id)->find(); $returnAddress = Db::name('order_return_addresses')->where('order_id', $id)->find(); if (!$returnAddress) { $returnAddress = Db::name('user_addresses') ->where('user_id', (int)$order['user_id']) ->where('is_default', 1) ->order('id', 'desc') ->find() ?: Db::name('user_addresses') ->where('user_id', (int)$order['user_id']) ->order('id', 'desc') ->find(); if ($returnAddress) { $returnAddress = [ 'user_address_id' => (int)$returnAddress['id'], 'consignee' => $returnAddress['consignee'], 'mobile' => $returnAddress['mobile'], 'province' => $returnAddress['province'], 'city' => $returnAddress['city'], 'district' => $returnAddress['district'], 'detail_address' => $returnAddress['detail_address'], ]; } } $logisticsNodes = []; if ($sendLogistics) { $logisticsNodes = Db::name('order_logistics_nodes') ->where('logistics_id', $sendLogistics['id']) ->order('node_time', 'desc') ->select() ->toArray(); } $returnLogisticsNodes = []; if ($returnLogistics) { $returnLogisticsNodes = Db::name('order_logistics_nodes') ->where('logistics_id', $returnLogistics['id']) ->order('node_time', 'desc') ->select() ->toArray(); } return api_success([ 'order_info' => [ 'id' => (int)$order['id'], 'order_no' => $order['order_no'], 'appraisal_no' => $order['appraisal_no'], 'service_provider' => $order['service_provider'], 'service_provider_text' => $order['service_provider'] === 'zhongjian' ? '中检鉴定' : '实物鉴定', 'source_channel' => $this->normalizeOrderSourceChannel((string)($order['source_channel'] ?? '')), 'source_channel_text' => $this->sourceChannelText((string)($order['source_channel'] ?? '')), 'source_customer_id' => (string)($order['source_customer_id'] ?? ''), 'order_status' => $order['order_status'], 'display_status' => $this->displayStatus( (string)$order['order_status'], (string)$order['display_status'], $returnLogistics['tracking_no'] ?? '', $returnLogistics['tracking_status'] ?? '', ), 'pay_amount' => (float)$order['pay_amount'], 'estimated_finish_time' => $order['estimated_finish_time'], 'created_at' => $order['created_at'], 'can_reassign_warehouse' => $order['order_status'] === 'pending_shipping' && empty($sendLogistics['tracking_no']), 'can_mark_received' => $order['order_status'] === 'pending_shipping' && (!empty($sendLogistics['tracking_no']) || ($order['source_channel'] ?? '') === 'enterprise_push'), 'can_submit_return_logistics' => $hasPublishedOrderReport && $canAttemptReturnLogistics, 'return_logistics_block_reason' => (!$hasPublishedOrderReport && $canAttemptReturnLogistics) ? '订单报告未发布前,物品不允许寄回' : '', 'can_mark_return_received' => $order['order_status'] === 'completed' && !empty($returnLogistics['tracking_no']) && ($returnLogistics['tracking_status'] ?? '') !== 'received', ], 'product_info' => [ 'product_name' => $product['product_name'] ?? '', 'category_id' => (int)($product['category_id'] ?? 0), 'category_name' => $product['category_name'] ?? '', 'brand_id' => (int)($product['brand_id'] ?? 0), 'brand_name' => $product['brand_name'] ?? '', 'color' => $product['color'] ?? '', 'size_spec' => $product['size_spec'] ?? '', 'serial_no' => $product['serial_no'] ?? '', ], 'extra_info' => [ 'purchase_channel' => $extra['purchase_channel'] ?? '', 'purchase_price' => (float)($extra['purchase_price'] ?? 0), 'usage_status' => $extra['usage_status'] ?? '', 'condition_desc' => $extra['condition_desc'] ?? '', 'remark' => $extra['remark'] ?? '', ], 'shipping_target' => $shippingTarget ? [ 'warehouse_id' => (int)($shippingTarget['warehouse_id'] ?? 0), 'warehouse_name' => $shippingTarget['warehouse_name'], 'warehouse_code' => $shippingTarget['warehouse_code'], 'receiver_name' => $shippingTarget['receiver_name'], 'receiver_mobile' => $shippingTarget['receiver_mobile'], 'full_address' => trim(sprintf( '%s%s%s%s', $shippingTarget['province'] ?? '', $shippingTarget['city'] ?? '', $shippingTarget['district'] ?? '', $shippingTarget['detail_address'] ?? '' )), 'service_time' => $shippingTarget['service_time'], 'notice' => $shippingTarget['notice'], ] : null, 'return_address' => $returnAddress ? [ 'user_address_id' => (int)($returnAddress['user_address_id'] ?? 0), 'consignee' => $returnAddress['consignee'], 'mobile' => $returnAddress['mobile'], 'full_address' => trim(sprintf( '%s%s%s%s', $returnAddress['province'] ?? '', $returnAddress['city'] ?? '', $returnAddress['district'] ?? '', $returnAddress['detail_address'] ?? '' )), ] : null, 'timeline' => $timeline, 'logistics_info' => $sendLogistics ? [ 'express_company' => $sendLogistics['express_company'], 'tracking_no' => $sendLogistics['tracking_no'], 'tracking_status' => $sendLogistics['tracking_status'], 'tracking_status_text' => $this->trackingStatusText($sendLogistics['tracking_status'], 'send_to_center'), 'latest_desc' => $this->formatAdminLogisticsDesc( 'send_to_center', $sendLogistics['tracking_status'], $sendLogistics['express_company'], $sendLogistics['tracking_no'], $sendLogistics['latest_desc'] ), 'latest_time' => $sendLogistics['latest_time'], 'nodes' => array_map(fn (array $item) => [ 'node_time' => $item['node_time'], 'node_desc' => $this->formatAdminLogisticsDesc( 'send_to_center', $sendLogistics['tracking_status'], $sendLogistics['express_company'], $sendLogistics['tracking_no'], $item['node_desc'] ), 'node_location' => $item['node_location'], ], $logisticsNodes), ] : null, 'return_logistics' => $returnLogistics ? [ 'express_company' => $returnLogistics['express_company'], 'tracking_no' => $returnLogistics['tracking_no'], 'tracking_status' => $returnLogistics['tracking_status'], 'tracking_status_text' => $this->trackingStatusText($returnLogistics['tracking_status'], 'return_to_user'), 'latest_desc' => $this->formatAdminLogisticsDesc( 'return_to_user', $returnLogistics['tracking_status'], $returnLogistics['express_company'], $returnLogistics['tracking_no'], $returnLogistics['latest_desc'] ), 'latest_time' => $returnLogistics['latest_time'], 'nodes' => array_map(fn (array $item) => [ 'node_time' => $item['node_time'], 'node_desc' => $this->formatAdminLogisticsDesc( 'return_to_user', $returnLogistics['tracking_status'], $returnLogistics['express_company'], $returnLogistics['tracking_no'], $item['node_desc'] ), 'node_location' => $item['node_location'], ], $returnLogisticsNodes), ] : null, 'supplement_task' => $supplement ? [ 'reason' => $supplement['reason'], 'deadline' => $supplement['deadline'], 'status' => $supplement['status'], 'items' => $supplementItems, ] : null, 'report_summary' => $report ? [ 'id' => (int)$report['id'], 'report_no' => $report['report_no'], 'report_title' => $report['report_title'], 'report_status' => $report['report_status'], 'report_status_text' => $this->reportStatusText((string)$report['report_status']), 'publish_time' => $report['publish_time'], ] : null, ]); } public function warehouseOptions(Request $request) { $id = (int)$request->input('id', 0); if ($id <= 0) { return api_error('订单 ID 不能为空', 422); } $order = Db::name('orders')->where('id', $id)->find(); if (!$order) { return api_error('订单不存在', 404); } $product = Db::name('order_products')->where('order_id', $id)->find(); $options = (new WarehouseService())->optionsForOrder( (string)($order['service_provider'] ?? 'anxinyan'), !empty($product['category_id']) ? (int)$product['category_id'] : null ); return api_success([ 'list' => $options, ]); } public function reassignWarehouse(Request $request) { $id = (int)$request->input('id', 0); $warehouseId = (int)$request->input('warehouse_id', 0); if ($id <= 0 || $warehouseId <= 0) { return api_error('订单 ID 和仓库 ID 不能为空', 422); } $order = Db::name('orders')->where('id', $id)->find(); if (!$order) { return api_error('订单不存在', 404); } $logistics = Db::name('order_logistics') ->where('order_id', $id) ->where('logistics_type', 'send_to_center') ->order('id', 'desc') ->find(); if ($order['order_status'] !== 'pending_shipping' || !empty($logistics['tracking_no'])) { return api_error('当前订单已进入寄送流程,暂不支持改派仓库', 422); } $warehouse = Db::name('shipping_warehouses') ->where('id', $warehouseId) ->where('status', 'enabled') ->find(); if (!$warehouse) { return api_error('目标仓库不存在或已停用', 404); } $product = Db::name('order_products')->where('order_id', $id)->find(); $categoryId = !empty($product['category_id']) ? (int)$product['category_id'] : null; $allowedWarehouses = (new WarehouseService())->optionsForOrder((string)$order['service_provider'], $categoryId); $allowedIds = array_column($allowedWarehouses, 'id'); if (!in_array($warehouseId, $allowedIds, true)) { return api_error('目标仓库不适用于当前订单服务类型或品类', 422); } $currentTarget = Db::name('order_shipping_targets')->where('order_id', $id)->find(); if ($currentTarget && (int)($currentTarget['warehouse_id'] ?? 0) === $warehouseId) { return api_error('当前订单已绑定该仓库,无需重复改派', 422); } $snapshot = [ 'warehouse_id' => (int)$warehouse['id'], 'warehouse_name' => $warehouse['warehouse_name'], 'warehouse_code' => $warehouse['warehouse_code'], 'receiver_name' => $warehouse['receiver_name'], 'receiver_mobile' => $warehouse['receiver_mobile'], 'province' => $warehouse['province'], 'city' => $warehouse['city'], 'district' => $warehouse['district'], 'detail_address' => $warehouse['detail_address'], 'service_time' => $warehouse['service_time'], 'notice' => $warehouse['notice'], ]; $now = date('Y-m-d H:i:s'); Db::startTrans(); try { (new WarehouseService())->bindOrderTarget($id, (string)$order['service_provider'], $categoryId); Db::name('order_shipping_targets')->where('order_id', $id)->update([ 'warehouse_id' => $snapshot['warehouse_id'], 'warehouse_name' => $snapshot['warehouse_name'], 'warehouse_code' => $snapshot['warehouse_code'], 'service_provider' => $order['service_provider'], 'receiver_name' => $snapshot['receiver_name'], 'receiver_mobile' => $snapshot['receiver_mobile'], 'province' => $snapshot['province'], 'city' => $snapshot['city'], 'district' => $snapshot['district'], 'detail_address' => $snapshot['detail_address'], 'service_time' => $snapshot['service_time'], 'notice' => $snapshot['notice'], 'updated_at' => $now, ]); Db::name('order_timelines')->insert([ 'order_id' => $id, 'node_code' => 'warehouse_reassigned', 'node_text' => '仓库已改派', 'node_desc' => sprintf('订单收货仓库已改派至 %s', $snapshot['warehouse_name']), 'operator_type' => 'admin', 'operator_id' => (int)$request->header('x-admin-id', 0) ?: null, 'occurred_at' => $now, 'created_at' => $now, ]); Db::commit(); } catch (\Throwable $e) { Db::rollback(); return api_error('仓库改派失败', 500, [ 'detail' => $e->getMessage(), ]); } return api_success([ 'id' => $id, 'warehouse_id' => $snapshot['warehouse_id'], 'warehouse_name' => $snapshot['warehouse_name'], ], '仓库已改派'); } public function receiveLogistics(Request $request) { $id = (int)$request->input('id', 0); if ($id <= 0) { return api_error('订单 ID 不能为空', 422); } $order = Db::name('orders')->where('id', $id)->find(); if (!$order) { return api_error('订单不存在', 404); } $logistics = Db::name('order_logistics') ->where('order_id', $id) ->where('logistics_type', 'send_to_center') ->order('id', 'desc') ->find(); $allowEnterpriseManualReceive = ($order['source_channel'] ?? '') === 'enterprise_push'; if ((!$logistics || $logistics['tracking_no'] === '') && !$allowEnterpriseManualReceive) { return api_error('当前订单还没有有效运单信息', 422); } if ($order['order_status'] !== 'pending_shipping') { return api_error('当前订单状态不支持标记签收', 422); } $now = date('Y-m-d H:i:s'); $latestDesc = '鉴定中心已签收包裹,等待鉴定师开始处理。'; Db::startTrans(); try { if ($logistics) { Db::name('order_logistics')->where('id', $logistics['id'])->update([ 'tracking_status' => 'received', 'latest_desc' => $latestDesc, 'latest_time' => $now, 'updated_at' => $now, ]); $logisticsId = (int)$logistics['id']; } else { $latestDesc = '大客户推送订单已确认到仓,等待鉴定师开始处理。'; $logisticsId = (int)Db::name('order_logistics')->insertGetId([ 'order_id' => $id, 'logistics_type' => 'send_to_center', 'express_company' => '', 'tracking_no' => '', 'tracking_status' => 'received', 'latest_desc' => $latestDesc, 'latest_time' => $now, 'created_at' => $now, 'updated_at' => $now, ]); } Db::name('order_logistics_nodes')->insert([ 'logistics_id' => $logisticsId, 'node_time' => $now, 'node_desc' => $latestDesc, 'node_location' => '鉴定中心', 'created_at' => $now, ]); Db::name('orders')->where('id', $id)->update([ 'order_status' => 'in_first_review', 'display_status' => '鉴定中', 'updated_at' => $now, ]); $taskUpdate = [ 'status' => 'processing', 'updated_at' => $now, ]; $task = Db::name('appraisal_tasks') ->where('order_id', $id) ->where('task_stage', 'first_review') ->order('id', 'asc') ->find(); if ($task && empty($task['started_at'])) { $taskUpdate['started_at'] = $now; } if ($task) { Db::name('appraisal_tasks')->where('id', (int)$task['id'])->update($taskUpdate); } Db::name('order_timelines')->insert([ 'order_id' => $id, 'node_code' => 'first_review', 'node_text' => '鉴定中', 'node_desc' => $logistics ? '包裹已由鉴定中心签收,订单已进入鉴定流程' : '大客户推送订单已确认到仓,订单已进入鉴定流程', 'operator_type' => 'admin', 'operator_id' => (int)$request->header('x-admin-id', 0) ?: null, 'occurred_at' => $now, 'created_at' => $now, ]); Db::commit(); } catch (\Throwable $e) { Db::rollback(); return api_error('标记签收失败', 500, [ 'detail' => $e->getMessage(), ]); } (new EnterpriseWebhookService())->recordOrderEvent($id, 'inbound_received', [ 'express_company' => (string)($logistics['express_company'] ?? ''), 'tracking_no' => (string)($logistics['tracking_no'] ?? ''), 'received_at' => $now, ]); return api_success(['id' => $id], '已标记鉴定中心签收'); } public function saveReturnLogistics(Request $request) { $id = (int)$request->input('id', 0); $expressCompany = trim((string)$request->input('express_company', '')); $trackingNo = trim((string)$request->input('tracking_no', '')); if ($id <= 0 || $expressCompany === '' || $trackingNo === '') { return api_error('订单、快递公司和运单号不能为空', 422); } $order = Db::name('orders')->where('id', $id)->find(); if (!$order) { return api_error('订单不存在', 404); } if (!in_array($order['order_status'], ['report_published', 'completed'], true)) { return api_error('当前订单状态不支持登记回寄运单', 422); } $report = Db::name('reports')->where('order_id', $id)->order('id', 'desc')->find(); if (!$report || ($report['report_status'] ?? '') !== 'published') { return api_error('订单报告未发布前,物品不允许寄回', 422); } $returnAddress = Db::name('order_return_addresses')->where('order_id', $id)->find(); if (!$returnAddress) { $fallbackAddress = Db::name('user_addresses') ->where('user_id', (int)$order['user_id']) ->where('is_default', 1) ->order('id', 'desc') ->find() ?: Db::name('user_addresses') ->where('user_id', (int)$order['user_id']) ->order('id', 'desc') ->find(); if (!$fallbackAddress) { return api_error('当前订单尚未确认寄回地址,且用户账户下没有可用地址', 422); } $returnAddress = [ 'user_address_id' => (int)$fallbackAddress['id'], 'consignee' => $fallbackAddress['consignee'], 'mobile' => $fallbackAddress['mobile'], 'province' => $fallbackAddress['province'], 'city' => $fallbackAddress['city'], 'district' => $fallbackAddress['district'], 'detail_address' => $fallbackAddress['detail_address'], ]; } $existing = Db::name('order_logistics') ->where('order_id', $id) ->where('logistics_type', 'return_to_user') ->order('id', 'desc') ->find(); $now = date('Y-m-d H:i:s'); $latestDesc = sprintf('平台已通过 %s 回寄商品,运单号 %s。', $expressCompany, $trackingNo); Db::startTrans(); try { $existingReturnAddress = Db::name('order_return_addresses')->where('order_id', $id)->find(); if (!$existingReturnAddress) { Db::name('order_return_addresses')->insert([ 'order_id' => $id, 'user_address_id' => $returnAddress['user_address_id'] ?? null, 'consignee' => $returnAddress['consignee'] ?? '', 'mobile' => $returnAddress['mobile'] ?? '', 'province' => $returnAddress['province'] ?? '', 'city' => $returnAddress['city'] ?? '', 'district' => $returnAddress['district'] ?? '', 'detail_address' => $returnAddress['detail_address'] ?? '', 'created_at' => $now, 'updated_at' => $now, ]); } if ($existing) { Db::name('order_logistics')->where('id', $existing['id'])->update([ 'logistics_type' => 'return_to_user', 'express_company' => $expressCompany, 'tracking_no' => $trackingNo, 'tracking_status' => 'in_transit', 'latest_desc' => $latestDesc, 'latest_time' => $now, 'updated_at' => $now, ]); $logisticsId = (int)$existing['id']; $nodeText = '已更新回寄运单'; $nodeDesc = sprintf('平台更新回寄运单:%s %s', $expressCompany, $trackingNo); } else { $logisticsId = (int)Db::name('order_logistics')->insertGetId([ 'order_id' => $id, 'logistics_type' => 'return_to_user', 'express_company' => $expressCompany, 'tracking_no' => $trackingNo, 'tracking_status' => 'in_transit', 'latest_desc' => $latestDesc, 'latest_time' => $now, 'created_at' => $now, 'updated_at' => $now, ]); $nodeText = '已寄回用户'; $nodeDesc = sprintf('平台已通过 %s 回寄商品,运单号 %s', $expressCompany, $trackingNo); } Db::name('order_logistics_nodes')->insert([ 'logistics_id' => $logisticsId, 'node_time' => $now, 'node_desc' => $latestDesc, 'node_location' => $returnAddress['city'] ?? '用户地址', 'created_at' => $now, ]); Db::name('orders')->where('id', $id)->update([ 'order_status' => 'completed', 'display_status' => '物品已寄回', 'updated_at' => $now, ]); Db::name('order_timelines')->insert([ 'order_id' => $id, 'node_code' => 'return_shipped', 'node_text' => $nodeText, 'node_desc' => $nodeDesc, 'operator_type' => 'admin', 'operator_id' => (int)$request->header('x-admin-id', 0) ?: null, 'occurred_at' => $now, 'created_at' => $now, ]); (new MessageDispatcher())->sendInboxEvent('return_shipped', [ 'user_id' => (int)($order['user_id'] ?? 0), 'biz_type' => 'return_shipped', 'biz_id' => $id, 'express_company' => $expressCompany, 'tracking_no' => $trackingNo, 'fallback_title' => '鉴定物品已寄回', 'fallback_content' => sprintf('平台已通过%s回寄鉴定物品,运单号 %s,可前往订单详情查看物流进度。', $expressCompany, $trackingNo), ]); Db::commit(); } catch (\Throwable $e) { Db::rollback(); return api_error('回寄运单登记失败', 500, [ 'detail' => $e->getMessage(), ]); } (new EnterpriseWebhookService())->recordOrderEvent($id, 'return_shipped', [ 'express_company' => $expressCompany, 'tracking_no' => $trackingNo, 'shipped_at' => $now, ]); return api_success([ 'id' => $id, 'express_company' => $expressCompany, 'tracking_no' => $trackingNo, ], '回寄运单已登记'); } public function receiveReturnLogistics(Request $request) { $id = (int)$request->input('id', 0); if ($id <= 0) { return api_error('订单 ID 不能为空', 422); } $order = Db::name('orders')->where('id', $id)->find(); if (!$order) { return api_error('订单不存在', 404); } $logistics = Db::name('order_logistics') ->where('order_id', $id) ->where('logistics_type', 'return_to_user') ->order('id', 'desc') ->find(); if (!$logistics || $logistics['tracking_no'] === '') { return api_error('当前订单还没有有效回寄运单', 422); } if (($logistics['tracking_status'] ?? '') === 'received') { return api_error('当前订单已标记用户签收,无需重复操作', 422); } $now = date('Y-m-d H:i:s'); $latestDesc = '用户已签收回寄商品,本次订单已完成。'; Db::startTrans(); try { Db::name('order_logistics')->where('id', $logistics['id'])->update([ 'tracking_status' => 'received', 'latest_desc' => $latestDesc, 'latest_time' => $now, 'updated_at' => $now, ]); Db::name('order_logistics_nodes')->insert([ 'logistics_id' => $logistics['id'], 'node_time' => $now, 'node_desc' => $latestDesc, 'node_location' => '用户地址', 'created_at' => $now, ]); Db::name('orders')->where('id', $id)->update([ 'order_status' => 'completed', 'display_status' => '已完成', 'updated_at' => $now, ]); Db::name('order_timelines')->insert([ 'order_id' => $id, 'node_code' => 'return_received', 'node_text' => '用户已签收', 'node_desc' => '回寄商品已由用户签收,本次订单已完成。', 'operator_type' => 'admin', 'operator_id' => (int)$request->header('x-admin-id', 0) ?: null, 'occurred_at' => $now, 'created_at' => $now, ]); (new MessageDispatcher())->sendInboxEvent('return_received', [ 'user_id' => (int)($order['user_id'] ?? 0), 'biz_type' => 'return_received', 'biz_id' => $id, 'fallback_title' => '回寄商品已签收', 'fallback_content' => '系统已确认您签收回寄商品,本次鉴定订单已完成。', ]); Db::commit(); } catch (\Throwable $e) { Db::rollback(); return api_error('标记用户签收失败', 500, [ 'detail' => $e->getMessage(), ]); } (new EnterpriseWebhookService())->recordOrderEvent($id, 'completed', [ 'express_company' => (string)($logistics['express_company'] ?? ''), 'tracking_no' => (string)($logistics['tracking_no'] ?? ''), 'completed_at' => $now, ]); return api_success(['id' => $id], '已标记用户签收'); } private function trackingStatusText(string $status, string $logisticsType = 'send_to_center'): string { if ($logisticsType === 'return_to_user') { return match ($status) { 'submitted' => '已登记回寄运单', 'in_transit' => '回寄途中', 'received' => '用户已签收', default => $status === '' ? '待回寄' : $status, }; } return match ($status) { 'submitted' => '用户已提交运单', 'in_transit' => '用户已寄出,运输中', 'received' => '鉴定中心已签收', default => $status === '' ? '待提交' : $status, }; } private function reportStatusText(string $status): string { return match ($status) { 'draft' => '草稿中', 'pending_publish' => '待发布', 'published' => '已发布', 'updated' => '已更新', 'invalid' => '已作废', default => $status, }; } private function displayStatus(string $orderStatus, string $displayStatus, string $returnTrackingNo = '', string $returnTrackingStatus = ''): string { if ($orderStatus === 'report_published') { return '待寄回'; } if ($orderStatus === 'completed') { if ($returnTrackingStatus === 'received') { return '已完成'; } if ($returnTrackingNo !== '') { return '物品已寄回'; } } return $displayStatus; } private function latestLogisticsMap(array $orderIds, string $logisticsType): array { $orderIds = array_values(array_unique(array_filter(array_map('intval', $orderIds)))); if (!$orderIds) { return []; } $rows = Db::name('order_logistics') ->whereIn('order_id', $orderIds) ->where('logistics_type', $logisticsType) ->order('id', 'desc') ->select() ->toArray(); $map = []; foreach ($rows as $row) { $orderId = (int)($row['order_id'] ?? 0); if ($orderId > 0 && !isset($map[$orderId])) { $map[$orderId] = [ 'tracking_no' => (string)($row['tracking_no'] ?? ''), 'tracking_status' => (string)($row['tracking_status'] ?? ''), ]; } } return $map; } private function warehouseOrderBucket( string $orderStatus, string $sendTrackingNo = '', string $sendTrackingStatus = '', string $displayStatus = '' ): string { if ($orderStatus === 'pending_shipping') { $hasSubmittedTracking = $sendTrackingNo !== '' && $sendTrackingStatus !== 'received'; $hasSubmittedDisplayStatus = in_array($displayStatus, ['已提交运单', '用户已提交运单'], true) && $sendTrackingStatus !== 'received'; if ($hasSubmittedTracking || $hasSubmittedDisplayStatus) { return 'warehouse_in_transit'; } } if (in_array($orderStatus, [ 'received', 'in_first_review', 'pending_supplement', 'in_final_review', 'generating_report', ], true)) { return 'warehouse_received'; } if ($orderStatus === 'report_published') { return 'warehouse_pending_return'; } return ''; } private function warehouseOrderBucketText(string $bucket): string { return match ($bucket) { 'warehouse_in_transit' => '在途', 'warehouse_received' => '已入仓', 'warehouse_pending_return' => '待寄回', default => '', }; } private function normalizeOrderSourceChannel(string $sourceChannel): string { $sourceChannel = trim($sourceChannel); $aliases = [ 'wechat_mini_program' => 'mini_program', 'weixin_mini_program' => 'mini_program', 'mp_weixin' => 'mini_program', 'miniapp' => 'mini_program', 'user_app' => 'mini_program', 'web_h5' => 'h5', 'enterprise' => 'enterprise_push', 'enterprise_order' => 'enterprise_push', 'customer_push' => 'enterprise_push', 'large_customer_push' => 'enterprise_push', ]; $sourceChannel = $aliases[$sourceChannel] ?? $sourceChannel; return in_array($sourceChannel, ['mini_program', 'h5', 'enterprise_push'], true) ? $sourceChannel : ''; } private function sourceChannelText(string $sourceChannel): string { return match ($this->normalizeOrderSourceChannel($sourceChannel)) { 'mini_program' => '小程序', 'h5' => 'H5', 'enterprise_push' => '大客户推送订单', default => '未知渠道', }; } private function formatAdminLogisticsDesc(string $logisticsType, string $status, string $expressCompany, string $trackingNo, string $fallback): string { $expressCompany = trim($expressCompany); $trackingNo = trim($trackingNo); if ($logisticsType === 'return_to_user') { if (in_array($status, ['submitted', 'in_transit'], true) && $expressCompany !== '' && $trackingNo !== '') { return sprintf('平台已登记回寄运单:%s %s,商品正在回寄途中。', $expressCompany, $trackingNo); } if ($status === 'received') { return '用户已签收回寄商品,订单已完成。'; } return $fallback; } if ($status === 'submitted' && $expressCompany !== '' && $trackingNo !== '') { return sprintf('用户已提交寄送运单:%s %s,等待鉴定中心签收。', $expressCompany, $trackingNo); } if ($status === 'in_transit' && $expressCompany !== '' && $trackingNo !== '') { return sprintf('用户已寄出商品:%s %s,当前运输中。', $expressCompany, $trackingNo); } if ($status === 'received') { return '鉴定中心已签收包裹,等待鉴定师开始处理。'; } return $fallback; } }