import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' export interface FeePage { records: T[]; page: number; size: number; total: number; pages: number } export type OverseasFeeDirection = 'RECEIVABLE' | 'PAYABLE' export interface OverseasFee { storageRent?: { outboundNo: string; outboundAt: string; calculatedAmount: number; chargedAmount: number | null; lines: Array<{ lotId: string; inboundAt: string; quantity: number; days: number; calculatedAmount: number; sourceBusinessType?: string | null; sourceBusinessNo?: string | null; batchNo?: string | null }> } | null id: string; sourceFeeId: string; sourceVersion: number; customerId: string; customerName: string sourceType: string; businessType: string; businessId: string | null; businessNo: string displayBusinessType?: string direction: OverseasFeeDirection supplierId?: string | null; supplierName?: string | null; supplierCategory?: string | null settlementMode?: 'BILL' | 'WALLET' | 'ON_ACCOUNT' | 'DIRECT_PAID' | null paidAt?: string | null; paymentMethod?: string | null; paymentReferenceNo?: string | null; paymentVoucherFileIds?: string[] | null orderId: string | null; warehouseId: string | null; feeItemCode: string; feeItemName: string feeRemark?: string | null businessDate: string; currency: string; amount: number; originalCurrency: string; originalAmount: number exchangeRate?: number | null; exchangeRateMonth?: string | null chargeableQuantity: number | null; quantityUnit: string | null; unitPrice: number | null periodStart: string | null; periodEnd: string | null; reviewStatus: string; sourceStatus: string publicationStatus: string; collectionMode: 'BILL' | 'WALLET'; walletStatus: string | null deliveryStatus?: 'PENDING' | 'PROCESSING' | 'PUBLISHED' | 'DEAD' | null deliveryKind?: 'FEE' | 'FUNDING' | null; deliveryEventId?: string | null; deliveryErrorMessage?: string | null walletErrorMessage: string | null; reviewReason: string | null; reviewerId?: string | null; reviewerName?: string | null; reviewedAt: string | null createdAt: string; updatedAt: string; allowedActions: string[] } export interface BusinessFeeOption { businessId: string; businessType: string; businessNo: string; customerId: string; customerName: string warehouseId: string | null; orderId: string | null; orderType?: string } export interface SaveOverseasFee { customerId: string; customerName: string; sourceType: string; businessType: string businessId: string; businessNo: string; orderId: string | null; warehouseId: string | null feeItemCode: string; feeItemName: string; direction: OverseasFeeDirection; businessDate: string feeRemark?: string | null supplierId?: string | null; supplierName?: string | null; supplierCategory?: string | null settlementMode?: 'BILL' | 'WALLET' | 'ON_ACCOUNT' | 'DIRECT_PAID' | null paidAt?: string | null; paymentMethod?: string | null; paymentReferenceNo?: string | null; paymentVoucherFileIds?: string[] | null currency: 'CNY' | 'USD'; amount: number chargeableQuantity?: number; quantityUnit?: string; unitPrice?: number } const base = '/platform/v1/last-leg/finance-fee-sources' export interface OverseasFeeRetryRecord { requestId: string; requestedAt: string; operatorName: string; action: string result: 'SUCCESS' | 'FAILED' | 'SUBMITTED'; errorMessage: string | null } export async function listOverseasFeeRetries(id: string): Promise { return unwrapApiResponse(await http.get, ApiEnvelope>(`${base}/${encodeURIComponent(id)}/retries`)) } export async function listOverseasFeeItems(direction: 'RECEIVABLE' | 'PAYABLE' = 'RECEIVABLE') { const response = await http.get>, ApiEnvelope>>(`${base}/fee-items`, { params: { direction } }) return unwrapApiResponse(response).map(item => ({ feeCode: item.code, feeTypeName: item.name, defaultPrice: item.defaultPrice })) } export async function listOverseasFeeCustomers() { const response = await http.get>, ApiEnvelope>>(`${base}/customer-options`) return unwrapApiResponse(response).map(row => ({ ...row, customerId: String(row.customerId) })) } function normalizeFee(value: OverseasFee): OverseasFee { return { ...value, id: String(value.id), sourceFeeId: String(value.sourceFeeId), customerId: String(value.customerId), businessId: value.businessId == null ? null : String(value.businessId), orderId: value.orderId == null ? null : String(value.orderId), warehouseId: value.warehouseId == null ? null : String(value.warehouseId), allowedActions: value.allowedActions || [] } } export async function listOverseasFees(query: Record): Promise> { const response = await http.get>, ApiEnvelope>>(`${base}/page`, { params: query }) const data = unwrapApiResponse(response) return { ...data, records: data.records.map(normalizeFee), total: Number(data.total), pages: Number(data.pages) } } export async function listBusinessOverseasFees(business: BusinessFeeOption | BusinessFeeOption[], pagination: { page: number; size: number }, direction: OverseasFeeDirection = 'RECEIVABLE'): Promise> { const { page, size } = pagination const scopes = Array.isArray(business) ? business : [business] if (!scopes.length || scopes.some(scope => !scope.businessId || !scope.customerId || !scope.businessType)) throw new Error('业务单据信息不完整') // 非分页接口返回两种方向;先按客户、业务身份及收付方向匹配,再合并分页。 const groups = await Promise.all(scopes.map(async scope => { const response = await http.get, ApiEnvelope>(base, { params: { customerId: scope.customerId, businessType: scope.businessType, businessNo: scope.businessNo }, }) return unwrapApiResponse(response).map(normalizeFee).filter(row => row.customerId === scope.customerId && row.businessType === scope.businessType && row.businessId === scope.businessId && row.direction === direction) })) const records = [...new Map(groups.flat().map(row => [row.sourceFeeId, row])).values()] .sort((left, right) => right.createdAt.localeCompare(left.createdAt) || right.sourceFeeId.localeCompare(left.sourceFeeId)) return { records: records.slice((page - 1) * size, page * size), total: records.length, pages: Math.ceil(records.length / size), page, size } } export async function getOverseasFee(id: string) { return normalizeFee(unwrapApiResponse(await http.get, ApiEnvelope>(`${base}/${encodeURIComponent(id)}`))) } export async function listBusinessFeeOptions(params: { businessType: string; keyword?: string; customerId?: string }) { const response = await http.get, ApiEnvelope>(`${base}/business-options`, { params }) return unwrapApiResponse(response).map(row => ({ ...row, businessId: String(row.businessId), customerId: String(row.customerId), orderId: row.orderId == null ? null : String(row.orderId), warehouseId: row.warehouseId == null ? null : String(row.warehouseId) })) } export async function createOverseasFee(payload: SaveOverseasFee, key: string) { return normalizeFee(unwrapApiResponse(await http.post, ApiEnvelope>(base, payload, { headers: { 'Idempotency-Key': key } }))) } export async function actOnOverseasFee(id: string, action: string, body: Record) { const endpoint = action === 'APPROVE' || action === 'REJECT' ? 'review' : ['RETRY_PUBLISH', 'RETRY_FUNDING_DELIVERY'].includes(action) ? 'retry-delivery' : action.startsWith('RETRY_') ? 'retry-funding' : action.toLowerCase() return normalizeFee(unwrapApiResponse(await http.post, ApiEnvelope>(`${base}/${encodeURIComponent(id)}/${endpoint}`, body))) }