Files
client-web/src/modules/first-leg/api/inboundBilling.ts
T
2026-09-03 17:49:09 +08:00

183 lines
7.0 KiB
TypeScript

import { apiRequest } from '@/api/http'
import type {
MerchantInboundBilling,
MerchantInboundFeeSnapshot,
MerchantInboundResult,
MerchantInboundSummary,
MerchantPalletLocation,
} from '@/modules/first-leg/types/inboundBilling'
interface MerchantInboundBillingWireResponse {
inbound: MerchantInboundResult
measurementSummary: MerchantInboundBilling['measurementSummary']
measurementItems: MerchantInboundBilling['measurementItems']
billingBasis: MerchantInboundBilling['billingBasis']
latestFee: MerchantInboundFeeSnapshot
}
function sanitizeSummary(summary: MerchantInboundSummary): MerchantInboundSummary {
return {
id: summary.id,
orderId: summary.orderId,
orderNo: summary.orderNo,
inboundCode: summary.inboundCode,
processStatus: summary.processStatus,
feeStatus: summary.feeStatus,
notificationStatus: summary.notificationStatus,
warehouseName: summary.warehouseName,
deliveryWarehouseName: summary.deliveryWarehouseName,
destinationCode: summary.destinationCode,
destinationName: summary.destinationName,
totalPackageCount: summary.totalPackageCount,
actualGrossWeightKg: summary.actualGrossWeightKg,
actualNetWeightKg: summary.actualNetWeightKg,
actualVolumeCbm: summary.actualVolumeCbm,
volumeWeightKg: summary.volumeWeightKg,
chargeableWeightKg: summary.chargeableWeightKg,
chargeableVolumeCbm: summary.chargeableVolumeCbm,
palletCount: summary.palletCount,
locationCodes: summary.locationCodes,
measureCompletedAt: summary.measureCompletedAt,
inboundedAt: summary.inboundedAt,
}
}
function sanitizePalletLocation(item: MerchantPalletLocation): MerchantPalletLocation {
return {
allocationNo: item.allocationNo,
warehouseId: item.warehouseId,
palletId: item.palletId,
palletCode: item.palletCode,
palletCapacityCbm: item.palletCapacityCbm,
allocatedVolumeCbm: item.allocatedVolumeCbm,
allocatedPackageCount: item.allocatedPackageCount,
locationId: item.locationId,
locationCode: item.locationCode,
allocationStatus: item.allocationStatus,
}
}
function sanitizeFee(fee: MerchantInboundFeeSnapshot): MerchantInboundFeeSnapshot {
return {
feeId: fee.feeId,
feeNo: fee.feeNo,
feeVersion: fee.feeVersion,
feeStatus: fee.feeStatus,
inboundId: fee.inboundId,
inboundCode: fee.inboundCode,
measurementId: fee.measurementId == null ? null : String(fee.measurementId),
measurementSource: fee.measurementSource,
chargeRuleId: fee.chargeRuleId,
ruleCode: fee.ruleCode,
chargeRuleVersion: fee.chargeRuleVersion,
carrierRateId: fee.carrierRateId,
rateCode: fee.rateCode,
carrierRateVersion: fee.carrierRateVersion,
customerId: fee.customerId,
customerName: fee.customerName,
originWarehouseId: fee.originWarehouseId,
originWarehouseName: fee.originWarehouseName,
businessType: fee.businessType,
channelCode: fee.channelCode,
carrierId: fee.carrierId,
carrierName: fee.carrierName,
destinationCode: fee.destinationCode,
taxMode: fee.taxMode,
chargeMode: fee.chargeMode,
conversionFactor: fee.conversionFactor,
roundingScale: fee.roundingScale,
roundingMode: fee.roundingMode,
actualWeightKg: fee.actualWeightKg,
actualVolumeCbm: fee.actualVolumeCbm,
convertedQuantity: fee.convertedQuantity,
chargeableQuantity: fee.chargeableQuantity,
chargeUnit: fee.chargeUnit,
unitPrice: fee.unitPrice,
subtotalAmount: fee.subtotalAmount,
additionalAmount: fee.additionalAmount,
totalAmount: fee.totalAmount,
currency: fee.currency,
rateEffectiveFrom: fee.rateEffectiveFrom,
rateEffectiveTo: fee.rateEffectiveTo,
calculatedAt: fee.calculatedAt,
confirmedAt: fee.confirmedAt,
sentAt: fee.sentAt,
nextProcessStatus: fee.nextProcessStatus,
customerConfirmationStatus: fee.customerConfirmationStatus,
customerConfirmedBy: fee.customerConfirmedBy == null ? null : String(fee.customerConfirmedBy),
customerConfirmedByName: fee.customerConfirmedByName,
customerConfirmedAt: fee.customerConfirmedAt,
}
}
function sanitizeMeasurementSummary(item: MerchantInboundBilling['measurementSummary']): MerchantInboundBilling['measurementSummary'] {
return {
measurementId: item.measurementId == null ? null : String(item.measurementId),
sourceType: item.sourceType,
measurementStatus: item.measurementStatus,
actualPackageCount: item.actualPackageCount,
actualGrossWeightKg: item.actualGrossWeightKg,
actualNetWeightKg: item.actualNetWeightKg,
actualVolumeCbm: item.actualVolumeCbm,
volumeWeightKg: item.volumeWeightKg,
actualGoodsName: item.actualGoodsName,
calculationSource: item.calculationSource,
}
}
function sanitizeMeasurementItem(item: MerchantInboundBilling['measurementItems'][number]): MerchantInboundBilling['measurementItems'][number] {
return {
id: String(item.id),
itemNo: item.itemNo,
boxSpecification: item.boxSpecification,
packageCount: item.packageCount,
lengthCm: item.lengthCm,
widthCm: item.widthCm,
heightCm: item.heightCm,
unitVolumeCbm: item.unitVolumeCbm,
totalVolumeCbm: item.totalVolumeCbm,
volumeWeightKg: item.volumeWeightKg,
chargeRuleId: item.chargeRuleId == null ? null : String(item.chargeRuleId),
coefficientMode: item.coefficientMode,
calculationRuleVersion: item.calculationRuleVersion,
}
}
function sanitizeInboundBilling(response: MerchantInboundBillingWireResponse): MerchantInboundBilling {
return {
inbound: {
summary: sanitizeSummary(response.inbound.summary),
publicRemark: response.inbound.publicRemark,
palletLocations: response.inbound.palletLocations.map(sanitizePalletLocation),
},
measurementSummary: sanitizeMeasurementSummary(response.measurementSummary),
measurementItems: (response.measurementItems || []).map(sanitizeMeasurementItem),
billingBasis: response.billingBasis,
latestFee: sanitizeFee(response.latestFee),
}
}
export async function getMerchantInboundBilling(orderId: string): Promise<MerchantInboundBilling> {
const response = await apiRequest<MerchantInboundBillingWireResponse>({
method: 'GET',
url: `/merchant/v1/first-leg/orders/${orderId}/inbound-billing`,
})
return sanitizeInboundBilling(response)
}
function createFeeConfirmationKey(orderId: string, feeVersion: number) {
const randomPart = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`
return `inbound-fee-confirm-${orderId}-${feeVersion}-${randomPart}`
}
export async function confirmMerchantInboundFee(orderId: string, expectedFeeVersion: number,
idempotencyKey = createFeeConfirmationKey(orderId, expectedFeeVersion)) {
const response = await apiRequest<MerchantInboundFeeSnapshot>({
method: 'POST',
url: `/merchant/v1/first-leg/orders/${encodeURIComponent(orderId)}/inbound-billing/confirm`,
headers: { 'Idempotency-Key': idempotencyKey },
data: { expectedFeeVersion },
})
return sanitizeFee(response)
}