'T001', 'warehouse_name' => '成品仓', 'barcode' => 'GBGJ-SJ038024N1410V02,OTHER-SKU', 'express_no' => 'SF001', 'express_name1' => '首发快递', 'ship_time' => '2026-08-01 12:00:00'], $changes); } private function accessory(array $changes = []): array { return array_replace(['trade_no' => 'T001', 'barcode' => 'GBGJ-SJ038024N1410V02-10,OTHER-SKU-2', 'freight' => '12.30'], $changes); } public function test_first_accessory_barcode_and_last_suffix_only(): void { $this->assertSame('GBGJ-SJ038024N1410V02', AccessoryReshipMatcher::barcode('GBGJ-SJ038024N1410V02-10,GBGJ-SJ038024N1410V02-2,GBGJ-SJ038024N1410V02-7')); $this->assertSame('ABC', AccessoryReshipMatcher::barcode('ABC')); $this->assertSame('', AccessoryReshipMatcher::barcode('')); } public function test_exact_trade_and_split_original_barcodes_without_normalizing_original(): void { $matcher = new AccessoryReshipMatcher; $result = $matcher->match([$this->accessory(), $this->accessory(['barcode' => 'OTHER-SKU-2']), $this->accessory(['trade_no' => 'T002'])], [$this->original()]); $this->assertSame(['matched', 'matched', 'unmatched'], array_column($result, 'status')); $this->assertSame(1230, $result[0]['deduction_cents']); $this->assertSame('2026-08', $result[0]['matches'][0]['month']); $this->assertSame('unmatched', $matcher->match([$this->accessory()], [$this->original(['barcode' => 'GBGJ-SJ038024N1410V02-10'])])[0]['status']); } public function test_blank_waybill_accessory_warehouse_and_empty_keys_do_not_qualify(): void { $matcher = new AccessoryReshipMatcher; $result = $matcher->match([$this->accessory(), $this->accessory(['trade_no' => '']), $this->accessory(['barcode' => ''])], [$this->original(['express_no' => ' ']), $this->original(['warehouse_name' => AccessoryReshipMatcher::WAREHOUSE])]); $this->assertSame(['unmatched', 'unmatched', 'unmatched'], array_column($result, 'status')); $this->assertSame([0, 0, 0], array_column($result, 'deduction_cents')); } public function test_multiple_matches_keep_one_deduction_and_deduplicate_same_waybill(): void { $matcher = new AccessoryReshipMatcher; $originals = [$this->original(), $this->original(), $this->original(['express_no' => 'YTO002', 'express_name1' => '另一快递', 'ship_time' => '2026-09-01'])]; $result = $matcher->match([$this->accessory()], $originals); $this->assertCount(1, $result); $this->assertCount(2, $result[0]['matches']); $this->assertSame(1230, $result[0]['deduction_cents']); $this->assertSame($result, $matcher->match([$this->accessory()], $originals)); } public function test_invalid_amount_stays_matched_but_cannot_supply_amount(): void { foreach ([null, '', '-2.00', 'abc'] as $value) { $row = (new AccessoryReshipMatcher)->match([$this->accessory(['freight' => $value])], [$this->original()])[0]; $this->assertSame('matched', $row['status']); $this->assertNull($row['deduction_cents']); } $this->assertSame(1230, AccessoryReshipMatcher::cents('12.3000')); $this->assertSame(1240, AccessoryReshipMatcher::cents('12.395')); $this->assertSame(0, AccessoryReshipMatcher::cents('0.00')); } }