统一作业列表与待办统计并更新废弃扫码

This commit is contained in:
2026-10-30 15:42:38 +08:00
parent 868f18d7df
commit fadef06191
15 changed files with 482 additions and 66 deletions
+70
View File
@@ -0,0 +1,70 @@
import { listReceivingCandidates } from './inbound.js'
import { assertReceivingWarehouse } from './receiving-adapter.js'
import { listWarehouseTransferCandidates } from './warehouse-transfers.js'
import { listReturnCountTasks } from './return-counting.js'
import { listPackingPlans } from './return-packing.js'
import { listDispatchTasks, listOutboundTasks, listCargoDiscardTasks } from './fulfillment.js'
import { listRelabelCandidates } from './relabel.js'
export const WORKBENCH_COUNT_KEYS = ['receiving', 'warehouseTransferList', 'warehouseTransferWork', 'returnCounting', 'returnPacking', 'dispatchPicking', 'outboundPicking', 'relabelReceive', 'relabelExecute', 'relabelReview']
const ready = (count, unit = '单') => ({ status: 'ready', count, unit })
const distinctCount = (rows, key) => {
if (!Array.isArray(rows)) throw new Error('待办列表返回结构无效')
const codes = rows.map((row) => String(row?.[key] || '').trim())
if (codes.some((code) => !code)) throw new Error('待办列表缺少业务编号')
return new Set(codes).size
}
export async function loadWorkbenchCounts({ profile, activeTransferNo = '' }) {
const sources = [
{ keys: ['receiving'], load: async () => {
const rows = await listReceivingCandidates()
rows.forEach((row) => assertReceivingWarehouse(row, profile, '待收货柜'))
if (rows.some((row) => !row.sourceContainerBookingId || !row.containerNo)) throw new Error('待收货柜缺少业务定位信息')
const keys = rows.map((row) => ({ key: `${row.sourceContainerBookingId}:${row.containerNo}` }))
return { receiving: ready(distinctCount(keys, 'key'), '柜') }
} },
{ keys: ['warehouseTransferList', 'warehouseTransferWork'], load: async () => {
const rows = (await listWarehouseTransferCandidates()).filter((row) => ['WAIT_PDA_RECEIVING', 'PARTIALLY_RECEIVED'].includes(row.status))
const active = rows.filter((row) => String(row.transferNo).trim() === String(activeTransferNo).trim())
return { warehouseTransferList: ready(distinctCount(rows, 'transferNo')), warehouseTransferWork: ready(distinctCount(active, 'transferNo'), '单进行中') }
} },
{ keys: ['returnCounting'], load: async () => {
const value = await listReturnCountTasks({ page: 1, size: 1 })
const total = value?.total
if ((typeof total !== 'number' && !(typeof total === 'string' && /^\d+$/.test(total))) || !Number.isSafeInteger(Number(total)) || Number(total) < 0) throw new Error('退货清点待办总数无效')
return { returnCounting: ready(Number(total), '托') }
} },
{ keys: ['returnPacking'], load: async () => ({ returnPacking: ready(distinctCount((await listPackingPlans()).filter((row) => row.status === 'READY'), 'planNo'), '单计划') }) },
{ keys: ['dispatchPicking'], load: async () => {
const rows = (await listDispatchTasks()).filter((row) => row.status !== 'CANCELLED' && ['BEFORE_LOADING', 'LOADING', 'AFTER_LOADING'].includes(row.stage))
return { dispatchPicking: ready(distinctCount(rows, 'dispatchNo'), '单未发车') }
} },
{ keys: ['outboundPicking'], load: async () => {
const [outbounds, discards] = await Promise.all([listOutboundTasks('PENDING'), listCargoDiscardTasks()])
return { outboundPicking: { ...ready(distinctCount(outbounds, 'taskNo')), discardCount: distinctCount(discards.filter((row) => row.status !== 'COMPLETED'), 'discardNo') } }
} },
{ keys: ['relabelReceive', 'relabelExecute', 'relabelReview'], load: async () => {
const rows = await listRelabelCandidates()
return {
relabelReceive: ready(distinctCount(rows.filter((row) => !row.taskStatus || row.taskStatus === 'AVAILABLE'), 'relabelNo'), '单待接'),
relabelExecute: ready(distinctCount(rows.filter((row) => row.taskStatus === 'PROCESSING' && row.claimedByCurrentOperator === true), 'relabelNo'), '单进行中'),
relabelReview: ready(distinctCount(rows.filter((row) => row.taskStatus === 'WAIT_REVIEW'), 'relabelNo'), '单待复核')
}
} }
]
const results = await Promise.allSettled(sources.map((source) => source.load()))
return Object.assign({}, ...results.map((result, index) => result.status === 'fulfilled' ? result.value : Object.fromEntries(sources[index].keys.map((key) => [key, { status: 'error', error: result.reason }]))))
}
export function workbenchBadge(key, value) {
if (!WORKBENCH_COUNT_KEYS.includes(key)) return { label: key.endsWith('Query') ? '扫码查询' : key === 'returnReceipt' ? '扫码签收' : '扫码作业', tone: 'is-static' }
if (!value) return { label: '加载中…', tone: 'is-loading' }
if (value.status === 'error') return { label: '获取失败', tone: 'is-error' }
const hasWork = value.count > 0 || value.discardCount > 0
let label = `${value.count} ${value.unit}`
if (key === 'warehouseTransferWork' && value.count === 0) label = '未开始'
if (key === 'outboundPicking' && value.discardCount > 0) label = `${value.count} 出库 · ${value.discardCount} 废弃`
return { label, tone: hasWork ? 'has-work' : 'is-empty' }
}