chore: prepare release build
This commit is contained in:
@@ -9,31 +9,21 @@ class FulfillmentFlowService
|
||||
{
|
||||
public function lookupInboundByTrackingNo(string $trackingNo): array
|
||||
{
|
||||
$trackingNo = trim($trackingNo);
|
||||
if ($trackingNo === '') {
|
||||
throw new \InvalidArgumentException('请先扫描寄入运单号');
|
||||
}
|
||||
|
||||
$rows = Db::name('order_logistics')
|
||||
->where('logistics_type', 'send_to_center')
|
||||
->where('tracking_no', $trackingNo)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
if (!$rows) {
|
||||
throw new \RuntimeException('未匹配到订单,请核对寄入运单号', 404);
|
||||
}
|
||||
if (count($rows) > 1) {
|
||||
throw new \RuntimeException('该运单号匹配到多笔订单,请人工核查后处理', 409);
|
||||
}
|
||||
|
||||
return $this->formatOrderContext((int)$rows[0]['order_id']);
|
||||
return $this->lookupInboundByInboundNo($trackingNo);
|
||||
}
|
||||
|
||||
public function receiveInbound(string $trackingNo, string $tagNo, Request $request): array
|
||||
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);
|
||||
$context = $this->lookupInboundByTrackingNo($trackingNo);
|
||||
$match = $this->resolveInboundOrder($inboundNo);
|
||||
$context = $this->formatOrderContext((int)$match['order_id']);
|
||||
$order = $context['order_info'];
|
||||
$orderId = (int)$order['id'];
|
||||
|
||||
@@ -47,6 +37,12 @@ class FulfillmentFlowService
|
||||
}
|
||||
|
||||
$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 {
|
||||
@@ -114,8 +110,8 @@ class FulfillmentFlowService
|
||||
$logistics = Db::name('order_logistics')
|
||||
->where('order_id', $orderId)
|
||||
->where('logistics_type', 'send_to_center')
|
||||
->where('tracking_no', trim($trackingNo))
|
||||
->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([
|
||||
@@ -140,7 +136,11 @@ class FulfillmentFlowService
|
||||
]);
|
||||
|
||||
$this->insertTimeline($orderId, 'inbound_received', '已入仓待检', '仓管扫描寄入运单并完成物品入库。', $operator, $now);
|
||||
$this->insertFlowLog($flow, 'inbound_received', '入库完成', '', '', 'warehouse_received', 'warehouse_pending_inspection', $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();
|
||||
@@ -149,7 +149,7 @@ class FulfillmentFlowService
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $this->formatOrderContext($orderId);
|
||||
return $this->formatOrderContext($orderId, $request);
|
||||
}
|
||||
|
||||
public function scanTransferForAppraisal(string $tagNo, Request $request): array
|
||||
@@ -283,15 +283,10 @@ class FulfillmentFlowService
|
||||
|
||||
public function verifyReturnMaterialTag(string $tagNo, string $qrInput, 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']);
|
||||
if (!$report || ($report['report_status'] ?? '') !== 'published') {
|
||||
throw new \InvalidArgumentException('订单报告未发布,不能确认寄回');
|
||||
@@ -302,7 +297,13 @@ class FulfillmentFlowService
|
||||
throw new \InvalidArgumentException('验真吊牌与当前订单报告不匹配');
|
||||
}
|
||||
|
||||
return $this->markReturnConfirmed($flow, $operator, 'return_tag_verified', '验真吊牌确认', '仓管已扫描验真吊牌并确认报告信息。');
|
||||
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
|
||||
@@ -326,7 +327,44 @@ class FulfillmentFlowService
|
||||
return $this->markReturnConfirmed($flow, $operator, 'return_confirmed', '中检报告已确认', '仓管已查看中检报告编号和报告文件。');
|
||||
}
|
||||
|
||||
public function shipReturn(string $tagNo, string $expressCompany, string $trackingNo, Request $request): array
|
||||
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('订单报告未发布,不能确认寄回');
|
||||
}
|
||||
if ((int)$report['id'] !== $reportId) {
|
||||
throw new \InvalidArgumentException('确认的报告与当前订单报告不匹配');
|
||||
}
|
||||
if ((string)($flow['current_stage'] ?? '') === 'return_confirmed') {
|
||||
return $this->formatOrderContext((int)$flow['order_id'], $request);
|
||||
}
|
||||
|
||||
if (($flow['service_provider'] ?? '') === 'zhongjian') {
|
||||
$content = Db::name('report_contents')->where('report_id', (int)$report['id'])->find();
|
||||
$files = $this->decodeJsonArray($content['zhongjian_report_files_json'] ?? null);
|
||||
if (trim((string)($report['zhongjian_report_no'] ?? '')) === '' || !$files) {
|
||||
throw new \InvalidArgumentException('中检报告未完整录入,不能确认寄回');
|
||||
}
|
||||
|
||||
return $this->markReturnConfirmed($flow, $operator, 'return_confirmed', '中检报告已确认', '仓管已查看中检报告编号和报告文件。');
|
||||
}
|
||||
|
||||
$boundTag = (new MaterialTagService())->findBoundTagForReport((int)$report['id']);
|
||||
if (!$boundTag) {
|
||||
throw new \InvalidArgumentException('当前报告未绑定验真吊牌,不能确认寄回');
|
||||
}
|
||||
|
||||
return $this->markReturnConfirmed($flow, $operator, 'return_tag_verified', '验真吊牌确认', '仓管已核对验真吊牌与报告信息。');
|
||||
}
|
||||
|
||||
public function shipReturn(string $tagNo, string $expressCompany, string $trackingNo, Request $request, array $packingAttachments = []): array
|
||||
{
|
||||
$operator = $this->operator($request);
|
||||
$flow = $this->findActiveFlowByTagNo($tagNo);
|
||||
@@ -342,6 +380,12 @@ class FulfillmentFlowService
|
||||
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();
|
||||
@@ -432,7 +476,9 @@ class FulfillmentFlowService
|
||||
]);
|
||||
|
||||
$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);
|
||||
$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),
|
||||
@@ -547,18 +593,7 @@ class FulfillmentFlowService
|
||||
'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) => [
|
||||
'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'] ?? ''),
|
||||
], $flowLogs),
|
||||
'flow_logs' => array_map(fn (array $log) => $this->formatFlowLog($log, $request), $flowLogs),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -649,7 +684,7 @@ class FulfillmentFlowService
|
||||
]);
|
||||
}
|
||||
|
||||
private function insertFlowLog(array $flow, string $code, string $text, string $beforeStage, string $beforeLocation, string $afterStage, string $afterLocation, array $operator, string $remark, string $now): void
|
||||
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'],
|
||||
@@ -665,11 +700,33 @@ class FulfillmentFlowService
|
||||
'operator_id' => $operator['id'],
|
||||
'operator_name' => $operator['name'],
|
||||
'remark' => mb_substr($remark, 0, 500),
|
||||
'payload_json' => null,
|
||||
'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();
|
||||
@@ -695,6 +752,89 @@ class FulfillmentFlowService
|
||||
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);
|
||||
@@ -849,6 +989,7 @@ class FulfillmentFlowService
|
||||
'mini_program' => '小程序',
|
||||
'h5' => 'H5',
|
||||
'enterprise_push' => '大客户推送订单',
|
||||
'manual_entry' => '后台补录订单',
|
||||
default => $sourceChannel ?: '未知渠道',
|
||||
};
|
||||
}
|
||||
@@ -865,6 +1006,19 @@ class FulfillmentFlowService
|
||||
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) {
|
||||
|
||||
@@ -352,9 +352,6 @@ class MaterialTagService
|
||||
if (!$task) {
|
||||
throw new \RuntimeException('任务不存在', 404);
|
||||
}
|
||||
if (($task['service_provider'] ?? '') === 'zhongjian') {
|
||||
throw new \InvalidArgumentException('中检订单不使用平台验真吊牌');
|
||||
}
|
||||
$report = Db::name('reports')
|
||||
->where('order_id', (int)$task['order_id'])
|
||||
->where('report_type', 'appraisal')
|
||||
|
||||
Reference in New Issue
Block a user