diff --git a/app/Console/Commands/SyncStoreReceiptOrderMatches.php b/app/Console/Commands/SyncStoreReceiptOrderMatches.php index d14a23ee..dcf0f8d1 100644 --- a/app/Console/Commands/SyncStoreReceiptOrderMatches.php +++ b/app/Console/Commands/SyncStoreReceiptOrderMatches.php @@ -36,7 +36,8 @@ class SyncStoreReceiptOrderMatches extends Command {--chunk=1200 : 单次向上游查询的订单号数量} {--limit=0 : 仅处理前 N 笔账单(调试用,0 表示全量)} {--amounts-only : 只回填「优惠金额 / 冲减金额 / 种菜金额」三列,不重跑订单侧匹配} - {--planting-only : 只回填「种菜金额」一列(不读账单,直接按该月已有明细行的订单号刷,一次覆盖全平台)}'; + {--planting-only : 只回填「种菜金额」一列(不读账单,直接按该月已有明细行的订单号刷,一次覆盖全平台)} + {--dry-run : 只计算并打印统计,不写库(用于评估口径调整的影响)}'; protected $description = '按店铺收款账户账单匹配订单侧商品金额,生成「业绩明细」核对镜像'; @@ -176,6 +177,9 @@ class SyncStoreReceiptOrderMatches extends Command /** 双口径兜底命中「卖家实收」时写入的标识(页面「未匹配原因」列展示)。 */ private const REMARK_SELLER_INCOME_BASIS = '订单实付按卖家实收核对'; + /** 实收缺失时退回「应付/已付」核对命中的标识。 */ + private const REMARK_PAYABLE_BASIS = '订单实付按应付/已付核对'; + public function handle(): int { // 全量重建时账单与订单侧映射都在内存里:拼多多 / 淘系支付宝这类大平台需要 1G 以上, @@ -594,9 +598,13 @@ class SyncStoreReceiptOrderMatches extends Command MAX(CASE WHEN link_o_id IS NULL OR link_o_id = 0 THEN paid_alt END), MAX(paid_alt) ) as paid_alt + , COALESCE( + MAX(CASE WHEN link_o_id IS NULL OR link_o_id = 0 THEN paid_pay END), + MAX(paid_pay) + ) as paid_pay FROM ( SELECT o.so_id as so_id, o.shop_name as shop_name, o.link_o_id as link_o_id, - o.{$paidColumn} as paid, o.{$paidAltColumn} as paid_alt + o.{$paidColumn} as paid, o.{$paidAltColumn} as paid_alt, o.pay_amount as paid_pay FROM tmp_receipt_order_nos t STRAIGHT_JOIN {$ordersTable} o ON o.so_id = t.no ) x @@ -620,6 +628,8 @@ class SyncStoreReceiptOrderMatches extends Command $orderPaid = []; /** @var array $orderPaidAlt 订单侧「卖家实收」金额(双口径兜底用) */ $orderPaidAlt = []; + /** @var array $orderPaidPay 订单侧「应付」金额(实收缺失时的退化口径) */ + $orderPaidPay = []; foreach ($paidRows as $row) { $core = self::cleanOrderNo((string) $row['so_id']); if ($core === '') { @@ -629,6 +639,7 @@ class SyncStoreReceiptOrderMatches extends Command $key = $shopKey . '|' . $core; $orderPaid[$key] = ($orderPaid[$key] ?? 0.0) + (float) $row['paid']; $orderPaidAlt[$key] = ($orderPaidAlt[$key] ?? 0.0) + (float) ($row['paid_alt'] ?? 0.0); + $orderPaidPay[$key] = ($orderPaidPay[$key] ?? 0.0) + (float) ($row['paid_pay'] ?? 0.0); // 订单实付命中同样算「订单侧存在」:部分订单(退款流程 / 拆单 / 商品明细未同步)没有商品明细行, // 若只按商品明细判断存在性,这些单会被误判「订单表无此单」(实付明明在)。 // 商品金额数组仍只由商品明细构建:无明细时商品金额保持为空、按口径标「订单无商品金额」。 @@ -658,10 +669,13 @@ class SyncStoreReceiptOrderMatches extends Command } // 只重建「本账期 + 本平台」的数据,避免把同月其它平台的结果一起清掉。 - DB::connection($connection)->table(self::TARGET_TABLE) - ->where('report_month', $reportMonth) - ->where('platform', (string) $source['label']) - ->delete(); + $dryRun = (bool) $this->option('dry-run'); + if (! $dryRun) { + DB::connection($connection)->table(self::TARGET_TABLE) + ->where('report_month', $reportMonth) + ->where('platform', (string) $source['label']) + ->delete(); + } $target = DB::connection($connection)->table(self::TARGET_TABLE); $now = now(); @@ -740,12 +754,36 @@ class SyncStoreReceiptOrderMatches extends Command $coresByCore, self::REMARK_NO_PAID, ); - if ($altStatus === self::STATUS_MATCHED && $altAmount !== null - && abs($income + $promoAmount - (float) $altAmount) <= 0.02) { + $altMatched = $altStatus === self::STATUS_MATCHED && $altAmount !== null; + $altValue = $altMatched ? (float) $altAmount : 0.0; + // 实收有值且能对上 → 按实收核对;实收缺失(0/空)时退回「应付/已付」核对(拆单主单实收常为空)。 + $payAmount = null; + if (! ($altMatched && $altValue > 0.005)) { + [$payStatus, $payValue] = $this->matchOrderSide( + $core, + (string) $group['shop_key'], + $orderPaidPay, + $orderSeen, + $coreSeen, + $coresByCore, + self::REMARK_NO_PAID, + ); + if ($payStatus === self::STATUS_MATCHED && $payValue !== null) { + $payAmount = (float) $payValue; + } + } + + if ($altMatched && $altValue > 0.005 + && abs($income + $promoAmount - $altValue) <= 0.02) { $paidAmount = round((float) $altAmount, 2); $basisAmount = $paidAmount; $difference = 0.0; $remark = self::REMARK_SELLER_INCOME_BASIS; + } elseif ($payAmount !== null && abs($income + $promoAmount - $payAmount) <= 0.02) { + $paidAmount = round($payAmount, 2); + $basisAmount = $paidAmount; + $difference = 0.0; + $remark = self::REMARK_PAYABLE_BASIS; } } if ($productAmount !== null) { @@ -789,7 +827,9 @@ class SyncStoreReceiptOrderMatches extends Command ]; if (count($buffer) >= 500) { - $target->insertOrIgnore($buffer); + if (! $dryRun) { + $target->insertOrIgnore($buffer); + } $imported += count($buffer); $buffer = []; } @@ -799,14 +839,18 @@ class SyncStoreReceiptOrderMatches extends Command foreach ($promoOnlyGroups as $promoGroup) { $buffer[] = self::promoOnlyRow($reportMonth, (string) $source['label'], $promoGroup, $now); if (count($buffer) >= 500) { - $target->insertOrIgnore($buffer); + if (! $dryRun) { + $target->insertOrIgnore($buffer); + } $imported += count($buffer); $buffer = []; } } if ($buffer !== []) { - $target->insertOrIgnore($buffer); + if (! $dryRun) { + $target->insertOrIgnore($buffer); + } $imported += count($buffer); }