diff --git a/modules/EcommerceFinance/Services/PlantingLossCalculator.php b/modules/EcommerceFinance/Services/PlantingLossCalculator.php
index 6d40c670..5dbdfb63 100644
--- a/modules/EcommerceFinance/Services/PlantingLossCalculator.php
+++ b/modules/EcommerceFinance/Services/PlantingLossCalculator.php
@@ -32,7 +32,15 @@ class PlantingLossCalculator
public const RESULTS = [self::RESULT_DEDUCT, self::RESULT_EXEMPT, self::RESULT_FREE_GOODS, self::RESULT_UNMATCHED];
/** 发货商品名称或条码含这些字样的不计扣款。 */
- public const FREE_GOODS_KEYWORDS = ['信誉订单虚拟条码', '虚拟条码', '透明挂钩'];
+ public const FREE_GOODS_KEYWORDS = ['信誉订单虚拟条码', '虚拟条码', '透明挂钩', '无实物'];
+
+ /**
+ * 信誉订单虚拟条码(种菜单自身的虚拟商品)。发货数据里同一交易单号下若出现它,说明这单本身是按虚拟条码走的,
+ * 同单号下的其它商品是合并发货的别的订单,整单不扣款(2026-10-19 财务确认:信誉订单虚拟条码不扣款)。
+ */
+ public const VIRTUAL_ORDER_KEYWORDS = ['信誉订单虚拟条码', '虚拟条码'];
+
+ public const VIRTUAL_ORDER_BARCODES = ['X00006', 'X00002'];
public const SKU_DATASET = 'operations_replica.rpt.jst_sku';
@@ -101,6 +109,20 @@ class PlantingLossCalculator
return false;
}
+ public static function isVirtualOrderItem(string $barcode, string $productName): bool
+ {
+ if (in_array(strtoupper(trim($barcode)), self::VIRTUAL_ORDER_BARCODES, true)) {
+ return true;
+ }
+ foreach (self::VIRTUAL_ORDER_KEYWORDS as $keyword) {
+ if (str_contains($productName, $keyword)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/** 成本价(元,字符串)× 数量 → 分。 */
public static function deductionCents(string $costPrice, int $qty): int
{
@@ -153,7 +175,13 @@ class PlantingLossCalculator
$deductLines = [];
$missingCost = [];
$cents = 0;
+ $virtualOrder = false;
foreach ($shipment['lines'] ?? [] as $line) {
+ if (self::isVirtualOrderItem($line['barcode'], $line['product_name'])) {
+ $virtualOrder = true;
+ }
+ }
+ foreach ($virtualOrder ? [] : ($shipment['lines'] ?? []) as $line) {
if (self::isFreeGoods($line['barcode'], $line['product_name'])) {
continue;
}
@@ -186,7 +214,8 @@ class PlantingLossCalculator
[$result, $basis] = match (true) {
$shipment === null => [self::RESULT_UNMATCHED, self::shippingLabel($month) . ' 发货数据中无此交易单号'],
$keyword !== null => [self::RESULT_EXEMPT, '备注含“' . $keyword . '”'],
- $deductLines === [] => [self::RESULT_FREE_GOODS, '只发了虚拟条码/透明挂钩'],
+ $virtualOrder => [self::RESULT_FREE_GOODS, '发货含信誉订单虚拟条码(同单号其它商品为合并发货的别的订单)'],
+ $deductLines === [] => [self::RESULT_FREE_GOODS, '只发了虚拟条码/透明挂钩/无实物商品'],
$missingText !== '' => [self::RESULT_DEDUCT, $missingText . ',扣款金额未含缺失部分'],
default => [self::RESULT_DEDUCT, '已匹配发货,按成本价×数量+运费扣款'],
};
diff --git a/resources/views/business/pages/billing-transactions/planting-loss.blade.php b/resources/views/business/pages/billing-transactions/planting-loss.blade.php
index a2be3683..d4b753e2 100644
--- a/resources/views/business/pages/billing-transactions/planting-loss.blade.php
+++ b/resources/views/business/pages/billing-transactions/planting-loss.blade.php
@@ -42,7 +42,7 @@
不扣款(虚拟条码/透明挂钩){{ number_format($free['count']) }}最终报销 {{ $money($free['reimbursement_amount']) }}
未匹配发货(不计入){{ number_format($unmatched['count']) }}最终报销 {{ $money($unmatched['reimbursement_amount']) }}
- ① 取该月种菜报销明细:工单类型“种菜报销”、状态“已完成”,完成时间 M 月 2 日 00:00 至次月 1 日 23:59:59(与「种菜报销」页同一口径)。② 用线上订单号(交易单号)精确匹配「快递应付 → 发货数据」中发货时间在上月 1 日至本月末的交易单号,匹配到的保留,同一单多个包裹/商品合并为一行。③ 匹配到的单中,任务备注或卖家备注写了“拍A发B”或“拍A发A”(不区分全半角、大小写和空格)的不做扣款,单独列出发给运营。④ 实际只发了“信誉订单虚拟条码”“透明挂钩”这类商品的不扣款;同一单里这类商品也不计入扣款。⑤ 其余匹配到的为扣款项:扣款金额 = Σ 发货条形码(商品编码)在「普通商品资料」中的成本价 × 发货数量 + 该订单在「发货数据」中的运费合计;普通商品资料里没有成本价(空或 0)的商品、发货数据里运费为空的订单分别标为“成本价缺失”“运费缺失”,缺失部分不计入扣款金额,补齐后自动重算。未匹配到发货的单不计入种菜损耗,仅供核对。 ① 取该月种菜报销明细:工单类型“种菜报销”、状态“已完成”,完成时间 M 月 2 日 00:00 至次月 1 日 23:59:59(与「种菜报销」页同一口径)。② 用线上订单号(交易单号)精确匹配「快递应付 → 发货数据」中发货时间在上月 1 日至本月末的交易单号,匹配到的保留,同一单多个包裹/商品合并为一行。③ 匹配到的单中,任务备注或卖家备注写了“拍A发B”或“拍A发A”(不区分全半角、大小写和空格)的不做扣款,单独列出发给运营。④ 发货数据里该交易单号下出现“信誉订单虚拟条码”(X00006/X00002)的整单不扣款——同单号下的其它商品是合并发货的别的订单;只发了“透明挂钩”“无实物”这类商品的也不扣款,同一单里这类商品不计入扣款。⑤ 其余匹配到的为扣款项:扣款金额 = Σ 发货条形码(商品编码)在「普通商品资料」中的成本价 × 发货数量 + 该订单在「发货数据」中的运费合计;普通商品资料里没有成本价(空或 0)的商品、发货数据里运费为空的订单分别标为“成本价缺失”“运费缺失”,缺失部分不计入扣款金额,补齐后自动重算。未匹配到发货的单不计入种菜损耗,仅供核对。计算口径
计算口径