54 lines
7.1 KiB
Vue
54 lines
7.1 KiB
Vue
<script setup lang="ts">
|
||
import { computed, onMounted, ref } from 'vue'
|
||
import { ElMessage } from 'element-plus'
|
||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||
import { getContainerPlanProgress, getContainerInboundPlanPreview, convertLastMileContainerToInboundPlans, type ContainerPlanProgressDetailDto, type ContainerInboundPlanPreviewDto, type ConvertContainerInboundPlanPayload } from '@/modules/overseas-warehouse'
|
||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||
import { rejectedCommand, useWorkDraft } from './useWorkDraft'
|
||
const props = defineProps<{ containerBookingId: string; canWrite: boolean }>()
|
||
const emit = defineEmits<{ external: [path: string]; updated: [] }>()
|
||
const access = useAccessPreviewStore(), detail = ref<ContainerPlanProgressDetailDto | null>(null), loading = ref(false), error = ref<ApiErrorDetails | null>(null)
|
||
const readable = computed(() => access.can('platform:overseas-warehouse:last-mile-order:list'))
|
||
const writable = computed(() => props.canWrite && readable.value && access.can('platform:overseas-warehouse:inbound-order:create'))
|
||
const preview = ref<ContainerInboundPlanPreviewDto | null>(null), previewLoading = ref(false), saving = ref(false), previewError = ref<ApiErrorDetails | null>(null), saveError = ref<ApiErrorDetails | null>(null)
|
||
const pending = ref<{ body: ConvertContainerInboundPlanPayload; key: string } | null>(null)
|
||
useWorkDraft(computed(() => false), computed(() => saving.value || !!pending.value))
|
||
async function loadPreview() {
|
||
if (!writable.value || saving.value || pending.value) return
|
||
previewLoading.value = true; previewError.value = null
|
||
try { preview.value = await getContainerInboundPlanPreview(props.containerBookingId) }
|
||
catch (cause) { previewError.value = getApiErrorDetails(cause, '本柜入库计划预览失败') }
|
||
finally { previewLoading.value = false }
|
||
}
|
||
async function createPlans() {
|
||
if (!writable.value || saving.value || previewLoading.value || previewError.value || !preview.value) return
|
||
if (!pending.value) {
|
||
if (!preview.value.ready || (!preview.value.resourceRetry && preview.value.pendingOrderCount <= 0)) return
|
||
const snapshot = preview.value.resourceRetry || preview.value
|
||
pending.value = { body: { sourceContainerBookingId: props.containerBookingId, snapshotVersion: snapshot.snapshotVersion, snapshotHash: snapshot.snapshotHash }, key: preview.value.resourceRetry?.idempotencyKey || crypto.randomUUID() }
|
||
}
|
||
saving.value = true; saveError.value = null
|
||
try {
|
||
await convertLastMileContainerToInboundPlans(pending.value.body, pending.value.key)
|
||
pending.value = null; preview.value = null; ElMessage.success('本柜入库计划已确认,请查看原接仓记录'); emit('updated'); await load()
|
||
} catch (cause) {
|
||
saveError.value = getApiErrorDetails(cause, '入库计划生成结果待确认,请重试原请求')
|
||
if (rejectedCommand(cause)) { pending.value = null; preview.value = null }
|
||
} finally { saving.value = false }
|
||
}
|
||
async function load() { if (!readable.value) return; loading.value = true; error.value = null; try { detail.value = await getContainerPlanProgress(props.containerBookingId) } catch (cause) { error.value = getApiErrorDetails(cause, '本柜入仓计划读取失败') } finally { loading.value = false } }
|
||
onMounted(load)
|
||
</script>
|
||
<template>
|
||
<section aria-label="本柜接仓计划"><h3>选择本柜入仓计划</h3><p>实际收货与卸柜由仓库原作业流程登记;此处进入对应客户的原入库单查看。</p><p v-if="!readable">读取本柜计划需要尾程订单列表权限。</p>
|
||
<LoadErrorState v-if="error" title="本柜入仓计划读取失败" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
|
||
<template v-if="detail && !error"><p>{{ detail.summary.containerNo || '柜号待核实' }} · {{ detail.summary.overseasWarehouseName || '接仓仓库待核实' }}</p><p v-if="detail.summary.blockingReason">{{ detail.summary.blockingReason }}</p><table class="density-table"><thead><tr><th>操作</th><th>入库单号</th><th>入仓码 / 订单号</th></tr></thead><tbody><tr v-for="row in detail.orders" :key="`${row.customerId}:${row.orderId}`"><td><el-button :disabled="saving || !!pending || !row.planNo || !row.customerId || !row.planValid || !access.can('platform:overseas-warehouse:inbound-order:list')" @click="emit('external', `/overseas-warehouse/inbound-orders/${encodeURIComponent(row.planNo!)}?customerId=${encodeURIComponent(row.customerId)}`)">查看原接仓记录</el-button></td><td>{{ row.planNo || '尚未生成有效计划' }}</td><td>{{ row.inboundCode || row.orderNo || '—' }}</td></tr></tbody></table><p v-if="!detail.orders.length">本柜尚无可读取的入仓来源。</p></template>
|
||
<el-button v-if="writable && !preview && !pending" :loading="previewLoading" @click="loadPreview">核对本柜入库计划预览</el-button>
|
||
<LoadErrorState v-if="previewError" title="入库计划预览失败" :message="previewError.message" :loading="previewLoading" @retry="loadPreview"><template #details><ApiErrorMeta :details="previewError" /></template></LoadErrorState>
|
||
<div v-if="preview && !previewError"><h4>核对本柜计划与托盘</h4><p>{{ preview.containerNo || '柜号待核实' }} · {{ preview.overseasWarehouseName || '接仓仓库待核实' }} · {{ preview.pendingOrderCount }} 票待生成 · {{ preview.blockedOrderCount }} 票待处理</p><p>头程已有 {{ preview.sourcePalletCount }} 托;海外补充 {{ preview.supplementPalletCount }} 托;已保存计划 {{ preview.totalPlannedPalletCount }} 托。</p><table class="density-table"><thead><tr><th>客户</th><th>原票</th><th>目的地</th><th>箱数</th><th>目标托数</th><th>计划状态</th></tr></thead><tbody><tr v-for="row in preview.groups.flatMap(group => group.orders)" :key="`${row.customerId}:${row.orderId}`"><td>{{ row.customerName || '客户名称待核实' }}</td><td>{{ row.inboundCode || row.businessOrderNo || '—' }}</td><td>{{ row.destinationWarehouseName || row.fbaWarehouseCode || (row.destinationType === 'PRIVATE_ADDRESS' ? '客户地址' : '目的地待核实') }}</td><td>{{ row.packageCount }}</td><td>{{ row.targetPalletCount }}</td><td>{{ row.existingPlanNo || ({ READY: '可生成', RESOURCE_PENDING: '资源待确认', PLANNED: '已有计划', BLOCKED: '来源条件未就绪' }[row.readiness]) }}</td></tr></tbody></table><p v-if="!preview.ready">本柜尚不具备生成条件,请在原尾程订单核对阻塞来源。</p><el-button type="primary" :loading="saving" :disabled="!writable || (!pending && (!preview.ready || (!preview.resourceRetry && preview.pendingOrderCount <= 0)))" @click="createPlans">{{ pending ? '重试本次计划生成' : preview.resourceRetry ? '重试原资源确认' : '确认生成本柜计划' }}</el-button></div>
|
||
<LoadErrorState v-if="saveError" title="入库计划尚未确认" :message="saveError.message" :show-retry="false"><template #details><ApiErrorMeta :details="saveError" /></template></LoadErrorState>
|
||
</section>
|
||
</template>
|