- 店铺/打款原因筛选项改为自绘可滚动面板(原生 datalist 高度与滚动不可控) - 完成日期改为与账单页一致的日历控件(文本输入 + 日历按钮 + 中文月历弹层) 值只承载日期,服务端补成整天半开区间,避免分钟级边界丢单 - 测试:php artisan test --filter=SmallPayment 16 passed (126 assertions)
275 lines
14 KiB
PHP
275 lines
14 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-01T08:30:00', 'to' => '2026-09-03T17:45:59',
|
|
'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',
|
|
'filtered_payment_amount' => '50.00',
|
|
]);
|
|
$repository->shouldReceive('filterOptions')->once()->andReturn([
|
|
'shops' => ['京东旗舰店'],
|
|
'payment_reasons' => ['售后补偿'],
|
|
]);
|
|
$this->app->instance(SmallPaymentWorkOrderMirrorRepository::class, $repository);
|
|
|
|
$url = route('business.pages.show', [
|
|
'company_id' => $this->company->id,
|
|
'section' => 'financial-operations', 'page' => 'small-payment-work-orders',
|
|
]) . '?' . http_build_query([
|
|
'from' => '2026-09-01T08:30', 'to' => '2026-09-03T17:45', '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('业务镜像同步中,当前显示最近成功数据')
|
|
->assertSeeText('完成日期')
|
|
->assertSee('value="2026-09-01"', false)
|
|
->assertSee('value="2026-09-03"', false)
|
|
->assertSee('data-date-picker', false)
|
|
->assertSee('data-date-toggle', false)
|
|
->assertSee('aria-label="聚水潭小额打款工单"', false)
|
|
->assertSeeText('GD-001')
|
|
->assertSeeText('支付宝已打款')
|
|
->assertSeeText('¥ 50.00')
|
|
->assertSeeText('共 26 条记录,本页 1 条')
|
|
->assertSeeText('第 2 / 2 页')
|
|
->assertSee('workspace/financial-operations/small-payment-work-orders/export', false)
|
|
->assertSee('data-finance-export-base=', false)
|
|
->assertSee('data-finance-export-query="{"from":"2026-09-01T08:30:00","to":"2026-09-03T17:45:59"', false)
|
|
->assertDontSee('data-finance-export-query="{&quot;', false)
|
|
->assertSee('data-export-field="work_order_no"', 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.financial-operations.small-payment-work-orders.export', [
|
|
'from' => '2026-09-01', 'to' => '2026-09-03',
|
|
]));
|
|
|
|
$response->assertOk()->assertDownload('small_payment_work_orders_2026-09-01_00-00-00_2026-09-03_23-59-59.csv');
|
|
$content = $response->streamedContent();
|
|
$this->assertStringStartsWith("\xEF\xBB\xBF", $content);
|
|
$this->assertStringContainsString('工单编号', $content);
|
|
$this->assertStringContainsString('GD-001', $content);
|
|
$this->assertStringContainsString('50.00', $content);
|
|
}
|
|
|
|
/**
|
|
* 筛选下拉必须是自绘的可滚动面板:原生 <datalist> 的面板由浏览器绘制,
|
|
* 高度与滚动条无法控制,店铺有 200+ 个选项时看不全也滑不动。
|
|
*/
|
|
public function test_filter_dropdowns_are_scrollable_option_panels(): void
|
|
{
|
|
$this->grantAdminPermission();
|
|
setting()->set('default.list_limit', 25);
|
|
$states = [
|
|
SmallPaymentWorkOrderMirrorRepository::WORK_ORDER_DATASET => [
|
|
'status' => 'succeeded', 'initial_completed_at' => '2026-09-01 00:00:00',
|
|
],
|
|
SmallPaymentWorkOrderMirrorRepository::PAYMENT_MAP_DATASET => [
|
|
'status' => 'succeeded', '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' => 'healthy', 'last_succeeded_at' => '2026-09-03 12:00:00',
|
|
]);
|
|
$repository->shouldReceive('ready')->once()->andReturnTrue();
|
|
$repository->shouldReceive('queryReady')->once()->andReturnTrue();
|
|
$repository->shouldReceive('paginate')->once()->andReturn([
|
|
'data' => [], 'total' => 0,
|
|
'page_payment_amount' => '0.00', 'filtered_payment_amount' => '0.00',
|
|
]);
|
|
$repository->shouldReceive('filterOptions')->once()->andReturn([
|
|
'shops' => ['京东-HX旗舰店101', '京东-HX旗舰店102'],
|
|
'payment_reasons' => ['买家原因/买家-不想要'],
|
|
]);
|
|
$this->app->instance(SmallPaymentWorkOrderMirrorRepository::class, $repository);
|
|
|
|
$this->loginAs()->get(route('business.pages.show', [
|
|
'section' => 'financial-operations', 'page' => 'small-payment-work-orders',
|
|
]))
|
|
->assertOk()
|
|
->assertSee('data-filter-combobox', false)
|
|
->assertSee('class="upstream-data-combobox__menu"', false)
|
|
// 唯一能锁住"可滚动"的常量:面板有最大高度且内部滚动
|
|
->assertSee('max-height: min(18rem, 50vh)', false)
|
|
->assertSee('data-combobox-option', false)
|
|
->assertSee('data-value="京东-HX旗舰店101"', false)
|
|
->assertSee('data-combobox-empty', false)
|
|
->assertDontSee('list="small-payment', false)
|
|
->assertDontSee('<datalist id=', false);
|
|
}
|
|
|
|
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' => 'financial-operations', 'page' => 'small-payment-work-orders',
|
|
]))
|
|
->assertOk()
|
|
->assertSeeText('业务镜像正在执行首次同步')
|
|
->assertSeeText('首次同步尚未完成,完成后将自动显示小额打款工单。')
|
|
->assertDontSeeText('当前筛选范围没有小额打款工单');
|
|
}
|
|
|
|
public function test_export_accepts_only_declared_columns_in_requested_order(): 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' => '12345678901234567890',
|
|
'payment_amount' => '50.00',
|
|
'shop_name' => '京东旗舰店',
|
|
]]);
|
|
$this->app->instance(SmallPaymentWorkOrderMirrorRepository::class, $repository);
|
|
|
|
$response = $this->loginAs()->get(route('business.financial-operations.small-payment-work-orders.export', [
|
|
'columns' => 'shop_name,work_order_no,not_a_real_field,shop_name',
|
|
]));
|
|
|
|
$response->assertOk();
|
|
$content = $response->streamedContent();
|
|
$header = strtok($content, "\r\n");
|
|
$this->assertSame("\xEF\xBB\xBF店铺名称,工单编号", $header);
|
|
$this->assertStringContainsString("京东旗舰店,'12345678901234567890", $content);
|
|
$this->assertStringNotContainsString('打款金额', $content);
|
|
}
|
|
|
|
public function test_page_rejects_users_without_sidebar_permission(): void
|
|
{
|
|
$subject = 'user_' . $this->user->id;
|
|
$this->user->roles()->sync([\App\Models\Auth\Role::where('name', 'admin')->firstOrFail()->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' => 'financial-operations', 'page' => 'small-payment-work-orders',
|
|
]))->assertForbidden();
|
|
}
|
|
|
|
public function test_legacy_refund_url_redirects_to_financial_operations_and_preserves_filters(): void
|
|
{
|
|
$this->grantAdminPermission();
|
|
|
|
$this->loginAs()->get(route('business.pages.show', [
|
|
'section' => 'refunds',
|
|
'page' => 'small-payment-work-orders',
|
|
'from' => '2026-09-01',
|
|
'shop_name' => '测试店铺',
|
|
]))->assertRedirect(route('business.pages.show', [
|
|
'section' => 'financial-operations',
|
|
'page' => 'small-payment-work-orders',
|
|
'from' => '2026-09-01',
|
|
'shop_name' => '测试店铺',
|
|
]));
|
|
}
|
|
}
|