2026-08-11 10:45:59 +08:00
|
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
|
import { http } from '@/shared/api/http'
|
2026-08-11 11:41:47 +08:00
|
|
|
import { adjustFinanceBill, createFinanceCost, createFinanceReceipt, getFinanceBill, issueFinanceBill, listFinanceBills, listFinanceCosts, listFinanceReceipts, updateFinanceBill, voidFinanceBill } from './finance'
|
2026-08-11 10:45:59 +08:00
|
|
|
|
|
|
|
|
describe('finance api', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.restoreAllMocks()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('uses the OpenAPI bill path and pageSize parameter', async () => {
|
|
|
|
|
const get = vi.spyOn(http, 'get').mockResolvedValue({
|
|
|
|
|
code: '200', message: 'ok', data: [{ id: 1, bill_no: 'B-1', customer_name_snapshot: '客户A', bill_date: '2026-08-11', due_date: '2026-09-10', total_amount: 100, received_amount: 0, outstanding_amount: 100, status: 'DRAFT', version: 2 }],
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const result = await listFinanceBills({ customerId: '10001', status: 'DRAFT', page: 1, pageSize: 20 })
|
|
|
|
|
|
|
|
|
|
expect(get).toHaveBeenCalledWith('/platform/finance/v1/bills', { params: { customerId: '10001', status: 'DRAFT', page: 1, pageSize: 20 } })
|
|
|
|
|
expect(result.records[0]).toMatchObject({ id: '1', billNo: 'B-1', currency: 'CNY', status: 'DRAFT', version: 2 })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('uses the receipt and cost entry paths and fixes currency to CNY', async () => {
|
|
|
|
|
const get = vi.spyOn(http, 'get').mockResolvedValue({ code: '200', message: 'ok', data: [] })
|
|
|
|
|
const post = vi.spyOn(http, 'post').mockResolvedValue({ code: '200', message: 'ok', data: {} })
|
|
|
|
|
|
|
|
|
|
await listFinanceReceipts('10001')
|
|
|
|
|
await listFinanceCosts('10001')
|
|
|
|
|
await createFinanceReceipt({ customerId: '10001', amount: 100 }, ['90001'])
|
|
|
|
|
await createFinanceCost({ customerId: '10001', amount: 100 }, ['90001'])
|
|
|
|
|
|
|
|
|
|
expect(get).toHaveBeenNthCalledWith(1, '/platform/finance/v1/receipts', { params: { customerId: '10001' } })
|
|
|
|
|
expect(get).toHaveBeenNthCalledWith(2, '/platform/finance/v1/cost-entries', { params: { customerId: '10001' } })
|
|
|
|
|
expect(post.mock.calls[0]?.[1]).toMatchObject({ currency: 'CNY', voucherFileIds: ['90001'] })
|
|
|
|
|
expect(post.mock.calls[1]?.[1]).toMatchObject({ currency: 'CNY', voucherFileIds: ['90001'] })
|
|
|
|
|
expect(post.mock.calls[0]?.[2]).toMatchObject({ headers: { 'Idempotency-Key': expect.any(String) } })
|
|
|
|
|
expect(post.mock.calls[1]?.[2]).toMatchObject({ headers: { 'Idempotency-Key': expect.any(String) } })
|
|
|
|
|
})
|
|
|
|
|
|
2026-08-11 11:41:47 +08:00
|
|
|
it('maps the real cost fee_item_name and cost_amount fields', async () => {
|
|
|
|
|
vi.spyOn(http, 'get').mockResolvedValue({ code: '200', message: 'ok', data: [{ id: 71, tenant_id: 10001, cost_type: 'CONTAINER_SHARED', container_id: 81, fee_item_name: '拖车费', cost_amount: '680.500000', voucher_file_ids: '["90001"]', created_at: '2026-08-11T10:00:00' }] })
|
|
|
|
|
|
|
|
|
|
const result = await listFinanceCosts('10001')
|
|
|
|
|
|
|
|
|
|
expect(result.records[0]).toMatchObject({ id: '71', customerId: '10001', containerId: '81', expenseName: '拖车费', amount: 680.5, voucherFileIds: ['90001'] })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('normalizes bill details and uses update, adjust and void contracts', async () => {
|
|
|
|
|
const raw = { id: 81, tenant_id: 10001, bill_no: 'B-81', customer_name_snapshot: '客户A', bill_date: '2026-08-11', due_date: '2026-09-10', total_amount: 100, received_amount: 0, outstanding_amount: 100, status: 'DRAFT', version: 3, lines: [{ source_id: 91, source_version: 2, fee_item_name: '运费', amount: 100 }] }
|
|
|
|
|
vi.spyOn(http, 'get').mockResolvedValue({ code: '200', message: 'ok', data: raw })
|
|
|
|
|
const put = vi.spyOn(http, 'put').mockResolvedValue({ code: '200', message: 'ok', data: raw })
|
|
|
|
|
const post = vi.spyOn(http, 'post').mockResolvedValue({ code: '200', message: 'ok', data: raw })
|
|
|
|
|
|
|
|
|
|
const detail = await getFinanceBill('81')
|
|
|
|
|
await updateFinanceBill('81', { customerId: '10001', expectedVersion: 3, lines: [] })
|
|
|
|
|
await adjustFinanceBill('81', { expectedVersion: 3, reason: '金额修正', lines: [] })
|
|
|
|
|
await voidFinanceBill('81', 3, '重复账单')
|
|
|
|
|
|
|
|
|
|
expect(detail).toMatchObject({ id: '81', customerId: '10001', version: 3, lines: [{ sourceFeeId: '91', sourceFeeVersion: 2, feeItemName: '运费', amount: 100 }] })
|
|
|
|
|
expect(put).toHaveBeenCalledWith('/platform/finance/v1/bills/81', expect.objectContaining({ expectedVersion: 3 }), { headers: { 'Idempotency-Key': expect.any(String) } })
|
|
|
|
|
expect(post).toHaveBeenNthCalledWith(1, '/platform/finance/v1/bills/81/adjust', expect.objectContaining({ expectedVersion: 3, reason: '金额修正' }), { headers: { 'Idempotency-Key': expect.any(String) } })
|
|
|
|
|
expect(post).toHaveBeenNthCalledWith(2, '/platform/finance/v1/bills/81/void', { expectedVersion: 3, reason: '重复账单' }, { headers: { 'Idempotency-Key': expect.any(String) } })
|
|
|
|
|
})
|
|
|
|
|
|
2026-08-11 10:45:59 +08:00
|
|
|
it('issues a bill with optimistic version and an idempotency key', async () => {
|
|
|
|
|
const post = vi.spyOn(http, 'post').mockResolvedValue({ code: '200', message: 'ok', data: {} })
|
|
|
|
|
|
|
|
|
|
await issueFinanceBill('81001', 3)
|
|
|
|
|
|
|
|
|
|
expect(post).toHaveBeenCalledWith('/platform/finance/v1/bills/81001/issue', { expectedVersion: 3 }, { headers: { 'Idempotency-Key': expect.any(String) } })
|
|
|
|
|
})
|
|
|
|
|
})
|