diff --git a/app/Console/Commands/SyncStoreReceiptProductCost.php b/app/Console/Commands/SyncStoreReceiptProductCost.php index d69aceed..499ba0f8 100644 --- a/app/Console/Commands/SyncStoreReceiptProductCost.php +++ b/app/Console/Commands/SyncStoreReceiptProductCost.php @@ -54,6 +54,9 @@ class SyncStoreReceiptProductCost extends Command private const PRICE_SOURCE_DING = '线上代发价格-代发单价'; + /** 最后兜底:普通商品资料(operations_replica.rpt.jst_sku)里的成本价。 */ + private const PRICE_SOURCE_CATALOG = '普通商品资料成本价'; + /** 线上代发价格的采购单来源(与「线上代发价格」页面同一口径)。 */ private const PURCHASE_PRICE_TABLE = 'product_purchase_price_mirror_rows'; @@ -236,11 +239,14 @@ class SyncStoreReceiptProductCost extends Command $prices = $this->weightedAverageUnitPrices($mirror, $reportMonth); // 月加权平均单价匹配不到时,用「线上代发价格」(采购单价 / 钉钉代发单价)兜底。 $fallbackPrices = $this->onlineDaifaUnitPrices($mirror); + // 代发价格也没有时,再用普通商品资料里的商品成本价兜底。 + $catalogPrices = $this->catalogCostPrices($mirror); $this->info(sprintf( - '仓库映射条目:%d,月加权单价条目:%d,线上代发价格条目:%d', + '仓库映射条目:%d,月加权单价条目:%d,线上代发价格条目:%d,商品资料成本价条目:%d', count($shortNames['by_provider']), count($prices), - count($fallbackPrices) + count($fallbackPrices), + count($catalogPrices) )); $this->pruneTarget($connection, $reportMonth, $platform); @@ -254,6 +260,7 @@ class SyncStoreReceiptProductCost extends Command $missingPriceRows = 0; $missingWarehouseRows = 0; $fallbackPriceRows = 0; + $catalogPriceRows = 0; $mismatchRows = 0; foreach ($billByCore as $core => $bill) { @@ -272,6 +279,15 @@ class SyncStoreReceiptProductCost extends Command $fallbackPriceRows++; } } + // 线上代发价格也没有时,最后用普通商品资料的商品成本价兜底。 + if ($price === null) { + $catalogPrice = $catalogPrices[$line['sku']] ?? null; + if ($catalogPrice !== null) { + $price = $catalogPrice; + $priceSource = self::PRICE_SOURCE_CATALOG; + $catalogPriceRows++; + } + } $quantity = round($line['qty'], 4); $unitPrice = $price === null ? null : round((float) $price, 2); $cost = $unitPrice === null ? null : round($quantity * $unitPrice, 2); @@ -342,9 +358,10 @@ class SyncStoreReceiptProductCost extends Command $this->info(sprintf(' 商品数量合计:%s', number_format($quantityTotal, 2))); $this->info(sprintf(' 商品成本合计:%s', number_format($costTotal, 2))); $this->info(sprintf( - ' 未匹配仓库简称:%d 行;线上代发价格兜底:%d 行;仍未匹配商品单价:%d 行;数量与单价口径不一致(不计成本):%d 行', + ' 未匹配仓库简称:%d 行;线上代发价格兜底:%d 行;商品资料成本价兜底:%d 行;仍未匹配商品单价:%d 行;数量与单价口径不一致(不计成本):%d 行', $missingWarehouseRows, $fallbackPriceRows, + $catalogPriceRows, $missingPriceRows, $mismatchRows )); @@ -589,6 +606,32 @@ class SyncStoreReceiptProductCost extends Command return $prices; } + /** + * 普通商品资料(operations_replica.rpt.jst_sku → ops_jst_sku)里的商品成本价: + * 只取普通商品(sku_type=normal)且成本价大于 0 的记录,按商品编码建索引。 + * + * @return array + */ + private function catalogCostPrices($mirror): array + { + $prices = []; + try { + foreach ($mirror->table('ops_jst_sku') + ->where('sku_type', 'normal') + ->where('cost_price', '>', 0) + ->get(['sku_id', 'cost_price']) as $row) { + $sku = mb_strtoupper(trim((string) $row->sku_id)); + if ($sku !== '') { + $prices[$sku] = round((float) $row->cost_price, 2); + } + } + } catch (Throwable $exception) { + $this->warn('普通商品资料成本价读取失败(跳过该兜底来源):' . $exception->getMessage()); + } + + return $prices; + } + private function pruneTarget(string $connection, string $reportMonth, string $platform): void { $query = DB::connection($connection)->table(self::TARGET_TABLE)->where('report_month', $reportMonth); diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index da3fc98b..96ba8398 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -2039,6 +2039,7 @@ class Pages extends Controller SUM(CASE WHEN unit_price IS NULL THEN product_quantity ELSE 0 END) as missing_price_quantity, SUM(CASE WHEN warehouse_short_name IS NULL THEN 1 ELSE 0 END) as missing_warehouse_rows, SUM(CASE WHEN price_source LIKE \'线上代发价格%\' THEN 1 ELSE 0 END) as fallback_price_rows, + SUM(CASE WHEN price_source = \'普通商品资料成本价\' THEN 1 ELSE 0 END) as catalog_price_rows, SUM(CASE WHEN remark LIKE \'%口径不一致%\' THEN 1 ELSE 0 END) as unit_mismatch_rows ')->first(); @@ -2084,6 +2085,7 @@ class Pages extends Controller 'missing_warehouse_rows' => (int) ($agg->missing_warehouse_rows ?? 0), 'excluded_rows' => $excludedRows, 'fallback_price_rows' => (int) ($agg->fallback_price_rows ?? 0), + 'catalog_price_rows' => (int) ($agg->catalog_price_rows ?? 0), 'unit_mismatch_rows' => (int) ($agg->unit_mismatch_rows ?? 0), ], 'productCostMonths' => $months, diff --git a/resources/views/business/pages/financial-operations/product-costs.blade.php b/resources/views/business/pages/financial-operations/product-costs.blade.php index 351049b1..0c7d3377 100644 --- a/resources/views/business/pages/financial-operations/product-costs.blade.php +++ b/resources/views/business/pages/financial-operations/product-costs.blade.php @@ -114,8 +114,8 @@
口径:以「业绩明细」的账单行为基数(发生时间 / 店铺名称 / 线上单号),按「店铺 + 线上订单号」展开订单侧商品行, 只取收入金额有数据且不为 0 的账单行,一行 = 一个发货仓库的一个商品;发货仓库取订单的聚水潭仓库名称,仓库简称查仓库映射表, - 商品单价按「仓库简称 + 商品编码」取「商品月加权平均单价」里的加权平均价,匹配不到时退回「线上代发价格」, - 商品成本 = 商品数量 × 商品单价。 + 商品单价按「仓库简称 + 商品编码」取「商品月加权平均单价」里的加权平均价;匹配不到时退回「线上代发价格」 + (采购单价优先、其次钉钉代发单价),仍匹配不到时用「普通商品资料」里的商品成本价兜底;商品成本 = 商品数量 × 商品单价。 拆单子单重复列出的同一批商品只算一次,同一单分多仓发货的按子单实际仓库分行;虚拟条码(X0000x)不计入。 定制配件这类明细行,聚水潭把配件件数写进了「商品数量」(例如数量 9524、金额 2000.04,实际单价 0.21), 乘成品的月加权/代发单价会放大几百倍;成本单价高于该行明细单价 3 倍时判为「数量与单价口径不一致」,成本留空、不计入合计。 @@ -130,6 +130,7 @@
未匹配商品单价行数{{ number_format((int) ($summary['missing_price_rows'] ?? 0)) }}
未匹配单价的商品数量{{ $qty($summary['missing_price_quantity'] ?? null) }}
其中用线上代发价格兜底{{ number_format((int) ($summary['fallback_price_rows'] ?? 0)) }}
+
其中用商品资料成本价兜底{{ number_format((int) ($summary['catalog_price_rows'] ?? 0)) }}
数量与单价口径不一致(不计成本){{ number_format((int) ($summary['unit_mismatch_rows'] ?? 0)) }}
未匹配仓库简称行数{{ number_format((int) ($summary['missing_warehouse_rows'] ?? 0)) }}
已隐藏的「-不看」仓行数{{ number_format((int) ($summary['excluded_rows'] ?? 0)) }}