diff --git a/src/modules/finance/api/finance.test.ts b/src/modules/finance/api/finance.test.ts index ba891da5..3e53ae0a 100644 --- a/src/modules/finance/api/finance.test.ts +++ b/src/modules/finance/api/finance.test.ts @@ -21,7 +21,7 @@ describe('finance api', () => { }) it('uses only the formal settlement customer summary and keeps money as decimal strings', async () => { - const get = vi.spyOn(http, 'get').mockResolvedValue({ code: '200', data: { customerId: '9007199254740993', currency: 'CNY', totalReceivable: '123456.780000', receivedAmount: '100000.000000', outstandingReceivable: '23456.780000', pendingBilling: '0.000000', outstandingPayable: '0.000000', asOf: '2026-08-13T04:00:00Z', lastChangedAt: null } }) + const get = vi.spyOn(http, 'get').mockResolvedValue({ code: '200', data: { customerId: '9007199254740993', currency: 'CNY', totalReceivable: '123456.780000', receivedAmount: '100000.000000', outstandingReceivable: '23456.780000', pendingBilling: '0.000000', totalCost: '300.000000', paidCost: '200.000000', outstandingPayable: '100.000000', asOf: '2026-08-13T04:00:00Z', lastChangedAt: null } }) const result = await getFinanceCustomerAccountSummary('9007199254740993') @@ -57,12 +57,12 @@ describe('finance api', () => { }) it('uses the formal G1 account list path and fixed page DTO', async () => { - const get = vi.spyOn(http, 'get').mockResolvedValue({ code: '200', message: 'success', requestId: 'req-g1', traceId: 'trace-g1', data: [{ customerId: 10001, customerCode: 'C-10001', customerName: '客户A', contactName: '张三', contactPhone: '13800000000', outstandingReceivable: 300, pendingBilling: 50, receivedInPeriod: 100, outstandingPayable: 80, status: 'ACTIVE', lastChangedAt: '2026-08-11T10:00:00' }], page: { page: 2, pageSize: 20, total: 21 } }) + const get = vi.spyOn(http, 'get').mockResolvedValue({ code: '200', message: 'success', requestId: 'req-g1', traceId: 'trace-g1', data: [{ customerId: 10001, customerCode: 'C-10001', customerName: '客户A', contactName: '张三', contactPhone: '13800000000', outstandingReceivable: 300, pendingBilling: 50, receivedInPeriod: 100, totalCost: 260, paidCost: 180, outstandingPayable: 80, status: 'ACTIVE', lastChangedAt: '2026-08-11T10:00:00' }], page: { page: 2, pageSize: 20, total: 21 } }) const result = await listFinanceAccounts({ page: 2, size: 20, customerId: '10001', status: 'ACTIVE' }) expect(get).toHaveBeenCalledWith('/platform/finance/v1/accounts', { params: { page: 2, pageSize: 20, customerId: '10001', status: 'ACTIVE' } }) - expect(result).toEqual({ records: [{ customerId: '10001', customerCode: 'C-10001', customerName: '客户A', contactName: '张三', contactPhone: '13800000000', outstandingReceivable: 300, pendingBilling: 50, receivedInPeriod: 100, outstandingPayable: 80, status: 'ACTIVE', lastChangedAt: '2026-08-11T10:00:00' }], total: 21, pages: 2 }) + expect(result).toEqual({ records: [{ customerId: '10001', customerCode: 'C-10001', customerName: '客户A', contactName: '张三', contactPhone: '13800000000', outstandingReceivable: 300, pendingBilling: 50, receivedInPeriod: 100, totalCost: 260, paidCost: 180, outstandingPayable: 80, status: 'ACTIVE', lastChangedAt: '2026-08-11T10:00:00' }], total: 21, pages: 2 }) }) it('uses the formal G1 account ledger path and fixed page DTO', async () => { diff --git a/src/modules/finance/api/finance.ts b/src/modules/finance/api/finance.ts index 6ce1c52f..368c4bf9 100644 --- a/src/modules/finance/api/finance.ts +++ b/src/modules/finance/api/finance.ts @@ -9,6 +9,8 @@ export interface FinanceCustomerAccountSummaryResource { receivedAmount: number | string outstandingReceivable: number | string pendingBilling: number | string + totalCost: number | string + paidCost: number | string outstandingPayable: number | string asOf: string lastChangedAt: string | null @@ -103,6 +105,7 @@ export async function getFinanceCustomerAccountSummary(customerId: string): Prom customerId: stringValue(item.customerId), currency: 'CNY', totalReceivable: stringValue(item.totalReceivable), receivedAmount: stringValue(item.receivedAmount), outstandingReceivable: stringValue(item.outstandingReceivable), pendingBilling: stringValue(item.pendingBilling), + totalCost: stringValue(item.totalCost), paidCost: stringValue(item.paidCost), outstandingPayable: stringValue(item.outstandingPayable), asOf: stringValue(item.asOf), lastChangedAt: item.lastChangedAt == null ? null : stringValue(item.lastChangedAt), } @@ -121,7 +124,7 @@ export async function listFinanceAccounts(params: { page?: number; size?: number const query = { page: params.page ?? 1, pageSize: params.size ?? 20, customerId: params.customerId || undefined, status: params.status || undefined } const response = await http.get & { page?: RawResource }, ApiEnvelope & { page?: RawResource }>(`${basePath}/accounts`, { params: query }) const data = unwrapApiResponse(response) - const records = data.map((item) => ({ customerId: stringValue(item.customerId), customerCode: stringValue(item.customerCode), customerName: stringValue(item.customerName), contactName: stringValue(item.contactName), contactPhone: stringValue(item.contactPhone), outstandingReceivable: numberValue(item.outstandingReceivable), pendingBilling: numberValue(item.pendingBilling), receivedInPeriod: numberValue(item.receivedInPeriod), outstandingPayable: numberValue(item.outstandingPayable), status: stringValue(item.status), lastChangedAt: stringValue(item.lastChangedAt) })) + const records = data.map((item) => ({ customerId: stringValue(item.customerId), customerCode: stringValue(item.customerCode), customerName: stringValue(item.customerName), contactName: stringValue(item.contactName), contactPhone: stringValue(item.contactPhone), outstandingReceivable: numberValue(item.outstandingReceivable), pendingBilling: numberValue(item.pendingBilling), receivedInPeriod: numberValue(item.receivedInPeriod), totalCost: numberValue(item.totalCost), paidCost: numberValue(item.paidCost), outstandingPayable: numberValue(item.outstandingPayable), status: stringValue(item.status), lastChangedAt: stringValue(item.lastChangedAt) })) const total = numberValue(response.page?.total ?? records.length) const pageSize = numberValue(response.page?.pageSize ?? query.pageSize) || query.pageSize return { records, total, pages: Math.max(1, Math.ceil(total / pageSize)) } diff --git a/src/modules/finance/financeDelivery.test.ts b/src/modules/finance/financeDelivery.test.ts index ad52aa23..0cbe8bac 100644 --- a/src/modules/finance/financeDelivery.test.ts +++ b/src/modules/finance/financeDelivery.test.ts @@ -82,7 +82,7 @@ describe('finance six-page delivery contract', () => { const columns = page.slice(page.indexOf('const columns:')) expect(columns.indexOf("key: 'actions'")).toBeLessThan(columns.indexOf("key: 'direction'")) expect(page).toContain('') + expect(page).toContain('') expect(page).not.toContain(' { vi.stubGlobal('ResizeObserver', ResizeObserverStub) vi.stubGlobal('localStorage', memoryStorage()) vi.stubGlobal('sessionStorage', memoryStorage()) - vi.mocked(listFinanceAccounts).mockReset().mockResolvedValue({ records: [{ customerId: '10001', customerCode: 'C-001', customerName: '客户A', contactName: '张三', contactPhone: '13800000000', outstandingReceivable: 100, pendingBilling: 20, receivedInPeriod: 30, outstandingPayable: 40, status: 'ACTIVE', lastChangedAt: '2026-08-13 10:00:00' }], total: 1, pages: 1 }) + vi.mocked(listFinanceAccounts).mockReset().mockResolvedValue({ records: [{ customerId: '10001', customerCode: 'C-001', customerName: '客户A', contactName: '张三', contactPhone: '13800000000', outstandingReceivable: 100, pendingBilling: 20, receivedInPeriod: 30, totalCost: 90, paidCost: 50, outstandingPayable: 40, status: 'ACTIVE', lastChangedAt: '2026-08-13 10:00:00' }], total: 1, pages: 1 }) vi.mocked(listFinanceAccountLedger).mockReset().mockResolvedValue({ records: [{ ledgerId: '1', occurredAt: '2026-08-13T10:00:00', businessType: 'REC', documentType: 'RECEIPT', documentId: '2', documentNo: 'REC-2', receivableChange: -10, payableChange: 0, outstandingReceivableAfter: 90, operatorName: 'operator-1000001', remark: '收款' }], total: 1, pages: 1 }) vi.mocked(listPlatformTenants).mockReset().mockResolvedValue({ records: [{ id: '10001', tenantName: '客户A', status: 'ACTIVE' } as PlatformTenantResource], page: 1, size: 200, total: 1, pages: 1 }) vi.mocked(createPlatformExportTask).mockReset().mockResolvedValue({ id: 'exp-1', taskNo: 'EXP-1', taskName: '客户账户', status: 'PENDING' } as never) @@ -155,7 +155,7 @@ describe('FinanceCustomerAccountsView delivery', () => { }) it('shows explicit master-data empty states without exposing the customer id', async () => { - vi.mocked(listFinanceAccounts).mockResolvedValue({ records: [{ customerId: '10001', customerCode: '', customerName: '客户A', contactName: '', contactPhone: '', outstandingReceivable: 0, pendingBilling: 0, receivedInPeriod: 0, outstandingPayable: 0, status: 'ACTIVE', lastChangedAt: '' }], total: 1, pages: 1 }) + vi.mocked(listFinanceAccounts).mockResolvedValue({ records: [{ customerId: '10001', customerCode: '', customerName: '客户A', contactName: '', contactPhone: '', outstandingReceivable: 0, pendingBilling: 0, receivedInPeriod: 0, totalCost: 0, paidCost: 0, outstandingPayable: 0, status: 'ACTIVE', lastChangedAt: '' }], total: 1, pages: 1 }) const wrapper = await mountAccountsView(['platform:finance:customer-account:list']) expect(wrapper.text()).toContain('客户编号') expect(wrapper.text()).toContain('联系人') diff --git a/src/modules/finance/pages/FinanceCustomerAccountsView.vue b/src/modules/finance/pages/FinanceCustomerAccountsView.vue index 1d20e88c..40460837 100644 --- a/src/modules/finance/pages/FinanceCustomerAccountsView.vue +++ b/src/modules/finance/pages/FinanceCustomerAccountsView.vue @@ -68,6 +68,8 @@ const columns: DataTableColumn[] = [ { key: 'outstandingReceivable', label: '待收(CNY)', width: 130 }, { key: 'pendingBilling', label: '待开账(CNY)', width: 140 }, { key: 'receivedInPeriod', label: '本期已收(CNY)', width: 140 }, + { key: 'totalCost', label: '总成本(CNY)', width: 140 }, + { key: 'paidCost', label: '已付成本(CNY)', width: 150 }, { key: 'outstandingPayable', label: '待付(CNY)', width: 130 }, { key: 'status', label: '客户状态', width: 110, slotName: 'status' }, { key: 'lastChangedAt', label: '最近变动', width: 170, slotName: 'lastChangedAt' }, @@ -89,8 +91,10 @@ const summary = computed(() => rows.value.reduce((result, row) => ({ receivable: result.receivable + row.outstandingReceivable, pending: result.pending + row.pendingBilling, received: result.received + row.receivedInPeriod, + totalCost: result.totalCost + row.totalCost, + paidCost: result.paidCost + row.paidCost, payable: result.payable + row.outstandingPayable, -}), { receivable: 0, pending: 0, received: 0, payable: 0 })) +}), { receivable: 0, pending: 0, received: 0, totalCost: 0, paidCost: 0, payable: 0 })) async function load(model: QueryModel = filters.value) { Object.assign(filters.value, model) @@ -263,7 +267,7 @@ watch(() => [route.name, String(route.params.customerId || '')], ([name, custome - + @@ -271,7 +275,7 @@ watch(() => [route.name, String(route.params.customerId || '')], ([name, custome