fix: lock dropship inbound plan pallet count
This commit is contained in:
@@ -287,6 +287,40 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
expect(payload).not.toHaveProperty('plannedWarehouseName')
|
||||
})
|
||||
|
||||
it('locks one-click dropship manual plans to the frozen source pallet count', async () => {
|
||||
vi.mocked(getContainerInboundPlanPreview).mockResolvedValue({
|
||||
...preview,
|
||||
sourcePalletCount: 1,
|
||||
groups: [{
|
||||
...preview.groups[0], destinationType: 'ONE_CLICK_DROPSHIP', sourcePalletCount: 1,
|
||||
orders: [{
|
||||
...preview.groups[0].orders[0], orderType: 'ONE_CLICK_DROPSHIP',
|
||||
destinationType: 'ONE_CLICK_DROPSHIP', sourcePalletCount: 1,
|
||||
targetPalletCount: 1, supplementPalletCount: 0,
|
||||
}],
|
||||
}],
|
||||
} as never)
|
||||
const wrapper = await openPending()
|
||||
await selectRows(wrapper, [selectedRow({ orderTypeCode: 'ONE_CLICK_DROPSHIP', pallets: 2 })])
|
||||
await button(wrapper, '生成入库计划')!.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
const palletInput = wrapper.find('.command-form').findComponent({ name: 'ElInputNumber' })
|
||||
expect(palletInput.props('disabled')).toBe(true)
|
||||
expect(palletInput.props('modelValue')).toBe(1)
|
||||
expect(document.body.textContent).toContain('固定按头程已封托托盘数整体入库')
|
||||
|
||||
wrapper.find('.command-form').findComponent({ name: 'ElDatePicker' })
|
||||
.vm.$emit('update:modelValue', '2026-09-08')
|
||||
await wrapper.vm.$nextTick()
|
||||
;([...document.body.querySelectorAll('button')]
|
||||
.find((item) => item.textContent?.includes('保存并生成入库单')) as HTMLButtonElement).click()
|
||||
await flushPromises()
|
||||
|
||||
expect(createManualInboundPlan).toHaveBeenCalledWith(expect.objectContaining({ palletCount: 1 }),
|
||||
expect.stringContaining('manual-inbound-plan-'))
|
||||
})
|
||||
|
||||
it('opens a cabinet selector without requiring order selection and loads server summaries only then', async () => {
|
||||
const wrapper = await openPending()
|
||||
await openContainerDialog(wrapper)
|
||||
|
||||
@@ -49,6 +49,13 @@ const inboundPlanWarehouseLabel = computed(() => [actionTarget.value?.overseasWa
|
||||
.filter((value) => value && value !== '—')
|
||||
.join(' · '))
|
||||
const inboundPlanMinimumPalletCount = ref(1)
|
||||
const inboundPlanSourcePalletCount = ref<number | null>(null)
|
||||
const inboundPlanPreviewLoading = ref(false)
|
||||
const inboundPlanPreviewError = ref('')
|
||||
const inboundPlanIsDropship = computed(() => actionTarget.value?.orderTypeCode === 'ONE_CLICK_DROPSHIP')
|
||||
const inboundPlanSubmitDisabled = computed(() => inboundPlanIsDropship.value
|
||||
&& (inboundPlanPreviewLoading.value || inboundPlanSourcePalletCount.value == null
|
||||
|| inboundPlanSourcePalletCount.value <= 0))
|
||||
const inboundPlanForm = reactive({ plannedArrivalDate: '', inboundCode: '', palletCount: 1, palletType: 'WOOD' as 'STANDARD' | 'WOOD' | 'PAPER', assigned: false, mergeRequired: false, urgent: false, remark: '' })
|
||||
const holdOpen = ref(false)
|
||||
const holdForm = reactive({ held: true, chargeable: true, reason: '' })
|
||||
@@ -481,6 +488,8 @@ function openInboundPlan(source?: object) {
|
||||
const volumeMinimum = typeof target.volume === 'number' && target.volume > 0 ? Math.ceil(target.volume / 2) : 1
|
||||
inboundPlanMinimumPalletCount.value = Math.max(1, volumeMinimum,
|
||||
typeof target.pallets === 'number' ? target.pallets : 0)
|
||||
inboundPlanSourcePalletCount.value = null
|
||||
inboundPlanPreviewError.value = ''
|
||||
Object.assign(inboundPlanForm, { plannedArrivalDate: '', inboundCode: target.inboundCode === '—' ? '' : target.inboundCode, palletCount: inboundPlanMinimumPalletCount.value, palletType: 'WOOD', assigned: false, mergeRequired: false, urgent: false, remark: '' })
|
||||
actionError.value = null
|
||||
inboundPlanOpen.value = true
|
||||
@@ -488,20 +497,39 @@ function openInboundPlan(source?: object) {
|
||||
}
|
||||
async function refreshManualPalletMinimum(target: LastMileOrderListItem) {
|
||||
if (!target.sourceContainerBookingId) return
|
||||
inboundPlanPreviewLoading.value = true
|
||||
inboundPlanPreviewError.value = ''
|
||||
try {
|
||||
const preview = await getContainerInboundPlanPreview(target.sourceContainerBookingId)
|
||||
if (actionTarget.value?.id !== target.id || !inboundPlanOpen.value) return
|
||||
const order = preview.groups.flatMap((group) => group.orders).find((item) => item.orderId === target.id)
|
||||
if (target.orderTypeCode === 'ONE_CLICK_DROPSHIP') {
|
||||
const sourcePalletCount = Number(order?.sourcePalletCount || 0)
|
||||
if (sourcePalletCount <= 0) throw new Error('一件代发订单缺少头程封托托盘快照')
|
||||
inboundPlanSourcePalletCount.value = sourcePalletCount
|
||||
inboundPlanMinimumPalletCount.value = sourcePalletCount
|
||||
inboundPlanForm.palletCount = sourcePalletCount
|
||||
return
|
||||
}
|
||||
const suggested = Number(order?.suggestedPalletCount || 0)
|
||||
inboundPlanMinimumPalletCount.value = Math.max(inboundPlanMinimumPalletCount.value, suggested)
|
||||
inboundPlanForm.palletCount = Math.max(inboundPlanForm.palletCount, inboundPlanMinimumPalletCount.value)
|
||||
} catch {
|
||||
// The backend repeats the same calculation and remains authoritative on submit.
|
||||
} catch (error) {
|
||||
if (actionTarget.value?.id === target.id && inboundPlanOpen.value) {
|
||||
inboundPlanPreviewError.value = getApiErrorDetails(error, '柜级托盘预览加载失败,请重试').message
|
||||
}
|
||||
} finally {
|
||||
if (actionTarget.value?.id === target.id) inboundPlanPreviewLoading.value = false
|
||||
}
|
||||
}
|
||||
async function submitInboundPlan() {
|
||||
const row = actionTarget.value
|
||||
const frozenInboundCode = String(row?.inboundCode || '').trim()
|
||||
if (inboundPlanIsDropship.value && (inboundPlanSourcePalletCount.value == null
|
||||
|| inboundPlanForm.palletCount !== inboundPlanSourcePalletCount.value)) {
|
||||
ElMessage.warning('一件代发必须按头程已封托托盘数整体生成入库计划')
|
||||
return
|
||||
}
|
||||
if (!row || !row.overseasWarehouseId || !inboundPlanForm.plannedArrivalDate || !frozenInboundCode || frozenInboundCode === '—' || inboundPlanForm.palletCount < inboundPlanMinimumPalletCount.value) {
|
||||
ElMessage.warning('当前柜须已分配接柜仓,并完整填写预计到仓日期和托数')
|
||||
return
|
||||
@@ -719,6 +747,7 @@ function blockerText(blockers: string[]) {
|
||||
PLAN_ALREADY_STARTED: '已有计划已开始作业,不能再补生成托盘',
|
||||
PLAN_PALLET_BINDING_INCOMPLETE: '已有计划只绑定了部分真实托盘,请联系管理员处理',
|
||||
DROPSHIP_PALLET_SNAPSHOT_MISSING: '缺少一件代发托盘快照',
|
||||
DROPSHIP_PALLET_COUNT_MISMATCH: '一件代发计划托数与头程已封托托盘数不一致,请重新走单',
|
||||
PALLET_COUNT_NOT_READY: '计划托数暂未就绪',
|
||||
}
|
||||
return [...new Set(blockers.map((code) => labels[code] || '计划数据暂未就绪,请刷新或联系管理员'))].join(';') || '—'
|
||||
@@ -926,10 +955,11 @@ onMounted(() => { void load() })
|
||||
</LoadErrorState>
|
||||
</el-form>
|
||||
</AppDialog>
|
||||
<AppDialog v-model="inboundPlanOpen" title="入库计划" width="min(720px, calc(100vw - 28px))" confirm-text="保存并生成入库单" :confirm-loading="actionLoading" @confirm="submitInboundPlan">
|
||||
<AppDialog v-model="inboundPlanOpen" title="入库计划" width="min(720px, calc(100vw - 28px))" confirm-text="保存并生成入库单" :loading="inboundPlanPreviewLoading" :confirm-loading="actionLoading" :confirm-disabled="inboundPlanSubmitDisabled" @confirm="submitInboundPlan">
|
||||
<el-alert title="保存后生成入库单;清关完成后入库计划不可更改。" type="info" :closable="false" show-icon />
|
||||
<el-alert v-if="inboundPlanPreviewError" class="container-convert-alert" :title="inboundPlanPreviewError" type="error" :closable="false" show-icon />
|
||||
<el-form class="command-form" label-position="top">
|
||||
<div class="command-form__grid"><el-form-item label="计划入库仓"><el-input :model-value="inboundPlanWarehouseLabel" disabled placeholder="当前柜接柜仓" /><small>接柜仓按柜统一;如需变更,请在柜子工作台执行“变更接柜仓”。</small></el-form-item><el-form-item label="预计到仓日期" required><el-date-picker v-model="inboundPlanForm.plannedArrivalDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择日期" /></el-form-item><el-form-item label="入仓码"><el-input :model-value="inboundPlanForm.inboundCode" disabled placeholder="订单冻结入仓码" /></el-form-item><el-form-item label="托数" required><el-input-number v-model="inboundPlanForm.palletCount" :min="inboundPlanMinimumPalletCount" :max="999" controls-position="right" placeholder="请输入托数" /><small>按柜级预览至少 {{ inboundPlanMinimumPalletCount }} 托,可增加,不能减少。</small></el-form-item><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><el-form-item label="是否分配"><el-switch v-model="inboundPlanForm.assigned" inline-prompt active-text="是" inactive-text="否" /></el-form-item><el-form-item label="允许合托"><el-switch v-model="inboundPlanForm.mergeRequired" inline-prompt active-text="是" inactive-text="否" /><small>仅作为现场可选提示,不强制工人合托。</small></el-form-item><el-form-item label="是否加急"><el-switch v-model="inboundPlanForm.urgent" inline-prompt active-text="是" inactive-text="否" /></el-form-item></div>
|
||||
<div class="command-form__grid"><el-form-item label="计划入库仓"><el-input :model-value="inboundPlanWarehouseLabel" disabled placeholder="当前柜接柜仓" /><small>接柜仓按柜统一;如需变更,请在柜子工作台执行“变更接柜仓”。</small></el-form-item><el-form-item label="预计到仓日期" required><el-date-picker v-model="inboundPlanForm.plannedArrivalDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择日期" /></el-form-item><el-form-item label="入仓码"><el-input :model-value="inboundPlanForm.inboundCode" disabled placeholder="订单冻结入仓码" /></el-form-item><el-form-item label="托数" required><el-input-number v-model="inboundPlanForm.palletCount" :min="inboundPlanMinimumPalletCount" :max="inboundPlanIsDropship ? inboundPlanMinimumPalletCount : 999" :disabled="inboundPlanIsDropship" controls-position="right" placeholder="请输入托数" /><small v-if="inboundPlanIsDropship">一件代发固定按头程已封托托盘数整体入库,不可增减。</small><small v-else>按柜级预览至少 {{ inboundPlanMinimumPalletCount }} 托,可增加,不能减少。</small></el-form-item><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><el-form-item label="是否分配"><el-switch v-model="inboundPlanForm.assigned" inline-prompt active-text="是" inactive-text="否" /></el-form-item><el-form-item label="允许合托"><el-switch v-model="inboundPlanForm.mergeRequired" inline-prompt active-text="是" inactive-text="否" /><small>仅作为现场可选提示,不强制工人合托。</small></el-form-item><el-form-item label="是否加急"><el-switch v-model="inboundPlanForm.urgent" inline-prompt active-text="是" inactive-text="否" /></el-form-item></div>
|
||||
<el-form-item label="备注"><el-input v-model="inboundPlanForm.remark" type="textarea" :rows="3" maxlength="1000" show-word-limit placeholder="请输入备注(选填)" /></el-form-item>
|
||||
</el-form>
|
||||
</AppDialog>
|
||||
|
||||
Reference in New Issue
Block a user