diff --git a/src/option/business/waybill-manage.js b/src/option/business/waybill-manage.js index fb9b1fe..5c07a81 100644 --- a/src/option/business/waybill-manage.js +++ b/src/option/business/waybill-manage.js @@ -32,6 +32,23 @@ const parseJsonArray = value => { } }; +const hasProcessConfig = value => { + if (isEmpty(value)) return false; + if (typeof value !== 'string') { + return Array.isArray(value) ? value.length > 0 : typeof value === 'object'; + } + const text = value.trim(); + if (!text) return false; + try { + const result = JSON.parse(text); + return Array.isArray(result) + ? result.length > 0 + : Boolean(result && Object.keys(result).length); + } catch (error) { + return true; + } +}; + const getFirstValue = (row, props) => { const prop = props.find(item => !isEmpty(row[item])); return prop ? row[prop] : ''; @@ -96,6 +113,11 @@ export const config = { exportUrl: '/blade-transport/waybill-manage/export-waybill-manage', exportName: '运单管理', importUrl: '/blade-transport/waybill-manage/import-waybill-manage', + defaultForm: { + carrierType: '承运商', + quantityUnit: '吨', + priceUnit: '元/吨', + }, enableAllDept: false, enableProjectSelect: true, templateCreateTarget: 'waybill-manage', @@ -103,6 +125,13 @@ export const config = { enableShippingPlanSelect: true, enableShippingInfoForm: true, enableTaskInfoForm: true, + enableWaybillFooterFreightSummary: true, + enableDraftSave: true, + saveAsDraft: true, + skipDraftValidation: true, + draftStatusProp: 'businessStatus', + draftStatus: 'draft', + draftSaveText: '暂存', batchDelete: false, actionButtonLikeSearch: true, createText: '新建运单', @@ -118,6 +147,16 @@ export const config = { actions: ['copy', 'cancel', 'reassign', 'complete', 'batchComplete'], statusProp: 'businessStatus', statusTextProp: 'businessStatusName', + formatStatus(row, prop, defaultText) { + if ( + prop === 'businessStatus' && + row.businessStatus === 'pending' && + !hasProcessConfig(row.processJson) + ) { + return '进行中'; + } + return defaultText; + }, deleteStatus: ['draft'], editStatus: ['draft', 'pending'], }; diff --git a/src/views/business/components/business-crud-page.vue b/src/views/business/components/business-crud-page.vue index 1b6ebd1..d31cfd8 100644 --- a/src/views/business/components/business-crud-page.vue +++ b/src/views/business/components/business-crud-page.vue @@ -368,19 +368,19 @@ @@ -2420,18 +2516,21 @@ - + + row._duplicate).length; }, @@ -4479,50 +4581,50 @@ export default { .join('; '); return cargoInfo || this.dispatchRow.goodsInfo || ''; }, - dispatchSummaryTotalQuantity() { - const goodsQuantity = this.dispatchPlanGoodsRows.reduce( - (total, row) => - total + - this.parseDispatchQuantity(row.quantity || row.cargoQuantity || row.goodsQuantity), - 0 - ); - if (goodsQuantity > 0) return goodsQuantity; - return this.parseDispatchQuantity( - this.dispatchRow.totalQuantity || - this.dispatchRow.totalCargoQuantity || - this.dispatchRow.quantity - ); + dispatchSummaryItems() { + const summaryMap = new Map(); + const addQuantity = (unit, field, quantity) => { + const normalizedUnit = unit || '吨'; + if (!summaryMap.has(normalizedUnit)) { + summaryMap.set(normalizedUnit, { unit: normalizedUnit, total: 0, assigned: 0 }); + } + summaryMap.get(normalizedUnit)[field] += this.parseDispatchQuantity(quantity); + }; + const goodsRows = this.dispatchPlanGoodsRows.length + ? this.dispatchPlanGoodsRows + : [this.dispatchRow]; + + goodsRows.forEach(row => { + addQuantity( + this.getDispatchQuantityUnit(row), + 'total', + row.quantity || + row.cargoQuantity || + row.goodsQuantity || + row.totalQuantity || + row.totalCargoQuantity + ); + }); + this.dispatchRows.forEach(row => { + addQuantity(this.getDispatchQuantityUnit(row), 'assigned', row.quantity); + }); + + return Array.from(summaryMap.values()).map(item => ({ + ...item, + remaining: Math.max(item.total - item.assigned, 0), + })); }, - dispatchSummaryAssignedQuantity() { - return this.dispatchRows.reduce( - (total, row) => total + this.parseDispatchQuantity(row.quantity), - 0 - ); - }, - dispatchSummaryRemainingQuantity() { - const total = this.dispatchSummaryTotalQuantity; - const assigned = this.dispatchSummaryAssignedQuantity; - const remain = total - assigned; - return remain > 0 ? remain : 0; - }, - dispatchSummaryUnit() { - const unitRow = this.dispatchPlanGoodsRows.find( - row => row.quantityUnit || row.goodsQuantityUnit || row.unit - ); - return ( - unitRow?.quantityUnit || - unitRow?.goodsQuantityUnit || - unitRow?.unit || - this.dispatchRow.quantityUnit || - this.dispatchRows.find(row => row.quantityUnit)?.quantityUnit || - '吨' - ); + dispatchHasRemainingQuantity() { + return this.dispatchSummaryItems.some(item => item.remaining > 0); }, dispatchSummaryText() { - const total = this.formatDispatchQuantity(this.dispatchSummaryTotalQuantity); - const assigned = this.formatDispatchQuantity(this.dispatchSummaryAssignedQuantity); - const remain = this.formatDispatchQuantity(this.dispatchSummaryRemainingQuantity); - return `共${total}${this.dispatchSummaryUnit} | 已调度 ${assigned}${this.dispatchSummaryUnit},剩余${remain}${this.dispatchSummaryUnit}`; + const formatSummary = field => + this.dispatchSummaryItems + .map(item => `${this.formatDispatchQuantity(item[field])}${item.unit}`) + .join('、'); + return `共${formatSummary('total')} | 已调度 ${formatSummary('assigned')},剩余${formatSummary( + 'remaining' + )}`; }, dispatchAttachmentRows() { return this.parseJsonArray(this.dispatchRow.attachmentsJson); @@ -4738,8 +4840,22 @@ export default { : Number(this.shippingTemplateFreight.freightAmount || 0); return freightAmount + Number(this.shippingTemplateFreight.otherFreightAmount || 0); }, - formatShippingTemplateFooterFreightTotal() { - return this.shippingTemplateFooterFreightTotal.toLocaleString('zh-CN', { + waybillFooterFreightTotal() { + return Number(this.taskFreightAmount || 0) + Number(this.form.otherFeeTotal || 0); + }, + showFooterFreightSummary() { + return ( + this.config.enableShippingTemplateFreight === true || + this.config.enableWaybillFooterFreightSummary === true + ); + }, + footerFreightTotal() { + return this.config.enableShippingTemplateFreight === true + ? this.shippingTemplateFooterFreightTotal + : this.waybillFooterFreightTotal; + }, + formatFooterFreightTotal() { + return this.footerFreightTotal.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2, }); @@ -5053,13 +5169,15 @@ export default { }); }, displayStatus(row, prop) { + const formatStatus = this.config.formatStatus; const textProp = prop === this.config.statusProp ? this.config.statusTextProp : ''; - if (textProp && row[textProp]) { - return row[textProp]; - } const column = this.findColumn(this.option.column, prop); const item = column && (column.dicData || []).find(dic => dic.value === row[prop]); - return item ? item.label : row[prop] || '未知'; + const defaultText = + textProp && row[textProp] ? row[textProp] : item ? item.label : row[prop] || '未知'; + return typeof formatStatus === 'function' + ? formatStatus(row, prop, defaultText) + : defaultText; }, syncContractNoDisabled(type) { if (!this.config.enableContractForm) return; @@ -5223,16 +5341,17 @@ export default { }, normalizeRow(row, saveAsDraft = false) { const submitRow = { ...row }; + const skipValidation = saveAsDraft && this.config.skipDraftValidation === true; if (this.config.enableTransportPlanForm) { - if (!this.validateTransportPlanForm(submitRow)) return false; + if (!skipValidation && !this.validateTransportPlanForm(submitRow)) return false; submitRow.goodsJson = JSON.stringify(this.transportCargoRows); if (this.config.enableShippingTemplateFreight) { - if (!this.validateShippingTemplateFreight()) return false; + if (!skipValidation && !this.validateShippingTemplateFreight()) return false; submitRow.freightJson = this.serializeShippingTemplateFreight(); } } if (this.taskInfoFormEnabled) { - if (!this.validateTaskInfoForm(submitRow)) return false; + if (!skipValidation && !this.validateTaskInfoForm(submitRow)) return false; this.syncTaskInfoToRow(submitRow); } if (this.config.enableAttachmentTable) { @@ -5245,7 +5364,9 @@ export default { submitRow.billingPlanJson = JSON.stringify(this.billingPlanRows); } if (this.config.enableSettlementRule) { - if (this.settlementRuleEnabled && !this.validateSettlementRule()) return false; + if (!skipValidation && this.settlementRuleEnabled && !this.validateSettlementRule()) { + return false; + } submitRow.settlementRuleJson = JSON.stringify(this.normalizeSettlementRule()); } if (this.config.enableReconciliation) { @@ -5255,7 +5376,7 @@ export default { submitRow.changeRecordJson = JSON.stringify(this.changeRecordRows); } if (this.config.saveAsDraft && saveAsDraft) { - submitRow.approvalStatus = 'draft'; + submitRow[this.config.draftStatusProp || 'approvalStatus'] = this.config.draftStatus || 'draft'; } delete submitRow.basicInfoTitle; delete submitRow.templateInfoTitle; @@ -5619,7 +5740,7 @@ export default { this.draftSaveLoading = true; request(submitRow) .then(() => { - this.$message.success('保存成功'); + this.$message.success(`${this.config.draftSaveText || '保存'}成功`); this.onLoad(this.page, this.query); this.closeCrudDialog(); }) @@ -5798,6 +5919,15 @@ export default { const number = Number(match[0]); return Number.isFinite(number) ? number : 0; }, + getDispatchQuantityUnit(row = {}) { + return row.quantityUnit || row.goodsQuantityUnit || row.unit || '吨'; + }, + getDispatchSummaryItem(unit) { + return this.dispatchSummaryItems.find(item => item.unit === unit); + }, + getDispatchRemainingQuantity(unit) { + return this.getDispatchSummaryItem(unit)?.remaining || 0; + }, formatDispatchQuantity(value) { const number = Number(value || 0); if (!Number.isFinite(number)) return '0'; @@ -6060,10 +6190,10 @@ export default { this.form.model = this.form.model || taskInfo.model || goodsInfo.model || ''; this.form.quantity = this.form.quantity || taskInfo.quantity || goodsInfo.quantity || ''; this.form.quantityUnit = - this.form.quantityUnit || taskInfo.quantityUnit || goodsInfo.quantityUnit || ''; + this.form.quantityUnit || taskInfo.quantityUnit || goodsInfo.quantityUnit || '吨'; this.form.mileage = this.form.mileage || taskInfo.mileage || goodsInfo.mileage || ''; this.form.unitPrice = this.form.unitPrice || taskInfo.unitPrice || goodsInfo.unitPrice || ''; - this.form.priceUnit = this.form.priceUnit || taskInfo.priceUnit || goodsInfo.priceUnit || ''; + this.form.priceUnit = this.form.priceUnit || taskInfo.priceUnit || goodsInfo.priceUnit || '元/吨'; this.form.otherFeeTotal = this.form.otherFeeTotal || taskInfo.otherFeeTotal || goodsInfo.otherFeeTotal || ''; this.form.estimatedStartTime = @@ -6111,9 +6241,9 @@ export default { loadTaskCarrierOptions() { if (!this.taskInfoFormEnabled || this.taskCarrierLoading) return Promise.resolve([]); this.taskCarrierLoading = true; - return getCarrierCustomerList(1, 9999, { customerType: '承运商' }) + return getCarrierCustomerList(1, 9999, { customerType: '承运商', status: 1 }) .then(res => { - this.taskCarrierOptions = extractRecords(res); + this.taskCarrierOptions = extractRecords(res).filter(item => Number(item.status) === 1); return this.taskCarrierOptions; }) .finally(() => { @@ -6307,6 +6437,11 @@ export default { handleTaskNumberInput(prop, value) { this.form[prop] = this.normalizeTaskNumber(value); }, + handleTaskMileageInput(value) { + this.form.mileage = this.isRoadTransport + ? String(value || '').replace(/\D/g, '').slice(0, 10) + : this.normalizeTaskNumber(value); + }, normalizeTaskNumber(value) { const text = String(value || '').replace(/[^\d.]/g, ''); const parts = text.split('.'); @@ -6352,6 +6487,9 @@ export default { requiredFields.push(['carrierName', '承运商']); } if (row.carrierType === '自运') { + if (this.isRoadTransport) { + requiredFields.push(['driverName', '司机']); + } requiredFields.push(['driverPhone', '手机号']); } const emptyField = requiredFields.find(([prop]) => this.isBillingFieldEmpty(row[prop])); @@ -6363,8 +6501,10 @@ export default { ['quantity', '数量'], ['mileage', '里程'], ['unitPrice', '单价'], - ['otherFeeTotal', '其他运费合计'], ]; + if (this.isTaskFullMode) { + numberFields.push(['otherFeeTotal', '其他运费合计']); + } const invalidField = numberFields.find(([prop]) => { const value = row[prop]; return value !== undefined && value !== null && value !== '' && Number(value) < 0; @@ -6373,6 +6513,27 @@ export default { this.$message.warning(`${invalidField[1]}不能小于0`); return false; } + if (this.isRoadTransport) { + const invalidLengthField = [ + ['driverName', '司机', 20], + ['driverPhone', '手机号', 20], + ['vehicleNo', '车牌号', 30], + ['trailerVehicleNo', '挂车车牌号', 30], + ['escortName', '押运人', 20], + ['escortPhone', '押运人手机号', 20], + ].find(([prop, , max]) => String(row[prop] || '').length > max); + if (invalidLengthField) { + this.$message.warning(`${invalidLengthField[1]}不能超过${invalidLengthField[2]}个字符`); + return false; + } + if ( + row.mileage && + (!/^\d{1,10}$/.test(String(row.mileage)) || Number(row.mileage) <= 0) + ) { + this.$message.warning('里程必须为不超过10位的正整数'); + return false; + } + } if ( row.estimatedStartTime && row.estimatedEndTime && @@ -8886,17 +9047,51 @@ export default { }; }, formatDispatchCargoInfo(plan = {}, item = {}) { - const cargoName = item.cargoName || item.goodsName || plan.cargoName || ''; - const cargoType = item.cargoType || item.goodsType || plan.cargoType || ''; - const quantity = item.quantity || item.cargoQuantity || item.goodsQuantity || ''; - const unit = item.quantityUnit || item.goodsQuantityUnit || item.unit || ''; - const quantityText = quantity ? `${quantity}${unit}` : ''; - return ( - [cargoName, cargoType, quantityText].filter(Boolean).join(' - ') || plan.goodsInfo || '' - ); + const goodsRows = this.parseJsonArray(plan.goodsJson); + const sourceRows = goodsRows.length ? goodsRows : [item]; + const groups = sourceRows.reduce((result, goods = {}) => { + const cargoType = + goods.cargoType || + goods.secondCargoTypeName || + goods.secondCargoType || + goods.goodsType || + goods.type || + plan.cargoType || + '货物'; + const unit = this.getDispatchQuantityUnit(goods); + if (!result[unit]) { + result[unit] = { + cargoType, + count: 0, + quantity: 0, + unit, + }; + } + result[unit].count += 1; + result[unit].quantity += this.parseDispatchQuantity( + goods.quantity || goods.cargoQuantity || goods.goodsQuantity + ); + return result; + }, {}); + + const cargoInfo = Object.values(groups) + .map( + group => + `${group.cargoType}等${group.count}种货物 | ${this.formatDispatchQuantity( + group.quantity + )}${group.unit || ''}` + ) + .join(';'); + return cargoInfo || plan.goodsInfo || ''; }, openDispatchItemDialog(index = -1, row = defaultDispatchRow()) { - const defaultGoods = this.dispatchPlanGoodsRows[0] || {}; + const defaultSummaryItem = + this.dispatchSummaryItems.find(item => item.remaining > 0) || this.dispatchSummaryItems[0]; + const defaultGoods = + this.dispatchPlanGoodsRows.find( + item => this.getDispatchQuantityUnit(item) === defaultSummaryItem?.unit + ) || this.dispatchPlanGoodsRows[0] || {}; + const defaultUnit = defaultSummaryItem?.unit || this.getDispatchQuantityUnit(defaultGoods); const baseRow = { ...defaultDispatchRow(), transportType: this.dispatchRow.transportType || '', @@ -8910,8 +9105,8 @@ export default { quantity: index >= 0 ? defaultGoods.quantity || '' - : this.formatDispatchQuantity(this.dispatchSummaryRemainingQuantity), - quantityUnit: defaultGoods.quantityUnit || '吨', + : this.formatDispatchQuantity(this.getDispatchRemainingQuantity(defaultUnit)), + quantityUnit: defaultUnit, specification: defaultGoods.specification || '', model: defaultGoods.model || '', packageType: defaultGoods.packageType || '', @@ -9000,17 +9195,18 @@ export default { ...this.dispatchItemForm, attachmentsJson: JSON.stringify(this.dispatchItemAttachmentRows), }; + const quantityUnit = this.getDispatchQuantityUnit(nextRow); const otherDispatchedQuantity = this.dispatchRows.reduce((total, row, index) => { - if (index === this.dispatchItemIndex) return total; + if (index === this.dispatchItemIndex || this.getDispatchQuantityUnit(row) !== quantityUnit) { + return total; + } return total + this.parseDispatchQuantity(row.quantity); }, 0); const currentQuantity = this.parseDispatchQuantity(nextRow.quantity); - const totalQuantity = this.dispatchSummaryTotalQuantity; + const totalQuantity = this.getDispatchSummaryItem(quantityUnit)?.total || 0; if (totalQuantity > 0 && otherDispatchedQuantity + currentQuantity > totalQuantity) { this.$message.warning( - `已调度数量合计不能超过总数量${this.formatDispatchQuantity(totalQuantity)}${ - this.dispatchSummaryUnit - }` + `已调度${quantityUnit}合计不能超过总数量${this.formatDispatchQuantity(totalQuantity)}${quantityUnit}` ); return; } @@ -9039,7 +9235,7 @@ export default { }); }, handleDispatchAddRow() { - if (this.dispatchSummaryRemainingQuantity <= 0) { + if (!this.dispatchHasRemainingQuantity) { this.$message.warning('剩余数量为0,不能新增调度明细'); return; } @@ -9141,7 +9337,7 @@ export default { }, submitDispatch(mode) { this.dispatchSaving = mode; - const shouldCompletePlan = mode === 'submit' && this.dispatchSummaryRemainingQuantity <= 0; + const shouldCompletePlan = mode === 'submit' && !this.dispatchHasRemainingQuantity; this.api .dispatch(this.buildDispatchPayload(mode)) .then(() => { @@ -9329,12 +9525,6 @@ export default { width: 100%; } - &__task-toolbar { - display: flex; - justify-content: flex-end; - margin-bottom: 16px; - } - &__task-form { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));