'GD' . $orderNo, 'online_order_no' => $orderNo, 'shop_name' => '京喜-星光闪闪京喜自营工厂店', 'initiator' => '星璃', 'payee_name' => '王敏佳', 'payee_account' => '6214000011112222', 'payee_bank' => '招商银行', 'payee_mobile' => '13800000000', 'task_payment' => (string) $amount, 'task_freight' => '0.00', 'task_commission' => '0.00', 'jingxi_price_diff' => '0.00', 'task_deduction' => '0.00', 'task_empty_package_fee' => '0.00', 'reimbursement_amount' => (string) $amount, 'actual_paid_amount' => (string) $amount, 'erp_order_label' => '', 'paid_at' => '2026-11-11 10:00:00', 'status' => '处理中', ], $patch); } private function audit(array $rows, array $refs = []): array { return (new Auditor())->audit($rows, $refs + [ 'reimbursed' => [], 'commission_people' => ['白菜' => true], 'roster' => ['王敏佳' => true, '姚佳炜' => true, '李润' => true], 'roster_exceptions' => ['柴嘉伟(建行)', '柴嘉伟'], 'profit_centers' => ['京喜-星光闪闪京喜自营工厂店' => '运营中心A', '天猫-欧意朗旗舰店' => '运营中心A', '天猫-HX旗舰店101' => '运营中心G'], 'monitor' => null, ], '2026-11-12'); } private function decisions(array $result): array { return array_column($result['rows'], 'decision', 'online_order_no'); } public function test_same_day_daily_over_limit_keeps_orders_by_paid_time_until_limit(): void { $rows = []; $monitor = []; foreach ([1, 2, 3, 4, 5] as $i) { $rows[] = $this->row("10{$i}", 300, ['paid_at' => "2026-11-11 1{$i}:00:00"]); $monitor["10{$i}"] = ['risk' => '超额', 'hint' => '当日累计 1500.00 超过上限 800.00', 'risk_type' => '每日超额', 'owner' => '星璃', 'daily_limit' => '800']; } $result = $this->audit($rows, ['monitor' => $monitor]); $this->assertSame(['101' => 'pass', '102' => 'pass', '103' => 'cancel', '104' => 'cancel', '105' => 'cancel'], $this->decisions($result)); $this->assertSame('600.00', $result['summary']['total']); $this->assertSame(3, $result['summary']['counts']['cancel']); $this->assertStringContainsString('@星璃 以上订单本次不予报销', $result['summary']['notices'][0]['message']); } public function test_over_limit_on_different_days_cancels_all(): void { $monitor = [ '1' => ['risk' => '超额', 'risk_type' => '每日超额', 'owner' => '星璃', 'daily_limit' => '800'], '2' => ['risk' => '超额', 'risk_type' => '每日超额', 'owner' => '星璃', 'daily_limit' => '800'], ]; $result = $this->audit([ $this->row('1', 100, ['paid_at' => '2026-11-10 09:00:00']), $this->row('2', 100, ['paid_at' => '2026-11-11 09:00:00']), ], ['monitor' => $monitor]); $this->assertSame(['1' => 'cancel', '2' => 'cancel'], $this->decisions($result)); } public function test_rule_failures_cancel_and_exceptions_pass(): void { $result = $this->audit([ $this->row('201', 100, ['shop_name' => '天猫-欧意朗旗舰店', 'initiator' => '白菜', 'payee_name' => '姚佳炜']), $this->row('202', 100, ['initiator' => '观海', 'task_commission' => '5.00', 'reimbursement_amount' => '105.00']), $this->row('203', 89.5, ['shop_name' => '天猫-欧意朗旗舰店', 'erp_order_label' => '工单同步,以旧换新', 'actual_paid_amount' => '50.00']), $this->row('204', 10, ['shop_name' => '天猫-HX旗舰店101', 'payee_name' => '柴嘉伟(建行)']), $this->row('205', 10, ['payee_name' => '离职的人']), $this->row('206', 10, ['reimbursement_amount' => '12.00']), $this->row('207', 10, ['work_order_no' => 'GD207A']), $this->row('207', 10, ['work_order_no' => 'GD207B']), $this->row('208', 10, ['shop_name' => '拼多多-新店']), $this->row('209', 120, ['shop_name' => '京东-某店', 'erp_order_label' => '以旧换新', 'actual_paid_amount' => '50.00']), ], ['reimbursed' => ['201' => '2026-11-03']]); $rows = []; foreach ($result['rows'] as $row) { $rows[$row['work_order_no']] = $row; } $this->assertSame('cancel', $rows['GD201']['decision']); $this->assertStringContainsString('2026-11-03', $rows['GD201']['issue_text']); $this->assertSame('cancel', $rows['GD202']['decision']); $this->assertStringContainsString('报销佣金人员', $rows['GD202']['issue_text']); $this->assertSame('cancel', $rows['GD203']['decision']); $this->assertStringContainsString('买家实付', $rows['GD203']['issue_text']); $this->assertSame('pass', $rows['GD204']['decision']); $this->assertSame('cancel', $rows['GD205']['decision']); $this->assertSame('cancel', $rows['GD206']['decision']); $this->assertSame('pass', $rows['GD207A']['decision']); $this->assertSame('cancel', $rows['GD207B']['decision']); // 查不到利润中心只提示,不取消。 $this->assertSame('pass', $rows['GD208']['decision']); $this->assertStringContainsString('利润中心', $rows['GD208']['issue_text']); // 以旧换新只校验天猫、淘宝店。 $this->assertSame('pass', $rows['GD209']['decision']); } public function test_trade_in_uses_lowest_buyer_paid_amount(): void { $result = $this->audit([ $this->row('3317016073022051663', 89.5, ['shop_name' => '天猫-欧意朗旗舰店', 'erp_order_label' => '工单同步,以旧换新·国家补贴', 'actual_paid_amount' => '89.50']), ], ['monitor' => ['3317016073022051663' => ['risk' => '正常', 'order_paid' => '76.08']]]); $this->assertSame('cancel', $result['rows'][0]['decision']); $this->assertStringContainsString('76.08', $result['rows'][0]['issue_text']); // 工单里标签为空、买家实付更低 → 待复查,去聚水潭确认。 $missing = $this->audit([ $this->row('1', 89.5, ['shop_name' => '天猫-欧意朗旗舰店', 'erp_order_label' => '', 'buyer_paid_amount' => '76.08']), $this->row('2', 89.5, ['shop_name' => '天猫-欧意朗旗舰店', 'erp_order_label' => '工单同步', 'buyer_paid_amount' => '76.08']), ]); $this->assertSame(['1' => 'recheck', '2' => 'pass'], $this->decisions($missing)); } public function test_missing_roster_skips_payee_check(): void { $result = $this->audit([$this->row('1', 10, ['payee_name' => '任何人'])], ['roster' => null]); $this->assertSame(['1' => 'pass'], $this->decisions($result)); } public function test_monitor_missing_is_recheck(): void { $result = $this->audit([$this->row('1', 10), $this->row('2', 10)], ['monitor' => ['1' => ['risk' => '正常']]]); $this->assertSame(['1' => 'pass', '2' => 'recheck'], $this->decisions($result)); // 看板提示职级为空 → 取消,原因写明职级为空。 $rank = $this->audit([$this->row('3', 10)], ['monitor' => ['3' => ['risk' => '待补', 'risk_type' => '职级为空', 'hint' => '职级为空,暂不判断平台规则', 'owner' => '星璃']]]); $this->assertSame('cancel', $rank['rows'][0]['decision']); $this->assertStringContainsString('职级为空', $rank['summary']['notices'][0]['message']); $this->assertSame(1, $result['summary']['counts']['recheck']); } public function test_summary_groups_ding_lines_cards_and_pivots(): void { $result = $this->audit([ $this->row('1', 60, ['shop_name' => '天猫-欧意朗旗舰店', 'initiator' => '白菜', 'payee_name' => '姚佳炜']), $this->row('2', 40.5, ['shop_name' => '天猫-欧意朗旗舰店', 'initiator' => '白菜', 'payee_name' => '姚佳炜']), $this->row('3', 10, ['shop_name' => '天猫-HX旗舰店101', 'initiator' => '观海', 'payee_name' => '李润']), $this->row('4', 20, ['initiator' => '观海', 'payee_name' => '李润']), ]); $summary = $result['summary']; $this->assertSame('2026-11-11', $summary['bill_date']); $this->assertSame('11.11种菜报销', $summary['purpose']); $this->assertSame('100.50', $summary['group_amounts']['三大天猫']); $this->assertSame('30.00', $summary['group_amounts']['其他']); $this->assertSame('130.50', $summary['total']); $this->assertSame([['运营中心A', '欧意朗', 10050], ['运营中心A', '其他', 2000], ['运营中心G', '其他', 1000]], array_map( static fn (array $line): array => [$line['department'], $line['shop'], $line['cents']], $summary['ding_lines'], )); $this->assertCount(1, $summary['pivots']['三大天猫']); $this->assertCount(2, $summary['pivots']['其他']); $this->assertCount(2, $summary['cards']['其他']); $this->assertSame('招商银行', $summary['cards']['三大天猫'][0]['payee_bank']); $this->assertSame(['count' => 4, 'amount' => '130.50'], $summary['source']); $this->assertSame([true, true, true, true, true, true], array_column($summary['checks'], 'ok')); } }