diff --git a/src/api/business/transport-plan.js b/src/api/business/transport-plan.js index b43f2d6..4a1f813 100644 --- a/src/api/business/transport-plan.js +++ b/src/api/business/transport-plan.js @@ -10,7 +10,6 @@ export const submit = api.submit; export const dispatch = api.dispatch; export const remove = api.remove; export const copy = api.copy; -export const cancel = api.cancel; export const complete = api.complete; export const importTransportPlan = ({ diff --git a/src/api/common.js b/src/api/common.js index b89c0e5..854e4d2 100644 --- a/src/api/common.js +++ b/src/api/common.js @@ -34,7 +34,8 @@ export const exportBlob = (url, params, options = {}) => { }); }; -export const importBlob = (url, file) => { +export const importBlob = (url, file, options = {}) => { + const { timeout = 60000 } = options; const formData = new FormData(); formData.append('file', file); return request({ @@ -42,6 +43,6 @@ export const importBlob = (url, file) => { method: 'post', data: formData, responseType: 'blob', - timeout: 60000, + timeout, }); }; diff --git a/src/option/business/common.js b/src/option/business/common.js index 7ac8431..9b15eac 100644 --- a/src/option/business/common.js +++ b/src/option/business/common.js @@ -49,7 +49,6 @@ export const waybillStatusOptions = [ export const dataSourceOptions = [ { label: '手工创建', value: '手工创建' }, { label: '批量导入', value: '批量导入' }, - { label: '模板生成', value: '模板生成' }, { label: '计划调度', value: '计划调度' }, ]; diff --git a/src/option/business/shipping-template.js b/src/option/business/shipping-template.js index 54e1fa0..eb90940 100644 --- a/src/option/business/shipping-template.js +++ b/src/option/business/shipping-template.js @@ -269,7 +269,7 @@ export const option = { order: 270, hide: true, labelWidth: '0px', - className: 'business-crud-page__full-form-item', + className: 'shipping-template-page__full-form-item', }, { label: '运费信息', prop: 'freightJson', hide: true, display: false }, { @@ -290,7 +290,7 @@ export const option = { order: 250, hide: true, labelWidth: '0px', - className: 'business-crud-page__full-form-item', + className: 'shipping-template-page__full-form-item', }, { label: '项目ID', prop: 'projectId', hide: true, display: false }, { label: '客户合同ID', prop: 'contractId', hide: true, display: false }, @@ -300,7 +300,14 @@ export const option = { { label: '收货地', prop: 'arrivalName', hide: true, display: false }, { label: '收货联系人', prop: 'arrivalContact', hide: true, display: false }, { label: '收货联系方式', prop: 'arrivalPhone', hide: true, display: false }, - { label: '所属组织', prop: 'deptName', minWidth: 150, addDisplay: false, editDisplay: false, display: false }, + { + label: '所属组织', + prop: 'deptName', + minWidth: 150, + addDisplay: false, + editDisplay: false, + display: false, + }, ...auditColumns.map(column => ({ ...column, display: false, diff --git a/src/option/business/transport-plan.js b/src/option/business/transport-plan.js index ec940fb..d15c736 100644 --- a/src/option/business/transport-plan.js +++ b/src/option/business/transport-plan.js @@ -66,17 +66,11 @@ const getSecondCargoTypeName = item => '货物'; const formatGoodsInfo = row => { - const rows = [ - row.goodsJson, - row.goodsList, - row.goodsRows, - row.cargoList, - row.cargoRows, - row.goods, - ] - .map(source => parseGoodsRows(source)) - .filter(source => source.length) - .sort((left, right) => right.length - left.length)[0] || []; + const rows = + [row.goodsJson, row.goodsList, row.goodsRows, row.cargoList, row.cargoRows, row.goods] + .map(source => parseGoodsRows(source)) + .filter(source => source.length) + .sort((left, right) => right.length - left.length)[0] || []; if (!rows.length) return row.goodsInfo || ''; const groups = rows.reduce((result, item = {}) => { @@ -176,9 +170,12 @@ const formatDispatchProgress = row => { } } const lines = Array.from(summary.entries()).map( - ([unit, item]) => `${formatDispatchQuantity(item.assigned)}/${formatDispatchQuantity(item.total)}${unit}` + ([unit, item]) => + `${formatDispatchQuantity(item.assigned)}/${formatDispatchQuantity(item.total)}${unit}` ); - return lines.length ? lines.join('\n') : row.dispatchProgressName || row.dispatchProgress || row.progress || ''; + return lines.length + ? lines.join('\n') + : row.dispatchProgressName || row.dispatchProgress || row.progress || ''; }; const auditColumn = prop => ({ ...auditColumns.find(item => item.prop === prop) }); @@ -188,7 +185,6 @@ export const config = { permission: 'transport_plan', exportUrl: '/blade-transport/transport-plan/export-transport-plan', exportName: '运输计划', - enableAllDept: false, enableProjectSelect: true, projectQueryParams: { approvalStatuses: 'approved,change_approved', @@ -202,7 +198,7 @@ export const config = { enableTransportPlanCreateActions: true, enableTransportPlanImport: true, fixedTransportAddressType: true, - transportPlanImportTitle: '导入发货计划', + transportPlanImportTitle: '导入运输计划', transportPlanTemplateUrl: '/blade-transport/transport-plan/export-template', attachmentTitle: '附件', searchRangeMap: { @@ -319,7 +315,6 @@ export const option = { prop: 'goodsInfo', formatter: row => formatGoodsInfo(row), minWidth: 320, - className: 'business-crud-page__goods-info-cell', overHidden: false, showOverflowTooltip: false, addDisplay: false, @@ -331,7 +326,6 @@ export const option = { prop: 'dispatchProgress', formatter: row => formatDispatchProgress(row), minWidth: 120, - className: 'business-crud-page__dispatch-progress-cell', overHidden: false, showOverflowTooltip: false, addDisplay: false, @@ -397,6 +391,7 @@ export const option = { span: 6, order: 350, minWidth: 150, + rules: [{ required: true, message: '请选择计划开始日期', trigger: 'change' }], }, { label: '计划结束日期', @@ -407,6 +402,7 @@ export const option = { span: 6, order: 340, minWidth: 150, + rules: [{ required: true, message: '请选择计划结束日期', trigger: 'change' }], }, { label: '数据来源', @@ -606,7 +602,7 @@ export const option = { order: 270, hide: true, labelWidth: '0px', - className: 'business-crud-page__full-form-item', + className: 'transport-plan-page__full-form-item', }, { label: '', @@ -627,7 +623,7 @@ export const option = { order: 260, hide: true, labelWidth: '0px', - className: 'business-crud-page__full-form-item', + className: 'transport-plan-page__full-form-item', }, ]) ), diff --git a/src/option/business/waybill-manage.js b/src/option/business/waybill-manage.js index 4ddf551..8791e50 100644 --- a/src/option/business/waybill-manage.js +++ b/src/option/business/waybill-manage.js @@ -867,7 +867,7 @@ export const option = { order: 760, hide: true, labelWidth: '0px', - className: 'business-crud-page__full-form-item', + className: 'waybill-manage-page__full-form-item', }, { label: '实际发货时间', @@ -979,7 +979,7 @@ export const option = { order: 740, hide: true, labelWidth: '0px', - className: 'business-crud-page__full-form-item', + className: 'waybill-manage-page__full-form-item', }, { label: '所属组织', diff --git a/src/utils/import-excel.js b/src/utils/import-excel.js index 8e105bb..425d5df 100644 --- a/src/utils/import-excel.js +++ b/src/utils/import-excel.js @@ -7,8 +7,20 @@ import 'nprogress/nprogress.css'; const excelPattern = /\.(xls|xlsx)$/; const isExcelResponse = res => { - const contentType = res.headers['content-type'] || res.data.type || ''; - return contentType.includes('application/vnd.ms-excel'); + const headers = res.headers || {}; + const contentType = String(headers['content-type'] || res.data?.type || '').toLowerCase(); + const contentDisposition = String(headers['content-disposition'] || '').toLowerCase(); + if ( + contentType.includes('application/vnd.ms-excel') || + contentType.includes('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + ) { + return true; + } + // 网关或对象存储可能将 Excel 流标记为二进制类型,此时通过下载文件名识别。 + return ( + contentType.includes('application/octet-stream') && + /\.(xls|xlsx)(?:["';]|$)/.test(contentDisposition) + ); }; const parseBlobJson = async blob => { @@ -44,9 +56,10 @@ const decorateFailExcel = async (blob, businessName, failDetailDecorator) => { * @param {Function} [onSuccess] 导入成功回调 * @param {Object} [options] 扩展项 * @param {Function} [options.failDetailDecorator] 失败明细加工函数,用于对后端返回的失败 Excel 标红/改写原因 + * @param {Number} [options.timeout=60000] 导入请求超时时间(毫秒) */ export const openImportDialog = (vm, businessName, onSuccess, options = {}) => { - const { failDetailDecorator } = options; + const { failDetailDecorator, timeout = 60000 } = options; const excelOption = vm.excelOption || vm.excelOptionConfig; const column = vm.findColumn(excelOption?.column || [], 'excelFile'); if (!column) { @@ -54,12 +67,15 @@ export const openImportDialog = (vm, businessName, onSuccess, options = {}) => { return; } column.httpRequest = (option, uploadColumn) => - handleImportExcel(vm, option, uploadColumn, businessName, onSuccess, { failDetailDecorator }); + handleImportExcel(vm, option, uploadColumn, businessName, onSuccess, { + failDetailDecorator, + timeout, + }); vm.excelBox = true; }; export const handleImportExcel = async (vm, option, column, businessName, onSuccess, options = {}) => { - const { failDetailDecorator } = options; + const { failDetailDecorator, timeout = 60000 } = options; const file = option.file; const fileName = file && file.name ? file.name.toLowerCase() : ''; if (!excelPattern.test(fileName)) { @@ -77,7 +93,7 @@ export const handleImportExcel = async (vm, option, column, businessName, onSucc background: 'rgba(255, 255, 255, 0.7)', }); try { - const res = await importBlob(column.action, file); + const res = await importBlob(column.action, file, { timeout }); if (isExcelResponse(res)) { const decorated = await decorateFailExcel(res.data, businessName, failDetailDecorator); downloadXls( diff --git a/src/views/base/insurance-ocr-template.vue b/src/views/base/insurance-ocr-template.vue index 60fb726..d1085de 100644 --- a/src/views/base/insurance-ocr-template.vue +++ b/src/views/base/insurance-ocr-template.vue @@ -201,7 +201,7 @@ export default { }, onLoad() { this.loading = true; - getList(this.page.currentPage, this.page.pageSize, this.query) + return getList(this.page.currentPage, this.page.pageSize, this.query) .then(res => { const pageData = res.data.data || {}; this.data = pageData.records || []; @@ -268,10 +268,10 @@ export default { this.mappingRows.map(item => ({ key: item.key, value: String(item.value || '').trim() })) ), }) - .then(() => { + .then(async () => { this.$message.success('操作成功'); this.dialogVisible = false; - this.onLoad(); + await this.onLoad(); }) .finally(() => { this.submitLoading = false; diff --git a/src/views/base/port-terminal.vue b/src/views/base/port-terminal.vue index 94ed862..4052c6f 100644 --- a/src/views/base/port-terminal.vue +++ b/src/views/base/port-terminal.vue @@ -555,9 +555,8 @@ export default { editDisplay: false, dicData: [ { label: '全部', value: '' }, - { label: '初始化录入', value: '初始化录入' }, { label: '批量导入', value: '批量导入' }, - { label: '手动录入', value: '手动录入' }, + { label: '手动输入', value: '手动录入' }, ], }, { diff --git a/src/views/base/railway-station.vue b/src/views/base/railway-station.vue index 3563b03..5b04b5a 100644 --- a/src/views/base/railway-station.vue +++ b/src/views/base/railway-station.vue @@ -150,7 +150,7 @@ export default { }; const validateTelegraphCode = (rule, value, callback) => { if (!/^[A-Z]{3}$/.test(String(value || '').toUpperCase())) { - callback(new Error('电报码格式不正确或已存在')); + callback(new Error('电报略码格式不正确或已存在')); } else { callback(); } @@ -246,12 +246,12 @@ export default { change: ({ value }) => this.handleTmisChange(value), }, { - label: '电报码', + label: '电报略码', prop: 'telegraphCode', minWidth: 100, maxlength: 3, rules: [ - { required: true, message: '电报码格式不正确或已存在', trigger: 'blur' }, + { required: true, message: '电报略码格式不正确或已存在', trigger: 'blur' }, { validator: validateTelegraphCode, trigger: 'blur' }, ], change: ({ value }) => this.handleTelegraphChange(value), @@ -454,9 +454,8 @@ export default { editDisplay: false, dicData: [ { label: '全部', value: '' }, - { label: '初始化导入', value: '初始化导入' }, - { label: '批量', value: '批量' }, - { label: '手动', value: '手动' }, + { label: '批量导入', value: '批量' }, + { label: '手动导入', value: '手动' }, ], }, ], @@ -695,13 +694,13 @@ export default { return Promise.reject(new Error('该TMIS编码已存在')); } if (telegraphExists) { - return Promise.reject(new Error('电报码格式不正确或已存在')); + return Promise.reject(new Error('电报略码格式不正确或已存在')); } return Promise.resolve(); }); }, handleSubmitError(error, loading) { - const uniqueMessages = ['该TMIS编码已存在', '电报码格式不正确或已存在']; + const uniqueMessages = ['该TMIS编码已存在', '电报略码格式不正确或已存在']; if (uniqueMessages.includes(error.message)) { this.$message.warning(error.message); } @@ -782,10 +781,18 @@ export default { }); }, beforeOpen(done, type) { + const tmisCodeColumn = this.findColumn('tmisCode'); + if (tmisCodeColumn) { + // Avue 会复用列配置,打开不同记录前必须先清除上一次编辑状态。 + tmisCodeColumn.disabled = false; + } if (['edit', 'view'].includes(type)) { this.regionInitializing = true; getDetail(this.form.id).then(res => { const detail = this.normalizeRegionForm(res.data.data || {}); + if (tmisCodeColumn) { + tmisCodeColumn.disabled = type === 'edit' && Number(detail.status) === 1; + } this.loadCityOptions(detail.provinceCode) .then(() => { this.ensureCityOption(detail); @@ -851,7 +858,7 @@ export default { }); }, handleImport() { - openImportDialog(this, '铁路车站主数据'); + openImportDialog(this, '铁路车站主数据', () => this.onLoad(this.page, this.query)); }, handleExport() { this.$confirm('是否导出铁路车站主数据?', '提示', { diff --git a/src/views/base/region.vue b/src/views/base/region.vue index c748c8a..118f9bf 100644 --- a/src/views/base/region.vue +++ b/src/views/base/region.vue @@ -4,7 +4,11 @@
- +
@@ -27,20 +31,8 @@ - - - - - - -
- - - - - - +
搜索 清空 @@ -180,8 +172,6 @@ export default { code: '', name: '', regionLevel: undefined, - dataSource: '', - status: undefined, }, parentOptions: [], syncLoading: false, @@ -203,6 +193,7 @@ export default { }, addBtn: false, menu: false, + defaultExpandAll: false, size: 'default', props: { labelText: '标题', @@ -458,7 +449,13 @@ export default { }); }); }, + hasSearchCriteria() { + return Object.values(this.searchForm).some( + value => value !== '' && value !== undefined && value !== null + ); + }, handleSearch() { + this.treeOption.defaultExpandAll = this.hasSearchCriteria(); this.initTree(); this.regionForm = {}; }, @@ -467,8 +464,6 @@ export default { code: '', name: '', regionLevel: undefined, - dataSource: '', - status: undefined, }; this.handleSearch(); }, @@ -507,6 +502,10 @@ export default { this.$message.warning('请先选择一项区划'); return; } + if (Number(this.regionForm.regionLevel) >= 3) { + this.$message.warning('区县已是末级,不能新增下级'); + return; + } const parentCode = this.regionForm.code; const parentName = this.regionForm.name; const parentGroupCode = this.regionForm.parentCode || this.topCode; @@ -517,8 +516,7 @@ export default { this.regionForm.subCode = ''; this.regionForm.originalCode = ''; this.regionForm.name = ''; - this.regionForm.regionLevel = - this.regionForm.regionLevel === 5 ? 5 : this.regionForm.regionLevel + 1; + this.regionForm.regionLevel = Number(this.regionForm.regionLevel) + 1; this.loadParentOptionsByGroup(parentGroupCode, parentCode, parentName); }, setRegionColumnDisplay(prop, display) { @@ -637,9 +635,14 @@ export default { }, handleImport() { this.syncImportAction(); - openImportDialog(this, '行政区划', () => { - this.initTree(); - }); + openImportDialog( + this, + '行政区划', + () => { + this.initTree(); + }, + { timeout: 180000 } + ); }, syncImportAction() { const column = this.findColumn(this.excelOption.column, 'excelFile'); diff --git a/src/views/business/components/business-crud-page.vue b/src/views/business/components/business-crud-page.vue index c12c759..a58f367 100644 --- a/src/views/business/components/business-crud-page.vue +++ b/src/views/business/components/business-crud-page.vue @@ -2265,7 +2265,7 @@
合同编号 - {{ detailRow.contractNo || detailRow.contractName || '-' }} + {{ detailRow.contractNo || '-' }}
货物信息 @@ -3339,7 +3339,7 @@
合同编号
- {{ dispatchRow.contractName || dispatchRow.contractNo || '-' }} + {{ dispatchRow.contractNo || '-' }}
@@ -3389,7 +3389,11 @@ {{ parseDispatchRoutePoint(dispatchRow, 'start').name || '-' }}
- {{ parseDispatchRoutePoint(dispatchRow, 'start').address || '-' }} + {{ + formatTransportPlanProvinceCityDistrict( + parseDispatchRoutePoint(dispatchRow, 'start').address + ) + }}
{{ parseDispatchRoutePoint(dispatchRow, 'start').contact || '-' }} @@ -3406,7 +3410,11 @@ {{ parseDispatchRoutePoint(dispatchRow, 'end').name || '-' }}
- {{ parseDispatchRoutePoint(dispatchRow, 'end').address || '-' }} + {{ + formatTransportPlanProvinceCityDistrict( + parseDispatchRoutePoint(dispatchRow, 'end').address + ) + }}
{{ parseDispatchRoutePoint(dispatchRow, 'end').contact || '-' }} @@ -3867,7 +3875,7 @@ @@ -4086,7 +4094,7 @@ @@ -5446,6 +5454,7 @@ const defaultDispatchRow = () => ({ transportType: '', carrierType: '承运商', carrierId: '', + carrierContractId: '', carrierName: '', driverId: '', driverName: '', @@ -6255,6 +6264,16 @@ export default { dispatchCarrierLabel() { return this.dispatchItemForm.carrierType === '网货平台' ? '承运方' : '承运商'; }, + dispatchCarrierSelection: { + get() { + return this.dispatchIsCarrierMode + ? this.dispatchItemForm.carrierContractId || '' + : this.dispatchItemForm.carrierName || ''; + }, + set(value) { + this.handleDispatchCarrierChange(value); + }, + }, dispatchStationMode() { return ['water', 'rail', 'air'].includes(this.dispatchTransportMode); }, @@ -12226,6 +12245,8 @@ export default { taskEntryMode: item.taskEntryMode || 'simple', transportType: item.transportType || plan.transportType || '', carrierType: item.carrierType || plan.carrierType || '承运商', + carrierId: item.carrierId || plan.carrierId || '', + carrierContractId: item.carrierContractId || plan.carrierContractId || '', carrierName: item.carrierName || plan.carrierName || '', driverName: item.driverName || plan.driverName || '', driverPhone: item.driverPhone || plan.driverPhone || '', @@ -12643,11 +12664,15 @@ export default { this.dispatchItemForm.trailerVehicleNo = ''; this.dispatchItemForm.escortName = ''; this.dispatchItemForm.escortPhone = ''; - } else if (!this.isDispatchCarrierRequired(nextValue)) { - this.dispatchItemForm.carrierId = ''; - this.dispatchItemForm.carrierName = ''; + } else { + this.dispatchItemForm.carrierContractId = ''; + if (!this.isDispatchCarrierRequired(nextValue)) { + this.dispatchItemForm.carrierId = ''; + this.dispatchItemForm.carrierName = ''; + } } this.dispatchItemForm.carrierType = nextValue; + this.loadDispatchCarrierOptions(this.dispatchRow); }, isDispatchCarrierRequired(carrierType) { return ['承运商', '网货平台'].includes(String(carrierType || '').trim()); @@ -12708,22 +12733,61 @@ export default { if (this.dispatchCarrierLoading) return Promise.resolve(this.dispatchCarrierOptions); const projectId = plan?.projectId; const requestId = ++this.dispatchCarrierRequestId; + const carrierTypeAtRequest = this.dispatchItemForm.carrierType || '承运商'; const project = this.projectOptions.find(item => String(item.id) === String(projectId)); this.dispatchCarrierOptions = this.getProjectCarrierOptions({ ...project, ...plan }); if (!projectId) return Promise.resolve(this.dispatchCarrierOptions); this.dispatchCarrierLoading = true; - return getProjectDetail(projectId) - .then(res => { - if (requestId !== this.dispatchCarrierRequestId) return this.dispatchCarrierOptions; - const detail = res?.data?.data || {}; - this.dispatchCarrierOptions = this.getProjectCarrierOptions({ - ...project, - ...plan, - ...detail, + const carrierContractPromise = getContractList(1, 9999, { + projectId, + projectName: plan?.projectName || project?.projectName, + contractCategory: '承运商合同', + }).then(res => this.getWaybillCarrierContractOptions(extractRecords(res))); + if (carrierTypeAtRequest === '承运商') { + return carrierContractPromise + .then(options => { + if (requestId !== this.dispatchCarrierRequestId) return this.dispatchCarrierOptions; + this.dispatchCarrierOptions = options || []; + const current = this.dispatchCarrierOptions.find( + item => + String(item.carrierContractId) === + String(this.dispatchItemForm.carrierContractId || '') + ); + if (current) { + this.dispatchItemForm.carrierName = current.carrierName; + } else if (!this.dispatchItemForm.carrierContractId) { + const matches = this.dispatchCarrierOptions.filter( + item => String(item.carrierName) === String(this.dispatchItemForm.carrierName || '') + ); + if (matches.length === 1) { + this.dispatchItemForm.carrierContractId = matches[0].carrierContractId; + } + } + return this.dispatchCarrierOptions; + }) + .catch(() => this.dispatchCarrierOptions) + .finally(() => { + if (requestId === this.dispatchCarrierRequestId) { + this.dispatchCarrierLoading = false; + } }); - return this.dispatchCarrierOptions; - }) - .catch(() => this.dispatchCarrierOptions) + } + return Promise.all([ + getProjectDetail(projectId) + .then(res => { + if (requestId !== this.dispatchCarrierRequestId) return this.dispatchCarrierOptions; + const detail = res?.data?.data || {}; + this.dispatchCarrierOptions = this.getProjectCarrierOptions({ + ...project, + ...plan, + ...detail, + }); + return this.dispatchCarrierOptions; + }) + .catch(() => this.dispatchCarrierOptions), + carrierContractPromise.catch(() => []), + ]) + .then(([options]) => options) .finally(() => { if (requestId === this.dispatchCarrierRequestId) { this.dispatchCarrierLoading = false; @@ -12731,9 +12795,19 @@ export default { }); }, handleDispatchCarrierChange(value) { - const carrier = this.dispatchCarrierOptions.find(item => item.value === String(value || '')); + const carrier = this.dispatchCarrierOptions.find(item => { + const optionValue = this.dispatchIsCarrierMode ? item.carrierContractId : item.value; + return String(optionValue || '') === String(value || ''); + }); + if (this.dispatchIsCarrierMode) { + this.dispatchItemForm.carrierContractId = carrier?.carrierContractId || ''; + this.dispatchItemForm.carrierId = ''; + this.dispatchItemForm.carrierName = carrier?.carrierName || ''; + return; + } + this.dispatchItemForm.carrierContractId = ''; this.dispatchItemForm.carrierId = carrier?.id || ''; - this.dispatchItemForm.carrierName = value || ''; + this.dispatchItemForm.carrierName = carrier?.carrierName || value || ''; }, loadDispatchDriverOptions() { if (this.taskDriverLoading) return Promise.resolve([]); @@ -12837,7 +12911,54 @@ export default { }, dispatchItemRemainingQuantity(row = {}) { const unit = this.getDispatchQuantityUnit(row); - let remaining = this.getDispatchRemainingQuantity(unit); + const hasCargoIdentity = Boolean( + String(row.cargoName || row.goodsName || row.name || '').trim() || + String( + row.cargoType || row.secondCargoTypeName || row.goodsType || row.type || '' + ).trim() + ); + const planGoodsRows = this.dispatchPlanGoodsRows; + const cargoKey = this.getDispatchCargoIdentity(row); + + // 完整录入时按货物身份计算,不能把同单位的其他货物数量合并到当前行。 + if (planGoodsRows.length && hasCargoIdentity) { + const matchedPlanRows = planGoodsRows.filter( + goods => this.getDispatchCargoIdentity(goods) === cargoKey + ); + if (!matchedPlanRows.length) return 0; + const total = matchedPlanRows.reduce( + (sum, goods) => + sum + + this.parseDispatchQuantity( + goods.quantity || goods.cargoQuantity || goods.goodsQuantity + ), + 0 + ); + const assigned = this.dispatchRows.reduce((sum, dispatchRow, index) => { + if ( + index === this.dispatchItemIndex || + !this.hasDispatchVehicleIdentifier(dispatchRow) + ) { + return sum; + } + return ( + sum + + this.getDispatchGoodsSourceRows(dispatchRow).reduce((goodsSum, goods) => { + if (this.getDispatchCargoIdentity(goods) !== cargoKey) return goodsSum; + return ( + goodsSum + + this.parseDispatchQuantity( + goods.quantity || goods.cargoQuantity || goods.goodsQuantity + ) + ); + }, 0) + ); + }, 0); + return Math.max(total - assigned, 0); + } + + // 兼容没有货物明细的旧数据,以及尚未选择货物的空白行。 + const remaining = this.getDispatchRemainingQuantity(unit); const editingRow = this.dispatchRows[this.dispatchItemIndex]; if (!editingRow || !this.hasDispatchVehicleIdentifier(editingRow)) return remaining; const editingGoodsRows = this.dispatchGoodsRows(editingRow.goodsJson); @@ -12943,6 +13064,9 @@ export default { if (this.isDispatchCarrierRequired(row.carrierType)) { requiredFields.push(['carrierName', row.carrierType === '网货平台' ? '承运方' : '承运商']); } + if (row.carrierType === '承运商') { + requiredFields.push(['carrierContractId', '承运商合同']); + } const fullEntry = row.taskEntryMode === 'full'; const roadTransport = this.resolveTransportMode(row.transportType) === 'road'; if (fullEntry) { @@ -13056,16 +13180,25 @@ export default { ? this.dispatchItemCargoRows : [this.dispatchItemForm]; if (!this.validateDispatchItemData(this.dispatchItemForm, goodsRows)) return false; - const quantitiesByUnit = new Map(); + const quantityGroups = new Map(); goodsRows.forEach(goods => { const unit = this.getDispatchQuantityUnit(goods); - quantitiesByUnit.set( - unit, - (quantitiesByUnit.get(unit) || 0) + this.parseDispatchQuantity(goods.quantity) + const hasCargoIdentity = Boolean( + String(goods.cargoName || goods.goodsName || goods.name || '').trim() || + String( + goods.cargoType || goods.secondCargoTypeName || goods.goodsType || goods.type || '' + ).trim() ); + const key = hasCargoIdentity + ? `cargo:${this.getDispatchCargoIdentity(goods)}` + : `unit:${unit}`; + const group = quantityGroups.get(key) || { row: goods, quantity: 0 }; + group.quantity += this.parseDispatchQuantity(goods.quantity); + quantityGroups.set(key, group); }); - for (const [unit, quantity] of quantitiesByUnit.entries()) { - const remaining = this.dispatchItemRemainingQuantity({ quantityUnit: unit }); + for (const { row: goods, quantity } of quantityGroups.values()) { + const unit = this.getDispatchQuantityUnit(goods); + const remaining = this.dispatchItemRemainingQuantity(goods); if (quantity - remaining > 1e-8) { this.$message.warning( `本次调度${unit}合计不能超过剩余数量${this.formatDispatchQuantity(remaining)}${unit}` diff --git a/src/views/business/components/master-order-dispatch.vue b/src/views/business/components/master-order-dispatch.vue index 747540b..86ac299 100644 --- a/src/views/business/components/master-order-dispatch.vue +++ b/src/views/business/components/master-order-dispatch.vue @@ -134,7 +134,6 @@ - @@ -143,16 +142,16 @@