fix(appointments): let linked external cargo join shared arrangements
This commit is contained in:
@@ -10,23 +10,43 @@ import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||
import RowActions from '@/shared/components/RowActions.vue'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import { listPlanningCargo, resolvePlanningSource, previewPlanningBatch, createSourceAppointment, type SourceAppointmentPayload, type PlanningBatchPreview, type ResolvedPlanningSource, type PlanningCargo } from './planningApi'
|
||||
import { listPlanningCargo, resolvePlanningSource, previewPlanningBatch, createSourceAppointment, type SourceAppointmentPayload, type PlanningBatchPreview, type ResolvedPlanningSource, type PlanningCargo, type PlanningSource } from './planningApi'
|
||||
import AppointmentFollowupDialog from './AppointmentFollowupDialog.vue'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
|
||||
const router = useRouter()
|
||||
const access = useAccessPreviewStore()
|
||||
const emit = defineEmits<{ created: [] }>()
|
||||
const props = defineProps<{ externalSource?: PlanningSource | null }>()
|
||||
const emit = defineEmits<{ created: []; sourceAccepted: [] }>()
|
||||
const arrangement = reactive(createAppointmentForm())
|
||||
const creating = ref(false), createError = ref<ApiErrorDetails | null>(null)
|
||||
const pendingCreate = ref<SourceAppointmentPayload | null>(null)
|
||||
const following = ref<ResolvedPlanningSource | null>(null)
|
||||
const followupError = ref<ApiErrorDetails | null>(null)
|
||||
const selected = ref<Record<string, PlanningCargo>>({})
|
||||
type Selection = Pick<PlanningSource, 'sourceKey' | 'customerId' | 'originWarehouseId' | 'destinationWarehouseCode' | 'inboundCode'> & { bookingNo?: string | null }
|
||||
const selected = ref<Record<string, Selection>>({})
|
||||
const selectedRows = computed(() => Object.values(selected.value))
|
||||
const externalRows = computed(() => selectedRows.value.filter(row => row.sourceKey.startsWith('BOOKING:')))
|
||||
const batchPreview = ref<PlanningBatchPreview | null>(null)
|
||||
const batchLoading = ref(false), batchError = ref<ApiErrorDetails | null>(null)
|
||||
let batchRequest = 0
|
||||
function addSource(source: PlanningSource) {
|
||||
if (!access.can(overseasWarehousePermissions.inboundOrderAppointment) || batchLoading.value || creating.value) return false
|
||||
if (!source.canFollowup) { ElMessage.warning('当前来源尚不能安排预约'); return false }
|
||||
const anchor = selectedRows.value[0]
|
||||
if (anchor && (anchor.originWarehouseId !== source.originWarehouseId || anchor.destinationWarehouseCode !== source.destinationWarehouseCode)) {
|
||||
ElMessage.warning('请先完成或清空当前合批选择;不同起运仓或目的仓不能混入'); return false
|
||||
}
|
||||
if (!selected.value[source.sourceKey] && selectedRows.value.length >= 100) { ElMessage.warning('每批最多选择100票'); return false }
|
||||
selected.value[source.sourceKey] = source; batchError.value = null
|
||||
return true
|
||||
}
|
||||
function acceptExternalSource() {
|
||||
if (props.externalSource && addSource(props.externalSource)) emit('sourceAccepted')
|
||||
}
|
||||
function arrangeFollowing() {
|
||||
if (following.value && !following.value.coverage.appointments.length && addSource(following.value.source)) following.value = null
|
||||
}
|
||||
function selectable(row: PlanningCargo) {
|
||||
const anchor = selectedRows.value[0]
|
||||
return !row.detained && !row.mixedDestinations && !row.hasActiveAppointment && (!anchor || (anchor.originWarehouseId === row.originWarehouseId && anchor.destinationWarehouseCode === row.destinationWarehouseCode))
|
||||
@@ -124,6 +144,14 @@ onBeforeUnmount(() => { ++request; ++followupRequest; ++batchRequest; controller
|
||||
</template>
|
||||
<template #filters><QueryToolbar v-model="query" ariaLabel="在途货物查询" :fields="fields" :loading="loading" @search="load(true)" /></template>
|
||||
<template #notices>
|
||||
<div v-if="externalSource" class="cargo-note">
|
||||
待加入来源:{{ externalSource.bookingNo || externalSource.inboundCode || '尚未关联业务编号' }} · {{ externalSource.originWarehouseName }} → {{ externalSource.destinationWarehouseCode }}
|
||||
<el-button v-if="access.can(overseasWarehousePermissions.inboundOrderAppointment)" :disabled="batchLoading || creating" @click="acceptExternalSource">加入本次选择</el-button>
|
||||
</div>
|
||||
<details v-if="externalRows.length" class="external-selection">
|
||||
<summary>已选外接货 {{ externalRows.length }} 票(可与同仓头程货合批)</summary>
|
||||
<div class="external-selection-list"><div v-for="row in externalRows" :key="row.sourceKey">{{ row.bookingNo || row.inboundCode || '尚未关联业务编号' }}<el-button :disabled="batchLoading || creating" @click="delete selected[row.sourceKey]">移除选择</el-button></div></div>
|
||||
</details>
|
||||
<LoadErrorState v-if="batchError" title="所选货物暂不能合批" :message="batchError.message" :show-retry="false"><template #details><ApiErrorMeta :details="batchError" /></template></LoadErrorState>
|
||||
<LoadErrorState v-if="followupError" title="来源核对失败" :message="followupError.message" :show-retry="false" />
|
||||
<LoadErrorState v-if="error" title="已开船货物加载失败" :message="error.message" :loading="loading" @retry="load()"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
|
||||
@@ -138,7 +166,7 @@ onBeforeUnmount(() => { ++request; ++followupRequest; ++batchRequest; controller
|
||||
<span v-else>{{ row.hasInboundPlan ? '已建入仓计划,待安排' : '尚未建入仓计划' }}</span>
|
||||
</template>
|
||||
</DataTable>
|
||||
<AppointmentFollowupDialog v-if="following" :source="following.source" :coverage="following.coverage" :can-write="access.can('platform:appointment:followup')" @close="following = null" />
|
||||
<AppointmentFollowupDialog v-if="following" :source="following.source" :coverage="following.coverage" :can-write="access.can('platform:appointment:followup')" :can-arrange="access.can(overseasWarehousePermissions.inboundOrderAppointment)" @arrange="arrangeFollowing" @close="following = null" />
|
||||
<AppDialog v-if="batchPreview" :model-value="true" class="admin-density-dialog" title="合批预约安排" width="min(1080px, calc(100vw - 48px))" :show-confirm="access.can(overseasWarehousePermissions.inboundOrderAppointment)" :confirm-text="pendingCreate ? '重试本次保存' : '保存预约安排'" :confirm-loading="creating" cancel-text="关闭" @confirm="saveArrangement" @update:model-value="batchPreview = null">
|
||||
<p>{{ batchPreview.originWarehouseName }} → {{ batchPreview.destinationWarehouseCode }}</p>
|
||||
<p>{{ batchPreview.group.sourceCount }} 票 / {{ batchPreview.group.customerCount }} 个客户 / 预报 {{ batchPreview.group.packages ?? '待核实' }} 箱</p>
|
||||
@@ -151,4 +179,7 @@ onBeforeUnmount(() => { ++request; ++followupRequest; ++batchRequest; controller
|
||||
|
||||
<style scoped>
|
||||
.cargo-note { margin: var(--admin-space-3) 0; color: var(--el-text-color-secondary); font-size: var(--admin-font-caption); }
|
||||
.external-selection { margin: var(--admin-space-3) 0; }
|
||||
.external-selection-list { max-height: 216px; overflow-y: auto; }
|
||||
.external-selection-list > div { display: flex; align-items: center; justify-content: space-between; gap: 12px; overflow-wrap: anywhere; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user