CaiWuXiTong/tests/Unit/EcommerceFinance/SupplierBookkeepingDeductionRuleTest.php

131 lines
7.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Tests\Unit\EcommerceFinance;
use InvalidArgumentException;
use Modules\EcommerceFinance\Services\SupplierBookkeepingDeductionRule as Rule;
use PHPUnit\Framework\TestCase;
require_once dirname(__DIR__, 3) . '/modules/EcommerceFinance/Services/SupplierBookkeepingDeductionRule.php';
class SupplierBookkeepingDeductionRuleTest extends TestCase
{
public function test_parses_every_rule_text_in_finance_sheet(): void
{
$this->assertSame(['mode' => Rule::MODE_NONE, 'effective_from' => null, 'effective_to' => null], Rule::parseRuleText('不记账'));
$this->assertSame(Rule::MODE_NORMAL, Rule::parseRuleText('正常记账')['mode']);
$this->assertSame(Rule::MODE_DROPSHIP_WRONG, Rule::parseRuleText('代发仓发货错需记账,其他原因均不记账')['mode']);
$this->assertSame(Rule::MODE_DROPSHIP_WRONG, Rule::parseRuleText('厂家暂未出货,后期若建立代发仓,发货错需记账,其他原因均不记账')['mode']);
$this->assertSame(['mode' => Rule::MODE_NONE, 'effective_from' => '2025-03-01', 'effective_to' => '2026-02-28'], Rule::parseRuleText('不记账(2025.3月-2026.2月)'));
$this->assertSame(['mode' => Rule::MODE_NONE, 'effective_from' => '2025-03-01', 'effective_to' => '2027-12-30'], Rule::parseRuleText('不记账(2025.3月-2027.12.30)'));
$this->assertSame(Rule::MODE_DROPSHIP_WRONG, Rule::parseRuleText('仅代发仓发错货记账')['mode']);
}
public function test_rejects_unknown_rule_text(): void
{
$this->expectException(InvalidArgumentException::class);
Rule::parseRuleText('看情况');
}
private function row(array $patch = []): array
{
return array_replace([
'started_at' => '2026-09-05 10:00:00', 'reason_level_1' => '厂家记账', 'reason_level_2' => '孔位错误',
'shipping_warehouse' => '常州主仓', 'supplier' => '甲厂',
], $patch);
}
private function configs(): array
{
return [
'甲厂' => ['mode' => Rule::MODE_NORMAL, 'effective_from' => null, 'effective_to' => null],
'乙厂' => ['mode' => Rule::MODE_DROPSHIP_WRONG, 'effective_from' => null, 'effective_to' => null],
'丙厂' => ['mode' => Rule::MODE_NONE, 'effective_from' => null, 'effective_to' => null],
'丁厂' => ['mode' => Rule::MODE_NONE, 'effective_from' => '2025-03-01', 'effective_to' => '2026-02-28'],
];
}
private function judge(array $patch): string
{
return Rule::decide($this->row($patch), $this->configs())['deduction_status'];
}
public function test_supplier_reasons_follow_supplier_config(): void
{
$this->assertSame(Rule::STATUS_DEDUCT, $this->judge([]));
$this->assertSame(Rule::STATUS_SKIP, $this->judge(['supplier' => '乙厂']));
$this->assertSame(Rule::STATUS_SKIP, $this->judge(['supplier' => '丙厂']));
// 配置表中没有的供应商视同正常记账,可扣款。
$this->assertSame(Rule::STATUS_DEDUCT, $this->judge(['supplier' => '未知厂']));
$this->assertSame(Rule::STATUS_REVIEW, $this->judge(['supplier' => '']));
// 限期不记账:期外视同不在配置表,可扣款。
$this->assertSame(Rule::STATUS_DEDUCT, $this->judge(['supplier' => '丁厂']));
$this->assertSame(Rule::STATUS_SKIP, $this->judge(['supplier' => '丁厂', 'started_at' => '2026-02-28 23:00:00']));
}
public function test_warehouse_reasons_only_deduct_dropship_wrong_shipment(): void
{
$wrong = ['reason_level_1' => '仓库记账', 'reason_level_2' => '仓库发错货(面单照片,实物照片,条形码照片)', 'shipping_warehouse' => '曹县洪耀代发仓'];
$this->assertSame(Rule::STATUS_DEDUCT, $this->judge($wrong + ['supplier' => '乙厂']));
$this->assertSame(Rule::STATUS_DEDUCT, $this->judge($wrong));
$this->assertSame(Rule::STATUS_SKIP, $this->judge($wrong + ['supplier' => '丙厂']));
$this->assertSame(Rule::STATUS_DEDUCT, $this->judge($wrong + ['supplier' => '未知厂']));
// 非代发仓、或代发仓的其他仓库记账原因:一律不作为厂家扣款项,无需配置。
$this->assertSame(Rule::STATUS_SKIP, $this->judge(['shipping_warehouse' => '常州主仓'] + $wrong + ['supplier' => '乙厂']));
$this->assertSame(Rule::STATUS_SKIP, $this->judge(['reason_level_2' => '仓库双面单'] + $wrong + ['supplier' => '乙厂']));
$this->assertSame(Rule::STATUS_SKIP, $this->judge(['reason_level_2' => '退货运费'] + $wrong + ['supplier' => '未知厂']));
}
public function test_multiple_suppliers_need_review_when_outcomes_differ(): void
{
$this->assertSame(Rule::STATUS_DEDUCT, $this->judge(['supplier' => "甲厂\n甲厂"]));
$result = Rule::decide($this->row(['supplier' => "甲厂\n丙厂"]), $this->configs());
$this->assertSame(Rule::STATUS_REVIEW, $result['deduction_status']);
$this->assertStringContainsString('甲厂:扣款', $result['deduction_basis']);
$this->assertSame("正常记账\n不记账", $result['supplier_config']);
}
public function test_small_compensation_uses_bookkeeping_divided_by_buyer_paid_and_preserves_notes(): void
{
foreach (['补偿款项', '记账全款', '记账全额'] as $type) {
$row = $this->row(['bookkeeping_type' => $type, 'amount' => '30.00', 'buyer_paid_amount' => '100', 'remark' => '协商补偿']);
$result = Rule::decide($row, $this->configs());
$this->assertSame('0.00', $result['amount']);
$this->assertSame(Rule::STATUS_SKIP, $result['deduction_status']);
$this->assertSame("协商补偿\n小于50且小于0.4,记账改0", $result['remark']);
$this->assertSame('30.00', $row['amount']);
}
$multiple = Rule::decide($this->row(['bookkeeping_type' => '补偿款项', 'amount' => '30.00', 'buyer_paid_amount' => "40\n60"]), []);
$this->assertSame('0.00', $multiple['amount']);
}
public function test_small_compensation_requires_both_strict_limits_and_complete_positive_paid_amount(): void
{
foreach ([
['50.00', '200'], ['49.99', '124.975'], ['40.00', '100'], ['39.99', '99'],
['30.00', '10'], ['30.00', ''], ['30.00', '0'], ['30.00', '-100'], ['30.00', "100\n"],
['30.00', "\n100"], ['30.00', 'invalid'], ['0.00', '100'], ['-1.00', '100'], ['', '100'],
] as [$amount, $paid]) {
$result = Rule::decide($this->row(['bookkeeping_type' => '补偿款项', 'amount' => $amount, 'buyer_paid_amount' => $paid]), $this->configs());
$this->assertArrayNotHasKey('amount', $result, "$amount / $paid");
}
$hit = Rule::decide($this->row(['bookkeeping_type' => '补偿款项', 'amount' => '39.99', 'buyer_paid_amount' => '100']), $this->configs());
$this->assertSame('0.00', $hit['amount']);
$freight = Rule::decide($this->row(['bookkeeping_type' => '运费损耗', 'amount' => '10.00', 'buyer_paid_amount' => '100']), $this->configs());
$this->assertArrayNotHasKey('amount', $freight);
}
public function test_second_round_uses_order_actual_paid_when_buyer_paid_round_does_not_hit(): void
{
foreach (['10', '', '0', "100\n"] as $buyer) {
$result = Rule::decide($this->row(['bookkeeping_type' => '补偿款项', 'amount' => '30.00',
'buyer_paid_amount' => $buyer, 'actual_paid_amount' => '100.00']), $this->configs());
$this->assertSame('0.00', $result['amount']);
$this->assertSame(Rule::STATUS_SKIP, $result['deduction_status']);
}
$equal = Rule::decide($this->row(['bookkeeping_type' => '记账全款', 'amount' => '40.00',
'buyer_paid_amount' => '50', 'actual_paid_amount' => '100.00']), $this->configs());
$this->assertArrayNotHasKey('amount', $equal);
}
}