更新
This commit is contained in:
@@ -19,6 +19,8 @@ class ExpressTrackService
|
||||
private const KUAIDI_COM_JT = 'jtexpress'; // 极兔速递
|
||||
|
||||
/**
|
||||
* @param string $phoneTailOverride 手工填写的收件电话(仅数字;完整 11 位或与面单一致的后四位等),优先于订单收货手机
|
||||
*
|
||||
* @return array{
|
||||
* carrier: string,
|
||||
* carrier_label: string,
|
||||
@@ -31,11 +33,13 @@ class ExpressTrackService
|
||||
* official_url: string
|
||||
* }
|
||||
*/
|
||||
public static function query(string $expressCompany, string $trackingNumber, string $recipientPhone = ''): array
|
||||
public static function query(string $expressCompany, string $trackingNumber, string $recipientPhone = '', string $phoneTailOverride = ''): array
|
||||
{
|
||||
$num = trim($trackingNumber);
|
||||
$phoneDigits = preg_replace('/\D/', '', $recipientPhone) ?? '';
|
||||
$phoneLast4 = strlen($phoneDigits) >= 4 ? substr($phoneDigits, -4) : '';
|
||||
$overrideDigits = preg_replace('/\D/', '', $phoneTailOverride) ?? '';
|
||||
$recipientDigits = preg_replace('/\D/', '', $recipientPhone) ?? '';
|
||||
// 快递100 文档:phone 为收/寄件人电话;顺丰等必填。示例为完整 11 位手机号,仅传后四位易触发 408「验证码错误」
|
||||
$phoneForKuaidi = self::buildKuaidiPhoneParam($overrideDigits, $recipientDigits);
|
||||
|
||||
$resolved = self::resolveCarrier($expressCompany, $num);
|
||||
$carrier = $resolved['carrier'];
|
||||
@@ -64,13 +68,20 @@ class ExpressTrackService
|
||||
return $out;
|
||||
}
|
||||
|
||||
// 顺丰(及快递100 要求电话的承运商)无 phone 时不请求接口,避免无效调用
|
||||
if ($phoneForKuaidi === '' && self::kuaidiPhoneRequired($kuaidiCom)) {
|
||||
$out['hint'] = '顺丰查询需在快递100 中同时提交单号与收/寄件人电话(可与面单一致的完整手机号或后四位)。请填写收件电话后点「刷新轨迹」。';
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
$paramArr = [
|
||||
'com' => $kuaidiCom,
|
||||
'num' => $num,
|
||||
'resultv2' => '1',
|
||||
];
|
||||
if ($phoneLast4 !== '') {
|
||||
$paramArr['phone'] = $phoneLast4;
|
||||
if ($phoneForKuaidi !== '') {
|
||||
$paramArr['phone'] = $phoneForKuaidi;
|
||||
}
|
||||
|
||||
$paramJson = json_encode($paramArr, JSON_UNESCAPED_UNICODE);
|
||||
@@ -154,6 +165,31 @@ class ExpressTrackService
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 快递100「phone」入参:有手动覆盖且不少于 4 位时用覆盖;否则用订单收货号码。
|
||||
* 对 11 位及以上数字取后 11 位作为手机号(去掉可能的前缀符号位)。
|
||||
*/
|
||||
private static function buildKuaidiPhoneParam(string $overrideDigits, string $recipientDigits): string
|
||||
{
|
||||
$d = strlen($overrideDigits) >= 4 ? $overrideDigits : $recipientDigits;
|
||||
if ($d === '') {
|
||||
return '';
|
||||
}
|
||||
if (strlen($d) >= 11) {
|
||||
return substr($d, -11);
|
||||
}
|
||||
|
||||
return $d;
|
||||
}
|
||||
|
||||
/** 实时查询文档:顺丰速运、中通快递等 phone 必填 */
|
||||
private static function kuaidiPhoneRequired(string $kuaidiCom): bool
|
||||
{
|
||||
$c = strtolower($kuaidiCom);
|
||||
|
||||
return $c === self::KUAIDI_COM_SF || $c === 'zhongtong';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{carrier: string, kuaidi_com: string, label: string}
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,8 @@ use app\common\model\ExpressTracking;
|
||||
use app\common\model\ExpressTrace;
|
||||
use app\common\model\ExpressStateLog;
|
||||
use app\common\model\ExpressQueryLog;
|
||||
use app\common\model\tcm\Diagnosis;
|
||||
use app\common\model\tcm\DiagnosisAssignLog;
|
||||
use app\common\model\tcm\PrescriptionOrder;
|
||||
use app\common\model\tcm\PrescriptionOrderLog;
|
||||
use think\facade\Log;
|
||||
@@ -76,7 +78,7 @@ class ExpressTrackingService
|
||||
*
|
||||
* @param int $trackingId
|
||||
* @param bool $isAuto 是否自动查询
|
||||
* @return array|null
|
||||
* @return array<string, mixed>|null 快递100 查询结果;自动任务时可能含 _assistant_sync(医助移除/跳过)
|
||||
*/
|
||||
public static function queryAndUpdate(int $trackingId, bool $isAuto = false): ?array
|
||||
{
|
||||
@@ -174,7 +176,13 @@ class ExpressTrackingService
|
||||
self::logStateChange($tracking, $oldState);
|
||||
}
|
||||
|
||||
return $result;
|
||||
// 定时拉轨迹:业务订单已发货时释放诊单医助(有正式指派记录则跳过,与结案逻辑一致)
|
||||
$assistantSync = null;
|
||||
if ($isAuto) {
|
||||
$assistantSync = self::maybeClearDiagnosisAssistantWhenShippedPrescription($tracking);
|
||||
}
|
||||
|
||||
return $assistantSync !== null ? array_merge($result, ['_assistant_sync' => $assistantSync]) : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,6 +241,147 @@ class ExpressTrackingService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 业务订单已发货(5)/已签收(6)时清空关联诊单医助;存在 tcm_diagnosis_assign_log 则跳过。
|
||||
* 供 express:auto-update、gancao:sync-logistics、甘草回调等共用。
|
||||
*
|
||||
* @param array{tracking_id?:int,tracking_number?:string,source?:string} $meta
|
||||
* @return array{
|
||||
* action: 'cleared'|'skipped_assign_log',
|
||||
* source?: string,
|
||||
* diagnosis_id: int,
|
||||
* prescription_order_id: int,
|
||||
* tracking_id: int,
|
||||
* tracking_number: string,
|
||||
* former_assistant_id?: string,
|
||||
* assistant_id?: string,
|
||||
* assign_log_count?: int,
|
||||
* fulfillment_status?: int
|
||||
* }|null
|
||||
*/
|
||||
public static function applyAssistantReleaseForShippedPrescriptionOrder(PrescriptionOrder $order, array $meta = []): ?array
|
||||
{
|
||||
$orderId = (int) $order->id;
|
||||
if ($orderId <= 0) {
|
||||
return null;
|
||||
}
|
||||
$source = (string) ($meta['source'] ?? '');
|
||||
$trackingId = (int) ($meta['tracking_id'] ?? 0);
|
||||
$tn = (string) ($meta['tracking_number'] ?? $order->tracking_number ?? '');
|
||||
|
||||
try {
|
||||
$fs = (int) $order->fulfillment_status;
|
||||
if (!in_array($fs, [5, 6], true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$diagnosisId = (int) ($order->diagnosis_id ?? 0);
|
||||
if ($diagnosisId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$diag = Diagnosis::where('id', $diagnosisId)->whereNull('delete_time')->find();
|
||||
if (!$diag) {
|
||||
return null;
|
||||
}
|
||||
$oldAssistant = (string) ($diag->getAttr('assistant_id') ?? '');
|
||||
if ($oldAssistant === '' || $oldAssistant === '0') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$assignLogCount = (int) DiagnosisAssignLog::where('diagnosis_id', $diagnosisId)
|
||||
->whereNull('delete_time')
|
||||
->count();
|
||||
if ($assignLogCount > 0) {
|
||||
return [
|
||||
'action' => 'skipped_assign_log',
|
||||
'source' => $source,
|
||||
'diagnosis_id' => $diagnosisId,
|
||||
'prescription_order_id' => $orderId,
|
||||
'tracking_id' => $trackingId,
|
||||
'tracking_number' => $tn,
|
||||
'assistant_id' => $oldAssistant,
|
||||
'assign_log_count' => $assignLogCount,
|
||||
'fulfillment_status' => $fs,
|
||||
];
|
||||
}
|
||||
|
||||
Diagnosis::where('id', $diagnosisId)->whereNull('delete_time')->update(['assistant_id' => '']);
|
||||
|
||||
Log::info('Cleared diagnosis assistant (shipped/signed prescription order)', [
|
||||
'source' => $source,
|
||||
'diagnosis_id' => $diagnosisId,
|
||||
'prescription_order_id' => $orderId,
|
||||
'tracking_id' => $trackingId,
|
||||
'fulfillment_status' => $fs,
|
||||
'former_assistant_id' => $oldAssistant,
|
||||
]);
|
||||
|
||||
return [
|
||||
'action' => 'cleared',
|
||||
'source' => $source,
|
||||
'diagnosis_id' => $diagnosisId,
|
||||
'prescription_order_id' => $orderId,
|
||||
'tracking_id' => $trackingId,
|
||||
'tracking_number' => $tn,
|
||||
'former_assistant_id' => $oldAssistant,
|
||||
'fulfillment_status' => $fs,
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('applyAssistantReleaseForShippedPrescriptionOrder failed: ' . $e->getMessage(), [
|
||||
'source' => $source,
|
||||
'order_id' => $orderId,
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* action: 'cleared'|'skipped_assign_log',
|
||||
* source?: string,
|
||||
* diagnosis_id: int,
|
||||
* prescription_order_id: int,
|
||||
* tracking_id: int,
|
||||
* tracking_number: string,
|
||||
* former_assistant_id?: string,
|
||||
* assistant_id?: string,
|
||||
* assign_log_count?: int,
|
||||
* fulfillment_status?: int
|
||||
* }|null
|
||||
*/
|
||||
private static function maybeClearDiagnosisAssistantWhenShippedPrescription(ExpressTracking $tracking): ?array
|
||||
{
|
||||
if ((string) $tracking->order_type !== 'prescription') {
|
||||
return null;
|
||||
}
|
||||
$orderId = (int) $tracking->order_id;
|
||||
if ($orderId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$order = PrescriptionOrder::where('id', $orderId)->whereNull('delete_time')->find();
|
||||
if (!$order) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::applyAssistantReleaseForShippedPrescriptionOrder($order, [
|
||||
'tracking_id' => (int) $tracking->id,
|
||||
'tracking_number' => (string) $tracking->tracking_number,
|
||||
'source' => 'express_auto_update',
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('maybeClearDiagnosisAssistantWhenShippedPrescription failed: ' . $e->getMessage(), [
|
||||
'tracking_id' => (int) $tracking->id,
|
||||
'order_id' => $orderId,
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 物流表未存收件人手机时,从关联业务订单回填(顺丰/京东等查件必填后四位)
|
||||
*/
|
||||
@@ -383,7 +532,14 @@ class ExpressTrackingService
|
||||
* 批量自动更新(定时任务调用)
|
||||
*
|
||||
* @param int $limit 每次处理数量
|
||||
* @return array
|
||||
* @return array{
|
||||
* total: int,
|
||||
* success: int,
|
||||
* failed: int,
|
||||
* assistant_cleared: int,
|
||||
* assistant_skipped_assign_log: int,
|
||||
* assistant_lines: list<string>
|
||||
* }
|
||||
*/
|
||||
public static function autoUpdateBatch(int $limit = 50): array
|
||||
{
|
||||
@@ -423,11 +579,40 @@ class ExpressTrackingService
|
||||
|
||||
$success = 0;
|
||||
$failed = 0;
|
||||
$assistantCleared = 0;
|
||||
$assistantSkippedAssign = 0;
|
||||
$assistantLines = [];
|
||||
|
||||
foreach ($list as $tracking) {
|
||||
try {
|
||||
self::queryAndUpdate($tracking->id, true);
|
||||
$ret = self::queryAndUpdate($tracking->id, true);
|
||||
$success++;
|
||||
if (is_array($ret) && isset($ret['_assistant_sync']) && is_array($ret['_assistant_sync'])) {
|
||||
$sync = $ret['_assistant_sync'];
|
||||
$act = (string) ($sync['action'] ?? '');
|
||||
if ($act === 'cleared') {
|
||||
$assistantCleared++;
|
||||
$assistantLines[] = sprintf(
|
||||
'[移除医助] 诊单=%d 业务订单=%d 运单=%s 原医助ID=%s 履约状态=%d',
|
||||
(int) ($sync['diagnosis_id'] ?? 0),
|
||||
(int) ($sync['prescription_order_id'] ?? 0),
|
||||
(string) ($sync['tracking_number'] ?? ''),
|
||||
(string) ($sync['former_assistant_id'] ?? ''),
|
||||
(int) ($sync['fulfillment_status'] ?? 0)
|
||||
);
|
||||
} elseif ($act === 'skipped_assign_log') {
|
||||
$assistantSkippedAssign++;
|
||||
$assistantLines[] = sprintf(
|
||||
'[保留医助] 诊单=%d 业务订单=%d 运单=%s 医助ID=%s 指派记录=%d条 履约状态=%d',
|
||||
(int) ($sync['diagnosis_id'] ?? 0),
|
||||
(int) ($sync['prescription_order_id'] ?? 0),
|
||||
(string) ($sync['tracking_number'] ?? ''),
|
||||
(string) ($sync['assistant_id'] ?? ''),
|
||||
(int) ($sync['assign_log_count'] ?? 0),
|
||||
(int) ($sync['fulfillment_status'] ?? 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$failed++;
|
||||
Log::error('Auto update express tracking failed', [
|
||||
@@ -442,6 +627,9 @@ class ExpressTrackingService
|
||||
'total' => count($list),
|
||||
'success' => $success,
|
||||
'failed' => $failed,
|
||||
'assistant_cleared' => $assistantCleared,
|
||||
'assistant_skipped_assign_log' => $assistantSkippedAssign,
|
||||
'assistant_lines' => $assistantLines,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use app\common\model\ExpressStateLog;
|
||||
use app\common\model\ExpressTrace;
|
||||
use app\common\model\ExpressTracking;
|
||||
use app\common\model\tcm\PrescriptionOrder;
|
||||
use app\common\service\ExpressTrackingService;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
@@ -57,7 +58,7 @@ final class GancaoLogisticsRouteService
|
||||
* 拉取并落库单个订单的物流路由
|
||||
*
|
||||
* @param PrescriptionOrder $order
|
||||
* @return array{success:bool, message:string, traces_count:int, state:string, raw?:array}
|
||||
* @return array{success:bool, message:string, traces_count:int, state:string, raw?:array, assistant_sync?:array|null}
|
||||
*/
|
||||
public static function syncOne(PrescriptionOrder $order): array
|
||||
{
|
||||
@@ -118,12 +119,18 @@ final class GancaoLogisticsRouteService
|
||||
|
||||
self::logQuery($order, $trackingNumber, 'auto', true, $resp['runtime_ms'], '', count($routeList));
|
||||
|
||||
$assistantSync = ExpressTrackingService::applyAssistantReleaseForShippedPrescriptionOrder($order, [
|
||||
'tracking_number' => $trackingNumber,
|
||||
'source' => 'gancao_route_sync',
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'ok',
|
||||
'traces_count' => $tracesCount,
|
||||
'state' => $newState,
|
||||
'raw' => $result,
|
||||
'assistant_sync' => $assistantSync,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -423,11 +430,20 @@ final class GancaoLogisticsRouteService
|
||||
/**
|
||||
* 批量同步:选取候选订单(已上传甘草、且未签收/未取消的)
|
||||
*
|
||||
* @return array{total:int, success:int, failed:int, skipped:int, details:array<int, array<string,mixed>>}
|
||||
* @return array{total:int, success:int, failed:int, skipped:int, assistant_cleared:int, assistant_skipped_assign_log:int, assistant_lines:list<string>, details:array<int, array<string,mixed>>}
|
||||
*/
|
||||
public static function syncBatch(int $limit = 100, ?int $onlyOrderId = null): array
|
||||
{
|
||||
$stats = ['total' => 0, 'success' => 0, 'failed' => 0, 'skipped' => 0, 'details' => []];
|
||||
$stats = [
|
||||
'total' => 0,
|
||||
'success' => 0,
|
||||
'failed' => 0,
|
||||
'skipped' => 0,
|
||||
'assistant_cleared' => 0,
|
||||
'assistant_skipped_assign_log' => 0,
|
||||
'assistant_lines' => [],
|
||||
'details' => [],
|
||||
];
|
||||
|
||||
$q = PrescriptionOrder::whereNull('delete_time')
|
||||
->where('gancao_reciperl_order_no', '<>', '');
|
||||
@@ -462,6 +478,32 @@ final class GancaoLogisticsRouteService
|
||||
];
|
||||
if ($r['success']) {
|
||||
$stats['success']++;
|
||||
if (!empty($r['assistant_sync']) && is_array($r['assistant_sync'])) {
|
||||
$sync = $r['assistant_sync'];
|
||||
$act = (string) ($sync['action'] ?? '');
|
||||
if ($act === 'cleared') {
|
||||
$stats['assistant_cleared']++;
|
||||
$stats['assistant_lines'][] = sprintf(
|
||||
'[移除医助][甘草路由] 诊单=%d 业务订单=%d 运单=%s 原医助ID=%s 履约状态=%d',
|
||||
(int) ($sync['diagnosis_id'] ?? 0),
|
||||
(int) ($sync['prescription_order_id'] ?? 0),
|
||||
(string) ($sync['tracking_number'] ?? ''),
|
||||
(string) ($sync['former_assistant_id'] ?? ''),
|
||||
(int) ($sync['fulfillment_status'] ?? 0)
|
||||
);
|
||||
} elseif ($act === 'skipped_assign_log') {
|
||||
$stats['assistant_skipped_assign_log']++;
|
||||
$stats['assistant_lines'][] = sprintf(
|
||||
'[保留医助][甘草路由] 诊单=%d 业务订单=%d 运单=%s 医助ID=%s 指派记录=%d条 履约状态=%d',
|
||||
(int) ($sync['diagnosis_id'] ?? 0),
|
||||
(int) ($sync['prescription_order_id'] ?? 0),
|
||||
(string) ($sync['tracking_number'] ?? ''),
|
||||
(string) ($sync['assistant_id'] ?? ''),
|
||||
(int) ($sync['assign_log_count'] ?? 0),
|
||||
(int) ($sync['fulfillment_status'] ?? 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$stats['failed']++;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user