期初余额取所选月份开始前聚水潭京东钱包每店铺账户最后一笔余额;期末余额统一按期初加收入减支出计算,并增加余额查询索引与定向测试。 > 开工时标题使用 `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: #901
164 lines
10 KiB
PHP
164 lines
10 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\View;
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
class AccountBillsTableTest extends TestCase
|
|
{
|
|
public function test_authoritative_bill_pages_use_stable_date_controls_and_existing_table_interactions(): void
|
|
{
|
|
$source = file_get_contents(__DIR__ . '/../../../resources/assets/js/components/business/AccountBills.vue');
|
|
|
|
$this->assertStringContainsString("isDouyin(){return this.billType==='douyin';}", $source);
|
|
$this->assertStringContainsString("usesStableDateControl(){return true;}", $source);
|
|
$this->assertStringContainsString("{ 'is-stable-date': usesStableDateControl }", $source);
|
|
$this->assertStringContainsString('.tw-date.is-stable-date .tw-date-control', $source);
|
|
$this->assertStringContainsString("['douyin','pinduoduo','dewu','taobao-wechat','jd-transactions','jingxi','jd-wallet','vipshop','vipshop-jx','promotion-marketing'].indexOf(this.billType)!==-1", $source);
|
|
$this->assertStringContainsString('v-if="usesMirrorOptions && !filterOptionsError"', $source);
|
|
$this->assertStringContainsString(':page-key="pageKey"', $source);
|
|
$this->assertStringContainsString(':tab-key="billType"', $source);
|
|
$this->assertStringContainsString("value=\"page-visible\"", $source);
|
|
$this->assertStringContainsString("value=\"filtered-visible\"", $source);
|
|
$this->assertStringContainsString("value=\"filtered-all\"", $source);
|
|
$this->assertStringContainsString('position:sticky', $source);
|
|
$this->assertStringContainsString('overflow:auto', $source);
|
|
}
|
|
|
|
public function test_store_receiving_tabs_route_the_vipshop_jx_account(): void
|
|
{
|
|
$source = file_get_contents(__DIR__ . '/../../../resources/assets/js/views/business/pages.js');
|
|
|
|
$this->assertStringContainsString(
|
|
"const GENERIC_TABS = ['taobao-wechat', 'pinduoduo', 'jd-wallet', 'jd-transactions', 'jingxi', 'douyin', 'dewu', 'vipshop', 'vipshop-jx', 'promotion-marketing'];",
|
|
$source,
|
|
);
|
|
$this->assertStringContainsString(
|
|
"const STORE_RECEIVING_TABS = ['taobao-alipay', 'taobao-wechat', 'pinduoduo', 'jd-wallet', 'jd-transactions', 'jingxi', 'douyin', 'dewu', 'vipshop', 'vipshop-jx'];",
|
|
$source,
|
|
);
|
|
$this->assertStringContainsString(
|
|
"context.page === 'store-receiving-bills' || context.tab === 'promotion-marketing'",
|
|
$source,
|
|
);
|
|
}
|
|
|
|
public function test_store_filter_remains_usable_when_options_are_empty_or_the_date_changes(): void
|
|
{
|
|
$source = file_get_contents(__DIR__ . '/../../../resources/assets/js/components/business/AccountBills.vue');
|
|
|
|
$this->assertStringContainsString('filterable allow-create default-first-option', $source);
|
|
$this->assertStringContainsString('没有匹配的店铺,输入后回车使用', $source);
|
|
$this->assertStringContainsString('暂无店铺,输入后回车使用', $source);
|
|
$this->assertStringContainsString('placeholder="输入店铺名称"', $source);
|
|
$this->assertStringContainsString(
|
|
'canUseStoreSelect(){return !this.filterOptionsError&&(this.filterOptionsLoading||this.filterOptions.shops.length>0);}',
|
|
$source,
|
|
);
|
|
$this->assertStringContainsString(
|
|
'if(selected&&shops.indexOf(selected)===-1)shops.unshift(selected);',
|
|
$source,
|
|
);
|
|
$this->assertStringContainsString(
|
|
"const dateRangeChanged=this.queryFilters.start!==this.filters.start||this.queryFilters.end!==this.filters.end;",
|
|
$source,
|
|
);
|
|
$this->assertStringContainsString(
|
|
'if(this.usesMirrorOptions&&!this.compact&&dateRangeChanged)requests.push(this.fetchFilterOptions());',
|
|
$source,
|
|
);
|
|
}
|
|
|
|
public function test_jd_and_jingxi_tabs_share_the_same_date_and_summary_behaviour(): void
|
|
{
|
|
$source = file_get_contents(__DIR__ . '/../../../resources/assets/js/components/business/AccountBills.vue');
|
|
|
|
$this->assertStringContainsString("const JD_BILL_TABS = ['jd-transactions','jingxi'];", $source);
|
|
$this->assertStringContainsString("isJdBillTab(){return JD_BILL_TABS.indexOf(this.billType)!==-1;}", $source);
|
|
// 唯品会两个页签的筛选标签是「账单期间」(后端也只按 payload.settle_period 过滤)。
|
|
$this->assertStringContainsString("const DATE_FILTER_LABELS = {'jd-transactions':'费用结算时间','jingxi':'费用结算时间','jd-wallet':'费用结算时间','douyin':'结算时间','vipshop':'账单期间','vipshop-jx':'账单期间','promotion-marketing':'交易时间'};", $source);
|
|
$this->assertStringContainsString('{{ dateFilterLabel }}', $source);
|
|
$this->assertStringContainsString('v-if="isJdBillTab && sync"', $source);
|
|
$this->assertStringContainsString("netLabel=JD_BILL_TABS.concat(['vipshop-jx']).indexOf(this.billType)!==-1", $source);
|
|
}
|
|
|
|
public function test_taobao_wechat_places_alipay_balances_before_income(): void
|
|
{
|
|
$source = file_get_contents(__DIR__ . '/../../../resources/assets/js/components/business/AccountBills.vue');
|
|
|
|
$this->assertStringContainsString("'tw-stats--taobao-wechat': billType === 'taobao-wechat'", $source);
|
|
$this->assertStringContainsString("if(this.billType==='taobao-wechat')", $source);
|
|
$this->assertStringContainsString("label:'期初余额'", $source);
|
|
$this->assertStringContainsString("label:'期末余额(最新)'", $source);
|
|
$this->assertStringContainsString("this.summary.balance_source==='unavailable'?'淘系支付宝余额暂不可用':'同淘系支付宝账户'", $source);
|
|
$this->assertLessThan(
|
|
strpos($source, "label:'收入合计'"),
|
|
strpos($source, "label:'期初余额'"),
|
|
);
|
|
$this->assertStringContainsString('.tw-stats--taobao-wechat{grid-template-columns:repeat(5,minmax(0,1fr))}', $source);
|
|
}
|
|
|
|
public function test_jd_wallet_places_opening_balance_before_income_and_uses_period_formula(): void
|
|
{
|
|
$source = file_get_contents(__DIR__ . '/../../../resources/assets/js/components/business/AccountBills.vue');
|
|
|
|
$this->assertStringContainsString("'tw-stats--jd-wallet': billType === 'jd-wallet'", $source);
|
|
$this->assertStringContainsString("if(this.billType==='jd-wallet')", $source);
|
|
$this->assertStringContainsString("label:'期末余额'", $source);
|
|
$this->assertStringContainsString("note:'期初余额 + 收入合计 - 支出合计'", $source);
|
|
$this->assertStringContainsString('聚水潭京东钱包最后一笔', $source);
|
|
$this->assertStringNotContainsString("this.billType==='jd-wallet'?'账户余额合计(最新)'", $source);
|
|
$this->assertLessThan(
|
|
strpos($source, "label:'收入合计'"),
|
|
strpos($source, "label:'期初余额'"),
|
|
);
|
|
$this->assertStringContainsString('.tw-stats--jd-wallet{grid-template-columns:repeat(5,minmax(0,1fr))}', $source);
|
|
}
|
|
|
|
public function test_promotion_marketing_card_opens_the_store_detail_modal(): void
|
|
{
|
|
$source = file_get_contents(__DIR__ . '/../../../resources/assets/js/components/business/AccountBills.vue');
|
|
|
|
$this->assertStringContainsString('推广费(营销账户)', $source);
|
|
$this->assertStringContainsString("label:'推广费(营销账户)'", $source);
|
|
$this->assertStringContainsString('查看明细 →', $source);
|
|
$this->assertStringContainsString('aria-labelledby="promotion-expense-detail-title"', $source);
|
|
$this->assertStringContainsString('<strong id="promotion-expense-detail-title">推广费(营销账户) · 明细汇总</strong>', $source);
|
|
$this->assertStringContainsString('>平台汇总</button>', $source);
|
|
$this->assertStringContainsString('>店铺明细</button>', $source);
|
|
$this->assertStringContainsString('aria-label="筛选平台"', $source);
|
|
$this->assertStringContainsString('aria-label="筛选店铺名称"', $source);
|
|
$this->assertStringContainsString('aria-label="筛选备注"', $source);
|
|
$this->assertStringContainsString('<th scope="col">平台</th>', $source);
|
|
$this->assertStringContainsString('<th scope="col">店铺名称</th>', $source);
|
|
$this->assertStringContainsString('<th scope="col" class="is-number">推广费金额(万元)</th>', $source);
|
|
$this->assertStringContainsString('promotionFilteredPlatformRows', $source);
|
|
$this->assertStringContainsString('promotionFilteredStoreRows', $source);
|
|
$this->assertStringContainsString('tw-stats--promotion-only', $source);
|
|
$this->assertStringContainsString('grid-template-columns:minmax(0,1.15fr) repeat(3,minmax(0,1fr))', $source);
|
|
$this->assertStringContainsString('tw-stats--promotion-compact', $source);
|
|
$this->assertStringContainsString('table-workspace--compact', $source);
|
|
$this->assertStringContainsString('class="tw-date-icon"', $source);
|
|
$this->assertStringContainsString("const overview={label:'推广费(营销账户)'", $source);
|
|
$this->assertStringContainsString("if(this.compact)return [overview];", $source);
|
|
$this->assertStringContainsString("label:'收入金额'", $source);
|
|
$this->assertStringContainsString("label:'支出金额'", $source);
|
|
$this->assertStringContainsString("label:'余额'", $source);
|
|
$this->assertStringContainsString("note:'收入金额 - 支出金额'", $source);
|
|
}
|
|
|
|
public function test_taobao_marketing_tab_places_the_promotion_card_before_income(): void
|
|
{
|
|
$source = file_get_contents(__DIR__ . '/../../../resources/views/banking/marketing_taobao_alipay_bills/partials/embedded-table.blade.php');
|
|
|
|
$promotionPosition = strpos($source, 'data-promotion-expense-summary');
|
|
$incomePosition = strpos($source, '收入合计(万元)');
|
|
|
|
$this->assertNotFalse($promotionPosition);
|
|
$this->assertNotFalse($incomePosition);
|
|
$this->assertLessThan($incomePosition, $promotionPosition);
|
|
$this->assertStringContainsString('sm:grid-cols-5', $source);
|
|
$this->assertStringContainsString('data-api-base=', $source);
|
|
}
|
|
}
|