提交
This commit is contained in:
1 parent
6d9354a194
commit
aff71d296b
2759 files changed
+860425
-13211
No files matched your search
@@ -62,6 +62,7 @@ import {
|
||||
setupDeliverySlipEditorTypeCheckboxes,
|
||||
type DeliverySlipRenderContext
|
||||
} from '@/utils/pl-delivery-slip-template'
|
||||
import { getPlSlipPdfCanvasScale } from '@/utils/pl-slip-pdf-render'
|
||||
|
||||
const visible = ref(false)
|
||||
const slipContent = ref('')
|
||||
@@ -94,8 +95,7 @@ async function waitForImagesLoaded(container: HTMLElement) {
|
||||
}
|
||||
|
||||
function getPdfCanvasScale() {
|
||||
const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1
|
||||
return Math.min(4, Math.max(3, dpr * 2))
|
||||
return getPlSlipPdfCanvasScale()
|
||||
}
|
||||
|
||||
async function buildSlipRenderContext(plan: Plan): Promise<DeliverySlipRenderContext> {
|
||||
|
||||
@@ -60,6 +60,7 @@ import {
|
||||
restoreInboundSlipPlaceholders,
|
||||
type InboundSlipRenderContext
|
||||
} from '@/utils/pl-inbound-slip-template'
|
||||
import { getPlSlipPdfCanvasScale } from '@/utils/pl-slip-pdf-render'
|
||||
|
||||
const visible = ref(false)
|
||||
const slipContent = ref('')
|
||||
@@ -92,8 +93,7 @@ async function waitForImagesLoaded(container: HTMLElement) {
|
||||
}
|
||||
|
||||
function getPdfCanvasScale() {
|
||||
const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1
|
||||
return Math.min(4, Math.max(3, dpr * 2))
|
||||
return getPlSlipPdfCanvasScale()
|
||||
}
|
||||
|
||||
async function buildSlipRenderContext(plan: Plan): Promise<InboundSlipRenderContext> {
|
||||
|
||||
@@ -115,11 +115,14 @@
|
||||
<script setup lang="ts">
|
||||
import { Check, Close } from '@element-plus/icons-vue'
|
||||
import {
|
||||
inboundOrderApi,
|
||||
outboundOrderApi,
|
||||
planApi,
|
||||
transportApi,
|
||||
type Plan,
|
||||
type PlanArchiveBody,
|
||||
type PlanProduct,
|
||||
type PlIoOrder,
|
||||
type Transport
|
||||
} from '@/api/pl'
|
||||
import PlPlanFormProductTable from '@/components/business/pl-plan-form-dialog/PlPlanFormProductTable.vue'
|
||||
@@ -271,6 +274,27 @@ async function handleOpened() {
|
||||
const rawProducts = Array.isArray(data.products) ? data.products : []
|
||||
const enrichedProducts = await enrichPlanProductsHazardFromCrm(rawProducts)
|
||||
productRows.value = mapPlanProducts(enrichedProducts)
|
||||
// 入库/出库单实际数量预填归档表单(归档提交时后端也会再同步一次)
|
||||
if (data.planType === 'inbound' || data.planType === 'outbound') {
|
||||
try {
|
||||
const api = data.planType === 'inbound' ? inboundOrderApi : outboundOrderApi
|
||||
const ioRes = await api.getByPlan(data.id)
|
||||
const io = pickBxData<PlIoOrder | null>(ioRes)
|
||||
if (io && io.status !== 'voided' && Array.isArray(io.items)) {
|
||||
const qtyMap = new Map(
|
||||
io.items
|
||||
.filter(it => it.planProductId && it.actualQty != null)
|
||||
.map(it => [it.planProductId, Number(it.actualQty)])
|
||||
)
|
||||
productRows.value = productRows.value.map(row => {
|
||||
if (!row.id || !qtyMap.has(row.id)) return row
|
||||
return { ...row, actualQty: qtyMap.get(row.id) }
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
// 无入库/出库单时保持计划原实际数量
|
||||
}
|
||||
}
|
||||
await applyFreightPrefill(data)
|
||||
} catch (error) {
|
||||
innerVisible.value = false
|
||||
|
||||
@@ -344,9 +344,6 @@ import { useDictStore } from '@/store/modules/dict'
|
||||
import { isTonProductUnit } from '@/utils/crm-product-display'
|
||||
import { formatDate } from '@/utils/format'
|
||||
import { pickBxData, pickBxList } from '@/utils/http/bx'
|
||||
import { canPrintInboundSlip } from '@/utils/pl-inbound-slip-template'
|
||||
import { canPrintDeliverySlip } from '@/utils/pl-delivery-slip-template'
|
||||
import { canPrintSubpackSlip } from '@/utils/pl-subpack-slip-template'
|
||||
import { formatPlVehiclePlateDisplay } from '@/utils/pl-display'
|
||||
|
||||
const dictStore = useDictStore()
|
||||
@@ -439,15 +436,10 @@ const dialogTitle = computed(() => {
|
||||
})
|
||||
|
||||
const isRearrange = computed(() => plan.value.transportStatus === 'arranged')
|
||||
const showInboundSlipPrint = computed(
|
||||
() => arrangeSuccess.value && canPrintInboundSlip(plan.value as Plan)
|
||||
)
|
||||
const showDeliverySlipPrint = computed(
|
||||
() => arrangeSuccess.value && canPrintDeliverySlip(plan.value as Plan)
|
||||
)
|
||||
const showSubpackSlipPrint = computed(
|
||||
() => arrangeSuccess.value && canPrintSubpackSlip(plan.value as Plan)
|
||||
)
|
||||
/** 安排成功后始终展示打印入口,与运输计划列表工具栏一致,便于现场立即开单 */
|
||||
const showInboundSlipPrint = computed(() => arrangeSuccess.value)
|
||||
const showDeliverySlipPrint = computed(() => arrangeSuccess.value)
|
||||
const showSubpackSlipPrint = computed(() => arrangeSuccess.value)
|
||||
|
||||
function getUnitLabel(unit: string) {
|
||||
return dictStore.getLabel('crm_product_unit', unit) || unit
|
||||
@@ -828,12 +820,14 @@ async function handleSubmit() {
|
||||
generateDibangPlan: formData.generateDibangPlan
|
||||
}
|
||||
|
||||
const wasRearrange = isRearrange.value
|
||||
const res = await transportApi.arrange(props.planId, payload)
|
||||
const result = pickBxData<TransportArrangeResult>(res)
|
||||
arrangeResult.value = result
|
||||
plan.value = { ...plan.value, transportStatus: 'arranged' }
|
||||
applyArrangeResult(result, plan.value, products.value)
|
||||
arrangeSuccess.value = true
|
||||
ElMessage.success(isRearrange.value ? '运输调整成功' : '运输安排成功')
|
||||
ElMessage.success(wasRearrange ? '运输调整成功' : '运输安排成功')
|
||||
await tabsRef.value?.reload()
|
||||
tabsRef.value?.setActiveTab('transport')
|
||||
emit('success')
|
||||
|
||||
@@ -224,8 +224,21 @@
|
||||
<el-button @click="innerVisible = false">
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
||||
<el-icon><Check /></el-icon>{{ isEdit ? '保存' : '创建' }}
|
||||
<el-button
|
||||
:type="showSaveAndArrange ? 'default' : 'primary'"
|
||||
:class="showSaveAndArrange ? 'btn-ghost' : undefined"
|
||||
:loading="submitLoading"
|
||||
@click="handleSubmit('save')"
|
||||
>
|
||||
<el-icon><Check /></el-icon>{{ saveButtonLabel }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="showSaveAndArrange"
|
||||
type="primary"
|
||||
:loading="submitLoading"
|
||||
@click="handleSubmit('saveAndArrange')"
|
||||
>
|
||||
<el-icon><Van /></el-icon>{{ saveAndArrangeLabel }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -246,7 +259,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Check, Close, Search, View } from '@element-plus/icons-vue'
|
||||
import { Check, Close, Search, Van, View } from '@element-plus/icons-vue'
|
||||
import { getContract } from '@/api/crm/contract'
|
||||
import type { ContractVO } from '@/api/crm/contract/types'
|
||||
import { carrierApi, planApi, transportApi, vehicleApi, type Carrier, type Plan, type PlanProduct, type PlanUpdatePayload, type Vehicle, type VehiclePersonnelRow } from '@/api/pl'
|
||||
@@ -262,13 +275,16 @@ import {
|
||||
mapContractProducts,
|
||||
type PlanFormProductRow
|
||||
} from '@/composables/usePlContractToPlan'
|
||||
import { enrichPlanProductsHazardFromCrm } from '@/composables/usePlToxicProductCertCheck'
|
||||
import {
|
||||
buildMissingHazardCertReadyMessage,
|
||||
enrichPlanProductsHazardFromCrm
|
||||
} from '@/composables/usePlToxicProductCertCheck'
|
||||
import { usePlFreightCalc } from '@/composables/usePlFreightCalc'
|
||||
import { loadVehicleViewBundle, type VehicleViewBundle } from '@/composables/usePlVehicleViewDetail'
|
||||
import { useDictStore } from '@/store/modules/dict'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { formatDate } from '@/utils/format'
|
||||
import { pickBxList } from '@/utils/http/bx'
|
||||
import { pickBxData, pickBxList } from '@/utils/http/bx'
|
||||
|
||||
const ContractDetailDialog = defineAsyncComponent(
|
||||
() => import('@/views/crm/contract/components/ContractDetailDialog.vue')
|
||||
@@ -284,7 +300,7 @@ const props = defineProps<{
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
success: []
|
||||
success: [payload?: { planId: string; openArrange: boolean }]
|
||||
}>()
|
||||
|
||||
const userStore = useUserStore()
|
||||
@@ -315,6 +331,13 @@ function resolveCurrentUserName() {
|
||||
|
||||
const isEdit = computed(() => !!props.planId)
|
||||
const dialogTitle = computed(() => (isEdit.value ? '编辑运输计划' : '新增运输计划'))
|
||||
const showSaveAndArrange = computed(
|
||||
() => !isEdit.value || loadedTransportStatus.value === 'pending'
|
||||
)
|
||||
const saveButtonLabel = computed(() => (isEdit.value ? '保存' : '创建'))
|
||||
const saveAndArrangeLabel = computed(() =>
|
||||
isEdit.value ? '保存并安排运输' : '创建并安排运输'
|
||||
)
|
||||
|
||||
const innerVisible = computed({
|
||||
get: () => props.visible,
|
||||
@@ -323,6 +346,7 @@ const innerVisible = computed({
|
||||
|
||||
const submitLoading = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const loadedTransportStatus = ref('pending')
|
||||
const contractSelectorVisible = ref(false)
|
||||
const contractDetailDialogActive = ref(false)
|
||||
const contractDetailRef = ref<InstanceType<typeof ContractDetailDialog>>()
|
||||
@@ -618,6 +642,7 @@ async function restoreFreightFromTransport(plan: Plan) {
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
loadedTransportStatus.value = 'pending'
|
||||
contractSnapshot.value = null
|
||||
vehicleBundle.value = null
|
||||
selectedDriverId.value = ''
|
||||
@@ -716,6 +741,7 @@ async function loadPlanForEdit(planId: string) {
|
||||
remark: data.remark || '',
|
||||
products: (data.products || []).map(mapPlanProductToFormRow)
|
||||
})
|
||||
loadedTransportStatus.value = data.transportStatus || 'pending'
|
||||
|
||||
if (data.contractId) {
|
||||
try {
|
||||
@@ -890,26 +916,55 @@ function validateBusinessRules() {
|
||||
return true
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
async function handleSubmit(mode: 'save' | 'saveAndArrange' = 'save') {
|
||||
const valid = await basicFormRef.value?.validate().catch(() => false)
|
||||
if (!valid || !validateBusinessRules()) return
|
||||
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const commonPayload = buildPlanCommonPayload({ allowClearTransport: isEdit.value })
|
||||
let planId = props.planId || ''
|
||||
|
||||
if (isEdit.value) {
|
||||
if (!props.planId) return
|
||||
await planApi.update(props.planId, commonPayload)
|
||||
planId = props.planId
|
||||
ElMessage.success('保存成功')
|
||||
} else {
|
||||
await planApi.createFull({
|
||||
const res = await planApi.createFull({
|
||||
...commonPayload,
|
||||
contractId: formData.contractId
|
||||
} as Parameters<typeof planApi.createFull>[0])
|
||||
const created = pickBxData<Plan>(res)
|
||||
planId = String(created?.id || '')
|
||||
if (!planId) {
|
||||
ElMessage.error('创建成功但未返回计划 ID')
|
||||
return
|
||||
}
|
||||
ElMessage.success('创建成功')
|
||||
}
|
||||
|
||||
let openArrange = false
|
||||
if (mode === 'saveAndArrange' && planId) {
|
||||
const detailRes = await planApi.get(planId)
|
||||
const plan = pickBxData<Plan>(detailRes)
|
||||
const rawProducts = Array.isArray(plan?.products) ? plan.products : []
|
||||
const enrichedProducts = await enrichPlanProductsHazardFromCrm(rawProducts)
|
||||
const certMessage = buildMissingHazardCertReadyMessage(plan, enrichedProducts)
|
||||
if (certMessage) {
|
||||
await ElMessageBox.alert(
|
||||
`${certMessage},请至【危化品证上传】页面操作。`,
|
||||
'无法安排运输',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
openArrange = false
|
||||
} else {
|
||||
openArrange = true
|
||||
}
|
||||
}
|
||||
|
||||
innerVisible.value = false
|
||||
emit('success')
|
||||
emit('success', { planId, openArrange })
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ import {
|
||||
setupSubpackSlipEditorCheckboxes,
|
||||
type SubpackSlipRenderContext
|
||||
} from '@/utils/pl-subpack-slip-template'
|
||||
import { getPlSlipPdfCanvasScale } from '@/utils/pl-slip-pdf-render'
|
||||
|
||||
const visible = ref(false)
|
||||
const slipContent = ref('')
|
||||
@@ -96,8 +97,7 @@ async function waitForImagesLoaded(container: HTMLElement) {
|
||||
}
|
||||
|
||||
function getPdfCanvasScale() {
|
||||
const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1
|
||||
return Math.min(4, Math.max(3, dpr * 2))
|
||||
return getPlSlipPdfCanvasScale()
|
||||
}
|
||||
|
||||
async function buildSlipRenderContext(plan: Plan): Promise<SubpackSlipRenderContext> {
|
||||
|
||||
Reference in new issue
Block a user