1090 lines
49 KiB
PHP
1090 lines
49 KiB
PHP
<?php
|
||
|
||
namespace app\support;
|
||
|
||
use support\Request;
|
||
use support\think\Db;
|
||
|
||
class FulfillmentFlowService
|
||
{
|
||
public function lookupInboundByTrackingNo(string $trackingNo): array
|
||
{
|
||
return $this->lookupInboundByInboundNo($trackingNo);
|
||
}
|
||
|
||
public function lookupInboundByInboundNo(string $inboundNo): array
|
||
{
|
||
$match = $this->resolveInboundOrder($inboundNo);
|
||
|
||
return $this->formatOrderContext((int)$match['order_id']);
|
||
}
|
||
|
||
public function receiveInbound(string $inboundNo, string $tagNo, Request $request, mixed $attachments = []): array
|
||
{
|
||
$operator = $this->operator($request);
|
||
$match = $this->resolveInboundOrder($inboundNo);
|
||
$context = $this->formatOrderContext((int)$match['order_id']);
|
||
$order = $context['order_info'];
|
||
$orderId = (int)$order['id'];
|
||
|
||
if (!in_array((string)$order['order_status'], ['pending_shipping', 'received'], true)) {
|
||
throw new \InvalidArgumentException('当前订单状态不支持入库绑定');
|
||
}
|
||
|
||
$tagNo = $this->normalizeTagNo($tagNo);
|
||
if ($tagNo === '') {
|
||
throw new \InvalidArgumentException('请扫描或输入内部流转挂牌编号');
|
||
}
|
||
|
||
$now = date('Y-m-d H:i:s');
|
||
$inboundAttachments = (new AppraisalEvidenceService())->normalize($attachments, $request, true);
|
||
$attachmentCount = count($inboundAttachments);
|
||
$inboundRemark = $this->inboundMatchRemark($match);
|
||
if ($attachmentCount > 0) {
|
||
$inboundRemark .= sprintf(',已上传拆包附件 %d 个', $attachmentCount);
|
||
}
|
||
|
||
Db::startTrans();
|
||
try {
|
||
$tag = $this->ensureTransferTag($tagNo, $operator, $now);
|
||
$activeFlow = $this->findActiveFlowByTagId((int)$tag['id']);
|
||
if ($activeFlow && (int)$activeFlow['order_id'] !== $orderId) {
|
||
Db::rollback();
|
||
throw new \InvalidArgumentException('该内部流转挂牌已绑定其他未结束订单');
|
||
}
|
||
|
||
$flow = Db::name('order_transfer_flows')
|
||
->where('order_id', $orderId)
|
||
->where('flow_status', '<>', 'ended')
|
||
->order('id', 'desc')
|
||
->find();
|
||
|
||
if ($flow && (int)$flow['internal_tag_id'] !== (int)$tag['id']) {
|
||
Db::rollback();
|
||
throw new \InvalidArgumentException('当前订单已绑定其他内部流转挂牌');
|
||
}
|
||
|
||
if (!$flow) {
|
||
$flowId = (int)Db::name('order_transfer_flows')->insertGetId([
|
||
'order_id' => $orderId,
|
||
'internal_tag_id' => (int)$tag['id'],
|
||
'internal_tag_no' => $tagNo,
|
||
'service_provider' => (string)$order['service_provider'],
|
||
'flow_status' => 'active',
|
||
'current_stage' => 'warehouse_received',
|
||
'current_location' => 'warehouse_pending_inspection',
|
||
'inbound_by' => $operator['id'],
|
||
'inbound_by_name' => $operator['name'],
|
||
'inbound_at' => $now,
|
||
'created_at' => $now,
|
||
'updated_at' => $now,
|
||
]);
|
||
$flow = Db::name('order_transfer_flows')->where('id', $flowId)->find();
|
||
} else {
|
||
Db::name('order_transfer_flows')->where('id', (int)$flow['id'])->update([
|
||
'current_stage' => 'warehouse_received',
|
||
'current_location' => 'warehouse_pending_inspection',
|
||
'inbound_by' => $flow['inbound_by'] ?: $operator['id'],
|
||
'inbound_by_name' => $flow['inbound_by_name'] ?: $operator['name'],
|
||
'inbound_at' => $flow['inbound_at'] ?: $now,
|
||
'updated_at' => $now,
|
||
]);
|
||
$flow = Db::name('order_transfer_flows')->where('id', (int)$flow['id'])->find();
|
||
}
|
||
|
||
Db::name('internal_transfer_tags')->where('id', (int)$tag['id'])->update([
|
||
'bind_status' => 'bound',
|
||
'current_order_id' => $orderId,
|
||
'current_flow_id' => (int)$flow['id'],
|
||
'current_stage' => 'warehouse_received',
|
||
'current_location' => 'warehouse_pending_inspection',
|
||
'bound_by' => $operator['id'],
|
||
'bound_by_name' => $operator['name'],
|
||
'bound_at' => $now,
|
||
'released_by' => null,
|
||
'released_by_name' => '',
|
||
'released_at' => null,
|
||
'updated_at' => $now,
|
||
]);
|
||
|
||
$logistics = Db::name('order_logistics')
|
||
->where('order_id', $orderId)
|
||
->where('logistics_type', 'send_to_center')
|
||
->order('id', 'desc')
|
||
->when(($match['match_type'] ?? '') === 'tracking_no', fn ($query) => $query->where('tracking_no', (string)$match['match_no']))
|
||
->find();
|
||
if ($logistics) {
|
||
Db::name('order_logistics')->where('id', (int)$logistics['id'])->update([
|
||
'tracking_status' => 'received',
|
||
'latest_desc' => '仓库已扫描寄入包裹并完成入库。',
|
||
'latest_time' => $now,
|
||
'updated_at' => $now,
|
||
]);
|
||
Db::name('order_logistics_nodes')->insert([
|
||
'logistics_id' => (int)$logistics['id'],
|
||
'node_time' => $now,
|
||
'node_desc' => '仓库已扫描寄入包裹并完成入库。',
|
||
'node_location' => '仓库',
|
||
'created_at' => $now,
|
||
]);
|
||
}
|
||
|
||
Db::name('orders')->where('id', $orderId)->update([
|
||
'order_status' => 'received',
|
||
'display_status' => '已入仓待检',
|
||
'updated_at' => $now,
|
||
]);
|
||
|
||
$this->insertTimeline($orderId, 'inbound_received', '已入仓待检', '仓管扫描寄入运单并完成物品入库。', $operator, $now);
|
||
$this->insertFlowLog($flow, 'inbound_received', '入库完成', '', '', 'warehouse_received', 'warehouse_pending_inspection', $operator, $inboundRemark, $now, [
|
||
'match_type' => (string)($match['match_type'] ?? ''),
|
||
'match_no' => (string)($match['match_no'] ?? ''),
|
||
'inbound_attachments' => $inboundAttachments,
|
||
]);
|
||
$this->insertFlowLog($flow, 'internal_tag_bound', '绑定内部流转挂牌', 'warehouse_received', 'warehouse_pending_inspection', 'warehouse_received', 'warehouse_pending_inspection', $operator, $tagNo, $now);
|
||
|
||
Db::commit();
|
||
} catch (\Throwable $e) {
|
||
Db::rollback();
|
||
throw $e;
|
||
}
|
||
|
||
return $this->formatOrderContext($orderId, $request);
|
||
}
|
||
|
||
public function scanTransferForAppraisal(string $tagNo, Request $request): array
|
||
{
|
||
$operator = $this->operator($request);
|
||
$flow = $this->findActiveFlowByTagNo($tagNo);
|
||
if (!$flow) {
|
||
throw new \RuntimeException('未找到可用的内部流转挂牌', 404);
|
||
}
|
||
|
||
$order = Db::name('orders')->where('id', (int)$flow['order_id'])->find();
|
||
if (!$order) {
|
||
throw new \RuntimeException('订单不存在', 404);
|
||
}
|
||
|
||
$serviceProvider = (string)($order['service_provider'] ?? '');
|
||
$stage = (string)($flow['current_stage'] ?? '');
|
||
if ($serviceProvider === 'zhongjian' && !in_array($stage, ['zhongjian_returned', 'appraising'], true)) {
|
||
throw new \InvalidArgumentException('中检订单需完成送检入库后才能录入报告');
|
||
}
|
||
if ($serviceProvider !== 'zhongjian' && !in_array($stage, ['warehouse_received', 'appraising'], true)) {
|
||
throw new \InvalidArgumentException('当前流转状态不支持进入鉴定作业');
|
||
}
|
||
|
||
$task = Db::name('appraisal_tasks')
|
||
->where('order_id', (int)$flow['order_id'])
|
||
->where('task_stage', 'first_review')
|
||
->order('id', 'asc')
|
||
->find();
|
||
if (!$task) {
|
||
throw new \RuntimeException('鉴定任务不存在', 404);
|
||
}
|
||
|
||
$now = date('Y-m-d H:i:s');
|
||
Db::startTrans();
|
||
try {
|
||
$taskUpdate = [
|
||
'status' => 'processing',
|
||
'started_at' => $task['started_at'] ?: $now,
|
||
'updated_at' => $now,
|
||
];
|
||
if (empty($task['assignee_id']) || empty($task['assignee_name']) || $task['assignee_name'] === '未分配') {
|
||
$taskUpdate['assignee_id'] = $operator['id'];
|
||
$taskUpdate['assignee_name'] = $operator['name'];
|
||
}
|
||
Db::name('appraisal_tasks')->where('id', (int)$task['id'])->update($taskUpdate);
|
||
|
||
Db::name('orders')->where('id', (int)$flow['order_id'])->update([
|
||
'order_status' => 'in_first_review',
|
||
'display_status' => $serviceProvider === 'zhongjian' ? '中检报告录入中' : '鉴定中',
|
||
'updated_at' => $now,
|
||
]);
|
||
|
||
$this->updateFlowStage($flow, 'appraising', $serviceProvider === 'zhongjian' ? 'zhongjian_report_entry' : 'appraiser_workbench', [
|
||
'appraisal_started_by' => $flow['appraisal_started_by'] ?: $operator['id'],
|
||
'appraisal_started_by_name' => $flow['appraisal_started_by_name'] ?: $operator['name'],
|
||
'appraisal_started_at' => $flow['appraisal_started_at'] ?: $now,
|
||
], $now);
|
||
|
||
$this->insertTimeline((int)$flow['order_id'], 'appraisal_started', $serviceProvider === 'zhongjian' ? '中检报告录入中' : '鉴定中', $serviceProvider === 'zhongjian' ? '报告录入人已扫描内部流转码进入中检报告录入。' : '鉴定师已扫描内部流转码进入鉴定作业。', $operator, $now);
|
||
$this->insertFlowLog($flow, 'appraisal_started', $serviceProvider === 'zhongjian' ? '中检报告录入开始' : '鉴定开始', (string)$flow['current_stage'], (string)$flow['current_location'], 'appraising', $serviceProvider === 'zhongjian' ? 'zhongjian_report_entry' : 'appraiser_workbench', $operator, '', $now);
|
||
|
||
Db::commit();
|
||
} catch (\Throwable $e) {
|
||
Db::rollback();
|
||
throw $e;
|
||
}
|
||
|
||
return [
|
||
'task_id' => (int)$task['id'],
|
||
'order_id' => (int)$flow['order_id'],
|
||
'service_provider' => $serviceProvider,
|
||
'service_provider_text' => $serviceProvider === 'zhongjian' ? '中检鉴定' : '实物鉴定',
|
||
];
|
||
}
|
||
|
||
public function lookupZhongjianTransfer(string $tagNo): array
|
||
{
|
||
$flow = $this->findActiveFlowByTagNo($tagNo);
|
||
if (!$flow) {
|
||
throw new \RuntimeException('未找到可用的内部流转挂牌', 404);
|
||
}
|
||
if (($flow['service_provider'] ?? '') !== 'zhongjian') {
|
||
throw new \InvalidArgumentException('非中检订单不能进行送检出入库');
|
||
}
|
||
|
||
$stage = (string)$flow['current_stage'];
|
||
$nextAction = match ($stage) {
|
||
'warehouse_received' => 'outbound',
|
||
'sent_to_zhongjian' => 'inbound',
|
||
default => '',
|
||
};
|
||
$nextActionText = match ($stage) {
|
||
'warehouse_received' => '送检出库',
|
||
'sent_to_zhongjian' => '送检入库',
|
||
'report_published' => '待寄回订单可填写回寄物流',
|
||
default => '暂无可执行送检动作',
|
||
};
|
||
|
||
return array_merge($this->formatOrderContext((int)$flow['order_id']), [
|
||
'next_action' => $nextAction,
|
||
'next_action_text' => $nextActionText,
|
||
]);
|
||
}
|
||
|
||
public function zhongjianOutbound(string $tagNo, Request $request): array
|
||
{
|
||
return $this->moveZhongjian($tagNo, 'warehouse_received', 'sent_to_zhongjian', 'zhongjian_institution', 'zhongjian_outbound', '送检出库', '仓管扫描内部流转码,物品已送出至中检机构。', $request);
|
||
}
|
||
|
||
public function zhongjianInbound(string $tagNo, Request $request): array
|
||
{
|
||
return $this->moveZhongjian($tagNo, 'sent_to_zhongjian', 'zhongjian_returned', 'warehouse_pending_report_entry', 'zhongjian_inbound', '送检入库', '仓管扫描内部流转码,中检物品已回收入库。', $request);
|
||
}
|
||
|
||
public function lookupReturn(string $tagNo, Request $request): array
|
||
{
|
||
$flow = $this->findActiveFlowByTagNo($tagNo);
|
||
if (!$flow) {
|
||
throw new \RuntimeException('未找到可用的内部流转挂牌', 404);
|
||
}
|
||
|
||
$context = $this->formatOrderContext((int)$flow['order_id'], $request);
|
||
$report = $context['report_info'] ?? null;
|
||
if (!$report || ($report['report_status'] ?? '') !== 'published') {
|
||
throw new \InvalidArgumentException('该报告未发布,不符合寄回条件');
|
||
}
|
||
$this->ensurePendingReturnOrder($flow);
|
||
|
||
return $context + [
|
||
'return_confirmation' => [
|
||
'confirmed' => (string)($flow['current_stage'] ?? '') === 'return_confirmed',
|
||
'confirmed_by_name' => (string)($flow['return_confirmed_by_name'] ?? ''),
|
||
'confirmed_at' => (string)($flow['return_confirmed_at'] ?? ''),
|
||
],
|
||
];
|
||
}
|
||
|
||
public function verifyReturnMaterialTag(string $tagNo, string $qrInput, Request $request): array
|
||
{
|
||
$flow = $this->findActiveFlowByTagNo($tagNo);
|
||
if (!$flow) {
|
||
throw new \RuntimeException('未找到可用的内部流转挂牌', 404);
|
||
}
|
||
$report = $this->latestReport((int)$flow['order_id']);
|
||
if (!$report || ($report['report_status'] ?? '') !== 'published') {
|
||
throw new \InvalidArgumentException('该报告未发布,不符合寄回条件');
|
||
}
|
||
$this->ensurePendingReturnOrder($flow);
|
||
|
||
$tag = (new MaterialTagService())->findTagByInput($qrInput);
|
||
if (!$tag || (int)($tag['report_id'] ?? 0) !== (int)$report['id']) {
|
||
throw new \InvalidArgumentException('验真吊牌与当前订单报告不匹配');
|
||
}
|
||
|
||
return array_merge($this->formatOrderContext((int)$flow['order_id'], $request), [
|
||
'return_verification' => [
|
||
'verified' => true,
|
||
'report_id' => (int)$report['id'],
|
||
'report_no' => (string)$report['report_no'],
|
||
],
|
||
]);
|
||
}
|
||
|
||
public function confirmZhongjianReturn(string $tagNo, Request $request): array
|
||
{
|
||
$operator = $this->operator($request);
|
||
$flow = $this->findActiveFlowByTagNo($tagNo);
|
||
if (!$flow) {
|
||
throw new \RuntimeException('未找到可用的内部流转挂牌', 404);
|
||
}
|
||
if (($flow['service_provider'] ?? '') !== 'zhongjian') {
|
||
throw new \InvalidArgumentException('非中检订单需扫描平台验真吊牌确认');
|
||
}
|
||
|
||
$report = $this->latestReport((int)$flow['order_id']);
|
||
$content = $report ? Db::name('report_contents')->where('report_id', (int)$report['id'])->find() : null;
|
||
$files = $this->decodeJsonArray($content['zhongjian_report_files_json'] ?? null);
|
||
if (!$report || ($report['report_status'] ?? '') !== 'published') {
|
||
throw new \InvalidArgumentException('该报告未发布,不符合寄回条件');
|
||
}
|
||
if (trim((string)($report['zhongjian_report_no'] ?? '')) === '' || !$files) {
|
||
throw new \InvalidArgumentException('中检报告未完整录入,不能确认寄回');
|
||
}
|
||
|
||
return $this->markReturnConfirmed($flow, $operator, 'return_confirmed', '中检报告已确认', '仓管已查看中检报告编号和报告文件。');
|
||
}
|
||
|
||
public function confirmReturnReport(string $tagNo, int $reportId, Request $request): array
|
||
{
|
||
$operator = $this->operator($request);
|
||
$flow = $this->findActiveFlowByTagNo($tagNo);
|
||
if (!$flow) {
|
||
throw new \RuntimeException('未找到可用的内部流转挂牌', 404);
|
||
}
|
||
|
||
$report = $this->latestReport((int)$flow['order_id']);
|
||
if (!$report || ($report['report_status'] ?? '') !== 'published') {
|
||
throw new \InvalidArgumentException('该报告未发布,不符合寄回条件');
|
||
}
|
||
$this->ensurePendingReturnOrder($flow);
|
||
if ((int)$report['id'] !== $reportId) {
|
||
throw new \InvalidArgumentException('确认的报告与当前订单报告不匹配');
|
||
}
|
||
if ((string)($flow['current_stage'] ?? '') === 'return_confirmed') {
|
||
return $this->formatOrderContext((int)$flow['order_id'], $request);
|
||
}
|
||
|
||
return $this->markReturnConfirmed($flow, $operator, 'return_confirmed', '回寄确认', '仓管扫描内部流转码确认订单处于待寄回状态。');
|
||
}
|
||
|
||
public function shipReturn(string $tagNo, string $expressCompany, string $trackingNo, Request $request, array $packingAttachments = []): array
|
||
{
|
||
$operator = $this->operator($request);
|
||
$flow = $this->findActiveFlowByTagNo($tagNo);
|
||
if (!$flow) {
|
||
throw new \RuntimeException('未找到可用的内部流转挂牌', 404);
|
||
}
|
||
$report = $this->latestReport((int)$flow['order_id']);
|
||
if (!$report || ($report['report_status'] ?? '') !== 'published') {
|
||
throw new \InvalidArgumentException('该报告未发布,不符合寄回条件');
|
||
}
|
||
if ((string)($flow['current_stage'] ?? '') !== 'return_confirmed') {
|
||
throw new \InvalidArgumentException('请先完成报告确认,再登记回寄运单');
|
||
}
|
||
|
||
$expressCompany = trim($expressCompany);
|
||
$trackingNo = trim($trackingNo);
|
||
if ($expressCompany === '' || $trackingNo === '') {
|
||
throw new \InvalidArgumentException('请填写回寄快递公司和运单号');
|
||
}
|
||
$packingAttachments = $this->normalizeAssetList($packingAttachments, $request);
|
||
foreach ($packingAttachments as $asset) {
|
||
if (!in_array((string)($asset['file_type'] ?? ''), ['image', 'video'], true)) {
|
||
throw new \InvalidArgumentException('打包装箱附件仅支持图片或视频');
|
||
}
|
||
}
|
||
|
||
$orderId = (int)$flow['order_id'];
|
||
$order = Db::name('orders')->where('id', $orderId)->find();
|
||
if (!$order) {
|
||
throw new \RuntimeException('订单不存在', 404);
|
||
}
|
||
|
||
$returnAddress = $this->returnAddressForOrder($order);
|
||
if (!$returnAddress) {
|
||
throw new \InvalidArgumentException('当前订单尚未确认寄回地址,且用户账户下没有可用地址');
|
||
}
|
||
|
||
$now = date('Y-m-d H:i:s');
|
||
$latestDesc = sprintf('平台已通过 %s 回寄商品,运单号 %s。', $expressCompany, $trackingNo);
|
||
|
||
Db::startTrans();
|
||
try {
|
||
$this->ensureReturnAddressSnapshot($orderId, $returnAddress, $now);
|
||
|
||
$existing = Db::name('order_logistics')
|
||
->where('order_id', $orderId)
|
||
->where('logistics_type', 'return_to_user')
|
||
->order('id', 'desc')
|
||
->find();
|
||
if ($existing) {
|
||
Db::name('order_logistics')->where('id', (int)$existing['id'])->update([
|
||
'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' => $orderId,
|
||
'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', $orderId)->update([
|
||
'order_status' => 'completed',
|
||
'display_status' => '物品已寄回',
|
||
'updated_at' => $now,
|
||
]);
|
||
|
||
Db::name('order_transfer_flows')->where('id', (int)$flow['id'])->update([
|
||
'flow_status' => 'ended',
|
||
'current_stage' => 'return_shipped',
|
||
'current_location' => 'ended',
|
||
'return_shipped_by' => $operator['id'],
|
||
'return_shipped_by_name' => $operator['name'],
|
||
'return_shipped_at' => $now,
|
||
'ended_at' => $now,
|
||
'updated_at' => $now,
|
||
]);
|
||
|
||
Db::name('internal_transfer_tags')->where('id', (int)$flow['internal_tag_id'])->update([
|
||
'bind_status' => 'released',
|
||
'current_order_id' => null,
|
||
'current_flow_id' => null,
|
||
'current_stage' => 'idle',
|
||
'current_location' => 'warehouse',
|
||
'released_by' => $operator['id'],
|
||
'released_by_name' => $operator['name'],
|
||
'released_at' => $now,
|
||
'updated_at' => $now,
|
||
]);
|
||
|
||
$this->insertTimeline($orderId, 'return_shipped', $nodeText, $nodeDesc, $operator, $now);
|
||
$this->insertFlowLog($flow, 'return_shipped', '物品寄回', 'return_confirmed', (string)$flow['current_location'], 'return_shipped', 'ended', $operator, $trackingNo, $now, [
|
||
'packing_attachments' => $packingAttachments,
|
||
]);
|
||
|
||
(new MessageDispatcher())->sendInboxEvent('return_shipped', [
|
||
'user_id' => (int)($order['user_id'] ?? 0),
|
||
'biz_type' => 'return_shipped',
|
||
'biz_id' => $orderId,
|
||
'express_company' => $expressCompany,
|
||
'tracking_no' => $trackingNo,
|
||
'fallback_title' => '鉴定物品已寄回',
|
||
'fallback_content' => sprintf('平台已通过%s回寄鉴定物品,运单号 %s,可前往订单详情查看物流进度。', $expressCompany, $trackingNo),
|
||
]);
|
||
|
||
Db::commit();
|
||
} catch (\Throwable $e) {
|
||
Db::rollback();
|
||
throw $e;
|
||
}
|
||
|
||
(new EnterpriseWebhookService())->recordOrderEvent($orderId, 'return_shipped', [
|
||
'express_company' => $expressCompany,
|
||
'tracking_no' => $trackingNo,
|
||
'shipped_at' => $now,
|
||
]);
|
||
(new OrderLogisticsSyncService())->subscribeAsync($logisticsId);
|
||
|
||
return $this->formatOrderContext($orderId);
|
||
}
|
||
|
||
public function markReportPublished(int $orderId, Request $request): void
|
||
{
|
||
$flow = Db::name('order_transfer_flows')
|
||
->where('order_id', $orderId)
|
||
->where('flow_status', '<>', 'ended')
|
||
->order('id', 'desc')
|
||
->find();
|
||
if (!$flow) {
|
||
return;
|
||
}
|
||
|
||
$operator = $this->operator($request);
|
||
$now = date('Y-m-d H:i:s');
|
||
$this->updateFlowStage($flow, 'report_published', 'warehouse_return_pending', [
|
||
'report_published_by' => $operator['id'],
|
||
'report_published_by_name' => $operator['name'],
|
||
'report_published_at' => $now,
|
||
], $now);
|
||
$this->insertFlowLog($flow, 'report_published', '报告已发布', (string)$flow['current_stage'], (string)$flow['current_location'], 'report_published', 'warehouse_return_pending', $operator, '', $now);
|
||
}
|
||
|
||
public function formatOrderContext(int $orderId, ?Request $request = null): array
|
||
{
|
||
$order = Db::name('orders')->where('id', $orderId)->find();
|
||
if (!$order) {
|
||
throw new \RuntimeException('订单不存在', 404);
|
||
}
|
||
$product = Db::name('order_products')->where('order_id', $orderId)->find() ?: [];
|
||
$sendLogistics = Db::name('order_logistics')->where('order_id', $orderId)->where('logistics_type', 'send_to_center')->order('id', 'desc')->find();
|
||
$returnLogistics = Db::name('order_logistics')->where('order_id', $orderId)->where('logistics_type', 'return_to_user')->order('id', 'desc')->find();
|
||
$syncService = new OrderLogisticsSyncService();
|
||
$sendSyncStatus = $sendLogistics ? $syncService->formatSyncStatus((int)$sendLogistics['id']) : ['provider_status_text' => '', 'sync_status_text' => '未同步', 'sync_error' => ''];
|
||
$returnSyncStatus = $returnLogistics ? $syncService->formatSyncStatus((int)$returnLogistics['id']) : ['provider_status_text' => '', 'sync_status_text' => '未同步', 'sync_error' => ''];
|
||
$flow = Db::name('order_transfer_flows')->where('order_id', $orderId)->order('id', 'desc')->find();
|
||
$report = $this->latestReport($orderId);
|
||
$content = $report ? Db::name('report_contents')->where('report_id', (int)$report['id'])->find() : null;
|
||
$returnAddress = $this->returnAddressForOrder($order);
|
||
$flowLogs = $flow ? Db::name('order_transfer_flow_logs')
|
||
->where('flow_id', (int)$flow['id'])
|
||
->order('id', 'asc')
|
||
->select()
|
||
->toArray() : [];
|
||
|
||
return [
|
||
'order_info' => [
|
||
'id' => (int)$order['id'],
|
||
'order_no' => (string)$order['order_no'],
|
||
'appraisal_no' => (string)$order['appraisal_no'],
|
||
'service_provider' => (string)$order['service_provider'],
|
||
'service_provider_text' => $this->serviceProviderText((string)$order['service_provider']),
|
||
'source_channel' => (string)($order['source_channel'] ?? ''),
|
||
'source_channel_text' => $this->sourceChannelText((string)($order['source_channel'] ?? '')),
|
||
'source_customer_id' => (string)($order['source_customer_id'] ?? ''),
|
||
'order_status' => (string)$order['order_status'],
|
||
'display_status' => (string)$order['display_status'],
|
||
],
|
||
'product_info' => [
|
||
'product_name' => (string)($product['product_name'] ?? ''),
|
||
'category_name' => (string)($product['category_name'] ?? ''),
|
||
'brand_name' => (string)($product['brand_name'] ?? ''),
|
||
'color' => (string)($product['color'] ?? ''),
|
||
'size_spec' => (string)($product['size_spec'] ?? ''),
|
||
'serial_no' => (string)($product['serial_no'] ?? ''),
|
||
],
|
||
'logistics_info' => $sendLogistics ? [
|
||
'express_company' => (string)$sendLogistics['express_company'],
|
||
'tracking_no' => (string)$sendLogistics['tracking_no'],
|
||
'tracking_status' => (string)$sendLogistics['tracking_status'],
|
||
'tracking_status_text' => $this->trackingStatusText((string)$sendLogistics['tracking_status'], 'send_to_center'),
|
||
'provider_status_text' => $sendSyncStatus['provider_status_text'],
|
||
'sync_status_text' => $sendSyncStatus['sync_status_text'],
|
||
'sync_error' => $sendSyncStatus['sync_error'],
|
||
'latest_desc' => (string)($sendLogistics['latest_desc'] ?? ''),
|
||
'latest_time' => (string)($sendLogistics['latest_time'] ?? ''),
|
||
'nodes' => array_map(fn (array $item) => [
|
||
'node_time' => (string)$item['node_time'],
|
||
'node_desc' => (string)$item['node_desc'],
|
||
'node_location' => (string)$item['node_location'],
|
||
], $syncService->nodesForLogistics((int)$sendLogistics['id'])),
|
||
] : null,
|
||
'return_address' => $returnAddress ? [
|
||
'consignee' => (string)($returnAddress['consignee'] ?? ''),
|
||
'mobile' => (string)($returnAddress['mobile'] ?? ''),
|
||
'full_address' => trim(sprintf('%s%s%s%s', $returnAddress['province'] ?? '', $returnAddress['city'] ?? '', $returnAddress['district'] ?? '', $returnAddress['detail_address'] ?? '')),
|
||
] : null,
|
||
'return_logistics' => $returnLogistics ? [
|
||
'express_company' => (string)$returnLogistics['express_company'],
|
||
'tracking_no' => (string)$returnLogistics['tracking_no'],
|
||
'tracking_status' => (string)$returnLogistics['tracking_status'],
|
||
'tracking_status_text' => $this->trackingStatusText((string)$returnLogistics['tracking_status'], 'return_to_user'),
|
||
'provider_status_text' => $returnSyncStatus['provider_status_text'],
|
||
'sync_status_text' => $returnSyncStatus['sync_status_text'],
|
||
'sync_error' => $returnSyncStatus['sync_error'],
|
||
'latest_desc' => (string)($returnLogistics['latest_desc'] ?? ''),
|
||
'latest_time' => (string)($returnLogistics['latest_time'] ?? ''),
|
||
'nodes' => array_map(fn (array $item) => [
|
||
'node_time' => (string)$item['node_time'],
|
||
'node_desc' => (string)$item['node_desc'],
|
||
'node_location' => (string)$item['node_location'],
|
||
], $syncService->nodesForLogistics((int)$returnLogistics['id'])),
|
||
] : null,
|
||
'transfer_flow' => $flow ? $this->formatFlow($flow) : null,
|
||
'report_info' => $report ? [
|
||
'id' => (int)$report['id'],
|
||
'report_no' => (string)$report['report_no'],
|
||
'report_title' => (string)$report['report_title'],
|
||
'report_status' => (string)$report['report_status'],
|
||
'publish_time' => (string)($report['publish_time'] ?? ''),
|
||
'zhongjian_report_no' => (string)($report['zhongjian_report_no'] ?? ''),
|
||
'report_entry_admin_name' => (string)($report['report_entry_admin_name'] ?? ''),
|
||
'report_entered_at' => (string)($report['report_entered_at'] ?? ''),
|
||
'zhongjian_report_files' => $this->normalizeAssetList($this->decodeJsonArray($content['zhongjian_report_files_json'] ?? null), $request),
|
||
] : null,
|
||
'flow_logs' => array_map(fn (array $log) => $this->formatFlowLog($log, $request), $flowLogs),
|
||
];
|
||
}
|
||
|
||
private function moveZhongjian(string $tagNo, string $expectedStage, string $nextStage, string $nextLocation, string $actionCode, string $actionText, string $desc, Request $request): array
|
||
{
|
||
$operator = $this->operator($request);
|
||
$flow = $this->findActiveFlowByTagNo($tagNo);
|
||
if (!$flow) {
|
||
throw new \RuntimeException('未找到可用的内部流转挂牌', 404);
|
||
}
|
||
if (($flow['service_provider'] ?? '') !== 'zhongjian') {
|
||
throw new \InvalidArgumentException('非中检订单不能进行送检出入库');
|
||
}
|
||
if ((string)$flow['current_stage'] !== $expectedStage) {
|
||
throw new \InvalidArgumentException('当前流转状态不支持该送检动作');
|
||
}
|
||
|
||
$now = date('Y-m-d H:i:s');
|
||
$auditFields = $actionCode === 'zhongjian_outbound'
|
||
? ['zhongjian_outbound_by' => $operator['id'], 'zhongjian_outbound_by_name' => $operator['name'], 'zhongjian_outbound_at' => $now]
|
||
: ['zhongjian_inbound_by' => $operator['id'], 'zhongjian_inbound_by_name' => $operator['name'], 'zhongjian_inbound_at' => $now];
|
||
|
||
Db::startTrans();
|
||
try {
|
||
$this->updateFlowStage($flow, $nextStage, $nextLocation, $auditFields, $now);
|
||
Db::name('orders')->where('id', (int)$flow['order_id'])->update([
|
||
'display_status' => $actionCode === 'zhongjian_outbound' ? '中检送检中' : '中检待录入报告',
|
||
'updated_at' => $now,
|
||
]);
|
||
$this->insertTimeline((int)$flow['order_id'], $actionCode, $actionText, $desc, $operator, $now);
|
||
$this->insertFlowLog($flow, $actionCode, $actionText, (string)$flow['current_stage'], (string)$flow['current_location'], $nextStage, $nextLocation, $operator, '', $now);
|
||
Db::commit();
|
||
} catch (\Throwable $e) {
|
||
Db::rollback();
|
||
throw $e;
|
||
}
|
||
|
||
return $this->lookupZhongjianTransfer($tagNo);
|
||
}
|
||
|
||
private function markReturnConfirmed(array $flow, array $operator, string $actionCode, string $actionText, string $timelineDesc): array
|
||
{
|
||
$now = date('Y-m-d H:i:s');
|
||
Db::startTrans();
|
||
try {
|
||
$this->updateFlowStage($flow, 'return_confirmed', 'warehouse_return_pending', [
|
||
'return_confirmed_by' => $operator['id'],
|
||
'return_confirmed_by_name' => $operator['name'],
|
||
'return_confirmed_at' => $now,
|
||
], $now);
|
||
$this->insertTimeline((int)$flow['order_id'], $actionCode, $actionText, $timelineDesc, $operator, $now);
|
||
$this->insertFlowLog($flow, $actionCode, $actionText, (string)$flow['current_stage'], (string)$flow['current_location'], 'return_confirmed', 'warehouse_return_pending', $operator, '', $now);
|
||
Db::commit();
|
||
} catch (\Throwable $e) {
|
||
Db::rollback();
|
||
throw $e;
|
||
}
|
||
|
||
return $this->formatOrderContext((int)$flow['order_id']);
|
||
}
|
||
|
||
private function updateFlowStage(array $flow, string $stage, string $location, array $extra, string $now): void
|
||
{
|
||
Db::name('order_transfer_flows')->where('id', (int)$flow['id'])->update(array_merge([
|
||
'current_stage' => $stage,
|
||
'current_location' => $location,
|
||
'updated_at' => $now,
|
||
], $extra));
|
||
|
||
Db::name('internal_transfer_tags')->where('id', (int)$flow['internal_tag_id'])->update([
|
||
'current_stage' => $stage,
|
||
'current_location' => $location,
|
||
'updated_at' => $now,
|
||
]);
|
||
}
|
||
|
||
private function insertTimeline(int $orderId, string $code, string $text, string $desc, array $operator, string $now): void
|
||
{
|
||
Db::name('order_timelines')->insert([
|
||
'order_id' => $orderId,
|
||
'node_code' => $code,
|
||
'node_text' => $text,
|
||
'node_desc' => $desc,
|
||
'operator_type' => 'admin',
|
||
'operator_id' => $operator['id'],
|
||
'occurred_at' => $now,
|
||
'created_at' => $now,
|
||
]);
|
||
}
|
||
|
||
private function insertFlowLog(array $flow, string $code, string $text, string $beforeStage, string $beforeLocation, string $afterStage, string $afterLocation, array $operator, string $remark, string $now, ?array $payload = null): void
|
||
{
|
||
Db::name('order_transfer_flow_logs')->insert([
|
||
'flow_id' => (int)$flow['id'],
|
||
'order_id' => (int)$flow['order_id'],
|
||
'internal_tag_id' => (int)$flow['internal_tag_id'],
|
||
'internal_tag_no' => (string)$flow['internal_tag_no'],
|
||
'action_code' => $code,
|
||
'action_text' => $text,
|
||
'before_stage' => $beforeStage,
|
||
'before_location' => $beforeLocation,
|
||
'after_stage' => $afterStage,
|
||
'after_location' => $afterLocation,
|
||
'operator_id' => $operator['id'],
|
||
'operator_name' => $operator['name'],
|
||
'remark' => mb_substr($remark, 0, 500),
|
||
'payload_json' => $payload ? json_encode($payload, JSON_UNESCAPED_UNICODE) : null,
|
||
'created_at' => $now,
|
||
]);
|
||
}
|
||
|
||
private function formatFlowLog(array $log, ?Request $request): array
|
||
{
|
||
$payload = $this->decodeJsonObject($log['payload_json'] ?? null);
|
||
$attachments = $this->normalizeAssetList($this->decodeJsonArray($payload['inbound_attachments'] ?? []), $request);
|
||
$packingAttachments = $this->normalizeAssetList($this->decodeJsonArray($payload['packing_attachments'] ?? []), $request);
|
||
|
||
return [
|
||
'id' => (int)$log['id'],
|
||
'action_code' => (string)$log['action_code'],
|
||
'action_text' => (string)$log['action_text'],
|
||
'before_stage' => $this->stageText((string)($log['before_stage'] ?? '')),
|
||
'before_location' => $this->locationText((string)($log['before_location'] ?? '')),
|
||
'after_stage' => $this->stageText((string)($log['after_stage'] ?? '')),
|
||
'after_location' => $this->locationText((string)($log['after_location'] ?? '')),
|
||
'operator_name' => (string)($log['operator_name'] ?? ''),
|
||
'remark' => (string)($log['remark'] ?? ''),
|
||
'created_at' => (string)($log['created_at'] ?? ''),
|
||
'inbound_attachments' => $attachments,
|
||
'packing_attachments' => $packingAttachments,
|
||
];
|
||
}
|
||
|
||
private function ensureTransferTag(string $tagNo, array $operator, string $now): array
|
||
{
|
||
$tag = Db::name('internal_transfer_tags')->where('tag_no', $tagNo)->find();
|
||
if ($tag) {
|
||
if (($tag['status'] ?? 'active') === 'invalid') {
|
||
throw new \InvalidArgumentException('该内部流转挂牌已失效');
|
||
}
|
||
return $tag;
|
||
}
|
||
|
||
$id = (int)Db::name('internal_transfer_tags')->insertGetId([
|
||
'tag_no' => $tagNo,
|
||
'status' => 'active',
|
||
'bind_status' => 'free',
|
||
'current_stage' => 'idle',
|
||
'current_location' => 'warehouse',
|
||
'created_by' => $operator['id'],
|
||
'created_by_name' => $operator['name'],
|
||
'created_at' => $now,
|
||
'updated_at' => $now,
|
||
]);
|
||
|
||
return Db::name('internal_transfer_tags')->where('id', $id)->find();
|
||
}
|
||
|
||
private function resolveInboundOrder(string $inboundNo): array
|
||
{
|
||
$inboundNo = trim($inboundNo);
|
||
if ($inboundNo === '') {
|
||
throw new \InvalidArgumentException('请先扫描快递单号或输入鉴定订单号');
|
||
}
|
||
|
||
$logisticsRows = Db::name('order_logistics')
|
||
->where('logistics_type', 'send_to_center')
|
||
->where('tracking_no', $inboundNo)
|
||
->select()
|
||
->toArray();
|
||
if ($logisticsRows) {
|
||
if (count($logisticsRows) > 1) {
|
||
throw new \RuntimeException('该快递单号匹配到多笔订单,请人工核查后处理', 409);
|
||
}
|
||
|
||
return [
|
||
'order_id' => (int)$logisticsRows[0]['order_id'],
|
||
'match_type' => 'tracking_no',
|
||
'match_no' => $inboundNo,
|
||
];
|
||
}
|
||
|
||
$orderRows = Db::name('orders')
|
||
->where(function ($builder) use ($inboundNo) {
|
||
$builder->whereRaw(
|
||
'(order_no = :order_no OR appraisal_no = :appraisal_no)',
|
||
[
|
||
'order_no' => $inboundNo,
|
||
'appraisal_no' => $inboundNo,
|
||
]
|
||
);
|
||
})
|
||
->field(['id', 'order_no', 'appraisal_no'])
|
||
->select()
|
||
->toArray();
|
||
if ($orderRows) {
|
||
if (count($orderRows) > 1) {
|
||
throw new \RuntimeException('该订单号匹配到多笔订单,请人工核查后处理', 409);
|
||
}
|
||
$order = $orderRows[0];
|
||
|
||
return [
|
||
'order_id' => (int)$order['id'],
|
||
'match_type' => $inboundNo === (string)($order['appraisal_no'] ?? '') ? 'appraisal_no' : 'order_no',
|
||
'match_no' => $inboundNo,
|
||
];
|
||
}
|
||
|
||
$externalRows = Db::name('enterprise_customer_order_refs')
|
||
->where('external_order_no', $inboundNo)
|
||
->field(['order_id'])
|
||
->select()
|
||
->toArray();
|
||
if ($externalRows) {
|
||
if (count($externalRows) > 1) {
|
||
throw new \RuntimeException('该外部订单号匹配到多笔订单,请人工核查后处理', 409);
|
||
}
|
||
|
||
return [
|
||
'order_id' => (int)$externalRows[0]['order_id'],
|
||
'match_type' => 'external_order_no',
|
||
'match_no' => $inboundNo,
|
||
];
|
||
}
|
||
|
||
throw new \RuntimeException('未匹配到待入库订单', 404);
|
||
}
|
||
|
||
private function inboundMatchRemark(array $match): string
|
||
{
|
||
$label = match ((string)($match['match_type'] ?? '')) {
|
||
'tracking_no' => '快递单号',
|
||
'appraisal_no' => '鉴定单号',
|
||
'order_no' => '订单号',
|
||
'external_order_no' => '外部订单号',
|
||
default => '入库编号',
|
||
};
|
||
|
||
return sprintf('扫描%s入库:%s', $label, (string)($match['match_no'] ?? ''));
|
||
}
|
||
|
||
private function findActiveFlowByTagNo(string $tagNo): ?array
|
||
{
|
||
$tagNo = $this->normalizeTagNo($tagNo);
|
||
if ($tagNo === '') {
|
||
throw new \InvalidArgumentException('请扫描内部流转挂牌编号');
|
||
}
|
||
|
||
return Db::name('order_transfer_flows')
|
||
->where('internal_tag_no', $tagNo)
|
||
->where('flow_status', '<>', 'ended')
|
||
->order('id', 'desc')
|
||
->find() ?: null;
|
||
}
|
||
|
||
private function findActiveFlowByTagId(int $tagId): ?array
|
||
{
|
||
return Db::name('order_transfer_flows')
|
||
->where('internal_tag_id', $tagId)
|
||
->where('flow_status', '<>', 'ended')
|
||
->order('id', 'desc')
|
||
->find() ?: null;
|
||
}
|
||
|
||
private function latestReport(int $orderId): ?array
|
||
{
|
||
return Db::name('reports')
|
||
->where('order_id', $orderId)
|
||
->where('report_type', 'appraisal')
|
||
->order('id', 'desc')
|
||
->find() ?: null;
|
||
}
|
||
|
||
private function returnAddressForOrder(array $order): ?array
|
||
{
|
||
$orderId = (int)($order['id'] ?? 0);
|
||
$address = Db::name('order_return_addresses')->where('order_id', $orderId)->find();
|
||
if ($address) {
|
||
return $address;
|
||
}
|
||
|
||
return Db::name('user_addresses')
|
||
->where('user_id', (int)($order['user_id'] ?? 0))
|
||
->where('is_default', 1)
|
||
->order('id', 'desc')
|
||
->find()
|
||
?: Db::name('user_addresses')
|
||
->where('user_id', (int)($order['user_id'] ?? 0))
|
||
->order('id', 'desc')
|
||
->find()
|
||
?: null;
|
||
}
|
||
|
||
private function ensureReturnAddressSnapshot(int $orderId, array $address, string $now): void
|
||
{
|
||
if (Db::name('order_return_addresses')->where('order_id', $orderId)->find()) {
|
||
return;
|
||
}
|
||
|
||
Db::name('order_return_addresses')->insert([
|
||
'order_id' => $orderId,
|
||
'user_address_id' => $address['user_address_id'] ?? ($address['id'] ?? null),
|
||
'consignee' => $address['consignee'] ?? '',
|
||
'mobile' => $address['mobile'] ?? '',
|
||
'province' => $address['province'] ?? '',
|
||
'city' => $address['city'] ?? '',
|
||
'district' => $address['district'] ?? '',
|
||
'detail_address' => $address['detail_address'] ?? '',
|
||
'created_at' => $now,
|
||
'updated_at' => $now,
|
||
]);
|
||
}
|
||
|
||
private function ensurePendingReturnOrder(array $flow): array
|
||
{
|
||
$order = Db::name('orders')->where('id', (int)$flow['order_id'])->find();
|
||
if (!$order) {
|
||
throw new \RuntimeException('订单不存在', 404);
|
||
}
|
||
if ((string)($order['order_status'] ?? '') !== 'report_published') {
|
||
throw new \InvalidArgumentException('当前订单不处于待寄回状态');
|
||
}
|
||
|
||
return $order;
|
||
}
|
||
|
||
private function operator(Request $request): array
|
||
{
|
||
$id = (int)$request->header('x-admin-id', 0);
|
||
$name = trim((string)$request->header('x-admin-name', ''));
|
||
if ($id <= 0 || $name === '') {
|
||
throw new \RuntimeException('当前登录管理员信息异常', 401);
|
||
}
|
||
|
||
return ['id' => $id, 'name' => $name];
|
||
}
|
||
|
||
private function normalizeTagNo(string $tagNo): string
|
||
{
|
||
return mb_substr(trim($tagNo), 0, 80);
|
||
}
|
||
|
||
private function formatFlow(array $flow): array
|
||
{
|
||
return [
|
||
'id' => (int)$flow['id'],
|
||
'internal_tag_no' => (string)$flow['internal_tag_no'],
|
||
'flow_status' => (string)$flow['flow_status'],
|
||
'current_stage' => (string)$flow['current_stage'],
|
||
'current_stage_text' => $this->stageText((string)$flow['current_stage']),
|
||
'current_location' => (string)$flow['current_location'],
|
||
'current_location_text' => $this->locationText((string)$flow['current_location']),
|
||
'inbound_by_name' => (string)($flow['inbound_by_name'] ?? ''),
|
||
'inbound_at' => (string)($flow['inbound_at'] ?? ''),
|
||
'zhongjian_outbound_by_name' => (string)($flow['zhongjian_outbound_by_name'] ?? ''),
|
||
'zhongjian_outbound_at' => (string)($flow['zhongjian_outbound_at'] ?? ''),
|
||
'zhongjian_inbound_by_name' => (string)($flow['zhongjian_inbound_by_name'] ?? ''),
|
||
'zhongjian_inbound_at' => (string)($flow['zhongjian_inbound_at'] ?? ''),
|
||
'appraisal_started_by_name' => (string)($flow['appraisal_started_by_name'] ?? ''),
|
||
'appraisal_started_at' => (string)($flow['appraisal_started_at'] ?? ''),
|
||
'report_published_by_name' => (string)($flow['report_published_by_name'] ?? ''),
|
||
'report_published_at' => (string)($flow['report_published_at'] ?? ''),
|
||
'return_confirmed_by_name' => (string)($flow['return_confirmed_by_name'] ?? ''),
|
||
'return_confirmed_at' => (string)($flow['return_confirmed_at'] ?? ''),
|
||
'return_shipped_by_name' => (string)($flow['return_shipped_by_name'] ?? ''),
|
||
'return_shipped_at' => (string)($flow['return_shipped_at'] ?? ''),
|
||
];
|
||
}
|
||
|
||
private function stageText(string $stage): string
|
||
{
|
||
return match ($stage) {
|
||
'warehouse_received' => '已入仓待检',
|
||
'sent_to_zhongjian' => '中检送检出库',
|
||
'zhongjian_returned' => '中检送检入库',
|
||
'appraising' => '鉴定/报告录入中',
|
||
'report_published' => '报告已发布待寄回',
|
||
'return_confirmed' => '寄回确认完成',
|
||
'return_shipped' => '已寄回',
|
||
default => $stage,
|
||
};
|
||
}
|
||
|
||
private function locationText(string $location): string
|
||
{
|
||
return match ($location) {
|
||
'warehouse_pending_inspection' => '仓库待检区',
|
||
'zhongjian_institution' => '中检机构',
|
||
'warehouse_pending_report_entry' => '仓库中检回收区',
|
||
'appraiser_workbench' => '鉴定师作业区',
|
||
'zhongjian_report_entry' => '中检报告录入区',
|
||
'warehouse_return_pending' => '仓库待寄回区',
|
||
'ended' => '流转结束',
|
||
default => $location,
|
||
};
|
||
}
|
||
|
||
private function serviceProviderText(string $serviceProvider): string
|
||
{
|
||
return $serviceProvider === 'zhongjian' ? '中检鉴定' : '实物鉴定';
|
||
}
|
||
|
||
private function sourceChannelText(string $sourceChannel): string
|
||
{
|
||
return match ($sourceChannel) {
|
||
'mini_program' => '小程序',
|
||
'h5' => 'H5',
|
||
'enterprise_push' => '大客户推送订单',
|
||
'manual_entry' => '后台补录订单',
|
||
default => $sourceChannel ?: '未知渠道',
|
||
};
|
||
}
|
||
|
||
private function trackingStatusText(string $status, string $logisticsType): 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 decodeJsonArray(mixed $value): array
|
||
{
|
||
if (is_array($value)) {
|
||
return array_values($value);
|
||
}
|
||
if (is_string($value) && $value !== '') {
|
||
$decoded = json_decode($value, true);
|
||
return is_array($decoded) ? array_values($decoded) : [];
|
||
}
|
||
return [];
|
||
}
|
||
|
||
private function decodeJsonObject(mixed $value): array
|
||
{
|
||
if (is_array($value)) {
|
||
return $value;
|
||
}
|
||
if (is_string($value) && $value !== '') {
|
||
$decoded = json_decode($value, true);
|
||
return is_array($decoded) ? $decoded : [];
|
||
}
|
||
|
||
return [];
|
||
}
|
||
|
||
private function normalizeAssetList(array $files, ?Request $request): array
|
||
{
|
||
if (!$request) {
|
||
return $files;
|
||
}
|
||
|
||
return (new AppraisalEvidenceService())->normalize($files, $request);
|
||
}
|
||
}
|