费用优化
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
const client = vi.hoisted(() => ({ get: vi.fn(), post: vi.fn() }))
|
||||
vi.mock('@/shared/api/http', async original => ({ ...await original<typeof import('@/shared/api/http')>(), http: client }))
|
||||
import { actOnOverseasFee, createOverseasFee, getOverseasFee, listBusinessFeeOptions, listBusinessOverseasFees, listOverseasFeeItems, listOverseasFees, listOverseasFeeRetries, type SaveOverseasFee } from './overseasFees'
|
||||
const base = '/platform/v1/last-leg/finance-fee-sources'
|
||||
const envelope = <T>(data: T) => ({ code: '200', message: 'success', data })
|
||||
const row = { id: '9007199254740993', sourceFeeId: '9007199254740993', customerId: '1', businessId: '9007199254740995', direction: 'RECEIVABLE', allowedActions: ['APPROVE'] }
|
||||
describe('海外费用真实接口', () => {
|
||||
beforeEach(() => vi.clearAllMocks())
|
||||
it('分页不退回旧非分页接口,长整型身份保持字符串', async () => {
|
||||
client.get.mockResolvedValue(envelope({ records: [row], total: '1', pages: '1', page: 1, size: 20 }))
|
||||
const result = await listOverseasFees({ page: 1, size: 20, feeItemCode: 'F1' })
|
||||
expect(client.get).toHaveBeenCalledWith(`${base}/page`, { params: { page: 1, size: 20, feeItemCode: 'F1' } })
|
||||
expect(result.records[0]?.businessId).toBe('9007199254740995')
|
||||
expect(result.total).toBe(1)
|
||||
client.get.mockResolvedValue(envelope(row))
|
||||
expect((await getOverseasFee(row.sourceFeeId)).sourceFeeId).toBe(row.sourceFeeId)
|
||||
})
|
||||
it('创建传正式业务订单及幂等键,审核和锁款重试各用正式命令', async () => {
|
||||
client.post.mockResolvedValue(envelope(row))
|
||||
const payload = { businessNo: 'WT1', businessType: 'WAREHOUSE_TRANSFER', amount: 10 } as SaveOverseasFee
|
||||
await createOverseasFee(payload, 'create-1')
|
||||
expect(client.post).toHaveBeenCalledWith(base, payload, { headers: { 'Idempotency-Key': 'create-1' } })
|
||||
await actOnOverseasFee(row.sourceFeeId, 'REJECT', { decision: 'REJECT', reason: '重复费用', idempotencyKey: 'review-1' })
|
||||
expect(client.post).toHaveBeenLastCalledWith(`${base}/${row.sourceFeeId}/review`, { decision: 'REJECT', reason: '重复费用', idempotencyKey: 'review-1' })
|
||||
await actOnOverseasFee(row.sourceFeeId, 'RETRY_FREEZE', { idempotencyKey: 'retry-1' })
|
||||
expect(client.post).toHaveBeenLastCalledWith(`${base}/${row.sourceFeeId}/retry-funding`, { idempotencyKey: 'retry-1' })
|
||||
await actOnOverseasFee(row.sourceFeeId, 'RETRY_PUBLISH', { expectedVersion: 1, eventId: 'original-event', idempotencyKey: 'retry-2' })
|
||||
expect(client.post).toHaveBeenLastCalledWith(`${base}/${row.sourceFeeId}/retry-delivery`, { expectedVersion: 1, eventId: 'original-event', idempotencyKey: 'retry-2' })
|
||||
})
|
||||
it('目录和业务订单选项读取真实数据,不套用人民币默认价', async () => {
|
||||
client.get.mockResolvedValueOnce(envelope([{ code: 'F1', name: '操作费', defaultPrice: 10 }]))
|
||||
.mockResolvedValueOnce(envelope([{ businessId: '9007199254740995', customerId: '1', orderId: null, warehouseId: '2' }]))
|
||||
expect(await listOverseasFeeItems()).toEqual([{ feeCode: 'F1', feeTypeName: '操作费', defaultPrice: 10 }])
|
||||
expect(client.get).toHaveBeenLastCalledWith(`${base}/fee-items`, { params: { direction: 'RECEIVABLE' } })
|
||||
expect((await listBusinessFeeOptions({ businessType: 'WAREHOUSE_TRANSFER', keyword: 'WT' }))[0]?.businessId).toBe('9007199254740995')
|
||||
expect(client.get).toHaveBeenLastCalledWith(`${base}/business-options`, { params: { businessType: 'WAREHOUSE_TRANSFER', keyword: 'WT' } })
|
||||
})
|
||||
it('应付项目明确传递方向', async () => {
|
||||
client.get.mockResolvedValue(envelope([]))
|
||||
await listOverseasFeeItems('PAYABLE')
|
||||
expect(client.get).toHaveBeenCalledWith(`${base}/fee-items`, { params: { direction: 'PAYABLE' } })
|
||||
})
|
||||
it('按稳定费用身份查询完整重试记录', async () => {
|
||||
client.get.mockResolvedValue(envelope([]))
|
||||
expect(await listOverseasFeeRetries(row.sourceFeeId)).toEqual([])
|
||||
expect(client.get).toHaveBeenCalledWith(`${base}/${row.sourceFeeId}/retries`)
|
||||
})
|
||||
it('单据费用先严格匹配客户、类型及ID,合并去重后分页', async () => {
|
||||
const business = { businessId: row.businessId, businessType: 'LASTLEG_ORDER', businessNo: 'TB1', customerId: '1', customerName: '测试客户', orderId: null, warehouseId: null }
|
||||
const related = { ...business, businessId: 'return-1', businessType: 'RETURN_ORDER' }
|
||||
const first = { ...row, businessType: business.businessType, createdAt: '2026-10-22T10:00:00' }
|
||||
const second = { ...first, sourceFeeId: '9007199254740994', businessId: related.businessId, businessType: related.businessType }
|
||||
client.get.mockResolvedValue(envelope([first, second, first,
|
||||
{ ...first, sourceFeeId: 'wrong-id', businessId: 'another' },
|
||||
{ ...first, sourceFeeId: 'wrong-type', businessType: 'RETURN_ORDER' },
|
||||
{ ...first, sourceFeeId: 'wrong-customer', customerId: '2' },
|
||||
]))
|
||||
const result = await listBusinessOverseasFees([business, related], { page: 2, size: 1 })
|
||||
expect(client.get).toHaveBeenCalledWith(base, { params: { customerId: '1', businessType: 'LASTLEG_ORDER', businessNo: 'TB1' } })
|
||||
expect(result).toMatchObject({ total: 2, pages: 2, page: 2, size: 1 })
|
||||
expect(result.records.map(fee => fee.sourceFeeId)).toEqual([first.sourceFeeId])
|
||||
})
|
||||
it.each(['RECEIVABLE', 'PAYABLE'] as const)('混合返回时先按 %s 分开,再统计分页,不遗漏第二页数据', async direction => {
|
||||
const business = { businessId: row.businessId, businessType: 'LASTLEG_ORDER', businessNo: 'TB1', customerId: '1', customerName: '客户', orderId: null, warehouseId: null }
|
||||
const first = { ...row, businessType: 'LASTLEG_ORDER', direction, createdAt: '2026-10-23T10:00:00' }
|
||||
const second = { ...first, sourceFeeId: 'second', createdAt: '2026-10-23T11:00:00' }
|
||||
const opposite = { ...first, sourceFeeId: 'opposite', direction: direction === 'RECEIVABLE' ? 'PAYABLE' : 'RECEIVABLE' }
|
||||
client.get.mockResolvedValue(envelope([first, opposite, second, { ...first, sourceFeeId: 'unknown', direction: null }]))
|
||||
const result = await listBusinessOverseasFees(business, { page: 2, size: 1 }, direction)
|
||||
expect(result).toMatchObject({ total: 2, pages: 2 })
|
||||
expect(result.records.map(fee => fee.sourceFeeId)).toEqual([first.sourceFeeId])
|
||||
expect(result.records.every(fee => fee.direction === direction)).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,99 @@
|
||||
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
|
||||
|
||||
export interface FeePage<T> { 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<OverseasFeeRetryRecord[]> {
|
||||
return unwrapApiResponse(await http.get<ApiEnvelope<OverseasFeeRetryRecord[]>, ApiEnvelope<OverseasFeeRetryRecord[]>>(`${base}/${encodeURIComponent(id)}/retries`))
|
||||
}
|
||||
export async function listOverseasFeeItems(direction: 'RECEIVABLE' | 'PAYABLE' = 'RECEIVABLE') {
|
||||
const response = await http.get<ApiEnvelope<Array<{ code: string; name: string; defaultPrice: number | null }>>, ApiEnvelope<Array<{ code: string; name: string; defaultPrice: number | null }>>>(`${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<Array<{ customerId: string; customerName: string }>>, ApiEnvelope<Array<{ customerId: string; customerName: string }>>>(`${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<string, string | number | undefined>): Promise<FeePage<OverseasFee>> {
|
||||
const response = await http.get<ApiEnvelope<FeePage<OverseasFee>>, ApiEnvelope<FeePage<OverseasFee>>>(`${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<FeePage<OverseasFee>> {
|
||||
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<OverseasFee[]>, ApiEnvelope<OverseasFee[]>>(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<OverseasFee>, ApiEnvelope<OverseasFee>>(`${base}/${encodeURIComponent(id)}`)))
|
||||
}
|
||||
export async function listBusinessFeeOptions(params: { businessType: string; keyword?: string; customerId?: string }) {
|
||||
const response = await http.get<ApiEnvelope<BusinessFeeOption[]>, ApiEnvelope<BusinessFeeOption[]>>(`${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<OverseasFee>, ApiEnvelope<OverseasFee>>(base, payload, { headers: { 'Idempotency-Key': key } })))
|
||||
}
|
||||
export async function actOnOverseasFee(id: string, action: string, body: Record<string, unknown>) {
|
||||
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<OverseasFee>, ApiEnvelope<OverseasFee>>(`${base}/${encodeURIComponent(id)}/${endpoint}`, body)))
|
||||
}
|
||||
@@ -57,8 +57,8 @@ export async function listOverseasPurchaseCustomerOptions(keyword = '', limit =
|
||||
const response = await http.get<ApiEnvelope<OverseasPurchaseCustomerOption[]>, ApiEnvelope<OverseasPurchaseCustomerOption[]>>(`${procurementBasePath}/customer-options`, { params: { keyword: keyword.trim() || undefined, limit } })
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
export async function listOverseasPurchaseWarehouseOptions() {
|
||||
const response = await http.get<ApiEnvelope<OverseasPurchaseWarehouseOption[]>, ApiEnvelope<OverseasPurchaseWarehouseOption[]>>(`${procurementBasePath}/warehouse-options`)
|
||||
export async function listOverseasPurchaseWarehouseOptions(procurementDirection?: import('../procurementDirection').ProcurementDirection) {
|
||||
const response = await http.get<ApiEnvelope<OverseasPurchaseWarehouseOption[]>, ApiEnvelope<OverseasPurchaseWarehouseOption[]>>(`${procurementBasePath}/warehouse-options`, { params: { procurementDirection } })
|
||||
return unwrapApiResponse(response).map((item) => ({ ...item, warehouseId: String(item.warehouseId) }))
|
||||
}
|
||||
export async function listOverseasPurchases(query: OverseasPurchaseQuery = {}) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { ProcurementFile, ProcurementFileCategory, ProcurementRemainingKind
|
||||
|
||||
export type ProcurementCommand = 'review' | 'purchase' | 'purchase/confirm' | 'budget-authorizations' | 'planned-warehouse' | 'cancel' | 'overseas-receipts' | 'domestic-receipts' | 'overseas-receipts/complete' | 'domestic-receipts/complete' | 'packing' | 'packing/confirm' | 'packing/withdraw' | 'dispositions' | 'receipts/correct' | 'dispositions/revoke' | 'labels' | 'international-estimate' | 'labels/apply' | 'domestic-repack' | 'overseas-shipment' | 'customer-shipment' | 'charges/confirm' | 'fees/retry' | 'fees/terminate' | 'fees/release-estimate' | 'fees/correct-target' | 'fees/refund' | 'fees/replace-failed' | 'fees/additional' | 'control-notes' | 'control-notes/resolve' | 'actual-warehouse/correct' | 'exception-close'
|
||||
export interface ProcurementCommandBody {
|
||||
procurementDirection?: import('../procurementDirection').ProcurementDirection
|
||||
name?: string
|
||||
targetType?: 'PURCHASE' | 'WAREHOUSE' | 'RECEIPT' | 'SHIPMENT'; targetId?: string; blocking?: boolean; clientSummary?: string; noteId?: string
|
||||
resolutions?: ProcurementResolution[]; customerVoucherFileId?: string; financialConclusion?: string
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
const client = vi.hoisted(() => ({ get: vi.fn() }))
|
||||
vi.mock('@/shared/api/http', async original => ({ ...await original<typeof import('@/shared/api/http')>(), http: client }))
|
||||
import { getStorageRentLot, listStorageRentLots, listStorageRentWarehouses } from './storageRent'
|
||||
|
||||
const base = '/platform/v1/last-leg/storage-rent'
|
||||
const envelope = <T>(data: T) => ({ code: '200', message: 'success', data })
|
||||
const lot = { id: '9223372036854775001', customerId: '9223372036854775002', warehouseId: '9223372036854775003', businessNo: 'WT1' }
|
||||
|
||||
describe('仓租查询接口', () => {
|
||||
beforeEach(() => vi.clearAllMocks())
|
||||
it('uses the paginated root endpoint with server filters and preserves long IDs', async () => {
|
||||
client.get.mockResolvedValue(envelope({ records: [lot], total: '1', page: 1, size: 20, pages: 1 }))
|
||||
const params = { page: 1, size: 20, businessType: 'WAREHOUSE_TRANSFER', warehouseId: lot.warehouseId, status: 'IN_STOCK' }
|
||||
const result = await listStorageRentLots(params)
|
||||
expect(client.get).toHaveBeenCalledWith(base, { params })
|
||||
expect(result.total).toBe(1)
|
||||
expect(result.records[0]).toMatchObject(lot)
|
||||
})
|
||||
it('reads a lot with its actual charges and warehouse options from dedicated endpoints', async () => {
|
||||
const charge = { id: '9223372036854775004', outboundNo: 'OUT1', quantity: 2, originalAmount: 23, currency: 'CNY', settlementAmount: 23 }
|
||||
const currencyLot = { ...lot, generatedAmount: null, generatedCurrency: null, generatedAmounts: [{ currency: 'USD', amount: 12 }, { currency: 'CNY', amount: 23 }] }
|
||||
client.get.mockResolvedValueOnce(envelope({ lot: currencyLot, charges: [charge] }))
|
||||
.mockResolvedValueOnce(envelope([{ id: lot.warehouseId, warehouseName: '测试仓' }]))
|
||||
const detail = await getStorageRentLot(lot.id)
|
||||
expect(client.get).toHaveBeenNthCalledWith(1, `${base}/${lot.id}`)
|
||||
expect(detail.lot.id).toBe(lot.id)
|
||||
expect(detail.lot).toMatchObject(currencyLot)
|
||||
expect(detail.charges).toEqual([charge])
|
||||
expect(await listStorageRentWarehouses()).toEqual([{ id: lot.warehouseId, warehouseName: '测试仓' }])
|
||||
expect(client.get).toHaveBeenNthCalledWith(2, `${base}/warehouse-options`)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,39 @@
|
||||
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
|
||||
import type { FeePage } from './overseasFees'
|
||||
import type { OverseasFee } from './overseasFees'
|
||||
|
||||
export async function adjustStorageRentFee(id: string, body: { expectedVersion: number; amount: number; reason: string; idempotencyKey: string }) {
|
||||
return unwrapApiResponse(await http.post<ApiEnvelope<OverseasFee>, ApiEnvelope<OverseasFee>>(`/platform/v1/last-leg/storage-rent/fees/${encodeURIComponent(id)}/adjust`, body))
|
||||
}
|
||||
|
||||
export interface StorageRentLot {
|
||||
id: string; customerId: string; customerName: string; businessType: string; businessNo: string
|
||||
warehouseId: string; warehouseName: string; batchNo: string; inboundAt: string; storageDays: number
|
||||
originalQuantity: number; remainingQuantity: number; quantityUnit: string; billingQuantity: number
|
||||
billingUnit: string; ruleCode: string; status: string; generatedAmount: number | null
|
||||
generatedCurrency: string | null; generatedAmounts: Array<{ currency: string; amount: number }>
|
||||
settledAmount?: number; chargeCount?: number; settledChargeCount?: number
|
||||
}
|
||||
export interface StorageRentCharge {
|
||||
id: string; outboundNo: string; businessNo: string; quantity: number; storageDays: number
|
||||
originalAmount: number; currency: string; settlementAmount: number; currencyRate: number; status: string
|
||||
createdAt: string; outboundAt: string
|
||||
}
|
||||
export interface StorageRentDetail { lot: StorageRentLot; charges: StorageRentCharge[] }
|
||||
const base = '/platform/v1/last-leg/storage-rent'
|
||||
function normalizeLot(row: StorageRentLot): StorageRentLot {
|
||||
return { ...row, id: String(row.id), customerId: String(row.customerId), warehouseId: String(row.warehouseId) }
|
||||
}
|
||||
export async function listStorageRentLots(params: Record<string, string | number | undefined>) {
|
||||
const response = await http.get<ApiEnvelope<FeePage<StorageRentLot>>, ApiEnvelope<FeePage<StorageRentLot>>>(base, { params })
|
||||
const data = unwrapApiResponse(response)
|
||||
return { ...data, total: Number(data.total), records: data.records.map(normalizeLot) }
|
||||
}
|
||||
export async function getStorageRentLot(id: string) {
|
||||
const data = unwrapApiResponse(await http.get<ApiEnvelope<StorageRentDetail>, ApiEnvelope<StorageRentDetail>>(`${base}/${encodeURIComponent(id)}`))
|
||||
return { lot: normalizeLot(data.lot), charges: data.charges.map(row => ({ ...row, id: String(row.id) })) }
|
||||
}
|
||||
export async function listStorageRentWarehouses() {
|
||||
const data = unwrapApiResponse(await http.get<ApiEnvelope<Array<{ id: string; warehouseName: string }>>, ApiEnvelope<Array<{ id: string; warehouseName: string }>>>(`${base}/warehouse-options`))
|
||||
return data.map(row => ({ ...row, id: String(row.id) }))
|
||||
}
|
||||
Reference in New Issue
Block a user