商品成本:订单内有带仓库行时丢弃空仓库汇总行 #464

Merged
chenxi merged 1 commits from fix/chenxi-product-cost-wh-priority into main 2026-10-22 22:49:07 +08:00
2 changed files with 31 additions and 7 deletions

View File

@ -187,7 +187,7 @@ class SyncStoreReceiptProductCost extends Command
}
$this->info('订单侧候选单号:' . count($candidates));
/** @var array<string,list<array{sku:string,qty:float,amount:float,warehouse:string,o_id:string,parent_o_id:string,row_key:string}>> $lines */
/** @var array<string,list<array{sku:string,qty:float,amount:float,warehouse:string,o_id:string,parent_o_id:string,row_key:string,status:string}>> $lines */
$lines = [];
$excludedSkuSql = "'" . implode("','", self::EXCLUDED_SKUS) . "'";
foreach (self::SOURCES as $source) {
@ -198,14 +198,13 @@ class SyncStoreReceiptProductCost extends Command
. " COALESCE(wh.name, '') as warehouse_name, "
. " o.o_id as o_id, o.link_o_id as parent_o_id, "
. " CONCAT(o.o_id, '-', i.oi_id) as row_key, "
. " i.sku_id as sku_id, i.qty as qty, i.amount as amount
. " i.sku_id as sku_id, i.qty as qty, i.amount as amount, o.status as status
FROM tmp_cost_order_nos t
STRAIGHT_JOIN {$source['orders']} o ON o.so_id = t.no
STRAIGHT_JOIN {$source['items']} i ON i.o_id = o.o_id
LEFT JOIN ops_jst_warehouse_info wh ON wh.wms_co_id = o.wms_co_id
WHERE COALESCE(i.sku_id, '') NOT LIKE 'X0000%'
AND UPPER(TRIM(COALESCE(i.sku_id, ''))) NOT IN ({$excludedSkuSql})
AND UPPER(TRIM(COALESCE(o.status, ''))) <> 'CANCELLED'"
AND UPPER(TRIM(COALESCE(i.sku_id, ''))) NOT IN ({$excludedSkuSql})"
);
} catch (Throwable $exception) {
$this->error('订单明细查询失败(' . $source['items'] . '):' . $exception->getMessage());
@ -262,6 +261,7 @@ class SyncStoreReceiptProductCost extends Command
// 拆单层级:o_id 与上级 link_o_id,用于只保留末级发货行(叶子行)。
'o_id' => (string) $row->o_id,
'parent_o_id' => (string) ($row->parent_o_id ?? ''),
'status' => (string) ($row->status ?? ''),
'row_key' => (string) $row->row_key,
];
}
@ -409,9 +409,11 @@ class SyncStoreReceiptProductCost extends Command
* 1) 按「o_id + oi_id + 商品编码」去掉完全重复行;
* 2) 只保留叶子行——凡是被同一订单其他行 link_o_id 引用过的 o_id 全部剔除,
* 避免主单、中间汇总层与末级发货行重复计数;
* 3) 同一订单的多条末级行(不同仓库 / 分批发货)按「发货仓库 + 商品编码」汇总数量。
* 3) 订单内只要存在带仓库的行,就丢弃空仓库的汇总/占位行;
* 如果带仓库的行全部是 Cancelled,则整单不参与商品成本;
* 4) 排除 Cancelled 行,同一订单的多条末级行按「发货仓库 + 商品编码」汇总数量。
*
* @param list<array{sku:string,qty:float,amount:float,warehouse:string,o_id:string,parent_o_id:string,row_key:string}> $rows
* @param list<array{sku:string,qty:float,amount:float,warehouse:string,o_id:string,parent_o_id:string,row_key:string,status:string}> $rows
* @return list<array{sku:string,qty:float,amount:float,warehouse:string}>
*/
private function costLines(array $rows): array
@ -446,7 +448,28 @@ class SyncStoreReceiptProductCost extends Command
}
}
// 3) 汇总到「发货仓库 + 商品编码」。
// 3) 组内只要出现带仓库的行,空仓库行就是汇总/占位行,直接丢弃。
$hasWarehouse = false;
foreach ($unique as $row) {
if (trim((string) $row['warehouse']) !== '') {
$hasWarehouse = true;
break;
}
}
if ($hasWarehouse) {
$unique = array_filter(
$unique,
static fn (array $row): bool => trim((string) $row['warehouse']) !== ''
);
}
// 4) 排除已取消行;若带仓库行全部取消,上面已把空仓库汇总行丢掉,这里结果为空 → 整单不计。
$unique = array_filter(
$unique,
static fn (array $row): bool => strtoupper(trim((string) ($row['status'] ?? ''))) !== 'CANCELLED'
);
// 5) 汇总到「发货仓库 + 商品编码」。
$aggregated = [];
foreach ($unique as $row) {
$key = $row['warehouse'] . "\0" . $row['sku'];

View File

@ -117,6 +117,7 @@
发货仓库默认排除「常州简尚家居有限公司」「简尚-苏州福利品仓」「简尚-自动发货仓」,
其中常州简尚家居有限公司的「DF-」商品行例外保留;先排除 TCXJ/FU WU/AZF,再执行仓库过滤。
订单状态为 Cancelled(已取消)的明细行不参与商品成本。
同一订单内只要存在带仓库的订单行,空仓库的汇总/占位行即丢弃;若带仓库行全部是 Cancelled,则整单不计入。
定制配件这类明细行,聚水潭把配件件数写进了「商品数量」(例如数量 9524、金额 2000.04,实际单价 0.21),
乘成品的月加权/代发单价会放大几百倍;成本单价高于该行明细单价 3 倍时判为「数量与单价口径不一致」,成本留空、不计入合计。
页面默认展示全部数据:已匹配/未匹配商品单价的行、仓库映射表里标记为「-不看」的仓