import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' import { listSuppliers, type SupplierCategory } from '@/modules/suppliers' export type InboundFeeDirection = 'RECEIVABLE' | 'PAYABLE' export type InboundFeeReviewStatus = 'PENDING' | 'APPROVED' | 'REJECTED' export type FeeSettlementMode = 'ON_ACCOUNT' | 'DIRECT_PAID' export type FeePaymentMethod = 'ONLINE' | 'BANK_TRANSFER' | 'CASH' | 'OTHER' export interface InboundFeeDetail { id: string | number inboundId: string | number feeItemId: string | number sourceType: string sourceFeeVersion: number inboundCode: string | null direction: InboundFeeDirection supplierId: string | number | null supplierName: string | null supplierCategory: SupplierCategory | null settlementMode?: FeeSettlementMode | null paidAt?: string | null paymentMethod?: FeePaymentMethod | null paymentReferenceNo?: string | null paymentVoucherFileIds?: Array | null businessDate: string customerName: string | null feeTypeNameSnapshot: string unitPrice: number currency: string amount: number remark: string | null reviewStatus: InboundFeeReviewStatus reviewReason: string | null reviewerName: string | null reviewedAt: string | null publicationStatus: string } export interface SaveInboundFeeDetailRequest { feeItemId: string | number direction: InboundFeeDirection businessDate: string unitPrice: number currency: string amount: number supplierId: string | number | null settlementMode: FeeSettlementMode | null paidAt: string | null paymentMethod: FeePaymentMethod | null paymentReferenceNo: string | null paymentVoucherFileIds: Array | null remark: string | null } export interface InboundFeeItemOption { defaultPrice: number | null id: string | number feeTypeName: string feeCode: string supplierCategory: SupplierCategory | null warehousePayable: boolean warehouseReceivable: boolean } export interface InboundFeeSupplierOption { id: string; supplierName: string; category: SupplierCategory } export interface FeePaymentVoucher { fileId: string; fileName: string } const supplierCategories: SupplierCategory[] = ['BOOKING', 'CUSTOMS', 'TRUCKING', 'CERTIFICATE'] function supplierCategory(value: unknown): SupplierCategory | null { if (value == null || value === '') return null if (supplierCategories.includes(value as SupplierCategory)) return value as SupplierCategory throw new TypeError(`Unsupported supplierCategory: ${String(value)}`) } function basePath(inboundId: string) { const identifier = String(inboundId ?? '').trim() if (!identifier || identifier === 'null' || identifier === 'undefined') throw new TypeError('inboundId is required') return `/platform/v1/inbounds/${encodeURIComponent(identifier)}/fee-details` } const config = (accountId: string) => ({ params: { accountId } }) export async function listInboundFeeDetails(accountId: string, inboundId: string) { const response = await http.get>(basePath(inboundId), config(accountId)) return unwrapApiResponse(response) || [] } export async function createInboundFeeDetail(accountId: string, inboundId: string, payload: SaveInboundFeeDetailRequest) { return unwrapApiResponse(await http.post>(basePath(inboundId), payload, config(accountId))) } export async function updateInboundFeeDetail(accountId: string, inboundId: string, detailId: string, payload: SaveInboundFeeDetailRequest) { return unwrapApiResponse(await http.put>(`${basePath(inboundId)}/${encodeURIComponent(detailId)}`, payload, config(accountId))) } export async function deleteInboundFeeDetail(accountId: string, inboundId: string, detailId: string) { return unwrapApiResponse(await http.delete>(`${basePath(inboundId)}/${encodeURIComponent(detailId)}`, config(accountId))) } export async function reviewInboundFeeDetail(accountId: string, inboundId: string, detailId: string, action: 'approve' | 'reject', reason?: string) { return unwrapApiResponse(await http.post>( `${basePath(inboundId)}/${encodeURIComponent(detailId)}/${action}`, action === 'reject' ? { reason } : undefined, config(accountId), )) } export async function adjustInboundFeeDetail(accountId: string, inboundId: string, detailId: string, expectedSourceVersion: number, payload: SaveInboundFeeDetailRequest) { return unwrapApiResponse(await http.post>( `${basePath(inboundId)}/${encodeURIComponent(detailId)}/adjust`, payload, { params: { accountId, expectedSourceVersion } }, )) } export async function voidInboundFeeDetail(accountId: string, inboundId: string, detailId: string, expectedSourceVersion: number) { return unwrapApiResponse(await http.post>( `${basePath(inboundId)}/${encodeURIComponent(detailId)}/void`, { expectedSourceVersion }, config(accountId), )) } export async function listInboundFeeItemOptions() { const response = await http.get[] }>>( '/platform/v1/fee-items', { params: { status: 'ACTIVE', page: 1, size: 200 } }, ) return (unwrapApiResponse(response).records || []).map((raw): InboundFeeItemOption => ({ id: raw.id as string | number, feeTypeName: String(raw.feeTypeName || ''), defaultPrice: raw.defaultPrice == null ? null : Number(raw.defaultPrice), feeCode: String(raw.feeCode || ''), supplierCategory: supplierCategory(raw.supplierCategory), warehousePayable: Boolean(raw.warehousePayable), warehouseReceivable: Boolean(raw.warehouseReceivable), })) } export async function listInboundFeeSupplierOptions(category: SupplierCategory | null) { const page = await listSuppliers({ status: 'ACTIVE', category: category ?? undefined, page: 1, size: 200 }) return page.records.filter((supplier) => supplier.status === 'ACTIVE' && (!category || supplier.category === category)).map((supplier): InboundFeeSupplierOption => ({ id: supplier.id, supplierName: supplier.name, category: supplier.category, })) } export async function uploadInboundFeePaymentVoucher(customerId: string, file: File): Promise { const body = new FormData(); body.append('file', file) const raw = unwrapApiResponse(await http.post>>('/platform/finance/v1/vouchers', body, { params: { customerId }, headers: { 'Content-Type': 'multipart/form-data' }, })) const fileId = raw.fileId == null ? '' : String(raw.fileId) const echoedCustomerId = raw.tenantId == null ? String(raw.customerId ?? customerId) : String(raw.tenantId) if (!fileId || echoedCustomerId !== customerId) throw new Error('付款凭证与当前客户不匹配') return { fileId, fileName: String(raw.fileName || raw.file_name || file.name) } }