Files
admin-web/src/modules/appointment-calendar/WorkExistingDispatchForm.vue
T

62 lines
5.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import InlineHelp from '@/shared/components/InlineHelp.vue'
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { getDispatch, stageLabel, type DispatchDetail } from '@/modules/overseas-warehouse'
import { ApiResponseError, getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import WorkSharedTripForm from './WorkSharedTripForm.vue'
import { resolveBatchWork } from './workApi'
const props = defineProps<{ dispatches: Array<{ batchKey: string; dispatchNo: string; customerId?: string }>; canWrite: boolean }>()
const emit = defineEmits<{ updated: []; external: [path: string]; ready: [valid: boolean] }>()
const access = useAccessPreviewStore(), rows = ref<DispatchDetail[]>([]), loading = ref(false), error = ref<ApiErrorDetails | null>(null)
const addingNo = ref(''), addingBusy = ref(false)
const readable = computed(() => access.can('platform:overseas-warehouse:dispatch:list') && (!props.dispatches.some(row => row.batchKey.startsWith('DISPATCH:')) || access.can('platform:appointment:list')))
function path(no: string) { return `/overseas-warehouse/dispatches/${encodeURIComponent(no)}` }
function canEdit(row: DispatchDetail) { return props.canWrite && readable.value && access.can('platform:overseas-warehouse:dispatch:edit') && row.status === 'ACTIVE' && row.actions.EDIT?.allowed === true }
function canAdd(row: DispatchDetail) { return props.canWrite && readable.value && access.can('platform:overseas-warehouse:dispatch:edit') && row.status === 'ACTIVE' && row.stage === 'BEFORE_LOADING' && row.actions.ADD_CARGO?.allowed === true }
let generation = 0
async function load() {
const current = ++generation; emit('ready', false); rows.value = []; error.value = null
if (!readable.value) return
loading.value = true
try {
const references = props.dispatches.map(row => ({ ...row }))
const details = await Promise.all([...new Set(references.map(row => row.dispatchNo))].map(async no => {
const row = await getDispatch(no)
if (row.dispatchNo !== no || row.status === 'CANCELLED') throw new ApiResponseError('排车单与工作批次关联不一致,请刷新核对', 'WORK_DISPATCH_MISMATCH')
for (const reference of references.filter(value => value.dispatchNo === no)) {
let matches = false
if (/^FBA_APPOINTMENT:\d+$/.test(reference.batchKey)) {
matches = Boolean(reference.customerId && row.dispatchInfo.appointmentBatches.some(batch => batch.batchKey === reference.batchKey && batch.customerId === reference.customerId))
} else if (/^DISPATCH:\d+$/.test(reference.batchKey)) {
// The work domain resolves its opaque batch identity; Detail has no internal dispatch ID.
const resolution = await resolveBatchWork(reference.batchKey, reference.customerId)
matches = resolution.batchKey === reference.batchKey && (resolution.detail
? resolution.detail.item.batchKey === reference.batchKey && resolution.detail.item.batchNo === no && (!reference.customerId || resolution.customerId === reference.customerId)
: resolution.candidates.length > 0 && resolution.candidates.every(candidate => candidate.batchNo === no) && (!reference.customerId || resolution.candidates.some(candidate => candidate.customerId === reference.customerId)))
}
if (!matches) throw new ApiResponseError('排车单与工作批次关联不一致,请刷新核对', 'WORK_DISPATCH_MISMATCH')
}
return row
}))
if (current !== generation) return
rows.value = details; emit('ready', details.length > 0)
} catch (cause) { if (current === generation) error.value = getApiErrorDetails(cause, '原排车单读取失败') }
finally { if (current === generation) loading.value = false }
}
function add(row: DispatchDetail) { if (canAdd(row) && !loading.value && !error.value && !addingBusy.value) addingNo.value = row.dispatchNo }
function updated() { emit('updated'); void load() }
watch(() => props.dispatches, load, { immediate: true, deep: true })
onBeforeUnmount(() => { generation++ })
</script>
<template>
<section aria-label="沿用原整车排车"><h3>沿用原整车排车 <InlineHelp title="排车说明"><p>在原排车单维护车辆、司机和货物;一张排车单就是一趟车。</p></InlineHelp></h3>
<p v-if="!readable">查看原排车单及批次关联需要相应列表权限。</p><p v-if="loading">正在核对原排车单与批次…</p>
<LoadErrorState v-if="error" title="原排车单读取失败" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
<table v-if="!loading && !error && readable" class="density-table"><thead><tr><th>操作</th><th>排车单号</th><th>车牌号</th><th>箱数</th><th>阶段</th></tr></thead><tbody><tr v-for="row in rows" :key="row.dispatchNo"><td><el-button v-if="canEdit(row)" :disabled="addingBusy" @click="emit('external', path(row.dispatchNo))">继续维护本车</el-button><el-button v-if="canAdd(row)" :disabled="addingBusy" @click="add(row)">为本车添加同仓货物</el-button><el-button @click="emit('external', path(row.dispatchNo))">查看原排车单</el-button></td><td>{{ row.dispatchNo }}</td><td>{{ row.vehicleInfo.vehicle?.plate || '—' }}</td><td>{{ row.counts.packageCount }}</td><td>{{ stageLabel(row.stage) }}</td></tr></tbody></table>
<WorkSharedTripForm v-if="addingNo" :key="addingNo" :dispatch-no="addingNo" :can-write="canWrite" @busy="addingBusy = $event" @updated="updated" @external="value => emit('external', value)" />
</section>
</template>