✨ 进行中配载可编辑、汇票选择与收款方展示优化,并支持业务部门与待确认回单
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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`,
|
||||
|
||||
@@ -139,3 +139,10 @@ export const getPlatformCompanySelect = () => {
|
||||
method: 'get',
|
||||
});
|
||||
};
|
||||
|
||||
export const getBusinessDeptSelect = () => {
|
||||
return request({
|
||||
url: '/blade-system/dept/business-dept-select',
|
||||
method: 'get',
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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' },
|
||||
];
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -271,7 +271,7 @@ export default {
|
||||
this.unwrapData(
|
||||
await billLedgerApi.getAvailableOptions(
|
||||
this.billDialog.keyword,
|
||||
this.form.deptId,
|
||||
undefined,
|
||||
this.form.billLedgerId
|
||||
)
|
||||
) || [];
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user