- 种菜数据汇总新增「种菜报销工作台」:按聚工单锁单口径取单(待处理 WAIT_CONFIRM、付款 D-7~D-1、ERP订单状态非取消/删除/空、售后单号为空;可切换处理中 CONFIRMING 和付款起始日),逐单校验重复报销、金额公式、佣金资格、收款人在职、以旧换新买家实付(取买家实付/看板订单实付中最低,查不到标签的待复查)、看板超额(同日按付款先后报到日限额)与看板未匹配 - 导出前须勾选「已在聚工单核对条数和金额一致」;页面给出应核对的条数/金额、各口径合计自检,聚工单镜像停更超过 20 分钟时提示先别报销 - 页面给出可报销合计(三大天猫/其他)、钉钉「种菜报销流程」明细行、按发起人的发群通知与取消清单 - 导出种菜对账、种菜报销明细(打款卡号明细表)、聚工单种菜任务工单导入模版,格式对齐手工文件 - 财务可上传种菜监控看板、报销佣金人员、花名册在职、店铺信息;名单存 storage 持久卷 JSON,花名册只存姓名 - 聚工单镜像读取补充 ERP订单标签、待报销工单和已报销订单查询 涉及:只读聚工单镜像;不涉及迁移、上游写入、钉钉和聚工单写操作。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VANWYsNUbu2piMtCKymoyd
159 lines
8.9 KiB
PHP
159 lines
8.9 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_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));
|
|
$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'));
|
|
}
|
|
}
|