150 lines
6.7 KiB
TypeScript
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) }
|
|
}
|