131 lines
7.8 KiB
PHP
131 lines
7.8 KiB
PHP
<?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);
|
||
}
|
||
}
|