✨ 进行中配载可编辑、汇票选择与收款方展示优化,并支持业务部门与待确认回单

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-22 15:41:01 +08:00
parent d7969c191a
commit 8d9fe998c0
11 changed files with 783 additions and 98 deletions
+7
View File
@@ -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`,
+7
View File
@@ -139,3 +139,10 @@ export const getPlatformCompanySelect = () => {
method: 'get',
});
};
export const getBusinessDeptSelect = () => {
return request({
url: '/blade-system/dept/business-dept-select',
method: 'get',
});
};
+1
View File
@@ -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' },
];
+17 -3
View File
@@ -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'],
@@ -1591,8 +1591,8 @@
<el-link type="primary" v-if="canCancel(row)" @click="handleAction('cancel', row)">
取消
</el-link>
<el-link type="primary" v-if="canComplete(row)" @click="handleAction('complete', row)">
{{ completeActionLabel }}
<el-link type="primary" v-if="canComplete(row)" @click="handleCompleteAction(row)">
{{ completeActionLabelFor(row) }}
</el-link>
<el-link type="primary" v-if="canMaintainMileage(row)" @click="openMileageDialog(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',
+306 -28
View File
@@ -8,11 +8,17 @@
<el-form-item label="变更类型" class="contract-basic-section__change-type">
<el-radio-group v-model="form.changeType">
<el-radio label="合同信息变更" />
<el-radio label="结算规则变更" />
<el-radio label="终止合同" />
</el-radio-group>
</el-form-item>
<el-form-item label="签约类型" prop="signType">
<el-select v-model="form.signType" placeholder="请选择签约类型" clearable>
<el-select
v-model="form.signType"
placeholder="请选择签约类型"
clearable
:disabled="isSettlementRuleChange"
>
<el-option
v-for="item in signTypeOptions"
:key="item.value"
@@ -30,6 +36,7 @@
maxlength="100"
show-word-limit
placeholder="请输入合同名称"
:disabled="isSettlementRuleChange"
/>
</el-form-item>
<el-form-item label="合同类型">
@@ -80,6 +87,7 @@
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="请输入"
:disabled="isSettlementRuleChange"
/>
<span>至</span>
<el-date-picker
@@ -88,6 +96,7 @@
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="请输入"
:disabled="isSettlementRuleChange"
/>
</div>
</el-form-item>
@@ -95,6 +104,7 @@
<el-input
v-model="form.paymentDays"
placeholder="请输入"
:disabled="isSettlementRuleChange"
@input="value => positiveIntegerInput('paymentDays', value)"
>
<template #suffix>天</template>
@@ -105,22 +115,36 @@
:model-value="contractAmountDisplay"
placeholder="请输入"
clearable
:disabled="isSettlementRuleChange"
@input="handleContractAmountInput"
@blur="formatContractAmountField"
@clear="form.contractAmount = null"
/>
</el-form-item>
<el-form-item label="是否范本">
<el-select v-model="form.templateFlag" placeholder="请选择">
<el-select
v-model="form.templateFlag"
placeholder="请选择"
:disabled="isSettlementRuleChange"
>
<el-option label="否" :value="0" />
<el-option label="是" :value="1" />
</el-select>
</el-form-item>
<el-form-item label="原件合同编号">
<el-input v-model="form.originalContractNo" maxlength="100" placeholder="请输入" />
<el-input
v-model="form.originalContractNo"
maxlength="100"
placeholder="请输入"
:disabled="isSettlementRuleChange"
/>
</el-form-item>
<el-form-item label="是否电子章">
<el-select v-model="form.electronicSealFlag" placeholder="请选择">
<el-select
v-model="form.electronicSealFlag"
placeholder="请选择"
:disabled="isSettlementRuleChange"
>
<el-option label="否" :value="0" />
<el-option label="是" :value="1" />
</el-select>
@@ -129,13 +153,19 @@
<el-input
v-model="form.invoiceCycle"
placeholder="请输入"
:disabled="isSettlementRuleChange"
@input="value => positiveIntegerInput('invoiceCycle', value)"
>
<template #suffix>天</template>
</el-input>
</el-form-item>
<el-form-item label="结算币种" prop="settlementCurrency">
<el-select v-model="form.settlementCurrency" placeholder="请选择结算币种" clearable>
<el-select
v-model="form.settlementCurrency"
placeholder="请选择结算币种"
clearable
:disabled="isSettlementRuleChange"
>
<el-option
v-for="item in settlementCurrencyOptions"
:key="item.value"
@@ -145,7 +175,12 @@
</el-select>
</el-form-item>
<el-form-item label="合同格式">
<el-select v-model="form.contractFormat" placeholder="请选择合同格式" clearable>
<el-select
v-model="form.contractFormat"
placeholder="请选择合同格式"
clearable
:disabled="isSettlementRuleChange"
>
<el-option
v-for="item in contractFormatOptions"
:key="item.value"
@@ -158,19 +193,30 @@
<el-input
v-model="form.copyCount"
placeholder="请输入"
:disabled="isSettlementRuleChange"
@input="value => positiveIntegerInput('copyCount', value)"
>
<template #suffix>份</template>
</el-input>
</el-form-item>
<el-form-item label="是否需要加盖法人章">
<el-select v-model="form.legalSealFlag" placeholder="请选择" clearable>
<el-select
v-model="form.legalSealFlag"
placeholder="请选择"
clearable
:disabled="isSettlementRuleChange"
>
<el-option label="否" :value="0" />
<el-option label="是" :value="1" />
</el-select>
</el-form-item>
<el-form-item label="结算方式">
<el-select v-model="form.settlementMode" placeholder="请选择结算方式" clearable>
<el-select
v-model="form.settlementMode"
placeholder="请选择结算方式"
clearable
:disabled="isSettlementRuleChange"
>
<el-option
v-for="item in settlementModeOptions"
:key="item.value"
@@ -191,6 +237,7 @@
maxlength="1000"
show-word-limit
placeholder="请输入合同主要内容"
:disabled="isSettlementRuleChange"
/>
</el-form-item>
<el-form-item label="其他事项说明" class="contract-basic-section__remark">
@@ -201,6 +248,7 @@
maxlength="200"
show-word-limit
placeholder="请输入其他事项说明"
:disabled="isSettlementRuleChange"
/>
</el-form-item>
</section>
@@ -209,6 +257,7 @@
title="用印材料"
description
attachment-type
:readonly="isSettlementRuleChange"
:attachment-location-options="changeAttachmentLocationOptions"
:rows="contractFileRows"
:preview="previewAttachment"
@@ -219,12 +268,36 @@
<section class="change-section">
<div class="section-head">
<div class="dialog-section-title">计费信息</div>
<el-button v-if="feeGenerationMode !== 'manual'" type="primary" plain @click="addPlan"
<el-button
v-if="feeGenerationMode !== 'manual'"
type="primary"
plain
@click="addPlan"
>添加</el-button
>
</div>
<el-radio-group v-model="feeGenerationMode"><el-radio label="system">系统生成</el-radio><el-radio label="manual">账单导入生成</el-radio></el-radio-group>
<el-table :data="plans" border class="change-table"><el-table-column type="index" label="序号" width="70" /><el-table-column prop="planName" label="方案名称" min-width="220" /><el-table-column label="默认方案" min-width="120"><template #default="{ row }">{{ row.defaultPlan ? '是' : '否' }}</template></el-table-column><el-table-column prop="remark" label="备注" min-width="260" /><el-table-column label="操作" width="180"><template #default="{ row, $index }"><el-link type="primary" @click="editPlan(row, $index)">编辑</el-link><el-link type="danger" @click="plans.splice($index, 1)">删除</el-link></template></el-table-column></el-table>
<el-radio-group v-model="feeGenerationMode"
><el-radio label="system">系统生成</el-radio
><el-radio label="manual">账单导入生成</el-radio></el-radio-group
>
<el-table :data="plans" border class="change-table"
><el-table-column type="index" label="序号" width="70" /><el-table-column
prop="planName"
label="方案名称"
min-width="220"
/><el-table-column label="默认方案" min-width="120"
><template #default="{ row }">{{
row.defaultPlan ? '是' : '否'
}}</template></el-table-column
><el-table-column prop="remark" label="备注" min-width="260" /><el-table-column
label="操作"
width="180"
><template #default="{ row, $index }"
><el-link type="primary" @click="editPlan(row, $index)">编辑</el-link
><el-link type="danger" @click="plans.splice($index, 1)">删除</el-link></template
></el-table-column
></el-table
>
</section>
<section class="change-section">
@@ -243,19 +316,106 @@
<el-radio :label="0">关闭</el-radio>
</el-radio-group>
</div>
<el-form v-if="settlementRule.autoGenerate === 1" :model="settlementRule" label-position="right" label-width="auto" class="settlement-form">
<el-form-item label="账单起始日" required><el-date-picker v-model="settlementRule.billStartDate" type="date" placeholder="请选择账单起始日" format="YYYY-MM-DD" value-format="YYYY-MM-DD" /></el-form-item>
<el-form-item label="结算类型" required><el-select v-model="settlementRule.settlementType" placeholder="请选择结算类型" @change="handleSettlementTypeChange"><el-option v-for="item in settlementTypeOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item>
<el-form-item v-if="showSettlementBillCycleType" label="结算周期" required><el-select v-model="settlementRule.billCycleType" placeholder="请选择结算周期" @change="handleCycleTypeChange"><el-option v-for="item in billCycleTypeOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item>
<el-form-item v-if="showSettlementBillCutoffDay" label="账单截单日" required><el-select v-model="settlementRule.billCutoffDay" placeholder="请选择账单截单日"><el-option v-for="item in billCutoffDayOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item>
<el-form-item v-if="showSettlementCycleDays" label="周期天数" required><el-select v-model="settlementRule.cycleDays" placeholder="请选择周期天数"><el-option v-for="item in cycleDayOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item>
<el-form
v-if="settlementRule.autoGenerate === 1"
:model="settlementRule"
label-position="right"
label-width="auto"
class="settlement-form"
>
<el-form-item label="账单起始日" required
><el-date-picker
v-model="settlementRule.billStartDate"
type="date"
placeholder="请选择账单起始日"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
/></el-form-item>
<el-form-item label="结算类型" required
><el-select
v-model="settlementRule.settlementType"
placeholder="请选择结算类型"
@change="handleSettlementTypeChange"
><el-option
v-for="item in settlementTypeOptions"
:key="item"
:label="item"
:value="item"
/></el-select></el-form-item>
<el-form-item v-if="showSettlementBillCycleType" label="结算周期" required
><el-select
v-model="settlementRule.billCycleType"
placeholder="请选择结算周期"
@change="handleCycleTypeChange"
><el-option
v-for="item in billCycleTypeOptions"
:key="item"
:label="item"
:value="item"
/></el-select></el-form-item>
<el-form-item v-if="showSettlementBillCutoffDay" label="账单截单日" required
><el-select v-model="settlementRule.billCutoffDay" placeholder="请选择账单截单日"
><el-option
v-for="item in billCutoffDayOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/></el-select></el-form-item>
<el-form-item v-if="showSettlementCycleDays" label="周期天数" required
><el-select v-model="settlementRule.cycleDays" placeholder="请选择周期天数"
><el-option
v-for="item in cycleDayOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/></el-select></el-form-item>
</el-form>
<el-table v-if="settlementRule.autoGenerate === 1 && showSettlementCustomPeriods" :data="settlementRule.customPeriods || []" border class="change-table custom-periods-table">
<el-table
v-if="settlementRule.autoGenerate === 1 && showSettlementCustomPeriods"
:data="settlementRule.customPeriods || []"
border
class="change-table custom-periods-table"
>
<el-table-column type="index" label="序号" width="70" />
<el-table-column label="运单区间-开始日" min-width="180"><template #default="{ row, $index }"><el-select :model-value="row.startDay" placeholder="请选择" clearable @update:model-value="value => handleCustomPeriodStartDayChange($index, value)"><el-option v-for="item in billCutoffDayOptions" :key="`start-${item.value}`" :label="item.label" :value="item.value" /></el-select></template></el-table-column>
<el-table-column label="运单区间-结束日" min-width="200"><template #default="{ row, $index }"><el-select :model-value="row.endDay" placeholder="请选择" clearable @update:model-value="value => handleCustomPeriodEndDayChange($index, value)"><el-option v-for="item in customPeriodEndDayOptions(row)" :key="item.value" :label="item.label" :value="item.value" /></el-select></template></el-table-column>
<el-table-column label="操作" width="100"><template #default="{ $index }"><el-link v-if="$index === 0" type="primary" @click="addCustomPeriodRow">添加</el-link><el-link v-else type="danger" @click="removeCustomPeriodRow($index)">删除</el-link></template></el-table-column>
<template #empty><el-link type="primary" @click="addCustomPeriodRow">添加</el-link></template>
<el-table-column label="运单区间-开始日" min-width="180"
><template #default="{ row, $index }"
><el-select
:model-value="row.startDay"
placeholder="请选择"
clearable
@update:model-value="value => handleCustomPeriodStartDayChange($index, value)"
><el-option
v-for="item in billCutoffDayOptions"
:key="`start-${item.value}`"
:label="item.label"
:value="item.value"
/></el-select></template
></el-table-column>
<el-table-column label="运单区间-结束日" min-width="200"
><template #default="{ row, $index }"
><el-select
:model-value="row.endDay"
placeholder="请选择"
clearable
@update:model-value="value => handleCustomPeriodEndDayChange($index, value)"
><el-option
v-for="item in customPeriodEndDayOptions(row)"
:key="item.value"
:label="item.label"
:value="item.value"
/></el-select></template
></el-table-column>
<el-table-column label="操作" width="100"
><template #default="{ $index }"
><el-link v-if="$index === 0" type="primary" @click="addCustomPeriodRow">添加</el-link
><el-link v-else type="danger" @click="removeCustomPeriodRow($index)"
>删除</el-link
></template
></el-table-column
>
<template #empty
><el-link type="primary" @click="addCustomPeriodRow">添加</el-link></template
>
</el-table>
</section>
@@ -263,17 +423,48 @@
<div class="dialog-section-title">
付款比例设置
<el-tooltip content="非必填;配置付款比例时,合计必须等于100%。" placement="top">
<el-icon style="margin-left: 4px; color: #909399; cursor: pointer"><el-icon-question-filled /></el-icon>
<el-icon style="margin-left: 4px; color: #909399; cursor: pointer"
><el-icon-question-filled
/></el-icon>
</el-tooltip>
</div>
<el-table :data="paymentRatioRows" border class="change-table">
<el-table-column type="index" label="序号" width="70" />
<el-table-column prop="paymentTerm" label="付款笔数" min-width="180" />
<el-table-column label="付款比例上限(%)" min-width="220"><template #default="{ row }"><el-input-number v-model="row.ratioLimit" :min="0" :max="100" :precision="2" controls-position="right" /></template></el-table-column>
<el-table-column label="备注" min-width="220"><template #default="{ row }"><el-input v-model="row.remark" maxlength="200" /></template></el-table-column>
<el-table-column label="操作" width="100"><template #default="{ $index }"><el-link type="danger" @click="paymentRatioRows.splice($index, 1)">删除</el-link></template></el-table-column>
<el-table-column label="付款比例上限(%)" min-width="220"
><template #default="{ row }"
><el-input-number
v-model="row.ratioLimit"
:min="0"
:max="100"
:precision="2"
controls-position="right"
:disabled="isSettlementRuleChange"
/></template
></el-table-column>
<el-table-column label="备注" min-width="220"
><template #default="{ row }"
><el-input
v-model="row.remark"
maxlength="200"
:disabled="isSettlementRuleChange"
/></template
></el-table-column>
<el-table-column v-if="!isSettlementRuleChange" label="操作" width="100"
><template #default="{ $index }"
><el-link type="danger" @click="paymentRatioRows.splice($index, 1)"
>删除</el-link
></template
></el-table-column
>
</el-table>
<el-button type="primary" plain @click="addPaymentRatioRow">添加</el-button>
<el-button
v-if="!isSettlementRuleChange"
type="primary"
plain
@click="addPaymentRatioRow"
>添加</el-button
>
</section>
<billing-plan-editor
@@ -290,6 +481,7 @@
attachment-type
attachment-location="附件(佐证材料等)"
attachment-type-dict-code="contract_supporting_attachment_type"
:readonly="isSettlementRuleChange"
:attachment-location-options="changeAttachmentLocationOptions"
:rows="attachments"
:preview="previewAttachment"
@@ -494,7 +686,13 @@ export default {
pageTitle() {
return this.$route.query.name || '合同变更';
},
isSettlementRuleChange() {
return this.form.changeType === '结算规则变更';
},
changeAttachmentLocationOptions() {
if (this.isSettlementRuleChange) {
return ['变更材料'];
}
return ['用印材料', '附件(佐证材料等)', '变更材料'];
},
contractAmountDisplay() {
@@ -720,6 +918,12 @@ export default {
previewAttachment(row, rows = this.attachments) { const url = this.attachmentUrl(row); if (!url) { this.$message.warning('附件地址为空,无法预览'); return; } if (this.isAttachmentImage(row)) { this.imagePreviewUrls = (rows || []).filter(item => 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();
},
},
};
</script>
+290 -26
View File
@@ -1357,19 +1357,29 @@
</template>
</el-table-column>
<el-table-column label="操作" width="110" fixed="right">
<template #default="{ $index }">
<template #default="{ row, $index }">
<el-link
v-if="!dialogReadonly"
v-if="!dialogReadonly && !isSelectedWaybillRemoveLocked(row)"
type="danger"
@click="removeSelectedWaybill($index)"
>删除</el-link
>
<span
v-else-if="!dialogReadonly && isSelectedWaybillRemoveLocked(row)"
class="loading-manage-dialog__locked-tip"
>已打卡</span
>
</template>
</el-table-column>
</el-table>
</section-card>
<section-card title="线路编辑">
<template v-if="isRunningEdit" #extra>
<span class="loading-manage-dialog__route-hint"
>已打卡地点置灰不可拖拽,新增地址仅可在未打卡节点间调整</span
>
</template>
<div class="loading-manage-dialog__route-list" v-if="routeNodes.length">
<div
v-for="(node, index) in routeNodes"
@@ -1436,7 +1446,7 @@
<el-segmented
v-model="dialogForm.carrierType"
:options="carrierTypeOptions"
:disabled="dialogReadonly"
:disabled="dialogReadonly || isRunningTaskRequiredLocked"
@change="handleCarrierTypeChange"
/>
</div>
@@ -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)"
/>
</el-form-item>
@@ -1490,13 +1505,28 @@
label="手机号"
:required="dialogForm.carrierType === '自运'"
>
<el-input v-model="dialogForm.driverPhone" clearable placeholder="请输入" />
<el-input
v-model="dialogForm.driverPhone"
clearable
placeholder="请输入"
:disabled="dialogReadonly || isRunningSelfDriverLocked"
/>
</el-form-item>
<el-form-item label="车牌号" required>
<el-input v-model="dialogForm.vehicleNo" clearable placeholder="请输入" />
<el-input
v-model="dialogForm.vehicleNo"
clearable
placeholder="请输入"
:disabled="dialogReadonly || isRunningTaskRequiredLocked"
/>
</el-form-item>
<el-form-item v-if="dialogForm.carrierType !== '承运商'" label="挂车车牌号">
<el-input v-model="dialogForm.trailerVehicleNo" clearable placeholder="请输入" />
<el-input
v-model="dialogForm.trailerVehicleNo"
clearable
placeholder="请输入"
:disabled="dialogReadonly"
/>
</el-form-item>
<el-form-item v-if="dialogForm.carrierType !== '承运商'" label="押运人">
<el-autocomplete
@@ -1506,11 +1536,17 @@
:fetch-suggestions="fetchEscortSuggestions"
:loading="escortLoading"
placeholder="请输入"
:disabled="dialogReadonly"
@select="item => handleEscortSuggestionSelect('dialog', item)"
/>
</el-form-item>
<el-form-item v-if="dialogForm.carrierType !== '承运商'" label="押运人手机号">
<el-input v-model="dialogForm.escortPhone" clearable placeholder="请输入" />
<el-input
v-model="dialogForm.escortPhone"
clearable
placeholder="请输入"
:disabled="dialogReadonly"
/>
</el-form-item>
<el-form-item label="里程(km)">
<el-input
@@ -1528,6 +1564,7 @@
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="请选择"
:disabled="dialogReadonly"
/>
</el-form-item>
<el-form-item label="预计完成日期">
@@ -1537,6 +1574,7 @@
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="请选择"
:disabled="dialogReadonly"
/>
</el-form-item>
<el-form-item label="备注" class="loading-manage-dialog__span-2">
@@ -1547,6 +1585,7 @@
maxlength="500"
show-word-limit
placeholder="请输入,500字以内"
:disabled="dialogReadonly"
/>
</el-form-item>
</div>
@@ -1561,11 +1600,17 @@
</template>
<template v-else>
<el-button @click="closeLoadingDialog">关闭</el-button>
<el-button type="primary" plain :loading="dialogSaving === 'draft'" @click="saveDraft">
<el-button
v-if="!isRunningEdit"
type="primary"
plain
:loading="dialogSaving === 'draft'"
@click="saveDraft"
>
暂存
</el-button>
<el-button type="primary" :loading="dialogSaving === 'submit'" @click="submitLoading">
{{ dialogMode === 'reassign' ? '确认派单' : '确认' }}
{{ dialogMode === 'reassign' ? '确认派单' : isRunningEdit ? '保存' : '确认' }}
</el-button>
</template>
</div>
@@ -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;
+91 -16
View File
@@ -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) => {
+1 -1
View File
@@ -271,7 +271,7 @@ export default {
this.unwrapData(
await billLedgerApi.getAvailableOptions(
this.billDialog.keyword,
this.form.deptId,
undefined,
this.form.billLedgerId
)
) || [];
+3 -20
View File
@@ -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');
+24
View File
@@ -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);