2026-10-15 17:11:03 +08:00
< script setup lang = "ts" >
import { computed , onMounted , reactive , ref } from 'vue'
import { ElMessage } from 'element-plus'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
2026-10-19 17:02:27 +08:00
import { FbaAppointmentFields , createAppointmentForm , appointmentFormError , appointmentPayload , overseasWarehousePermissions , type FbaAppointmentDto } from '@/modules/overseas-warehouse'
2026-10-15 17:11:03 +08:00
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import { getApiErrorDetails , type ApiErrorDetails } from '@/shared/api/http'
import { previewPlanningBatch , createSourceAppointment , type PlanningBatchPreview , type SourceAppointmentPayload } from './planningApi'
import type { SourceReference } from './workApi'
import { rejectedCommand , useWorkDraft } from './useWorkDraft'
2026-10-15 17:33:36 +08:00
import { workReason } from './workPresentation'
2026-10-15 17:11:03 +08:00
const props = defineProps < { sources : SourceReference []; canWrite : boolean } > ()
2026-10-19 17:02:27 +08:00
const emit = defineEmits < { saved : [ appointment : FbaAppointmentDto ] } > ()
2026-10-15 17:11:03 +08:00
const access = useAccessPreviewStore (), preview = ref < PlanningBatchPreview | null >( null )
2026-10-21 14:30:33 +08:00
const confirmationReady = ref ( false )
const appointmentFields = ref < InstanceType < typeof FbaAppointmentFields > | null > ( null )
2026-10-15 17:11:03 +08:00
const form = reactive ( createAppointmentForm ()), baseline = ref ( JSON . stringify ( form ))
const loading = ref ( false ), saving = ref ( false ), error = ref < ApiErrorDetails | null >( null ), saveError = ref < ApiErrorDetails | null >( null )
const pending = ref < SourceAppointmentPayload | null >( null )
const writable = computed (() => props . canWrite && access . can ( overseasWarehousePermissions . inboundOrderAppointment ))
2026-10-21 17:22:25 +08:00
const visibleMissingFields = computed (() => preview . value ? . missingFields . filter ( field => field !== 'READY_WINDOW_UNVERIFIED' ) || [])
2026-10-15 17:11:03 +08:00
useWorkDraft ( computed (() => baseline . value !== JSON . stringify ( form )), computed (() => saving . value || !! pending . value ))
async function load () {
if ( ! props . sources . length || props . sources . some ( source => ! source . customerId || ! source . sourceKey || ! source . destinationKey )) { error . value = { message : '缺少完整货源、客户或目的仓范围,无法新建预约。' }; return }
loading . value = true ; error . value = null
try { preview . value = await previewPlanningBatch ( props . sources . map ( source => ({ ... source , destinationKey : source . destinationKey ! }))) }
catch ( cause ) { error . value = getApiErrorDetails ( cause , '预约货物预览失败' ) }
finally { loading . value = false }
}
async function save () {
if ( ! writable . value || loading . value || saving . value || error . value || ! preview . value ) return
if ( ! pending . value ) {
const invalid = appointmentFormError ( form ); if ( invalid ) { ElMessage . warning ( invalid ); return }
2026-10-21 14:30:33 +08:00
if ( form . appointmentStatus === 'SUCCESS' && ! confirmationReady . value ) { ElMessage . warning ( '请先在“正式确认前”区域补齐并保存每票货物资料' ); return }
if ( appointmentFields . value ? . canLeave && ! await appointmentFields . value . canLeave ()) return
if ( saving . value || ! writable . value ) return
2026-10-15 17:11:03 +08:00
pending . value = { originWarehouseId : preview . value . group . originWarehouseId , fbaWarehouseCode : preview . value . destinationWarehouseCode , sources : preview . value . sources . map ( source => ({ sourceKey : source . sourceKey , customerId : source . customerId , destinationKey : source . destinationKey })), details : appointmentPayload ( form ), idempotencyKey : crypto . randomUUID () }
}
saving . value = true ; saveError . value = null
try {
const result = await createSourceAppointment ( pending . value )
pending . value = null ; baseline . value = JSON . stringify ( form )
2026-10-19 17:02:27 +08:00
ElMessage . success ( `预约安排 ${ result . appointmentNo } 已保存` ); emit ( 'saved' , result )
2026-10-15 17:11:03 +08:00
} catch ( cause ) {
saveError . value = getApiErrorDetails ( cause , '创建结果待确认,请重试本次请求' )
if ( rejectedCommand ( cause )) { pending . value = null ; await load () }
} finally { saving . value = false }
}
onMounted ( load )
</ script >
< template >
2026-10-20 10:11:31 +08:00
< section class = "work-form density-dialog-sections" aria -label =" 新建预约安排 " >
2026-10-15 17:11:03 +08:00
< LoadErrorState v-if = "error" title="预约货物预览失败" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error" /></ template ></ LoadErrorState >
< p v-if = "loading" > 正在核对本次货物范围 … </ p >
< template v-if = "preview && !error" >
2026-10-20 10:11:31 +08:00
< section class = "density-dialog-section" data -tone =" summary " aria -label =" 本次预约货物 " >
< h3 class = "density-dialog-section__title" > 本次预约货物 </ h3 >
< table class = "density-table" >< tbody >
< tr >< th scope = "row" > 起运仓 </ th >< td >{{ preview . originWarehouseName || '—' }}</ td >< th scope = "row" > 目的FBA仓 </ th >< td >{{ preview . destinationWarehouseCode || '—' }}</ td ></ tr >
< tr >< th scope = "row" > 票数 </ th >< td >{{ preview . group . sourceCount }}</ td >< th scope = "row" > 箱数 </ th >< td >{{ preview . group . packages ?? '箱数待核实' }}</ td ></ tr >
< tr >< th scope = "row" > 货物业务号 </ th >< td colspan = "3" >< template v-for = "(source, index) in preview.sources" :key="`${source.customerId}:${source.sourceKey}`" >{{ index ? '、' : '' }}{{ source.inboundCode || source.bookingNo || ' 原票业务号未提供 ' }}</ template ></ td ></ tr >
</ tbody ></ table >
2026-10-21 17:22:25 +08:00
< p v-for = "field in visibleMissingFields" :key="field" role="status" class="density-dialog-note" >{{ workReason ( field ) }}</ p >
2026-10-20 10:11:31 +08:00
</ section >
2026-10-28 12:01:43 +08:00
< FbaAppointmentFields continuous hide -confirmation-times ref = "appointmentFields" : sources = "sources.map(source => ({ ...source, destinationKey: source.destinationKey || undefined }))" @ confirmation -ready =" confirmationReady = $ event " :model = "form" :timezone = "preview.destinationTimezone" : disabled = "!writable || loading || saving || !!pending" />
2026-10-15 17:11:03 +08:00
< LoadErrorState v-if = "saveError" title="预约尚未创建" :message="saveError.message" :show-retry="false"><template #details><ApiErrorMeta :details="saveError" /></ template ></ LoadErrorState >
2026-10-20 10:11:31 +08:00
< div class = "density-actions density-actions--footer" >< el-button type = "primary" : disabled = "!writable || loading" :loading = "saving" @click ="save" >{{ pending ? ' 重试本次创建 ' : ' 保存预约安排 ' }} </ el -button ></ div >
2026-10-15 17:11:03 +08:00
</ template >
</ section >
</ template >