业绩明细:合单发货取数优先取原单行(merge_so_id 为空),修复 878 行误取合并汇总金额(7/8月实付合计修正 -11.17 万);商品金额/实付/实收/应付统一三档优先级;字段结构说明同步新取数与口径兜底/待核实顺序

This commit is contained in:
yueyang 2026-10-16 11:59:43 +08:00
parent 8fbc9abd53
commit 6c76a373a3
3 changed files with 28 additions and 9 deletions

View File

@ -551,10 +551,11 @@ class SyncStoreReceiptOrderMatches extends Command
['ops_jst_orders', 'ops_jst_order_item', 'buyer_paid_amount', 'ops_jst', 'seller_income_amount'], ['ops_jst_orders', 'ops_jst_order_item', 'buyer_paid_amount', 'ops_jst', 'seller_income_amount'],
] as [$ordersTable, $itemsTable, $paidColumn, $tag, $paidAltColumn]) { ] as [$ordersTable, $itemsTable, $paidColumn, $tag, $paidAltColumn]) {
try { try {
// 商品金额只取主单(link_o_id 为空/0):聚水潭的拆单子单会把主单的商品行再列一遍, // 商品金额取数优先级:主单(link_o_id 为空/0)→ 原单行(merge_so_id 为空)→ 全部行。
// 子单的平台子订单号与主单不同时,按「子订单号 + 条码」的去重键拦不住,会把同一批 // - 拆单:主单优先,子单会把主单的商品行再列一遍,按「子订单号 + 条码」的去重键拦不住;
// 商品重复累加。整单没有任何主单行时退化为取全部行,避免把真实金额抹掉——与实付 // - 合单发货(拼多多/京喜/抖音/京东等):原单行与合并汇总行共用同一线上单号、互相 link,
// 「主单优先,无主单退化」保持同一套规则。 // 没有「主单」。此时整单退化为全部行会把被合并进来的另一单商品算到本单号上;
// 优先只取原单行(merge_so_id 为空),原单行不存在时再退化为全部行。
$mirror->statement('DROP TEMPORARY TABLE IF EXISTS tmp_receipt_main_nos'); $mirror->statement('DROP TEMPORARY TABLE IF EXISTS tmp_receipt_main_nos');
$mirror->statement('CREATE TEMPORARY TABLE tmp_receipt_main_nos (no varchar(64) NOT NULL, PRIMARY KEY(no)) ENGINE=InnoDB'); $mirror->statement('CREATE TEMPORARY TABLE tmp_receipt_main_nos (no varchar(64) NOT NULL, PRIMARY KEY(no)) ENGINE=InnoDB');
$mirror->statement( $mirror->statement(
@ -564,6 +565,15 @@ class SyncStoreReceiptOrderMatches extends Command
STRAIGHT_JOIN {$ordersTable} o ON o.so_id = t.no STRAIGHT_JOIN {$ordersTable} o ON o.so_id = t.no
WHERE o.link_o_id IS NULL OR o.link_o_id = 0" WHERE o.link_o_id IS NULL OR o.link_o_id = 0"
); );
$mirror->statement('DROP TEMPORARY TABLE IF EXISTS tmp_receipt_own_nos');
$mirror->statement('CREATE TEMPORARY TABLE tmp_receipt_own_nos (no varchar(64) NOT NULL, PRIMARY KEY(no)) ENGINE=InnoDB');
$mirror->statement(
"INSERT IGNORE INTO tmp_receipt_own_nos (no)
SELECT DISTINCT o.so_id as no
FROM tmp_receipt_order_nos t
STRAIGHT_JOIN {$ordersTable} o ON o.so_id = t.no
WHERE o.merge_so_id IS NULL OR o.merge_so_id = ''"
);
// 同一线上订单号在聚水潭可能对应多个 o_id(拆单/子单),且子单会重复包含 // 同一线上订单号在聚水潭可能对应多个 o_id(拆单/子单),且子单会重复包含
// 同一批子订单商品行;先按「子订单号 + 条码」去重再求和,避免重复计算。 // 同一批子订单商品行;先按「子订单号 + 条码」去重再求和,避免重复计算。
@ -580,30 +590,38 @@ class SyncStoreReceiptOrderMatches extends Command
STRAIGHT_JOIN {$ordersTable} o ON o.so_id = t.no STRAIGHT_JOIN {$ordersTable} o ON o.so_id = t.no
STRAIGHT_JOIN {$itemsTable} i ON i.o_id = o.o_id STRAIGHT_JOIN {$itemsTable} i ON i.o_id = o.o_id
LEFT JOIN tmp_receipt_main_nos m ON m.no = o.so_id LEFT JOIN tmp_receipt_main_nos m ON m.no = o.so_id
LEFT JOIN tmp_receipt_own_nos w ON w.no = o.so_id
WHERE (o.link_o_id IS NULL OR o.link_o_id = 0) WHERE (o.link_o_id IS NULL OR o.link_o_id = 0)
OR m.no IS NULL OR (m.no IS NULL AND (o.merge_so_id IS NULL OR o.merge_so_id = ''))
OR (m.no IS NULL AND w.no IS NULL)
GROUP BY o.so_id, o.shop_name, dedup_key GROUP BY o.so_id, o.shop_name, dedup_key
) d ) d
GROUP BY so_id, shop_name" GROUP BY so_id, shop_name"
); );
// 订单实付:取订单级字段(明细行上的实付会重复);拆单取主单(link_o_id 为空), // 订单实付:取订单级字段(明细行上的实付会重复);优先级与商品金额一致:
// 没有主单时退化为取该单号下的最大值。 // 主单(link_o_id 为空)→ 原单行(merge_so_id 为空)→ 兜底取该单号下的最大值。
// 合单发货两行互相 link、没有主单,直接取最大值会取到合并汇总行(含被合并的另一单),
// 必须让原单行优先;实付、卖家实收、应付三个字段同一套规则。
$paid = $mirror->select( $paid = $mirror->select(
"SELECT so_id, shop_name, "SELECT so_id, shop_name,
COALESCE( COALESCE(
MAX(CASE WHEN link_o_id IS NULL OR link_o_id = 0 THEN paid END), MAX(CASE WHEN link_o_id IS NULL OR link_o_id = 0 THEN paid END),
MAX(CASE WHEN merge_so_id IS NULL OR merge_so_id = '' THEN paid END),
MAX(paid) MAX(paid)
) as paid ) as paid
, COALESCE( , COALESCE(
MAX(CASE WHEN link_o_id IS NULL OR link_o_id = 0 THEN paid_alt END), MAX(CASE WHEN link_o_id IS NULL OR link_o_id = 0 THEN paid_alt END),
MAX(CASE WHEN merge_so_id IS NULL OR merge_so_id = '' THEN paid_alt END),
MAX(paid_alt) MAX(paid_alt)
) as paid_alt ) as paid_alt
, COALESCE( , COALESCE(
MAX(CASE WHEN link_o_id IS NULL OR link_o_id = 0 THEN paid_pay END), MAX(CASE WHEN link_o_id IS NULL OR link_o_id = 0 THEN paid_pay END),
MAX(CASE WHEN merge_so_id IS NULL OR merge_so_id = '' THEN paid_pay END),
MAX(paid_pay) MAX(paid_pay)
) as paid_pay ) as paid_pay
FROM ( FROM (
SELECT o.so_id as so_id, o.shop_name as shop_name, o.link_o_id as link_o_id, SELECT o.so_id as so_id, o.shop_name as shop_name, o.link_o_id as link_o_id,
o.merge_so_id as merge_so_id,
o.{$paidColumn} as paid, o.{$paidAltColumn} as paid_alt, o.pay_amount as paid_pay o.{$paidColumn} as paid, o.{$paidAltColumn} as paid_alt, o.pay_amount as paid_pay
FROM tmp_receipt_order_nos t FROM tmp_receipt_order_nos t
STRAIGHT_JOIN {$ordersTable} o ON o.so_id = t.no STRAIGHT_JOIN {$ordersTable} o ON o.so_id = t.no

View File

@ -292,6 +292,7 @@ class Pages extends Controller
. " OR store_name LIKE '京东-亿家达全屋%' OR store_name LIKE '京东-朴美家居%')" . " OR store_name LIKE '京东-亿家达全屋%' OR store_name LIKE '京东-朴美家居%')"
. ' AND ' . $effective . ' < -0.005'; . ' AND ' . $effective . ' < -0.005';
$jdSofiaPay = 'COALESCE((SELECT COALESCE(MAX(CASE WHEN o.link_o_id IS NULL OR o.link_o_id = 0 THEN o.pay_amount END),' $jdSofiaPay = 'COALESCE((SELECT COALESCE(MAX(CASE WHEN o.link_o_id IS NULL OR o.link_o_id = 0 THEN o.pay_amount END),'
. " MAX(CASE WHEN o.merge_so_id IS NULL OR o.merge_so_id = '' THEN o.pay_amount END),"
. ' MAX(o.pay_amount)) FROM ops_jst_orders o WHERE o.so_id = store_receipt_order_match_rows.online_order_no), 0)'; . ' MAX(o.pay_amount)) FROM ops_jst_orders o WHERE o.so_id = store_receipt_order_match_rows.online_order_no), 0)';
$jdSofiaGuobu = '(' . $jdSofiaPay . ' - COALESCE(income_amount, 0)) > 0.005'; $jdSofiaGuobu = '(' . $jdSofiaPay . ' - COALESCE(income_amount, 0)) > 0.005';
$rules['差额=订单实付包含商家承担优惠和以旧换新国补'] = $jdSofiaGuard . ' AND ' . $jdSofiaGuobu; $rules['差额=订单实付包含商家承担优惠和以旧换新国补'] = $jdSofiaGuard . ' AND ' . $jdSofiaGuobu;
@ -356,7 +357,7 @@ class Pages extends Controller
$row = DB::connection((string) config('upstream_mirrors.connection', 'business_mirror')) $row = DB::connection((string) config('upstream_mirrors.connection', 'business_mirror'))
->table('ops_jst_orders') ->table('ops_jst_orders')
->where('so_id', $orderNo) ->where('so_id', $orderNo)
->selectRaw('COALESCE(MAX(CASE WHEN link_o_id IS NULL OR link_o_id = 0 THEN pay_amount END), MAX(pay_amount)) as pay') ->selectRaw("COALESCE(MAX(CASE WHEN link_o_id IS NULL OR link_o_id = 0 THEN pay_amount END), MAX(CASE WHEN merge_so_id IS NULL OR merge_so_id = '' THEN pay_amount END), MAX(pay_amount)) as pay")
->first(); ->first();
$payCache[$orderNo] = (float) ($row->pay ?? 0); $payCache[$orderNo] = (float) ($row->pay ?? 0);
} }

View File

@ -807,7 +807,7 @@ $navigation = [
'directory' => [ 'directory' => [
'icon' => 'receipt_long', 'icon' => 'receipt_long',
'status' => '共享镜像', 'status' => '共享镜像',
'description' => '覆盖店铺收款账户账单各平台页签(总账备注=订单收入),按「店铺 + 订单号」匹配订单侧商品金额与订单实付金额;一行 = 一个店铺的一个订单,商品金额只挂一次。「商品金额」只取主单(拆单子单不参与,整单没有主单时退化为取全部行),并排除虚拟条码(X0000x:信誉订单 / 虚拟订单)明细行;「订单实付金额」取订单级实付(拆单取主单)再减去虚拟条码行实付。「优惠金额」「冲减金额」另取总账备注=优惠收入 / 冲减收入的账单金额,不计入收入金额;「退款金额」另取总账备注=退款的账单金额,只单独列示,不计入收入金额、也不参与差额;「种菜金额」是订单号在种菜明细表或聚水潭订单标签明细(是否种菜订单=是)任意一边出现过时,该订单的收入金额(两个条件是或的关系),只是收入金额里的一部分,不参与差额;差额=(收入金额+优惠金额+冲减金额)−订单实付金额,匹配状态与差额异常备注同样按订单实付金额判定,未匹配到订单实付金额的行按订单实付金额=0 计入差额,只反映真正对不上的部分。挂不到订单行的优惠/冲减会补成一行(收入/商品留空)。「差额异常备注」只说明差额为什么产生,按顺序自动判定:订单实付未匹配(未匹配到订单实付金额,具体原因看「未匹配原因」列);订单实付取数为0(种菜)(订单实付=0 且是种菜单)、订单实付取数为0(订单实付=0 但不是种菜单)、订单实付未减冲减(订单实付=收入+优惠 → 差额=冲减金额)、订单实付未减优惠(订单实付=收入+冲减 → 差额=优惠金额)、订单实付未减优惠和冲减(订单实付=收入 → 差额=优惠+冲减)、订单实付含已退款部分(仅抖音负差额:聚水潭退款单/取消子单金额≈差额,或退款合计+账单退款净额≈实付−收入,或整单全退(退款合计≈实付)对上才标注)、订单实付未减联合营销费(仅得物负差额:联合营销费未让利尾数对上才标注)、订单实付取供货价(仅京喜正差额:差额=该单直营服务费(供货模式净收)时标注)、差额=直营补贴款(待开票结算)(仅京喜账单·出类拔萃店负差额且无退款:京东直营补贴款待开票结算时标注)、差额=直营补贴款(订单退款已冲回)(同店负差额但订单已退款:补贴款已随退款冲回)、差额=订单实付包含商家承担优惠和以旧换新国补(仅京东账单·索菲亚客厅/办公/书房/功能、亿家达全屋、朴美家居店负差额、且该单含国补(聚水潭应付−账单收入>0):差额为商家承担优惠(店铺东券/单品折扣等)与以旧换新国补之和,已用营销对账逐单核实)、差额=订单实付包含商家承担优惠(同店负差额但不含国补的单:差额即商家承担优惠);都不满足则留空。「未匹配原因」单独一列,取值:账单无单号 / 账单无单号(采销收款)/ 单号格式异常 / 订单表无此单 / 1688采销收款(非销售单)/ 订单无实付金额 / 无收款账单(「采销收款」两档专指「支付宝-1688采销专用」账户:该账户收的是采销/供货结算款,不是店铺销售单收款,不按丢单处理)。', 'description' => '覆盖店铺收款账户账单各平台页签(总账备注=订单收入),按「店铺 + 订单号」匹配订单侧商品金额与订单实付金额;一行 = 一个店铺的一个订单,商品金额只挂一次。「商品金额」按「主单(拆单子单不参与)→ 原单行(merge_so_id 为空)→ 全部行」优先级取数,合单发货(原单与合并行共用同一线上单号、没有主单)时只取本单原单行,避免把合并的另一单算进来;并排除虚拟条码(X0000x:信誉订单 / 虚拟订单)明细行;「订单实付金额」取订单级实付(同一套优先级,末档兜底取该单号下最大值)再减去虚拟条码行实付。「优惠金额」「冲减金额」另取总账备注=优惠收入 / 冲减收入的账单金额,不计入收入金额;「退款金额」另取总账备注=退款的账单金额,只单独列示,不计入收入金额、也不参与差额;「种菜金额」是订单号在种菜明细表或聚水潭订单标签明细(是否种菜订单=是)任意一边出现过时,该订单的收入金额(两个条件是或的关系),只是收入金额里的一部分,不参与差额;差额=(收入金额+优惠金额+冲减金额)−订单实付金额,匹配状态与差额异常备注同样按订单实付金额判定,未匹配到订单实付金额的行按订单实付金额=0 计入差额,只反映真正对不上的部分。挂不到订单行的优惠/冲减会补成一行(收入/商品留空)。「差额异常备注」只说明差额为什么产生,按顺序自动判定:订单实付未匹配(未匹配到订单实付金额,具体原因看「匹配原因」列);订单实付取数为0(种菜)(订单实付=0 且是种菜单)、订单实付取数为0(订单实付=0 但不是种菜单)、订单实付未减联合营销费(仅得物负差额:联合营销费未让利尾数对上才标注)、订单实付取供货价(仅京喜正差额:差额=该单直营服务费(供货模式净收)时标注)、差额=直营补贴款(待开票结算)(仅京喜账单·出类拔萃店负差额且无退款:京东直营补贴款待开票结算时标注)、差额=直营补贴款(订单退款已冲回)(同店负差额但订单已退款:补贴款已随退款冲回)、差额=订单实付包含商家承担优惠和以旧换新国补(仅京东账单·索菲亚客厅/办公/书房/功能、亿家达全屋、朴美家居店负差额、且该单含国补(聚水潭应付−账单收入>0):差额为商家承担优惠(店铺东券/单品折扣等)与以旧换新国补之和,已用营销对账逐单核实)、差额=订单实付包含商家承担优惠(同店负差额但不含国补的单:差额即商家承担优惠)、订单实付未减冲减(订单实付=收入+优惠 → 差额=冲减金额)、订单实付未减优惠(订单实付=收入+冲减 → 差额=优惠金额)、订单实付未减优惠和冲减(订单实付=收入 → 差额=优惠+冲减)(以上三档等式对带口径标识的行让位给口径兜底)、订单实付含已退款部分(仅抖音负差额:聚水潭退款单/取消子单金额≈差额,或退款合计+账单退款净额≈实付−收入,或整单全退(退款合计≈实付)对上才标注);都不满足时,若「卖家实收」或「应付/已付」能对上账单,差额按 0 计并在「匹配原因」列标注口径核对;其余标「差额待核实」。「匹配原因」单独一列,取值:账单无单号 / 账单无单号(采销收款)/ 单号格式异常 / 订单表无此单 / 1688采销收款(非销售单)/ 订单无实付金额 / 无收款账单(「采销收款」两档专指「支付宝-1688采销专用」账户:该账户收的是采销/供货结算款,不是店铺销售单收款,不按丢单处理)。',
'empty' => '店铺收款匹配订单明细尚未同步,请先执行 store-receipt:order-match:sync。', 'empty' => '店铺收款匹配订单明细尚未同步,请先执行 store-receipt:order-match:sync。',
'fields' => [ 'fields' => [
['name' => 'platform', 'label' => '平台'], ['name' => 'platform', 'label' => '平台'],