- {{ lineSourceCache.get(row.sourceFeeId)?.inboundCode || row.inboundId || '—' }}
- {{ lineSourceCache.get(row.sourceFeeId)?.containerNo || (row.containerId ? `柜次ID ${row.containerId}` : '—') }}
- {{ lineSourceCache.get(row.sourceFeeId)?.businessDate || '—' }}
- {{ lineSourceCache.get(row.sourceFeeId)?.quantity ?? '—' }}
- {{ lineSourceCache.get(row.sourceFeeId)?.unitPrice ?? '—' }}
+ {{ row.direction === 'PAYABLE' ? '应付' : '应收' }}
+ {{ financeSourceTypeLabel(row.sourceType) }}
+ {{ row.orderId ? `订单 ${row.orderId}` : '—' }}
+ {{ row.inboundCode || '—' }}
+ {{ row.containerNo || '—' }}
+ {{ row.businessDate || '—' }}
+ {{ row.quantity ?? '—' }}
+ {{ row.unitPrice ?? '—' }}
{{ detail.billNo }}
{{ detail.periodStart || '—' }}
{{ detail.periodEnd || '—' }}
@@ -498,6 +498,7 @@ watch(detailRouteId, (statementId) => {
{{ customerDisplayName(detail.customerId, detail.customerName) }}
{{ formatDateTime(row.transactionDate).slice(0, 10) }}
{{ paymentMethodLabel(row.method) }}
+ {{ row.voucherFileIds.length ? `${row.voucherFileIds.length} 个附件` : (row.referenceNo || '—') }}
{{ operatorDisplayName(row.createdByName) }}
{{ formatDateTime(row.occurredAt) }}
@@ -535,6 +536,9 @@ watch(detailRouteId, (statementId) => {
{{ row.settled ? '是' : '否' }}—
{{ operatorDisplayName(row.createdByName) }}
{{ formatDateTime(row.createdAt) }}
+ {{ formatDateTime(row.submittedAt) }}
+ {{ formatDateTime(row.reviewedAt) }}
+ {{ formatDateTime(row.issuedAt) }}
{{ operatorDisplayName(row.updatedByName) }}
{{ formatDateTime(row.updatedAt) }}
共 {{ filteredRows.length }} 条 · 币种:CNY
diff --git a/src/modules/finance/pages/FinanceFormalSettlementFields.test.ts b/src/modules/finance/pages/FinanceFormalSettlementFields.test.ts
new file mode 100644
index 00000000..ab20d608
--- /dev/null
+++ b/src/modules/finance/pages/FinanceFormalSettlementFields.test.ts
@@ -0,0 +1,64 @@
+import { readFileSync } from 'node:fs'
+import { resolve } from 'node:path'
+import { afterEach, describe, expect, it, vi } from 'vitest'
+import { http } from '@/shared/api/http'
+import { getFinanceSettlement, listFinancePayableSettlements, listFinanceReceivableSettlements } from '../api/finance'
+
+const pageSource = readFileSync(resolve(process.cwd(), 'src/modules/finance/pages/FinanceFormalSettlementPage.vue'), 'utf8')
+
+afterEach(() => vi.restoreAllMocks())
+
+describe('formal settlement field contract', () => {
+ it('maps list business fields without exposing internal versions', async () => {
+ const get = vi.spyOn(http, 'get')
+ .mockResolvedValueOnce({ code: '200', data: [{
+ settlementId: 701, settlementNo: 'AR-701', direction: 'RECEIVABLE', customerId: 10001,
+ customerName: '客户A', reconciliationStatementId: 501, reconciliationStatementNo: 'REC-501',
+ businessPeriod: '2026-08', feeItemSummary: '海运费、拖车费', currency: 'CNY', totalAmount: 300,
+ paidAmount: 100, outstandingAmount: 200, generatedDate: '2026-08-17', dueDate: '2026-09-16',
+ paymentStatus: 'PARTIALLY_PAID', status: 'PARTIALLY_PAID', createdBy: 7, createdByName: '财务员',
+ createdAt: '2026-08-17T09:00:00', updatedAt: '2026-08-17T10:00:00', lastPaymentAt: '2026-08-17T10:00:00', version: 1,
+ }] } as never)
+ .mockResolvedValueOnce({ code: '200', data: [{
+ settlementId: 801, settlementNo: 'AP-801', direction: 'PAYABLE', customerId: 10001,
+ customerName: '客户A', supplierId: 81, supplierName: '承运供应商', businessPeriod: '2026-08',
+ feeItemSummary: '拖车费', currency: 'CNY', totalAmount: 300, paidAmount: 0, outstandingAmount: 300,
+ generatedDate: '2026-08-17', dueDate: '2026-09-16', paymentStatus: 'UNPAID', status: 'ISSUED',
+ createdBy: 7, createdByName: '财务员', createdAt: '2026-08-17T09:00:00', updatedAt: '2026-08-17T09:00:00', version: 0,
+ }] } as never)
+
+ const [receivable] = await listFinanceReceivableSettlements('10001')
+ const [payable] = await listFinancePayableSettlements('10001')
+
+ expect(receivable).toMatchObject({ reconciliationStatementNo: 'REC-501', feeItemSummary: '海运费、拖车费', createdByName: '财务员', lastPaymentAt: '2026-08-17T10:00:00' })
+ expect(payable).toMatchObject({ customerName: '客户A', supplierName: '承运供应商', feeItemSummary: '拖车费', createdAt: '2026-08-17T09:00:00' })
+ expect(get).toHaveBeenNthCalledWith(1, '/platform/finance/v1/receivable-settlements', { params: { customerId: '10001' } })
+ })
+
+ it('maps immutable source identity and payment evidence in detail', async () => {
+ vi.spyOn(http, 'get').mockResolvedValue({ code: '200', data: {
+ settlementId: 801, settlementNo: 'AP-801', direction: 'PAYABLE', customerId: 10001, customerName: '客户A',
+ supplierId: 81, supplierName: '承运供应商', feeItemSummary: '拖车费', totalAmount: 300, paidAmount: 100,
+ outstandingAmount: 200, currency: 'CNY', generatedDate: '2026-08-17', dueDate: '2026-09-16',
+ paymentStatus: 'PARTIALLY_PAID', status: 'PARTIALLY_PAID', createdBy: 7, createdByName: '财务员',
+ createdAt: '2026-08-17T09:00:00', updatedAt: '2026-08-17T10:00:00', lastPaymentAt: '2026-08-17T10:00:00', version: 1,
+ lines: [{ feeSourceId: 21, sourceType: 'CONTAINER_FEE', sourceFeeId: 9001, sourceVersion: 1, direction: 'PAYABLE', customerId: 10001, supplierId: 81, supplierName: '承运供应商', orderId: 11, orderNo: 'FL-11', inboundId: 12, inboundCode: 'IN-12', containerId: 13, containerNo: 'CONT-13', businessDate: '2026-08-15', feeItemName: '拖车费', amount: 300, currency: 'CNY', reviewStatus: 'APPROVED', settlementIncluded: true, sourceStatus: 'AVAILABLE', statementGenerated: true }],
+ transactions: [{ transactionId: 31, transactionType: 'PAYMENT', transactionDate: '2026-08-17', amount: 100, method: 'BANK_TRANSFER', referenceNo: 'BANK-31', voucherFileIds: [41], createdBy: 7, createdByName: '财务员', createdAt: '2026-08-17T10:00:00' }],
+ revisions: [{ revisionNo: 1, action: 'CREATE', beforeAmount: 0, afterAmount: 300, operatorId: 7, operatorName: '财务员', occurredAt: '2026-08-17T09:00:00' }],
+ } } as never)
+
+ const detail = await getFinanceSettlement('801')
+
+ expect(detail.lines[0]).toMatchObject({ orderId: '11', orderNo: 'FL-11', inboundCode: 'IN-12', containerNo: 'CONT-13', feeItemName: '拖车费', amount: 300 })
+ expect(detail.transactions[0]).toMatchObject({ method: 'BANK_TRANSFER', referenceNo: 'BANK-31', voucherFileIds: ['41'], createdByName: '财务员' })
+ })
+
+ it('renders the confirmed list and detail fields as business labels', () => {
+ for (const label of ['关联对账单', '结算周期', '创建时间', '最后收款时间', '最后付款时间', '订单号', '银行流水号/凭证号', '修订记录']) {
+ expect(pageSource).toContain(label)
+ }
+ expect(pageSource).toContain("BANK_TRANSFER: '银行转账'")
+ expect(pageSource).not.toContain('prop="sourceVersion" label="来源版本"')
+ expect(pageSource).not.toContain('{{ detail.version }}')
+ })
+})
diff --git a/src/modules/finance/pages/FinanceFormalSettlementPage.vue b/src/modules/finance/pages/FinanceFormalSettlementPage.vue
index 7f4740f6..46667470 100644
--- a/src/modules/finance/pages/FinanceFormalSettlementPage.vue
+++ b/src/modules/finance/pages/FinanceFormalSettlementPage.vue
@@ -127,30 +127,53 @@ const columns = computed(() => props.kind === 'receivable'
{ key: 'actions', label: '操作', width: 190, fixed: 'left', slotName: 'actions' },
{ key: 'settlementNo', label: '结算单号', minWidth: 180 },
{ key: 'customerId', label: '客户', minWidth: 180, slotName: 'customer' },
+ { key: 'feeItemSummary', label: '费用类型', minWidth: 150, slotName: 'feeItemSummary' },
{ key: 'currency', label: '币种', width: 80 },
{ key: 'receivableAmount', label: '应收金额', width: 130, slotName: 'money' },
{ key: 'receivedAmount', label: '已收', width: 120, slotName: 'paidMoney' },
{ key: 'outstandingAmount', label: '余额', width: 120, slotName: 'outstandingMoney' },
- { key: 'billDate', label: '生成日期', width: 130, slotName: 'billDate' },
+ { key: 'reconciliationStatementNo', label: '关联对账单', minWidth: 180, slotName: 'reconciliationStatementNo' },
+ { key: 'businessPeriod', label: '结算周期', width: 120, slotName: 'businessPeriod' },
+ { key: 'generatedDate', label: '生成日期', width: 130, slotName: 'generatedDate' },
{ key: 'dueDate', label: '到期日', width: 130, slotName: 'dueDate' },
- { key: 'status', label: '收款状态', width: 120, slotName: 'status' },
+ { key: 'paymentStatus', label: '收款状态', width: 120, slotName: 'paymentStatus' },
+ { key: 'createdByName', label: '制单人', minWidth: 130, slotName: 'createdByName' },
+ { key: 'createdAt', label: '创建时间', width: 170, slotName: 'createdAt' },
+ { key: 'lastPaymentAt', label: '最后收款时间', width: 170, slotName: 'lastPaymentAt' },
+ { key: 'status', label: '结算状态', width: 120, slotName: 'settlementStatus' },
]
: [
{ key: 'actions', label: '操作', width: 190, fixed: 'left', slotName: 'actions' },
{ key: 'settlementNo', label: '结算单号', minWidth: 180 },
{ key: 'supplierName', label: '供应商', minWidth: 180, slotName: 'supplier' },
- { key: 'feeItemName', label: '费用类型', minWidth: 150, slotName: 'feeType' },
+ { key: 'customerId', label: '客户', minWidth: 180, slotName: 'customer' },
+ { key: 'feeItemSummary', label: '费用类型', minWidth: 150, slotName: 'feeItemSummary' },
{ key: 'currency', label: '币种', width: 80 },
{ key: 'payableAmount', label: '应付金额', width: 130, slotName: 'money' },
{ key: 'paidAmount', label: '已付', width: 120, slotName: 'paidMoney' },
{ key: 'outstandingAmount', label: '余额', width: 120, slotName: 'outstandingMoney' },
- { key: 'createdAt', label: '生成日期', width: 130, slotName: 'createdAt' },
- { key: 'status', label: '付款状态', width: 120, slotName: 'status' },
+ { key: 'businessPeriod', label: '结算周期', width: 120, slotName: 'businessPeriod' },
+ { key: 'generatedDate', label: '生成日期', width: 130, slotName: 'generatedDate' },
+ { key: 'dueDate', label: '到期日', width: 130, slotName: 'dueDate' },
+ { key: 'paymentStatus', label: '付款状态', width: 120, slotName: 'paymentStatus' },
+ { key: 'createdByName', label: '制单人', minWidth: 130, slotName: 'createdByName' },
+ { key: 'createdAt', label: '创建时间', width: 170, slotName: 'createdAt' },
+ { key: 'lastPaymentAt', label: '最后付款时间', width: 170, slotName: 'lastPaymentAt' },
+ { key: 'status', label: '结算状态', width: 120, slotName: 'settlementStatus' },
])
function money(value: number) { return `¥${Number(value || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` }
function displayDate(value?: string) { return value || '—' }
function displayDateTime(value?: string) { return value ? value.replace('T', ' ').slice(0, 16) : '—' }
+function settlementPaymentStatusLabel(status?: string) {
+ if (status === 'UNPAID') return props.kind === 'receivable' ? '待收款' : '待付款'
+ if (status === 'PARTIALLY_PAID') return props.kind === 'receivable' ? '部分收款' : '部分付款'
+ if (status === 'PAID') return '已结清'
+ return '—'
+}
+function paymentMethodLabel(method?: string) {
+ return ({ BANK_TRANSFER: '银行转账', CASH: '现金', OTHER: '其他' } as Record)[String(method || '')] || '—'
+}
function operatorDisplayName(value?: string) {
const candidate = String(value || '').trim()
return candidate && !/^(?:operator-)?\d+$/i.test(candidate) ? candidate : '名称数据缺失'
@@ -488,12 +511,18 @@ onMounted(() => {
{{ detail.settlementNo }}
{{ detailPartyName() }}
+ {{ customerName(detail.customerId, detail.customerName) }}
CNY
- {{ financeStatusLabel(detail.status) }}
- {{ detail.reconciliationStatementId || '—' }}
- {{ detail.businessPeriod || '—' }}
+ {{ settlementPaymentStatusLabel(detail.paymentStatus) }}
+ {{ financeStatusLabel(detail.status) }}
+ {{ detail.reconciliationStatementNo || '—' }}
+ {{ detail.businessPeriod || '—' }}
+ {{ detail.feeItemSummary || '—' }}
{{ displayDate(detail.generatedDate) }}
{{ displayDate(detail.dueDate) }}
+ {{ operatorDisplayName(detail.createdByName) }}
+ {{ displayDateTime(detail.createdAt) }}
+ {{ displayDateTime(detail.lastPaymentAt) }}
@@ -501,6 +530,7 @@ onMounted(() => {
{{ financeSourceTypeLabel(scope.row.sourceType) }}
+ {{ scope.row.orderNo || '—' }}
{{ scope.row.inboundCode || '—' }}
{{ scope.row.containerNo || '—' }}
{{ displayDate(scope.row.businessDate) }}
@@ -513,11 +543,11 @@ onMounted(() => {
{{ money(scope.row.amount) }}
-
- {{ scope.row.referenceNo || '—' }}
+ {{ paymentMethodLabel(scope.row.method) }}
+ {{ scope.row.referenceNo || '—' }}
{{ operatorDisplayName(scope.row.createdByName) }}
{{ displayDateTime(scope.row.createdAt) }}
- {{ scope.row.voucherFileIds.length ? `${scope.row.voucherFileIds.length} 个` : '—' }}
+ {{ scope.row.voucherFileIds.length ? `已上传 ${scope.row.voucherFileIds.length} 个` : '—' }}
{{ scope.row.remark || '—' }}
@@ -566,14 +596,19 @@ onMounted(() => {