feat(admin): allow late container number entry

This commit is contained in:
2026-09-01 15:24:37 +08:00
parent b4c29e2226
commit 0b676ddb74
23 changed files with 155 additions and 71 deletions
@@ -61,20 +61,22 @@ describe('PreloadPlanDetailView prototype contract', () => {
expect(source).toContain(':confirm-loading="remarkSaving"')
})
it('keeps preload as a correction-only entry before PDA claim and does not imply that preload directly opens PDA', () => {
it('supports first entry and reasoned correction before PDA claim without blocking earlier flow', () => {
expect(source).toContain("{ required: true, message: '请输入集装箱号'")
expect(source).toContain("summary.status === 'EDITING'")
expect(source).toContain("summary?.status === 'EDITING'")
expect(source).toContain("(!pdaStatus || pdaStatus === 'PENDING')")
expect(source).not.toContain('summary?.containerNo && summary.status')
expect(source).toContain('PDA 已领取或作业已开始,真实集装箱号只读。')
expect(source).toContain("const editDialogTitle = computed(() => '纠正集装箱号 / 航次')")
expect(source).toContain("{ required: true, message: '请输入操作原因'")
expect(source).toContain("isContainerCorrection.value ? '纠正集装箱号 / 航次' : '补录集装箱号 / 航次'")
expect(source).toContain("isContainerCorrection.value && !String(value || '').trim() ? new Error('纠错时请输入操作原因')")
expect(source).toContain("String(value || '').trim().toUpperCase()")
expect(source).toContain('operationReason: editForm.operationReason.trim()')
expect(source).toContain('operationReason: editForm.operationReason.trim() || null')
expect(detailSource).toContain('v-if="canEditTransport"')
expect(detailSource).not.toContain("preloadSummary?.containerNo ? '修改集装箱号 / 航次' : '录入集装箱号'")
expect(detailSource).toContain("summary.containerNo || '待补录'")
expect(source).toContain('由服务端按真实条件创建唯一 PDA 任务')
expect(source).toContain('真实集装箱号未录入时不能确认')
expect(source).toContain('PDA 领取或作业开始后集装箱号只读')
expect(source).toContain('缺号不阻断此前流程')
expect(source).toContain('PDA 确认装柜完成前由服务端执行最终门禁')
expect(source).not.toContain('真实集装箱号未录入时不能确认')
})
it('uses a flat blocking note instead of repeated colored page alerts', () => {
@@ -51,17 +51,19 @@ const canEdit = computed(() => access.can('platform:firstleg:preload:edit'))
const canCorrectTransport = computed(() => {
const summary = detail.value?.summary
const pdaStatus = summary?.readiness.pdaTaskStatus
return Boolean(canEdit.value && summary?.containerNo && summary.status === 'EDITING'
return Boolean(canEdit.value && summary?.status === 'EDITING'
&& summary.readiness.preloadPushed && (!pdaStatus || pdaStatus === 'PENDING'))
})
const isContainerCorrection = computed(() => Boolean(detail.value?.summary.containerNo))
const transportCorrectionNote = computed(() => {
const summary = detail.value?.summary
if (!summary?.containerNo) return '真实集装箱号尚未录入;请返回货柜管理,通过“关联已放舱订舱”完成初次录入。'
if (!summary) return ''
if (summary.readiness.pdaTaskStatus && summary.readiness.pdaTaskStatus !== 'PENDING') return 'PDA 已领取或作业已开始,真实集装箱号只读。'
if (!canCorrectTransport.value) return '当前服务端阶段不允许纠正真实集装箱号。'
return '真实集装箱号仅可在 PDA 领取前纠错,保存时必须填写操作原因。'
if (!summary.containerNo) return '真实集装箱号待补录;可在预装详情首次录入,PDA 领取或作业开始后只读。'
return '真实集装箱号仅可在 PDA 领取前纠错,纠错保存时必须填写操作原因。'
})
const editDialogTitle = computed(() => '纠正集装箱号 / 航次')
const editDialogTitle = computed(() => isContainerCorrection.value ? '纠正集装箱号 / 航次' : '补录集装箱号 / 航次')
const editRules: FormRules = {
containerNo: [
{ required: true, message: '请输入集装箱号', trigger: 'blur' },
@@ -69,7 +71,7 @@ const editRules: FormRules = {
],
voyageNo: [{ max: 64, message: '航次不能超过 64 个字符', trigger: 'blur' }],
operationReason: [
{ required: true, message: '请输入操作原因', trigger: 'blur' },
{ validator: (_rule, value, callback) => callback(isContainerCorrection.value && !String(value || '').trim() ? new Error('纠错时请输入操作原因') : undefined), trigger: 'blur' },
{ max: 500, message: '操作原因不能超过 500 个字符', trigger: 'blur' },
],
}
@@ -135,6 +137,7 @@ function openEdit() {
async function saveTransport() {
if (!detail.value || !canCorrectTransport.value || !(await editFormRef.value?.validate().catch(() => false))) return
const correcting = isContainerCorrection.value
editForm.containerNo = normalizedContainerNo(editForm.containerNo)
editSaving.value = true
editError.value = null
@@ -142,11 +145,11 @@ async function saveTransport() {
detail.value = await updatePreloadTransport(planId.value, warehouseId.value, {
containerNo: editForm.containerNo,
voyageNo: editForm.voyageNo.trim() || null,
operationReason: editForm.operationReason.trim(),
operationReason: editForm.operationReason.trim() || null,
expectedVersion: detail.value.summary.version,
})
editOpen.value = false
ElMessage.success('运输信息已更新')
ElMessage.success(correcting ? '运输信息已纠正' : '真实集装箱号已补录')
} catch (cause) {
const details = getApiErrorDetails(cause, '运输信息保存失败,请刷新详情后重试')
if (details.code === 'A0409' || /版本|并发|已被其他人修改/.test(details.message)) {
@@ -247,9 +250,9 @@ watch([planId, warehouseId], () => void loadDetail())
<AppDialog v-model="editOpen" class="loading-transport-dialog" :title="editDialogTitle" width="min(620px, calc(100vw - 28px))" confirm-text="保存" :confirm-loading="editSaving" :confirm-disabled="!canEdit" @confirm="saveTransport">
<el-form ref="editFormRef" class="transport-form" :model="editForm" :rules="editRules" label-position="top">
<el-form-item label="真实集装箱号" prop="containerNo"><el-input v-model="editForm.containerNo" maxlength="64" placeholder="请输入港口取得的真实集装箱号" @blur="editForm.containerNo = normalizedContainerNo(editForm.containerNo)" /></el-form-item>
<el-form-item label="真实集装箱号" prop="containerNo"><el-input v-model="editForm.containerNo" maxlength="64" placeholder="请输入提柜后取得的真实集装箱号" @blur="editForm.containerNo = normalizedContainerNo(editForm.containerNo)" /></el-form-item>
<el-form-item label="航次" prop="voyageNo"><el-input v-model="editForm.voyageNo" maxlength="64" placeholder="请输入航次" /></el-form-item>
<el-form-item label="操作原因" prop="operationReason"><el-input v-model="editForm.operationReason" type="textarea" :rows="3" maxlength="500" placeholder="必填,将记录在操作历史中" /></el-form-item>
<el-form-item :label="isContainerCorrection ? '纠错原因' : '操作原因(可选)'" prop="operationReason"><el-input v-model="editForm.operationReason" type="textarea" :rows="3" maxlength="500" :placeholder="isContainerCorrection ? '必填,将记录在操作历史中' : '可填写补录说明'" /></el-form-item>
</el-form>
</AppDialog>
@@ -265,11 +268,11 @@ watch([planId, warehouseId], () => void loadDetail())
<AppDialog v-model="confirmOpen" class="preload-confirm-dialog" title="确认整份预装计划" width="min(520px, calc(100vw - 28px))" confirm-text="确认预装" :confirm-loading="confirmSaving" :confirm-disabled="!detail?.summary.readiness.canConfirm" @confirm="submitConfirmPreload">
<div class="confirm-content">
<el-alert type="warning" :closable="false" show-icon title="本操作将确认整份预装计划" description="真实集装箱号未录入时不能确认;PDA 领取或作业开始后集装箱号只读。系统会原子确认本计划包含的全部入库预装票据,并由服务端按真实条件创建唯一 PDA 任务;当前页面不会伪造 PDA 成功。" />
<el-alert type="warning" :closable="false" show-icon title="本操作将确认整份预装计划" description="真实集装箱号可在提柜或预装详情补录,缺号不阻断此前流程;PDA 确认装柜完成前由服务端执行最终门禁。系统会原子确认本计划包含的全部入库预装票据,并由服务端按真实条件创建唯一 PDA 任务;当前页面不会伪造 PDA 成功。" />
<el-descriptions v-if="detail" :column="1" border>
<el-descriptions-item label="预装单号">{{ detail.summary.preloadNo }}</el-descriptions-item>
<el-descriptions-item label="柜次号">{{ detail.summary.cabinetNo || '—' }}</el-descriptions-item>
<el-descriptions-item label="集装箱号">{{ detail.summary.containerNo || '待录入' }}</el-descriptions-item>
<el-descriptions-item label="集装箱号">{{ detail.summary.containerNo || '待补录' }}</el-descriptions-item>
</el-descriptions>
</div>
</AppDialog>