2026-08-12 16:31:46 +08:00
|
|
|
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
|
2026-08-19 09:14:35 +08:00
|
|
|
import { listSuppliers, type SupplierCategory } from '@/modules/suppliers'
|
2026-08-12 16:31:46 +08:00
|
|
|
|
|
|
|
|
export type InboundFeeDirection = 'RECEIVABLE' | 'PAYABLE'
|
|
|
|
|
export type InboundFeeReviewStatus = 'PENDING' | 'APPROVED' | 'REJECTED'
|
|
|
|
|
|
|
|
|
|
export interface InboundFeeDetail {
|
|
|
|
|
id: string | number
|
|
|
|
|
inboundId: string | number
|
|
|
|
|
feeItemId: string | number
|
|
|
|
|
sourceType: string
|
|
|
|
|
sourceFeeVersion: number
|
|
|
|
|
inboundCode: string | null
|
|
|
|
|
direction: InboundFeeDirection
|
2026-08-19 09:14:35 +08:00
|
|
|
supplierId: string | number | null
|
|
|
|
|
supplierName: string | null
|
|
|
|
|
supplierCategory: SupplierCategory | null
|
2026-08-12 16:31:46 +08:00
|
|
|
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
|
2026-08-19 09:14:35 +08:00
|
|
|
supplierId: string | number | null
|
2026-08-12 16:31:46 +08:00
|
|
|
remark: string | null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface InboundFeeItemOption {
|
|
|
|
|
id: string | number
|
|
|
|
|
feeTypeName: string
|
2026-08-19 09:14:35 +08:00
|
|
|
feeCode: string
|
|
|
|
|
supplierCategory: SupplierCategory | null
|
2026-08-12 16:31:46 +08:00
|
|
|
warehousePayable: boolean
|
|
|
|
|
warehouseReceivable: boolean
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-19 09:14:35 +08:00
|
|
|
export interface InboundFeeSupplierOption { id: string; supplierName: string; category: SupplierCategory }
|
|
|
|
|
|
|
|
|
|
const supplierCategories: SupplierCategory[] = ['BOOKING', 'CUSTOMS', 'TRUCKING', 'CERTIFICATE']
|
|
|
|
|
|
|
|
|
|
function supplierCategory(value: unknown): SupplierCategory | null {
|
|
|
|
|
return supplierCategories.includes(value as SupplierCategory) ? value as SupplierCategory : null
|
|
|
|
|
}
|
2026-08-12 16:31:46 +08:00
|
|
|
|
2026-08-18 16:09:43 +08:00
|
|
|
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`
|
|
|
|
|
}
|
2026-08-12 16:31:46 +08:00
|
|
|
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() {
|
2026-08-19 09:14:35 +08:00
|
|
|
const response = await http.get<unknown, ApiEnvelope<{ records: Record<string, unknown>[] }>>(
|
2026-08-12 16:31:46 +08:00
|
|
|
'/platform/v1/fee-items', { params: { status: 'ACTIVE', page: 1, size: 200 } },
|
|
|
|
|
)
|
2026-08-19 09:14:35 +08:00
|
|
|
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),
|
|
|
|
|
}))
|
2026-08-12 16:31:46 +08:00
|
|
|
}
|
|
|
|
|
|
2026-08-19 09:14:35 +08:00
|
|
|
export async function listInboundFeeSupplierOptions(category: SupplierCategory) {
|
|
|
|
|
const page = await listSuppliers({ status: 'ACTIVE', category, page: 1, size: 200 })
|
|
|
|
|
return page.records.filter((supplier) => supplier.status === 'ACTIVE' && supplier.category === category).map((supplier): InboundFeeSupplierOption => ({
|
|
|
|
|
id: supplier.id, supplierName: supplier.name, category: supplier.category,
|
|
|
|
|
}))
|
2026-08-12 16:31:46 +08:00
|
|
|
}
|