diff --git a/AGENTS.md b/AGENTS.md index 39fa370..2a2e661 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,7 +34,7 @@ - 默认验收身份:`merchant_admin`,用于“客户端”的样式、路由、权限展示和交互验收。 - 密码不写入仓库;从本机未提交环境变量 `CHONGCHONG_CLIENT_ACCEPTANCE_PASSWORD` 或 `C:/Users/BY-OY/.codex/chongchong-global-dev-acceptance.env` 读取。若凭据不存在,再向用户索取,不重复询问账号名。 - 该身份仅适用于开发环境。不要把密码、Token 或真实接口地址写入源码、测试快照、日志或 `AGENTS.md`;开发环境变更时只更新本机凭据。 -- 样式验收优先使用该身份进入真实页面,并按原型还原规则检查加载、空态、错误态、权限态和窄屏布局。 +- 由人工使用该身份进入真实页面,按 `1920×1080` 视口和 `100%` 浏览器缩放完成操作与截图,再把截图发送给 AI 核对加载、空态、错误态、权限态和桌面布局;AI 默认不主动调用截图或视觉类技能取证。 ## 模块快速查询 @@ -46,7 +46,7 @@ | 企业资料、成员与角色 | `src/modules/tenant-management/`、`src/modules/account/` | Backend `iam/`、`file/` | | 工作台与系统入口 | `src/modules/dashboard/`、`src/modules/system/` | `src/navigation/clientNavigation.ts`、`src/router/index.ts` | | 企业成员消息收件箱 | `src/modules/notifications/` | Backend `iam/src/main/java/com/baiyue/iam/notification/`;列表、详情、已读与未读数契约见 `iam/src/main/resources/openapi/notification-v1.yaml` | -| 头程下单、订单与履约进度 | `src/modules/first-leg/` | Backend `order/`、`firstleg/`、`warehouse/` | +| 头程下单、订单、箱唛与履约进度 | `src/modules/first-leg/`;箱唛下载消费者从 `views/FirstLegOrdersView.vue`、`api/firstLeg.ts` 进入 | Backend `order/`、`firstleg/`、`warehouse/`;稳定箱码与打印契约先读 Backend `firstleg/AGENTS.md` | | 统一订单展示 | `src/modules/order-management/` | `src/modules/first-leg/` 与对应后端订单契约 | | 客户财务账单、收款与未收余额 | `src/modules/finance/` | Backend `finance/`;以登录态客户隔离,只读接口从 `api/finance.ts` 进入 | | 跨境与海外仓候选能力 | `src/modules/cross-border/`、`src/modules/overseas-warehouse/` | 先核对各自 `routes.ts` 的发布状态与阶段范围,不自动扩展实现 | @@ -71,7 +71,7 @@ - 优先使用 Element Plus 和 Lucide 图标;不使用表情符号代替功能图标,不引入第二套组件库。 - 业务页按实际场景覆盖适用的加载、空数据、失败、无权限和数据过期状态;危险操作需要确认和明确结果。 - 保持键盘可达、可见焦点、语义化标签、足够对比度和清晰错误提示。 -- 响应式变更至少检查 320px、720px 和桌面布局;避免只能横向滚动才能完成核心任务。 +- 当前样式开发与验收只处理 `1920×1080` 桌面视口,不检查或整改 `720px`、`320px` 及其他窄屏;这些视口不作为测试、截图或交付门禁。保留现有响应式实现,不为当前阶段主动删除,用户恢复多视口验收后再统一处理。 ## 本地命令与质量门禁 @@ -94,7 +94,7 @@ pnpm build - Client 全量验收唯一入口为 `../../baiyue-firstleg/scripts/client-test-env-acceptance.mjs`;二期专项场景才单独执行 `client-phase2-test-env-acceptance.mjs`,不替代完整入口。 - 入口通过测试环境 HTTP 验证注册/审核、客户隔离、订单、入库计费、文件、财务和权限链路,会创建或修改测试数据,必须获得当前对话明确授权。 - `pnpm test/typecheck` 和 `frontend-full-regression.ps1` 仅是本地质量检查,不代表 Client 服务已完成全量验收。 -- HTTP 验收通过后,再使用开发验收账号通过浏览器逐页检查真实接口调用、客户可见字段、原型、权限、错误态、空态和窄屏布局。 +- HTTP 验收通过后,由人工使用开发验收账号逐页检查真实接口调用、客户可见字段、原型、权限、错误态、空态和 `1920×1080` 桌面布局,并将截图发送给 AI 分析;未收到人工截图时标记为待人工确认,不追加 AI 截图或窄屏浏览器验收。 ## Client 全量验收快速索引 diff --git a/src/modules/finance/api/adapters.ts b/src/modules/finance/api/adapters.ts index b8bed3d..33ea5c8 100644 --- a/src/modules/finance/api/adapters.ts +++ b/src/modules/finance/api/adapters.ts @@ -2,6 +2,7 @@ import type { FinanceBillDetail, FinanceBillLine, FinanceBillStatus, + FinanceCustomerConfirmationStatus, FinanceBillSummary, FinanceReceiptRecord, } from '../contracts' @@ -20,6 +21,10 @@ export interface FinanceBillWire { totalAmount: WireScalar receivedAmount: WireScalar outstandingAmount: WireScalar + version: number + customerConfirmationStatus: FinanceCustomerConfirmationStatus + customerConfirmedByName: string | null + customerConfirmedAt: string | null } export interface FinanceBillLineWire { @@ -73,6 +78,10 @@ function mapBillSummary(bill: FinanceBillWire): FinanceBillSummary { issueDate: bill.billDate, dueDate: bill.dueDate, status: bill.status, + version: bill.version ?? 0, + customerConfirmationStatus: bill.customerConfirmationStatus ?? 'NOT_REQUIRED', + customerConfirmedByName: bill.customerConfirmedByName ?? null, + customerConfirmedAt: bill.customerConfirmedAt ?? null, } } diff --git a/src/modules/finance/api/finance.ts b/src/modules/finance/api/finance.ts index 14138af..fa5a2db 100644 --- a/src/modules/finance/api/finance.ts +++ b/src/modules/finance/api/finance.ts @@ -1,5 +1,5 @@ import { apiRequest } from '@/api/http' -import type { FinanceBillDetail, FinanceBillPage, FinanceBillQuery, FinanceReceiptRecord } from '../contracts' +import type { FinanceBillDetail, FinanceBillPage, FinanceBillQuery, FinanceCustomerConfirmationResult, FinanceReceiptRecord } from '../contracts' import { mapFinanceBillDetail, mapFinanceBills, @@ -54,3 +54,18 @@ export async function getMerchantFinanceReceipts(billId: string): Promise({ + method: 'POST', + url: `/client/finance/v1/statements/${encodeURIComponent(statementId)}/confirm`, + headers: { 'Idempotency-Key': idempotencyKey }, + data: { expectedVersion }, + }) +} diff --git a/src/modules/finance/contracts.ts b/src/modules/finance/contracts.ts index 59e5b0c..0eaafac 100644 --- a/src/modules/finance/contracts.ts +++ b/src/modules/finance/contracts.ts @@ -1,6 +1,7 @@ import type { PageResult } from '@/modules/first-leg/api/firstLeg' export type FinanceBillStatus = 'DRAFT' | 'ISSUED' | 'PARTIALLY_PAID' | 'PAID' | 'VOIDED' +export type FinanceCustomerConfirmationStatus = 'NOT_REQUIRED' | 'PENDING_CONFIRMATION' | 'CONFIRMED' export interface FinanceBillQuery { keyword?: string @@ -20,6 +21,19 @@ export interface FinanceBillSummary { issueDate: string | null dueDate: string | null status: FinanceBillStatus + version: number + customerConfirmationStatus: FinanceCustomerConfirmationStatus + customerConfirmedByName: string | null + customerConfirmedAt: string | null +} + +export interface FinanceCustomerConfirmationResult { + statementId: string + confirmationStatus: 'CONFIRMED' + confirmedBy: string + confirmedByName: string + confirmedAt: string + version: number } export interface FinanceBillLine { diff --git a/src/modules/finance/views/FinanceBillsView.test.mjs b/src/modules/finance/views/FinanceBillsView.test.mjs index fc471e7..da7f842 100644 --- a/src/modules/finance/views/FinanceBillsView.test.mjs +++ b/src/modules/finance/views/FinanceBillsView.test.mjs @@ -20,7 +20,7 @@ async function importTypeScriptModule(url) { const financeAdapters = await importTypeScriptModule(new URL('../api/adapters.ts', import.meta.url)) const permissionContracts = await importTypeScriptModule(new URL('../../tenant-management/contracts.ts', import.meta.url)) -test('activates the customer finance route with a read-only permission', () => { +test('activates the customer finance route with its existing list and detail permission', () => { assert.match(routesSource, /path: 'finance\/bills'/) assert.match(routesSource, /FinanceBillsView\.vue/) assert.match(routesSource, /release: 'active'/) @@ -28,13 +28,17 @@ test('activates the customer finance route with a read-only permission', () => { assert.doesNotMatch(routesSource, /finance\/payables|应付账单/) }) -test('uses client finance read APIs without browser-selected customer scope', () => { +test('uses authenticated-scope finance APIs and confirms without browser-selected customer scope', () => { assert.match(apiSource, /method: 'GET'/) assert.match(apiSource, /\/client\/finance\/v1\/bills/) assert.match(apiSource, /\/client\/finance\/v1\/receipts/) assert.doesNotMatch(apiSource, /\/merchant\/v1\/finance/) assert.doesNotMatch(apiSource, /customerId|tenantId/) - assert.doesNotMatch(apiSource, /method: '(POST|PUT|PATCH|DELETE)'/) + assert.match(apiSource, /method: 'POST'/) + assert.match(apiSource, /\/client\/finance\/v1\/statements\/\$\{encodeURIComponent\(statementId\)\}\/confirm/) + assert.match(apiSource, /'Idempotency-Key'/) + assert.match(apiSource, /data: \{ expectedVersion \}/) + assert.doesNotMatch(apiSource, /method: '(PUT|PATCH|DELETE)'/) assert.doesNotMatch(apiSource, /params:\s*\{\s*billId/) }) @@ -65,6 +69,10 @@ test('does not expose draft or voided bills returned by the server', () => { totalAmount: '100.00', receivedAmount: '0.00', outstandingAmount: '100.00', + version: 1, + customerConfirmationStatus: 'PENDING_CONFIRMATION', + customerConfirmedByName: null, + customerConfirmedAt: null, }) const visible = financeAdapters.mapFinanceBills([ bill(1, 'DRAFT'), @@ -82,6 +90,7 @@ test('maps nested bill detail and customer-safe receipt allocations', () => { id: 2, customerId: 10001, billNo: 'B-2', customerName: '测试客户', currency: 'CNY', billDate: '2026-08-11', dueDate: '2026-09-10', status: 'ISSUED', totalAmount: '100.00', receivedAmount: '40.00', outstandingAmount: '60.00', + version: 3, customerConfirmationStatus: 'PENDING_CONFIRMATION', customerConfirmedByName: null, customerConfirmedAt: null, }, lines: [{ id: 21, sourceType: 'INBOUND_FEE', sourceId: 81, sourceVersion: 3, orderId: 31, inboundId: 41, feeItemCode: 'FREIGHT', feeItemName: '运费', amount: '100.00', currency: 'CNY' }], }) @@ -104,21 +113,21 @@ test('requires both finance statement authorities for the page permission', () = assert.equal(allowed(['merchant:finance:statement:list', 'merchant:finance:statement:view']), true) }) -test('keeps actions first and exposes list, balance, detail, receipt, empty and error states', () => { +test('keeps actions first and exposes list, balance, detail, confirmation, receipt, empty and error states', () => { const actionIndex = viewSource.indexOf('label="操作"') const billNumberIndex = viewSource.indexOf('label="账单编号"') assert.ok(actionIndex >= 0 && actionIndex < billNumberIndex) - for (const expected of ['未收余额', '账单详情', '收款记录', '暂无账单', '暂无收款记录', 'state-panel--error']) { + for (const expected of ['未收余额', '账单详情', '确认账单', '客户确认信息', '收款记录', '暂无账单', '暂无收款记录', 'state-panel--error']) { assert.match(viewSource, new RegExp(expected)) } - for (const forbidden of ['确认账单', '发起争议', '新增收款', '核销', '修改金额']) { + assert.match(viewSource, /auth\.hasPermission\('finance:bill:confirm'\)/) + for (const forbidden of ['发起争议', '新增收款', '核销', '修改金额']) { assert.doesNotMatch(viewSource, new RegExp(forbidden)) } }) -test('catalogs customer finance as an assignable view-only role permission', () => { +test('keeps the existing customer finance view permission mapping', () => { assert.match(permissionSource, /code: 'finance:bill:view'/) - assert.match(permissionSource, /不包含任何财务写操作/) assert.match(permissionSource, /merchant:finance:statement:list/) assert.match(permissionSource, /merchant:finance:statement:view/) assert.match(permissionSource, /FRONTEND_PERMISSIONS_REQUIRING_ALL_BACKEND_CODES/) diff --git a/src/modules/finance/views/FinanceBillsView.vue b/src/modules/finance/views/FinanceBillsView.vue index 8088f22..d3e055b 100644 --- a/src/modules/finance/views/FinanceBillsView.vue +++ b/src/modules/finance/views/FinanceBillsView.vue @@ -1,11 +1,14 @@