业绩明细:实收缺失时退回按应付/已付核对;同步命令新增 --dry-run 开关

This commit is contained in:
yueyang 2026-10-15 13:21:29 +08:00
parent c314209705
commit 72b3f43da5

View File

@ -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<string,float> $orderPaidAlt 订单侧「卖家实收」金额(双口径兜底用) */
$orderPaidAlt = [];
/** @var array<string,float> $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);
}