feat: connect plan-level preload confirmation
This commit is contained in:
@@ -25,4 +25,30 @@ describe('PreloadPlanDetailView prototype contract', () => {
|
||||
expect(source).toContain('width="min(620px, calc(100vw - 28px))"')
|
||||
expect(source).not.toContain('saveMfNote')
|
||||
})
|
||||
|
||||
it('is the formal container-number entry and does not imply that preload directly opens PDA', () => {
|
||||
expect(source).toContain("{ required: true, message: '请输入集装箱号'")
|
||||
expect(source).toContain("detail.value?.summary.containerNo ? '修改集装箱号 / 航次' : '录入集装箱号'")
|
||||
expect(source).toContain('预装清单不会直接开放 PDA')
|
||||
expect(source).toContain('订舱关联与放舱、预装确认后')
|
||||
})
|
||||
|
||||
it('confirms the whole plan through readiness with stable idempotency and refreshes the detail', () => {
|
||||
expect(source).toContain('confirmPreloadPlan(summary.planId, summary.version, confirmOperationId.value)')
|
||||
expect(source).toContain('summary?.readiness.canConfirm')
|
||||
expect(source).toContain('confirmSaving.value')
|
||||
expect(source).toContain('await loadDetail()')
|
||||
expect(source).toContain('本操作将确认整份预装计划')
|
||||
expect(source).toContain('全部入库预装票据')
|
||||
expect(source).toContain('当前页面不会伪造 PDA 成功')
|
||||
expect(source).not.toContain('/inbounds/')
|
||||
})
|
||||
|
||||
it('keeps backend confirmation errors visible and supports narrow dialogs', () => {
|
||||
expect(source).toContain("confirmError.value = getApiErrorDetails(cause, '确认预装失败,请稍后重试')")
|
||||
expect(source).toContain(':message="confirmError.message"')
|
||||
expect(source).toContain('width="min(520px, calc(100vw - 28px))"')
|
||||
expect(source).toContain("label=\"柜次号\"")
|
||||
expect(source).toContain("label=\"集装箱号\"")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import LoadingListDetailDrawer from '../components/LoadingListDetailDrawer.vue'
|
||||
import { getPreloadPlan, updatePreloadTransport, type LoadingDetail, type PreloadSummary } from '../api/platformLoadingLists'
|
||||
import { confirmPreloadPlan, getPreloadPlan, updatePreloadTransport, type LoadingDetail, type PreloadSummary } from '../api/platformLoadingLists'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -21,13 +21,21 @@ const error = ref<ApiErrorDetails | null>(null)
|
||||
const editOpen = ref(false)
|
||||
const editSaving = ref(false)
|
||||
const editError = ref<ApiErrorDetails | null>(null)
|
||||
const confirmOpen = ref(false)
|
||||
const confirmSaving = ref(false)
|
||||
const confirmError = ref<ApiErrorDetails | null>(null)
|
||||
const confirmOperationId = ref('')
|
||||
const editFormRef = ref<FormInstance>()
|
||||
const editForm = reactive({ containerNo: '', voyageNo: '', operationReason: '' })
|
||||
const planId = computed(() => String(route.params.planId || ''))
|
||||
const warehouseId = computed(() => typeof route.query.warehouseId === 'string' ? route.query.warehouseId : '')
|
||||
const canEdit = computed(() => access.can('platform:firstleg:preload:edit'))
|
||||
const editDialogTitle = computed(() => detail.value?.summary.containerNo ? '修改集装箱号 / 航次' : '录入集装箱号')
|
||||
const editRules: FormRules = {
|
||||
containerNo: [{ max: 64, message: '集装箱号不能超过 64 个字符', trigger: 'blur' }],
|
||||
containerNo: [
|
||||
{ required: true, message: '请输入集装箱号', trigger: 'blur' },
|
||||
{ max: 64, message: '集装箱号不能超过 64 个字符', trigger: 'blur' },
|
||||
],
|
||||
voyageNo: [{ max: 64, message: '航次不能超过 64 个字符', trigger: 'blur' }],
|
||||
operationReason: [{ max: 500, message: '修改原因不能超过 500 个字符', trigger: 'blur' }],
|
||||
}
|
||||
@@ -74,7 +82,7 @@ async function saveTransport() {
|
||||
editError.value = null
|
||||
try {
|
||||
detail.value = await updatePreloadTransport(planId.value, warehouseId.value, {
|
||||
containerNo: editForm.containerNo.trim() || null,
|
||||
containerNo: editForm.containerNo.trim(),
|
||||
voyageNo: editForm.voyageNo.trim() || null,
|
||||
operationReason: editForm.operationReason.trim() || null,
|
||||
expectedVersion: detail.value.summary.version,
|
||||
@@ -92,6 +100,32 @@ async function saveTransport() {
|
||||
}
|
||||
}
|
||||
|
||||
function openConfirmPreload() {
|
||||
const summary = detail.value?.summary
|
||||
if (!canEdit.value || !summary?.readiness.canConfirm) return
|
||||
confirmError.value = null
|
||||
confirmOperationId.value = `admin-preload-confirm-${summary.planId}-${summary.version}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`
|
||||
confirmOpen.value = true
|
||||
}
|
||||
|
||||
async function submitConfirmPreload() {
|
||||
const summary = detail.value?.summary
|
||||
if (!summary?.readiness.canConfirm || confirmSaving.value) return
|
||||
confirmSaving.value = true
|
||||
confirmError.value = null
|
||||
try {
|
||||
const result = await confirmPreloadPlan(summary.planId, summary.version, confirmOperationId.value)
|
||||
await loadDetail()
|
||||
confirmOpen.value = false
|
||||
confirmOperationId.value = ''
|
||||
ElMessage.success(result.readiness.readyForPda ? '预装已确认,PDA 任务已具备下发条件' : '预装已确认')
|
||||
} catch (cause) {
|
||||
confirmError.value = getApiErrorDetails(cause, '确认预装失败,请稍后重试')
|
||||
} finally {
|
||||
confirmSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => void loadDetail())
|
||||
</script>
|
||||
|
||||
@@ -99,13 +133,21 @@ onMounted(() => void loadDetail())
|
||||
<AdminPage>
|
||||
<div class="preload-detail-page">
|
||||
<el-button link type="primary" class="back-button" @click="goBack"><ArrowLeft :size="16" />返回列表</el-button>
|
||||
<el-alert
|
||||
v-if="detail"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
:title="detail.summary.containerNo ? '集装箱号已录入,继续完成订舱放舱与预装确认' : '下一步:录入集装箱号'"
|
||||
description="预装清单不会直接开放 PDA;需完成集装箱号、订舱关联与放舱、预装确认后,才由服务端生成 PDA 任务。"
|
||||
/>
|
||||
<LoadErrorState v-if="error && !detail" title="预装清单详情加载失败" :message="error.message" :loading="loading" @retry="loadDetail">
|
||||
<template #details><ApiErrorMeta :details="error" /></template>
|
||||
</LoadErrorState>
|
||||
<LoadingListDetailDrawer v-else :mode="'preload'" :loading="loading" :detail="detail" :error="error" :can-edit="canEdit" @retry="loadDetail" @edit="openEdit" />
|
||||
<LoadingListDetailDrawer v-else :mode="'preload'" :loading="loading" :detail="detail" :error="error" :can-edit="canEdit" :confirm-saving="confirmSaving" @retry="loadDetail" @edit="openEdit" @confirm-preload="openConfirmPreload" />
|
||||
</div>
|
||||
|
||||
<AppDialog v-model="editOpen" class="loading-transport-dialog" title="编辑预装运输信息" width="min(620px, calc(100vw - 28px))" confirm-text="保存" :confirm-loading="editSaving" :confirm-disabled="!canEdit" @confirm="saveTransport">
|
||||
<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="请输入集装箱号" /></el-form-item>
|
||||
<el-form-item label="航次" prop="voyageNo"><el-input v-model="editForm.voyageNo" maxlength="64" placeholder="请输入航次" /></el-form-item>
|
||||
@@ -113,6 +155,18 @@ onMounted(() => void loadDetail())
|
||||
</el-form>
|
||||
<LoadErrorState v-if="editError" compact title="保存失败" :message="editError.message" :show-retry="false"><template #details><ApiErrorMeta :details="editError" /></template></LoadErrorState>
|
||||
</AppDialog>
|
||||
|
||||
<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 成功。" />
|
||||
<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>
|
||||
<LoadErrorState v-if="confirmError" compact title="确认预装失败" :message="confirmError.message" :show-retry="false"><template #details><ApiErrorMeta :details="confirmError" /></template></LoadErrorState>
|
||||
</div>
|
||||
</AppDialog>
|
||||
</AdminPage>
|
||||
</template>
|
||||
|
||||
@@ -121,6 +175,7 @@ onMounted(() => void loadDetail())
|
||||
.back-button { width: fit-content; }
|
||||
.transport-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; }
|
||||
.transport-form :deep(.el-form-item:last-child) { grid-column: 1 / -1; }
|
||||
.confirm-content { display: grid; gap: 16px; }
|
||||
:global(.loading-transport-dialog.el-dialog) { max-height: 90vh; overflow: hidden; }
|
||||
@media (max-width: 720px) { .transport-form { grid-template-columns: 1fr; } .transport-form :deep(.el-form-item:last-child) { grid-column: auto; } }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user