allocate( [ ['business_id' => 'approval-2', 'line_no' => 1, 'amount' => '100.00'], ['business_id' => 'approval-2', 'line_no' => 2, 'amount' => '200.00'], ], [[ 'business_id' => 'approval-2', 'process_instance_id' => 'process-2', 'payment_no' => 1, 'sequence_spec' => '1-2', 'amount' => '300.00', 'paid_at' => '2026-10-15 00:00', ]], 'personal_permission', Carbon::parse('2026-11-02 12:00:00'), ); $this->assertSame( [[1, 100.0], [2, 200.0]], array_map( static fn (array $row): array => [ (int) $row['line_no'], (float) $row['amount'], ], $rows, ), ); } public function test_it_allocates_partial_payments_to_the_correct_months(): void { Carbon::setTestNow('2026-11-02 12:00:00'); try { $rows = (new DingProcessPaymentLineAllocator())->allocate( [ ['business_id' => 'approval-1', 'line_no' => 1, 'amount' => '100.00'], ['business_id' => 'approval-1', 'line_no' => 2, 'amount' => '50.00'], ], [ [ 'business_id' => 'approval-1', 'process_instance_id' => 'process-1', 'payment_no' => 1, 'sequence_spec' => '1', 'amount' => '40.00', 'paid_at' => '2026-09-30 00:00', ], [ 'business_id' => 'approval-1', 'process_instance_id' => 'process-1', 'payment_no' => 2, 'sequence_spec' => '1', 'amount' => '60.00', 'paid_at' => '2026-10-15 00:00', ], [ 'business_id' => 'approval-1', 'process_instance_id' => 'process-1', 'payment_no' => 3, 'sequence_spec' => '2', 'amount' => '50.00', 'paid_at' => '2026-10-20 00:00', ], ], 'personal_permission', Carbon::parse('2026-11-02 12:00:00'), ); $this->assertSame( [ ['2026-09', '1', 40.0], ['2026-10', '1', 60.0], ['2026-10', '2', 50.0], ], array_map( static fn (array $row): array => [ $row['paid_month'], (string) $row['line_no'], (float) $row['amount'], ], $rows, ), ); } finally { Carbon::setTestNow(); } } public function test_it_allocates_an_invalid_sequence_to_the_unique_matching_detail(): void { $rows = (new DingProcessPaymentLineAllocator())->allocate( [ ['business_id' => 'approval-3', 'line_no' => 1, 'amount' => '100.00'], ['business_id' => 'approval-3', 'line_no' => 2, 'amount' => '200.00'], ], [[ 'business_id' => 'approval-3', 'process_instance_id' => 'process-3', 'payment_no' => 1, 'sequence_spec' => '9', 'amount' => '200.00', 'paid_at' => '2026-10-15 00:00', ]], 'personal_permission', Carbon::parse('2026-11-02 12:00:00'), ); $this->assertSame( [[2, 200.0]], array_map( static fn (array $row): array => [ (int) $row['line_no'], (float) $row['amount'], ], $rows, ), ); } public function test_it_allocates_a_payment_without_a_sequence_across_all_details(): void { $rows = (new DingProcessPaymentLineAllocator())->allocate( [ ['business_id' => 'approval-4', 'line_no' => 1, 'amount' => '100.00'], ['business_id' => 'approval-4', 'line_no' => 2, 'amount' => '200.00'], ], [[ 'business_id' => 'approval-4', 'process_instance_id' => 'process-4', 'payment_no' => 1, 'sequence_spec' => '', 'amount' => '300.00', 'paid_at' => '2026-10-15 00:00', ]], 'personal_permission', Carbon::parse('2026-11-02 12:00:00'), ); $this->assertSame( [[1, 100.0], [2, 200.0]], array_map( static fn (array $row): array => [ (int) $row['line_no'], (float) $row['amount'], ], $rows, ), ); } public function test_it_does_not_drop_a_payment_when_the_sequence_and_amount_do_not_match(): void { $rows = (new DingProcessPaymentLineAllocator())->allocate( [ ['business_id' => 'approval-5', 'line_no' => 1, 'amount' => '100.00'], ['business_id' => 'approval-5', 'line_no' => 2, 'amount' => '200.00'], ], [[ 'business_id' => 'approval-5', 'process_instance_id' => 'process-5', 'payment_no' => 1, 'sequence_spec' => '9', 'amount' => '150.00', 'paid_at' => '2026-10-15 00:00', ]], 'personal_permission', Carbon::parse('2026-11-02 12:00:00'), ); $this->assertSame( [[1, 50.0], [2, 100.0]], array_map( static fn (array $row): array => [ (int) $row['line_no'], (float) $row['amount'], ], $rows, ), ); $this->assertSame(150.0, array_sum(array_column($rows, 'amount'))); } }