feat(admin): show single-remainder fulfillment aggregate
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
|
||||
|
||||
export interface UnifiedOrderFulfillmentFragment {
|
||||
orderId: string
|
||||
orderNo: string | null
|
||||
splitSequence: number | null
|
||||
splitRole: string | null
|
||||
plannedPackageCount: number | null
|
||||
pickedPackageCount: number | null
|
||||
unpickedPackageCount: number | null
|
||||
loadingPlanId: string | null
|
||||
containerBookingId: string | null
|
||||
sourceOrderId: string | null
|
||||
status: string | null
|
||||
historical: boolean
|
||||
}
|
||||
|
||||
export interface UnifiedOrderFulfillmentTree {
|
||||
rootOrderId: string
|
||||
rootOrderNo: string | null
|
||||
rootVersion: number | null
|
||||
originalPackageCount: number | null
|
||||
cumulativeLoadedPackageCount: number | null
|
||||
currentRemainderPackageCount: number | null
|
||||
fragments: UnifiedOrderFulfillmentFragment[]
|
||||
historicalTwoChildModel: boolean
|
||||
}
|
||||
|
||||
type UnknownRecord = Record<string, unknown>
|
||||
|
||||
function record(value: unknown): UnknownRecord {
|
||||
return value && typeof value === 'object' ? value as UnknownRecord : {}
|
||||
}
|
||||
|
||||
function stringValue(value: unknown) {
|
||||
return value === null || value === undefined || value === '' ? null : String(value)
|
||||
}
|
||||
|
||||
function numberValue(value: unknown) {
|
||||
if (value === null || value === undefined || value === '') return null
|
||||
const parsed = Number(value)
|
||||
return Number.isFinite(parsed) ? Math.trunc(parsed) : null
|
||||
}
|
||||
|
||||
function firstNumber(source: UnknownRecord, keys: string[]) {
|
||||
for (const key of keys) {
|
||||
const value = numberValue(source[key])
|
||||
if (value !== null) return value
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function firstString(source: UnknownRecord, keys: string[]) {
|
||||
for (const key of keys) {
|
||||
const value = stringValue(source[key])
|
||||
if (value !== null) return value
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function normalizeOrderFulfillmentTree(payload: unknown, fallbackOrderId: string): UnifiedOrderFulfillmentTree {
|
||||
const raw = record(payload)
|
||||
const rootOrderId = firstString(raw, ['rootOrderId', 'parentOrderId', 'orderId']) || fallbackOrderId
|
||||
const rootOrderNo = firstString(raw, ['rootOrderNo', 'parentOrderNo', 'orderNo'])
|
||||
const children = Array.isArray(raw.fragments) ? raw.fragments : Array.isArray(raw.children) ? raw.children : []
|
||||
const legacy = raw.historicalTwoChildModel === true
|
||||
|| children.some((item) => {
|
||||
const child = record(item)
|
||||
const role = firstString(child, ['splitRole', 'role'])
|
||||
const childId = firstString(child, ['orderId', 'childOrderId'])
|
||||
const sourceId = firstString(child, ['sourceOrderId', 'parentOrderId'])
|
||||
return role === 'LOADED' && childId !== null && sourceId !== null && childId !== sourceId
|
||||
})
|
||||
const original = firstNumber(raw, ['originalPackageCount', 'sourcePackageCount'])
|
||||
?? (() => {
|
||||
const allocated = firstNumber(raw, ['cumulativeLoadedPackageCount', 'allocatedPackageCount', 'loadedPackageCount'])
|
||||
const remainder = firstNumber(raw, ['currentRemainderPackageCount', 'remainingPackageCount'])
|
||||
return allocated === null || remainder === null ? null : allocated + remainder
|
||||
})()
|
||||
const cumulativeLoaded = firstNumber(raw, ['cumulativeLoadedPackageCount', 'allocatedPackageCount', 'loadedPackageCount'])
|
||||
const currentRemainder = firstNumber(raw, ['currentRemainderPackageCount', 'remainingPackageCount'])
|
||||
const fragments = children.map((item): UnifiedOrderFulfillmentFragment => {
|
||||
const child = record(item)
|
||||
const role = firstString(child, ['splitRole', 'fragmentRole', 'role'])
|
||||
const planned = firstNumber(child, ['plannedPackageCount', 'sourcePackageCount', 'packageCount'])
|
||||
const picked = firstNumber(child, ['pickedPackageCount', 'loadedPackageCount'])
|
||||
?? (role === 'LOADED' ? firstNumber(child, ['packageCount']) : 0)
|
||||
const unpicked = firstNumber(child, ['unpickedPackageCount', 'remainingPackageCount'])
|
||||
?? (planned === null || picked === null ? null : Math.max(0, planned - picked))
|
||||
return {
|
||||
orderId: firstString(child, ['orderId', 'childOrderId', 'sourceOrderId']) || '',
|
||||
orderNo: firstString(child, ['orderNo', 'childOrderNo', 'sourceOrderNo']),
|
||||
splitSequence: firstNumber(child, ['splitSequence', 'sequence']),
|
||||
splitRole: role,
|
||||
plannedPackageCount: planned,
|
||||
pickedPackageCount: picked,
|
||||
unpickedPackageCount: unpicked,
|
||||
loadingPlanId: firstString(child, ['loadingPlanId']),
|
||||
containerBookingId: firstString(child, ['containerBookingId']),
|
||||
sourceOrderId: firstString(child, ['sourceOrderId', 'parentOrderId']),
|
||||
status: firstString(child, ['status', 'orderStatus', 'processStatus']),
|
||||
historical: legacy,
|
||||
}
|
||||
}).filter((item) => item.orderId)
|
||||
return {
|
||||
rootOrderId,
|
||||
rootOrderNo,
|
||||
rootVersion: firstNumber(raw, ['rootVersion', 'parentVersion']),
|
||||
originalPackageCount: original,
|
||||
cumulativeLoadedPackageCount: cumulativeLoaded,
|
||||
currentRemainderPackageCount: currentRemainder,
|
||||
fragments,
|
||||
historicalTwoChildModel: legacy,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPlatformOrderFulfillmentTree(tenantId: string, orderId: string) {
|
||||
const response = await http.get<ApiEnvelope<unknown>, ApiEnvelope<unknown>>(
|
||||
`/platform/v1/orders/${encodeURIComponent(orderId)}/fulfillment-tree`,
|
||||
{ params: { tenantId } },
|
||||
)
|
||||
return normalizeOrderFulfillmentTree(unwrapApiResponse(response), orderId)
|
||||
}
|
||||
Reference in New Issue
Block a user