Files
baiyue-firstleg/scripts/admin-test-env-modules/fulfillment-list-field-contract.mjs
T
2026-08-26 14:56:33 +08:00

171 lines
5.6 KiB
JavaScript

function valueAt(record, path) {
return path.split('.').reduce((value, key) => value?.[key], record)
}
export function isPositiveId(value) {
return /^\d+$/.test(String(value ?? '')) && BigInt(String(value)) > 0n
}
export function isNonBlank(value) {
return typeof value === 'string' && value.trim().length > 0
}
export function isIsoDateTime(value) {
return isNonBlank(value) && Number.isFinite(Date.parse(value))
}
export function isNonNegativeNumber(value) {
return value !== null && value !== '' && Number.isFinite(Number(value)) && Number(value) >= 0
}
function gaps(record, rules) {
return Object.entries(rules)
.filter(([path, predicate]) => !predicate(valueAt(record, path), record))
.map(([path]) => path)
}
export function containerFieldGaps(record, { requireContainerNo = false } = {}) {
return gaps(record, {
id: isPositiveId,
cabinetNo: isNonBlank,
warehouseId: isPositiveId,
warehouseName: isNonBlank,
channelCode: isNonBlank,
containerType: isNonBlank,
capacityCbm: (value) => Number(value) > 0,
maxWeightKg: (value) => Number(value) > 0,
planStatus: isNonBlank,
...(requireContainerNo ? { containerNo: isNonBlank } : {}),
})
}
export function preloadFieldGaps(record) {
return gaps(record, {
planId: isPositiveId,
containerBookingId: isPositiveId,
preloadNo: isNonBlank,
cabinetNo: isNonBlank,
customers: (value) => Array.isArray(value) && value.length > 0
&& value.every((customer) => isPositiveId(customer?.accountId) && isNonBlank(customer?.customerName)),
warehouseName: isNonBlank,
containerType: isNonBlank,
status: isNonBlank,
skuCount: isNonNegativeNumber,
totalPackageCount: isNonNegativeNumber,
totalVolumeCbm: isNonNegativeNumber,
totalGrossWeightKg: isNonNegativeNumber,
createdAt: isIsoDateTime,
})
}
export function loadingListFieldGaps(record) {
return gaps(record, {
planId: isPositiveId,
containerBookingId: isPositiveId,
loadingListNo: isNonBlank,
containerNo: isNonBlank,
cabinetNo: isNonBlank,
warehouseName: isNonBlank,
status: (value) => value === 'COMPLETED',
totalPackageCount: isNonNegativeNumber,
totalGrossWeightKg: isNonNegativeNumber,
totalNetWeightKg: isNonNegativeNumber,
totalVolumeCbm: isNonNegativeNumber,
totalQuantity: isNonNegativeNumber,
createdAt: isIsoDateTime,
})
}
export function oceanBookingFieldGaps(record) {
return gaps(record, {
id: isPositiveId,
bookingNo: isNonBlank,
status: (value) => ['DRAFT', 'RELEASED'].includes(value),
vesselName: isNonBlank,
voyageNo: isNonBlank,
estimatedSailingAt: isIsoDateTime,
estimatedArrivalAt: isIsoDateTime,
customerName: (value, item) => item.customerId == null ? value == null : isNonBlank(value),
shippingCompanyName: (value, item) => item.shippingCompanyId == null ? value == null : isNonBlank(value),
bookingAgentName: (value, item) => item.bookingAgentId == null ? value == null : isNonBlank(value),
})
}
export function portScheduleFieldGaps(record) {
const view = record?.portSchedule ?? record
const schedule = view?.schedule ?? view
return gaps(schedule, {
id: isPositiveId,
scheduleNo: isNonBlank,
sourceName: isNonBlank,
vesselName: isNonBlank,
voyageNo: isNonBlank,
departurePortCode: isNonBlank,
destinationPortCode: isNonBlank,
etdAt: isIsoDateTime,
etaAt: isIsoDateTime,
observedAt: isIsoDateTime,
updatedAt: isIsoDateTime,
expiresAt: isIsoDateTime,
}).concat(typeof view?.expired === 'boolean' ? [] : ['expired'])
}
export function truckAppointmentFieldGaps(record) {
return gaps(record, {
id: isPositiveId,
appointmentNo: isNonBlank,
oceanBookingId: (value) => value == null || isPositiveId(value),
containerBookingId: (value) => value == null || isPositiveId(value),
loadingPlanId: (value) => value == null || isPositiveId(value),
warehouseId: (value) => value == null || isPositiveId(value),
plannedLoadingAt: isIsoDateTime,
status: isNonBlank,
customerId: isPositiveId,
customerName: isNonBlank,
carrierId: isPositiveId,
carrierName: isNonBlank,
attachmentCount: isNonNegativeNumber,
})
}
export function customsCaseFieldGaps(record) {
const customsCase = record?.customsCase ?? record?.case ?? record
return gaps(customsCase, {
id: isPositiveId,
customsNo: isNonBlank,
orderId: isPositiveId,
inboundId: isPositiveId,
containerBookingId: isPositiveId,
checklistVersionId: isPositiveId,
checklistCode: isNonBlank,
checklistVersionNo: isNonBlank,
status: isNonBlank,
updatedAt: isIsoDateTime,
})
}
export function outboundAggregateFieldGaps(record) {
const aggregate = record?.aggregate ?? record
const result = gaps(aggregate, {
containerBookingId: isPositiveId,
containerNo: isNonBlank,
loadingPlanId: isPositiveId,
loadingPlanStatus: isNonBlank,
pickTaskId: isPositiveId,
pickTaskStatus: isNonBlank,
oceanBookingId: isPositiveId,
oceanBookingStatus: isNonBlank,
dispatchStatus: isNonBlank,
version: isNonNegativeNumber,
inbounds: (value) => Array.isArray(value) && value.length > 0,
'gate.inbounds': (value) => Array.isArray(value) && value.length > 0,
})
for (const [index, inbound] of (Array.isArray(aggregate?.inbounds) ? aggregate.inbounds : []).entries()) {
result.push(...gaps(inbound, {
orderId: isPositiveId, inboundId: isPositiveId, inboundCode: isNonBlank,
tenantId: isPositiveId, inboundStatus: isNonBlank,
}).map((field) => `inbounds[${index}].${field}`))
}
return result
}