'879735', 'date' => '2026-09-10', 'skuCode' => 'DNZ033609', 'productCode' => 'DNZ033609N1178V01', 'unitPrice' => '100', 'qty' => '20', 'amount' => '2000', ]]; $theirs = [[ 'purchaseNo' => '879735', 'createdAt' => '2026-09-17', 'barcode' => 'DNZ033609N1097V01', 'unitPrice' => '100', 'qty' => '20', 'amount' => '2000', ]]; $result = (new SupplierStatementMatcher())->compare($mine, $theirs); $this->assertSame(1, $result['summary']['match']); $this->assertSame(0, $result['summary']['supplierExtra']); $this->assertSame(0, $result['summary']['supplierMissing']); $this->assertSame(0, $result['summary']['mismatch']); $this->assertSame(0.0, $result['summary']['differenceAmount']); $this->assertSame(2000.0, $result['summary']['settleAmount']); $this->assertSame(7, $result['rows'][0]['dateToleranceDays']); $this->assertStringContainsString('日期相差 7 天', $result['rows'][0]['note']); } public function test_date_difference_over_seven_days_remains_a_difference(): void { $mine = [[ 'purchaseNo' => '879735', 'date' => '2026-09-10', 'skuCode' => 'DNZ033609', 'qty' => '20', 'amount' => '2000', ]]; $theirs = [[ 'purchaseNo' => '879735', 'createdAt' => '2026-09-18', 'barcode' => 'DNZ033609N1097V01', 'qty' => '20', 'amount' => '2000', ]]; $result = (new SupplierStatementMatcher())->compare($mine, $theirs); $this->assertSame(0, $result['summary']['match']); $this->assertSame(1, $result['summary']['supplierExtra']); $this->assertSame(1, $result['summary']['supplierMissing']); } public function test_date_tolerance_still_requires_equal_quantity_and_amount(): void { $mine = [[ 'purchaseNo' => '879735', 'date' => '2026-09-10', 'skuCode' => 'DNZ033609', 'qty' => '20', 'amount' => '2000', ]]; $theirs = [[ 'purchaseNo' => '879735', 'createdAt' => '2026-09-12', 'barcode' => 'DNZ033609N1097V01', 'qty' => '19', 'amount' => '1900', ]]; $result = (new SupplierStatementMatcher())->compare($mine, $theirs); $this->assertSame(0, $result['summary']['match']); $this->assertSame(1, $result['summary']['supplierExtra']); $this->assertSame(1, $result['summary']['supplierMissing']); } public function test_unrecognized_supplier_date_matches_unique_purchase_and_barcode(): void { $mine = [[ 'purchaseNo' => '879735', 'date' => '2026-10-10', 'skuCode' => 'SJ009363', 'productCode' => 'SJ009363N160V01', 'qty' => '130', 'amount' => '12350', ]]; $theirs = [[ 'purchaseNo' => '879735', 'createdAt' => '2026.1', 'barcode' => 'SJ009363N160V01', 'qty' => '130', 'amount' => '12350', ]]; $result = (new SupplierStatementMatcher())->compare($mine, $theirs); $this->assertSame(1, $result['summary']['match']); $this->assertSame(0, $result['summary']['supplierExtra']); $this->assertSame(0, $result['summary']['supplierMissing']); $this->assertSame(0, $result['summary']['mismatch']); $this->assertSame('2026-10-10', $result['rows'][0]['date']); $this->assertStringContainsString('厂家创建时间无法识别', $result['rows'][0]['note']); } public function test_unrecognized_supplier_date_does_not_guess_when_purchase_and_barcode_are_ambiguous(): void { $mine = [ [ 'purchaseNo' => '879735', 'date' => '2026-10-10', 'skuCode' => 'SJ009363', 'qty' => '130', 'amount' => '12350', ], [ 'purchaseNo' => '879735', 'date' => '2026-10-12', 'skuCode' => 'SJ009363', 'qty' => '130', 'amount' => '12350', ], ]; $theirs = [[ 'purchaseNo' => '879735', 'createdAt' => '2026.1', 'barcode' => 'SJ009363N160V01', 'qty' => '130', 'amount' => '12350', ]]; $result = (new SupplierStatementMatcher())->compare($mine, $theirs); $this->assertSame(0, $result['summary']['match']); $this->assertSame(1, $result['summary']['supplierExtra']); $this->assertSame(2, $result['summary']['supplierMissing']); } public function test_unrecognized_supplier_date_matches_mine_rows_aggregated_across_dates(): void { $mine = [ [ 'purchaseNo' => '923923', 'date' => '2026-10-03', 'skuCode' => 'CBG011611', 'qty' => '36', 'amount' => '2466', ], [ 'purchaseNo' => '923923', 'date' => '2026-10-04', 'skuCode' => 'CBG011611', 'qty' => '44', 'amount' => '3014', ], ]; $theirs = [[ 'purchaseNo' => '923923', 'createdAt' => '2026.1', 'barcode' => 'CBG011611N160V01', 'qty' => '80', 'amount' => '5480', ]]; $result = (new SupplierStatementMatcher())->compare($mine, $theirs); $this->assertSame(1, $result['summary']['match']); $this->assertSame(0, $result['summary']['supplierExtra']); $this->assertSame(0, $result['summary']['supplierMissing']); $this->assertSame(80.0, $result['rows'][0]['mineQty']); $this->assertSame(5480.0, $result['rows'][0]['mineAmount']); $this->assertSame(2, $result['rows'][0]['dateCount']); $this->assertStringContainsString('汇总我方 2 个日期', $result['rows'][0]['note']); } public function test_duplicate_mine_rows_keep_the_purchase_number_present_in_supplier_data(): void { $mine = [ [ 'purchaseNo' => '911299', 'date' => '2026-10-10', 'skuCode' => 'XG007322', 'productCode' => 'XG007322N160V01', 'qty' => '30', 'amount' => '4530', ], [ 'purchaseNo' => '941858', 'date' => '2026-10-10', 'skuCode' => 'XG007322', 'productCode' => 'XG007322N160V01', 'qty' => '30', 'amount' => '4530', ], ]; $theirs = [[ 'purchaseNo' => '941858', 'createdAt' => '2026.1', 'barcode' => 'XG007322N160V01', 'qty' => '30', 'amount' => '4530', ]]; $result = (new SupplierStatementMatcher())->compare($mine, $theirs); $this->assertSame(1, $result['summary']['match']); $this->assertSame(0, $result['summary']['supplierExtra']); $this->assertSame(0, $result['summary']['supplierMissing']); $this->assertSame('941858', $result['rows'][0]['purchaseNo']); } public function test_regression_keeps_only_the_real_supplier_extra_line(): void { $mine = [ [ 'purchaseNo' => '911299', 'date' => '2026-10-10', 'skuCode' => 'XG007322', 'qty' => '30', 'amount' => '4530', ], [ 'purchaseNo' => '941858', 'date' => '2026-10-10', 'skuCode' => 'XG007322', 'qty' => '30', 'amount' => '4530', ], [ 'purchaseNo' => '923923', 'date' => '2026-10-03', 'skuCode' => 'CBG011611', 'qty' => '36', 'amount' => '2466', ], [ 'purchaseNo' => '923923', 'date' => '2026-10-04', 'skuCode' => 'CBG011611', 'qty' => '44', 'amount' => '3014', ], [ 'purchaseNo' => '937779', 'date' => '2026-10-20', 'skuCode' => 'SJ009363', 'qty' => '130', 'amount' => '12350', ], ]; $theirs = [ [ 'purchaseNo' => '941858', 'createdAt' => '2026.1', 'barcode' => 'XG007322N160V01', 'qty' => '30', 'amount' => '4530', ], [ 'purchaseNo' => '923923', 'createdAt' => '2026.1', 'barcode' => 'CBG011611N160V01', 'qty' => '80', 'amount' => '5480', ], [ 'purchaseNo' => '937779', 'createdAt' => '2026.1', 'barcode' => 'SJ009363N160V01', 'qty' => '130', 'amount' => '12350', ], [ 'purchaseNo' => '922003', 'createdAt' => '2026.1', 'barcode' => 'SJ009363N160V01', 'qty' => '20', 'amount' => '0', ], ]; $result = (new SupplierStatementMatcher())->compare($mine, $theirs); $differences = array_values(array_filter( $result['rows'], static fn (array $row): bool => $row['type'] !== SupplierStatementMatcher::TYPE_MATCH, )); $this->assertSame(3, $result['summary']['match']); $this->assertSame(1, $result['summary']['supplierExtra']); $this->assertSame(0, $result['summary']['supplierMissing']); $this->assertCount(1, $differences); $this->assertSame('922003', $differences[0]['purchaseNo']); $this->assertSame('SJ009363N160V01', $differences[0]['barcode']); $this->assertSame(20.0, $differences[0]['qtyDifference']); } }