import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' const routesSource = readFileSync(new URL('../routes.ts', import.meta.url), 'utf8') const viewSource = readFileSync(new URL('./FinanceBillsView.vue', import.meta.url), 'utf8') const apiSource = readFileSync(new URL('../api/finance.ts', import.meta.url), 'utf8') const adapterSource = readFileSync(new URL('../api/adapters.ts', import.meta.url), 'utf8') const permissionSource = readFileSync(new URL('../../tenant-management/contracts.ts', import.meta.url), 'utf8') test('activates the customer finance route with a read-only permission', () => { assert.match(routesSource, /path: 'finance\/bills'/) assert.match(routesSource, /FinanceBillsView\.vue/) assert.match(routesSource, /release: 'active'/) assert.match(routesSource, /permission: 'finance:bill:view'/) assert.doesNotMatch(routesSource, /finance\/payables|应付账单/) }) test('uses client finance read APIs 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.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']) { assert.match(adapterSource, new RegExp(field)) } assert.match(adapterSource, /filter\(\(receipt\).*receipt\.bill_id/s) assert.match(viewSource, /loadDetail\(row\.id\)/) assert.doesNotMatch(viewSource, /loadDetail\(row\.billNo\)/) }) test('keeps actions first and exposes list, balance, detail, 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']) { assert.match(viewSource, new RegExp(expected)) } for (const forbidden of ['确认账单', '发起争议', '新增收款', '核销', '修改金额']) { assert.doesNotMatch(viewSource, new RegExp(forbidden)) } }) test('catalogs customer finance as an assignable view-only role permission', () => { assert.match(permissionSource, /code: 'finance:bill:view'/) assert.match(permissionSource, /不包含任何财务写操作/) assert.match(permissionSource, /merchant:finance:statement:list/) assert.match(permissionSource, /merchant:finance:statement:view/) })