import { beforeEach, expect, it, vi } from 'vitest' import { http, ApiResponseError } from '@/shared/api/http' import { getCompanyCost, commandCompanyCost, payCompanyCost, listCompanyCostHistory, listCompanyCostPayments, uploadCompanyCostVoucher, downloadCompanyCostVoucher } from './companyVehicleCosts' vi.mock('@/shared/api/http', async original => ({ ...(await original()), http: { get: vi.fn(), post: vi.fn() } })) const source = { sourceType: 'REPAIR', sourceNo: 'VR-ONE', sourceVersion: '1', vehicleId: '90071992547409931', vehiclePlate: 'VEH-A', tripNo: null, supplierId: '90071992547409932', feeName: '维修', businessDate: '2026-01-01', originalCurrency: 'USD', originalAmount: '10.00', exchangeRate: '7.000000', exchangeRateMonth: '2026-01', amount: '70.00', remark: null } const wire = { costNo: 'VC-ONE', subjectCode: 'PLATFORM_DEFAULT', supplierName: '维修供应商', currency: 'CNY', status: 'APPROVED', recordVersion: '90071992547409933', paidAmount: '0.00', outstandingAmount: '70.00', source } const base = '/platform/finance/v1/vehicle-costs/VC-ONE' const ok = (data: unknown) => ({ code: '200', data }) beforeEach(() => vi.resetAllMocks()) it('reads real company detail while preserving long versions and original-currency facts', async () => { vi.mocked(http.get).mockResolvedValue(ok(wire)) const result = await getCompanyCost('VC-ONE') expect(result).toMatchObject({ recordVersion: wire.recordVersion, paidAmount: 0, outstandingAmount: 70, source: { supplierId: source.supplierId, originalAmount: 10, amount: 70, exchangeRate: 7 } }) expect(http.get).toHaveBeenCalledExactlyOnceWith(base) }) it('sends exact action versions and stable Idempotency-Key headers for booking, void and payment', async () => { vi.mocked(http.post).mockResolvedValue(ok(wire)) const command = { expectedVersion: wire.recordVersion, reason: '核对' } await commandCompanyCost('VC-ONE', 'approve', 'approve-key', command) await commandCompanyCost('VC-ONE', 'void', 'void-key', command) const payment = { expectedVersion: wire.recordVersion, amount: 20, paidAt: '2026-01-02T00:00:00Z', method: 'BANK_TRANSFER' as const, referenceNo: null, voucherFileIds: ['90071992547409934'], remark: null } await payCompanyCost('VC-ONE', 'payment-key', payment) expect(http.post).toHaveBeenNthCalledWith(1, `${base}/approve`, command, { headers: { 'Idempotency-Key': 'approve-key' } }) expect(http.post).toHaveBeenNthCalledWith(2, `${base}/void`, command, { headers: { 'Idempotency-Key': 'void-key' } }) expect(http.post).toHaveBeenNthCalledWith(3, `${base}/payments`, payment, { headers: { 'Idempotency-Key': 'payment-key' } }) }) it('loads separately paged history and payment records without treating unknown payable as zero', async () => { vi.mocked(http.get).mockResolvedValueOnce(ok({ records: [{ recordVersion: '0', action: 'RECEIVE', reason: null, operatorName: '系统', occurredAt: '2026-01-01T00:00:00Z', snapshot: { ...wire, status: 'PENDING', outstandingAmount: null } }], page: 2, size: 10, total: 11, pages: 2 })) const history = await listCompanyCostHistory('VC-ONE', 2) expect(history.records[0]?.snapshot.outstandingAmount).toBeNull() vi.mocked(http.get).mockResolvedValueOnce(ok({ records: [{ paymentNo: 'VP-ONE', currency: 'CNY', amount: '20.00', voucherFileIds: ['90071992547409934'] }], page: 1, size: 10, total: 1, pages: 1 })) const payments = await listCompanyCostPayments('VC-ONE') expect(payments.records[0]).toMatchObject({ amount: 20, voucherFileIds: ['90071992547409934'] }) expect(http.get).toHaveBeenNthCalledWith(1, `${base}/history`, { params: { page: 2, size: 10 } }) expect(http.get).toHaveBeenNthCalledWith(2, `${base}/payments`, { params: { page: 1, size: 10 } }) }) it('uploads only to the selected cost and rejects customer or operational photographs as payment evidence', async () => { const file = new File(['png'], 'payment.png', { type: 'image/png' }) vi.mocked(http.post).mockResolvedValueOnce(ok({ fileId: '90071992547409934', fileName: 'payment.png', contentType: 'image/png', sizeBytes: '3', ownerScope: 'PLATFORM', tenantId: '0', businessType: 'FINANCE_VEHICLE_VOUCHER' })) expect(await uploadCompanyCostVoucher('VC-ONE', file)).toMatchObject({ fileId: '90071992547409934', sizeBytes: 3 }) expect(vi.mocked(http.post).mock.calls[0]?.[0]).toBe(`${base}/vouchers`) expect((vi.mocked(http.post).mock.calls[0]?.[1] as FormData).get('file')).toBe(file) vi.mocked(http.post).mockResolvedValueOnce(ok({ fileId: '1', ownerScope: 'CUSTOMER', tenantId: '12', businessType: 'FINANCE_VOUCHER' })) await expect(uploadCompanyCostVoucher('VC-ONE', file)).rejects.toThrow('凭证返回内容无效') }) it('keeps JSON download errors as diagnostic errors rather than successful images', async () => { vi.mocked(http.get).mockResolvedValue(new Blob([JSON.stringify({ code: 'FIN_404', message: '不存在', requestId: 'request-one', traceId: 'trace-one' })], { type: 'application/json' })) await expect(downloadCompanyCostVoucher('VC-ONE', '11')).rejects.toBeInstanceOf(ApiResponseError) }) it('rejects mismatched costs and imprecise money instead of displaying successful facts', async () => { vi.mocked(http.get).mockResolvedValueOnce(ok({ ...wire, costNo: 'VC-OTHER' })).mockResolvedValueOnce(ok({ ...wire, paidAmount: '90071992547409.92' })) await expect(getCompanyCost('VC-ONE')).rejects.toThrow('归属') await expect(getCompanyCost('VC-ONE')).rejects.toThrow('精确展示') })