提交
This commit is contained in:
1 parent
493f8d6756
commit
d84f4b944c
406 files changed
+58954
-10376
No files matched your search
@@ -116,7 +116,14 @@
|
||||
<DictTag dict-type="crm_contract_status" :value="row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="amount" label="合同金额" min-width="120" align="right" header-align="center">
|
||||
<el-table-column
|
||||
v-if="!hideAmount"
|
||||
prop="amount"
|
||||
label="合同金额"
|
||||
min-width="120"
|
||||
align="right"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span class="money-display">{{ formatMoneyDisplay(row.amount) }}</span>
|
||||
</template>
|
||||
@@ -168,9 +175,15 @@ import DictTag from '@/components/DictTag/index.vue'
|
||||
import ListPagination from '@/components/ListPagination/index.vue'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
}>()
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
visible: boolean
|
||||
hideAmount?: boolean
|
||||
}>(),
|
||||
{
|
||||
hideAmount: false
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
|
||||
@@ -16,6 +16,34 @@
|
||||
<section class="carrier-view-top">
|
||||
<h3 class="carrier-view-block-title">承运商信息</h3>
|
||||
<ViewKvTable v-if="carrier" :fields="carrierViewFields" mode="triple" />
|
||||
|
||||
<h3 class="carrier-view-block-title carrier-view-attachment-title">资质附件</h3>
|
||||
<div v-if="carrier" class="carrier-view-attachments form-grid-2">
|
||||
<div class="carrier-view-attachment-item">
|
||||
<div class="carrier-view-attachment-label">营业执照</div>
|
||||
<AttachmentUploadField
|
||||
:model-value="carrier.businessLicenseUrls || ''"
|
||||
disabled
|
||||
folder="pl/carrier/license"
|
||||
/>
|
||||
</div>
|
||||
<div class="carrier-view-attachment-item">
|
||||
<div class="carrier-view-attachment-label">道路经营许可证</div>
|
||||
<AttachmentUploadField
|
||||
:model-value="carrier.roadTransportLicenseUrls || ''"
|
||||
disabled
|
||||
folder="pl/carrier/permit"
|
||||
/>
|
||||
</div>
|
||||
<div class="carrier-view-attachment-item form-col-span-2">
|
||||
<div class="carrier-view-attachment-label">运输协议</div>
|
||||
<AttachmentUploadField
|
||||
:model-value="carrier.transportAgreementUrls || ''"
|
||||
disabled
|
||||
folder="pl/carrier/agreement"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="carrier-view-bottom">
|
||||
@@ -50,6 +78,7 @@
|
||||
<script setup lang="ts">
|
||||
import { Close } from '@element-plus/icons-vue'
|
||||
import { carrierApi, vehicleApi, type Carrier } from '@/api/pl'
|
||||
import AttachmentUploadField from '@/components/business/attachment-upload-field/index.vue'
|
||||
import PlVehicleViewPanel from '@/components/business/pl-vehicle-view-panel/index.vue'
|
||||
import ViewKvTable from '@/components/business/system-view-dialog/ViewKvTable.vue'
|
||||
import type { ViewField } from '@/components/business/system-view-dialog/types'
|
||||
@@ -58,7 +87,12 @@ import {
|
||||
type VehicleViewBundle
|
||||
} from '@/composables/usePlVehicleViewDetail'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
import { getPlCarrierStatusLabel, getPlCarrierStatusTagType } from '@/utils/pl-display'
|
||||
import {
|
||||
getPlCarrierAuditStatusLabel,
|
||||
getPlCarrierAuditStatusTagType,
|
||||
getPlCarrierStatusLabel,
|
||||
getPlCarrierStatusTagType
|
||||
} from '@/utils/pl-display'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
@@ -92,7 +126,29 @@ const carrierViewFields = computed<ViewField[]>(() => {
|
||||
value: getPlCarrierStatusLabel(c.status),
|
||||
tagType: getPlCarrierStatusTagType(c.status)
|
||||
},
|
||||
{
|
||||
label: '审核状态',
|
||||
value: getPlCarrierAuditStatusLabel(c.auditStatus),
|
||||
tagType: getPlCarrierAuditStatusTagType(c.auditStatus)
|
||||
},
|
||||
{
|
||||
label: '审核时间',
|
||||
value: c.auditTime ? formatDateTime(c.auditTime) : '—'
|
||||
},
|
||||
{
|
||||
label: '协议有效期',
|
||||
value: c.transportAgreementExpired
|
||||
? '协议已过期'
|
||||
: c.transportAgreementExpireDate || '—',
|
||||
tagType: c.transportAgreementExpired ? 'danger' : undefined
|
||||
},
|
||||
{
|
||||
label: '资质状态',
|
||||
value: c.qualificationComplete ? '已齐全' : '资质不全',
|
||||
tagType: c.qualificationComplete ? 'success' : 'warning'
|
||||
},
|
||||
{ label: '创建时间', value: formatDateTime(c.createTime) },
|
||||
{ label: '审核备注', value: c.auditRemark || '—', span: 'full' },
|
||||
{ label: '备注', value: c.remark || '—', span: 'full' }
|
||||
]
|
||||
})
|
||||
@@ -155,6 +211,22 @@ function handleClosed() {
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.carrier-view-attachment-title {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.carrier-view-attachments {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.carrier-view-attachment-label {
|
||||
margin-bottom: 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.carrier-view-empty {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
|
||||
@@ -78,9 +78,6 @@
|
||||
@change="freightCalc.markFreightAmountTouched()"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否开票">
|
||||
<el-switch v-model="formData.hasInvoice" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
@@ -146,8 +143,7 @@ const productRows = ref<PlanFormProductRow[]>([])
|
||||
const formData = reactive({
|
||||
hasFreight: 0,
|
||||
distanceKm: null as number | null,
|
||||
freightType: 'by_weight',
|
||||
hasInvoice: 0
|
||||
freightType: 'by_weight'
|
||||
})
|
||||
|
||||
const hasFreightRef = computed(() => formData.hasFreight)
|
||||
@@ -205,8 +201,7 @@ function resetState() {
|
||||
Object.assign(formData, {
|
||||
hasFreight: 0,
|
||||
distanceKm: null,
|
||||
freightType: 'by_weight',
|
||||
hasInvoice: 0
|
||||
freightType: 'by_weight'
|
||||
})
|
||||
freightCalc.resetFreightCalc()
|
||||
}
|
||||
@@ -227,7 +222,6 @@ async function applyFreightPrefill(data: Plan) {
|
||||
const transport = pickBxData<Transport>(transportRes)
|
||||
formData.distanceKm = transport.distanceKm != null ? Number(transport.distanceKm) : null
|
||||
formData.freightType = transport.freightType || 'by_weight'
|
||||
formData.hasInvoice = transport.hasInvoice ?? 0
|
||||
|
||||
if (transport.transportQty != null) {
|
||||
freightCalc.transportQty.value = Number(transport.transportQty)
|
||||
@@ -311,8 +305,7 @@ async function handleSubmit() {
|
||||
transportQty:
|
||||
formData.freightType === 'by_weight'
|
||||
? freightCalc.transportQty.value ?? undefined
|
||||
: undefined,
|
||||
hasInvoice: formData.hasInvoice
|
||||
: undefined
|
||||
}
|
||||
: {})
|
||||
}
|
||||
|
||||
@@ -65,6 +65,14 @@
|
||||
|
||||
<section v-else class="pl-plan-form-section">
|
||||
<h3 class="pl-plan-form-section-title">{{ isRearrange ? '调整运输' : '运输安排' }}</h3>
|
||||
<el-alert
|
||||
v-if="rearrangeBlocked"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
:title="rearrangeBlockedMessage"
|
||||
class="pl-arrange-block-alert"
|
||||
/>
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top">
|
||||
<h4 class="pl-plan-subtitle">车辆信息</h4>
|
||||
<div class="form-grid-4">
|
||||
@@ -100,9 +108,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="运输公司">
|
||||
<el-input v-if="isSelfVehicle" :model-value="SELF_CARRIER_NAME" readonly />
|
||||
<el-select
|
||||
v-else
|
||||
v-model="formData.carrierId"
|
||||
placeholder="请选择运输公司"
|
||||
filterable
|
||||
@@ -230,9 +236,6 @@
|
||||
@change="freightCalc.markFreightAmountTouched()"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否开票">
|
||||
<el-switch v-model="formData.hasInvoice" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<h4 class="pl-plan-subtitle">安排项</h4>
|
||||
@@ -346,7 +349,7 @@
|
||||
<el-button @click="innerVisible = false">
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
||||
<el-button type="primary" :loading="submitLoading" :disabled="rearrangeBlocked" @click="handleSubmit">
|
||||
<el-icon><Check /></el-icon>{{ isRearrange ? '确认调整' : '确认安排' }}
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -384,8 +387,6 @@ import { isTonProductUnit } from '@/utils/crm-product-display'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
import { pickBxData, pickBxList } from '@/utils/http/bx'
|
||||
|
||||
const SELF_CARRIER_NAME = '安庆市山峡化工有限公司'
|
||||
|
||||
const dictStore = useDictStore()
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -440,6 +441,8 @@ const vehicleBundle = ref<VehicleViewBundle | null>(null)
|
||||
const vehicleViewVisible = ref(false)
|
||||
const hasHistoricalDibang = ref(false)
|
||||
const hasHistoricalSiteLinks = ref(false)
|
||||
const rearrangeBlocked = ref(false)
|
||||
const rearrangeBlockedMessage = ref('')
|
||||
|
||||
const formData = reactive({
|
||||
vehicleId: '',
|
||||
@@ -454,7 +457,6 @@ const formData = reactive({
|
||||
hasFreight: 0,
|
||||
distanceKm: null as number | null,
|
||||
freightType: 'by_weight',
|
||||
hasInvoice: 0,
|
||||
remark: '',
|
||||
generateDibangPlan: false
|
||||
})
|
||||
@@ -523,10 +525,8 @@ const freightCalc = usePlFreightCalc({
|
||||
})
|
||||
|
||||
const selectedVehicle = computed(() => vehicleList.value.find(item => item.id === formData.vehicleId))
|
||||
const isSelfVehicle = computed(() => selectedVehicle.value?.vehicleType === 'self')
|
||||
|
||||
const summaryCarrierName = computed(() => {
|
||||
if (isSelfVehicle.value) return SELF_CARRIER_NAME
|
||||
const carrier = carrierList.value.find(item => item.id === formData.carrierId)
|
||||
return carrier?.carrierName || vehicleBundle.value?.detail?.carrierName || ''
|
||||
})
|
||||
@@ -565,12 +565,6 @@ function formatDriverLabel(d: VehiclePersonnelRow) {
|
||||
return `${d.personnelName}${phone}`
|
||||
}
|
||||
|
||||
function resolveSelfCarrierId() {
|
||||
if (!Array.isArray(carrierList.value)) return
|
||||
const matched = carrierList.value.find(item => item.carrierName === SELF_CARRIER_NAME)
|
||||
formData.carrierId = matched?.id || formData.carrierId || ''
|
||||
}
|
||||
|
||||
function applyVehiclePersonnel(personnelList: VehiclePersonnelRow[], preferredDriverId?: string) {
|
||||
vehicleDriverList.value = personnelList.filter(p => p.role === 'driver' && p.personnelId)
|
||||
if (preferredDriverId && vehicleDriverList.value.some(d => d.personnelId === preferredDriverId)) {
|
||||
@@ -632,7 +626,6 @@ async function applyPrefillFromPlan(data: Plan) {
|
||||
formData.remark = transport.remark || formData.remark
|
||||
formData.distanceKm = transport.distanceKm != null ? Number(transport.distanceKm) : null
|
||||
formData.freightType = transport.freightType || 'by_weight'
|
||||
formData.hasInvoice = transport.hasInvoice ?? 0
|
||||
prefilledDriverId.value = transport.driverId || prefilledDriverId.value
|
||||
|
||||
if (transport.transportQty != null) {
|
||||
@@ -662,6 +655,9 @@ function resetState() {
|
||||
vehicleBundle.value = null
|
||||
vehicleViewVisible.value = false
|
||||
hasHistoricalDibang.value = false
|
||||
hasHistoricalSiteLinks.value = false
|
||||
rearrangeBlocked.value = false
|
||||
rearrangeBlockedMessage.value = ''
|
||||
Object.assign(formData, {
|
||||
vehicleId: '',
|
||||
driverId: '',
|
||||
@@ -675,7 +671,6 @@ function resetState() {
|
||||
hasFreight: 0,
|
||||
distanceKm: null,
|
||||
freightType: 'by_weight',
|
||||
hasInvoice: 0,
|
||||
remark: '',
|
||||
generateDibangPlan: false
|
||||
})
|
||||
@@ -685,13 +680,47 @@ function resetState() {
|
||||
|
||||
async function loadMasterData() {
|
||||
const [vehicleRes, carrierRes] = await Promise.all([
|
||||
vehicleApi.list({ vehicleStatus: 'available' }),
|
||||
vehicleApi.list({ vehicleStatus: 'available', excludeIneligibleCarrier: true }),
|
||||
carrierApi.list({ status: '1' })
|
||||
])
|
||||
vehicleList.value = pickBxList<Vehicle>(vehicleRes)
|
||||
carrierList.value = pickBxList<Carrier>(carrierRes)
|
||||
}
|
||||
|
||||
async function updateRearrangeBlocked() {
|
||||
rearrangeBlocked.value = false
|
||||
rearrangeBlockedMessage.value = ''
|
||||
if (!isRearrange.value) return
|
||||
|
||||
const detail = vehicleBundle.value?.detail
|
||||
const carrierId = detail?.carrierId || formData.carrierId || selectedVehicle.value?.carrierId
|
||||
if (!carrierId) return
|
||||
|
||||
let carrier = carrierList.value.find(c => c.id === carrierId)
|
||||
if (!carrier) {
|
||||
try {
|
||||
const res = await carrierApi.get(carrierId)
|
||||
carrier = pickBxData<Carrier>(res)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!carrier?.qualificationComplete) {
|
||||
rearrangeBlocked.value = true
|
||||
rearrangeBlockedMessage.value = '该承运商资质附件不完整,请先补全后再调整运输'
|
||||
return
|
||||
}
|
||||
if (carrier.auditStatus !== 'approved') {
|
||||
rearrangeBlocked.value = true
|
||||
rearrangeBlockedMessage.value = '该承运商尚未审核通过,请先完成审核后再调整运输'
|
||||
return
|
||||
}
|
||||
if (carrier.transportAgreementExpired) {
|
||||
rearrangeBlocked.value = true
|
||||
rearrangeBlockedMessage.value = '该承运商运输协议已过期,请先更新协议后再调整运输'
|
||||
}
|
||||
}
|
||||
|
||||
async function handleVehicleChange(vehicleId: string, preferredDriverId?: string) {
|
||||
if (!vehicleId) {
|
||||
formData.driverId = ''
|
||||
@@ -704,19 +733,20 @@ async function handleVehicleChange(vehicleId: string, preferredDriverId?: string
|
||||
const vehicle = Array.isArray(vehicleList.value)
|
||||
? vehicleList.value.find(v => v.id === vehicleId)
|
||||
: undefined
|
||||
if (vehicle?.vehicleType === 'self') {
|
||||
resolveSelfCarrierId()
|
||||
} else if (vehicle?.carrierId) {
|
||||
if (vehicle?.carrierId) {
|
||||
formData.carrierId = vehicle.carrierId
|
||||
} else {
|
||||
formData.carrierId = ''
|
||||
}
|
||||
|
||||
vehicleLoading.value = true
|
||||
try {
|
||||
vehicleBundle.value = await loadVehicleViewBundle(vehicleId)
|
||||
if (vehicleBundle.value.detail?.vehicleType === 'self') {
|
||||
resolveSelfCarrierId()
|
||||
if (vehicleBundle.value.detail?.carrierId) {
|
||||
formData.carrierId = vehicleBundle.value.detail.carrierId
|
||||
}
|
||||
applyVehiclePersonnel(vehicleBundle.value.personnelList || [], preferredDriverId)
|
||||
await updateRearrangeBlocked()
|
||||
} finally {
|
||||
vehicleLoading.value = false
|
||||
}
|
||||
@@ -753,6 +783,10 @@ function handleClosed() {
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (rearrangeBlocked.value) {
|
||||
ElMessage.warning(rearrangeBlockedMessage.value || '当前承运商不可调整运输')
|
||||
return
|
||||
}
|
||||
const valid = await formRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
|
||||
@@ -815,7 +849,6 @@ async function handleSubmit() {
|
||||
formData.hasFreight === 1
|
||||
? freightCalc.calculatedFreight.value || freightCalc.freightAmount.value || undefined
|
||||
: undefined,
|
||||
hasInvoice: formData.hasInvoice,
|
||||
remark: formData.remark || undefined,
|
||||
generateDibangPlan: formData.generateDibangPlan
|
||||
}
|
||||
@@ -858,6 +891,10 @@ async function handleSubmit() {
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.pl-arrange-block-alert {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.pl-plan-subtitle {
|
||||
margin: 16px 0 10px;
|
||||
font-size: 13px;
|
||||
|
||||
@@ -50,15 +50,6 @@
|
||||
<span v-else>{{ row.actualQty ?? '—' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="含税单价(元)" min-width="120" align="right">
|
||||
<template #default="{ row }">{{ formatMoneyDisplay(row.taxPrice) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="含税总价(元)" min-width="120" align="right">
|
||||
<template #default="{ row }">{{ formatMoneyDisplay(row.taxSubtotal) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="税费(元)" min-width="100" align="right">
|
||||
<template #default="{ row }">{{ formatMoneyDisplay(row.taxAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="packageSpec" label="包装规格" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column label="送货方式" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
@@ -89,7 +80,6 @@ import type { PlanFormProductRow } from '@/composables/usePlContractToPlan'
|
||||
import { getDeliveryMethodLabel } from '@/composables/usePlContractToPlan'
|
||||
import PlProductNameHazardCell from '@/components/business/pl-product-name-hazard-cell/index.vue'
|
||||
import DictTag from '@/components/DictTag/index.vue'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
|
||||
defineProps<{
|
||||
products: PlanFormProductRow[]
|
||||
|
||||
@@ -100,9 +100,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="运输公司">
|
||||
<el-input v-if="isSelfVehicle" :model-value="SELF_CARRIER_NAME" readonly />
|
||||
<el-select
|
||||
v-else
|
||||
v-model="formData.carrierId"
|
||||
placeholder="请选择运输公司"
|
||||
filterable
|
||||
@@ -222,9 +220,6 @@
|
||||
@change="freightCalc.markFreightAmountTouched()"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否开票">
|
||||
<el-switch v-model="formData.hasInvoice" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -258,8 +253,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<ContractSelector v-model:visible="contractSelectorVisible" @confirm="handleContractConfirm" />
|
||||
<ContractDetailDialog v-if="contractDetailDialogActive" ref="contractDetailRef" />
|
||||
<ContractSelector v-model:visible="contractSelectorVisible" hide-amount @confirm="handleContractConfirm" />
|
||||
<ContractDetailDialog v-if="contractDetailDialogActive" ref="contractDetailRef" hide-product-price />
|
||||
<PlVehicleViewDialog
|
||||
v-model:visible="vehicleViewVisible"
|
||||
:vehicle-id="formData.vehicleId"
|
||||
@@ -296,8 +291,6 @@ const ContractDetailDialog = defineAsyncComponent(
|
||||
() => import('@/views/crm/contract/components/ContractDetailDialog.vue')
|
||||
)
|
||||
|
||||
const SELF_CARRIER_NAME = '安庆市山峡化工有限公司'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
planId?: string
|
||||
@@ -388,7 +381,6 @@ const formData = reactive({
|
||||
carrierId: '',
|
||||
distanceKm: null as number | null,
|
||||
freightType: 'by_weight',
|
||||
hasInvoice: 0,
|
||||
remark: '',
|
||||
products: [] as PlanFormProductRow[]
|
||||
})
|
||||
@@ -414,13 +406,8 @@ const freightUnitLabel = computed(() => {
|
||||
})
|
||||
|
||||
const selectedVehicle = computed(() => vehicleList.value.find(item => item.id === formData.vehicleId))
|
||||
const isSelfVehicle = computed(
|
||||
() =>
|
||||
selectedVehicle.value?.vehicleType === 'self' || vehicleBundle.value?.detail?.vehicleType === 'self'
|
||||
)
|
||||
|
||||
const summaryCarrierName = computed(() => {
|
||||
if (isSelfVehicle.value) return SELF_CARRIER_NAME
|
||||
const carrier = carrierList.value.find(item => item.id === formData.carrierId)
|
||||
if (carrier?.carrierName) return carrier.carrierName
|
||||
return vehicleBundle.value?.detail?.carrierName || ''
|
||||
@@ -445,12 +432,6 @@ function buildContractBasicFields(contract: ContractVO): ViewField[] {
|
||||
value: settlementMethod ? dictStore.getLabel('crm_settlement_method', settlementMethod) : '—',
|
||||
tagType: settlementMethod ? dictStore.getTagType('crm_settlement_method', settlementMethod) : undefined,
|
||||
long: false
|
||||
},
|
||||
{
|
||||
label: '合同金额(元)',
|
||||
value: formatMoneyDisplay(contract.amount),
|
||||
money: true,
|
||||
long: false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -470,11 +451,6 @@ const contractProductRows = computed<PlanFormProductRow[]>(() =>
|
||||
mapContractProducts(contractSnapshot.value?.products)
|
||||
)
|
||||
|
||||
function resolveSelfCarrierId() {
|
||||
const matched = carrierList.value.find(item => item.carrierName === SELF_CARRIER_NAME)
|
||||
formData.carrierId = matched?.id || ''
|
||||
}
|
||||
|
||||
function enrichProductsFromContract(products: PlanFormProductRow[], contract?: ContractVO | null) {
|
||||
if (!contract?.products?.length) return products
|
||||
const contractMap = new Map(
|
||||
@@ -556,8 +532,7 @@ function buildTransportBlock(options?: { allowClear?: boolean }) {
|
||||
if (!options?.allowClear) return undefined
|
||||
return {
|
||||
vehicleId: '',
|
||||
freightType: formData.freightType,
|
||||
hasInvoice: formData.hasInvoice
|
||||
freightType: formData.freightType
|
||||
}
|
||||
}
|
||||
return {
|
||||
@@ -570,8 +545,7 @@ function buildTransportBlock(options?: { allowClear?: boolean }) {
|
||||
formData.freightType === 'by_weight' ? freightCalc.transportQty.value ?? undefined : undefined,
|
||||
freightPrice:
|
||||
formData.freightType === 'by_weight' ? freightCalc.freightPrice.value ?? undefined : undefined,
|
||||
freightAmount: freightCalc.freightAmount.value ?? undefined,
|
||||
hasInvoice: formData.hasInvoice
|
||||
freightAmount: freightCalc.freightAmount.value ?? undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,7 +596,6 @@ async function restoreFreightFromTransport(plan: Plan) {
|
||||
const transport = (transportRes.data ?? transportRes) as {
|
||||
distanceKm?: number
|
||||
freightType?: string
|
||||
hasInvoice?: number
|
||||
freightPrice?: number
|
||||
transportQty?: number
|
||||
freightAmount?: number
|
||||
@@ -631,7 +604,6 @@ async function restoreFreightFromTransport(plan: Plan) {
|
||||
}
|
||||
formData.distanceKm = transport.distanceKm != null ? Number(transport.distanceKm) : null
|
||||
formData.freightType = transport.freightType || 'by_weight'
|
||||
formData.hasInvoice = transport.hasInvoice ?? 0
|
||||
if (transport.carrierId) {
|
||||
formData.carrierId = transport.carrierId
|
||||
}
|
||||
@@ -689,7 +661,6 @@ function resetForm() {
|
||||
carrierId: '',
|
||||
distanceKm: null,
|
||||
freightType: 'by_weight',
|
||||
hasInvoice: 0,
|
||||
remark: '',
|
||||
products: []
|
||||
})
|
||||
@@ -821,7 +792,7 @@ function handleClosed() {
|
||||
async function loadMasterData() {
|
||||
try {
|
||||
const [vehicleRes, carrierRes] = await Promise.all([
|
||||
vehicleApi.list({ vehicleStatus: 'available' }),
|
||||
vehicleApi.list({ vehicleStatus: 'available', excludeIneligibleCarrier: true }),
|
||||
carrierApi.list({ status: '1' })
|
||||
])
|
||||
vehicleList.value = (vehicleRes.data ?? vehicleRes) || []
|
||||
@@ -877,17 +848,17 @@ async function handleVehicleChange(vehicleId: string) {
|
||||
}
|
||||
|
||||
const vehicle = vehicleList.value.find(item => item.id === vehicleId)
|
||||
if (vehicle?.vehicleType === 'self') {
|
||||
resolveSelfCarrierId()
|
||||
} else if (vehicle?.carrierId) {
|
||||
if (vehicle?.carrierId) {
|
||||
formData.carrierId = vehicle.carrierId
|
||||
} else {
|
||||
formData.carrierId = ''
|
||||
}
|
||||
|
||||
vehicleLoading.value = true
|
||||
try {
|
||||
vehicleBundle.value = await loadVehicleViewBundle(vehicleId)
|
||||
if (vehicleBundle.value.detail?.vehicleType === 'self') {
|
||||
resolveSelfCarrierId()
|
||||
if (vehicleBundle.value.detail?.carrierId) {
|
||||
formData.carrierId = vehicleBundle.value.detail.carrierId
|
||||
}
|
||||
resolveDefaultDriver(vehicleBundle.value.personnelList || [])
|
||||
} finally {
|
||||
|
||||
@@ -341,7 +341,7 @@
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<ContractDetailDialog v-if="contractDetailDialogActive" ref="contractDetailRef" />
|
||||
<ContractDetailDialog v-if="contractDetailDialogActive" ref="contractDetailRef" hide-product-price />
|
||||
<PlVehicleViewDialog
|
||||
v-model:visible="vehicleViewVisible"
|
||||
:vehicle-id="plan?.vehicleId"
|
||||
@@ -376,6 +376,7 @@
|
||||
import {
|
||||
formatPlanProductListText,
|
||||
getPlFreightTypeLabel,
|
||||
getPlHasInvoiceLabelFromSettlement,
|
||||
getPlPlanCompanyLabel,
|
||||
getPlPlanTypeLabel,
|
||||
getPlYesNoLabel,
|
||||
@@ -388,8 +389,6 @@
|
||||
() => import('@/views/crm/contract/components/ContractDetailDialog.vue')
|
||||
)
|
||||
|
||||
const SELF_CARRIER_NAME = '安庆市山峡化工有限公司'
|
||||
|
||||
const dictStore = useDictStore()
|
||||
|
||||
const props = withDefaults(
|
||||
@@ -409,6 +408,7 @@
|
||||
plan,
|
||||
products,
|
||||
transport,
|
||||
freightSettlement,
|
||||
vehicleBundle,
|
||||
contractSnapshot,
|
||||
dibangRows,
|
||||
@@ -516,7 +516,6 @@
|
||||
if (transport.value?.carrierName) return transport.value.carrierName
|
||||
const vehicle = vehicleBundle.value?.detail
|
||||
if (vehicle?.carrierName) return vehicle.carrierName
|
||||
if (vehicle?.vehicleType === 'self') return SELF_CARRIER_NAME
|
||||
return ''
|
||||
})
|
||||
|
||||
@@ -608,7 +607,7 @@
|
||||
}
|
||||
fields.push(
|
||||
{ label: '运费金额(元)', value: displayFreightAmount.value, money: true },
|
||||
{ label: '是否开票', value: getPlYesNoLabel(transport.value?.hasInvoice) }
|
||||
{ label: '是否开票', value: getPlHasInvoiceLabelFromSettlement(freightSettlement.value) }
|
||||
)
|
||||
return fields
|
||||
})
|
||||
@@ -637,8 +636,7 @@
|
||||
value: c.startDate && c.endDate ? `${c.startDate} ~ ${c.endDate}` : '—',
|
||||
long: false
|
||||
},
|
||||
{ label: '结算方式', value: settlementLabel },
|
||||
{ label: '合同金额(元)', value: formatMoneyDisplay(c.amount), money: true }
|
||||
{ label: '结算方式', value: settlementLabel }
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<div class="vehicle-cert-part-tabs">
|
||||
<el-tabs v-if="hasTrailer" v-model="activePart" class="vehicle-cert-part-tabs__tabs">
|
||||
<el-tab-pane :label="headTabLabel" name="head">
|
||||
<PlCertFormList
|
||||
ref="headCertFormRef"
|
||||
v-model="headCertList"
|
||||
:title="headTabLabel"
|
||||
:upload-folder="uploadFolder"
|
||||
:cert-name-options="certNameOptions"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="trailerTabLabel" name="trailer">
|
||||
<PlCertFormList
|
||||
ref="trailerCertFormRef"
|
||||
v-model="trailerCertList"
|
||||
:title="trailerTabLabel"
|
||||
:upload-folder="uploadFolder"
|
||||
:cert-name-options="certNameOptions"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<PlCertFormList
|
||||
v-else
|
||||
ref="headCertFormRef"
|
||||
v-model="headCertList"
|
||||
:title="title"
|
||||
:upload-folder="uploadFolder"
|
||||
:cert-name-options="certNameOptions"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import PlCertFormList from '@/components/business/pl-cert-form-list/index.vue'
|
||||
import type { CertRow } from '@/api/pl'
|
||||
import {
|
||||
PL_VEHICLE_CERT_PART_HEAD,
|
||||
PL_VEHICLE_CERT_PART_TRAILER,
|
||||
mergeVehicleCertsByPart,
|
||||
splitVehicleCertsByPart
|
||||
} from '@/utils/pl-vehicle-cert'
|
||||
|
||||
type CertRowWithKey = CertRow & { _key?: string }
|
||||
type CertFormListRef = InstanceType<typeof PlCertFormList>
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
licensePlate?: string
|
||||
trailerNo?: string
|
||||
uploadFolder: string
|
||||
certNameOptions?: string[]
|
||||
title?: string
|
||||
}>(),
|
||||
{
|
||||
licensePlate: '',
|
||||
trailerNo: '',
|
||||
certNameOptions: () => [],
|
||||
title: '车辆证书'
|
||||
}
|
||||
)
|
||||
|
||||
const certList = defineModel<CertRowWithKey[]>('modelValue', { default: () => [] })
|
||||
|
||||
const activePart = ref(PL_VEHICLE_CERT_PART_HEAD)
|
||||
const headCertFormRef = ref<CertFormListRef>()
|
||||
const trailerCertFormRef = ref<CertFormListRef>()
|
||||
|
||||
const hasTrailer = computed(() => Boolean(props.trailerNo?.trim()))
|
||||
|
||||
const headTabLabel = computed(() => props.licensePlate?.trim() || '主车')
|
||||
const trailerTabLabel = computed(() => props.trailerNo?.trim() || '挂车')
|
||||
|
||||
const headCertList = computed<CertRowWithKey[]>({
|
||||
get() {
|
||||
return splitVehicleCertsByPart(certList.value).headCerts
|
||||
},
|
||||
set(value) {
|
||||
syncCertList(value, trailerCertList.value)
|
||||
}
|
||||
})
|
||||
|
||||
const trailerCertList = computed<CertRowWithKey[]>({
|
||||
get() {
|
||||
return splitVehicleCertsByPart(certList.value).trailerCerts
|
||||
},
|
||||
set(value) {
|
||||
syncCertList(headCertList.value, value)
|
||||
}
|
||||
})
|
||||
|
||||
function syncCertList(headCerts: CertRowWithKey[], trailerCerts: CertRowWithKey[]) {
|
||||
certList.value = mergeVehicleCertsByPart(headCerts, trailerCerts, hasTrailer.value)
|
||||
}
|
||||
|
||||
watch(hasTrailer, value => {
|
||||
if (!value) {
|
||||
activePart.value = PL_VEHICLE_CERT_PART_HEAD
|
||||
certList.value = mergeVehicleCertsByPart(
|
||||
splitVehicleCertsByPart(certList.value).headCerts,
|
||||
[],
|
||||
false
|
||||
)
|
||||
trailerCertFormRef.value?.clearUploadRefs()
|
||||
return
|
||||
}
|
||||
if (activePart.value !== PL_VEHICLE_CERT_PART_TRAILER) {
|
||||
activePart.value = PL_VEHICLE_CERT_PART_HEAD
|
||||
}
|
||||
})
|
||||
|
||||
function hasUploading() {
|
||||
return Boolean(
|
||||
headCertFormRef.value?.hasUploading?.() || trailerCertFormRef.value?.hasUploading?.()
|
||||
)
|
||||
}
|
||||
|
||||
function clearUploadRefs() {
|
||||
headCertFormRef.value?.clearUploadRefs()
|
||||
trailerCertFormRef.value?.clearUploadRefs()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
hasUploading,
|
||||
clearUploadRefs
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.vehicle-cert-part-tabs {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.vehicle-cert-part-tabs__tabs {
|
||||
:deep(.el-tabs__header) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.cert-form-section) {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="vehicle-cert-part-view">
|
||||
<el-tabs v-if="hasTrailer" v-model="activePart" class="vehicle-cert-part-view__tabs">
|
||||
<el-tab-pane :label="headTabLabel" name="head">
|
||||
<PlCertViewList :cert-list="headCertList" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="trailerTabLabel" name="trailer">
|
||||
<PlCertViewList :cert-list="trailerCertList" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<PlCertViewList v-else :cert-list="headCertList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import PlCertViewList from '@/components/business/pl-cert-view-list/index.vue'
|
||||
import type { CertRead } from '@/api/pl'
|
||||
import {
|
||||
PL_VEHICLE_CERT_PART_HEAD,
|
||||
splitVehicleCertsByPart
|
||||
} from '@/utils/pl-vehicle-cert'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
certList?: CertRead[]
|
||||
licensePlate?: string
|
||||
trailerNo?: string
|
||||
}>(),
|
||||
{
|
||||
certList: () => [],
|
||||
licensePlate: '',
|
||||
trailerNo: ''
|
||||
}
|
||||
)
|
||||
|
||||
const activePart = ref(PL_VEHICLE_CERT_PART_HEAD)
|
||||
|
||||
const hasTrailer = computed(() => Boolean(props.trailerNo?.trim()))
|
||||
const headTabLabel = computed(() => props.licensePlate?.trim() || '主车')
|
||||
const trailerTabLabel = computed(() => props.trailerNo?.trim() || '挂车')
|
||||
|
||||
const headCertList = computed(() => splitVehicleCertsByPart(props.certList).headCerts)
|
||||
const trailerCertList = computed(() => splitVehicleCertsByPart(props.certList).trailerCerts)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.vehicle-cert-part-view__tabs {
|
||||
:deep(.el-tabs__header) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<el-tabs v-model="activeTab" class="vehicle-detail-tabs">
|
||||
<el-tab-pane label="车辆证书" name="vehicle-cert">
|
||||
<PlCertViewList :cert-list="certList" />
|
||||
<PlVehicleCertPartView
|
||||
:cert-list="certList"
|
||||
:license-plate="licensePlate"
|
||||
:trailer-no="trailerNo"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane
|
||||
v-for="(person, index) in personnelList"
|
||||
@@ -15,7 +19,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import PlCertViewList from '@/components/business/pl-cert-view-list/index.vue'
|
||||
import PlVehicleCertPartView from '@/components/business/pl-vehicle-cert-part-view/index.vue'
|
||||
import PersonnelViewPanel from '@/components/business/pl-vehicle-personnel-view-list/PersonnelViewPanel.vue'
|
||||
import type { CertRead } from '@/api/pl'
|
||||
import type { VehiclePersonnelViewRow } from '@/components/business/pl-vehicle-personnel-view-list/index.vue'
|
||||
@@ -23,10 +27,14 @@ import type { VehiclePersonnelViewRow } from '@/components/business/pl-vehicle-p
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
certList?: CertRead[]
|
||||
licensePlate?: string
|
||||
trailerNo?: string
|
||||
personnelList?: VehiclePersonnelViewRow[]
|
||||
}>(),
|
||||
{
|
||||
certList: () => [],
|
||||
licensePlate: '',
|
||||
trailerNo: '',
|
||||
personnelList: () => []
|
||||
}
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user