## 任务占用与边界 - 任务名称:聚水潭聚工单-协同工单-打款工单-小额打款页面 - 实际开发者: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
168 lines
8.6 KiB
PHP
168 lines
8.6 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Business;
|
|
|
|
use App\Models\Auth\Permission;
|
|
use Modules\EcommerceFinance\Repositories\SmallPaymentWorkOrderMirrorRepository;
|
|
use Tests\Feature\FeatureTestCase;
|
|
|
|
class SmallPaymentWorkOrderPageTest extends FeatureTestCase
|
|
{
|
|
private function grantAdminPermission(): void
|
|
{
|
|
$this->user->attachPermission(
|
|
Permission::where('name', 'read-admin-panel')->firstOrFail()
|
|
);
|
|
}
|
|
|
|
public function test_page_renders_filtered_mirror_rows_summary_and_pagination(): void
|
|
{
|
|
$this->grantAdminPermission();
|
|
setting()->set('default.list_limit', 25);
|
|
$states = [
|
|
SmallPaymentWorkOrderMirrorRepository::WORK_ORDER_DATASET => [
|
|
'status' => 'running', 'initial_completed_at' => '2026-09-01 00:00:00',
|
|
],
|
|
SmallPaymentWorkOrderMirrorRepository::PAYMENT_MAP_DATASET => [
|
|
'status' => 'running', 'initial_completed_at' => '2026-09-01 00:00:00',
|
|
],
|
|
];
|
|
$repository = \Mockery::mock(SmallPaymentWorkOrderMirrorRepository::class);
|
|
$repository->shouldReceive('available')->once()->andReturnTrue();
|
|
$repository->shouldReceive('states')->once()->andReturn($states);
|
|
$repository->shouldReceive('syncStatus')->once()->with($states)->andReturn([
|
|
'code' => 'syncing', 'last_succeeded_at' => '2026-09-03 12:00:00',
|
|
]);
|
|
$repository->shouldReceive('ready')->once()->andReturnTrue();
|
|
$repository->shouldReceive('queryReady')->once()->andReturnTrue();
|
|
$repository->shouldReceive('paginate')->once()->withArgs(
|
|
static fn (array $filters, int $page, int $limit): bool => $filters === [
|
|
'from' => '2026-09-01', 'to' => '2026-09-03',
|
|
'work_order_no' => 'GD-001', 'shop_name' => '京东旗舰店',
|
|
'transaction_no' => 'BATCH-001', 'online_order_no' => 'ORDER-001',
|
|
'status' => '已完成', 'payment_reason' => '售后补偿',
|
|
] && $page === 2 && $limit === 25
|
|
)->andReturn([
|
|
'data' => [[
|
|
'work_order_no' => 'GD-001', 'alipay_batch_no' => 'BATCH-001',
|
|
'work_order_status' => '已完成', 'payment_status' => '支付宝已打款',
|
|
'work_order_title' => '小额打款', 'work_order_content' => '售后补偿',
|
|
'work_order_type' => '小额打款', 'priority' => '普通',
|
|
'payment_amount' => '50.00', 'urging_count' => 0,
|
|
'shop_name' => '京东旗舰店', 'online_order_no' => 'ORDER-001',
|
|
'buyer_account' => 'buyer-one', 'alipay_account' => 'account-one',
|
|
'alipay_name' => '测试买家', 'product_code' => 'ITEM-001',
|
|
'sku_code' => 'SKU-001', 'platform' => '京东',
|
|
'actual_paid_amount' => '199.90', 'payment_time' => '2026-09-02 10:30:00',
|
|
'payment_reason' => '售后补偿', 'creator' => '客服甲',
|
|
'work_order_created_at' => '2026-09-02 09:00:00',
|
|
'completed_at' => '2026-09-02 11:00:00',
|
|
]],
|
|
'total' => 26,
|
|
'page_payment_amount' => '50.00',
|
|
]);
|
|
$this->app->instance(SmallPaymentWorkOrderMirrorRepository::class, $repository);
|
|
|
|
$url = route('business.pages.show', [
|
|
'company_id' => $this->company->id,
|
|
'section' => 'refunds', 'page' => 'small-payment-work-orders',
|
|
]) . '?' . http_build_query([
|
|
'from' => '2026-09-01', 'to' => '2026-09-03', 'work_order_no' => 'GD-001',
|
|
'shop_name' => '京东旗舰店', 'transaction_no' => 'BATCH-001',
|
|
'online_order_no' => 'ORDER-001', 'status' => '已完成',
|
|
'payment_reason' => '售后补偿', 'page' => 2,
|
|
]);
|
|
|
|
$this->loginAs()->get($url)
|
|
->assertOk()
|
|
->assertSee('data-small-payment-work-orders="true"', false)
|
|
->assertSee('data-filter-contract="small-payment-work-orders.filters"', false)
|
|
->assertSee('data-sync-state="syncing"', false)
|
|
->assertSeeText('业务镜像同步中,当前显示最近成功数据')
|
|
->assertSee('aria-label="聚水潭小额打款工单"', false)
|
|
->assertSeeText('GD-001')
|
|
->assertSeeText('支付宝已打款')
|
|
->assertSeeText('¥ 50.00')
|
|
->assertSeeText('共 26 条记录,本页 1 条')
|
|
->assertSeeText('第 2 / 2 页')
|
|
->assertSee('workspace/refunds/small-payment-work-orders/export', false)
|
|
->assertSee('value="ORDER-001"', false);
|
|
}
|
|
|
|
public function test_export_downloads_filtered_csv(): void
|
|
{
|
|
$this->grantAdminPermission();
|
|
$repository = \Mockery::mock(SmallPaymentWorkOrderMirrorRepository::class);
|
|
$repository->shouldReceive('available')->once()->andReturnTrue();
|
|
$repository->shouldReceive('ready')->once()->andReturnTrue();
|
|
$repository->shouldReceive('queryReady')->once()->andReturnTrue();
|
|
$repository->shouldReceive('exportRows')->once()->andReturn([[
|
|
'work_order_no' => 'GD-001', 'alipay_batch_no' => 'BATCH-001',
|
|
'work_order_status' => '已完成', 'payment_status' => '支付宝已打款',
|
|
'work_order_title' => '小额打款', 'work_order_content' => '售后补偿',
|
|
'work_order_type' => '小额打款', 'priority' => '普通',
|
|
'payment_amount' => '50.00', 'urging_count' => 0,
|
|
'shop_name' => '京东旗舰店', 'online_order_no' => 'ORDER-001',
|
|
'buyer_account' => 'buyer-one', 'alipay_account' => 'account-one',
|
|
'alipay_name' => '测试买家', 'product_code' => 'ITEM-001',
|
|
'sku_code' => 'SKU-001', 'platform' => '京东',
|
|
'actual_paid_amount' => '199.90', 'payment_time' => '2026-09-02 10:30:00',
|
|
'payment_reason' => '售后补偿', 'creator' => '客服甲',
|
|
'work_order_created_at' => '2026-09-02 09:00:00',
|
|
'completed_at' => '2026-09-02 11:00:00',
|
|
]]);
|
|
$this->app->instance(SmallPaymentWorkOrderMirrorRepository::class, $repository);
|
|
|
|
$response = $this->loginAs()->get(route('business.refunds.small-payment-work-orders.export', [
|
|
'from' => '2026-09-01', 'to' => '2026-09-03',
|
|
]));
|
|
|
|
$response->assertOk()->assertDownload('small_payment_work_orders_2026-09-01_2026-09-03.csv');
|
|
$content = $response->streamedContent();
|
|
$this->assertStringStartsWith("\xEF\xBB\xBF", $content);
|
|
$this->assertStringContainsString('工单编号', $content);
|
|
$this->assertStringContainsString('GD-001', $content);
|
|
$this->assertStringContainsString('50.00', $content);
|
|
}
|
|
|
|
public function test_page_does_not_report_empty_before_both_mirrors_finish(): void
|
|
{
|
|
$this->grantAdminPermission();
|
|
$states = [
|
|
SmallPaymentWorkOrderMirrorRepository::WORK_ORDER_DATASET => ['status' => 'running'],
|
|
SmallPaymentWorkOrderMirrorRepository::PAYMENT_MAP_DATASET => ['status' => 'running'],
|
|
];
|
|
$repository = \Mockery::mock(SmallPaymentWorkOrderMirrorRepository::class);
|
|
$repository->shouldReceive('available')->once()->andReturnTrue();
|
|
$repository->shouldReceive('states')->once()->andReturn($states);
|
|
$repository->shouldReceive('syncStatus')->once()->with($states)->andReturn([
|
|
'code' => 'initializing', 'last_succeeded_at' => null,
|
|
]);
|
|
$repository->shouldReceive('ready')->once()->andReturnFalse();
|
|
$repository->shouldNotReceive('queryReady');
|
|
$repository->shouldNotReceive('paginate');
|
|
$this->app->instance(SmallPaymentWorkOrderMirrorRepository::class, $repository);
|
|
|
|
$this->loginAs()->get(route('business.pages.show', [
|
|
'section' => 'refunds', 'page' => 'small-payment-work-orders',
|
|
]))
|
|
->assertOk()
|
|
->assertSeeText('业务镜像正在执行首次同步')
|
|
->assertSeeText('首次同步尚未完成,完成后将自动显示小额打款工单。')
|
|
->assertDontSeeText('当前筛选范围没有小额打款工单');
|
|
}
|
|
|
|
public function test_page_rejects_users_without_sidebar_permission(): void
|
|
{
|
|
$subject = 'user_' . $this->user->id;
|
|
setting()->set('system.sidebar_permissions', json_encode([
|
|
'configured' => [$subject],
|
|
'subjects' => [$subject => ['company-management/basic-information']],
|
|
], JSON_UNESCAPED_UNICODE));
|
|
|
|
$this->withExceptionHandling()->loginAs()->get(route('business.pages.show', [
|
|
'section' => 'refunds', 'page' => 'small-payment-work-orders',
|
|
]))->assertForbidden();
|
|
}
|
|
}
|