169 lines
5.5 KiB
JavaScript
169 lines
5.5 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,
|
|
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
|
|
}
|