- 工作台新增「钉钉机器人设置」:Webhook、加签密钥加密存公司设置,页面不回显;支持自定义关键词、汇总单独发群、@ 报销人花名 - 核对后点「发取消通知到钉钉群」:按发起人一人一条,按钉钉花名册(花名 → userid)@ 发起人;同一批已发过的不重复发,可勾选重发 - 新增 planting-reimbursement:daily-digest,每天 10:25(宿主机 cron)生成当天批次,把待核对条数/金额、可报销合计、取消/待复查数、自检结果和工作台链接推到群里;数据停更或看板未上传会提醒 - 种菜监控看板提示「职级为空」的单改为取消工单,群通知原因写「职级为空」(2026-11-13 报销人确认) - 没上传花名册时,收款人在职改按钉钉花名册在职真名判断;两边都取不到时不做该项校验并提示,避免误判 涉及:只读聚工单镜像与钉钉花名册副本;钉钉群机器人发消息;不涉及迁移,不写聚工单。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VANWYsNUbu2piMtCKymoyd
171 lines
9.6 KiB
PHP
171 lines
9.6 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\EcommerceFinance;
|
|
|
|
use Modules\EcommerceFinance\Services\PlantingReimbursementAuditor as Auditor;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
class PlantingReimbursementAuditorTest extends TestCase
|
|
{
|
|
private function row(string $orderNo, float $amount, array $patch = []): array
|
|
{
|
|
return array_replace([
|
|
'work_order_no' => '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'));
|
|
}
|
|
}
|