This commit is contained in:
2026-11-02 17:15:37 +08:00
parent 7704532e8f
commit f8408b8076
26 changed files with 595 additions and 52 deletions
@@ -21,6 +21,7 @@ import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/compone
import RowActions, { type RowActionItem } from '@/shared/components/RowActions.vue'
import { changeContainerReceivingWarehouse, convertLastMileContainerToInboundPlans, createManualInboundPlan, getContainerInboundPlanPreview, getContainerPlanProgress, getLastMileOrderManualNodes, listContainerPlanProgress, listLastMileOrders, reviewDirectRelabelOrder, reviewReturnOrder, transferLastMileOrders, updateLastMileOrderPalletMerge, updateLastMileOrderHold, updateLastMileOrderManualNodes, type ContainerInboundPlanPreviewDto, type ContainerInboundPlanPreviewGroupDto, type ContainerInboundPlanPreviewOrderDto, type ContainerPlanProgressDetailDto, type ContainerPlanProgressSummaryDto, type ManualNodeCode, type LastMileOrderQuery } from '../api/lastMileOrders'
import { businessOrderLink } from '../businessOrderLink'
import { createExceptionReturnInboundPlan } from '../api/lastMileOrders'
import { getReturnOrderStatistics, type ReturnOrderStatistics } from '../api/lastMileOrders'
import OrderReferenceText from '../components/OrderReferenceText.vue'
import { reviewCargoDiscard } from '../api/lastMileOrders'
@@ -111,6 +112,8 @@ const inboundPlanSourcePalletCount = ref<number | null>(null)
const inboundPlanPreviewLoading = ref(false)
const inboundPlanPreviewError = ref('')
const inboundPlanIsDropship = computed(() => actionTarget.value?.orderTypeCode === 'ONE_CLICK_DROPSHIP')
const inboundPlanIsExceptionReturn = computed(() => actionTarget.value?.orderTypeCode === 'DELIVERY_RETURN')
const exceptionReturnArrival = ref('')
const inboundPlanArrival = ref<ContainerArrivalTime | null>(null)
const inboundPlanArrivalLoading = ref(false)
const inboundPlanArrivalError = ref('')
@@ -119,7 +122,7 @@ const inboundPlanArrivalProjection = computed(() => {
const arrival = inboundPlanArrival.value
return projectWarehouseArrival(arrival ? { ...arrival, plannedAt: arrival.estimatedWarehouseArrivalAt } : undefined)
})
const inboundPlanSubmitDisabled = computed(() => inboundPlanArrivalLoading.value || !inboundPlanArrivalProjection.value.date
const inboundPlanSubmitDisabled = computed(() => inboundPlanArrivalLoading.value || (inboundPlanIsExceptionReturn.value ? !exceptionReturnArrival.value : !inboundPlanArrivalProjection.value.date)
|| (inboundPlanIsDropship.value
&& (inboundPlanPreviewLoading.value || inboundPlanSourcePalletCount.value == null
|| inboundPlanSourcePalletCount.value <= 0)))
@@ -646,6 +649,7 @@ function openInboundPlan(source?: object) {
actionError.value = null
inboundPlanArrival.value = null
inboundPlanArrivalError.value = ''
exceptionReturnArrival.value = target.orderTypeCode === 'DELIVERY_RETURN' ? target.estimatedWarehouseAt || '' : ''
inboundPlanOpen.value = true
void refreshInboundPlanArrival(target)
void refreshManualPalletMinimum(target)
@@ -653,6 +657,7 @@ function openInboundPlan(source?: object) {
/** 每次打开重新读取柜级权威日期,避免使用列表旧值或在页面推算默认日期。 */
async function refreshInboundPlanArrival(target: LastMileOrderListItem) {
const request = ++inboundPlanArrivalRequest
if (target.orderTypeCode === 'DELIVERY_RETURN') { inboundPlanArrivalLoading.value = false; return }
inboundPlanArrivalLoading.value = true
try {
if (!target.sourceContainerBookingId) {
@@ -676,6 +681,7 @@ async function refreshInboundPlanArrival(target: LastMileOrderListItem) {
}
}
async function refreshManualPalletMinimum(target: LastMileOrderListItem) {
if (target.orderTypeCode === 'DELIVERY_RETURN') return
if (!target.sourceContainerBookingId) return
inboundPlanPreviewLoading.value = true
inboundPlanPreviewError.value = ''
@@ -710,23 +716,26 @@ async function submitInboundPlan() {
ElMessage.warning('一件代发必须按头程已封托托盘数整体生成入库计划')
return
}
if (!row || !row.overseasWarehouseId || inboundPlanArrivalLoading.value || !inboundPlanArrivalProjection.value.date || !frozenInboundCode || frozenInboundCode === '—' || inboundPlanForm.palletCount < inboundPlanMinimumPalletCount.value) {
ElMessage.warning('当前柜须已分配接柜仓、具备预计到仓时间,并完整填写托数')
if (!row || !row.overseasWarehouseId || inboundPlanSubmitDisabled.value || (!inboundPlanIsExceptionReturn.value && (!frozenInboundCode || frozenInboundCode === '—')) || inboundPlanForm.palletCount < inboundPlanMinimumPalletCount.value) {
ElMessage.warning(inboundPlanIsExceptionReturn.value ? '请选择预计到仓时间,并确认退回仓和计划托数' : '当前柜须已分配接柜仓、具备预计到仓时间,并完整填写托数')
return
}
actionLoading.value = true; actionError.value = null
try {
const result = await createManualInboundPlan({
const payload = {
customerId: row.customerId, orderId: row.id, expectedOrderVersion: row.version,
palletCount: inboundPlanForm.palletCount,
palletType: inboundPlanForm.palletType, assigned: inboundPlanForm.assigned,
mergeRequired: inboundPlanForm.mergeRequired, urgent: inboundPlanForm.urgent,
remark: inboundPlanForm.remark.trim() || undefined,
}, idempotencyKey('manual-inbound-plan'))
}
const result = inboundPlanIsExceptionReturn.value
? await createExceptionReturnInboundPlan(payload, exceptionReturnArrival.value, idempotencyKey('exception-return-plan'))
: await createManualInboundPlan(payload, idempotencyKey('manual-inbound-plan'))
const palletCodesReady = result.pallets?.length === result.palletCount
&& result.pallets.every((pallet) => Boolean(pallet.warehousePalletId && pallet.palletNo))
inboundPlanOpen.value = false; selectedRows.value = []
ElMessage.success(palletCodesReady
ElMessage.success(inboundPlanIsExceptionReturn.value ? '入库计划已生成,请在异常退回验收中完成收货' : palletCodesReady
? '入库计划和托盘码已自动生成'
: '入库计划已保存,整柜计划齐全后将自动生成托盘码')
await load()
@@ -1332,15 +1341,17 @@ onMounted(() => { void load() })
<el-alert v-if="inboundPlanPreviewError" :title="inboundPlanPreviewError" type="error" :closable="false" show-icon />
<el-alert v-if="inboundPlanArrivalError" :title="inboundPlanArrivalError" type="error" :closable="false" show-icon />
<el-form class="inbound-plan-form command-form density-form density-dialog-sections" label-position="left" label-width="110px" :disabled="actionLoading">
<section class="density-dialog-section" data-tone="source" aria-label="入库来源"><h3 class="density-dialog-section__title">来源摘要 <InlineHelp title="入库来源说明">优先使用整柜人工维护的预计到仓时间;未维护时,按到港当地日期加两个自然日默认推算。首个入库计划生成后冻结日期,同柜计划保持一致。接柜仓按柜统一;如需变更,请在柜子工作台执行“变更接柜仓”。</InlineHelp></h3>
<section class="density-dialog-section" data-tone="source" aria-label="入库来源"><h3 class="density-dialog-section__title">来源摘要 <InlineHelp title="入库来源说明">{{ inboundPlanIsExceptionReturn ? '异常退回按本次退回安排填写预计到仓时间,计划关联当前尾程订单,收货时从原出库来源恢复库存。' : '优先使用整柜人工维护的预计到仓时间;未维护时,按到港当地日期加两个自然日默认推算。首个入库计划生成后冻结日期,同柜计划保持一致。接柜仓按柜统一;如需变更,请在柜子工作台执行“变更接柜仓”。' }}</InlineHelp></h3>
<table class="density-table last-mile-dialog-facts inbound-plan-source"><colgroup><col class="inbound-plan-source__label" /><col /><col class="inbound-plan-source__label" /><col /></colgroup><tbody>
<tr><th scope="row">预计到仓时间</th><td colspan="3"><div class="inbound-plan-arrival">{{ inboundPlanExpectedArrival }} <el-button v-if="access.can('platform:firstleg:terminal-drayage:list') && actionTarget?.containerNo" link type="primary" @click="router.push({ name: 'overseas-container-workbench', query: { keyword: actionTarget.containerNo } })">前往货柜维护预计到仓</el-button></div></td></tr>
<tr><th scope="row">计划入库仓</th><td>{{ inboundPlanWarehouseLabel || '—' }}</td><th scope="row">入仓码</th><td>{{ inboundPlanForm.inboundCode || '—' }}</td></tr>
<tr v-if="inboundPlanIsExceptionReturn"><th scope="row">尾程单号</th><td>{{ actionTarget?.orderNo }}</td><th scope="row">来源尾程单号</th><td>{{ actionTarget?.sourceOrderNo || '—' }}</td></tr>
<tr v-if="inboundPlanIsExceptionReturn"><th scope="row">预计到仓时间 <span class="el-form-item__required">*</span></th><td colspan="3"><el-date-picker v-model="exceptionReturnArrival" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择本次退回预计到仓时间" clearable /></td></tr>
<tr v-else><th scope="row">预计到仓时间</th><td colspan="3"><div class="inbound-plan-arrival">{{ inboundPlanExpectedArrival }} <el-button v-if="access.can('platform:firstleg:terminal-drayage:list') && actionTarget?.containerNo" link type="primary" @click="router.push({ name: 'overseas-container-workbench', query: { keyword: actionTarget.containerNo } })">前往货柜维护预计到仓</el-button></div></td></tr>
<tr><th scope="row">计划入库仓</th><td>{{ inboundPlanWarehouseLabel || '—' }}</td><th scope="row">入仓码</th><td>{{ inboundPlanIsExceptionReturn ? actionTarget?.orderNo : inboundPlanForm.inboundCode || '—' }}</td></tr>
</tbody></table>
</section>
<section class="density-dialog-section" aria-label="入库计划登记"><h3 class="density-dialog-section__title">计划设置</h3>
<table class="density-table density-table--editable last-mile-command-table"><tbody>
<tr><td><el-form-item label="托数" required><div class="inbound-plan-pallet-field"><span v-if="inboundPlanIsDropship" class="frozen-pallet-count">{{ inboundPlanForm.palletCount }}</span><el-input-number v-else v-model="inboundPlanForm.palletCount" :min="inboundPlanMinimumPalletCount" :max="999" controls-position="right" /><span class="inbound-plan-pallet-note density-dialog-note" v-if="inboundPlanIsDropship">一件代发固定按头程已封托托盘数整体入库,不可增减。</span><span class="inbound-plan-pallet-note density-dialog-note" v-else>按柜级预览至少 {{ inboundPlanMinimumPalletCount }} 托,可增加,不能减少。</span></div></el-form-item></td><td><el-form-item label="托盘类型"><el-select v-model="inboundPlanForm.palletType" placeholder="请选择托盘类型"><el-option label="标准托盘" value="STANDARD" /><el-option label="木托盘" value="WOOD" /><el-option label="纸托盘" value="PAPER" /></el-select></el-form-item></td></tr>
<tr><td><el-form-item label="托数" required><div class="inbound-plan-pallet-field"><span v-if="inboundPlanIsDropship" class="frozen-pallet-count">{{ inboundPlanForm.palletCount }}</span><el-input-number v-else v-model="inboundPlanForm.palletCount" :min="inboundPlanMinimumPalletCount" :max="999" controls-position="right" /><span class="inbound-plan-pallet-note density-dialog-note" v-if="inboundPlanIsDropship">一件代发固定按头程已封托托盘数整体入库,不可增减。</span><span class="inbound-plan-pallet-note density-dialog-note" v-else>{{ inboundPlanIsExceptionReturn ? '按本次退回货物安排计划托数。' : `按柜级预览至少 ${inboundPlanMinimumPalletCount} 托,可增加,不能减少。` }}</span></div></el-form-item></td><td><el-form-item label="托盘类型"><el-select v-model="inboundPlanForm.palletType" placeholder="请选择托盘类型"><el-option label="标准托盘" value="STANDARD" /><el-option label="木托盘" value="WOOD" /><el-option label="纸托盘" value="PAPER" /></el-select></el-form-item></td></tr>
<tr><td><el-form-item label="是否外配"><el-switch v-model="inboundPlanForm.assigned" inline-prompt active-text="是" inactive-text="否" /></el-form-item></td><td><el-form-item label="允许合托"><el-switch v-model="inboundPlanForm.mergeRequired" inline-prompt active-text="是" inactive-text="否" /><InlineHelp title="合托提示说明">仅作为现场可选提示,不强制工人合托。</InlineHelp></el-form-item></td></tr>
<tr><td colspan="2"><el-form-item label="是否加急"><el-switch v-model="inboundPlanForm.urgent" inline-prompt active-text="是" inactive-text="否" /></el-form-item></td></tr>
<tr><td colspan="2"><el-form-item label="备注"><el-input v-model="inboundPlanForm.remark" type="textarea" :rows="3" maxlength="1000" show-word-limit /></el-form-item></td></tr>