🔀 合并主干 master 到 dev
This commit is contained in:
@@ -24,11 +24,16 @@ export const getDetail = id =>
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getCarrierContracts = projectIds =>
|
export const getCarrierContracts = (projectIds, waybillIds = []) =>
|
||||||
request({
|
request({
|
||||||
url: `${baseUrl}/carrier-contracts`,
|
url: `${baseUrl}/carrier-contracts`,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: { projectIds: (projectIds || []).join(',') },
|
params: {
|
||||||
|
projectIds: (projectIds || []).join(','),
|
||||||
|
...(waybillIds || []).filter(Boolean).length
|
||||||
|
? { waybillIds: (waybillIds || []).filter(Boolean).join(',') }
|
||||||
|
: {},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const saveDraft = row =>
|
export const saveDraft = row =>
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export const option = {
|
|||||||
searchOrder: 1,
|
searchOrder: 1,
|
||||||
searchslot: true,
|
searchslot: true,
|
||||||
slot: true,
|
slot: true,
|
||||||
|
overHidden: true,
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -55,6 +56,8 @@ export const option = {
|
|||||||
prop: 'useDepartment',
|
prop: 'useDepartment',
|
||||||
search: true,
|
search: true,
|
||||||
searchOrder: 3,
|
searchOrder: 3,
|
||||||
|
slot: true,
|
||||||
|
overHidden: true,
|
||||||
minWidth: 140,
|
minWidth: 140,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,8 +27,14 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="合同编号">
|
<el-form-item label="合同编号" prop="contractNo">
|
||||||
<el-input v-model="form.contractNo" disabled />
|
<el-input
|
||||||
|
v-model="form.contractNo"
|
||||||
|
maxlength="100"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入合同编号"
|
||||||
|
:disabled="isSettlementRuleChange"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="合同名称" prop="contractName">
|
<el-form-item label="合同名称" prop="contractName">
|
||||||
<el-input
|
<el-input
|
||||||
@@ -662,6 +668,24 @@ export default {
|
|||||||
'按数量',
|
'按数量',
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
|
contractNo: [
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (this.isSettlementRuleChange) {
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
const contractNo = String(value || '').trim();
|
||||||
|
if (!contractNo) {
|
||||||
|
return callback(new Error('请输入合同编号'));
|
||||||
|
}
|
||||||
|
if (contractNo.length > 100) {
|
||||||
|
return callback(new Error('合同编号不能超过100个字符'));
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
trigger: 'blur',
|
||||||
|
},
|
||||||
|
],
|
||||||
contractName: [{ required: true, message: '请输入合同名称', trigger: 'blur' }],
|
contractName: [{ required: true, message: '请输入合同名称', trigger: 'blur' }],
|
||||||
contractAmount: [
|
contractAmount: [
|
||||||
{ required: true, message: '请输入合同金额', trigger: 'blur' },
|
{ required: true, message: '请输入合同金额', trigger: 'blur' },
|
||||||
@@ -956,6 +980,16 @@ export default {
|
|||||||
}
|
}
|
||||||
await this.$refs.formRef.validate();
|
await this.$refs.formRef.validate();
|
||||||
if (!this.isSettlementRuleChange) {
|
if (!this.isSettlementRuleChange) {
|
||||||
|
const contractNo = String(this.form.contractNo || '').trim();
|
||||||
|
if (!contractNo) {
|
||||||
|
this.$message.warning('请输入合同编号');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (contractNo.length > 100) {
|
||||||
|
this.$message.warning('合同编号不能超过100个字符');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.form.contractNo = contractNo;
|
||||||
const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0);
|
const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0);
|
||||||
if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) {
|
if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) {
|
||||||
this.$message.warning('付款比例上限合计必须等于100%');
|
this.$message.warning('付款比例上限合计必须等于100%');
|
||||||
|
|||||||
@@ -901,7 +901,7 @@
|
|||||||
<div class="loading-manage-dialog__admission-tip">
|
<div class="loading-manage-dialog__admission-tip">
|
||||||
<div class="loading-manage-dialog__admission-title">可配载条件</div>
|
<div class="loading-manage-dialog__admission-title">可配载条件</div>
|
||||||
<div class="loading-manage-dialog__admission-text">
|
<div class="loading-manage-dialog__admission-text">
|
||||||
同时满足以下条件的运单可被配载:运输方式为「公路」;运单状态为「待执行」或「进行中」;未关联其他有效配载单。
|
同时满足以下条件的运单可被配载:运输方式为「公路」;运单状态为「待执行」或「进行中」;未关联其他有效配载单;同一配载单的子运单须属同一项目,且承运商合同乙方须存在共同可选值(按各子单客户合同甲方匹配后取交集)。
|
||||||
</div>
|
</div>
|
||||||
<div class="loading-manage-dialog__admission-title">配载后状态说明</div>
|
<div class="loading-manage-dialog__admission-title">配载后状态说明</div>
|
||||||
<div class="loading-manage-dialog__admission-text">
|
<div class="loading-manage-dialog__admission-text">
|
||||||
@@ -1193,6 +1193,16 @@
|
|||||||
min-width="160"
|
min-width="160"
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
/>
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="carrierContractName"
|
||||||
|
label="承运商合同"
|
||||||
|
min-width="160"
|
||||||
|
show-overflow-tooltip
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.carrierContractName || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
prop="planName"
|
prop="planName"
|
||||||
label="计划名称"
|
label="计划名称"
|
||||||
@@ -3896,6 +3906,7 @@ export default {
|
|||||||
async loadCandidateWaybills() {
|
async loadCandidateWaybills() {
|
||||||
this.candidateLoading = true;
|
this.candidateLoading = true;
|
||||||
try {
|
try {
|
||||||
|
const selectedProjectId = this.selectedWaybillRows.map(row => row.projectId).find(Boolean);
|
||||||
const query = {
|
const query = {
|
||||||
...this.buildQueryParams(this.candidateQuery),
|
...this.buildQueryParams(this.candidateQuery),
|
||||||
// 公路、待执行、未配载的判定都在后端可配载开关内
|
// 公路、待执行、未配载的判定都在后端可配载开关内
|
||||||
@@ -3905,6 +3916,10 @@ export default {
|
|||||||
// 其余账户限定本组织(不展开下级)
|
// 其余账户限定本组织(不展开下级)
|
||||||
allDept: 0,
|
allDept: 0,
|
||||||
};
|
};
|
||||||
|
// 已选运单后,候选列表优先限定同一项目,便于再按承运商合同乙方交集筛选
|
||||||
|
if (selectedProjectId && !query.projectId && !query.projectName) {
|
||||||
|
query.projectId = selectedProjectId;
|
||||||
|
}
|
||||||
const res = await getWaybillList(
|
const res = await getWaybillList(
|
||||||
this.candidatePage.currentPage,
|
this.candidatePage.currentPage,
|
||||||
this.candidatePage.pageSize,
|
this.candidatePage.pageSize,
|
||||||
@@ -3966,7 +3981,10 @@ export default {
|
|||||||
];
|
];
|
||||||
if (projectIds.length) {
|
if (projectIds.length) {
|
||||||
try {
|
try {
|
||||||
const response = await loadingApi.getCarrierContracts(projectIds);
|
const waybillIds = [...this.selectedWaybillRows, ...selectedRows]
|
||||||
|
.map(row => row.id)
|
||||||
|
.filter(Boolean);
|
||||||
|
const response = await loadingApi.getCarrierContracts(projectIds, waybillIds);
|
||||||
const contracts = response?.data?.data || response?.data || [];
|
const contracts = response?.data?.data || response?.data || [];
|
||||||
const stillAllowed = (Array.isArray(contracts) ? contracts : []).some(
|
const stillAllowed = (Array.isArray(contracts) ? contracts : []).some(
|
||||||
item => String(item.id) === String(contractId)
|
item => String(item.id) === String(contractId)
|
||||||
@@ -4015,6 +4033,19 @@ export default {
|
|||||||
exists.add(key);
|
exists.add(key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// 承运商类型:新增后须仍存在共同的承运商合同乙方(按客户合同甲方匹配后取交集)
|
||||||
|
if (
|
||||||
|
String(this.dialogForm.carrierType || '').trim() === '承运商' &&
|
||||||
|
nextRows.length > this.selectedWaybillRows.length
|
||||||
|
) {
|
||||||
|
const compatible = await this.hasCompatibleCarrierContracts(nextRows);
|
||||||
|
if (!compatible) {
|
||||||
|
this.$message.warning(
|
||||||
|
'所选运单在同一项目下没有共同的承运商合同乙方,不能加入同一配载单'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
const prevRouteNodes = [...this.routeNodes];
|
const prevRouteNodes = [...this.routeNodes];
|
||||||
this.selectedWaybillRows = nextRows;
|
this.selectedWaybillRows = nextRows;
|
||||||
if (this.isRunningEdit) {
|
if (this.isRunningEdit) {
|
||||||
@@ -4028,6 +4059,18 @@ export default {
|
|||||||
this.candidateAdding = false;
|
this.candidateAdding = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async hasCompatibleCarrierContracts(rows = []) {
|
||||||
|
const projectId = (rows || []).map(row => row.projectId).find(Boolean);
|
||||||
|
const waybillIds = (rows || []).map(row => row.id).filter(Boolean);
|
||||||
|
if (!projectId || waybillIds.length < 2) return true;
|
||||||
|
try {
|
||||||
|
const response = await loadingApi.getCarrierContracts([projectId], waybillIds);
|
||||||
|
const contracts = response?.data?.data || response?.data || [];
|
||||||
|
return (Array.isArray(contracts) ? contracts : []).length > 0;
|
||||||
|
} catch (error) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
async addSelectedWaybills() {
|
async addSelectedWaybills() {
|
||||||
await this.addWaybillRows(this.candidateSelection);
|
await this.addWaybillRows(this.candidateSelection);
|
||||||
},
|
},
|
||||||
@@ -4711,9 +4754,13 @@ export default {
|
|||||||
}
|
}
|
||||||
this.taskCarrierLoading = true;
|
this.taskCarrierLoading = true;
|
||||||
try {
|
try {
|
||||||
// 配载单只有一个项目,承运商合同也必须属于该项目
|
// 配载单只有一个项目;传入子运单后按客户合同甲方匹配并取承运商合同乙方交集
|
||||||
const projectId = this.selectedWaybillRows.map(row => row.projectId).find(Boolean);
|
const projectId = this.selectedWaybillRows.map(row => row.projectId).find(Boolean);
|
||||||
const response = await loadingApi.getCarrierContracts(projectId ? [projectId] : []);
|
const waybillIds = this.selectedWaybillRows.map(row => row.id).filter(Boolean);
|
||||||
|
const response = await loadingApi.getCarrierContracts(
|
||||||
|
projectId ? [projectId] : [],
|
||||||
|
waybillIds
|
||||||
|
);
|
||||||
if (requestId !== this.taskCarrierRequestId) return [];
|
if (requestId !== this.taskCarrierRequestId) return [];
|
||||||
const contracts = response.data?.data || response.data || [];
|
const contracts = response.data?.data || response.data || [];
|
||||||
if (this.dialogForm.carrierType !== '自运') {
|
if (this.dialogForm.carrierType !== '自运') {
|
||||||
|
|||||||
@@ -251,10 +251,10 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="不含税单价" min-width="130">
|
<el-table-column label="合计金额" min-width="140">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="row.unitPriceNoTax"
|
v-model="row.totalAmount"
|
||||||
:disabled="readonly"
|
:disabled="readonly"
|
||||||
:min="0"
|
:min="0"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
@@ -263,9 +263,6 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="不含税金额" min-width="130" align="right">
|
|
||||||
<template #default="{ row }">{{ formatMoney(row.amountNoTax) }}</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="税率" min-width="110">
|
<el-table-column label="税率" min-width="110">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-select
|
<el-select
|
||||||
@@ -282,11 +279,14 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="税额" min-width="120">
|
<el-table-column label="不含税单价" min-width="140" align="right">
|
||||||
<template #default="{ row }">{{ formatMoney(row.taxAmount) }}</template>
|
<template #default="{ row }">{{ formatUnitPrice(row.unitPriceNoTax) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="合计" min-width="130" align="right">
|
<el-table-column label="不含税金额" min-width="130" align="right">
|
||||||
<template #default="{ row }">{{ formatMoney(row.totalAmount) }}</template>
|
<template #default="{ row }">{{ formatMoney(row.amountNoTax) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="税额" min-width="120" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.taxAmount) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="备注" min-width="170">
|
<el-table-column label="备注" min-width="170">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
@@ -1108,15 +1108,30 @@ export default {
|
|||||||
addLine(sheet) {
|
addLine(sheet) {
|
||||||
sheet.lines.push(emptyLine());
|
sheet.lines.push(emptyLine());
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 填写合计金额 + 税率后反算:
|
||||||
|
* 合计金额 = 数量 × 不含税单价 × (1 + 税率)
|
||||||
|
* 不含税单价 = 合计金额 / 数量 / (1 + 税率),保留 6 位小数
|
||||||
|
* 不含税金额 = 数量 × 不含税单价,保留 2 位小数
|
||||||
|
*/
|
||||||
recalculateLine(row) {
|
recalculateLine(row) {
|
||||||
const quantity = Number(row.quantity || 0);
|
const quantity = Number(row.quantity || 0);
|
||||||
const unitPrice = Number(row.unitPriceNoTax || 0);
|
const totalAmount = Number(Number(row.totalAmount || 0).toFixed(2));
|
||||||
const amountNoTax = Number((quantity * unitPrice).toFixed(2));
|
|
||||||
const rate = Number(row.taxRate || 0) / 100;
|
const rate = Number(row.taxRate || 0) / 100;
|
||||||
|
row.totalAmount = totalAmount;
|
||||||
|
row.amountWithTax = totalAmount;
|
||||||
|
if (quantity <= 0 || totalAmount <= 0) {
|
||||||
|
row.unitPriceNoTax = 0;
|
||||||
|
row.amountNoTax = 0;
|
||||||
|
row.taxAmount = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const factor = 1 + rate;
|
||||||
|
const unitPriceNoTax = Number((totalAmount / quantity / factor).toFixed(6));
|
||||||
|
const amountNoTax = Number((quantity * unitPriceNoTax).toFixed(2));
|
||||||
|
row.unitPriceNoTax = unitPriceNoTax;
|
||||||
row.amountNoTax = amountNoTax;
|
row.amountNoTax = amountNoTax;
|
||||||
row.taxAmount = rate ? Number((amountNoTax * rate).toFixed(2)) : 0;
|
row.taxAmount = Number((totalAmount - amountNoTax).toFixed(2));
|
||||||
row.totalAmount = Number((amountNoTax + row.taxAmount).toFixed(2));
|
|
||||||
row.amountWithTax = row.totalAmount;
|
|
||||||
},
|
},
|
||||||
validateBusiness() {
|
validateBusiness() {
|
||||||
if (!this.selectedDetailIds.length) throw new Error('请至少选择一条开票明细');
|
if (!this.selectedDetailIds.length) throw new Error('请至少选择一条开票明细');
|
||||||
@@ -1129,12 +1144,14 @@ export default {
|
|||||||
for (const line of sheet.lines) {
|
for (const line of sheet.lines) {
|
||||||
if (!line.goodsCategory || !line.goodsName) throw new Error('请完整填写商品和服务信息');
|
if (!line.goodsCategory || !line.goodsName) throw new Error('请完整填写商品和服务信息');
|
||||||
if (
|
if (
|
||||||
[line.quantity, line.unitPriceNoTax, line.amountNoTax, line.taxRate].some(
|
[line.quantity, line.totalAmount, line.unitPriceNoTax, line.amountNoTax, line.taxRate].some(
|
||||||
Number.isNaN
|
Number.isNaN
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw new Error('开票商品行金额格式不正确');
|
throw new Error('开票商品行金额格式不正确');
|
||||||
}
|
}
|
||||||
|
if (Number(line.quantity || 0) <= 0) throw new Error('开票商品行数量必须大于0');
|
||||||
|
if (Number(line.totalAmount || 0) <= 0) throw new Error('开票商品行合计金额必须大于0');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1234,6 +1251,9 @@ export default {
|
|||||||
formatMoney(value) {
|
formatMoney(value) {
|
||||||
return Number(value || 0).toFixed(2);
|
return Number(value || 0).toFixed(2);
|
||||||
},
|
},
|
||||||
|
formatUnitPrice(value) {
|
||||||
|
return Number(value || 0).toFixed(6);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1388,16 +1388,12 @@ export default {
|
|||||||
},
|
},
|
||||||
immediate: true,
|
immediate: true,
|
||||||
},
|
},
|
||||||
summaryTotal(value) {
|
summaryTotal() {
|
||||||
this.form.settlementAmount = Number(value || 0).toFixed(2);
|
this.syncSettlementAmountsFromSummary();
|
||||||
this.form.localSettlementAmount = (
|
|
||||||
Number(value || 0) * Number(this.form.exchangeRate || 1)
|
|
||||||
).toFixed(2);
|
|
||||||
},
|
},
|
||||||
'form.exchangeRate'(value) {
|
'form.exchangeRate'() {
|
||||||
this.form.localSettlementAmount = (
|
if (this._skipExchangeRateRebuild) return;
|
||||||
Number(this.summaryTotal || 0) * Number(value || 1)
|
this.buildSummaryFeesFromDetails();
|
||||||
).toFixed(2);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -1683,8 +1679,9 @@ export default {
|
|||||||
current.settlementAmount = current.originalAmount;
|
current.settlementAmount = current.originalAmount;
|
||||||
summaryMap.set(key, current);
|
summaryMap.set(key, current);
|
||||||
});
|
});
|
||||||
|
// 先保留预结算手工费用行,再按明细本位币 + 结算汇率重算系统归集行
|
||||||
this.summaryFees = [...summaryMap.values()];
|
this.summaryFees = [...summaryMap.values()];
|
||||||
if (!this.summaryFees.length) await this.refreshSummaryFees();
|
await this.refreshSummaryFees();
|
||||||
},
|
},
|
||||||
async loadAllContracts() {
|
async loadAllContracts() {
|
||||||
const response = await getContractOptions('');
|
const response = await getContractOptions('');
|
||||||
@@ -1786,13 +1783,19 @@ export default {
|
|||||||
},
|
},
|
||||||
async fetchBfiExchangeRate() {
|
async fetchBfiExchangeRate() {
|
||||||
const currency = this.form.currency;
|
const currency = this.form.currency;
|
||||||
if (!currency || currency === 'RMB') return;
|
if (!currency || currency === 'RMB') {
|
||||||
|
this.buildSummaryFeesFromDetails();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!this.form.exchangeRateDate) return;
|
if (!this.form.exchangeRateDate) return;
|
||||||
try {
|
try {
|
||||||
const { data } = await getBfiExchangeRate(currency, this.form.exchangeRateDate);
|
const { data } = await getBfiExchangeRate(currency, this.form.exchangeRateDate);
|
||||||
const rateData = data?.data;
|
const rateData = data?.data;
|
||||||
if (rateData?.excval != null) {
|
if (rateData?.excval != null) {
|
||||||
|
this._skipExchangeRateRebuild = true;
|
||||||
this.form.exchangeRate = Number(rateData.excval);
|
this.form.exchangeRate = Number(rateData.excval);
|
||||||
|
this._skipExchangeRateRebuild = false;
|
||||||
|
this.buildSummaryFeesFromDetails();
|
||||||
} else {
|
} else {
|
||||||
this.$message.warning(`未查询到 ${currency} 的BFI汇率数据`);
|
this.$message.warning(`未查询到 ${currency} 的BFI汇率数据`);
|
||||||
}
|
}
|
||||||
@@ -1800,6 +1803,37 @@ export default {
|
|||||||
this.$message.warning('查询BFI汇率失败,请手动输入结算汇率');
|
this.$message.warning('查询BFI汇率失败,请手动输入结算汇率');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 明细金额按本位币理解;外币结算时结算合计原金额/结算金额 = 本位币金额 / 结算汇率。
|
||||||
|
*/
|
||||||
|
convertLocalToSettlementAmount(localAmount) {
|
||||||
|
const amount = Number(localAmount || 0);
|
||||||
|
if (!Number.isFinite(amount)) return 0;
|
||||||
|
if (this.isLocalCurrencySettlement) {
|
||||||
|
return Number(amount.toFixed(2));
|
||||||
|
}
|
||||||
|
const rate = Number(this.form.exchangeRate || 0);
|
||||||
|
if (rate <= 0) return 0;
|
||||||
|
return Number((amount / rate).toFixed(2));
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 结算金额取结算合计原币汇总值;本位币合计 = 结算金额 × 结算汇率。
|
||||||
|
*/
|
||||||
|
syncSettlementAmountsFromSummary() {
|
||||||
|
const settlementTotal = this.summaryFees.reduce(
|
||||||
|
(total, row) => total + Number(row.settlementAmount || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
this.form.settlementAmount = Number(settlementTotal || 0).toFixed(2);
|
||||||
|
if (this.isLocalCurrencySettlement) {
|
||||||
|
this.form.localSettlementAmount = this.form.settlementAmount;
|
||||||
|
this.form.exchangeRate = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rate = Number(this.form.exchangeRate || 0);
|
||||||
|
this.form.localSettlementAmount =
|
||||||
|
rate > 0 ? (Number(this.form.settlementAmount || 0) * rate).toFixed(2) : '0.00';
|
||||||
|
},
|
||||||
openCandidateDialog() {
|
openCandidateDialog() {
|
||||||
this.candidate.visible = true;
|
this.candidate.visible = true;
|
||||||
this.candidate.page.current = 1;
|
this.candidate.page.current = 1;
|
||||||
@@ -2021,8 +2055,9 @@ export default {
|
|||||||
.map(row => [`${row.feeType}\u0000${row.feeItem}`, row])
|
.map(row => [`${row.feeType}\u0000${row.feeItem}`, row])
|
||||||
);
|
);
|
||||||
const append = (feeItem, originalValue, settlementValue, fallbackFeeType = '') => {
|
const append = (feeItem, originalValue, settlementValue, fallbackFeeType = '') => {
|
||||||
const originalAmount = Number(originalValue || 0);
|
// 明细费用按本位币归集,外币结算时再按汇率换算成结算原币金额
|
||||||
const settlementAmount = Number(settlementValue || 0);
|
const originalAmount = this.convertLocalToSettlementAmount(originalValue);
|
||||||
|
const settlementAmount = this.convertLocalToSettlementAmount(settlementValue);
|
||||||
if (!feeItem || !Number.isFinite(originalAmount) || !Number.isFinite(settlementAmount)) {
|
if (!feeItem || !Number.isFinite(originalAmount) || !Number.isFinite(settlementAmount)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2060,6 +2095,7 @@ export default {
|
|||||||
});
|
});
|
||||||
const manualRows = this.summaryFees.filter(row => Number(row.manualFlag) === 1);
|
const manualRows = this.summaryFees.filter(row => Number(row.manualFlag) === 1);
|
||||||
this.summaryFees = [...generatedMap.values(), ...manualRows];
|
this.summaryFees = [...generatedMap.values(), ...manualRows];
|
||||||
|
this.syncSettlementAmountsFromSummary();
|
||||||
},
|
},
|
||||||
// 每条明细对应的费用行:[调整前基准行, 调整后当前行],缺少费用快照时退化为明细自身的汇总行。
|
// 每条明细对应的费用行:[调整前基准行, 调整后当前行],缺少费用快照时退化为明细自身的汇总行。
|
||||||
resolveDetailFeePairs(detail) {
|
resolveDetailFeePairs(detail) {
|
||||||
|
|||||||
@@ -1332,9 +1332,12 @@ export default {
|
|||||||
if (value) this.initialize();
|
if (value) this.initialize();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
summaryTotal(value) {
|
summaryTotal() {
|
||||||
this.form.settlementAmount = Number(value || 0).toFixed(2);
|
this.syncSettlementAmountsFromSummary();
|
||||||
this.recalculateLocalAmount();
|
},
|
||||||
|
'form.exchangeRate'() {
|
||||||
|
if (this._skipExchangeRateRebuild) return;
|
||||||
|
this.buildSummaryFeesFromDetails();
|
||||||
},
|
},
|
||||||
filteredDetails: {
|
filteredDetails: {
|
||||||
handler(list) {
|
handler(list) {
|
||||||
@@ -1890,8 +1893,9 @@ export default {
|
|||||||
.map(row => [`${row.feeType}\u0000${row.feeItem}`, row])
|
.map(row => [`${row.feeType}\u0000${row.feeItem}`, row])
|
||||||
);
|
);
|
||||||
const appendSummary = (feeItem, originalValue, settlementValue, fallbackFeeType = '') => {
|
const appendSummary = (feeItem, originalValue, settlementValue, fallbackFeeType = '') => {
|
||||||
const originalAmount = Number(originalValue || 0);
|
// 明细费用按本位币归集,外币结算时再按汇率换算成结算原币金额
|
||||||
const settlementAmount = Number(settlementValue || 0);
|
const originalAmount = this.convertLocalToSettlementAmount(originalValue);
|
||||||
|
const settlementAmount = this.convertLocalToSettlementAmount(settlementValue);
|
||||||
if (!feeItem || !Number.isFinite(originalAmount) || !Number.isFinite(settlementAmount)) {
|
if (!feeItem || !Number.isFinite(originalAmount) || !Number.isFinite(settlementAmount)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1946,7 +1950,7 @@ export default {
|
|||||||
});
|
});
|
||||||
nextFeeMap.forEach(row => orderedFees.push(row));
|
nextFeeMap.forEach(row => orderedFees.push(row));
|
||||||
this.summaryFees = orderedFees;
|
this.summaryFees = orderedFees;
|
||||||
this.recalculateLocalAmount();
|
this.syncSettlementAmountsFromSummary();
|
||||||
},
|
},
|
||||||
// 每条明细对应的费用行:[调整前基准行, 调整后当前行],缺少来源费用快照时退化为明细自身的汇总行。
|
// 每条明细对应的费用行:[调整前基准行, 调整后当前行],缺少来源费用快照时退化为明细自身的汇总行。
|
||||||
resolveDetailFeePairs(detail) {
|
resolveDetailFeePairs(detail) {
|
||||||
@@ -2034,17 +2038,21 @@ export default {
|
|||||||
},
|
},
|
||||||
handleExchangeRateDateChange() {
|
handleExchangeRateDateChange() {
|
||||||
this.fetchBfiExchangeRate();
|
this.fetchBfiExchangeRate();
|
||||||
this.recalculateLocalAmount();
|
|
||||||
},
|
},
|
||||||
async fetchBfiExchangeRate() {
|
async fetchBfiExchangeRate() {
|
||||||
if (!this.form.currency || this.form.currency === 'RMB') return;
|
if (!this.form.currency || this.form.currency === 'RMB') {
|
||||||
|
this.buildSummaryFeesFromDetails();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!this.form.exchangeRateDate) return;
|
if (!this.form.exchangeRateDate) return;
|
||||||
try {
|
try {
|
||||||
const { data } = await getBfiExchangeRate(this.form.currency, this.form.exchangeRateDate);
|
const { data } = await getBfiExchangeRate(this.form.currency, this.form.exchangeRateDate);
|
||||||
const rateData = data?.data;
|
const rateData = data?.data;
|
||||||
if (rateData?.excval != null) {
|
if (rateData?.excval != null) {
|
||||||
|
this._skipExchangeRateRebuild = true;
|
||||||
this.form.exchangeRate = Number(rateData.excval);
|
this.form.exchangeRate = Number(rateData.excval);
|
||||||
this.recalculateLocalAmount();
|
this._skipExchangeRateRebuild = false;
|
||||||
|
this.buildSummaryFeesFromDetails();
|
||||||
} else {
|
} else {
|
||||||
this.$message.warning(`未查询到 ${this.form.currency} 的BFI汇率数据`);
|
this.$message.warning(`未查询到 ${this.form.currency} 的BFI汇率数据`);
|
||||||
}
|
}
|
||||||
@@ -2052,9 +2060,39 @@ export default {
|
|||||||
this.$message.warning('查询BFI汇率失败,请手动输入结算汇率');
|
this.$message.warning('查询BFI汇率失败,请手动输入结算汇率');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 明细金额按本位币理解;外币结算时结算合计原金额/结算金额 = 本位币金额 / 结算汇率。
|
||||||
|
*/
|
||||||
|
convertLocalToSettlementAmount(localAmount) {
|
||||||
|
const amount = Number(localAmount || 0);
|
||||||
|
if (!Number.isFinite(amount)) return 0;
|
||||||
|
if (this.isLocalCurrencySettlement) {
|
||||||
|
return Number(amount.toFixed(2));
|
||||||
|
}
|
||||||
|
const rate = Number(this.form.exchangeRate || 0);
|
||||||
|
if (rate <= 0) return 0;
|
||||||
|
return Number((amount / rate).toFixed(2));
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 结算金额取结算合计原币汇总值;本位币合计 = 结算金额 × 结算汇率。
|
||||||
|
*/
|
||||||
|
syncSettlementAmountsFromSummary() {
|
||||||
|
const settlementTotal = this.summaryFees.reduce(
|
||||||
|
(total, row) => total + Number(row.settlementAmount || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
this.form.settlementAmount = Number(settlementTotal || 0).toFixed(2);
|
||||||
|
if (this.isLocalCurrencySettlement) {
|
||||||
|
this.form.localSettlementAmount = this.form.settlementAmount;
|
||||||
|
this.form.exchangeRate = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rate = Number(this.form.exchangeRate || 0);
|
||||||
|
this.form.localSettlementAmount =
|
||||||
|
rate > 0 ? (Number(this.form.settlementAmount || 0) * rate).toFixed(2) : '0.00';
|
||||||
|
},
|
||||||
recalculateLocalAmount() {
|
recalculateLocalAmount() {
|
||||||
const rate = this.form.currency === 'RMB' ? 1 : Number(this.form.exchangeRate || 0);
|
this.buildSummaryFeesFromDetails();
|
||||||
this.form.localSettlementAmount = (Number(this.form.settlementAmount || 0) * rate).toFixed(2);
|
|
||||||
},
|
},
|
||||||
openCandidateDialog() {
|
openCandidateDialog() {
|
||||||
if (!this.form.contractId) {
|
if (!this.form.contractId) {
|
||||||
|
|||||||
@@ -59,11 +59,22 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
<template #organizationName="{ row }">
|
<template #organizationName="{ row }">
|
||||||
<span
|
<el-tooltip
|
||||||
v-if="isBlankOrganizationName(row.organizationName)"
|
:content="formatOrgDisplayName(row.organizationName)"
|
||||||
class="organization-name--empty"
|
placement="top"
|
||||||
>未分配组织</span>
|
:disabled="!formatOrgDisplayName(row.organizationName)"
|
||||||
<span v-else>{{ String(row.organizationName || '').replace(/^[\s\u3000]+/, '') }}</span>
|
>
|
||||||
|
<span class="org-cell-text">{{ formatOrgDisplayName(row.organizationName) }}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
<template #useDepartment="{ row }">
|
||||||
|
<el-tooltip
|
||||||
|
:content="formatOrgDisplayName(row.useDepartment)"
|
||||||
|
placement="top"
|
||||||
|
:disabled="!formatOrgDisplayName(row.useDepartment)"
|
||||||
|
>
|
||||||
|
<span class="org-cell-text">{{ formatOrgDisplayName(row.useDepartment) }}</span>
|
||||||
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<template #status="{ row }">
|
<template #status="{ row }">
|
||||||
<el-tag :type="row.status === 1 ? 'primary' : 'info'" class="status-text">
|
<el-tag :type="row.status === 1 ? 'primary' : 'info'" class="status-text">
|
||||||
@@ -319,26 +330,58 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="所有组织" prop="organizationName">
|
<el-form-item label="所有组织" prop="organizationName">
|
||||||
<el-cascader
|
<el-tooltip
|
||||||
v-model="organizationCascaderValue"
|
:content="
|
||||||
:options="organizationTreeOptions"
|
formatOrgCascaderPath(organizationCascaderPath) ||
|
||||||
:props="organizationCascaderProps"
|
formatOrgDisplayName(vehicleForm.organizationName)
|
||||||
filterable
|
"
|
||||||
clearable
|
placement="top"
|
||||||
placeholder="请选择"
|
:disabled="
|
||||||
/>
|
!(
|
||||||
|
formatOrgCascaderPath(organizationCascaderPath) ||
|
||||||
|
formatOrgDisplayName(vehicleForm.organizationName)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="org-cascader-tooltip">
|
||||||
|
<el-cascader
|
||||||
|
v-model="organizationCascaderValue"
|
||||||
|
:options="organizationFormTreeOptions"
|
||||||
|
:props="formOrgCascaderProps"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
placeholder="请选择"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="使用部门" prop="useDepartment">
|
<el-form-item label="使用部门" prop="useDepartment">
|
||||||
<el-cascader
|
<el-tooltip
|
||||||
v-model="useDepartmentCascaderValue"
|
:content="
|
||||||
:options="organizationTreeOptions"
|
formatOrgCascaderPath(useDepartmentCascaderPath) ||
|
||||||
:props="organizationCascaderProps"
|
formatOrgDisplayName(vehicleForm.useDepartment)
|
||||||
filterable
|
"
|
||||||
clearable
|
placement="top"
|
||||||
placeholder="请选择"
|
:disabled="
|
||||||
/>
|
!(
|
||||||
|
formatOrgCascaderPath(useDepartmentCascaderPath) ||
|
||||||
|
formatOrgDisplayName(vehicleForm.useDepartment)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="org-cascader-tooltip">
|
||||||
|
<el-cascader
|
||||||
|
v-model="useDepartmentCascaderValue"
|
||||||
|
:options="useDepartmentFormTreeOptions"
|
||||||
|
:props="formOrgCascaderProps"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
placeholder="请选择"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -656,8 +699,17 @@ export default {
|
|||||||
vehicleCertificateUploads: {},
|
vehicleCertificateUploads: {},
|
||||||
uploadHeaders: getUploadHeaders(),
|
uploadHeaders: getUploadHeaders(),
|
||||||
organizationOptions: [],
|
organizationOptions: [],
|
||||||
// 搜索栏组织级联追加「未分配组织」一项(哨兵值见 organization-search mixin)
|
// 表单两个级联各自维护路径,避免名称回写互相覆盖;不要与搜索栏共用同一棵树节点
|
||||||
enableUnassignedOrgFilter: true,
|
organizationCascaderPath: [],
|
||||||
|
useDepartmentCascaderPath: [],
|
||||||
|
formOrgCascaderProps: {
|
||||||
|
value: 'id',
|
||||||
|
label: 'label',
|
||||||
|
children: 'children',
|
||||||
|
emitPath: true,
|
||||||
|
checkStrictly: true,
|
||||||
|
expandTrigger: 'hover',
|
||||||
|
},
|
||||||
vehicleTypeOptions: [
|
vehicleTypeOptions: [
|
||||||
{ label: '高栏车', value: '高栏' },
|
{ label: '高栏车', value: '高栏' },
|
||||||
{ label: '低栏车', value: '低栏' },
|
{ label: '低栏车', value: '低栏' },
|
||||||
@@ -669,7 +721,7 @@ export default {
|
|||||||
businessRelationOptions: ['自有', '管理挂靠', '业务合作'],
|
businessRelationOptions: ['自有', '管理挂靠', '业务合作'],
|
||||||
energyTypeOptions: ['柴油', '汽油', '新能源', '天然气'],
|
energyTypeOptions: ['柴油', '汽油', '新能源', '天然气'],
|
||||||
baseRules: {
|
baseRules: {
|
||||||
// 所属组织可为空:为空表示未分配组织,数据权限为空集
|
organizationName: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
|
||||||
useDepartment: [{ required: true, message: '请选择使用部门', trigger: 'change' }],
|
useDepartment: [{ required: true, message: '请选择使用部门', trigger: 'change' }],
|
||||||
plateNo: [
|
plateNo: [
|
||||||
{ required: true, message: '请输入车牌号', trigger: 'change' },
|
{ required: true, message: '请输入车牌号', trigger: 'change' },
|
||||||
@@ -703,22 +755,31 @@ export default {
|
|||||||
: [{ required: true, message: '请选择行驶证有效期', trigger: 'change' }],
|
: [{ required: true, message: '请选择行驶证有效期', trigger: 'change' }],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
// 所属组织级联:表单存组织名称,级联控件用 id 路径,两者在此双向转换
|
// 两个级联必须使用克隆后的独立树,否则 checkStrictly 会在节点上互相抢选中态
|
||||||
|
organizationFormTreeOptions() {
|
||||||
|
return this.cloneOrgTree(this.organizationTreeOptions);
|
||||||
|
},
|
||||||
|
useDepartmentFormTreeOptions() {
|
||||||
|
return this.cloneOrgTree(this.organizationTreeOptions);
|
||||||
|
},
|
||||||
|
// 所属组织级联:本地保存 id 路径,名称同步到表单字段供后端使用
|
||||||
organizationCascaderValue: {
|
organizationCascaderValue: {
|
||||||
get() {
|
get() {
|
||||||
return this.resolveOrgPath(this.vehicleForm.organizationName);
|
return this.organizationCascaderPath;
|
||||||
},
|
},
|
||||||
set(path) {
|
set(path) {
|
||||||
this.vehicleForm.organizationName = this.resolveOrgName(path);
|
this.organizationCascaderPath = Array.isArray(path) ? [...path] : [];
|
||||||
|
this.vehicleForm.organizationName = this.resolveOrgName(this.organizationCascaderPath);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 使用部门级联:与所属组织同一套部门树
|
// 使用部门级联:独立路径状态,避免与所属组织共用计算回写导致选不中
|
||||||
useDepartmentCascaderValue: {
|
useDepartmentCascaderValue: {
|
||||||
get() {
|
get() {
|
||||||
return this.resolveOrgPath(this.vehicleForm.useDepartment);
|
return this.useDepartmentCascaderPath;
|
||||||
},
|
},
|
||||||
set(path) {
|
set(path) {
|
||||||
this.vehicleForm.useDepartment = this.resolveOrgName(path);
|
this.useDepartmentCascaderPath = Array.isArray(path) ? [...path] : [];
|
||||||
|
this.vehicleForm.useDepartment = this.resolveOrgName(this.useDepartmentCascaderPath);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
isAdmin() {
|
isAdmin() {
|
||||||
@@ -751,10 +812,49 @@ export default {
|
|||||||
this.initDeptTree();
|
this.initDeptTree();
|
||||||
this.loadOrganizationOptions();
|
this.loadOrganizationOptions();
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
organizationTreeOptions() {
|
||||||
|
if (this.vehicleBox) {
|
||||||
|
this.syncOrgCascaderPaths();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
hasPermission(code) {
|
hasPermission(code) {
|
||||||
return this.isAdmin || this.permission?.[code] === true;
|
return this.isAdmin || this.permission?.[code] === true;
|
||||||
},
|
},
|
||||||
|
/** 去掉组织名称前导空白,供列表与提示展示 */
|
||||||
|
formatOrgDisplayName(name) {
|
||||||
|
return String(name || '').replace(/^[\s\u3000]+/, '');
|
||||||
|
},
|
||||||
|
/** 级联路径还原完整组织层级文案,悬停展示用 */
|
||||||
|
formatOrgCascaderPath(path) {
|
||||||
|
const ids = Array.isArray(path)
|
||||||
|
? path.filter(item => item !== undefined && item !== null && item !== '')
|
||||||
|
: [];
|
||||||
|
if (!ids.length) return '';
|
||||||
|
const labels = [];
|
||||||
|
let nodes = this.organizationTreeOptions || [];
|
||||||
|
for (const id of ids) {
|
||||||
|
const node = nodes.find(item => String(item.id) === String(id));
|
||||||
|
if (!node) break;
|
||||||
|
labels.push(node.rawLabel || node.label || '');
|
||||||
|
nodes = node.children || [];
|
||||||
|
}
|
||||||
|
return labels.filter(Boolean).join(' / ');
|
||||||
|
},
|
||||||
|
cloneOrgTree(tree = []) {
|
||||||
|
return (tree || []).map(item => ({
|
||||||
|
id: String(item.id),
|
||||||
|
label: item.label,
|
||||||
|
rawLabel: item.rawLabel,
|
||||||
|
children: item.children?.length ? this.cloneOrgTree(item.children) : undefined,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
syncOrgCascaderPaths() {
|
||||||
|
this.organizationCascaderPath = this.resolveOrgPath(this.vehicleForm.organizationName);
|
||||||
|
this.useDepartmentCascaderPath = this.resolveOrgPath(this.vehicleForm.useDepartment);
|
||||||
|
},
|
||||||
initDeptTree() {
|
initDeptTree() {
|
||||||
getDeptTree(this.userInfo.tenantId).then(res => {
|
getDeptTree(this.userInfo.tenantId).then(res => {
|
||||||
this.organizationOptions = this.flattenDeptOptions(res.data.data || []);
|
this.organizationOptions = this.flattenDeptOptions(res.data.data || []);
|
||||||
@@ -766,6 +866,9 @@ export default {
|
|||||||
if (this.vehicleBox && !this.vehicleForm.useDepartment) {
|
if (this.vehicleBox && !this.vehicleForm.useDepartment) {
|
||||||
this.vehicleForm.useDepartment = this.getCurrentOrganizationName();
|
this.vehicleForm.useDepartment = this.getCurrentOrganizationName();
|
||||||
}
|
}
|
||||||
|
if (this.vehicleBox) {
|
||||||
|
this.syncOrgCascaderPaths();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
flattenDeptOptions(tree = [], level = 0) {
|
flattenDeptOptions(tree = [], level = 0) {
|
||||||
@@ -860,6 +963,7 @@ export default {
|
|||||||
this.vehicleForm = emptyForm();
|
this.vehicleForm = emptyForm();
|
||||||
this.vehicleForm.organizationName = this.getCurrentOrganizationName();
|
this.vehicleForm.organizationName = this.getCurrentOrganizationName();
|
||||||
this.vehicleForm.useDepartment = this.getCurrentOrganizationName();
|
this.vehicleForm.useDepartment = this.getCurrentOrganizationName();
|
||||||
|
this.syncOrgCascaderPaths();
|
||||||
this.syncPlateNo(false);
|
this.syncPlateNo(false);
|
||||||
this.vehicleBox = true;
|
this.vehicleBox = true;
|
||||||
return;
|
return;
|
||||||
@@ -873,6 +977,7 @@ export default {
|
|||||||
if (!this.vehicleForm.useDepartment) {
|
if (!this.vehicleForm.useDepartment) {
|
||||||
this.vehicleForm.useDepartment = this.getCurrentOrganizationName();
|
this.vehicleForm.useDepartment = this.getCurrentOrganizationName();
|
||||||
}
|
}
|
||||||
|
this.syncOrgCascaderPaths();
|
||||||
this.splitPlateNo();
|
this.splitPlateNo();
|
||||||
this.syncPlateNo(false);
|
this.syncPlateNo(false);
|
||||||
this.vehicleBox = true;
|
this.vehicleBox = true;
|
||||||
@@ -884,6 +989,8 @@ export default {
|
|||||||
},
|
},
|
||||||
resetVehicle() {
|
resetVehicle() {
|
||||||
this.vehicleForm = emptyForm();
|
this.vehicleForm = emptyForm();
|
||||||
|
this.organizationCascaderPath = [];
|
||||||
|
this.useDepartmentCascaderPath = [];
|
||||||
this.vehicleCertificateUploads = {};
|
this.vehicleCertificateUploads = {};
|
||||||
this.readonly = false;
|
this.readonly = false;
|
||||||
this.submitLoading = false;
|
this.submitLoading = false;
|
||||||
@@ -1503,11 +1610,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 空组织记录在列表里的占位文案
|
|
||||||
.organization-name--empty {
|
|
||||||
color: #909399;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 编辑弹窗:灰底 + section-card 白卡分组
|
// 编辑弹窗:灰底 + section-card 白卡分组
|
||||||
.vehicle-form {
|
.vehicle-form {
|
||||||
:deep(.el-form-item__label) {
|
:deep(.el-form-item__label) {
|
||||||
@@ -1541,7 +1643,20 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.org-cell-text {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
.vehicle-form {
|
.vehicle-form {
|
||||||
|
.org-cascader-tooltip {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.el-date-editor.el-input),
|
:deep(.el-date-editor.el-input),
|
||||||
:deep(.el-date-editor.el-input__wrapper),
|
:deep(.el-date-editor.el-input__wrapper),
|
||||||
:deep(.el-select),
|
:deep(.el-select),
|
||||||
|
|||||||
@@ -102,15 +102,10 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="减分最高扣分">
|
<el-form-item label="减分最高扣分">
|
||||||
<el-input
|
<el-input :model-value="HARDCODED_MAX_MINUS_SCORE" disabled style="width: 500px">
|
||||||
:model-value="configForm.maxMinusScore"
|
|
||||||
placeholder="所有减分项合计不得超过该分值"
|
|
||||||
clearable
|
|
||||||
style="width: 500px"
|
|
||||||
@input="handleMaxMinusScoreInput"
|
|
||||||
>
|
|
||||||
<template #append>分</template>
|
<template #append>分</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
|
<div class="score-config__hint">暂定最高扣 15 分,加分合计同样最高按 15 分计</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="备注">
|
<el-form-item label="备注">
|
||||||
<el-input
|
<el-input
|
||||||
@@ -500,9 +495,15 @@ const standardNumberFields = [
|
|||||||
'creditLimitIncrease',
|
'creditLimitIncrease',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/** 暂定:加分/减分合计上限均为 15 分,超出按 15 分计 */
|
||||||
|
const HARDCODED_MAX_PLUS_SCORE = 15;
|
||||||
|
const HARDCODED_MAX_MINUS_SCORE = 15;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
HARDCODED_MAX_PLUS_SCORE,
|
||||||
|
HARDCODED_MAX_MINUS_SCORE,
|
||||||
form: {},
|
form: {},
|
||||||
query: {},
|
query: {},
|
||||||
loading: true,
|
loading: true,
|
||||||
@@ -639,7 +640,7 @@ export default {
|
|||||||
name: '',
|
name: '',
|
||||||
remark: '',
|
remark: '',
|
||||||
standardDescription: '',
|
standardDescription: '',
|
||||||
maxMinusScore: '',
|
maxMinusScore: HARDCODED_MAX_MINUS_SCORE,
|
||||||
status: 3,
|
status: 3,
|
||||||
categories: this.defaultCategories(),
|
categories: this.defaultCategories(),
|
||||||
standards: [],
|
standards: [],
|
||||||
@@ -696,7 +697,8 @@ export default {
|
|||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
...detail,
|
...detail,
|
||||||
maxMinusScore: this.normalizePlainDecimal(detail.maxMinusScore),
|
// 暂定写死最高扣分,不读取历史配置值
|
||||||
|
maxMinusScore: HARDCODED_MAX_MINUS_SCORE,
|
||||||
categories: categories.map(category => ({
|
categories: categories.map(category => ({
|
||||||
...(map[category.categoryCode] || category),
|
...(map[category.categoryCode] || category),
|
||||||
categoryName: category.categoryName,
|
categoryName: category.categoryName,
|
||||||
@@ -704,14 +706,6 @@ export default {
|
|||||||
standards: (detail.standards || []).map(standard => this.normalizeStandard(standard)),
|
standards: (detail.standards || []).map(standard => this.normalizeStandard(standard)),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
handleMaxMinusScoreInput(value) {
|
|
||||||
const raw = String(value ?? '')
|
|
||||||
.replace(/[^\d.]/g, '')
|
|
||||||
.replace(/^\./, '')
|
|
||||||
.replace(/(\..*)\./g, '$1')
|
|
||||||
.replace(/^(\d+)(\.\d{0,2})?.*$/, '$1$2');
|
|
||||||
this.configForm.maxMinusScore = raw;
|
|
||||||
},
|
|
||||||
openConfig(row, readonly = false) {
|
openConfig(row, readonly = false) {
|
||||||
this.readonly = readonly;
|
this.readonly = readonly;
|
||||||
if (row && row.id) {
|
if (row && row.id) {
|
||||||
@@ -837,9 +831,7 @@ export default {
|
|||||||
},
|
},
|
||||||
prepareSubmitConfig(config) {
|
prepareSubmitConfig(config) {
|
||||||
const payload = JSON.parse(JSON.stringify(config || {}));
|
const payload = JSON.parse(JSON.stringify(config || {}));
|
||||||
const maxMinus = this.normalizePlainDecimal(payload.maxMinusScore);
|
payload.maxMinusScore = this.toScoreValue(HARDCODED_MAX_MINUS_SCORE);
|
||||||
payload.maxMinusScore =
|
|
||||||
maxMinus === '' || maxMinus == null ? null : this.toScoreValue(maxMinus);
|
|
||||||
(payload.categories || []).forEach(category => {
|
(payload.categories || []).forEach(category => {
|
||||||
(category.items || []).forEach(item => {
|
(category.items || []).forEach(item => {
|
||||||
item.score = this.toScoreValue(item.score);
|
item.score = this.toScoreValue(item.score);
|
||||||
@@ -1096,14 +1088,8 @@ export default {
|
|||||||
this.$message.warning('请输入评定表名称');
|
this.$message.warning('请输入评定表名称');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (
|
// 减分最高扣分暂定写死为 15,提交前强制回填,避免历史脏数据触发校验
|
||||||
this.configForm.maxMinusScore !== '' &&
|
this.configForm.maxMinusScore = HARDCODED_MAX_MINUS_SCORE;
|
||||||
this.configForm.maxMinusScore != null &&
|
|
||||||
!this.isNonNegativeDecimal(this.configForm.maxMinusScore)
|
|
||||||
) {
|
|
||||||
this.$message.warning('减分最高扣分须为不小于0的数字,最多两位小数');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const basic = this.configForm.categories.find(item => item.categoryCode === 'basic');
|
const basic = this.configForm.categories.find(item => item.categoryCode === 'basic');
|
||||||
if (!basic || basic.items.length === 0) {
|
if (!basic || basic.items.length === 0) {
|
||||||
this.$message.warning('至少配置1个基础得分项目');
|
this.$message.warning('至少配置1个基础得分项目');
|
||||||
@@ -1586,6 +1572,13 @@ export default {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.score-config__hint {
|
||||||
|
margin-top: 6px;
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.score-config__name-input) {
|
:deep(.score-config__name-input) {
|
||||||
width: 500px;
|
width: 500px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1588,6 +1588,9 @@ const customerArchiveFormRoute = '/vehicle/customer-archive/form';
|
|||||||
const customerArchiveListRefreshKey = 'customer-archive-list-refresh';
|
const customerArchiveListRefreshKey = 'customer-archive-list-refresh';
|
||||||
/** 客商所属组织固定为顶层组织 */
|
/** 客商所属组织固定为顶层组织 */
|
||||||
const FIXED_OWNER_DEPT_NAME = '广西现代物流集团有限公司';
|
const FIXED_OWNER_DEPT_NAME = '广西现代物流集团有限公司';
|
||||||
|
/** 暂定:加分/减分合计上限均为 15 分,超出按 15 分计 */
|
||||||
|
const HARDCODED_MAX_PLUS_SCORE = 15;
|
||||||
|
const HARDCODED_MAX_MINUS_SCORE = 15;
|
||||||
|
|
||||||
const createTimeRangeMap = {
|
const createTimeRangeMap = {
|
||||||
createTimeRange: ['createTimeStart', 'createTimeEnd'],
|
createTimeRange: ['createTimeStart', 'createTimeEnd'],
|
||||||
@@ -2291,16 +2294,18 @@ export default {
|
|||||||
{ categoryCode: 'plus', categoryName: '加分项目' },
|
{ categoryCode: 'plus', categoryName: '加分项目' },
|
||||||
{ categoryCode: 'minus', categoryName: '减分项目' },
|
{ categoryCode: 'minus', categoryName: '减分项目' },
|
||||||
];
|
];
|
||||||
const maxMinus = Number(this.currentScore?.maxMinusScore);
|
|
||||||
const hasMaxMinus = Number.isFinite(maxMinus) && maxMinus >= 0;
|
|
||||||
const rows = categoryMap.map((category, index) => {
|
const rows = categoryMap.map((category, index) => {
|
||||||
const details = this.getScoreCategoryDetails(category.categoryCode);
|
const details = this.getScoreCategoryDetails(category.categoryCode);
|
||||||
let selfScore = details.reduce((sum, item) => sum + Number(item.selfScore || 0), 0);
|
let selfScore = details.reduce((sum, item) => sum + Number(item.selfScore || 0), 0);
|
||||||
let reviewScore = details.reduce((sum, item) => sum + Number(item.reviewScore || 0), 0);
|
let reviewScore = details.reduce((sum, item) => sum + Number(item.reviewScore || 0), 0);
|
||||||
// 减分项目合计受量化表「最高扣分」限制
|
// 暂定:加分/减分合计最高按 15 分计
|
||||||
if (category.categoryCode === 'minus' && hasMaxMinus) {
|
if (category.categoryCode === 'plus') {
|
||||||
selfScore = Math.min(selfScore, maxMinus);
|
selfScore = Math.min(selfScore, HARDCODED_MAX_PLUS_SCORE);
|
||||||
reviewScore = Math.min(reviewScore, maxMinus);
|
reviewScore = Math.min(reviewScore, HARDCODED_MAX_PLUS_SCORE);
|
||||||
|
}
|
||||||
|
if (category.categoryCode === 'minus') {
|
||||||
|
selfScore = Math.min(selfScore, HARDCODED_MAX_MINUS_SCORE);
|
||||||
|
reviewScore = Math.min(reviewScore, HARDCODED_MAX_MINUS_SCORE);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...category,
|
...category,
|
||||||
@@ -4216,7 +4221,8 @@ export default {
|
|||||||
scoreDate: this.$dayjs().format('YYYY-MM-DD'),
|
scoreDate: this.$dayjs().format('YYYY-MM-DD'),
|
||||||
applyCreditLimit: this.archiveForm.applyCreditLimit || '',
|
applyCreditLimit: this.archiveForm.applyCreditLimit || '',
|
||||||
maxCreditLimit: '',
|
maxCreditLimit: '',
|
||||||
maxMinusScore: '',
|
maxMinusScore: HARDCODED_MAX_MINUS_SCORE,
|
||||||
|
maxPlusScore: HARDCODED_MAX_PLUS_SCORE,
|
||||||
creditLevel: '',
|
creditLevel: '',
|
||||||
standards: [],
|
standards: [],
|
||||||
tempApplyCreditLimit: '',
|
tempApplyCreditLimit: '',
|
||||||
@@ -4236,10 +4242,8 @@ export default {
|
|||||||
: score.applyCreditLimit
|
: score.applyCreditLimit
|
||||||
),
|
),
|
||||||
maxCreditLimit: this.normalizeOptionalAmount(score.maxCreditLimit),
|
maxCreditLimit: this.normalizeOptionalAmount(score.maxCreditLimit),
|
||||||
maxMinusScore:
|
maxMinusScore: HARDCODED_MAX_MINUS_SCORE,
|
||||||
score.maxMinusScore === undefined || score.maxMinusScore === null
|
maxPlusScore: HARDCODED_MAX_PLUS_SCORE,
|
||||||
? ''
|
|
||||||
: score.maxMinusScore,
|
|
||||||
tempApplyCreditLimit: this.normalizeOptionalAmount(score.tempApplyCreditLimit),
|
tempApplyCreditLimit: this.normalizeOptionalAmount(score.tempApplyCreditLimit),
|
||||||
attachments: score.attachments || this.parseAttachments(score.proofAttachments),
|
attachments: score.attachments || this.parseAttachments(score.proofAttachments),
|
||||||
details: (score.details || []).map(detail => ({
|
details: (score.details || []).map(detail => ({
|
||||||
@@ -4699,11 +4703,8 @@ export default {
|
|||||||
applyCreditLimit: this.currentScore.applyCreditLimit,
|
applyCreditLimit: this.currentScore.applyCreditLimit,
|
||||||
maxCreditLimit: '',
|
maxCreditLimit: '',
|
||||||
creditLevel: '',
|
creditLevel: '',
|
||||||
maxMinusScore:
|
maxMinusScore: HARDCODED_MAX_MINUS_SCORE,
|
||||||
quantificationData.maxMinusScore ??
|
maxPlusScore: HARDCODED_MAX_PLUS_SCORE,
|
||||||
scoreTemplate.maxMinusScore ??
|
|
||||||
this.currentScore.maxMinusScore ??
|
|
||||||
'',
|
|
||||||
tempApplyCreditLimit: this.currentScore.tempApplyCreditLimit,
|
tempApplyCreditLimit: this.currentScore.tempApplyCreditLimit,
|
||||||
tempCreditStartDate: this.currentScore.tempCreditStartDate,
|
tempCreditStartDate: this.currentScore.tempCreditStartDate,
|
||||||
tempCreditEndDate: this.currentScore.tempCreditEndDate,
|
tempCreditEndDate: this.currentScore.tempCreditEndDate,
|
||||||
@@ -4722,14 +4723,11 @@ export default {
|
|||||||
},
|
},
|
||||||
applyMaxMinusScoreFromQuantification(quantificationId) {
|
applyMaxMinusScoreFromQuantification(quantificationId) {
|
||||||
if (!quantificationId) return Promise.resolve();
|
if (!quantificationId) return Promise.resolve();
|
||||||
return getCreditScoreQuantificationDetail(quantificationId).then(res => {
|
// 暂定写死加分/减分上限,不再依赖量化表配置值
|
||||||
const data = res?.data?.data || {};
|
this.currentScore.maxMinusScore = HARDCODED_MAX_MINUS_SCORE;
|
||||||
this.currentScore.maxMinusScore =
|
this.currentScore.maxPlusScore = HARDCODED_MAX_PLUS_SCORE;
|
||||||
data.maxMinusScore === undefined || data.maxMinusScore === null
|
this.calculateScore(this.currentScore);
|
||||||
? ''
|
return Promise.resolve();
|
||||||
: data.maxMinusScore;
|
|
||||||
this.calculateScore(this.currentScore);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
normalizeScoreTemplateDetails(template = {}) {
|
normalizeScoreTemplateDetails(template = {}) {
|
||||||
const categoryDetails = (template.categories || []).flatMap(category =>
|
const categoryDetails = (template.categories || []).flatMap(category =>
|
||||||
@@ -5073,17 +5071,17 @@ export default {
|
|||||||
const plusDetails = this.getScoreCategoryDetailsByList(details, 'plus');
|
const plusDetails = this.getScoreCategoryDetailsByList(details, 'plus');
|
||||||
const minusDetails = this.getScoreCategoryDetailsByList(details, 'minus');
|
const minusDetails = this.getScoreCategoryDetailsByList(details, 'minus');
|
||||||
const sumScore = (list, prop) => list.reduce((sum, item) => sum + Number(item[prop] || 0), 0);
|
const sumScore = (list, prop) => list.reduce((sum, item) => sum + Number(item[prop] || 0), 0);
|
||||||
|
const rawPlusSelf = sumScore(plusDetails, 'selfScore');
|
||||||
|
const rawPlusReview = sumScore(plusDetails, 'reviewScore');
|
||||||
const rawMinusSelf = sumScore(minusDetails, 'selfScore');
|
const rawMinusSelf = sumScore(minusDetails, 'selfScore');
|
||||||
const rawMinusReview = sumScore(minusDetails, 'reviewScore');
|
const rawMinusReview = sumScore(minusDetails, 'reviewScore');
|
||||||
// 量化表「减分最高扣分」:所有减分项合计不得超过该上限
|
// 暂定:加分/减分合计最高按 15 分计
|
||||||
const maxMinus = Number(score.maxMinusScore);
|
const plusSelf = Math.min(rawPlusSelf, HARDCODED_MAX_PLUS_SCORE);
|
||||||
const hasMaxMinus = Number.isFinite(maxMinus) && maxMinus >= 0;
|
const plusReview = Math.min(rawPlusReview, HARDCODED_MAX_PLUS_SCORE);
|
||||||
const minusSelf = hasMaxMinus ? Math.min(rawMinusSelf, maxMinus) : rawMinusSelf;
|
const minusSelf = Math.min(rawMinusSelf, HARDCODED_MAX_MINUS_SCORE);
|
||||||
const minusReview = hasMaxMinus ? Math.min(rawMinusReview, maxMinus) : rawMinusReview;
|
const minusReview = Math.min(rawMinusReview, HARDCODED_MAX_MINUS_SCORE);
|
||||||
const selfScore =
|
const selfScore = sumScore(basicDetails, 'selfScore') + plusSelf - minusSelf;
|
||||||
sumScore(basicDetails, 'selfScore') + sumScore(plusDetails, 'selfScore') - minusSelf;
|
const reviewScore = sumScore(basicDetails, 'reviewScore') + plusReview - minusReview;
|
||||||
const reviewScore =
|
|
||||||
sumScore(basicDetails, 'reviewScore') + sumScore(plusDetails, 'reviewScore') - minusReview;
|
|
||||||
const hasReviewScore = details.some(
|
const hasReviewScore = details.some(
|
||||||
item => !this.isBlankScore(item.reviewScore)
|
item => !this.isBlankScore(item.reviewScore)
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user