Files
admin-web/src/modules/fba-pricing/carrier-rates/api/platformPriceBooks.ts
T

277 lines
9.5 KiB
TypeScript

import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
export type PriceBookStatus = 'PENDING_CONFIRMATION' | 'ACTIVE' | 'SUPERSEDED' | 'FAILED'
export type PriceModel = 'FBA_WAREHOUSE_RATE' | 'REGION_WEIGHT_TIER'
export type PricingTaxMode = 'TAX_INCLUDED' | 'TAX_EXCLUDED'
export type PricingDestinationType = 'FBA' | 'OVERSEAS'
export type PriceBookChargeUnit = 'KG' | 'CBM'
type ApiId = string | number
type ApiNumber = string | number
export interface PlatformPriceBookVersion {
id: string
customerId: string
versionNo: number
originalFileName: string
sourceFileId: string | null
sourceSha256: string | null
status: PriceBookStatus
channelCount: number
rateCount: number
blockingErrorCount: number
warningCount: number
parseIssuesJson: string | null
createdBy: string | null
createdAt: string | null
publishedBy: string | null
publishedAt: string | null
supersededAt: string | null
}
/** Read-only channel data parsed from the uploaded workbook. */
export interface PlatformPriceBookParsedChannel {
channelCode: string
channelName: string
priceModel: PriceModel
sourceSheetName: string
estimatedDepartureDate: string | null
estimatedDepartureText: string | null
latestWarehouseEntry: string | null
vesselName: string | null
voyageNo: string | null
referenceTransit: string | null
surchargeDescription: string | null
remark: string | null
}
/** Read-only price row parsed from the uploaded workbook. */
export interface PlatformPriceBookParsedRate {
id: string | null
channelCode: string
priceModel: PriceModel
originWarehouseId: string | null
originWarehouseCode: string
originWarehouseName: string
destinationType: PricingDestinationType | null
destinationCode: string | null
destinationWarehouseId: string | null
destinationName: string | null
regionCode: string | null
pricingTaxMode: PricingTaxMode | null
chargeUnit: PriceBookChargeUnit
weightFromKg: number | null
unitPrice: number
currency: string
sourceSheetName: string
sourceRowNumber: number
sourceDestinationText: string | null
remark: string | null
}
export interface PlatformPriceBookParseIssue {
severity: 'BLOCKING' | 'WARNING' | string
issueCode: string
message: string
sourceSheetName: string | null
sourceRowNumber: number | null
sourceCell: string | null
}
export interface PlatformPriceBookDetail {
version: PlatformPriceBookVersion
channels: PlatformPriceBookParsedChannel[]
rates: PlatformPriceBookParsedRate[]
issues: PlatformPriceBookParseIssue[]
}
export interface PriceBookConfirmationRequest {
originMappings: Array<{
originWarehouseCode: string
warehouseId: string
}>
overseasMappings: Array<{
sourceDestinationName: string
warehouseId: string
}>
}
export interface PlatformPriceBookQuery {
customerId?: string
status?: PriceBookStatus | string
}
interface PriceBookVersionPayload extends Omit<PlatformPriceBookVersion,
'id' | 'customerId' | 'versionNo' | 'sourceFileId' | 'channelCount' | 'rateCount' | 'blockingErrorCount' | 'warningCount' | 'createdBy' | 'publishedBy'> {
id: ApiId
customerId: ApiId
versionNo: ApiNumber
sourceFileId?: ApiId | null
channelCount?: ApiNumber | null
rateCount?: ApiNumber | null
blockingErrorCount?: ApiNumber | null
warningCount?: ApiNumber | null
createdBy?: ApiId | null
publishedBy?: ApiId | null
}
interface PriceBookParsedChannelPayload extends Omit<PlatformPriceBookParsedChannel,
'estimatedDepartureDate' | 'estimatedDepartureText' | 'latestWarehouseEntry' | 'vesselName' | 'voyageNo'
| 'referenceTransit' | 'surchargeDescription' | 'remark'> {
estimatedDepartureDate?: string | null
estimatedDepartureText?: string | null
latestWarehouseEntry?: string | null
vesselName?: string | null
voyageNo?: string | null
referenceTransit?: string | null
surchargeDescription?: string | null
remark?: string | null
}
interface PriceBookParsedRatePayload extends Omit<PlatformPriceBookParsedRate,
'id' | 'originWarehouseId' | 'destinationWarehouseId' | 'weightFromKg' | 'unitPrice' | 'sourceRowNumber'> {
id?: ApiId | null
originWarehouseId?: ApiId | null
destinationWarehouseId?: ApiId | null
weightFromKg?: ApiNumber | null
unitPrice: ApiNumber
sourceRowNumber: ApiNumber
}
interface PriceBookParseIssuePayload extends Omit<PlatformPriceBookParseIssue, 'sourceRowNumber'> {
sourceRowNumber?: ApiNumber | null
}
interface PriceBookDetailPayload {
version: PriceBookVersionPayload
channels?: PriceBookParsedChannelPayload[] | null
rates?: PriceBookParsedRatePayload[] | null
issues?: PriceBookParseIssuePayload[] | null
}
const basePath = '/platform/v1/fba-pricing/carrier-rates/price-books'
function nullableId(value: ApiId | null | undefined) {
return value == null ? null : String(value)
}
function nullableText(value: string | null | undefined) {
return value ?? null
}
function numberValue(value: ApiNumber | null | undefined) {
const number = Number(value ?? 0)
return Number.isFinite(number) ? number : 0
}
function normalizeVersion(value: PriceBookVersionPayload): PlatformPriceBookVersion {
return {
...value,
id: String(value.id),
customerId: String(value.customerId),
versionNo: numberValue(value.versionNo),
sourceFileId: nullableId(value.sourceFileId),
channelCount: numberValue(value.channelCount),
rateCount: numberValue(value.rateCount),
blockingErrorCount: numberValue(value.blockingErrorCount),
warningCount: numberValue(value.warningCount),
parseIssuesJson: nullableText(value.parseIssuesJson),
createdBy: nullableId(value.createdBy),
createdAt: nullableText(value.createdAt),
publishedBy: nullableId(value.publishedBy),
publishedAt: nullableText(value.publishedAt),
supersededAt: nullableText(value.supersededAt),
}
}
function normalizeChannel(value: PriceBookParsedChannelPayload): PlatformPriceBookParsedChannel {
return {
...value,
estimatedDepartureDate: nullableText(value.estimatedDepartureDate),
estimatedDepartureText: nullableText(value.estimatedDepartureText),
latestWarehouseEntry: nullableText(value.latestWarehouseEntry),
vesselName: nullableText(value.vesselName),
voyageNo: nullableText(value.voyageNo),
referenceTransit: nullableText(value.referenceTransit),
surchargeDescription: nullableText(value.surchargeDescription),
remark: nullableText(value.remark),
}
}
function normalizeRate(value: PriceBookParsedRatePayload): PlatformPriceBookParsedRate {
return {
...value,
id: nullableId(value.id),
originWarehouseId: nullableId(value.originWarehouseId),
destinationWarehouseId: nullableId(value.destinationWarehouseId),
destinationType: value.destinationType ?? null,
destinationCode: nullableText(value.destinationCode),
destinationName: nullableText(value.destinationName),
regionCode: nullableText(value.regionCode),
pricingTaxMode: value.pricingTaxMode ?? null,
weightFromKg: value.weightFromKg == null ? null : numberValue(value.weightFromKg),
unitPrice: numberValue(value.unitPrice),
sourceRowNumber: numberValue(value.sourceRowNumber),
sourceDestinationText: nullableText(value.sourceDestinationText),
remark: nullableText(value.remark),
}
}
function normalizeIssue(value: PriceBookParseIssuePayload): PlatformPriceBookParseIssue {
return {
...value,
sourceSheetName: nullableText(value.sourceSheetName),
sourceRowNumber: value.sourceRowNumber == null ? null : numberValue(value.sourceRowNumber),
sourceCell: nullableText(value.sourceCell),
}
}
function normalizeDetail(value: PriceBookDetailPayload): PlatformPriceBookDetail {
return {
version: normalizeVersion(value.version),
channels: (value.channels || []).map(normalizeChannel),
rates: (value.rates || []).map(normalizeRate),
issues: (value.issues || []).map(normalizeIssue),
}
}
export async function listPlatformPriceBooks(query: PlatformPriceBookQuery = {}): Promise<PlatformPriceBookVersion[]> {
const status = query.status?.trim()
const customerId = query.customerId?.trim() || '0'
const response = await http.get<ApiEnvelope<PriceBookVersionPayload[]>, ApiEnvelope<PriceBookVersionPayload[]>>(basePath, {
params: status ? { customerId, status } : { customerId },
})
return unwrapApiResponse(response).map(normalizeVersion)
}
export async function getPlatformPriceBook(versionId: string): Promise<PlatformPriceBookDetail> {
const response = await http.get<ApiEnvelope<PriceBookDetailPayload>, ApiEnvelope<PriceBookDetailPayload>>(
`${basePath}/${encodeURIComponent(versionId)}`,
)
return normalizeDetail(unwrapApiResponse(response))
}
export async function uploadPlatformPriceBook(file: File, customerId = '0'): Promise<PlatformPriceBookDetail> {
const form = new FormData()
form.append('file', file)
const response = await http.post<ApiEnvelope<PriceBookDetailPayload>, ApiEnvelope<PriceBookDetailPayload>>(basePath, form, {
params: { customerId: customerId.trim() || '0' },
})
return normalizeDetail(unwrapApiResponse(response))
}
export async function publishPlatformPriceBook(
versionId: string,
request: PriceBookConfirmationRequest,
): Promise<PlatformPriceBookDetail> {
const response = await http.post<ApiEnvelope<PriceBookDetailPayload>, ApiEnvelope<PriceBookDetailPayload>>(
`${basePath}/${encodeURIComponent(versionId)}/publish`,
request,
)
return normalizeDetail(unwrapApiResponse(response))
}
export function downloadPlatformPriceBookSource(versionId: string) {
return http.get<Blob, Blob>(`${basePath}/${encodeURIComponent(versionId)}/source`, { responseType: 'blob' })
}