Files
client-web/src/modules/first-leg/api/firstLeg.ts
T
2026-08-07 18:44:58 +08:00

340 lines
9.6 KiB
TypeScript

import { apiBlobRequest, apiRequest } from '@/api/http'
export type ApiFirstLegStatus = 'DRAFT' | 'SUBMITTED' | 'APPROVED' | 'ACCEPTED' | 'REJECTED' | 'CANCELLED' | 'PROCESSING' | 'COMPLETED'
export type ApiTransportMode = 'SEA' | 'AIR' | 'RAIL' | 'TRUCK'
export type ApiPickupMethod = 'SELF_EXPRESS' | 'PICKUP' | 'SELF_TRUCK'
export type ApiTaxMode = 'BUY_DECLARATION' | 'TAX_REFUND'
export type ApiContainerMode = 'FULL_CONTAINER' | 'LCL'
export type ApiDestinationType = 'FBA' | 'OVERSEAS' | 'PRIVATE'
export type ApiPricingTaxMode = 'TAX_INCLUDED' | 'TAX_EXCLUDED'
export type ApiPriceModel = 'FBA_WAREHOUSE_RATE' | 'REGION_WEIGHT_TIER'
export interface PageResult<T> {
records: T[]
page: number
size: number
total: number
pages: number
}
export type MerchantWarehouseType = 'DOMESTIC' | 'FBA' | 'OVERSEAS'
export interface MerchantWarehouseQuery {
page?: number
size?: number
status?: 'ACTIVE' | 'INACTIVE'
warehouseType?: MerchantWarehouseType
}
export interface MerchantWarehouse {
id: number | string
warehouseCode: string
warehouseName: string
warehouseType: string
countryCode: string
timezone: string | null
contactName: string | null
contactPhone: string | null
contactEmail: string | null
status: 'ACTIVE' | 'INACTIVE'
remark: string | null
updatedAt: string | null
}
export interface MerchantWarehouseAddress {
countryCode: string
stateProvince: string | null
city: string | null
district: string | null
postalCode: string | null
addressLine1: string
addressLine2: string | null
}
export interface MerchantWarehouseCapability {
capabilityCode: string
capabilityName: string
enabled: boolean
cutoffTime: string | null
description: string | null
}
export interface MerchantWarehouseDetail {
warehouse: MerchantWarehouse
addresses: MerchantWarehouseAddress[]
capabilities: MerchantWarehouseCapability[]
}
export interface FirstLegOrderPartyPayload {
companyName: string
contactName: string
phone: string
email?: string
countryCode: string
province?: string
city: string
district?: string
addressLine: string
postalCode?: string
}
export interface FirstLegOrderCargoPayload {
cargoName: string
hsCode?: string
packageType: string
packageCount: number
grossWeightKg: number
volumeCbm: number
lengthCm?: number
widthCm?: number
heightCm?: number
declaredValue?: number
currency?: string
dangerousGoods?: boolean
cargoRemark?: string
skuNo?: string
upcNo?: string
cargoNameEn?: string
customsHsCode?: string
unitsPerPackage?: number
totalQuantity?: number
netWeightKg?: number
unitDeclaredValue?: number
material?: string
usage?: string
model?: string
brand?: string
productLengthCm?: number
productWidthCm?: number
productHeightCm?: number
shipmentId?: string
referenceId?: string
productImageFileId?: number | string
}
export interface ProductImageUploadResult {
fileId: number | string
fileName: string
contentType: string
sizeBytes: number
}
export interface FirstLegOrderSaveRequest {
customerId?: number | string
customerName: string
transportMode: ApiTransportMode
tradeTerm?: string
destinationCountryCode: string
destinationWarehouseId?: number | string
destinationWarehouseName: string
expectedShipDate?: string
customerRemark?: string
sender?: FirstLegOrderPartyPayload
receiver?: FirstLegOrderPartyPayload
cargoes: FirstLegOrderCargoPayload[]
pickupMethod: ApiPickupMethod
receivingWarehouseId: number | string
receivingWarehouseName: string
receivingWarehouseAddress?: string | null
receivingTime?: string | null
channelCode: string
channelName: string
pricingTaxMode: ApiPricingTaxMode
taxMode: ApiTaxMode
containerMode?: ApiContainerMode
customsInspection: boolean
destinationType: ApiDestinationType
amazonWarehouseCode?: string
shipmentId?: string
referenceId?: string
}
export interface MerchantFirstLegOrder {
id: number | string
orderNo: string | null
customerId: number | string | null
customerName: string
status: ApiFirstLegStatus
transportMode: ApiTransportMode
tradeTerm: string | null
destinationCountryCode: string
destinationWarehouseId: number | string | null
destinationWarehouseName: string
expectedShipDate: string | null
customerRemark: string | null
pickupMethod: ApiPickupMethod
receivingWarehouseId: number | string
receivingWarehouseName: string
receivingWarehouseAddress?: string | null
receivingTime?: string | null
channelCode: string
channelName: string
pricingTaxMode?: ApiPricingTaxMode | null
carrierId?: number | string | null
carrierName?: string | null
taxMode: ApiTaxMode
containerMode: ApiContainerMode | null
customsInspection: boolean
destinationType: ApiDestinationType
amazonWarehouseCode: string | null
shipmentId: string | null
referenceId: string | null
expressCompany?: string | null
expressTrackingNo?: string | null
submittedAt?: string | null
completedAt?: string | null
version: number
createdAt: string
updatedAt: string
}
export interface MerchantFirstLegOrderCargo extends FirstLegOrderCargoPayload {
id: number | string
itemNo: number
}
export interface MerchantFirstLegOrderParty extends FirstLegOrderPartyPayload {
id: number | string
partyType: 'SENDER' | 'RECEIVER' | string
}
export interface MerchantOrderStatusHistory {
id: number | string
fromStatus: ApiFirstLegStatus | null
toStatus: ApiFirstLegStatus
action: string
reason: string | null
operatorName: string | null
occurredAt: string
}
export interface MerchantFirstLegOrderDetail {
order: MerchantFirstLegOrder
cargoes: MerchantFirstLegOrderCargo[]
parties: MerchantFirstLegOrderParty[]
internalNotes: []
statusHistory: MerchantOrderStatusHistory[]
}
export function uploadMerchantProductImage(file: File) {
const data = new FormData()
data.append('file', file)
return apiRequest<ProductImageUploadResult>({ method: 'POST', url: '/merchant/v1/files/product-images', data })
}
export function downloadMerchantProductImage(fileId: number | string) {
return apiBlobRequest({ method: 'GET', url: `/merchant/v1/files/product-images/${fileId}` })
}
export interface FirstLegOrderQuery {
keyword?: string
status?: ApiFirstLegStatus
customerId?: number | string
destinationWarehouseId?: number | string
createdFrom?: string
createdTo?: string
page?: number
size?: number
}
export interface MerchantPricingChannel {
priceBookVersionId: number | string
channelCode: string
channelName: string
priceModel: ApiPriceModel
pricingTaxModes: ApiPricingTaxMode[]
estimatedDepartureDate: string | null
estimatedDepartureText: string | null
latestWarehouseEntry: string | null
referenceTransit: string | null
surchargeDescription: string | null
remark: string | null
}
export interface MerchantPriceEstimateRequest {
originWarehouseId: number | string
channelCode: string
destinationCode?: string
destinationType: Exclude<ApiDestinationType, 'PRIVATE'>
destinationWarehouseId?: number | string
destinationName?: string
pricingTaxMode: ApiPricingTaxMode
actualWeightKg: number
actualVolumeCbm: number
}
export interface MerchantPriceEstimate {
priceBookVersionId: number | string
channelCode: string
priceModel: ApiPriceModel
pricingTaxMode: ApiPricingTaxMode
chargeableQuantity: number
chargeUnit: string
unitPrice: number
estimatedAmount: number
currency: string
disclaimer: string
}
export function getMerchantWarehouses(params: MerchantWarehouseQuery = {}) {
return apiRequest<PageResult<MerchantWarehouse>>({
method: 'GET',
url: '/merchant/v1/warehouses',
params: { page: 1, size: 200, status: 'ACTIVE', ...params },
})
}
export function getMerchantWarehouseDetail(id: number | string) {
return apiRequest<MerchantWarehouseDetail>({ method: 'GET', url: `/merchant/v1/warehouses/${id}` })
}
export function getMerchantPricingChannels(originWarehouseId: number | string) {
return apiRequest<MerchantPricingChannel[]>({
method: 'GET',
url: '/merchant/v1/first-leg/pricing/channels',
params: { originWarehouseId },
})
}
export function estimateMerchantFirstLegPrice(payload: MerchantPriceEstimateRequest) {
return apiRequest<MerchantPriceEstimate>({
method: 'POST',
url: '/merchant/v1/first-leg/pricing/estimate',
data: payload,
})
}
export function getMerchantFirstLegOrders(params: FirstLegOrderQuery = {}) {
return apiRequest<PageResult<MerchantFirstLegOrder>>({ method: 'GET', url: '/merchant/v1/orders/first-leg', params: { page: 1, size: 200, ...params } })
}
export function getMerchantFirstLegOrder(id: number | string) {
return apiRequest<MerchantFirstLegOrderDetail>({ method: 'GET', url: `/merchant/v1/orders/first-leg/${id}` })
}
export function createMerchantFirstLegOrder(payload: FirstLegOrderSaveRequest) {
return apiRequest<number | string>({
method: 'POST',
url: '/merchant/v1/orders/first-leg',
data: payload,
headers: { 'Idempotency-Key': createIdempotencyKey('first-leg-create') },
})
}
export function updateMerchantFirstLegOrder(id: number | string, payload: FirstLegOrderSaveRequest) {
return apiRequest<void>({ method: 'PUT', url: `/merchant/v1/orders/first-leg/${id}`, data: payload })
}
export function submitMerchantFirstLegOrder(id: number | string) {
return apiRequest<void>({
method: 'POST',
url: `/merchant/v1/orders/first-leg/${id}/submit`,
headers: { 'Idempotency-Key': createIdempotencyKey(`first-leg-submit-${id}`) },
})
}
function createIdempotencyKey(scope: string) {
const randomPart = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`
return `${scope}-${randomPart}`
}