93 lines
5.7 KiB
PHP
93 lines
5.7 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\EcommerceFinance;
|
|
|
|
use Modules\EcommerceFinance\Repositories\ProductCatalogMirrorRepository;
|
|
use Modules\EcommerceFinance\Repositories\SupplierBookkeepingValuationRepository as Shipping;
|
|
use Modules\EcommerceFinance\Services\SupplierBookkeepingDeductionRule as Rule;
|
|
use Modules\EcommerceFinance\Services\SupplierBookkeepingProductResolver;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
class SupplierBookkeepingProductResolverTest extends TestCase
|
|
{
|
|
private function row(array $patch = []): array
|
|
{
|
|
return array_replace(['supplier' => '', 'product_code' => 'SKU-A', 'started_at' => '2026-10-15', 'tracking_no' => 'EXP-1',
|
|
'online_order_no' => 'ORDER-1', 'reason_level_1' => '厂家记账', 'bookkeeping_type' => '补偿款项', 'amount' => '100.00'], $patch);
|
|
}
|
|
|
|
private function service(array $products, array $shipments = []): SupplierBookkeepingProductResolver
|
|
{
|
|
$catalog = $this->createMock(ProductCatalogMirrorRepository::class);
|
|
$catalog->method('normalProductsBySkuCodes')->willReturnCallback(static fn ($codes) => array_values(array_filter($products,
|
|
static fn ($product) => in_array(Shipping::sku($product->sku_id), $codes, true))));
|
|
$shipping = $this->createMock(Shipping::class);
|
|
$shipping->method('shipmentsFor')->willReturn($shipments);
|
|
return new SupplierBookkeepingProductResolver($catalog, $shipping);
|
|
}
|
|
|
|
private function product(string $code, string $supplier, int $id = 1): object
|
|
{
|
|
return (object) ['autoid' => $id, 'sku_id' => $code, 'supplier_name' => $supplier];
|
|
}
|
|
|
|
public function test_exact_normal_product_wins_over_encoded_shape_and_latest_catalog_row_wins(): void
|
|
{
|
|
$code = 'MKH-SKU-A-1-SKU-B-1';
|
|
$row = $this->service([$this->product($code, '当前厂', 2), $this->product($code, '旧厂'), $this->product('SKU-A', '其他厂')])
|
|
->apply([$this->row(['product_code' => " '@mkh-sku-a-1-sku-b-1 "])])[0];
|
|
$this->assertSame('当前厂', $row['supplier']);
|
|
$this->assertSame($code, $row['resolved_product_code']);
|
|
$this->assertSame(" '@mkh-sku-a-1-sku-b-1 ", $row['product_code']);
|
|
}
|
|
|
|
public function test_encoded_components_use_each_catalog_supplier_before_existing_deduction_rules(): void
|
|
{
|
|
$row = $this->service([$this->product('SKU-A+B', '厂甲'), $this->product('GBGJ-SKU-B', '厂乙')])
|
|
->apply([$this->row(['product_code' => 'ZH-SKU-A+B-2-GBGJ-SKU-B-1'])])[0];
|
|
$this->assertSame("SKU-A+B\nGBGJ-SKU-B", $row['resolved_product_code']);
|
|
$this->assertSame("厂甲\n厂乙", $row['supplier']);
|
|
$this->assertStringContainsString('GBGJ-SKU-B→厂乙', $row['supplier_match_basis']);
|
|
$this->assertSame(Rule::STATUS_DEDUCT, Rule::decide($row, [])['deduction_status']);
|
|
$this->assertSame(Rule::STATUS_REVIEW, Rule::decide($row, ['厂乙' => ['mode' => Rule::MODE_NONE]])['deduction_status']);
|
|
$this->assertSame(Rule::STATUS_SKIP, Rule::decide($row, ['厂甲' => ['mode' => Rule::MODE_NONE], '厂乙' => ['mode' => Rule::MODE_NONE]])['deduction_status']);
|
|
$this->assertSame('0.00', Rule::decide(array_replace($row, ['amount' => '30.00', 'actual_paid_amount' => '100.00']), [])['amount']);
|
|
}
|
|
|
|
public function test_unknown_component_and_latest_blank_supplier_cannot_use_partial_supplier_names(): void
|
|
{
|
|
foreach ([[$this->product('SKU-A', '厂甲')], [$this->product('SKU-A', '厂甲'), $this->product('SKU-B', '旧厂'), $this->product('SKU-B', '', 2)]] as $products) {
|
|
$row = $this->service($products)->apply([$this->row(['product_code' => 'ZH-SKU-A-1-SKU-B-1'])])[0];
|
|
$this->assertSame('', $row['supplier']);
|
|
$this->assertStringContainsString('SKU-B→未匹配供应商', $row['supplier_match_basis']);
|
|
$this->assertSame(Rule::STATUS_REVIEW, Rule::decide($row, [])['deduction_status']);
|
|
}
|
|
}
|
|
|
|
public function test_named_bundle_uses_only_complete_original_shipment_references(): void
|
|
{
|
|
$shipment = ['tracking_numbers' => ['EXP-1'], 'order_numbers' => ['ORDER-1'], 'ship_time' => '2026-10-01 00:00:00',
|
|
'barcode' => 'SKU-A,SKU-B', 'unshipped' => '#N/A'];
|
|
$products = [$this->product('SKU-A', '同厂'), $this->product('SKU-B', '同厂')];
|
|
$row = $this->service($products, [$shipment])->apply([$this->row(['product_code' => '运营组合套装123', 'tracking_no' => " '@EXP-1 "])])[0];
|
|
$this->assertSame('同厂', $row['supplier']);
|
|
$this->assertSame("SKU-A\nSKU-B", $row['resolved_product_code']);
|
|
foreach ([['tracking_numbers' => ['EXP-OTHER']], ['order_numbers' => ['ORDER-OTHER']], ['ship_time' => '2026-04-30 00:00:00'], ['unshipped' => 'EXP-1']] as $patch) {
|
|
$failed = $this->service($products, [array_replace($shipment, $patch)])->apply([$this->row(['product_code' => '运营组合套装123'])])[0];
|
|
$this->assertSame('', $failed['supplier']);
|
|
}
|
|
$partial = $this->service($products, [$shipment])->apply([$this->row(['product_code' => '运营组合套装123', 'tracking_no' => 'EXP-1,EXP-2'])])[0];
|
|
$this->assertSame('', $partial['supplier']);
|
|
}
|
|
|
|
public function test_existing_supplier_is_preserved_without_reading_product_or_shipping_data(): void
|
|
{
|
|
$products = $this->createMock(ProductCatalogMirrorRepository::class);
|
|
$products->expects($this->never())->method('normalProductsBySkuCodes');
|
|
$shipping = $this->createMock(Shipping::class);
|
|
$shipping->expects($this->never())->method('shipmentsFor');
|
|
$rows = [$this->row(['supplier' => '工单厂家'])];
|
|
$this->assertSame($rows, (new SupplierBookkeepingProductResolver($products, $shipping))->apply($rows));
|
|
}
|
|
}
|