CaiWuXiTong/tests/Feature/Business/PromotionMarketingAccountBillsTest.php
qiuxiang 4a4537612f feat: 将推广费页签迁移到营销账户账单 (#711)
> 开工时标题使用 `WIP: <任务名称>` 并先填写“任务占用与边界”。Codex 完成独立合并前自审后补齐其余内容并移除 `WIP:`。

## 任务占用与边界

- 任务名称:
- 实际开发者:
- 任务来源/团队分工:
- 目标结果:
- 预计修改路径:
- 将写入的公共契约(路由、Controller 方法、Repository、表/视图、`source_key`、`scope`、权限名、菜单键或共享组件):
- 仅复用、明确不修改的公共契约:
- 数据源、表/视图和字段:
- 上游 `source_key` / `scope`:
- 权限名、菜单键或共享组件:
- 明确不修改的范围:

## 开工碰撞检查

- [ ] Codex 已执行 `scripts/codex-task-preflight.ps1`
- [ ] 已检查最新 `main` 和所有远程任务分支
- [ ] 已搜索主线既有页面、接口、Repository 和数据查询
- [ ] 已把“将写入”和“仅复用、不修改”的公共契约分开;仅复用项不作为单独阻断条件
- [ ] 未发现实际写入重叠;或已在下方写明相关分支、具体重叠项和协调结论

相关分支、重叠项与协调结论:

## 实际改动

- 完成内容:
- Codex 实际修改的文件:
- 新增或变更的公共契约:
- 与原计划的差异:

## 验证证据

- 正常流程:
- 空数据/错误输入/无权限场景:
- 受影响旧功能:
- 自动测试及结果:
- 实际页面、接口或只读业务查询结果:

## 风险检查

- [ ] 不涉及金额、权限、审批、数据库迁移、生产配置或数据修复
- [ ] 如涉及上述高风险内容,已在下方写明影响、回滚和所需授权
- [ ] 未提交 `.env`、密码、Token、私钥、真实财务数据、生产日志或构建产物
- [ ] 前端改动符合 `DESIGN.md`;无前端改动则写“不适用”

高风险影响、回滚和授权状态:

## 合并前复查

- [ ] 已重新拉取远程引用并用实际修改范围再次运行碰撞检查
- [ ] PR 不再是 `WIP:`,所有测试结果已经补齐
- [ ] Codex 已重新读取原始需求和验收标准,并逐文件审核 `origin/main...任务分支` 的完整 diff
- [ ] Codex 自审确认无越界修改、重复实现、敏感内容、未处理冲突和未验证结果
- [ ] 合并顺序不会覆盖其他未合并任务
- [ ] 已授权当前任务的 Codex 执行 Squash merge、验证 `main` 并清理任务分支
- [ ] 如涉及生产迁移、数据修复、删除、覆盖配置或部署,已另行取得对应高风险授权

Reviewed-on: #711
2026-10-28 11:16:15 +08:00

162 lines
6.8 KiB
PHP

<?php
namespace Tests\Feature\Business;
use Mockery;
use Modules\EcommerceFinance\Repositories\PromotionMarketingExpenseRepository;
use Modules\EcommerceFinance\Repositories\TmallTaobaoSpreadBillRepository;
use Tests\Feature\FeatureTestCase;
class PromotionMarketingAccountBillsTest extends FeatureTestCase
{
public function test_promotion_marketing_tab_is_first_inside_marketing_account_bills(): void
{
$pages = collect(config('business_navigation'))
->firstWhere('slug', 'billing-transactions')['pages'];
$receivingTabs = collect($pages)->firstWhere('slug', 'store-receiving-bills')['tabs'];
$marketingTabs = collect($pages)->firstWhere('slug', 'marketing-account-bills')['tabs'];
$this->assertNotContains('promotion-marketing', array_column($receivingTabs, 'slug'));
$this->assertSame('promotion-marketing', $marketingTabs[0]['slug'] ?? null);
$this->assertSame('推广费(营销账户)', $marketingTabs[0]['title'] ?? null);
$this->assertSame('taobao-alipay', $marketingTabs[1]['slug'] ?? null);
$this->loginAs()
->get(route('business.pages.show', [
'section' => 'billing-transactions',
'page' => 'marketing-account-bills',
'tab' => 'promotion-marketing',
]))
->assertOk()
->assertSeeText('推广费(营销账户)')
->assertSee('class="business-blank-page"', false);
}
public function test_promotion_marketing_page_and_details_share_contract(): void
{
$repository = Mockery::mock(PromotionMarketingExpenseRepository::class);
$result = [
'rows' => [[
'id' => 'row-1',
'store_name' => '测试推广店',
'occurred_at' => '2026-10-23 09:00:00',
'notes' => '商品推广',
'expense' => 12.34,
]],
'total' => 1,
'current_page' => 1,
'last_page' => 1,
'per_page' => 20,
'summary' => ['expense' => 12.34, 'count' => 1],
'platform_summary' => [['platform' => '淘系', 'amount' => 12.34]],
];
$repository->shouldReceive('paginate')->once()->ordered()->with([
'start' => '',
'end' => '',
'store_name' => '',
'note' => '',
], 1, 10)->andReturn($result);
$repository->shouldReceive('storeSummary')->once()->ordered()->with([
'start' => '2026-10-01',
'end' => '2026-10-31',
'store_name' => '',
'note' => '推广',
])->andReturn([
'store_summary' => [[
'platform' => '淘系',
'store_name' => '测试推广店',
'amount' => 12.34,
]],
'platform_summary' => [['platform' => '淘系', 'amount' => 12.34]],
'total' => 12.34,
]);
$this->app->instance(PromotionMarketingExpenseRepository::class, $repository);
$this->loginAs()
->getJson(route('ecommerce-finance.account-bills.index', [
'bill_type' => 'promotion-marketing',
]))
->assertOk()
->assertJsonPath('total', 1)
->assertJsonPath('summary.expense', 12.34)
->assertJsonPath('columns.title', '推广费(营销账户)')
->assertJsonPath('data.0.store_name', '测试推广店')
->assertJsonPath('columns.columns.0.label', '店铺名称')
->assertJsonPath('columns.columns.1.label', '交易时间')
->assertJsonPath('columns.columns.2.label', '备注')
->assertJsonPath('columns.columns.3.label', '支出金额');
$this->loginAs()
->getJson(route('ecommerce-finance.account-bills.promotion-expense-details', [
'bill_type' => 'promotion-marketing',
'start' => '2026-10-01',
'end' => '2026-10-31',
'note' => '推广',
]))
->assertOk()
->assertJsonPath('total', 1)
->assertJsonPath('summary.expense', 12.34)
->assertJsonPath('summary.count', 1)
->assertJsonPath('platform_summary.0.platform', '淘系')
->assertJsonPath('store_summary.0.platform', '淘系')
->assertJsonPath('store_summary.0.store_name', '测试推广店')
->assertJsonPath('store_summary.0.amount', 12.34);
}
public function test_taobao_alipay_tab_mounts_the_promotion_card_before_income(): void
{
$repository = Mockery::mock(TmallTaobaoSpreadBillRepository::class);
$repository->shouldReceive('usesBusinessMirror')->andReturnTrue();
$repository->shouldReceive('paginate')->once()->andReturn(['rows' => [], 'total' => 0]);
$repository->shouldReceive('summary')->once()->andReturn([
'income_total' => 100,
'expense_total' => 0,
'net' => 100,
'count' => 1,
]);
$repository->shouldReceive('storeOptions')->once()->andReturn([]);
$this->app->instance(TmallTaobaoSpreadBillRepository::class, $repository);
$response = $this->loginAs()->get(route('business.pages.show', [
'section' => 'billing-transactions',
'page' => 'marketing-account-bills',
'tab' => 'taobao-alipay',
]))->assertOk();
$html = $response->getContent();
$promotionPosition = strpos($html, 'data-promotion-expense-summary');
$incomePosition = strpos($html, '收入合计(万元)');
$this->assertNotFalse($promotionPosition);
$this->assertNotFalse($incomePosition);
$this->assertLessThan($incomePosition, $promotionPosition);
$response
->assertSee('public/js/business/pages.min.js', false)
->assertSee(route('ecommerce-finance.account-bills.index', [
'bill_type' => 'promotion-marketing',
]), false);
}
public function test_promotion_marketing_store_options_come_from_the_same_date_range(): void
{
$repository = Mockery::mock(PromotionMarketingExpenseRepository::class);
$repository->shouldReceive('storeOptions')->once()->with([
'start' => '2026-10-01',
'end' => '2026-10-31',
'store_name' => '',
'note' => '',
])->andReturn(['测试推广店', '另一推广店']);
$this->app->instance(PromotionMarketingExpenseRepository::class, $repository);
$this->loginAs()
->getJson(route('ecommerce-finance.account-bills.filter-options', [
'bill_type' => 'promotion-marketing',
'start' => '2026-10-01',
'end' => '2026-10-31',
]))
->assertOk()
->assertJsonPath('shops.0', '测试推广店')
->assertJsonPath('shops.1', '另一推广店');
}
}