From 4c617fd06377e9d54794a88b55993f3d5bd7cc94 Mon Sep 17 00:00:00 2001 From: limeng <1131177064@qq.com> Date: Mon, 19 Oct 2026 16:46:46 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E7=A7=8D=E8=8F=9C=E6=8D=9F=E8=80=97):=20?= =?UTF-8?q?=E6=89=A3=E6=AC=BE=E9=87=91=E9=A2=9D=E6=94=B9=E4=B8=BA=20?= =?UTF-8?q?=E6=88=90=E6=9C=AC=E4=BB=B7=C3=97=E6=95=B0=E9=87=8F=20+=20?= =?UTF-8?q?=E8=AF=A5=E5=8D=95=E5=8F=91=E8=B4=A7=E8=BF=90=E8=B4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 财务更正口径:种菜损耗扣款 = Σ 发货条码在普通商品资料的成本价 × 数量 + 该订单在发货数据中的运费合计, 不再按成本价×1.2;运费为空的订单标"运费缺失",缺失部分不计入扣款金额。 涉及:金额计算展示(只读);不涉及迁移、权限和上游写入。 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VANWYsNUbu2piMtCKymoyd --- .../Services/PlantingLossCalculator.php | 31 +++++++++++++------ .../View/PlantingLossPageData.php | 4 +-- .../planting-loss.blade.php | 6 ++-- .../Business/PlantingWorkOrderPagesTest.php | 5 ++- .../PlantingLossCalculatorTest.php | 12 +++---- 5 files changed, 36 insertions(+), 22 deletions(-) diff --git a/modules/EcommerceFinance/Services/PlantingLossCalculator.php b/modules/EcommerceFinance/Services/PlantingLossCalculator.php index e05ea5bf..6d40c670 100644 --- a/modules/EcommerceFinance/Services/PlantingLossCalculator.php +++ b/modules/EcommerceFinance/Services/PlantingLossCalculator.php @@ -16,7 +16,7 @@ use Modules\EcommerceFinance\Repositories\ShippingOrderRepository; * 2. 用线上订单号去匹配发货数据(jst_oms_shipping)中 M-1、M 两个月发货的交易单号,匹配到的保留; * 3. 匹配到的单里,任务备注或卖家备注写了“拍A发B”或“拍A发A”的不扣款,单独列出发给运营; * 4. 实际只发了「信誉订单虚拟条码」「透明挂钩」这类商品的不扣款; - * 5. 其余匹配到的作为扣款项,扣款金额 = Σ 发货商品成本价 × 数量 × 1.2(成本价+成本价×20%), + * 5. 其余匹配到的作为扣款项,扣款金额 = Σ 发货商品成本价 × 数量 + 该订单在发货数据中的运费(2026-10-19 更正), * 成本价按发货条形码(=商品编码)取普通商品资料。未匹配到发货的不计入种菜损耗。 */ class PlantingLossCalculator @@ -34,9 +34,6 @@ class PlantingLossCalculator /** 发货商品名称或条码含这些字样的不计扣款。 */ public const FREE_GOODS_KEYWORDS = ['信誉订单虚拟条码', '虚拟条码', '透明挂钩']; - /** 扣款 = 成本价 × (1 + 20%)。 */ - public const MARKUP_PERCENT = 20; - public const SKU_DATASET = 'operations_replica.rpt.jst_sku'; private const CHUNK = 1000; @@ -104,10 +101,10 @@ class PlantingLossCalculator return false; } - /** 成本价(元,字符串)× 数量 × 1.2 → 分。 */ + /** 成本价(元,字符串)× 数量 → 分。 */ public static function deductionCents(string $costPrice, int $qty): int { - return (int) round((float) $costPrice * 100 * $qty * (100 + self::MARKUP_PERCENT) / 100); + return (int) round((float) $costPrice * 100 * $qty); } /** @@ -168,23 +165,37 @@ class PlantingLossCalculator } $lineCents = self::deductionCents($cost, $line['qty']); $cents += $lineCents; - $deductLines[] = sprintf('%s × %d × %s × 1.2 = %s', $line['barcode'], $line['qty'], $cost, number_format($lineCents / 100, 2, '.', '')); + $deductLines[] = sprintf('%s × %d × %s = %s', $line['barcode'], $line['qty'], $cost, number_format($lineCents / 100, 2, '.', '')); } + $freight = $shipment['freight'] ?? null; + if ($deductLines !== []) { + if ($freight === null) { + $deductLines[] = '运费缺失'; + } else { + $cents += (int) round((float) $freight * 100); + $deductLines[] = '运费 ' . $freight; + } + } + $missing = array_values(array_unique($missingCost)); + $missingText = implode(';', array_filter([ + $missing === [] ? '' : '成本价缺失:' . implode('、', $missing), + $deductLines !== [] && $freight === null ? '运费缺失' : '', + ])); $keyword = $shipment === null ? null : self::exemptionKeyword((string) $row['task_remark'], (string) $row['seller_remark']); [$result, $basis] = match (true) { $shipment === null => [self::RESULT_UNMATCHED, self::shippingLabel($month) . ' 发货数据中无此交易单号'], $keyword !== null => [self::RESULT_EXEMPT, '备注含“' . $keyword . '”'], $deductLines === [] => [self::RESULT_FREE_GOODS, '只发了虚拟条码/透明挂钩'], - $missingCost !== [] => [self::RESULT_DEDUCT, '成本价缺失:' . implode('、', array_unique($missingCost)) . ',扣款金额未含该商品'], - default => [self::RESULT_DEDUCT, '已匹配发货,按成本价×1.2扣款'], + $missingText !== '' => [self::RESULT_DEDUCT, $missingText . ',扣款金额未含缺失部分'], + default => [self::RESULT_DEDUCT, '已匹配发货,按成本价×数量+运费扣款'], }; $row['loss_result'] = $result; $row['loss_basis'] = $basis; $row['deduction_amount'] = $result === self::RESULT_DEDUCT ? number_format($cents / 100, 2, '.', '') : null; $row['deduction_detail'] = $result === self::RESULT_DEDUCT ? implode("\n", $deductLines) : ''; - $row['cost_missing'] = $result === self::RESULT_DEDUCT && $missingCost !== []; + $row['cost_missing'] = $result === self::RESULT_DEDUCT && $missingText !== ''; $row['shipped_at'] = $shipment['shipped_at'] ?? ''; $row['express_no'] = $shipment['express_no'] ?? ''; $row['warehouse_name'] = $shipment['warehouse_name'] ?? ''; diff --git a/modules/EcommerceFinance/View/PlantingLossPageData.php b/modules/EcommerceFinance/View/PlantingLossPageData.php index c6768b8c..f2fbaf17 100644 --- a/modules/EcommerceFinance/View/PlantingLossPageData.php +++ b/modules/EcommerceFinance/View/PlantingLossPageData.php @@ -6,7 +6,7 @@ use Illuminate\Pagination\LengthAwarePaginator; use Modules\EcommerceFinance\Repositories\PlantingReimbursementRepository as Repository; use Modules\EcommerceFinance\Services\PlantingLossCalculator as Calculator; -/** 种菜数据汇总 → 种菜损耗:种菜报销明细匹配近两个月发货数据,拍A发B/拍A发A、只发虚拟条码/透明挂钩的不扣款,其余按成本价×1.2 扣款。 */ +/** 种菜数据汇总 → 种菜损耗:种菜报销明细匹配近两个月发货数据,拍A发B/拍A发A、只发虚拟条码/透明挂钩的不扣款,其余按成本价×数量+运费扣款。 */ class PlantingLossPageData { use PlantingPageFilters; @@ -25,7 +25,7 @@ class PlantingLossPageData ]; public const COLUMNS = [ - 'loss_result' => '损耗判定', 'deduction_amount' => '扣款金额', 'loss_basis' => '判定说明', 'deduction_detail' => '扣款计算(条码×数量×成本价×1.2)', + 'loss_result' => '损耗判定', 'deduction_amount' => '扣款金额', 'loss_basis' => '判定说明', 'deduction_detail' => '扣款计算(条码×数量×成本价+运费)', 'completed_at' => '报销完成时间', 'work_order_no' => '工单编号', 'platform' => '平台', 'shop_name' => '店铺名称', 'online_order_no' => '线上订单号', 'task_remark' => '任务备注', 'seller_remark' => '卖家备注', 'actual_paid_amount' => '实付金额', 'task_payment' => '任务付款金额', 'reimbursement_amount' => '最终报销金额', 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 d4efda69..a2be3683 100644 --- a/resources/views/business/pages/billing-transactions/planting-loss.blade.php +++ b/resources/views/business/pages/billing-transactions/planting-loss.blade.php @@ -13,7 +13,7 @@ @endphp
-

{{ $plantingFilters['month'] }} 月种菜报销明细(完成时间 {{ substr($plantingWindow['from'], 0, 10) }} 至 {{ \Illuminate\Support\Carbon::parse($plantingWindow['to'])->subDay()->format('Y-m-d') }})的线上订单号,匹配 {{ $shippingLabel }} 发货数据(发货时间 {{ substr($plantingShippingWindow['from'], 0, 10) }} 至 {{ \Illuminate\Support\Carbon::parse($plantingShippingWindow['to'])->subDay()->format('Y-m-d') }})中的交易单号。匹配到的单中,任务备注或卖家备注写了“拍A发B / 拍A发A”的、以及只发了信誉订单虚拟条码/透明挂钩的不扣款,其余按发货商品成本价×1.2 扣款。

+

{{ $plantingFilters['month'] }} 月种菜报销明细(完成时间 {{ substr($plantingWindow['from'], 0, 10) }} 至 {{ \Illuminate\Support\Carbon::parse($plantingWindow['to'])->subDay()->format('Y-m-d') }})的线上订单号,匹配 {{ $shippingLabel }} 发货数据(发货时间 {{ substr($plantingShippingWindow['from'], 0, 10) }} 至 {{ \Illuminate\Support\Carbon::parse($plantingShippingWindow['to'])->subDay()->format('Y-m-d') }})中的交易单号。匹配到的单中,任务备注或卖家备注写了“拍A发B / 拍A发A”的、以及只发了信誉订单虚拟条码/透明挂钩的不扣款,其余按发货商品成本价×数量+该单运费扣款。

{{ $syncLabels[$plantingSync['code']] ?? $plantingSync['code'] }}@if($plantingSync['last_succeeded_at']) · {{ \Illuminate\Support\Str::of($plantingSync['last_succeeded_at'])->substr(0, 16) }}@endif {{ $shippingLabels[$plantingShippingSync['code'] ?? 'unavailable'] ?? $plantingShippingSync['code'] }}@if($plantingShippingSync['last_succeeded_at'] ?? null) · {{ \Illuminate\Support\Str::of($plantingShippingSync['last_succeeded_at'])->substr(0, 16) }}@endif @@ -37,12 +37,12 @@ @php $deduct = $plantingSummary[Calculator::RESULT_DEDUCT]; $exempt = $plantingSummary[Calculator::RESULT_EXEMPT]; $free = $plantingSummary[Calculator::RESULT_FREE_GOODS]; $unmatched = $plantingSummary[Calculator::RESULT_UNMATCHED]; @endphp -
计算口径

① 取该月种菜报销明细:工单类型“种菜报销”、状态“已完成”,完成时间 M 月 2 日 00:00 至次月 1 日 23:59:59(与「种菜报销」页同一口径)。② 用线上订单号(交易单号)精确匹配「快递应付 → 发货数据」中发货时间在上月 1 日至本月末的交易单号,匹配到的保留,同一单多个包裹/商品合并为一行。③ 匹配到的单中,任务备注或卖家备注写了“拍A发B”或“拍A发A”(不区分全半角、大小写和空格)的不做扣款,单独列出发给运营。④ 实际只发了“信誉订单虚拟条码”“透明挂钩”这类商品的不扣款;同一单里这类商品也不计入扣款。⑤ 其余匹配到的为扣款项:扣款金额 = Σ 发货条形码(商品编码)在「普通商品资料」中的成本价 × 发货数量 × 1.2(成本价 + 成本价×20%);普通商品资料里没有成本价(空或 0)的商品标为“成本价缺失”,不计入扣款金额,需补齐成本价后自动重算。未匹配到发货的单不计入种菜损耗,仅供核对。

+
计算口径

① 取该月种菜报销明细:工单类型“种菜报销”、状态“已完成”,完成时间 M 月 2 日 00:00 至次月 1 日 23:59:59(与「种菜报销」页同一口径)。② 用线上订单号(交易单号)精确匹配「快递应付 → 发货数据」中发货时间在上月 1 日至本月末的交易单号,匹配到的保留,同一单多个包裹/商品合并为一行。③ 匹配到的单中,任务备注或卖家备注写了“拍A发B”或“拍A发A”(不区分全半角、大小写和空格)的不做扣款,单独列出发给运营。④ 实际只发了“信誉订单虚拟条码”“透明挂钩”这类商品的不扣款;同一单里这类商品也不计入扣款。⑤ 其余匹配到的为扣款项:扣款金额 = Σ 发货条形码(商品编码)在「普通商品资料」中的成本价 × 发货数量 + 该订单在「发货数据」中的运费合计;普通商品资料里没有成本价(空或 0)的商品、发货数据里运费为空的订单分别标为“成本价缺失”“运费缺失”,缺失部分不计入扣款金额,补齐后自动重算。未匹配到发货的单不计入种菜损耗,仅供核对。

@if(($plantingShippingSync['code'] ?? '') !== 'succeeded' && ! $plantingError)
发货数据当前状态为“{{ $shippingLabels[$plantingShippingSync['code'] ?? 'unavailable'] ?? $plantingShippingSync['code'] }}”{{ ($plantingShippingSync['warning'] ?? '') !== '' ? ':' . $plantingShippingSync['warning'] : '' }},匹配结果可能不完整。
@endif @if($plantingError)@endif
diff --git a/tests/Feature/Business/PlantingWorkOrderPagesTest.php b/tests/Feature/Business/PlantingWorkOrderPagesTest.php index 2540851d..0ffbde02 100644 --- a/tests/Feature/Business/PlantingWorkOrderPagesTest.php +++ b/tests/Feature/Business/PlantingWorkOrderPagesTest.php @@ -23,6 +23,7 @@ class PlantingWorkOrderPagesTest extends FeatureTestCase 'task_empty_package_fee' => '0.00', 'reimbursement_amount' => '39.79', 'payee_name' => '黄文梳', 'loss_result' => Calculator::RESULT_DEDUCT, 'loss_basis' => '已匹配发货,备注无拍A发B/拍A发A', 'shipped_amount' => '40.60', 'shipped_freight' => '3.20', 'express_no' => 'YT001', + 'deduction_amount' => '207.40', 'deduction_detail' => "DNZ-01 × 2 × 100.5 = 201.00\n运费 6.40", 'cost_missing' => false, ], $patch); } @@ -78,7 +79,9 @@ class PlantingWorkOrderPagesTest extends FeatureTestCase ->assertSee('GD565967381') ->assertDontSee('GD-EXEMPT') ->assertDontSee('GD-NONE') - ->assertSee('2026-08-01'); + ->assertSee('2026-08-01') + ->assertSee('207.40') + ->assertSee('DNZ-01 × 2 × 100.5 = 201.00'); $this->loginAs()->get($this->url('planting-loss', ['month' => '2026-09', 'result' => Calculator::RESULT_EXEMPT])) ->assertOk()->assertSee('GD-EXEMPT')->assertDontSee('GD565967381'); diff --git a/tests/Unit/EcommerceFinance/PlantingLossCalculatorTest.php b/tests/Unit/EcommerceFinance/PlantingLossCalculatorTest.php index 2790bced..153eb232 100644 --- a/tests/Unit/EcommerceFinance/PlantingLossCalculatorTest.php +++ b/tests/Unit/EcommerceFinance/PlantingLossCalculatorTest.php @@ -148,8 +148,8 @@ class PlantingLossCalculatorTest extends TestCase $this->assertTrue(Calculator::isFreeGoods('XN001', '信誉订单虚拟条码')); $this->assertTrue(Calculator::isFreeGoods('GG003-SZ', '透明挂钩3个')); $this->assertFalse(Calculator::isFreeGoods('DNZ-01', '儿童学习椅')); - $this->assertSame(12060, Calculator::deductionCents('100.5', 1)); - $this->assertSame(24120, Calculator::deductionCents('100.5', 2)); + $this->assertSame(10050, Calculator::deductionCents('100.5', 1)); + $this->assertSame(20100, Calculator::deductionCents('100.5', 2)); } public function test_loss_rows_match_shipments_and_split_exempt_from_deduct(): void @@ -187,9 +187,9 @@ class PlantingLossCalculatorTest extends TestCase $this->assertSame("YT001\nYT002", $rows['GD-DEDUCT']['express_no']); $this->assertSame('50.60', $rows['GD-DEDUCT']['shipped_amount']); $this->assertSame('3', $rows['GD-DEDUCT']['shipped_qty']); - // 取最新一条普通商品成本价 100.5:100.5 × 2 × 1.2 = 241.20,挂钩不计。 - $this->assertSame('241.20', $rows['GD-DEDUCT']['deduction_amount']); - $this->assertSame('DNZ-01 × 2 × 100.5 × 1.2 = 241.20', $rows['GD-DEDUCT']['deduction_detail']); + // 取最新一条普通商品成本价 100.5:100.5 × 2 = 201.00,挂钩不计货值;加该单运费 3.20 × 2 个包裹 = 6.40。 + $this->assertSame('207.40', $rows['GD-DEDUCT']['deduction_amount']); + $this->assertSame("DNZ-01 × 2 × 100.5 = 201.00\n运费 6.40", $rows['GD-DEDUCT']['deduction_detail']); $this->assertFalse($rows['GD-DEDUCT']['cost_missing']); $this->assertSame(Calculator::RESULT_FREE_GOODS, $rows['GD-VIRTUAL']['loss_result']); @@ -197,7 +197,7 @@ class PlantingLossCalculatorTest extends TestCase // 成本价为 0、或只有组合装资料的条码视为成本价缺失。 $this->assertSame(Calculator::RESULT_DEDUCT, $rows['GD-NOCOST']['loss_result']); - $this->assertSame('0.00', $rows['GD-NOCOST']['deduction_amount']); + $this->assertSame('6.40', $rows['GD-NOCOST']['deduction_amount']); $this->assertTrue($rows['GD-NOCOST']['cost_missing']); $this->assertStringContainsString('ZERO、DNZ-02', $rows['GD-NOCOST']['loss_basis']); $this->assertNull($rows['GD-EXEMPT']['deduction_amount']);