From fd51b26fc6e41486478186b0aed7a083366cdd33 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 13 Aug 2026 11:57:41 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/finance/api/adapters.ts | 101 ++++++++++-------- src/modules/finance/api/finance.ts | 3 +- src/modules/finance/contracts.ts | 1 - .../finance/views/FinanceBillsView.test.mjs | 48 +++++++-- 4 files changed, 96 insertions(+), 57 deletions(-) diff --git a/src/modules/finance/api/adapters.ts b/src/modules/finance/api/adapters.ts index a17f880..b8bed3d 100644 --- a/src/modules/finance/api/adapters.ts +++ b/src/modules/finance/api/adapters.ts @@ -10,37 +10,51 @@ type WireScalar = string | number export interface FinanceBillWire { id: WireScalar - bill_no: string - customer_name_snapshot: string - currency_code: string - bill_date: string | null - due_date: string | null + customerId: WireScalar + billNo: string + customerName: string + currency: string + billDate: string | null + dueDate: string | null status: FinanceBillStatus - total_amount: WireScalar - received_amount: WireScalar - outstanding_amount: WireScalar - lines?: FinanceBillLineWire[] + totalAmount: WireScalar + receivedAmount: WireScalar + outstandingAmount: WireScalar } export interface FinanceBillLineWire { id: WireScalar - source_no: string | null - order_id: WireScalar | null - fee_item_name: string + sourceType: string + sourceId: WireScalar + sourceVersion: number + orderId: WireScalar | null + inboundId: WireScalar | null + feeItemCode: string + feeItemName: string amount: WireScalar - currency_code: string + currency: string +} + +export interface FinanceBillDetailWire { + statement: FinanceBillWire + lines: FinanceBillLineWire[] +} + +export interface FinanceReceiptAllocationWire { + billId: WireScalar + billNo: string + appliedAmount: WireScalar } export interface FinanceReceiptWire { id: WireScalar - receipt_no: string - received_amount: WireScalar - received_at: string - payment_method: string | null - voucher_no: string | null + receiptNo: string + receivedAmount: WireScalar + receivedAt: string + paymentMethod: string | null + currency: string status: string - bill_id: WireScalar | null - applied_amount: WireScalar | null + allocations: FinanceReceiptAllocationWire[] } function text(value: WireScalar) { @@ -50,14 +64,14 @@ function text(value: WireScalar) { function mapBillSummary(bill: FinanceBillWire): FinanceBillSummary { return { id: text(bill.id), - billNo: bill.bill_no, - customerName: bill.customer_name_snapshot, + billNo: bill.billNo, + customerName: bill.customerName, currencyCode: 'CNY', - totalAmount: text(bill.total_amount), - receivedAmount: text(bill.received_amount), - outstandingAmount: text(bill.outstanding_amount), - issueDate: bill.bill_date, - dueDate: bill.due_date, + totalAmount: text(bill.totalAmount), + receivedAmount: text(bill.receivedAmount), + outstandingAmount: text(bill.outstandingAmount), + issueDate: bill.billDate, + dueDate: bill.dueDate, status: bill.status, } } @@ -65,9 +79,9 @@ function mapBillSummary(bill: FinanceBillWire): FinanceBillSummary { function mapBillLine(line: FinanceBillLineWire): FinanceBillLine { return { id: text(line.id), - orderId: line.order_id == null ? null : text(line.order_id), - feeNo: line.source_no, - feeName: line.fee_item_name, + orderId: line.orderId == null ? null : text(line.orderId), + feeNo: `${line.sourceType}:${text(line.sourceId)}/v${line.sourceVersion}`, + feeName: line.feeItemName, amount: text(line.amount), currencyCode: 'CNY', } @@ -79,27 +93,26 @@ export function mapFinanceBills(bills: FinanceBillWire[]) { .map(mapBillSummary) } -export function mapFinanceBillDetail(bill: FinanceBillWire): FinanceBillDetail { +export function mapFinanceBillDetail(detail: FinanceBillDetailWire): FinanceBillDetail { return { - ...mapBillSummary(bill), - lines: (bill.lines ?? []).map(mapBillLine), + ...mapBillSummary(detail.statement), + lines: (detail.lines ?? []).map(mapBillLine), receipts: [], } } export function mapFinanceReceipts(receipts: FinanceReceiptWire[], billId: string): FinanceReceiptRecord[] { - return receipts - .filter((receipt) => receipt.bill_id != null && text(receipt.bill_id) === billId) - .map((receipt) => ({ + return receipts.flatMap((receipt) => receipt.allocations + .filter((allocation) => text(allocation.billId) === billId) + .map((allocation) => ({ id: text(receipt.id), - receiptNo: receipt.receipt_no, - receivedAt: receipt.received_at, - amount: text(receipt.received_amount), - currencyCode: 'CNY', - method: receipt.payment_method, - voucherNo: receipt.voucher_no, - allocatedAmount: receipt.applied_amount == null ? '0' : text(receipt.applied_amount), - })) + receiptNo: receipt.receiptNo, + receivedAt: receipt.receivedAt, + amount: text(receipt.receivedAmount), + currencyCode: 'CNY' as const, + method: receipt.paymentMethod, + allocatedAmount: text(allocation.appliedAmount), + }))) } export function sumDecimalStrings(values: string[]) { diff --git a/src/modules/finance/api/finance.ts b/src/modules/finance/api/finance.ts index 17df23c..14138af 100644 --- a/src/modules/finance/api/finance.ts +++ b/src/modules/finance/api/finance.ts @@ -5,6 +5,7 @@ import { mapFinanceBills, mapFinanceReceipts, sumDecimalStrings, + type FinanceBillDetailWire, type FinanceBillWire, type FinanceReceiptWire, } from './adapters' @@ -39,7 +40,7 @@ export async function getMerchantFinanceBills(params: FinanceBillQuery = {}): Pr } export async function getMerchantFinanceBill(id: string): Promise { - const response = await apiRequest({ + const response = await apiRequest({ method: 'GET', url: `/client/finance/v1/bills/${encodeURIComponent(id)}`, }) diff --git a/src/modules/finance/contracts.ts b/src/modules/finance/contracts.ts index ab56bb7..59e5b0c 100644 --- a/src/modules/finance/contracts.ts +++ b/src/modules/finance/contracts.ts @@ -38,7 +38,6 @@ export interface FinanceReceiptRecord { amount: string currencyCode: 'CNY' method: string | null - voucherNo: string | null allocatedAmount: string } diff --git a/src/modules/finance/views/FinanceBillsView.test.mjs b/src/modules/finance/views/FinanceBillsView.test.mjs index e1f0635..fc471e7 100644 --- a/src/modules/finance/views/FinanceBillsView.test.mjs +++ b/src/modules/finance/views/FinanceBillsView.test.mjs @@ -38,11 +38,16 @@ test('uses client finance read APIs without browser-selected customer scope', () assert.doesNotMatch(apiSource, /params:\s*\{\s*billId/) }) -test('adapts the finance snake-case wire contract and filters receipts locally by bill id', () => { - for (const field of ['bill_no', 'customer_name_snapshot', 'bill_date', 'total_amount', 'outstanding_amount', 'fee_item_name', 'order_id', 'receipt_no', 'bill_id', 'applied_amount']) { +test('adapts the Finance Java DTO contract and filters nested receipt allocations by bill id', () => { + for (const field of ['billNo', 'customerName', 'billDate', 'totalAmount', 'outstandingAmount', 'feeItemName', 'orderId', 'receiptNo', 'allocations', 'billId', 'appliedAmount']) { assert.match(adapterSource, new RegExp(field)) } - assert.match(adapterSource, /filter\(\(receipt\).*receipt\.bill_id/s) + assert.match(adapterSource, /flatMap\(\(receipt\).*receipt\.allocations/s) + assert.match(adapterSource, /detail\.statement/) + assert.match(adapterSource, /detail\.lines/) + assert.doesNotMatch(adapterSource, /bill_no|customer_name|bill_date|total_amount|outstanding_amount|receipt_no|bill_id|applied_amount/) + assert.doesNotMatch(adapterSource, /receipt\.billId|receipt\.appliedAmount/) + assert.doesNotMatch(adapterSource, /voucherNo|voucher_no/) assert.match(viewSource, /loadDetail\(row\.id\)/) assert.doesNotMatch(viewSource, /loadDetail\(row\.billNo\)/) }) @@ -50,15 +55,16 @@ test('adapts the finance snake-case wire contract and filters receipts locally b test('does not expose draft or voided bills returned by the server', () => { const bill = (id, status) => ({ id, - bill_no: `B-${id}`, - customer_name_snapshot: '测试客户', - currency_code: 'CNY', - bill_date: '2026-08-11', - due_date: '2026-09-10', + customerId: 10001, + billNo: `B-${id}`, + customerName: '测试客户', + currency: 'CNY', + billDate: '2026-08-11', + dueDate: '2026-09-10', status, - total_amount: '100.00', - received_amount: '0.00', - outstanding_amount: '100.00', + totalAmount: '100.00', + receivedAmount: '0.00', + outstandingAmount: '100.00', }) const visible = financeAdapters.mapFinanceBills([ bill(1, 'DRAFT'), @@ -70,6 +76,26 @@ test('does not expose draft or voided bills returned by the server', () => { assert.deepEqual(visible.map((item) => item.status), ['ISSUED', 'PARTIALLY_PAID', 'PAID']) }) +test('maps nested bill detail and customer-safe receipt allocations', () => { + const detail = financeAdapters.mapFinanceBillDetail({ + statement: { + 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', + }, + lines: [{ id: 21, sourceType: 'INBOUND_FEE', sourceId: 81, sourceVersion: 3, orderId: 31, inboundId: 41, feeItemCode: 'FREIGHT', feeItemName: '运费', amount: '100.00', currency: 'CNY' }], + }) + const receipts = financeAdapters.mapFinanceReceipts([{ + id: 51, receiptNo: 'RCPT-51', receivedAmount: '40.00', receivedAt: '2026-08-12T10:00:00', + paymentMethod: 'BANK_TRANSFER', currency: 'CNY', status: 'CONFIRMED', + allocations: [{ billId: 2, billNo: 'B-2', appliedAmount: '40.00' }], + }], '2') + + assert.equal(detail.billNo, 'B-2') + assert.deepEqual(detail.lines[0], { id: '21', orderId: '31', feeNo: 'INBOUND_FEE:81/v3', feeName: '运费', amount: '100.00', currencyCode: 'CNY' }) + assert.deepEqual(receipts[0], { id: '51', receiptNo: 'RCPT-51', receivedAt: '2026-08-12T10:00:00', amount: '40.00', currencyCode: 'CNY', method: 'BANK_TRANSFER', allocatedAmount: '40.00' }) +}) + test('requires both finance statement authorities for the page permission', () => { const allowed = (codes) => permissionContracts.hasMappedMerchantPermission('finance:bill:view', new Set(codes)) assert.equal(allowed([]), false) From 5fc327a14d73c5e4984c72720d9e408e73f3dad0 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 13 Aug 2026 12:22:39 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/http.ts | 2 +- src/modules/auth/views/AuthViews.test.mjs | 7 +++++++ src/modules/auth/views/LoginView.vue | 8 ++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/api/http.ts b/src/api/http.ts index 977ead7..914acfb 100644 --- a/src/api/http.ts +++ b/src/api/http.ts @@ -95,7 +95,7 @@ transport.interceptors.response.use( (response) => response, async (error: AxiosError) => { const request = error.config as RetryableRequest | undefined - const publicAuthPaths = ['/auth/login', '/auth/register', '/auth/refresh'] + const publicAuthPaths = ['/auth/login', '/auth/register', '/auth/refresh', '/auth/impersonations/exchange'] const isPublicAuthRequest = publicAuthPaths.some((path) => request?.url?.endsWith(path)) if (error.response?.status !== 401 || !request || request._retriedAfterRefresh || isPublicAuthRequest) { throw mapError(error) diff --git a/src/modules/auth/views/AuthViews.test.mjs b/src/modules/auth/views/AuthViews.test.mjs index a7d762e..31f9a98 100644 --- a/src/modules/auth/views/AuthViews.test.mjs +++ b/src/modules/auth/views/AuthViews.test.mjs @@ -92,6 +92,13 @@ test('uses real auth endpoints and single-flight refresh handling', () => { assert.doesNotMatch(authStoreSource, /mock-client:/) }) +test('handles impersonation exchange as public authentication and exposes failures', () => { + assert.match(httpSource, /publicAuthPaths = \[[^\]]*'\/auth\/impersonations\/exchange'/) + assert.match(authStoreSource, /\/login\?impersonation=failed/) + assert.match(loginSource, /route\.query\.impersonation === 'failed'/) + assert.match(loginSource, /代登录失败或授权已失效,请返回管理后台重新发起/) +}) + test('treats only body code 200 as success', () => { assert.match(httpSource, /if \(envelope\.code !== '200'\)/) assert.doesNotMatch(httpSource, /if \(envelope\.code !== '0'\)/) diff --git a/src/modules/auth/views/LoginView.vue b/src/modules/auth/views/LoginView.vue index 6d25987..ba3cda6 100644 --- a/src/modules/auth/views/LoginView.vue +++ b/src/modules/auth/views/LoginView.vue @@ -17,6 +17,7 @@ const router = useRouter() const auth = useAuthStore() const formRef = ref() const loading = ref(false) +const impersonationFailed = route.query.impersonation === 'failed' const form = reactive({ account: typeof route.query.account === 'string' ? route.query.account @@ -77,6 +78,13 @@ function showResetHint() {

登录客户工作台

使用企业账号登录,继续管理您的跨境物流业务。

+ From e1ee280bc54c92f6a11326d5316fca1ecf2b359f Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 13 Aug 2026 14:37:57 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6d96090..12229a8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -91,9 +91,10 @@ pnpm build ## 全量验收脚本 -- 本地 Client 自动回归由后端仓库的 `../../baiyue-firstleg/scripts/frontend-full-regression.ps1` 统一编排;先执行 `-DryRun`,再运行 `pnpm test` 和 `pnpm typecheck`。 -- 测试环境接口验收入口为 `../../baiyue-firstleg/scripts/client-test-env-acceptance.mjs`;二期兼容链路再使用 `../../baiyue-firstleg/scripts/client-phase2-test-env-acceptance.mjs`。这些脚本会创建或修改测试数据,必须在当前对话获得明确测试环境授权后执行。 -- 脚本通过只证明自动化契约和接口检查通过;样式验收仍使用开发环境验收账号,通过浏览器逐页检查原型、客户可见字段、权限、错误态、空态和窄屏布局。 +- 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 验收通过后,再使用开发验收账号通过浏览器逐页检查真实接口调用、客户可见字段、原型、权限、错误态、空态和窄屏布局。 ## Client 全量验收快速索引 From 574b62fc6b2a71f3620e7d4c67584a8dda060225 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 13 Aug 2026 14:55:56 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shared/src/index.ts | 1 - src/modules/first-leg/api/unifiedOrders.ts | 1 - src/modules/first-leg/components/InboundBillingPanel.test.mjs | 2 +- src/modules/first-leg/components/InboundBillingPanel.vue | 2 +- src/modules/first-leg/types/inboundBilling.ts | 2 +- src/modules/first-leg/utils/firstLegOrder.test.mjs | 2 +- 6 files changed, 4 insertions(+), 6 deletions(-) diff --git a/shared/src/index.ts b/shared/src/index.ts index ff5fa2a..ceb64c2 100644 --- a/shared/src/index.ts +++ b/shared/src/index.ts @@ -86,7 +86,6 @@ export const FIRST_LEG_ORDER_STATUSES = [ { code: 'DRAFT', label: '草稿' }, { code: 'PENDING_REVIEW', label: '待审核' }, { code: 'WAIT_MEASURE', label: '待测量数据' }, - { code: 'INBOUNDED', label: '已入库' }, { code: 'WAIT_SPLIT', label: '待分柜' }, { code: 'LOADING', label: '装柜中' }, { code: 'LOADED', label: '装柜完成' }, diff --git a/src/modules/first-leg/api/unifiedOrders.ts b/src/modules/first-leg/api/unifiedOrders.ts index 3f24b87..7c7ee97 100644 --- a/src/modules/first-leg/api/unifiedOrders.ts +++ b/src/modules/first-leg/api/unifiedOrders.ts @@ -4,7 +4,6 @@ import type { ApiFirstLegStatus, MerchantFirstLegOrderDetail, PageResult } from export type ApiUnifiedOrderMainStatus = | 'PENDING_REVIEW' | 'WAIT_MEASURE' - | 'INBOUNDED' | 'WAIT_SPLIT' | 'LOADING' | 'LOADED' diff --git a/src/modules/first-leg/components/InboundBillingPanel.test.mjs b/src/modules/first-leg/components/InboundBillingPanel.test.mjs index e45257b..98edded 100644 --- a/src/modules/first-leg/components/InboundBillingPanel.test.mjs +++ b/src/modules/first-leg/components/InboundBillingPanel.test.mjs @@ -10,7 +10,7 @@ test('renders loading, pending, isolated error, and published states', () => { assert.match(source, /role="alert"/) assert.match(source, /v-else-if="props\.unavailable \|\| !props\.data"/) assert.match(source, /role="status"/) - assert.match(source, /已入库 · 费用已发布/) + assert.match(source, /待分柜 · 费用已发布/) assert.match(source, /\$emit\('retry'\)/) }) diff --git a/src/modules/first-leg/components/InboundBillingPanel.vue b/src/modules/first-leg/components/InboundBillingPanel.vue index b745b51..0b15be3 100644 --- a/src/modules/first-leg/components/InboundBillingPanel.vue +++ b/src/modules/first-leg/components/InboundBillingPanel.vue @@ -34,7 +34,7 @@ defineEmits<{

入库与费用

- 已入库 · 费用已发布 + 待分柜 · 费用已发布
diff --git a/src/modules/first-leg/types/inboundBilling.ts b/src/modules/first-leg/types/inboundBilling.ts index bf52f8a..e700078 100644 --- a/src/modules/first-leg/types/inboundBilling.ts +++ b/src/modules/first-leg/types/inboundBilling.ts @@ -6,7 +6,7 @@ export type MerchantInboundProcessStatus = | 'WAIT_PRICING' | 'FEE_CONFIRMING' | 'INBOUNDING' - | 'INBOUNDED' + | 'WAIT_SPLIT' | 'VOIDED' export type MerchantInboundFeeStatus = 'UNPRICED' | 'CALCULATED' | 'CONFIRMED' | 'SENT' | 'VOIDED' diff --git a/src/modules/first-leg/utils/firstLegOrder.test.mjs b/src/modules/first-leg/utils/firstLegOrder.test.mjs index 221113a..4df1da9 100644 --- a/src/modules/first-leg/utils/firstLegOrder.test.mjs +++ b/src/modules/first-leg/utils/firstLegOrder.test.mjs @@ -58,7 +58,7 @@ function validDraft() { test('uses the confirmed status and transport labels', () => { assert.equal(getFirstLegStatusLabel('WAIT_MEASURE'), '待测量数据') - assert.equal(getFirstLegStatusLabel('INBOUNDED'), '已入库') + assert.equal(getFirstLegStatusLabel('WAIT_SPLIT'), '待分柜') assert.equal(getFirstLegStatusLabel('WAIT_SPLIT'), '待分柜') assert.equal(getFirstLegStatusLabel('LOADING'), '装柜中') assert.equal(getFirstLegStatusLabel('LOADED'), '装柜完成')