## 任务占用与边界 - 任务名称:聚水潭聚工单-协同工单-打款工单-小额打款页面 - 实际开发者:yueyang - 任务来源/团队分工:退款数据子页面接入 - 目标结果:在“退款”下新增小额打款子页面,读取共享业务镜像,支持分页、筛选、汇总、导出和同步状态提示。 - 预计修改路径:Pages Controller、business_navigation、upstream_mirrors、镜像 Repository、Blade 页面、admin 路由、测试、镜像清单文档。 - 将写入的公共契约:页面 slug、管理路由、镜像数据集注册(jiansun 两张表)、Mirror Repository。 - 仅复用、明确不修改的公共契约:现有共享镜像连接、同步框架、权限校验、导航结构和通用分页/导出模式。 - 数据源、表/视图和字段:jiansun.dbo.jst_workOrderInfo、jiansun.dbo.jst_small_payment_order_map;固定小额打款工单类型 82D94F5C54544EE3833CD8A3AB8B9F73。 - 上游 source_key / scope:source_key=jiansun;沿用团队共享连接 scope。 - 明确不修改的范围:不执行共享数据库迁移、不执行首次全量同步、不修改 .env/APP_KEY、不配置 operations_replica。 ## 开工碰撞检查 - [x] 已执行团队预检并核对 origin/main 与远程任务分支 - [x] 已搜索主线既有页面、接口、Repository 和数据查询 - [x] 已区分将写入与仅复用的公共契约 - [x] 未发现实际写入重叠 ## 实际改动 - 新增退款“小额打款”页面及导航入口。 - 新增 SmallPaymentWorkOrderMirrorRepository:共享镜像分页、稳定排序、筛选、金额汇总、导出行和状态判断。 - 注册 jst_workOrderInfo 与 jst_small_payment_order_map 镜像数据集,并补充真实 businessFieldList 解析字段。 - 增加页面、Repository、导航和同步器相关测试。 ## 验证证据 - 相关测试:19 项通过,143 个断言通过。 - Blade 缓存、PHP 语法检查、git diff --check 通过。 - 实际本地页面已打开,导航显示“小额打款”;首次同步未执行时页面明确显示“业务镜像尚未建立”。 - route:cache 受项目已有重复 livewire.update 路由影响失败,与本次改动无关;普通路由列表和视图缓存正常。 ## 风险检查 - [x] 未提交 .env、密码、Token、私钥、真实财务数据、生产日志或构建产物 - [x] 不涉及共享数据库迁移或生产数据写入 - [x] 前端复用现有 VIP 黑金设计令牌和共享页面结构 ## 合并前复查 - [ ] PR 不再是 WIP,需在完成最终自审和测试后移除 WIP 前缀。 - [ ] Codex 已用 origin/main...任务分支复核完整 diff。 Reviewed-on: http://git.1080p.hk/WuYongJie/CaiWuXiTong/pulls/102
240 lines
13 KiB
PHP
240 lines
13 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\EcommerceFinance;
|
|
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Modules\EcommerceFinance\Repositories\GenericBusinessMirrorRepository;
|
|
use Modules\EcommerceFinance\Repositories\SmallPaymentWorkOrderMirrorRepository;
|
|
use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry;
|
|
use Tests\TestCase;
|
|
|
|
class SmallPaymentWorkOrderMirrorRepositoryTest extends TestCase
|
|
{
|
|
private SmallPaymentWorkOrderMirrorRepository $repository;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
config([
|
|
'database.connections.business_mirror' => [
|
|
'driver' => 'sqlite',
|
|
'database' => ':memory:',
|
|
'prefix' => '',
|
|
],
|
|
'upstream_mirrors.connection' => 'business_mirror',
|
|
'upstream_mirrors.datasets' => [
|
|
SmallPaymentWorkOrderMirrorRepository::WORK_ORDER_DATASET => [
|
|
'source_key' => 'jiansun', 'schema' => 'dbo', 'object' => 'jst_workOrderInfo',
|
|
'target' => 'fixture_work_orders', 'keys' => ['Id'], 'watermark' => 'updateTime',
|
|
'indexes' => [['woTypeId', 'woNo', 'Id']],
|
|
],
|
|
SmallPaymentWorkOrderMirrorRepository::PAYMENT_MAP_DATASET => [
|
|
'source_key' => 'jiansun', 'schema' => 'dbo', 'object' => 'jst_small_payment_order_map',
|
|
'target' => 'fixture_payment_map', 'keys' => ['woNo'], 'watermark' => 'updated_at',
|
|
'indexes' => [['woTypeId', 'work_order_create_time', 'woNo']],
|
|
],
|
|
],
|
|
]);
|
|
DB::purge('business_mirror');
|
|
|
|
$schema = Schema::connection('business_mirror');
|
|
$schema->create('business_mirror_table_states', function (Blueprint $table): void {
|
|
$table->string('dataset_key')->primary();
|
|
$table->string('status')->nullable();
|
|
$table->dateTime('initial_completed_at')->nullable();
|
|
$table->dateTime('last_succeeded_at')->nullable();
|
|
});
|
|
$schema->create('fixture_work_orders', function (Blueprint $table): void {
|
|
$table->integer('Id');
|
|
foreach (['woNo', 'woTypeId', 'title', 'content', 'status', 'grade', 'createby', 'executors', 'completer', 'completeTime', 'created', 'updated', 'updateTime', 'businessFieldList'] as $column) {
|
|
$table->text($column)->nullable();
|
|
}
|
|
$table->integer('urging')->nullable();
|
|
$table->index(['woTypeId', 'woNo', 'Id']);
|
|
});
|
|
$schema->create('fixture_payment_map', function (Blueprint $table): void {
|
|
foreach (['woNo', 'woTypeId', 'transaction_no', 'shop_name', 'payment_reason', 'title', 'status', 'work_order_create_time', 'work_order_update_time', 'parse_status', 'parse_error', 'updated_at'] as $column) {
|
|
$table->text($column)->nullable();
|
|
}
|
|
$table->index(['woTypeId', 'work_order_create_time', 'woNo']);
|
|
});
|
|
|
|
foreach (array_keys(config('upstream_mirrors.datasets')) as $key) {
|
|
DB::connection('business_mirror')->table('business_mirror_table_states')->insert([
|
|
'dataset_key' => $key,
|
|
'status' => 'succeeded',
|
|
'initial_completed_at' => '2026-09-01 00:00:00',
|
|
'last_succeeded_at' => '2026-09-03 12:00:00',
|
|
]);
|
|
}
|
|
|
|
$type = SmallPaymentWorkOrderMirrorRepository::SMALL_PAYMENT_WORK_TYPE_ID;
|
|
$workOrderDefaults = array_fill_keys([
|
|
'Id', 'woNo', 'woTypeId', 'title', 'content', 'status', 'grade',
|
|
'urging', 'createby', 'executors', 'completer', 'completeTime',
|
|
'created', 'updated', 'updateTime', 'businessFieldList',
|
|
], null);
|
|
$workOrderRows = [
|
|
[
|
|
'Id' => 1, 'woNo' => 'GD-001', 'woTypeId' => $type,
|
|
'title' => '小额打款一', 'content' => '售后补偿', 'status' => '已完成',
|
|
'grade' => '普通', 'urging' => 1, 'createby' => '客服甲', 'executors' => '财务甲',
|
|
'completer' => '财务甲', 'completeTime' => '2026-09-02 11:00:00',
|
|
'created' => '2026-09-02 09:00:00', 'updated' => '2026-09-02 11:00:00',
|
|
'updateTime' => '2026-09-02 11:01:00',
|
|
'businessFieldList' => json_encode([
|
|
['fieldName' => '打款状态', 'fieldValue' => '支付宝已打款'],
|
|
['fieldName' => '打款金额', 'fieldValue' => '50.005'],
|
|
['fieldName' => '线上订单号', 'fieldValue' => 'ORDER-001'],
|
|
['fieldName' => '买家账号', 'fieldValue' => 'buyer-one'],
|
|
['fieldName' => '支付宝账号', 'fieldValue' => 'account-one'],
|
|
['fieldName' => '支付宝姓名', 'fieldValue' => '测试买家'],
|
|
['fieldName' => '商品编码', 'fieldValue' => 'ITEM-001'],
|
|
['fieldName' => 'SKU编码', 'fieldValue' => 'SKU-001'],
|
|
['fieldName' => '平台', 'fieldValue' => '京东'],
|
|
['fieldName' => '实际已付金额', 'fieldValue' => '199.90'],
|
|
['fieldName' => '支付时间', 'fieldValue' => '2026-09-02 10:30:00'],
|
|
], JSON_UNESCAPED_UNICODE),
|
|
],
|
|
[
|
|
'Id' => 2, 'woNo' => 'GD-002', 'woTypeId' => $type,
|
|
'title' => '小额打款二', 'content' => '少收多收', 'status' => '处理中',
|
|
'grade' => '紧急', 'urging' => 0, 'createby' => '客服乙',
|
|
'created' => '2026-09-03 09:00:00', 'updated' => '2026-09-03 09:30:00',
|
|
'updateTime' => '2026-09-03 09:31:00',
|
|
'businessFieldList' => json_encode([
|
|
['fieldId' => 'shopField', 'type' => 'SELECT', 'value' => '18526685', 'valueLabel' => '天猫-亿家达旗舰店'],
|
|
['fieldId' => 'alipayAccountField', 'type' => 'TEXT', 'value' => 'account-two'],
|
|
['fieldId' => 'alipayNameField', 'type' => 'TEXT', 'value' => '测试买家二'],
|
|
['fieldId' => 'buyerIdField', 'type' => 'TEXT', 'value' => 'buyer-two'],
|
|
['childFieldList' => [[
|
|
['fieldId' => 'iid', 'type' => 'TEXT', 'value' => 'ITEM-002'],
|
|
['fieldId' => 'skuId', 'type' => 'TEXT', 'value' => 'SKU-002'],
|
|
]], 'fieldId' => 'itemInfo', 'type' => 'FORM'],
|
|
['fieldId' => 'actualPaidAmountField', 'type' => 'MONEY', 'value' => '188.88'],
|
|
['fieldId' => 'platform', 'type' => 'SELECT', 'value' => '淘宝天猫'],
|
|
['fieldId' => 'payDateField', 'type' => 'DATE', 'value' => '2026-09-03 08:30:00'],
|
|
['fieldId' => 'amountField', 'type' => 'MONEY', 'value' => '20'],
|
|
['fieldId' => 'orderField', 'type' => 'TEXT', 'value' => 'ORDER-002'],
|
|
], JSON_UNESCAPED_UNICODE),
|
|
],
|
|
[
|
|
'Id' => 3, 'woNo' => 'OTHER-001', 'woTypeId' => 'OTHER',
|
|
'title' => '其他工单', 'status' => '已完成',
|
|
'created' => '2026-09-03 10:00:00', 'updated' => '2026-09-03 10:00:00',
|
|
'updateTime' => '2026-09-03 10:00:00', 'businessFieldList' => '{}',
|
|
],
|
|
];
|
|
DB::connection('business_mirror')->table('fixture_work_orders')->insert(array_map(
|
|
static fn (array $row): array => array_replace($workOrderDefaults, $row),
|
|
$workOrderRows,
|
|
));
|
|
DB::connection('business_mirror')->table('fixture_payment_map')->insert([
|
|
[
|
|
'woNo' => 'GD-001', 'woTypeId' => $type, 'transaction_no' => 'BATCH-001',
|
|
'shop_name' => '京东旗舰店', 'payment_reason' => '售后补偿', 'title' => '小额打款一',
|
|
'status' => '已完成', 'work_order_create_time' => '2026-09-02 09:00:00',
|
|
'work_order_update_time' => '2026-09-02 11:00:00', 'parse_status' => 'success',
|
|
'updated_at' => '2026-09-02 11:01:00',
|
|
],
|
|
[
|
|
'woNo' => 'GD-002', 'woTypeId' => $type, 'transaction_no' => 'BATCH-002',
|
|
'shop_name' => '天猫旗舰店', 'payment_reason' => '少收多收', 'title' => '小额打款二',
|
|
'status' => '处理中', 'work_order_create_time' => '2026-09-03 09:00:00',
|
|
'work_order_update_time' => '2026-09-03 09:30:00', 'parse_status' => 'success',
|
|
'updated_at' => '2026-09-03 09:31:00',
|
|
],
|
|
[
|
|
'woNo' => 'OTHER-001', 'woTypeId' => 'OTHER', 'transaction_no' => 'BATCH-003',
|
|
'shop_name' => '其他店铺', 'payment_reason' => '其他', 'title' => '其他工单',
|
|
'status' => '已完成', 'work_order_create_time' => '2026-09-03 10:00:00',
|
|
'work_order_update_time' => '2026-09-03 10:00:00', 'parse_status' => 'success',
|
|
'updated_at' => '2026-09-03 10:00:00',
|
|
],
|
|
]);
|
|
|
|
$this->repository = new SmallPaymentWorkOrderMirrorRepository(
|
|
new BusinessMirrorDatasetRegistry(),
|
|
new GenericBusinessMirrorRepository(app('db')),
|
|
);
|
|
}
|
|
|
|
public function test_filters_paginates_and_parses_small_payment_business_fields(): void
|
|
{
|
|
$result = $this->repository->paginate($this->filters([
|
|
'shop_name' => '京东',
|
|
'online_order_no' => 'ORDER-001',
|
|
'status' => '已完成',
|
|
]), 1, 25);
|
|
|
|
$this->assertSame(1, $result['total']);
|
|
$this->assertSame('50.01', $result['page_payment_amount']);
|
|
$this->assertSame('GD-001', $result['data'][0]['work_order_no']);
|
|
$this->assertSame('小额打款', $result['data'][0]['work_order_type']);
|
|
$this->assertSame('支付宝已打款', $result['data'][0]['payment_status']);
|
|
$this->assertSame('ORDER-001', $result['data'][0]['online_order_no']);
|
|
$this->assertSame('SKU-001', $result['data'][0]['sku_code']);
|
|
$this->assertSame('199.90', $result['data'][0]['actual_paid_amount']);
|
|
$this->assertArrayNotHasKey('business_field_list', $result['data'][0]);
|
|
}
|
|
|
|
public function test_stable_order_second_page_and_exact_work_type_filter(): void
|
|
{
|
|
$result = $this->repository->paginate($this->filters(), 2, 1);
|
|
|
|
$this->assertSame(2, $result['total']);
|
|
$this->assertSame('GD-001', $result['data'][0]['work_order_no']);
|
|
$this->assertSame('50.01', $result['page_payment_amount']);
|
|
}
|
|
|
|
public function test_export_uses_same_filters_and_parsed_shape(): void
|
|
{
|
|
$rows = iterator_to_array($this->repository->exportRows($this->filters([
|
|
'transaction_no' => 'BATCH-002',
|
|
'payment_reason' => '少收',
|
|
]), 1));
|
|
|
|
$this->assertCount(1, $rows);
|
|
$this->assertSame('GD-002', $rows[0]['work_order_no']);
|
|
$this->assertSame('20.00', $rows[0]['payment_amount']);
|
|
$this->assertSame('ORDER-002', $rows[0]['online_order_no']);
|
|
$this->assertSame('buyer-two', $rows[0]['buyer_account']);
|
|
$this->assertSame('account-two', $rows[0]['alipay_account']);
|
|
$this->assertSame('测试买家二', $rows[0]['alipay_name']);
|
|
$this->assertSame('ITEM-002', $rows[0]['product_code']);
|
|
$this->assertSame('SKU-002', $rows[0]['sku_code']);
|
|
$this->assertSame('淘宝天猫', $rows[0]['platform']);
|
|
$this->assertSame('188.88', $rows[0]['actual_paid_amount']);
|
|
$this->assertSame('2026-09-03 08:30:00', $rows[0]['payment_time']);
|
|
$this->assertSame('处理中', $rows[0]['payment_status']);
|
|
}
|
|
|
|
public function test_unready_state_is_explicit(): void
|
|
{
|
|
DB::connection('business_mirror')->table('business_mirror_table_states')
|
|
->where('dataset_key', SmallPaymentWorkOrderMirrorRepository::PAYMENT_MAP_DATASET)
|
|
->update(['initial_completed_at' => null, 'status' => 'running']);
|
|
|
|
$states = $this->repository->states();
|
|
$status = $this->repository->syncStatus($states);
|
|
|
|
$this->assertFalse($this->repository->ready());
|
|
$this->assertSame('initializing', $status['code']);
|
|
$this->expectException(\RuntimeException::class);
|
|
$this->repository->paginate($this->filters(), 1, 25);
|
|
}
|
|
|
|
/** @param array<string,string> $overrides @return array<string,string> */
|
|
private function filters(array $overrides = []): array
|
|
{
|
|
return array_replace([
|
|
'from' => '2026-09-01', 'to' => '2026-09-30',
|
|
'work_order_no' => '', 'shop_name' => '', 'transaction_no' => '',
|
|
'online_order_no' => '', 'status' => '', 'payment_reason' => '',
|
|
], $overrides);
|
|
}
|
|
}
|