export const ITEM_STATUS = Object.freeze({ PENDING: 'pending', PICKED: 'picked', PARTIAL: 'partial', SKIPPED: 'skipped' }) export function isSealedPalletItem(item) { return item?.unitType === 'SEALED_PALLET' } // Physical cartons still absent from the picked count, independent of whether // the operator has handled the line as partial or skipped. export function remainingPhysicalCartons(items, states = {}, partialQuantities = {}) { const count = (value) => Number.isFinite(Number(value)) ? Math.max(0, Number(value)) : 0 return items.reduce((total, item) => { const planned = count(item.plannedPackageCount ?? item.qty) const hasServerCount = item.pickedPackageCount !== null && item.pickedPackageCount !== undefined && item.pickedPackageCount !== '' const picked = hasServerCount ? count(item.pickedPackageCount) : states[item.id] === ITEM_STATUS.PICKED ? planned : states[item.id] === ITEM_STATUS.PARTIAL ? count(partialQuantities[item.id]) : 0 return total + Math.max(0, planned - picked) }, 0) } export function summarizeInventoryResources(resources) { const list = Array.isArray(resources) ? resources : [] const locationCodes = [...new Set(list .map((resource) => String(resource?.locationCode || '').trim()) .filter(Boolean))] const availableCounts = list .map((resource) => resource?.availablePackageCount) .filter((value) => value !== null && value !== undefined && value !== '') .map(Number) .filter(Number.isFinite) return { location: locationCodes.join('、'), availablePackageCount: availableCounts.length ? availableCounts.reduce((total, value) => total + Math.max(0, value), 0) : null } } export function createItemStates(items) { return items.reduce((states, item) => { states[item.id] = ITEM_STATUS.PENDING return states }, {}) } export function getPickSummary(items, states, partialQuantities = {}) { const summary = { total: items.length, picked: 0, partial: 0, skipped: 0, pending: 0 } items.forEach((item) => { const status = states[item.id] || ITEM_STATUS.PENDING const quantity = Math.max(1, Number(item.qty || 1)) summary.total += quantity - 1 summary[status] += status === ITEM_STATUS.PARTIAL ? Math.max(0, Number(partialQuantities[item.id] || 0)) : quantity }) summary.processed = summary.picked + summary.partial + summary.skipped summary.progress = summary.total ? Math.round(summary.processed / summary.total * 100) : 0 summary.canSubmit = summary.total > 0 && ( summary.pending === 0 || summary.picked > 0 || summary.partial > 0 ) summary.buttonText = '提交拣货完成' return summary } export function submitStates(items, states) { const nextStates = { ...states } const autoSkippedIds = [] items.forEach((item) => { const status = nextStates[item.id] || ITEM_STATUS.PENDING if (status === ITEM_STATUS.PENDING) { nextStates[item.id] = ITEM_STATUS.SKIPPED autoSkippedIds.push(item.id) } }) return { states: nextStates, autoSkippedIds } } export function classifyScan(items, states, scanCode, scannedBoxCodes = {}) { const palletItem = items.find((entry) => isSealedPalletItem(entry) && entry.palletNo === scanCode) || null if (palletItem) { const status = states[palletItem.id] || ITEM_STATUS.PENDING if (scannedBoxCodes[scanCode] || status === ITEM_STATUS.PICKED) { return { type: 'processed', item: palletItem, status } } return { type: 'pallet', item: palletItem } } const matchingItems = items.filter((entry) => (entry.boxCodes || []).includes(scanCode)) const item = matchingItems.find((entry) => { const status = states[entry.id] || ITEM_STATUS.PENDING return status !== ITEM_STATUS.PICKED && status !== ITEM_STATUS.SKIPPED }) || matchingItems[0] || null if (!item) return { type: 'unknown', item: null } if (isSealedPalletItem(item)) { const status = states[item.id] || ITEM_STATUS.PENDING if (scannedBoxCodes[scanCode] || status === ITEM_STATUS.PICKED) { return { type: 'processed', item, status } } return { type: 'pallet', item, matchedBy: 'carton' } } if (scannedBoxCodes[scanCode]) return { type: 'processed', item, status: states[item.id] || ITEM_STATUS.PARTIAL } const status = states[item.id] || ITEM_STATUS.PENDING if (status !== ITEM_STATUS.PICKED && status !== ITEM_STATUS.SKIPPED) return { type: 'pending', item } return { type: 'processed', item, status } }