Files
admin-web/src/modules/unified-orders/api/inboundFeeDetails.ts
T
2026-08-20 18:02:24 +08:00

150 lines
6.7 KiB
TypeScript

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<string | number> | 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<string | number> | null
remark: string | null
}
export interface InboundFeeItemOption {
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<unknown, ApiEnvelope<InboundFeeDetail[]>>(basePath(inboundId), config(accountId))
return unwrapApiResponse(response) || []
}
export async function createInboundFeeDetail(accountId: string, inboundId: string, payload: SaveInboundFeeDetailRequest) {
return unwrapApiResponse(await http.post<unknown, ApiEnvelope<InboundFeeDetail>>(basePath(inboundId), payload, config(accountId)))
}
export async function updateInboundFeeDetail(accountId: string, inboundId: string, detailId: string, payload: SaveInboundFeeDetailRequest) {
return unwrapApiResponse(await http.put<unknown, ApiEnvelope<InboundFeeDetail>>(`${basePath(inboundId)}/${encodeURIComponent(detailId)}`, payload, config(accountId)))
}
export async function deleteInboundFeeDetail(accountId: string, inboundId: string, detailId: string) {
return unwrapApiResponse(await http.delete<unknown, ApiEnvelope<null>>(`${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<unknown, ApiEnvelope<null>>(
`${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<unknown, ApiEnvelope<InboundFeeDetail>>(
`${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<unknown, ApiEnvelope<null>>(
`${basePath(inboundId)}/${encodeURIComponent(detailId)}/void`, { expectedSourceVersion }, config(accountId),
))
}
export async function listInboundFeeItemOptions() {
const response = await http.get<unknown, ApiEnvelope<{ records: Record<string, unknown>[] }>>(
'/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 || ''),
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<FeePaymentVoucher> {
const body = new FormData(); body.append('file', file)
const raw = unwrapApiResponse(await http.post<unknown, ApiEnvelope<Record<string, unknown>>>('/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) }
}