diff --git a/src/api/business/waybill-manage.js b/src/api/business/waybill-manage.js
index ac31975..eb66cb3 100644
--- a/src/api/business/waybill-manage.js
+++ b/src/api/business/waybill-manage.js
@@ -14,6 +14,13 @@ export const cancel = api.cancel;
export const reassign = api.reassign;
export const complete = api.complete;
export const batchComplete = api.batchComplete;
+/** 确认回单:待确认回单运单完成后转为已完成 */
+export const confirmReturn = id =>
+ request({
+ url: `${baseUrl}/confirm-return`,
+ method: 'post',
+ params: { id },
+ });
export const changeRoute = data =>
request({
url: `${baseUrl}/change-route`,
diff --git a/src/api/system/dept.js b/src/api/system/dept.js
index c7f22de..2964557 100644
--- a/src/api/system/dept.js
+++ b/src/api/system/dept.js
@@ -139,3 +139,10 @@ export const getPlatformCompanySelect = () => {
method: 'get',
});
};
+
+export const getBusinessDeptSelect = () => {
+ return request({
+ url: '/blade-system/dept/business-dept-select',
+ method: 'get',
+ });
+};
diff --git a/src/option/business/common.js b/src/option/business/common.js
index 8221c47..c142601 100644
--- a/src/option/business/common.js
+++ b/src/option/business/common.js
@@ -42,6 +42,7 @@ export const waybillStatusOptions = [
{ label: '草稿', value: 'draft' },
{ label: '待执行', value: 'pending' },
{ label: '进行中', value: 'running' },
+ { label: '待确认回单', value: 'pending_return' },
{ label: '已完成', value: 'completed' },
{ label: '已取消', value: 'cancelled' },
];
diff --git a/src/option/business/waybill-manage.js b/src/option/business/waybill-manage.js
index 9f617d3..9fd695b 100644
--- a/src/option/business/waybill-manage.js
+++ b/src/option/business/waybill-manage.js
@@ -478,7 +478,15 @@ export const config = {
formatStatus(row, prop, defaultText) {
if (prop !== 'businessStatus') return defaultText;
const status = String(row.businessStatus || '');
- const terminalStatuses = ['draft', 'completed', 'cancelled', 'waiting_dispatch', 'dispatching'];
+ const terminalStatuses = [
+ 'draft',
+ 'completed',
+ 'cancelled',
+ 'waiting_dispatch',
+ 'dispatching',
+ 'pending_return',
+ ];
+ if (status === 'pending_return') return '待确认回单';
const needAccept =
row.requireAccept === true ||
row.requireAccept === 1 ||
@@ -497,14 +505,20 @@ export const config = {
getDriverRejectReason,
isDriverRejectedWaybill,
canEdit(row) {
- // 进行中的运单不允许编辑
+ // 进行中、待确认回单不允许编辑
if (isInProgressStatus(row, row.businessStatusName)) {
return false;
}
+ if (String(row.businessStatus || '') === 'pending_return') {
+ return false;
+ }
return !isDriverRejectedWaybill(row);
},
canCancel(row) {
- // 进行中的运单不允许取消
+ // 进行中、待确认回单不允许取消
+ if (String(row.businessStatus || '') === 'pending_return') {
+ return false;
+ }
return !isInProgressStatus(row, row.businessStatusName);
},
deleteStatus: ['draft'],
diff --git a/src/views/business/components/waybill-manage-page.vue b/src/views/business/components/waybill-manage-page.vue
index dad89a3..e7b489f 100644
--- a/src/views/business/components/waybill-manage-page.vue
+++ b/src/views/business/components/waybill-manage-page.vue
@@ -1591,8 +1591,8 @@
取消
-
- {{ completeActionLabel }}
+
+ {{ completeActionLabelFor(row) }}
维护里程
@@ -5111,7 +5111,29 @@ export default {
return false;
}
const status = this.statusValue(row);
- return status === 'processing' || status === 'running';
+ return status === 'processing' || status === 'running' || status === 'pending_return';
+ },
+ completeActionLabelFor(row) {
+ return this.statusValue(row) === 'pending_return' ? '确认回单' : this.completeActionLabel;
+ },
+ handleCompleteAction(row) {
+ const isPendingReturn = this.statusValue(row) === 'pending_return';
+ const actionName = this.completeActionLabelFor(row);
+ this.$confirm(`确定${actionName}该${this.config.title}?`, '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning',
+ })
+ .then(() => {
+ if (isPendingReturn && typeof this.api.confirmReturn === 'function') {
+ return this.api.confirmReturn(row.id);
+ }
+ return this.api.complete(row.id);
+ })
+ .then(() => {
+ this.$message.success(`${actionName}成功`);
+ this.onLoad(this.page, this.query);
+ });
},
canMaintainMileage(row) {
return (
@@ -6543,6 +6565,9 @@ export default {
if (status === 'completed') {
return 'primary';
}
+ if (status === 'pending_return') {
+ return 'warning';
+ }
return 'warning';
},
normalizeRow(row, saveAsDraft = false) {
@@ -10356,7 +10381,14 @@ export default {
this.$message.warning('请选择至少一条数据');
return;
}
- this.$confirm('确定完成选中的运单?', '提示', {
+ const pendingReturnCount = this.selectionList.filter(
+ row => this.statusValue(row) === 'pending_return'
+ ).length;
+ const tip =
+ pendingReturnCount > 0
+ ? `确定完成选中的运单?其中 ${pendingReturnCount} 条为待确认回单,确认后将转为已完成。`
+ : '确定完成选中的运单?';
+ this.$confirm(tip, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
diff --git a/src/views/business/contract-manage-change.vue b/src/views/business/contract-manage-change.vue
index c745705..63f5f88 100644
--- a/src/views/business/contract-manage-change.vue
+++ b/src/views/business/contract-manage-change.vue
@@ -8,11 +8,17 @@
+
-
+
@@ -80,6 +87,7 @@
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="请输入"
+ :disabled="isSettlementRuleChange"
/>
至
@@ -95,6 +104,7 @@
positiveIntegerInput('paymentDays', value)"
>
天
@@ -105,22 +115,36 @@
:model-value="contractAmountDisplay"
placeholder="请输入"
clearable
+ :disabled="isSettlementRuleChange"
@input="handleContractAmountInput"
@blur="formatContractAmountField"
@clear="form.contractAmount = null"
/>
-
+
-
+
-
+
@@ -129,13 +153,19 @@
positiveIntegerInput('invoiceCycle', value)"
>
天
-
+
-
+
positiveIntegerInput('copyCount', value)"
>
份
-
+
-
+
@@ -209,6 +257,7 @@
title="用印材料"
description
attachment-type
+ :readonly="isSettlementRuleChange"
:attachment-location-options="changeAttachmentLocationOptions"
:rows="contractFileRows"
:preview="previewAttachment"
@@ -219,12 +268,36 @@
- 系统生成账单导入生成
- {{ row.defaultPlan ? '是' : '否' }}编辑删除
+ 系统生成账单导入生成
+ {{
+ row.defaultPlan ? '是' : '否'
+ }}编辑删除
@@ -243,19 +316,106 @@
关闭
-
-
-
-
-
-
+
+
+
+
+
+
-
+
- handleCustomPeriodStartDayChange($index, value)">
- handleCustomPeriodEndDayChange($index, value)">
- 添加删除
- 添加
+ handleCustomPeriodStartDayChange($index, value)"
+ >
+ handleCustomPeriodEndDayChange($index, value)"
+ >
+ 添加删除
+ 添加
@@ -263,17 +423,48 @@
付款比例设置
-
+
-
-
- 删除
+
+
+ 删除
- 添加
+ 添加
this.isAttachmentImage(item) && this.attachmentUrl(item)).map(item => this.attachmentUrl(item)); this.imagePreviewIndex = Math.max(this.imagePreviewUrls.indexOf(url), 0); this.imagePreviewVisible = true; return; } this.previewFile = { name: this.attachmentName(row), url, mimeType: row.mimeType || row.contentType || '' }; this.documentPreviewVisible = true; },
handleAttachmentUploadToLocation({ location, rows = [] }) {
if (!rows.length) return;
+ if (this.isSettlementRuleChange) {
+ if (location === '变更材料') {
+ this.changeMaterials = [...(this.changeMaterials || []), ...rows];
+ }
+ return;
+ }
if (isContractSealMaterialLocation(location)) {
this.contractFileRows = [...(this.contractFileRows || []), ...rows];
return;
@@ -743,7 +947,81 @@ export default {
removeCustomPeriodRow(index) { if (index <= 0) return; const periods = [...(this.settlementRule.customPeriods || [])]; periods.splice(index, 1); this.settlementRule.customPeriods = normalizeCustomPeriods(periods); },
validateCustomPeriods(periods = [], label) { const rows = normalizeCustomPeriods(periods); if (!rows.length) { this.$message.warning(`${label}:请至少配置一段自定义周期`); return false; } for (let index = 0; index < rows.length; index += 1) { const row = rows[index]; const startDay = Number(row.startDay); const endDay = Number(row.endDay); if (!Number.isFinite(startDay) || startDay < 1 || startDay > 31) { this.$message.warning(`${label}:请选择第${index + 1}行运单区间开始日`); return false; } if (!Number.isFinite(endDay) || endDay < 1 || endDay > 31) { this.$message.warning(`${label}:请选择第${index + 1}行运单区间结束日`); return false; } if (endDay < startDay) { this.$message.warning(`${label}:第${index + 1}行结束日不能早于开始日`); return false; } if (index > 0 && startDay !== Number(rows[index - 1].endDay) + 1) { this.$message.warning(`${label}:自定义多周期区间必须连续,不允许重叠或存在日期缺口`); return false; } } return true; },
validateSettlementRule(rule, label) { if (Number(rule.autoGenerate) !== 1) return true; if (!rule.billStartDate || !rule.settlementType) { this.$message.warning(`${label}:请完整填写账单起始日和结算类型`); return false; } if (rule.settlementType === '月结' && !rule.billCycleType) { this.$message.warning(`${label}:请选择结算周期`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '固定截单日' && !rule.billCutoffDay) { this.$message.warning(`${label}:请选择账单截单日`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '自定义多周期') return this.validateCustomPeriods(rule.customPeriods, label); if (rule.settlementType === '固定天数周期结算' && !rule.cycleDays) { this.$message.warning(`${label}:请选择周期天数`); return false; } return true; },
- async submit() { await this.$refs.formRef.validate(); const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0); if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) { this.$message.warning('付款比例上限合计必须等于100%'); return; } if (this.settlementConfigTab === 'pre') this.preSettlementConfig = { ...this.settlementRule }; else this.formalSettlementConfig = { ...this.settlementRule }; if (!this.validateSettlementRule(this.preSettlementConfig, '预结算配置') || !this.validateSettlementRule(this.formalSettlementConfig, '正式结算配置')) return; const settlementRule = { preSettlementConfig: this.preSettlementConfig, formalSettlementConfig: this.formalSettlementConfig }; await api.submitChange({ ...this.form, settlementCurrency: String(this.form.settlementCurrency || '').trim() || 'RMB', copyCount: normalizeOptionalPositiveInteger(this.form.copyCount), invoiceCycle: normalizeOptionalPositiveInteger(this.form.invoiceCycle), paymentDays: normalizeOptionalPositiveInteger(this.form.paymentDays), contractAmount: normalizeOptionalAmount(this.form.contractAmount), startDate: this.period[0], endDate: this.period[1], feeGenerationMode: this.feeGenerationMode, billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0, billingPlanJson: JSON.stringify(this.plans), settlementRuleJson: JSON.stringify(settlementRule), preSettlementConfigJson: JSON.stringify(this.preSettlementConfig), formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig), paymentRatioJson: JSON.stringify(this.paymentRatioRows), contractFileJson: JSON.stringify(this.contractFileRows), attachmentsJson: JSON.stringify(this.attachments), changeContent: this.form.changeContent, changeReason: this.form.changeReason, changeAttachmentsJson: JSON.stringify(this.changeMaterials) }); await submitMkApprovalFlow({ bizType: 'contract-manage', bizId: this.form.id, subjectName: this.form.contractName || '', approvalStatus: this.form.approvalStatus || 'change_rejected', mkEnabled: await loadMkEnabled().catch(() => false) }); this.$message.success('变更已提交'); this.$router.back(); },
+ async submit() {
+ if (this.form.changeType === '终止合同') {
+ await api.terminate(this.form.id, this.form.changeReason);
+ this.$message.success('合同已终止');
+ this.$router.back();
+ return;
+ }
+ await this.$refs.formRef.validate();
+ if (!this.isSettlementRuleChange) {
+ const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0);
+ if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) {
+ this.$message.warning('付款比例上限合计必须等于100%');
+ return;
+ }
+ }
+ if (this.settlementConfigTab === 'pre') this.preSettlementConfig = { ...this.settlementRule };
+ else this.formalSettlementConfig = { ...this.settlementRule };
+ if (
+ !this.validateSettlementRule(this.preSettlementConfig, '预结算配置') ||
+ !this.validateSettlementRule(this.formalSettlementConfig, '正式结算配置')
+ ) {
+ return;
+ }
+ const settlementRule = {
+ preSettlementConfig: this.preSettlementConfig,
+ formalSettlementConfig: this.formalSettlementConfig,
+ };
+ const payload = this.isSettlementRuleChange
+ ? {
+ id: this.form.id,
+ changeType: this.form.changeType,
+ changeContent: this.form.changeContent,
+ changeReason: this.form.changeReason,
+ feeGenerationMode: this.feeGenerationMode,
+ billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0,
+ billingPlanJson: JSON.stringify(this.plans),
+ settlementRuleJson: JSON.stringify(settlementRule),
+ preSettlementConfigJson: JSON.stringify(this.preSettlementConfig),
+ formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig),
+ changeAttachmentsJson: JSON.stringify(this.changeMaterials),
+ }
+ : {
+ ...this.form,
+ changeType: this.form.changeType || '合同信息变更',
+ settlementCurrency: String(this.form.settlementCurrency || '').trim() || 'RMB',
+ copyCount: normalizeOptionalPositiveInteger(this.form.copyCount),
+ invoiceCycle: normalizeOptionalPositiveInteger(this.form.invoiceCycle),
+ paymentDays: normalizeOptionalPositiveInteger(this.form.paymentDays),
+ contractAmount: normalizeOptionalAmount(this.form.contractAmount),
+ startDate: this.period[0],
+ endDate: this.period[1],
+ feeGenerationMode: this.feeGenerationMode,
+ billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0,
+ billingPlanJson: JSON.stringify(this.plans),
+ settlementRuleJson: JSON.stringify(settlementRule),
+ preSettlementConfigJson: JSON.stringify(this.preSettlementConfig),
+ formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig),
+ paymentRatioJson: JSON.stringify(this.paymentRatioRows),
+ contractFileJson: JSON.stringify(this.contractFileRows),
+ attachmentsJson: JSON.stringify(this.attachments),
+ changeContent: this.form.changeContent,
+ changeReason: this.form.changeReason,
+ changeAttachmentsJson: JSON.stringify(this.changeMaterials),
+ };
+ await api.submitChange(payload);
+ await submitMkApprovalFlow({
+ bizType: 'contract-manage',
+ bizId: this.form.id,
+ subjectName: this.form.contractName || '',
+ approvalStatus: this.form.approvalStatus || 'change_rejected',
+ mkEnabled: await loadMkEnabled().catch(() => false),
+ });
+ this.$message.success('变更已提交');
+ this.$router.back();
+ },
},
};
diff --git a/src/views/business/loading-manage.vue b/src/views/business/loading-manage.vue
index aac814e..ce1374d 100644
--- a/src/views/business/loading-manage.vue
+++ b/src/views/business/loading-manage.vue
@@ -1357,19 +1357,29 @@
-
+
删除
+ 已打卡
+
+ 已打卡地点置灰不可拖拽,新增地址仅可在未打卡节点间调整
+
@@ -1450,7 +1460,11 @@
"
clearable
filterable
- :disabled="dialogReadonly || !selectedWaybillRows.length"
+ :disabled="
+ dialogReadonly ||
+ isRunningTaskRequiredLocked ||
+ !selectedWaybillRows.length
+ "
:loading="taskCarrierLoading"
:placeholder="taskCarrierPlaceholder"
@change="handleTaskCarrierChange"
@@ -1483,6 +1497,7 @@
:fetch-suggestions="fetchDriverSuggestions"
:loading="driverLoading"
placeholder="请输入"
+ :disabled="dialogReadonly || isRunningSelfDriverLocked"
@select="item => handleDriverSuggestionSelect('dialog', item)"
/>
@@ -1490,13 +1505,28 @@
label="手机号"
:required="dialogForm.carrierType === '自运'"
>
-
+
-
+
-
+
handleEscortSuggestionSelect('dialog', item)"
/>
-
+
@@ -1537,6 +1574,7 @@
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="请选择"
+ :disabled="dialogReadonly"
/>
@@ -1547,6 +1585,7 @@
maxlength="500"
show-word-limit
placeholder="请输入,500字以内"
+ :disabled="dialogReadonly"
/>
@@ -1561,11 +1600,17 @@
关闭
-
+
暂存
- {{ dialogMode === 'reassign' ? '确认派单' : '确认' }}
+ {{ dialogMode === 'reassign' ? '确认派单' : isRunningEdit ? '保存' : '确认' }}
@@ -2107,6 +2152,8 @@ export default {
routeNodes: [],
cargoRows: [],
routeDragIndex: -1,
+ /** 进行中编辑:子运单打卡记录映射 waybillId -> records[] */
+ waybillPunchMap: {},
transportTypeOptions: [],
projectOptions: [],
customerOptions: [],
@@ -2170,6 +2217,23 @@ export default {
dialogReadonly() {
return this.dialogMode === 'view';
},
+ /** 进行中配载编辑:可加子单/调未打卡路线,但任务必填字段锁定 */
+ isRunningEdit() {
+ return (
+ this.dialogMode === 'edit' &&
+ String(this.dialogForm.businessStatus || '') === 'running'
+ );
+ },
+ /** 进行中任务必填字段(已派车)不可改 */
+ isRunningTaskRequiredLocked() {
+ return this.isRunningEdit;
+ },
+ /** 自运进行中:司机/手机号同属必填派车信息,不可改 */
+ isRunningSelfDriverLocked() {
+ return (
+ this.isRunningEdit && String(this.dialogForm.carrierType || '').trim() === '自运'
+ );
+ },
// 打卡时间轴:已打卡按时间升序,未打卡保持原节点顺序排在其后
orderedPunchRecords() {
const records = Array.isArray(this.punchRecords) ? this.punchRecords : [];
@@ -2913,6 +2977,7 @@ export default {
if (status === 'running') {
return [
{ type: 'view', label: '详情' },
+ { type: 'edit', label: '编辑' },
{ type: 'complete', label: '完成' },
];
}
@@ -3014,7 +3079,12 @@ export default {
query: {
mode,
...(mode === 'edit' && row?.id ? { id: row.id } : {}),
- name: mode === 'edit' ? '编辑配载管理' : '新增配载管理',
+ name:
+ mode === 'edit'
+ ? row?.businessStatus === 'running'
+ ? '编辑进行中配载'
+ : '编辑配载管理'
+ : '新增配载管理',
},
});
return;
@@ -3037,6 +3107,9 @@ export default {
this.dialogForm.loadingSubNos
);
this.syncProcessWaybillOptions();
+ if (this.isRunningEdit) {
+ await this.refreshWaybillPunchMap();
+ }
this.restoreRouteAndCargo();
this.restoreRouteChangeRecords();
this.loadPunchRecords();
@@ -3071,6 +3144,8 @@ export default {
this.taskCarrierLoading = false;
this.routeNodes = [];
this.cargoRows = [];
+ this.routeDragIndex = -1;
+ this.waybillPunchMap = {};
this.candidatePage.currentPage = 1;
this.candidatePage.total = 0;
this.candidateSearchVisible = false;
@@ -3435,20 +3510,144 @@ export default {
const statusName = String(row.businessStatusName || row.statusName || '');
return statusName.includes('已完成');
},
+ isPunchRecordDone(record = {}) {
+ if (record.punched === false || record.statusName === '未打卡') return false;
+ return Boolean(record.punched || record.punchTime || record.punchAt || record.punchDateTime);
+ },
+ isLoadPunchRecord(record = {}) {
+ if (!this.isPunchRecordDone(record)) return false;
+ const code = String(record.nodeCode || '')
+ .trim()
+ .toLowerCase();
+ const name = String(record.nodeName || '').trim();
+ return code === 'load' || name.includes('装货');
+ },
+ isUnloadPunchRecord(record = {}) {
+ if (!this.isPunchRecordDone(record)) return false;
+ const code = String(record.nodeCode || '')
+ .trim()
+ .toLowerCase();
+ const name = String(record.nodeName || '').trim();
+ return (
+ code === 'unload' ||
+ name.includes('卸货') ||
+ code === 'arrival' ||
+ name.includes('到货') ||
+ code === 'sign' ||
+ name.includes('签收')
+ );
+ },
+ waybillPunchRecords(row = {}) {
+ const key = String(row?.id || '');
+ if (!key) return [];
+ const records = this.waybillPunchMap[key];
+ return Array.isArray(records) ? records : [];
+ },
+ isWaybillLoadPunched(row = {}) {
+ return this.waybillPunchRecords(row).some(record => this.isLoadPunchRecord(record));
+ },
+ isWaybillUnloadPunched(row = {}) {
+ return this.waybillPunchRecords(row).some(record => this.isUnloadPunchRecord(record));
+ },
+ isWaybillPunchLocked(row = {}) {
+ return this.isWaybillLoadPunched(row) || this.isWaybillUnloadPunched(row);
+ },
+ isSelectedWaybillRemoveLocked(row = {}) {
+ if (this.isWaybillCompleted(row)) return true;
+ return this.isRunningEdit && this.isWaybillPunchLocked(row);
+ },
+ isRouteNodeTagLocked(tag, rows = this.selectedWaybillRows) {
+ const match = String(tag || '').match(/^(装|卸)(\d+)$/);
+ if (!match) return false;
+ const row = rows[Number(match[2]) - 1];
+ if (!row) return false;
+ if (this.isRunningEdit) {
+ return match[1] === '装' ? this.isWaybillLoadPunched(row) : this.isWaybillUnloadPunched(row);
+ }
+ return this.isWaybillCompleted(row);
+ },
isRouteChangeNodeLocked(node = {}) {
return Boolean(node?.locked);
},
syncRouteNodeLockedFlags(nodes = [], rows = this.selectedWaybillRows) {
return (nodes || []).map(node => {
- const locked = (node.tags || []).some(tag => {
- const match = String(tag).match(/^(装|卸)(\d+)$/);
- if (!match) return false;
- const row = rows[Number(match[2]) - 1];
- return row ? this.isWaybillCompleted(row) : false;
- });
+ const locked = (node.tags || []).some(tag => this.isRouteNodeTagLocked(tag, rows));
return { ...node, locked };
});
},
+ async refreshWaybillPunchMap() {
+ const rows = (this.selectedWaybillRows || []).filter(row => row?.id);
+ if (!rows.length) {
+ this.waybillPunchMap = {};
+ return;
+ }
+ const entries = await Promise.all(
+ rows.map(async row => {
+ try {
+ const res = await getWaybillPunchRecords(row.id);
+ const data = res?.data?.data || res?.data || {};
+ return [String(row.id), Array.isArray(data.records) ? data.records : []];
+ } catch (error) {
+ return [String(row.id), []];
+ }
+ })
+ );
+ this.waybillPunchMap = Object.fromEntries(entries);
+ },
+ /**
+ * 进行中编辑:已打卡地点保持在前且不可插入,新增地址只能排在已打卡节点之后。
+ */
+ mergeRouteNodesKeepingPunched(prevNodes = [], builtNodes = []) {
+ const prev = Array.isArray(prevNodes) ? prevNodes : [];
+ const built = Array.isArray(builtNodes) ? builtNodes : [];
+ if (!this.isRunningEdit) {
+ return built;
+ }
+ const builtByAddress = new Map();
+ built.forEach(node => {
+ if (node?.address && !builtByAddress.has(node.address)) {
+ builtByAddress.set(node.address, node);
+ }
+ });
+ const lockedAddresses = new Set(
+ built.filter(node => node?.locked && node.address).map(node => node.address)
+ );
+ const used = new Set();
+ const lockedResult = [];
+ prev.forEach(node => {
+ if (!node?.address || !lockedAddresses.has(node.address) || used.has(node.address)) {
+ return;
+ }
+ const latest = builtByAddress.get(node.address);
+ if (!latest) return;
+ lockedResult.push({ ...latest, locked: true });
+ used.add(node.address);
+ });
+ built.forEach(node => {
+ if (!node?.locked || !node.address || used.has(node.address)) return;
+ lockedResult.push({ ...node, locked: true });
+ used.add(node.address);
+ });
+ const unlockedResult = [];
+ prev.forEach(node => {
+ if (!node?.address || lockedAddresses.has(node.address) || used.has(node.address)) {
+ return;
+ }
+ const latest = builtByAddress.get(node.address);
+ if (!latest) return;
+ unlockedResult.push({ ...latest, locked: false });
+ used.add(node.address);
+ });
+ built.forEach(node => {
+ if (node?.locked || !node?.address || used.has(node.address)) return;
+ unlockedResult.push({ ...node, locked: false });
+ used.add(node.address);
+ });
+ return [...lockedResult, ...unlockedResult].map((node, index) => ({
+ ...node,
+ key: `${node.address}-${index}`,
+ }));
+ },
openRouteChangeMapDialog(row, field) {
if (this.isWaybillCompleted(row)) return;
this.routeChangeMapTarget = { row, field };
@@ -3766,6 +3965,33 @@ export default {
}
})
);
+ if (this.isRunningEdit && String(this.dialogForm.carrierType || '').trim() === '承运商') {
+ const contractId = this.dialogForm.carrierContractId;
+ if (contractId) {
+ const projectIds = [
+ ...new Set(
+ [...this.selectedWaybillRows, ...selectedRows]
+ .map(row => row.projectId)
+ .filter(Boolean)
+ ),
+ ];
+ if (projectIds.length) {
+ try {
+ const response = await loadingApi.getCarrierContracts(projectIds);
+ const contracts = response?.data?.data || response?.data || [];
+ const stillAllowed = (Array.isArray(contracts) ? contracts : []).some(
+ item => String(item.id) === String(contractId)
+ );
+ if (!stillAllowed) {
+ ElMessage.warning('新增子运单须兼容当前配载单已选定的承运商合同');
+ return;
+ }
+ } catch (error) {
+ // 合同校验失败时仍允许添加,最终由后端确认拦截
+ }
+ }
+ }
+ }
const exists = new Set(
this.selectedWaybillRows.map(row => String(row.id || row.waybillNo))
);
@@ -3777,8 +4003,12 @@ export default {
exists.add(key);
}
});
+ const prevRouteNodes = [...this.routeNodes];
this.selectedWaybillRows = nextRows;
- this.rebuildSummaryFromWaybills();
+ if (this.isRunningEdit) {
+ await this.refreshWaybillPunchMap();
+ }
+ this.rebuildSummaryFromWaybills(true, prevRouteNodes);
await this.loadTaskCarrierOptions();
this.$refs.candidateTableRef?.clearSelection?.();
this.candidateSelection = [];
@@ -3790,11 +4020,23 @@ export default {
await this.addWaybillRows(this.candidateSelection);
},
removeSelectedWaybill(index) {
+ const row = this.selectedWaybillRows[index];
+ if (this.isSelectedWaybillRemoveLocked(row)) {
+ ElMessage.warning('已打卡的子运单不可移除');
+ return;
+ }
this.selectedWaybillRows.splice(index, 1);
+ if (this.isRunningEdit) {
+ this.refreshWaybillPunchMap().then(() => {
+ this.rebuildSummaryFromWaybills();
+ this.loadTaskCarrierOptions();
+ });
+ return;
+ }
this.rebuildSummaryFromWaybills();
this.loadTaskCarrierOptions();
},
- rebuildSummaryFromWaybills(rebuildRoute = true) {
+ rebuildSummaryFromWaybills(rebuildRoute = true, prevRouteNodes = null) {
const rows = this.selectedWaybillRows;
this.dialogForm.loadingSubNos = rows
.map(row => row.waybillNo)
@@ -3819,7 +4061,13 @@ export default {
this.dialogForm.transportType = rows.find(row => row.transportType)?.transportType || '';
}
if (rebuildRoute) {
- this.routeNodes = this.buildRouteNodes(rows);
+ const builtNodes = this.buildRouteNodes(rows);
+ this.routeNodes = this.isRunningEdit
+ ? this.mergeRouteNodesKeepingPunched(
+ prevRouteNodes || this.routeNodes,
+ builtNodes
+ )
+ : builtNodes;
}
this.cargoRows = this.buildCargoRows(rows);
// 编辑回填阶段 routeNodes 尚未恢复时跳过,避免清空已有发到货地
@@ -3839,7 +4087,9 @@ export default {
if (locked) node.locked = true;
};
rows.forEach((row, rowIndex) => {
- const locked = this.isWaybillCompleted(row);
+ const locked = this.isRunningEdit
+ ? this.isWaybillLoadPunched(row)
+ : this.isWaybillCompleted(row);
const departure = row.departureAddress || row.departureName;
appendNode(
departure,
@@ -3849,7 +4099,9 @@ export default {
);
});
rows.forEach((row, rowIndex) => {
- const locked = this.isWaybillCompleted(row);
+ const locked = this.isRunningEdit
+ ? this.isWaybillUnloadPunched(row)
+ : this.isWaybillCompleted(row);
const arrival = row.arrivalAddress || row.arrivalName;
appendNode(
arrival,
@@ -3901,7 +4153,9 @@ export default {
if (
originalLockedKeys.some((key, lockedIndex) => key && key !== nextLockedKeys[lockedIndex])
) {
- ElMessage.warning('已完成运单对应的路线不可调整');
+ ElMessage.warning(
+ this.isRunningEdit ? '已打卡地点不可调整,新地址只能在未打卡节点间拖拽' : '已完成运单对应的路线不可调整'
+ );
this.routeDragIndex = -1;
return;
}
@@ -4145,15 +4399,15 @@ export default {
try {
const payload = {
...this.normalizePayload(),
- // 确认生成正式配载单(待执行),覆盖草稿状态
- businessStatus: 'pending',
+ // 进行中编辑保持 running;其余确认生成正式配载单为待执行
+ businessStatus: this.isRunningEdit ? 'running' : 'pending',
};
if (this.dialogMode === 'reassign') {
await loadingApi.reassign(payload);
ElMessage.success('派单成功');
} else {
await loadingApi.submit(payload);
- ElMessage.success('确认成功');
+ ElMessage.success(this.isRunningEdit ? '保存成功' : '确认成功');
}
sessionStorage.setItem(loadingManageListRefreshKey, '1');
this.closeLoadingDialog();
@@ -4787,6 +5041,16 @@ export default {
color: #909399;
}
+ .loading-manage-dialog__route-hint {
+ color: #909399;
+ font-size: 12px;
+ }
+
+ .loading-manage-dialog__locked-tip {
+ color: #909399;
+ font-size: 12px;
+ }
+
.loading-manage-dialog__route-type {
width: 28px;
height: 28px;
diff --git a/src/views/business/project-apply.vue b/src/views/business/project-apply.vue
index e4b41bb..75dcdfe 100644
--- a/src/views/business/project-apply.vue
+++ b/src/views/business/project-apply.vue
@@ -921,7 +921,7 @@ import {
getList as getCustomerArchiveList,
getDetail as getCustomerArchiveDetail,
} from '@/api/vehicle/customer-archive';
-import { getDeptTree, getPlatformCompanySelect } from '@/api/system/dept';
+import { getBusinessDeptSelect, getDeptTree, getPlatformCompanySelect } from '@/api/system/dept';
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
import { getDictionary as getBizDictionary } from '@/api/system/dictbiz';
import { getList as getUserList } from '@/api/system/user';
@@ -1559,6 +1559,7 @@ export default {
projectType: type === 'majorSupplement' ? '重大项目' : '普通项目',
});
this.fillDefaultUsers();
+ this.fillDefaultBusinessDept();
return;
}
if (!id) return;
@@ -1891,6 +1892,7 @@ export default {
.filter(Boolean);
this.syncSelectedCustomerOptions();
this.ensureCurrentPlatformOption();
+ this.ensureCurrentBusinessDeptOption();
},
fillDefaultUsers() {
const userId = this.userInfo?.userId || this.userInfo?.id || '';
@@ -1909,6 +1911,25 @@ export default {
this.form.principalUserName = userName;
}
},
+ /**
+ * 新增/重大补录:业务部门默认取当前用户所属部门中第一个「是否业务部门=是」的组织。
+ */
+ fillDefaultBusinessDept() {
+ if (!['add', 'majorSupplement'].includes(this.dialogType)) return;
+ if (this.form.businessDeptId) return;
+ const raw = this.userInfo?.deptId || this.userInfo?.dept_id || '';
+ const userDeptIds = String(raw)
+ .split(/[,,]/)
+ .map(item => String(item || '').trim())
+ .filter(item => item && item !== '-1');
+ if (!userDeptIds.length || !this.deptOptions.length) return;
+ const matched = userDeptIds.find(id =>
+ this.deptOptions.some(item => String(item.value) === String(id))
+ );
+ if (!matched) return;
+ this.form.businessDeptId = matched;
+ this.handleBusinessDeptChange(matched);
+ },
resolveUserRealName(row = {}, role) {
return (
row[`${role}RealName`] ||
@@ -2491,17 +2512,61 @@ export default {
this.$refs.projectForm?.validateField('projectType');
},
loadDeptOptions() {
- getDeptTree().then(res => {
- const deptTree = res.data.data || [];
- // 新增/编辑/重大补录/项目变更均可选择业务部门,排除外部组织
- const businessDeptTree = ['add', 'edit', 'majorSupplement', 'change'].includes(this.dialogType)
- ? this.excludeExternalOrganization(deptTree)
- : deptTree;
- this.deptOptions = this.flattenDept(businessDeptTree);
- this.businessDeptTreeOptions = this.toDeptCascaderOptions(businessDeptTree);
- });
+ Promise.all([getDeptTree(), getBusinessDeptSelect()])
+ .then(([treeRes, bizRes]) => {
+ const deptTree = this.excludeExternalOrganization(treeRes.data.data || []);
+ const bizList = bizRes.data.data || [];
+ const bizIds = new Set(
+ bizList
+ .map(item => (item.id == null || item.id === '' ? '' : String(item.id)))
+ .filter(Boolean)
+ );
+ // 仅保留「是否业务部门=是」的节点及其祖先,祖先不可选
+ const businessDeptTree = this.filterBusinessDeptTree(deptTree, bizIds);
+ this.deptOptions = this.flattenDept(businessDeptTree).filter(item =>
+ bizIds.has(String(item.value))
+ );
+ this.businessDeptTreeOptions = this.toDeptCascaderOptions(businessDeptTree, bizIds);
+ this.fillDefaultBusinessDept();
+ this.ensureCurrentBusinessDeptOption();
+ })
+ .catch(() => {
+ this.deptOptions = [];
+ this.businessDeptTreeOptions = [];
+ });
this.loadPlatformCompanyOptions();
},
+ /**
+ * 剪枝组织树:只保留业务部门节点及其祖先路径。
+ */
+ filterBusinessDeptTree(list = [], bizIds = new Set()) {
+ return (list || []).reduce((result, item) => {
+ const children = item.children?.length
+ ? this.filterBusinessDeptTree(item.children, bizIds)
+ : [];
+ const isBiz = bizIds.has(String(item.id));
+ if (!isBiz && !children.length) {
+ return result;
+ }
+ result.push({
+ ...item,
+ children: children.length ? children : undefined,
+ });
+ return result;
+ }, []);
+ },
+ ensureCurrentBusinessDeptOption() {
+ const id = this.form.businessDeptId;
+ if (!id) return;
+ const exists = this.deptOptions.some(item => String(item.value) === String(id));
+ if (exists) return;
+ const label = this.form.businessDeptName || String(id);
+ this.deptOptions.unshift({ label, rawLabel: label, value: id });
+ const pathExists = this.findDeptPath(id, this.businessDeptTreeOptions);
+ if (!pathExists) {
+ this.businessDeptTreeOptions.unshift({ label, value: id });
+ }
+ },
loadPlatformCompanyOptions() {
getPlatformCompanySelect().then(res => {
const list = res.data.data || [];
@@ -2545,12 +2610,22 @@ export default {
return result;
}, []);
},
- toDeptCascaderOptions(list = []) {
- return list.map(item => ({
- label: item.title || item.deptName || item.name,
- value: item.id,
- children: item.children?.length ? this.toDeptCascaderOptions(item.children) : undefined,
- }));
+ toDeptCascaderOptions(list = [], bizIds = null) {
+ return list.map(item => {
+ const value = item.id == null || item.id === '' ? item.value : item.id;
+ const option = {
+ label: item.title || item.deptName || item.name || item.label,
+ value: value == null || value === '' ? '' : String(value),
+ children: item.children?.length
+ ? this.toDeptCascaderOptions(item.children, bizIds)
+ : undefined,
+ };
+ // 仅业务部门可选;祖先节点保留路径但禁用
+ if (bizIds && !bizIds.has(String(option.value))) {
+ option.disabled = true;
+ }
+ return option;
+ });
},
flattenDept(list = [], level = 0) {
return list.reduce((result, item) => {
diff --git a/src/views/payment/bill-payment-form.vue b/src/views/payment/bill-payment-form.vue
index 77297ed..d848ffd 100644
--- a/src/views/payment/bill-payment-form.vue
+++ b/src/views/payment/bill-payment-form.vue
@@ -271,7 +271,7 @@ export default {
this.unwrapData(
await billLedgerApi.getAvailableOptions(
this.billDialog.keyword,
- this.form.deptId,
+ undefined,
this.form.billLedgerId
)
) || [];
diff --git a/src/views/payment/payment-application-form.vue b/src/views/payment/payment-application-form.vue
index eb13204..2880a43 100644
--- a/src/views/payment/payment-application-form.vue
+++ b/src/views/payment/payment-application-form.vue
@@ -1229,14 +1229,6 @@ export default {
callback();
return;
}
- if (
- selected.billType &&
- selected.billType !== 'received' &&
- !this.billMatchesSettlementParties(selected)
- ) {
- callback(new Error('汇票付款仅可选择收票类型,或出票单位与收票单位均为当前结算收付款方的汇票'));
- return;
- }
if (selected.maturityDate) {
const maturity = this.$dayjs(selected.maturityDate).startOf('day');
const today = this.$dayjs().startOf('day');
@@ -2444,8 +2436,7 @@ export default {
}
},
payeeReceiptLabel(item) {
- const name = item.accountName || item.accountHolderName || '未命名收款方';
- return item.bankAccount ? `${name}(${item.bankAccount})` : name;
+ return item.accountName || item.accountHolderName || '未命名收款方';
},
receiptAccountLabel(item) {
return [item.accountName || item.accountHolderName, item.bankName, item.bankAccount]
@@ -2603,7 +2594,7 @@ export default {
this.unwrapData(
await billLedgerApi.getAvailableOptions(
keyword,
- this.form.deptId,
+ undefined,
selectedId,
this.settlementPayerName,
this.settlementPayeeName
@@ -2628,7 +2619,7 @@ export default {
this.billPage.current,
this.billPage.size,
String(this.billQuery.keyword || '').trim() || undefined,
- this.form.deptId,
+ undefined,
this.form.billLedgerId,
this.settlementPayerName,
this.settlementPayeeName
@@ -2667,14 +2658,6 @@ export default {
);
},
selectBill(row) {
- if (
- row?.billType &&
- row.billType !== 'received' &&
- !this.billMatchesSettlementParties(row)
- ) {
- ElMessage.warning('汇票付款仅可选择收票类型,或出票单位与收票单位均为当前结算收付款方的汇票');
- return;
- }
if (row?.maturityDate) {
const maturity = this.$dayjs(row.maturityDate).startOf('day');
const today = this.$dayjs().startOf('day');
diff --git a/src/views/system/dept.vue b/src/views/system/dept.vue
index a22b6f3..0b4033c 100644
--- a/src/views/system/dept.vue
+++ b/src/views/system/dept.vue
@@ -535,6 +535,27 @@ export default {
},
],
},
+ {
+ label: '是否业务部门',
+ prop: 'isBusinessDept',
+ type: 'select',
+ dicData: [
+ { label: '否', value: 0 },
+ { label: '是', value: 1 },
+ ],
+ dataType: 'number',
+ value: 0,
+ width: 120,
+ order: 34,
+ formatter: row => (Number(row.isBusinessDept) === 1 ? '是' : '否'),
+ rules: [
+ {
+ required: true,
+ message: '请选择是否业务部门',
+ trigger: 'change',
+ },
+ ],
+ },
{
label: '排序',
prop: 'sort',
@@ -1523,6 +1544,9 @@ export default {
if (this.form.isPlatformCompany === undefined || this.form.isPlatformCompany === null) {
this.form.isPlatformCompany = 0;
}
+ if (this.form.isBusinessDept === undefined || this.form.isBusinessDept === null) {
+ this.form.isBusinessDept = 0;
+ }
this.clearEmptyContractInitialSerial(this.form);
this.initData(this.form.tenantId);
this.loadParentDeptCode(this.form.parentId);