This commit is contained in:
2026-08-11 13:55:23 +08:00
parent e3757cc9f9
commit d6b2ea0938
5 changed files with 113 additions and 18 deletions
+23 -1
View File
@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { http } from '@/shared/api/http'
import { adjustFinanceBill, createFinanceCost, createFinanceReceipt, getFinanceBill, issueFinanceBill, listFinanceBills, listFinanceCosts, listFinanceReceipts, updateFinanceBill, voidFinanceBill } from './finance'
import { adjustFinanceBill, createFinanceCost, createFinanceReceipt, getFinanceBill, issueFinanceBill, listFinanceBills, listFinanceCosts, listFinanceReceipts, updateFinanceBill, uploadFinanceVoucher, voidFinanceBill } from './finance'
describe('finance api', () => {
beforeEach(() => {
@@ -43,6 +43,28 @@ describe('finance api', () => {
expect(result.records[0]).toMatchObject({ id: '71', customerId: '10001', containerId: '81', expenseName: '拖车费', amount: 680.5, voucherFileIds: ['90001'] })
})
it('uploads a real voucher through the Finance Controller proxy', async () => {
const post = vi.spyOn(http, 'post').mockResolvedValue({ code: '200', message: 'ok', data: { fileId: 90001, tenantId: 10001, fileName: 'receipt.png', contentType: 'image/png', sizeBytes: 128 } })
const file = new File(['image'], 'receipt.png', { type: 'image/png' })
const result = await uploadFinanceVoucher('10001', file)
expect(result).toEqual({ fileId: '90001', customerId: '10001', fileName: 'receipt.png', contentType: 'image/png', sizeBytes: 128 })
expect(post.mock.calls[0]?.[0]).toBe('/platform/finance/v1/vouchers')
expect(post.mock.calls[0]?.[1]).toBeInstanceOf(FormData)
expect((post.mock.calls[0]?.[1] as FormData).get('file')).toBe(file)
expect(post.mock.calls[0]?.[2]).toEqual({ params: { customerId: '10001' }, headers: { 'Content-Type': 'multipart/form-data' } })
})
it('submits a non-empty container volume snapshot with the uploaded voucher id', async () => {
const post = vi.spyOn(http, 'post').mockResolvedValue({ code: '200', message: 'ok', data: {} })
const payload = { customerId: '10001', costType: 'CONTAINER_SHARED', containerId: '81', expenseName: '拖车费', amount: '600.000000', containerOrders: [{ orderId: '91', inboundId: '101', containerId: '81', volumeCbm: '2.500000' }] }
await createFinanceCost(payload, ['90001'])
expect(post.mock.calls[0]?.[1]).toMatchObject({ ...payload, currency: 'CNY', voucherFileIds: ['90001'], containerOrders: [{ orderId: '91', volumeCbm: '2.500000' }] })
})
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 })
+15 -1
View File
@@ -1,5 +1,5 @@
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import type { FinanceBillDetail, FinanceBillLine, FinanceBillResource, FinanceCostResource, FinancePaymentMethod, FinanceReceiptResource } from '../types'
import type { FinanceBillDetail, FinanceBillLine, FinanceBillResource, FinanceCostResource, FinancePaymentMethod, FinanceReceiptResource, FinanceVoucherResource } from '../types'
const basePath = '/platform/finance/v1'
function idempotencyKey(operation: string) { return `finance-${operation}-${Date.now()}-${Math.random().toString(36).slice(2)}` }
@@ -94,6 +94,20 @@ export async function createFinanceReceipt(payload: Record<string, unknown>, vou
return unwrapApiResponse(response)
}
export async function uploadFinanceVoucher(customerId: string, file: File): Promise<FinanceVoucherResource> {
const body = new FormData()
body.append('file', file)
const response = await http.post<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/vouchers`, body, {
params: { customerId }, headers: { 'Content-Type': 'multipart/form-data' },
})
const item = unwrapApiResponse(response)
return {
fileId: stringValue(item.fileId ?? item.file_id), customerId: stringValue(item.tenantId ?? item.customerId ?? customerId),
fileName: stringValue(item.fileName ?? item.file_name), contentType: stringValue(item.contentType ?? item.content_type),
sizeBytes: numberValue(item.sizeBytes ?? item.size_bytes),
}
}
export async function listFinanceCosts(customerId?: string) {
const response = await http.get<ApiEnvelope<RawPage>, ApiEnvelope<RawPage>>(`${basePath}/cost-entries`, { params: { customerId: customerId || undefined } })
return normalizePage<FinanceCostResource>(unwrapApiResponse(response), (item) => ({