From 0a781bda547bbca73c692862e558754bcbe86b90 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 1 Jun 2026 17:43:01 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adminapi/logic/stats/YejiStatsLogic.php | 50 ++++--- .../common/service/ExpressTrackingService.php | 132 ++++++++++++++++-- 2 files changed, 154 insertions(+), 28 deletions(-) diff --git a/server/app/adminapi/logic/stats/YejiStatsLogic.php b/server/app/adminapi/logic/stats/YejiStatsLogic.php index 11063431..7acedadd 100755 --- a/server/app/adminapi/logic/stats/YejiStatsLogic.php +++ b/server/app/adminapi/logic/stats/YejiStatsLogic.php @@ -5827,7 +5827,20 @@ class YejiStatsLogic } $payMap = self::commissionSettlementBatchPayTsMap($orderIds); - $signMap = self::commissionSettlementBatchSignTsMapForOrderIds($orderIds); + + $signItems = []; + foreach ($rows as $r) { + $oid = (int) ($r['id'] ?? 0); + if ($oid <= 0) { + continue; + } + $signItems[] = [ + 'order_id' => $oid, + 'tracking_number' => trim((string) ($r['tracking_number'] ?? '')), + ]; + } + // 与快递单详情 / logisticsTrace 同源:PHP 推导含 trace_time 字符串回填,避免 SQL 仅认 trace_time_stamp 导致误为空 + $signMap = ExpressTrackingService::batchResolveSignUnixFromDbForOrders($signItems); foreach ($rows as &$r) { $oid = (int) ($r['id'] ?? 0); @@ -5836,14 +5849,19 @@ class YejiStatsLogic } $signTs = (int) ($signMap[$oid] ?? 0); if ($signTs <= 0) { - $tn = trim((string) ($r['tracking_number'] ?? '')); - if ($tn !== '') { - $signTs = ExpressTrackingService::resolveSignUnixTimeForCommission( - $tn, - (string) ($r['express_company'] ?? 'auto'), - (string) ($r['recipient_phone'] ?? ''), - $allowKuaidiFallback - ); + if ($allowKuaidiFallback && $oid > 0) { + // 与 logisticsTrace 一致:库内无轨迹时 queryAndUpdate 落库后再解析 + $signTs = ExpressTrackingService::syncSignUnixFromLogisticsForPrescriptionOrder($oid, true); + } else { + $tn = trim((string) ($r['tracking_number'] ?? '')); + if ($tn !== '') { + $signTs = ExpressTrackingService::resolveSignUnixTimeForCommission( + $tn, + (string) ($r['express_company'] ?? 'auto'), + (string) ($r['recipient_phone'] ?? ''), + false + ); + } } } $r['sign_ts'] = $signTs; @@ -6798,7 +6816,7 @@ class YejiStatsLogic ->page($page, $pageSize) ->select() ->toArray(); - $pageRows = self::commissionSettlementHydrateSignPay($pageRows, false); + $pageRows = self::commissionSettlementHydrateSignPay($pageRows, true); [$linkedApptByOrderId, $apptChannelsDictMap] = self::commissionSettlementLoadApptContextForOrders($pageRows); @@ -6835,7 +6853,7 @@ class YejiStatsLogic } // bucket=current|deferred:分块判定签收/尾款后再 PHP 分页 - [$matchingIds, $signPayById] = self::commissionSettlementCollectBucketMatchingOrders( + [$matchingIds] = self::commissionSettlementCollectBucketMatchingOrders( $pack, $params, $linesAssistScopeFlip, @@ -6865,14 +6883,8 @@ class YejiStatsLogic ->order('o.id', 'desc') ->select() ->toArray(); - foreach ($pageRows as &$r) { - $oid = (int) ($r['id'] ?? 0); - if ($oid > 0 && isset($signPayById[$oid])) { - $r['sign_ts'] = $signPayById[$oid]['sign_ts']; - $r['pay_ts'] = $signPayById[$oid]['pay_ts']; - } - } - unset($r); + // 明细展示:库内解析后仍缺签收时,与 logisticsTrace 一致补查快递100(仅当前页) + $pageRows = self::commissionSettlementHydrateSignPay($pageRows, true); [$linkedApptByOrderId, $apptChannelsDictMap] = self::commissionSettlementLoadApptContextForOrders($pageRows); diff --git a/server/app/common/service/ExpressTrackingService.php b/server/app/common/service/ExpressTrackingService.php index 6fddfc3c..6c1d7624 100755 --- a/server/app/common/service/ExpressTrackingService.php +++ b/server/app/common/service/ExpressTrackingService.php @@ -1086,11 +1086,21 @@ class ExpressTrackingService $detail = self::loadTrackingDetailNormalized($num); if (is_array($detail)) { - $fromDb = self::effectiveSignUnixFromTrackingDetail($detail); - if ($fromDb > 0) { - $memo[$cacheKey] = $fromDb; + // 与 logisticsTrace:库内有轨迹明细时才视为「库内可查」 + if (!empty($detail['traces'])) { + $fromDb = self::effectiveSignUnixFromTrackingDetail($detail); + if ($fromDb > 0) { + $memo[$cacheKey] = $fromDb; - return $fromDb; + return $fromDb; + } + } else { + $fromHead = self::effectiveSignUnixFromTrackingDetail($detail); + if ($fromHead > 0) { + $memo[$cacheKey] = $fromHead; + + return $fromHead; + } } } @@ -1100,10 +1110,7 @@ class ExpressTrackingService return 0; } - $ec = strtolower(trim($expressCompany)); - if ($ec === '') { - $ec = 'auto'; - } + $ec = ExpressTrackService::normalizeExpressCompanyCode($num, (string) $expressCompany); $api = ExpressTrackService::query($ec, $num, $recipientPhone, ''); $fromApi = self::effectiveSignUnixFromKuaidiPayload($api); $memo[$cacheKey] = $fromApi; @@ -1111,6 +1118,94 @@ class ExpressTrackingService return $fromApi; } + /** + * 按业务订单 id 解析签收时间,与 PrescriptionOrderLogic::logisticsTrace 同口径。 + * allowHttp 为 true 时:库内无轨迹则 queryAndUpdate 落库后再读,避免「快递单 API 已签收、提成结算库内为空」。 + */ + public static function syncSignUnixFromLogisticsForPrescriptionOrder(int $orderId, bool $allowHttp = true): int + { + static $memo = []; + $cacheKey = $orderId . '|' . ($allowHttp ? '1' : '0'); + if (isset($memo[$cacheKey])) { + return $memo[$cacheKey]; + } + + $order = PrescriptionOrder::where('id', $orderId)->whereNull('delete_time')->find(); + if (!$order) { + $memo[$cacheKey] = 0; + + return 0; + } + + $num = trim((string) ($order->tracking_number ?? '')); + if ($num === '') { + $memo[$cacheKey] = 0; + + return 0; + } + + $detail = self::loadTrackingDetailNormalized($num); + if (is_array($detail)) { + if (!empty($detail['traces'])) { + $fromDb = self::effectiveSignUnixFromTrackingDetail($detail); + if ($fromDb > 0) { + $memo[$cacheKey] = $fromDb; + + return $fromDb; + } + } else { + $fromHead = self::effectiveSignUnixFromTrackingDetail($detail); + if ($fromHead > 0) { + $memo[$cacheKey] = $fromHead; + + return $fromHead; + } + } + } + + if (!$allowHttp) { + $memo[$cacheKey] = 0; + + return 0; + } + + $tracking = self::resolveTrackingByNumber($num); + if ($tracking) { + try { + self::queryAndUpdate((int) $tracking->id, false); + } catch (\Throwable $e) { + Log::warning('syncSignUnixFromLogisticsForPrescriptionOrder queryAndUpdate failed: ' . $e->getMessage(), [ + 'order_id' => $orderId, + 'tracking_number' => $num, + ]); + } + $detailAfter = self::loadTrackingDetailNormalized($num); + if (is_array($detailAfter)) { + $fromDb = self::effectiveSignUnixFromTrackingDetail($detailAfter); + if ($fromDb > 0) { + $memo[$cacheKey] = $fromDb; + + return $fromDb; + } + } + } + + $ec = ExpressTrackService::normalizeExpressCompanyCode( + $num, + (string) ($order->express_company ?? 'auto') + ); + $api = ExpressTrackService::query( + $ec, + $num, + (string) ($order->recipient_phone ?? ''), + '' + ); + $fromApi = self::effectiveSignUnixFromKuaidiPayload($api); + $memo[$cacheKey] = $fromApi; + + return $fromApi; + } + /** * 单号精确匹配失败时尝试 TRIM(TrackingNumber)(历史数据首尾空格)。 * @@ -1188,7 +1283,26 @@ class ExpressTrackingService } } - return max($a, $b, $c); + return max($a, $b, $c, self::signedTrackingHeadFallbackUnix($detail, $stateOk)); + } + + /** + * 已签收但 sign_time / 轨迹时间戳均未写入时,用主表 latest_trace_time 回填(甘草等同步路径常见)。 + * + * @param array $detail + */ + private static function signedTrackingHeadFallbackUnix(array $detail, bool $stateOk): int + { + if (!$stateOk) { + return 0; + } + $ltRaw = trim((string) ($detail['latest_trace_time'] ?? '')); + if ($ltRaw === '') { + return 0; + } + $parsed = strtotime($ltRaw); + + return ($parsed !== false && $parsed > 0) ? (int) $parsed : 0; } /**