✨ 完善付款申请多结算行、结算导入编辑与计费规则展示
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -52,6 +52,7 @@ export const settlementDetailColumns = [
|
|||||||
{ label: '运输总量', prop: 'transportQuantityText', minWidth: 120 },
|
{ label: '运输总量', prop: 'transportQuantityText', minWidth: 120 },
|
||||||
{ label: '里程(KM)', prop: 'mileage', minWidth: 120 },
|
{ label: '里程(KM)', prop: 'mileage', minWidth: 120 },
|
||||||
{ label: '批次号', prop: 'batchNo', minWidth: 130 },
|
{ label: '批次号', prop: 'batchNo', minWidth: 130 },
|
||||||
|
{ label: '计费方式', prop: 'billingType', minWidth: 130 },
|
||||||
{ label: '运输单价', prop: 'unitPrice', minWidth: 120, money: true },
|
{ label: '运输单价', prop: 'unitPrice', minWidth: 120, money: true },
|
||||||
{ label: '运费', prop: 'freightAmount', minWidth: 120, money: true },
|
{ label: '运费', prop: 'freightAmount', minWidth: 120, money: true },
|
||||||
{ label: '原金额', prop: 'originalAmount', minWidth: 120, money: true },
|
{ label: '原金额', prop: 'originalAmount', minWidth: 120, money: true },
|
||||||
@@ -94,5 +95,6 @@ export const candidateDetailColumns = [
|
|||||||
{ label: '货物名称', prop: 'cargoName', minWidth: 140 },
|
{ label: '货物名称', prop: 'cargoName', minWidth: 140 },
|
||||||
{ label: '货物类型', prop: 'cargoType', minWidth: 130 },
|
{ label: '货物类型', prop: 'cargoType', minWidth: 130 },
|
||||||
{ label: '批次号', prop: 'batchNo', minWidth: 120 },
|
{ label: '批次号', prop: 'batchNo', minWidth: 120 },
|
||||||
|
{ label: '计费方式', prop: 'billingType', minWidth: 130 },
|
||||||
{ label: '结算金额', prop: 'totalAmount', minWidth: 130, money: true },
|
{ label: '结算金额', prop: 'totalAmount', minWidth: 130, money: true },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
export function isFreightFeeName(name) {
|
||||||
|
const text = String(name || '');
|
||||||
|
return text.includes('运费') || text.includes('运输费');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 运输单价对应的运费计费类型。 */
|
||||||
|
export function freightBillingType(row = {}) {
|
||||||
|
const rule = row.billingRule || {};
|
||||||
|
const rules = Array.isArray(rule.rules) ? rule.rules : [];
|
||||||
|
const freightRule = rules.find(
|
||||||
|
item => isFreightFeeName(item?.feeItem) || isFreightFeeName(item?.feeType)
|
||||||
|
);
|
||||||
|
return freightRule?.billingType || row.billingType || rule.billingType || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isTierUnitPrice(row) {
|
||||||
|
return freightBillingType(row) === '阶梯单价';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isIntervalBilling(row) {
|
||||||
|
const type = freightBillingType(row);
|
||||||
|
return type === '区间单价' || (type.includes('区间') && type.includes('一口价'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 阶梯单价、区间单价、区间一口价的运输单价不开放手工调整。 */
|
||||||
|
export function isUnitPriceAdjustable(row) {
|
||||||
|
const type = freightBillingType(row);
|
||||||
|
if (!type) return true;
|
||||||
|
return !isTierUnitPrice(row) && !isIntervalBilling(row);
|
||||||
|
}
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
><div class="payment-form-page__percentage-input">
|
><div class="payment-form-page__percentage-input">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="form.paymentRatio"
|
v-model="form.paymentRatio"
|
||||||
:disabled="readonly"
|
:disabled="readonly || multipleSettlementRows"
|
||||||
:min="0"
|
:min="0"
|
||||||
:max="100"
|
:max="100"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
><el-form-item label="申请付款金额" prop="appliedAmount"
|
><el-form-item label="申请付款金额" prop="appliedAmount"
|
||||||
><el-input-number
|
><el-input-number
|
||||||
v-model="form.appliedAmount"
|
v-model="form.appliedAmount"
|
||||||
:disabled="readonly"
|
:disabled="readonly || multipleSettlementRows"
|
||||||
:min="0"
|
:min="0"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
:step="0.01"
|
:step="0.01"
|
||||||
@@ -241,15 +241,39 @@
|
|||||||
<el-table-column prop="payableAmount" label="可付款金额" min-width="130">
|
<el-table-column prop="payableAmount" label="可付款金额" min-width="130">
|
||||||
<template #default="{ row }">{{ formatMoney(row.payableAmount) }}</template>
|
<template #default="{ row }">{{ formatMoney(row.payableAmount) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="paymentRatio" label="付款比例(%)" min-width="110">
|
<el-table-column prop="paymentRatio" label="付款比例(%)" min-width="160">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span :class="{ 'payment-form-page__ratio-exceeded': row.ratioExceeded }">
|
<el-input-number
|
||||||
|
v-if="multipleSettlementRows && !readonly"
|
||||||
|
:model-value="row.paymentRatio"
|
||||||
|
:min="0"
|
||||||
|
:max="rowPaymentRatioMax(row)"
|
||||||
|
:precision="2"
|
||||||
|
:controls="false"
|
||||||
|
:value-on-clear="null"
|
||||||
|
style="width: 100%"
|
||||||
|
@update:model-value="value => handleRowPaymentRatioChange(row, value)"
|
||||||
|
/>
|
||||||
|
<span v-else :class="{ 'payment-form-page__ratio-exceeded': row.ratioExceeded }">
|
||||||
{{ formatMoney(row.paymentRatio) }}
|
{{ formatMoney(row.paymentRatio) }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="appliedAmount" label="付款金额" min-width="130">
|
<el-table-column prop="appliedAmount" label="付款金额" min-width="160">
|
||||||
<template #default="{ row }">{{ formatMoney(row.appliedAmount) }}</template>
|
<template #default="{ row }">
|
||||||
|
<el-input-number
|
||||||
|
v-if="multipleSettlementRows && !readonly"
|
||||||
|
:model-value="row.appliedAmount"
|
||||||
|
:min="0"
|
||||||
|
:max="Number(row.payableAmount || 0)"
|
||||||
|
:precision="2"
|
||||||
|
:controls="false"
|
||||||
|
:value-on-clear="null"
|
||||||
|
style="width: 100%"
|
||||||
|
@update:model-value="value => handleRowAppliedAmountChange(row, value)"
|
||||||
|
/>
|
||||||
|
<span v-else>{{ formatMoney(row.appliedAmount) }}</span>
|
||||||
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="ratioRemark" label="备注" min-width="220" show-overflow-tooltip>
|
<el-table-column prop="ratioRemark" label="备注" min-width="220" show-overflow-tooltip>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
@@ -988,12 +1012,27 @@ export default {
|
|||||||
referenceSettlementCount() {
|
referenceSettlementCount() {
|
||||||
return this.referenceSettlementNos.length;
|
return this.referenceSettlementNos.length;
|
||||||
},
|
},
|
||||||
|
multipleSettlementRows() {
|
||||||
|
return this.referenceRows.length > 1;
|
||||||
|
},
|
||||||
// 结算单号展示:单张显示单号;多张仅提示数量,明细在下方「结算信息」表,悬停可看全部单号
|
// 结算单号展示:单张显示单号;多张仅提示数量,明细在下方「结算信息」表,悬停可看全部单号
|
||||||
referenceDisplayText() {
|
referenceDisplayText() {
|
||||||
if (this.referenceSettlementCount > 1) return `已选 ${this.referenceSettlementCount} 张结算单`;
|
if (this.referenceSettlementCount > 1) return `已选 ${this.referenceSettlementCount} 张结算单`;
|
||||||
return this.referenceLabel;
|
return this.referenceLabel;
|
||||||
},
|
},
|
||||||
settlementRows() {
|
settlementRows() {
|
||||||
|
if (this.referenceRows.length > 1) {
|
||||||
|
return this.referenceRows.map(item => {
|
||||||
|
const paymentRatio = item.paymentRatio ?? this.form.paymentRatio;
|
||||||
|
const appliedAmount = Number(item.appliedAmount || 0);
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
paymentRatio,
|
||||||
|
appliedAmount,
|
||||||
|
...this.buildSettlementRatioHint({ ...item, paymentRatio }),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
if (this.referenceRows.length) {
|
if (this.referenceRows.length) {
|
||||||
return this.referenceRows.map(item => {
|
return this.referenceRows.map(item => {
|
||||||
const appliedAmount =
|
const appliedAmount =
|
||||||
@@ -1057,7 +1096,7 @@ export default {
|
|||||||
},
|
},
|
||||||
'form.paymentRatio'(value) {
|
'form.paymentRatio'(value) {
|
||||||
this.checkContractPaymentRatio(value);
|
this.checkContractPaymentRatio(value);
|
||||||
if (this.amountSyncing || !this.paymentAmountBase) return;
|
if (this.amountSyncing || this.multipleSettlementRows || !this.paymentAmountBase) return;
|
||||||
this.amountSyncing = true;
|
this.amountSyncing = true;
|
||||||
this.form.appliedAmount = Number(
|
this.form.appliedAmount = Number(
|
||||||
((this.paymentAmountBase * Number(value || 0)) / 100).toFixed(2)
|
((this.paymentAmountBase * Number(value || 0)) / 100).toFixed(2)
|
||||||
@@ -1067,7 +1106,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
'form.appliedAmount'(value) {
|
'form.appliedAmount'(value) {
|
||||||
if (this.amountSyncing || !this.paymentAmountBase) return;
|
if (this.amountSyncing || this.multipleSettlementRows || !this.paymentAmountBase) return;
|
||||||
this.amountSyncing = true;
|
this.amountSyncing = true;
|
||||||
this.form.paymentRatio = Number(
|
this.form.paymentRatio = Number(
|
||||||
((Number(value || 0) / this.paymentAmountBase) * 100).toFixed(2)
|
((Number(value || 0) / this.paymentAmountBase) * 100).toFixed(2)
|
||||||
@@ -1080,6 +1119,7 @@ export default {
|
|||||||
if (
|
if (
|
||||||
this.amountSyncing ||
|
this.amountSyncing ||
|
||||||
this.form.paymentType === 'project_advance' ||
|
this.form.paymentType === 'project_advance' ||
|
||||||
|
this.multipleSettlementRows ||
|
||||||
!this.paymentAmountBase
|
!this.paymentAmountBase
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
@@ -1292,6 +1332,7 @@ export default {
|
|||||||
}
|
}
|
||||||
const referenceRow = this.createReferenceRow(data);
|
const referenceRow = this.createReferenceRow(data);
|
||||||
this.referenceRows = this.restoreReferenceRows(data, referenceRow);
|
this.referenceRows = this.restoreReferenceRows(data, referenceRow);
|
||||||
|
if (this.referenceRows.length > 1) this.syncHeaderPaymentFromRows();
|
||||||
this.paymentRecords = data.paymentRecords || [];
|
this.paymentRecords = data.paymentRecords || [];
|
||||||
},
|
},
|
||||||
async initialize() {
|
async initialize() {
|
||||||
@@ -1315,6 +1356,7 @@ export default {
|
|||||||
};
|
};
|
||||||
const referenceRow = this.createReferenceRow(data);
|
const referenceRow = this.createReferenceRow(data);
|
||||||
this.referenceRows = this.restoreReferenceRows(data, referenceRow);
|
this.referenceRows = this.restoreReferenceRows(data, referenceRow);
|
||||||
|
this.clearFormalReferenceWhenPreOnly();
|
||||||
this.paymentRecords = data.paymentRecords || [];
|
this.paymentRecords = data.paymentRecords || [];
|
||||||
if (this.form.paymentType === 'project_advance') {
|
if (this.form.paymentType === 'project_advance') {
|
||||||
await this.loadProjectOptions();
|
await this.loadProjectOptions();
|
||||||
@@ -1322,6 +1364,7 @@ export default {
|
|||||||
}
|
}
|
||||||
await this.fillBillTypeFromContract(this.form.contractId);
|
await this.fillBillTypeFromContract(this.form.contractId);
|
||||||
await this.refreshSettlementPaymentSequences();
|
await this.refreshSettlementPaymentSequences();
|
||||||
|
if (this.referenceRows.length > 1) this.syncHeaderPaymentFromRows();
|
||||||
await this.loadAttachmentRuleData(await this.loadCurrentSettlementDetails());
|
await this.loadAttachmentRuleData(await this.loadCurrentSettlementDetails());
|
||||||
if (this.billPayment) await this.loadBillOptions('', data.billLedgerId);
|
if (this.billPayment) await this.loadBillOptions('', data.billLedgerId);
|
||||||
} else {
|
} else {
|
||||||
@@ -1489,6 +1532,7 @@ export default {
|
|||||||
fixedAppliedAmount: true,
|
fixedAppliedAmount: true,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
this.seedMultipleRowPayments(referenceRows);
|
||||||
const settlementAmount = referenceRows.reduce(
|
const settlementAmount = referenceRows.reduce(
|
||||||
(total, item) => total + Number(item.settlementAmount || 0),
|
(total, item) => total + Number(item.settlementAmount || 0),
|
||||||
0
|
0
|
||||||
@@ -1553,20 +1597,48 @@ export default {
|
|||||||
this.$refs.formRef?.clearValidate();
|
this.$refs.formRef?.clearValidate();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
clearFormalReferenceWhenPreOnly() {
|
||||||
|
const progressAdvance = this.form.paymentType === 'progress_advance';
|
||||||
|
const onlyPre =
|
||||||
|
progressAdvance ||
|
||||||
|
(this.referenceRows.length > 0 &&
|
||||||
|
this.referenceRows.every(item => item.referenceType === 'pre'));
|
||||||
|
if (!onlyPre) return;
|
||||||
|
if (!this.form.preSettlementId) {
|
||||||
|
const preId =
|
||||||
|
this.referenceRows.find(item => item.referenceType === 'pre')?.id ||
|
||||||
|
(progressAdvance ? this.form.settlementId : null);
|
||||||
|
if (preId) {
|
||||||
|
this.form.preSettlementId = preId;
|
||||||
|
this.form.preSettlementIds = [preId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.form.settlementId = null;
|
||||||
|
this.form.settlementIds = [];
|
||||||
|
this.form.settlementNo = '';
|
||||||
|
},
|
||||||
restoreReferenceRows(data, fallbackRow) {
|
restoreReferenceRows(data, fallbackRow) {
|
||||||
const relations = Array.isArray(data?.settlements) ? data.settlements : [];
|
const relations = Array.isArray(data?.settlements) ? data.settlements : [];
|
||||||
const rows = relations
|
const rows = relations
|
||||||
.map(item => {
|
.map(item => {
|
||||||
const isPre = item.preSettlementId !== undefined && item.preSettlementId !== null && item.preSettlementId !== '';
|
const isPre = Boolean(item.preSettlementId || item.preSettlementNo);
|
||||||
const settlementNo = item.preSettlementNo || item.formalSettlementNo || '';
|
const settlementNo = item.preSettlementNo || item.formalSettlementNo || '';
|
||||||
if (!settlementNo) return null;
|
if (!settlementNo) return null;
|
||||||
return {
|
return {
|
||||||
id: isPre ? item.preSettlementId : item.formalSettlementId,
|
id: isPre ? item.preSettlementId || item.formalSettlementId : item.formalSettlementId,
|
||||||
settlementNo,
|
settlementNo,
|
||||||
referenceType: isPre ? 'pre' : 'formal',
|
referenceType: isPre ? 'pre' : 'formal',
|
||||||
settlementAmount: Number(Number(item.settlementAmount || 0).toFixed(2)),
|
settlementAmount: Number(Number(item.settlementAmount || 0).toFixed(2)),
|
||||||
payableAmount: Number(Number(item.payableAmount ?? item.settlementAmount ?? 0).toFixed(2)),
|
payableAmount: Number(Number(item.payableAmount ?? item.settlementAmount ?? 0).toFixed(2)),
|
||||||
appliedAmount: Number(Number(item.appliedAmount || 0).toFixed(2)),
|
appliedAmount: Number(Number(item.appliedAmount || 0).toFixed(2)),
|
||||||
|
paymentRatio: Number(item.settlementAmount || 0)
|
||||||
|
? Number(
|
||||||
|
(
|
||||||
|
(Number(item.appliedAmount || 0) / Number(item.settlementAmount || 0)) *
|
||||||
|
100
|
||||||
|
).toFixed(2)
|
||||||
|
)
|
||||||
|
: null,
|
||||||
fixedAppliedAmount: this.readonly,
|
fixedAppliedAmount: this.readonly,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@@ -1582,7 +1654,12 @@ export default {
|
|||||||
},
|
},
|
||||||
createReferenceRow(row) {
|
createReferenceRow(row) {
|
||||||
const settlementAmount = Number(row.settlementAmount || 0);
|
const settlementAmount = Number(row.settlementAmount || 0);
|
||||||
const isPre = Boolean(row.preSettlementNo || row.preSettlementId || row.referenceType === 'pre');
|
const isPre = Boolean(
|
||||||
|
row.preSettlementNo ||
|
||||||
|
row.preSettlementId ||
|
||||||
|
row.referenceType === 'pre' ||
|
||||||
|
row.paymentType === 'progress_advance'
|
||||||
|
);
|
||||||
const paymentSequence = Number(row.paymentSequence);
|
const paymentSequence = Number(row.paymentSequence);
|
||||||
// 可付款金额必须来自 referenceAmount 接口(已扣减占用中的付款申请);缺失时置 0,避免误用未扣减结算金额
|
// 可付款金额必须来自 referenceAmount 接口(已扣减占用中的付款申请);缺失时置 0,避免误用未扣减结算金额
|
||||||
const payableAmount =
|
const payableAmount =
|
||||||
@@ -1590,7 +1667,7 @@ export default {
|
|||||||
? 0
|
? 0
|
||||||
: Number(row.payableAmount || 0);
|
: Number(row.payableAmount || 0);
|
||||||
return {
|
return {
|
||||||
id: row.preSettlementId || row.settlementId || row.id,
|
id: row.preSettlementId || (isPre ? row.settlementId : null) || row.settlementId || row.id,
|
||||||
settlementNo: row.preSettlementNo || row.formalSettlementNo || row.settlementNo || '',
|
settlementNo: row.preSettlementNo || row.formalSettlementNo || row.settlementNo || '',
|
||||||
referenceType: isPre ? 'pre' : 'formal',
|
referenceType: isPre ? 'pre' : 'formal',
|
||||||
settlementAmount: Number(Number(settlementAmount || 0).toFixed(2)),
|
settlementAmount: Number(Number(settlementAmount || 0).toFixed(2)),
|
||||||
@@ -1604,7 +1681,11 @@ export default {
|
|||||||
*/
|
*/
|
||||||
buildSettlementRatioHint(row = {}) {
|
buildSettlementRatioHint(row = {}) {
|
||||||
const sequence = Number(row.paymentSequence || 1);
|
const sequence = Number(row.paymentSequence || 1);
|
||||||
const paymentRatio = Number(this.form.paymentRatio);
|
const rawRatio =
|
||||||
|
row.paymentRatio !== undefined && row.paymentRatio !== null && row.paymentRatio !== ''
|
||||||
|
? row.paymentRatio
|
||||||
|
: this.form.paymentRatio;
|
||||||
|
const paymentRatio = Number(rawRatio);
|
||||||
const ratioConfig = this.contractPaymentRatioRows[sequence - 1];
|
const ratioConfig = this.contractPaymentRatioRows[sequence - 1];
|
||||||
const ratioLimitValue = ratioConfig?.ratioLimit;
|
const ratioLimitValue = ratioConfig?.ratioLimit;
|
||||||
const ratioLimit = Number(ratioLimitValue);
|
const ratioLimit = Number(ratioLimitValue);
|
||||||
@@ -1614,9 +1695,9 @@ export default {
|
|||||||
String(ratioLimitValue).trim() !== '' &&
|
String(ratioLimitValue).trim() !== '' &&
|
||||||
Number.isFinite(ratioLimit);
|
Number.isFinite(ratioLimit);
|
||||||
const hasPaymentRatio =
|
const hasPaymentRatio =
|
||||||
this.form.paymentRatio !== null &&
|
rawRatio !== null &&
|
||||||
this.form.paymentRatio !== undefined &&
|
rawRatio !== undefined &&
|
||||||
String(this.form.paymentRatio).trim() !== '' &&
|
String(rawRatio).trim() !== '' &&
|
||||||
Number.isFinite(paymentRatio);
|
Number.isFinite(paymentRatio);
|
||||||
const ratioExceeded = hasLimit && hasPaymentRatio && paymentRatio > ratioLimit;
|
const ratioExceeded = hasLimit && hasPaymentRatio && paymentRatio > ratioLimit;
|
||||||
return {
|
return {
|
||||||
@@ -1737,6 +1818,7 @@ export default {
|
|||||||
this.$message.error(error?.message || '可付款金额获取失败,请重试');
|
this.$message.error(error?.message || '可付款金额获取失败,请重试');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.seedMultipleRowPayments(this.referenceRows);
|
||||||
const settlementAmount = this.referenceRows.reduce(
|
const settlementAmount = this.referenceRows.reduce(
|
||||||
(total, item) => total + Number(item.settlementAmount || 0),
|
(total, item) => total + Number(item.settlementAmount || 0),
|
||||||
0
|
0
|
||||||
@@ -1745,7 +1827,19 @@ export default {
|
|||||||
(total, item) => total + Number(item.payableAmount || 0),
|
(total, item) => total + Number(item.payableAmount || 0),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
const paymentRatio = Number(this.form.paymentRatio || 0);
|
const appliedAmount = this.referenceRows.reduce(
|
||||||
|
(total, item) => total + Number(item.appliedAmount || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const multiple = this.referenceRows.length > 1;
|
||||||
|
const paymentRatio = multiple
|
||||||
|
? settlementAmount
|
||||||
|
? Number(((appliedAmount / settlementAmount) * 100).toFixed(2))
|
||||||
|
: 0
|
||||||
|
: Number(this.form.paymentRatio || 0);
|
||||||
|
const headerAppliedAmount = multiple
|
||||||
|
? Number(appliedAmount.toFixed(2))
|
||||||
|
: Number(((payableAmount * paymentRatio) / 100).toFixed(2));
|
||||||
// 尾款付款场景下选择预付结算单时保留尾款付款类型,不强制改为进度预付
|
// 尾款付款场景下选择预付结算单时保留尾款付款类型,不强制改为进度预付
|
||||||
this.amountSyncing = true;
|
this.amountSyncing = true;
|
||||||
Object.assign(this.form, {
|
Object.assign(this.form, {
|
||||||
@@ -1771,8 +1865,8 @@ export default {
|
|||||||
settlementAmount: Number(settlementAmount.toFixed(2)),
|
settlementAmount: Number(settlementAmount.toFixed(2)),
|
||||||
payableAmount: Number(payableAmount.toFixed(2)),
|
payableAmount: Number(payableAmount.toFixed(2)),
|
||||||
billType: '',
|
billType: '',
|
||||||
// 申请金额按可付款金额×比例,避免未扣减已申请额度
|
paymentRatio,
|
||||||
appliedAmount: Number(((payableAmount * paymentRatio) / 100).toFixed(2)),
|
appliedAmount: headerAppliedAmount,
|
||||||
});
|
});
|
||||||
this.paymentRecords = [];
|
this.paymentRecords = [];
|
||||||
this.clearReceiptAccount();
|
this.clearReceiptAccount();
|
||||||
@@ -2914,6 +3008,94 @@ export default {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
rowPaymentRatioMax(row) {
|
||||||
|
const settlementAmount = Number(row.settlementAmount || 0);
|
||||||
|
const payableAmount = Number(row.payableAmount || 0);
|
||||||
|
if (settlementAmount <= 0) return 100;
|
||||||
|
return Number(Math.min(100, (payableAmount / settlementAmount) * 100).toFixed(2));
|
||||||
|
},
|
||||||
|
seedMultipleRowPayments(rows = []) {
|
||||||
|
if (rows.length <= 1) return;
|
||||||
|
const ratio = Number(this.form.paymentRatio || 0);
|
||||||
|
rows.forEach(item => {
|
||||||
|
const settlementAmount = Number(item.settlementAmount || 0);
|
||||||
|
const payableAmount = Number(item.payableAmount || 0);
|
||||||
|
const hasRatio =
|
||||||
|
item.paymentRatio !== null && item.paymentRatio !== undefined && item.paymentRatio !== '';
|
||||||
|
if (hasRatio) return;
|
||||||
|
let applied = Number(item.appliedAmount || 0);
|
||||||
|
if (applied <= 0 && ratio > 0) {
|
||||||
|
applied = Number(((settlementAmount * ratio) / 100).toFixed(2));
|
||||||
|
}
|
||||||
|
if (applied > payableAmount) applied = Number(payableAmount.toFixed(2));
|
||||||
|
item.appliedAmount = Number(applied.toFixed(2));
|
||||||
|
item.paymentRatio = settlementAmount
|
||||||
|
? Number(((item.appliedAmount / settlementAmount) * 100).toFixed(2))
|
||||||
|
: 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
findReferenceRow(row) {
|
||||||
|
return this.referenceRows.find(
|
||||||
|
item =>
|
||||||
|
item.id === row.id &&
|
||||||
|
item.referenceType === row.referenceType &&
|
||||||
|
item.settlementNo === row.settlementNo
|
||||||
|
);
|
||||||
|
},
|
||||||
|
handleRowPaymentRatioChange(row, value) {
|
||||||
|
const target = this.findReferenceRow(row);
|
||||||
|
if (!target) return;
|
||||||
|
const settlementAmount = Number(target.settlementAmount || 0);
|
||||||
|
const payableAmount = Number(target.payableAmount || 0);
|
||||||
|
const ratio = value === null || value === undefined || value === '' ? 0 : Number(value);
|
||||||
|
let applied = Number(((settlementAmount * ratio) / 100).toFixed(2));
|
||||||
|
if (applied > payableAmount) applied = Number(payableAmount.toFixed(2));
|
||||||
|
target.appliedAmount = applied;
|
||||||
|
target.paymentRatio = settlementAmount
|
||||||
|
? Number(((applied / settlementAmount) * 100).toFixed(2))
|
||||||
|
: Number(ratio.toFixed(2));
|
||||||
|
this.syncHeaderPaymentFromRows();
|
||||||
|
},
|
||||||
|
handleRowAppliedAmountChange(row, value) {
|
||||||
|
const target = this.findReferenceRow(row);
|
||||||
|
if (!target) return;
|
||||||
|
const settlementAmount = Number(target.settlementAmount || 0);
|
||||||
|
const payableAmount = Number(target.payableAmount || 0);
|
||||||
|
let applied = value === null || value === undefined || value === '' ? 0 : Number(value);
|
||||||
|
if (!Number.isFinite(applied) || applied < 0) applied = 0;
|
||||||
|
if (applied > payableAmount) applied = payableAmount;
|
||||||
|
target.appliedAmount = Number(applied.toFixed(2));
|
||||||
|
target.paymentRatio = settlementAmount
|
||||||
|
? Number(((target.appliedAmount / settlementAmount) * 100).toFixed(2))
|
||||||
|
: 0;
|
||||||
|
this.syncHeaderPaymentFromRows();
|
||||||
|
},
|
||||||
|
syncHeaderPaymentFromRows() {
|
||||||
|
if (this.referenceRows.length <= 1) return;
|
||||||
|
const settlementAmount = this.referenceRows.reduce(
|
||||||
|
(total, item) => total + Number(item.settlementAmount || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const payableAmount = this.referenceRows.reduce(
|
||||||
|
(total, item) => total + Number(item.payableAmount || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const appliedAmount = this.referenceRows.reduce(
|
||||||
|
(total, item) => total + Number(item.appliedAmount || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
this.amountSyncing = true;
|
||||||
|
this.form.settlementAmount = Number(settlementAmount.toFixed(2));
|
||||||
|
this.form.payableAmount = Number(payableAmount.toFixed(2));
|
||||||
|
this.form.appliedAmount = Number(appliedAmount.toFixed(2));
|
||||||
|
this.form.paymentRatio = settlementAmount
|
||||||
|
? Number(((appliedAmount / settlementAmount) * 100).toFixed(2))
|
||||||
|
: 0;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.amountSyncing = false;
|
||||||
|
this.$refs.formRef?.validateField?.(['settlementAmount', 'paymentRatio', 'appliedAmount']);
|
||||||
|
});
|
||||||
|
},
|
||||||
payload() {
|
payload() {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
@@ -2947,11 +3129,15 @@ export default {
|
|||||||
remark,
|
remark,
|
||||||
invoices,
|
invoices,
|
||||||
} = this.form;
|
} = this.form;
|
||||||
|
const usePreSettlementOnly =
|
||||||
|
paymentType === 'progress_advance' ||
|
||||||
|
(this.referenceRows.length > 0 &&
|
||||||
|
this.referenceRows.every(item => item.referenceType === 'pre'));
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
paymentType,
|
paymentType,
|
||||||
settlementId,
|
settlementId: usePreSettlementOnly ? null : settlementId,
|
||||||
settlementIds,
|
settlementIds: usePreSettlementOnly ? [] : settlementIds,
|
||||||
preSettlementId,
|
preSettlementId,
|
||||||
preSettlementIds,
|
preSettlementIds,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -2968,6 +3154,12 @@ export default {
|
|||||||
billType,
|
billType,
|
||||||
paymentRatio,
|
paymentRatio,
|
||||||
appliedAmount,
|
appliedAmount,
|
||||||
|
settlementLines: (this.referenceRows || []).map(item => ({
|
||||||
|
formalSettlementId: item.referenceType === 'formal' ? item.id : null,
|
||||||
|
preSettlementId: item.referenceType === 'pre' ? item.id : null,
|
||||||
|
paymentRatio: item.paymentRatio,
|
||||||
|
appliedAmount: item.appliedAmount,
|
||||||
|
})),
|
||||||
paymentMethod,
|
paymentMethod,
|
||||||
billLedgerId,
|
billLedgerId,
|
||||||
billNo,
|
billNo,
|
||||||
@@ -3027,6 +3219,16 @@ export default {
|
|||||||
ElMessage.warning('申请付款金额必填且必须大于0');
|
ElMessage.warning('申请付款金额必填且必须大于0');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (this.multipleSettlementRows) {
|
||||||
|
const invalidRow = this.referenceRows.find(
|
||||||
|
item => Number(item.appliedAmount || 0) > Number(item.payableAmount || 0)
|
||||||
|
);
|
||||||
|
if (invalidRow) {
|
||||||
|
this.clearMessages();
|
||||||
|
ElMessage.warning(`${invalidRow.settlementNo || '结算单'}付款金额不能超过可付款金额`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (this.form.paymentType === 'project_advance' && !this.form.projectId) {
|
if (this.form.paymentType === 'project_advance' && !this.form.projectId) {
|
||||||
this.clearMessages();
|
this.clearMessages();
|
||||||
ElMessage.warning('请选择所属项目');
|
ElMessage.warning('请选择所属项目');
|
||||||
|
|||||||
@@ -860,11 +860,14 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="运输单价" min-width="140" align="center">
|
<el-table-column label="运输单价" min-width="140" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
<span v-if="isTierUnitPrice(row)">-</span>
|
||||||
<el-input-number
|
<el-input-number
|
||||||
|
v-else
|
||||||
v-model="row.unitPrice"
|
v-model="row.unitPrice"
|
||||||
:min="0"
|
:min="0"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
|
:disabled="!isUnitPriceAdjustable(row)"
|
||||||
@change="recalculateAdjustRow(row, 'unitPrice')"
|
@change="recalculateAdjustRow(row, 'unitPrice')"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -1037,6 +1040,7 @@ import { getDictionary } from '@/api/system/dictbiz';
|
|||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { formatContractOptionLabel } from '@/utils/contract';
|
import { formatContractOptionLabel } from '@/utils/contract';
|
||||||
|
import { isTierUnitPrice, isUnitPriceAdjustable } from '@/utils/settlement-adjust-rule';
|
||||||
import { normalizeCurrencyCode } from '@/utils/currency';
|
import { normalizeCurrencyCode } from '@/utils/currency';
|
||||||
import { downloadFileByUrl } from '@/utils/util';
|
import { downloadFileByUrl } from '@/utils/util';
|
||||||
import {
|
import {
|
||||||
@@ -1318,6 +1322,8 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
isTierUnitPrice,
|
||||||
|
isUnitPriceAdjustable,
|
||||||
formatContractOptionLabel,
|
formatContractOptionLabel,
|
||||||
hasPermission(code) {
|
hasPermission(code) {
|
||||||
if (this.isAdmin) return true;
|
if (this.isAdmin) return true;
|
||||||
@@ -2128,15 +2134,21 @@ export default {
|
|||||||
const settlementType = row.settlementType || this.form.settlementType || 'payable';
|
const settlementType = row.settlementType || this.form.settlementType || 'payable';
|
||||||
const detailId = await resolveReceivablePayableDetailId(row, settlementType);
|
const detailId = await resolveReceivablePayableDetailId(row, settlementType);
|
||||||
if (!detailId) return this.$message.info('当前记录未找到对应的应收/应付明细');
|
if (!detailId) return this.$message.info('当前记录未找到对应的应收/应付明细');
|
||||||
this.$router.push({ path: settlementDetailPath(settlementType), query: { detailId } });
|
await this.$router.push({ path: settlementDetailPath(settlementType), query: { detailId } });
|
||||||
|
this.closeDetailDialog();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (column.prop === 'waybillNo') {
|
if (column.prop === 'waybillNo') {
|
||||||
const waybillId = await resolveWaybillId(row);
|
const waybillId = await resolveWaybillId(row);
|
||||||
if (!waybillId) return this.$message.info('当前记录未找到对应的运单详情');
|
if (!waybillId) return this.$message.info('当前记录未找到对应的运单详情');
|
||||||
this.$router.push({ path: '/business/waybill-manage/detail', query: { id: waybillId } });
|
await this.$router.push({ path: '/business/waybill-manage/detail', query: { id: waybillId } });
|
||||||
|
this.closeDetailDialog();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
closeDetailDialog() {
|
||||||
|
if (this.pageMode) return;
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
resetDetailQuery(apply = true) {
|
resetDetailQuery(apply = true) {
|
||||||
this.detailQuery = { documentNo: '', waybillNo: '', batchNo: '', cargoName: '' };
|
this.detailQuery = { documentNo: '', waybillNo: '', batchNo: '', cargoName: '' };
|
||||||
if (apply) this.handleDetailQuery();
|
if (apply) this.handleDetailQuery();
|
||||||
@@ -2260,11 +2272,16 @@ export default {
|
|||||||
row.settlementAmountTax = Number(
|
row.settlementAmountTax = Number(
|
||||||
(hasFreightItem ? feeItemTotal : Number(row.freightAmount || 0) + feeItemTotal).toFixed(2)
|
(hasFreightItem ? feeItemTotal : Number(row.freightAmount || 0) + feeItemTotal).toFixed(2)
|
||||||
);
|
);
|
||||||
if (changedField && this.adjust.sourceDetailId && (row.sourceFeeId || row.id)) {
|
if (
|
||||||
this.scheduleSourceAdjustCalculation(row);
|
['transportQuantity', 'mileage', 'unitPrice'].includes(changedField) &&
|
||||||
|
this.adjust.sourceDetailId &&
|
||||||
|
(row.sourceFeeId || row.id) &&
|
||||||
|
(changedField !== 'unitPrice' || isUnitPriceAdjustable(row))
|
||||||
|
) {
|
||||||
|
this.scheduleSourceAdjustCalculation(row, changedField);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scheduleSourceAdjustCalculation(row) {
|
scheduleSourceAdjustCalculation(row, changedField) {
|
||||||
if (row.adjustCalculateTimer) clearTimeout(row.adjustCalculateTimer);
|
if (row.adjustCalculateTimer) clearTimeout(row.adjustCalculateTimer);
|
||||||
row.adjustCalculateVersion = Number(row.adjustCalculateVersion || 0) + 1;
|
row.adjustCalculateVersion = Number(row.adjustCalculateVersion || 0) + 1;
|
||||||
row.calculating = true;
|
row.calculating = true;
|
||||||
@@ -2277,6 +2294,8 @@ export default {
|
|||||||
feeId: row.sourceFeeId || row.id,
|
feeId: row.sourceFeeId || row.id,
|
||||||
transportQuantity: Number(row.transportQuantity || 0),
|
transportQuantity: Number(row.transportQuantity || 0),
|
||||||
mileage: Number(row.mileage || 0),
|
mileage: Number(row.mileage || 0),
|
||||||
|
unitPrice: row.unitPrice === null || row.unitPrice === '' ? null : Number(row.unitPrice),
|
||||||
|
changedField,
|
||||||
freightAmount: Number(row.freightAmount || 0),
|
freightAmount: Number(row.freightAmount || 0),
|
||||||
feeItems: Object.fromEntries(
|
feeItems: Object.fromEntries(
|
||||||
Object.entries(row.feeItems || {}).map(([name, amount]) => [name, Number(amount || 0)])
|
Object.entries(row.feeItems || {}).map(([name, amount]) => [name, Number(amount || 0)])
|
||||||
@@ -2288,6 +2307,9 @@ export default {
|
|||||||
row.feeItems = Object.fromEntries(
|
row.feeItems = Object.fromEntries(
|
||||||
Object.entries(data.feeItems || {}).map(([name, amount]) => [name, Number(amount || 0)])
|
Object.entries(data.feeItems || {}).map(([name, amount]) => [name, Number(amount || 0)])
|
||||||
);
|
);
|
||||||
|
if (!isTierUnitPrice(row) && data.unitPrice !== undefined && data.unitPrice !== null) {
|
||||||
|
row.unitPrice = Number(data.unitPrice);
|
||||||
|
}
|
||||||
row.adjustAmount = Number(data.adjustAmount || 0);
|
row.adjustAmount = Number(data.adjustAmount || 0);
|
||||||
row.settlementAmountTax = Number(data.afterAmount || 0);
|
row.settlementAmountTax = Number(data.afterAmount || 0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -256,13 +256,23 @@
|
|||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="item.kind === 'fee'">
|
<span v-if="item.kind === 'fee'" class="pre-settlement-editor__billing-rule-cell">
|
||||||
{{
|
<span>
|
||||||
formatMoney(
|
{{
|
||||||
parseFeeItems(row.feeItemsJson)[item.name],
|
formatMoney(
|
||||||
row.currency || form.currency
|
parseFeeItems(row.feeItemsJson)[item.name],
|
||||||
)
|
row.currency || form.currency
|
||||||
}}
|
)
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<el-icon
|
||||||
|
v-if="hasDetailBillingRule(row)"
|
||||||
|
class="pre-settlement-editor__billing-rule-icon"
|
||||||
|
title="查看计费规则详情"
|
||||||
|
@click.stop="openDetailBillingRule(row, item.name)"
|
||||||
|
>
|
||||||
|
<InfoFilled />
|
||||||
|
</el-icon>
|
||||||
</span>
|
</span>
|
||||||
<el-link
|
<el-link
|
||||||
v-else-if="item.column.link && row[item.column.prop] && !publicMode"
|
v-else-if="item.column.link && row[item.column.prop] && !publicMode"
|
||||||
@@ -274,6 +284,20 @@
|
|||||||
<span v-else-if="item.column.money && item.column.prop === 'unitPrice'">
|
<span v-else-if="item.column.money && item.column.prop === 'unitPrice'">
|
||||||
{{ formatUnitPrice(row) }}
|
{{ formatUnitPrice(row) }}
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
v-else-if="item.column.prop === 'freightAmount'"
|
||||||
|
class="pre-settlement-editor__billing-rule-cell"
|
||||||
|
>
|
||||||
|
<span>{{ formatMoney(row[item.column.prop], row.currency || form.currency) }}</span>
|
||||||
|
<el-icon
|
||||||
|
v-if="hasDetailBillingRule(row)"
|
||||||
|
class="pre-settlement-editor__billing-rule-icon"
|
||||||
|
title="查看计费规则详情"
|
||||||
|
@click.stop="openDetailBillingRule(row, '运输费')"
|
||||||
|
>
|
||||||
|
<InfoFilled />
|
||||||
|
</el-icon>
|
||||||
|
</span>
|
||||||
<span v-else-if="item.column.money">
|
<span v-else-if="item.column.money">
|
||||||
{{ formatMoney(row[item.column.prop], row.currency || form.currency) }}
|
{{ formatMoney(row[item.column.prop], row.currency || form.currency) }}
|
||||||
</span>
|
</span>
|
||||||
@@ -781,12 +805,14 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="运输单价" min-width="140" align="center">
|
<el-table-column label="运输单价" min-width="140" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
<span v-if="isTierUnitPrice(row)">-</span>
|
||||||
<el-input-number
|
<el-input-number
|
||||||
|
v-else
|
||||||
v-model="row.unitPrice"
|
v-model="row.unitPrice"
|
||||||
:min="0"
|
:min="0"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
:disabled="adjustDialog.readonly"
|
:disabled="adjustDialog.readonly || !isUnitPriceAdjustable(row)"
|
||||||
@change="recalculateAdjustRow(row, 'unitPrice')"
|
@change="recalculateAdjustRow(row, 'unitPrice')"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -799,10 +825,7 @@
|
|||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div
|
<div class="pre-settlement-editor__billing-rule-cell">
|
||||||
v-if="isFreightFeeItem(feeItem)"
|
|
||||||
class="pre-settlement-editor__billing-rule-cell"
|
|
||||||
>
|
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="row.feeItems[feeItem]"
|
v-model="row.feeItems[feeItem]"
|
||||||
:min="0"
|
:min="0"
|
||||||
@@ -815,20 +838,11 @@
|
|||||||
v-if="hasBillingRule(row)"
|
v-if="hasBillingRule(row)"
|
||||||
class="pre-settlement-editor__billing-rule-icon"
|
class="pre-settlement-editor__billing-rule-icon"
|
||||||
title="查看计费规则详情"
|
title="查看计费规则详情"
|
||||||
@click="openBillingRuleInfo(row)"
|
@click="openBillingRuleInfo(row, feeItem)"
|
||||||
>
|
>
|
||||||
<InfoFilled />
|
<InfoFilled />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
<el-input-number
|
|
||||||
v-else
|
|
||||||
v-model="row.feeItems[feeItem]"
|
|
||||||
:min="0"
|
|
||||||
:precision="2"
|
|
||||||
:controls="false"
|
|
||||||
:disabled="adjustDialog.readonly"
|
|
||||||
@change="recalculateAdjustRow(row, feeItem)"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="结算金额(含税)" min-width="170" align="center">
|
<el-table-column label="结算金额(含税)" min-width="170" align="center">
|
||||||
@@ -986,6 +1000,7 @@ import {
|
|||||||
settlementSummaryColumns,
|
settlementSummaryColumns,
|
||||||
} from '@/option/settlement/preSettlementTable';
|
} from '@/option/settlement/preSettlementTable';
|
||||||
import { formatContractOptionLabel } from '@/utils/contract';
|
import { formatContractOptionLabel } from '@/utils/contract';
|
||||||
|
import { isTierUnitPrice, isUnitPriceAdjustable } from '@/utils/settlement-adjust-rule';
|
||||||
import { normalizeCurrencyCode } from '@/utils/currency';
|
import { normalizeCurrencyCode } from '@/utils/currency';
|
||||||
import { downloadFileByUrl } from '@/utils/util';
|
import { downloadFileByUrl } from '@/utils/util';
|
||||||
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
|
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||||
@@ -1234,8 +1249,14 @@ export default {
|
|||||||
});
|
});
|
||||||
return Array.from(names);
|
return Array.from(names);
|
||||||
},
|
},
|
||||||
|
// 费用项里已有运输费/运费时,不再单独展示运费列,避免和应收应付费用项重复。
|
||||||
|
showStandaloneFreightColumn() {
|
||||||
|
return !this.detailFeeItemNames.some(name => this.isFreightFeeItem(name));
|
||||||
|
},
|
||||||
detailDisplayColumns() {
|
detailDisplayColumns() {
|
||||||
const columns = this.visibleDetailColumns;
|
const columns = this.visibleDetailColumns.filter(
|
||||||
|
column => this.showStandaloneFreightColumn || column.prop !== 'freightAmount'
|
||||||
|
);
|
||||||
const unitPriceIndex = columns.findIndex(column => column.prop === 'unitPrice');
|
const unitPriceIndex = columns.findIndex(column => column.prop === 'unitPrice');
|
||||||
const beforeColumns = unitPriceIndex < 0 ? columns : columns.slice(0, unitPriceIndex + 1);
|
const beforeColumns = unitPriceIndex < 0 ? columns : columns.slice(0, unitPriceIndex + 1);
|
||||||
const afterColumns = unitPriceIndex < 0 ? [] : columns.slice(unitPriceIndex + 1);
|
const afterColumns = unitPriceIndex < 0 ? [] : columns.slice(unitPriceIndex + 1);
|
||||||
@@ -1306,6 +1327,8 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
isTierUnitPrice,
|
||||||
|
isUnitPriceAdjustable,
|
||||||
formatContractOptionLabel,
|
formatContractOptionLabel,
|
||||||
hasPermission(code) {
|
hasPermission(code) {
|
||||||
return this.isAdmin || this.validData(this.permission?.[code], false);
|
return this.isAdmin || this.validData(this.permission?.[code], false);
|
||||||
@@ -2321,11 +2344,38 @@ export default {
|
|||||||
rule.matchCondition?.cargoType
|
rule.matchCondition?.cargoType
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
openBillingRuleInfo(row) {
|
detailFeeRows(row = {}) {
|
||||||
this.billingRuleDialog.rule = this.normalizeBillingRule(row);
|
return this.detailFeeCache[row.id] || this.detailFeeCache[row.preSettlementDetailId] || [];
|
||||||
|
},
|
||||||
|
hasDetailBillingRule(row) {
|
||||||
|
if (this.hasBillingRule(row)) return true;
|
||||||
|
return this.detailFeeRows(row).some(fee => this.hasBillingRule(fee));
|
||||||
|
},
|
||||||
|
openDetailBillingRule(row, feeName = '') {
|
||||||
|
const fees = this.detailFeeRows(row);
|
||||||
|
const matched = fees.find(fee =>
|
||||||
|
this.collectBillingRules(fee).some(rule =>
|
||||||
|
this.feeNamesMatch(this.ruleFeeName(rule), feeName)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const source = matched || fees.find(fee => this.hasBillingRule(fee)) || row;
|
||||||
|
this.openBillingRuleInfo(source, feeName);
|
||||||
|
},
|
||||||
|
openBillingRuleInfo(row, feeName = '') {
|
||||||
|
this.billingRuleDialog.rule = this.normalizeBillingRule(row, feeName);
|
||||||
this.billingRuleDialog.visible = true;
|
this.billingRuleDialog.visible = true;
|
||||||
},
|
},
|
||||||
normalizeBillingRule(row) {
|
ruleFeeName(rule = {}) {
|
||||||
|
return String(rule.feeItem || rule.feeItemName || rule.name || '').trim();
|
||||||
|
},
|
||||||
|
feeNamesMatch(left, right) {
|
||||||
|
const source = String(left || '').trim();
|
||||||
|
const target = String(right || '').trim();
|
||||||
|
if (!source || !target) return false;
|
||||||
|
if (source === target) return true;
|
||||||
|
return this.isFreightFeeItem(source) && this.isFreightFeeItem(target);
|
||||||
|
},
|
||||||
|
collectBillingRules(row = {}) {
|
||||||
const source = row || {};
|
const source = row || {};
|
||||||
const nested = [
|
const nested = [
|
||||||
source.billingRule,
|
source.billingRule,
|
||||||
@@ -2338,28 +2388,63 @@ export default {
|
|||||||
source.rule,
|
source.rule,
|
||||||
].find(value => value !== undefined && value !== null && value !== '');
|
].find(value => value !== undefined && value !== null && value !== '');
|
||||||
const parsedNested = this.parseJsonValue(nested);
|
const parsedNested = this.parseJsonValue(nested);
|
||||||
const matchedRules = Array.isArray(parsedNested)
|
return Array.isArray(parsedNested)
|
||||||
? parsedNested.filter(item => item && typeof item === 'object')
|
? parsedNested.filter(item => item && typeof item === 'object')
|
||||||
: [];
|
: [];
|
||||||
let rule = this.parseObject(matchedRules[0] || parsedNested || nested);
|
},
|
||||||
if (!Object.keys(rule).length && typeof nested === 'string') rule = { name: nested };
|
normalizeBillingRule(row, feeName = '') {
|
||||||
|
const source = row || {};
|
||||||
|
const nested = [
|
||||||
|
source.billingRule,
|
||||||
|
source.billingRuleInfo,
|
||||||
|
source.billingRuleJson,
|
||||||
|
source.billingRulesJson,
|
||||||
|
source.billingRules,
|
||||||
|
source.billingPlanRule,
|
||||||
|
source.feeRule,
|
||||||
|
source.rule,
|
||||||
|
].find(value => value !== undefined && value !== null && value !== '');
|
||||||
|
const parsedNested = this.parseJsonValue(nested);
|
||||||
|
const parsedRules = this.collectBillingRules(source);
|
||||||
|
const requestedFeeName = String(feeName || '').trim();
|
||||||
|
const matchedRules = requestedFeeName
|
||||||
|
? parsedRules.filter(item => this.feeNamesMatch(this.ruleFeeName(item), requestedFeeName))
|
||||||
|
: parsedRules;
|
||||||
|
const sharedRule =
|
||||||
|
parsedRules.length === 1 && !this.ruleFeeName(parsedRules[0]) ? parsedRules[0] : null;
|
||||||
|
const ruleSource =
|
||||||
|
matchedRules[0] || sharedRule || (!requestedFeeName ? parsedNested || nested : null);
|
||||||
|
let rule = this.parseObject(ruleSource);
|
||||||
|
if (!Object.keys(rule).length && typeof nested === 'string' && !requestedFeeName) {
|
||||||
|
rule = { name: nested };
|
||||||
|
}
|
||||||
const matchCondition = this.parseObject(
|
const matchCondition = this.parseObject(
|
||||||
source.matchCondition ?? source.billingMatchCondition ?? rule.matchCondition
|
requestedFeeName
|
||||||
|
? (rule.matchCondition ?? source.matchCondition ?? source.billingMatchCondition)
|
||||||
|
: (source.matchCondition ?? source.billingMatchCondition ?? rule.matchCondition)
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
...rule,
|
...rule,
|
||||||
feeItem: source.billingFeeItem || source.feeItem || rule.feeItem,
|
feeItem: requestedFeeName || source.billingFeeItem || source.feeItem || rule.feeItem,
|
||||||
billingElement:
|
billingElement: requestedFeeName
|
||||||
source.billingElement ||
|
? rule.billingElement || rule.billingFactor || ''
|
||||||
source.billingFactor ||
|
: source.billingElement ||
|
||||||
rule.billingElement ||
|
source.billingFactor ||
|
||||||
rule.billingFactor,
|
rule.billingElement ||
|
||||||
billingType: source.billingType || rule.billingType,
|
rule.billingFactor,
|
||||||
billingUnit: source.billingUnit || source.priceUnit || rule.billingUnit || rule.priceUnit,
|
billingType: requestedFeeName ? rule.billingType || '' : source.billingType || rule.billingType,
|
||||||
unitPrice: source.billingUnitPrice ?? source.unitPrice ?? rule.unitPrice,
|
billingUnit: requestedFeeName
|
||||||
minimumBillingWeight:
|
? rule.billingUnit || rule.priceUnit || ''
|
||||||
source.minimumBillingWeight ?? rule.minimumBillingWeight ?? rule.lowerLimit,
|
: source.billingUnit || source.priceUnit || rule.billingUnit || rule.priceUnit,
|
||||||
remark: source.billingRuleRemark || source.ruleRemark || rule.remark,
|
unitPrice: requestedFeeName
|
||||||
|
? (rule.unitPrice ?? '')
|
||||||
|
: (source.billingUnitPrice ?? source.unitPrice ?? rule.unitPrice),
|
||||||
|
minimumBillingWeight: requestedFeeName
|
||||||
|
? (rule.minimumBillingWeight ?? rule.lowerLimit ?? '')
|
||||||
|
: (source.minimumBillingWeight ?? rule.minimumBillingWeight ?? rule.lowerLimit),
|
||||||
|
remark: requestedFeeName
|
||||||
|
? rule.remark || ''
|
||||||
|
: source.billingRuleRemark || source.ruleRemark || rule.remark,
|
||||||
rules: matchedRules,
|
rules: matchedRules,
|
||||||
matchCondition: {
|
matchCondition: {
|
||||||
...matchCondition,
|
...matchCondition,
|
||||||
@@ -2404,11 +2489,16 @@ export default {
|
|||||||
row.settlementAmountTax = Number(
|
row.settlementAmountTax = Number(
|
||||||
(hasFreightItem ? feeItemTotal : Number(row.freightAmount || 0) + feeItemTotal).toFixed(2)
|
(hasFreightItem ? feeItemTotal : Number(row.freightAmount || 0) + feeItemTotal).toFixed(2)
|
||||||
);
|
);
|
||||||
if (changedField && this.adjustDialog.sourceDetailId && row.id) {
|
if (
|
||||||
this.scheduleSourceAdjustCalculation(row);
|
['transportQuantity', 'mileage', 'unitPrice'].includes(changedField) &&
|
||||||
|
this.adjustDialog.sourceDetailId &&
|
||||||
|
row.id &&
|
||||||
|
(changedField !== 'unitPrice' || isUnitPriceAdjustable(row))
|
||||||
|
) {
|
||||||
|
this.scheduleSourceAdjustCalculation(row, changedField);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scheduleSourceAdjustCalculation(row) {
|
scheduleSourceAdjustCalculation(row, changedField) {
|
||||||
if (row.adjustCalculateTimer) clearTimeout(row.adjustCalculateTimer);
|
if (row.adjustCalculateTimer) clearTimeout(row.adjustCalculateTimer);
|
||||||
row.adjustCalculateVersion += 1;
|
row.adjustCalculateVersion += 1;
|
||||||
row.calculating = true;
|
row.calculating = true;
|
||||||
@@ -2421,6 +2511,8 @@ export default {
|
|||||||
feeId: row.id,
|
feeId: row.id,
|
||||||
transportQuantity: Number(row.transportQuantity || 0),
|
transportQuantity: Number(row.transportQuantity || 0),
|
||||||
mileage: Number(row.mileage || 0),
|
mileage: Number(row.mileage || 0),
|
||||||
|
unitPrice: row.unitPrice === null || row.unitPrice === '' ? null : Number(row.unitPrice),
|
||||||
|
changedField,
|
||||||
freightAmount: Number(row.freightAmount || 0),
|
freightAmount: Number(row.freightAmount || 0),
|
||||||
feeItems: Object.fromEntries(
|
feeItems: Object.fromEntries(
|
||||||
Object.entries(row.feeItems || {}).map(([name, amount]) => [
|
Object.entries(row.feeItems || {}).map(([name, amount]) => [
|
||||||
@@ -2435,6 +2527,9 @@ export default {
|
|||||||
row.feeItems = Object.fromEntries(
|
row.feeItems = Object.fromEntries(
|
||||||
Object.entries(data.feeItems || {}).map(([name, amount]) => [name, Number(amount || 0)])
|
Object.entries(data.feeItems || {}).map(([name, amount]) => [name, Number(amount || 0)])
|
||||||
);
|
);
|
||||||
|
if (!isTierUnitPrice(row) && data.unitPrice !== undefined && data.unitPrice !== null) {
|
||||||
|
row.unitPrice = Number(data.unitPrice);
|
||||||
|
}
|
||||||
row.adjustAmount = Number(data.adjustAmount || 0);
|
row.adjustAmount = Number(data.adjustAmount || 0);
|
||||||
row.settlementAmountTax = Number(data.afterAmount || 0);
|
row.settlementAmountTax = Number(data.afterAmount || 0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -2630,9 +2725,12 @@ export default {
|
|||||||
运输总量: this.formatQuantity(row),
|
运输总量: this.formatQuantity(row),
|
||||||
'里程(KM)': row.mileage,
|
'里程(KM)': row.mileage,
|
||||||
批次号: row.batchNo,
|
批次号: row.batchNo,
|
||||||
|
计费方式: row.billingType,
|
||||||
运输单价: this.formatMoney(row.unitPrice, row.currency || this.form.currency),
|
运输单价: this.formatMoney(row.unitPrice, row.currency || this.form.currency),
|
||||||
运费: this.formatMoney(row.freightAmount, row.currency || this.form.currency),
|
|
||||||
};
|
};
|
||||||
|
if (this.showStandaloneFreightColumn) {
|
||||||
|
result['运费'] = this.formatMoney(row.freightAmount, row.currency || this.form.currency);
|
||||||
|
}
|
||||||
dynamicFeeItems.forEach(name => {
|
dynamicFeeItems.forEach(name => {
|
||||||
result[name] = this.formatMoney(feeItems[name], row.currency || this.form.currency);
|
result[name] = this.formatMoney(feeItems[name], row.currency || this.form.currency);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -208,12 +208,13 @@
|
|||||||
:prop="column.prop"
|
:prop="column.prop"
|
||||||
:label="column.label"
|
:label="column.label"
|
||||||
:min-width="column.minWidth || 120"
|
:min-width="column.minWidth || 120"
|
||||||
show-overflow-tooltip
|
:show-overflow-tooltip="readonly || !column.editor || column.readonly"
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<template v-if="row._editing && column.editor && !column.readonly">
|
<template v-if="!readonly && column.editor && !column.readonly">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-if="column.editor === 'date'"
|
v-if="column.editor === 'date'"
|
||||||
|
:key="`${row._key}-${column.prop}`"
|
||||||
v-model="row[column.prop]"
|
v-model="row[column.prop]"
|
||||||
type="date"
|
type="date"
|
||||||
format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
||||||
@@ -222,6 +223,7 @@
|
|||||||
/>
|
/>
|
||||||
<el-select
|
<el-select
|
||||||
v-else-if="column.editor === 'select' && column.prop === 'transportType'"
|
v-else-if="column.editor === 'select' && column.prop === 'transportType'"
|
||||||
|
:key="`${row._key}-${column.prop}`"
|
||||||
v-model="row[column.prop]"
|
v-model="row[column.prop]"
|
||||||
clearable
|
clearable
|
||||||
filterable
|
filterable
|
||||||
@@ -244,8 +246,21 @@
|
|||||||
选择
|
选择
|
||||||
</el-link>
|
</el-link>
|
||||||
</div>
|
</div>
|
||||||
|
<el-cascader
|
||||||
|
v-else-if="column.editor === 'cargoType'"
|
||||||
|
:key="`${row._key}-${column.prop}`"
|
||||||
|
:model-value="resolveCargoTypePath(row)"
|
||||||
|
:options="cargoTypeSelectOptions(row)"
|
||||||
|
:props="cargoTypeCascaderProps"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择货物类型"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="value => handleCargoTypeChange(row, value)"
|
||||||
|
/>
|
||||||
<el-input
|
<el-input
|
||||||
v-else-if="column.editor === 'number' || column.editor === 'optionalNumber'"
|
v-else-if="column.editor === 'number' || column.editor === 'optionalNumber'"
|
||||||
|
:key="`${row._key}-${column.prop}`"
|
||||||
:model-value="resolveCellValue(row, column)"
|
:model-value="resolveCellValue(row, column)"
|
||||||
inputmode="decimal"
|
inputmode="decimal"
|
||||||
:placeholder="`请输入${column.label}`"
|
:placeholder="`请输入${column.label}`"
|
||||||
@@ -253,8 +268,10 @@
|
|||||||
/>
|
/>
|
||||||
<el-input
|
<el-input
|
||||||
v-else
|
v-else
|
||||||
|
:key="`${row._key}-${column.prop}`"
|
||||||
v-model="row[column.prop]"
|
v-model="row[column.prop]"
|
||||||
:placeholder="`请输入${column.label}`"
|
:placeholder="`请输入${column.label}`"
|
||||||
|
@update:model-value="value => syncEditedText(row, column, value)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.editor === 'waybillSelect' && !readonly">
|
<template v-else-if="column.editor === 'waybillSelect' && !readonly">
|
||||||
@@ -268,20 +285,13 @@
|
|||||||
<span v-else>{{ formatCell(row, column) }}</span>
|
<span v-else>{{ formatCell(row, column) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column v-if="!readonly" label="操作" width="180" fixed="right">
|
<el-table-column v-if="!readonly" label="操作" width="120" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="settlement-import-create__row-actions">
|
<div class="settlement-import-create__row-actions">
|
||||||
<template v-if="row._editing">
|
<el-link v-if="row._duplicate" type="primary" @click="confirmDuplicateRow(row)">
|
||||||
<el-link type="primary" @click="saveRow(row)">保存</el-link>
|
确认
|
||||||
<el-link type="primary" @click="cancelRow(row)">取消</el-link>
|
</el-link>
|
||||||
</template>
|
<el-link type="danger" @click="deleteRow(row)">删除</el-link>
|
||||||
<template v-else>
|
|
||||||
<el-link v-if="row._duplicate" type="primary" @click="confirmDuplicateRow(row)">
|
|
||||||
确认
|
|
||||||
</el-link>
|
|
||||||
<el-link type="primary" @click="editRow(row)">编辑</el-link>
|
|
||||||
<el-link type="danger" @click="deleteRow(row)">删除</el-link>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -345,6 +355,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
|
|||||||
import { QuestionFilled } from '@element-plus/icons-vue';
|
import { QuestionFilled } from '@element-plus/icons-vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import * as api from '@/api/settlement/settlementImport';
|
import * as api from '@/api/settlement/settlementImport';
|
||||||
|
import { getList as getCargoTypeList } from '@/api/base/cargo-type';
|
||||||
import { getList as getContractList } from '@/api/business/contract-manage';
|
import { getList as getContractList } from '@/api/business/contract-manage';
|
||||||
import { getDictionary } from '@/api/system/dictbiz';
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import { formatContractOptionLabel } from '@/utils/contract';
|
import { formatContractOptionLabel } from '@/utils/contract';
|
||||||
@@ -363,6 +374,7 @@ const parseLoading = ref(false);
|
|||||||
const rowSelection = ref([]);
|
const rowSelection = ref([]);
|
||||||
const previewTab = ref('all');
|
const previewTab = ref('all');
|
||||||
const feeItemNames = ref([]);
|
const feeItemNames = ref([]);
|
||||||
|
const parsedHeaders = ref([]);
|
||||||
const projects = ref([]);
|
const projects = ref([]);
|
||||||
const customerContracts = ref([]);
|
const customerContracts = ref([]);
|
||||||
const carrierContracts = ref([]);
|
const carrierContracts = ref([]);
|
||||||
@@ -370,6 +382,14 @@ const draftLoading = ref(false);
|
|||||||
const confirmLoading = ref(false);
|
const confirmLoading = ref(false);
|
||||||
const readonly = ref(false);
|
const readonly = ref(false);
|
||||||
const transportTypeOptions = ref([]);
|
const transportTypeOptions = ref([]);
|
||||||
|
const cargoTypeOptions = ref([]);
|
||||||
|
const cargoTypeFlatOptions = ref([]);
|
||||||
|
const cargoTypeCascaderProps = {
|
||||||
|
label: 'cargoName',
|
||||||
|
value: 'id',
|
||||||
|
children: 'children',
|
||||||
|
emitPath: true,
|
||||||
|
};
|
||||||
const historyWaybillVisible = ref(false);
|
const historyWaybillVisible = ref(false);
|
||||||
const historyWaybillLoading = ref(false);
|
const historyWaybillLoading = ref(false);
|
||||||
const historyWaybillRows = ref([]);
|
const historyWaybillRows = ref([]);
|
||||||
@@ -478,18 +498,18 @@ const loadContractsByProject = async (projectId, projectName = '') => {
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fixedDetailColumns = [
|
const columnsBeforeFee = [
|
||||||
{ prop: 'loadingIdentifier', label: '配载标识号', editor: 'input', minWidth: 140 },
|
|
||||||
{ prop: 'originalNo', label: '原始单号', editor: 'input', minWidth: 130 },
|
{ prop: 'originalNo', label: '原始单号', editor: 'input', minWidth: 130 },
|
||||||
|
{ prop: 'loadingIdentifier', label: '配载标识号', editor: 'input', minWidth: 140 },
|
||||||
{
|
{
|
||||||
prop: 'vehicleNo',
|
prop: 'vehicleNo',
|
||||||
label: '车牌号/航班号/船号/班列号',
|
label: '车牌号/航班号/船号/班列号',
|
||||||
editor: 'input',
|
editor: 'input',
|
||||||
minWidth: 190,
|
minWidth: 190,
|
||||||
},
|
},
|
||||||
{ prop: 'transportType', label: '运输方式', editor: 'select', minWidth: 120 },
|
{ prop: 'transportType', label: '运输方式', editor: 'select', minWidth: 140 },
|
||||||
{ prop: 'driverName', label: '司机/船长姓名', editor: 'input', minWidth: 120 },
|
{ prop: 'driverName', label: '司机/船长姓名', editor: 'input', minWidth: 130 },
|
||||||
{ prop: 'driverPhone', label: '司机/船长手机号', editor: 'input', minWidth: 140 },
|
{ prop: 'driverPhone', label: '司机/船长手机号', editor: 'input', minWidth: 150 },
|
||||||
{ prop: 'departureAddress', label: '发货地址', editor: 'input', minWidth: 160 },
|
{ prop: 'departureAddress', label: '发货地址', editor: 'input', minWidth: 160 },
|
||||||
{ prop: 'departureContact', label: '发货联系人', editor: 'input', minWidth: 120 },
|
{ prop: 'departureContact', label: '发货联系人', editor: 'input', minWidth: 120 },
|
||||||
{ prop: 'departurePhone', label: '发货联系人电话', editor: 'input', minWidth: 140 },
|
{ prop: 'departurePhone', label: '发货联系人电话', editor: 'input', minWidth: 140 },
|
||||||
@@ -497,30 +517,97 @@ const fixedDetailColumns = [
|
|||||||
{ prop: 'arrivalContact', label: '收货联系人', editor: 'input', minWidth: 120 },
|
{ prop: 'arrivalContact', label: '收货联系人', editor: 'input', minWidth: 120 },
|
||||||
{ prop: 'arrivalPhone', label: '收货联系人电话', editor: 'input', minWidth: 140 },
|
{ prop: 'arrivalPhone', label: '收货联系人电话', editor: 'input', minWidth: 140 },
|
||||||
{ prop: 'cargoName', label: '货物名称', editor: 'input', minWidth: 140 },
|
{ prop: 'cargoName', label: '货物名称', editor: 'input', minWidth: 140 },
|
||||||
{ prop: 'cargoType', label: '货物类型', editor: 'input', minWidth: 120 },
|
{ prop: 'cargoType', label: '货物类型', editor: 'cargoType', minWidth: 200 },
|
||||||
{ prop: 'packageType', label: '包装', editor: 'input', minWidth: 100 },
|
{ prop: 'packageType', label: '包装', editor: 'input', minWidth: 100 },
|
||||||
{ prop: 'quantity', label: '数量', editor: 'number', minWidth: 100 },
|
{ prop: 'quantity', label: '数量', editor: 'number', minWidth: 100 },
|
||||||
{ prop: 'quantityUnit', label: '数量单位', editor: 'input', minWidth: 100 },
|
{ prop: 'quantityUnit', label: '数量单位', editor: 'input', minWidth: 110 },
|
||||||
{ prop: 'specification', label: '规格', editor: 'input', minWidth: 110 },
|
{ prop: 'specification', label: '规格', editor: 'input', minWidth: 110 },
|
||||||
{ prop: 'model', label: '型号', editor: 'input', minWidth: 110 },
|
{ prop: 'model', label: '型号', editor: 'input', minWidth: 110 },
|
||||||
{ prop: 'mileage', label: '里程(km)', editor: 'optionalNumber', minWidth: 110 },
|
{ prop: 'mileage', label: '里程(km)', editor: 'optionalNumber', minWidth: 110 },
|
||||||
{ prop: 'unitPrice', label: '运输单价', editor: 'optionalNumber', minWidth: 100 },
|
{ prop: 'unitPrice', label: '运输单价', editor: 'optionalNumber', minWidth: 110 },
|
||||||
{ prop: 'transportFee', label: '运输费', editor: 'number', minWidth: 100 },
|
{ prop: 'transportFee', label: '运输费', editor: 'number', minWidth: 110 },
|
||||||
{ prop: 'actualStartDate', label: '实际发货时间', editor: 'date', minWidth: 140 },
|
];
|
||||||
{ prop: 'actualEndDate', label: '实际完成时间', editor: 'date', minWidth: 140 },
|
const columnsAfterFee = [
|
||||||
{ prop: 'planStartDate', label: '预计发货时间', editor: 'date', minWidth: 140 },
|
{ prop: 'actualStartDate', label: '实际发货时间', editor: 'date', minWidth: 150 },
|
||||||
{ prop: 'planEndDate', label: '预计完成时间', editor: 'date', minWidth: 140 },
|
{ prop: 'actualEndDate', label: '实际完成时间', editor: 'date', minWidth: 150 },
|
||||||
{ prop: 'waybillIdentifier', label: '同一运单标识号', editor: 'input', minWidth: 140 },
|
{ prop: 'planStartDate', label: '预计发货时间', editor: 'date', minWidth: 150 },
|
||||||
|
{ prop: 'planEndDate', label: '预计完成时间', editor: 'date', minWidth: 150 },
|
||||||
|
{ prop: 'remark', label: '备注', editor: 'input', minWidth: 160 },
|
||||||
|
{ prop: 'waybillIdentifier', label: '同一运单标识号', editor: 'input', minWidth: 150 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const detailColumns = computed(() => {
|
const headerColumnMap = {};
|
||||||
const feeColumns = feeItemNames.value.map(name => ({
|
[...columnsBeforeFee, ...columnsAfterFee].forEach(column => {
|
||||||
|
headerColumnMap[column.label] = column;
|
||||||
|
headerColumnMap[`*${column.label}`] = column;
|
||||||
|
});
|
||||||
|
headerColumnMap['结算金额'] = {
|
||||||
|
prop: 'settlementAmount',
|
||||||
|
label: '结算金额',
|
||||||
|
editor: 'number',
|
||||||
|
minWidth: 120,
|
||||||
|
};
|
||||||
|
headerColumnMap['里程'] = headerColumnMap['里程(km)'];
|
||||||
|
headerColumnMap['单价'] = headerColumnMap['运输单价'];
|
||||||
|
headerColumnMap['运输类型'] = headerColumnMap['运输方式'];
|
||||||
|
headerColumnMap['*运输类型'] = headerColumnMap['运输方式'];
|
||||||
|
|
||||||
|
const VEHICLE_ALIAS_KEYS = [
|
||||||
|
'*车牌号/航班号/船号/班列号',
|
||||||
|
'车牌号/航班号/船号/班列号',
|
||||||
|
'*车牌号/船班号/船次号/班次号',
|
||||||
|
'车牌号/船班号/船次号/班次号',
|
||||||
|
'车号',
|
||||||
|
];
|
||||||
|
|
||||||
|
const readFilledText = value => {
|
||||||
|
const text = String(value ?? '').trim();
|
||||||
|
return text;
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyVehicleNo = (row, value) => {
|
||||||
|
const vehicleNo = readFilledText(value);
|
||||||
|
row.vehicleNo = vehicleNo;
|
||||||
|
VEHICLE_ALIAS_KEYS.forEach(key => {
|
||||||
|
row[key] = vehicleNo;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const syncEditedText = (row, column, value) => {
|
||||||
|
if (column.prop !== 'vehicleNo') return;
|
||||||
|
applyVehicleNo(row, value);
|
||||||
|
};
|
||||||
|
prop: name,
|
||||||
|
label: name,
|
||||||
|
editor: 'number',
|
||||||
|
minWidth: 110,
|
||||||
|
isFeeItem: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const columnFromHeader = header => {
|
||||||
|
const name = String(header || '').trim();
|
||||||
|
if (!name || name === '序号') return null;
|
||||||
|
if (feeItemNames.value.includes(name)) return feeColumn(name);
|
||||||
|
const mapped = headerColumnMap[name];
|
||||||
|
if (mapped) return { ...mapped, label: mapped.label || name.replace(/^\*/, '') };
|
||||||
|
return {
|
||||||
prop: name,
|
prop: name,
|
||||||
label: name,
|
label: name.replace(/^\*/, ''),
|
||||||
editor: 'number',
|
editor: 'input',
|
||||||
minWidth: 110,
|
minWidth: 140,
|
||||||
isFeeItem: true,
|
};
|
||||||
}));
|
};
|
||||||
|
|
||||||
|
const dedupeColumns = columns => {
|
||||||
|
const seen = new Set();
|
||||||
|
return columns.filter(column => {
|
||||||
|
if (!column?.prop || seen.has(column.prop)) return false;
|
||||||
|
seen.add(column.prop);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const detailColumns = computed(() => {
|
||||||
const historyColumns =
|
const historyColumns =
|
||||||
form.waybillMode === 'history'
|
form.waybillMode === 'history'
|
||||||
? [
|
? [
|
||||||
@@ -532,19 +619,20 @@ const detailColumns = computed(() => {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
return [
|
const fallback = [
|
||||||
...historyColumns,
|
...columnsBeforeFee,
|
||||||
...fixedDetailColumns,
|
...feeItemNames.value.map(name => feeColumn(name)),
|
||||||
...feeColumns,
|
|
||||||
{
|
{
|
||||||
prop: 'settlementAmount',
|
prop: 'settlementAmount',
|
||||||
label: '结算金额',
|
label: '结算金额',
|
||||||
editor: 'number',
|
editor: 'number',
|
||||||
readonly: true,
|
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
},
|
},
|
||||||
{ prop: 'remark', label: '备注', editor: 'input', minWidth: 140 },
|
...columnsAfterFee,
|
||||||
];
|
];
|
||||||
|
const uploaded = parsedHeaders.value.map(columnFromHeader).filter(Boolean);
|
||||||
|
const columns = dedupeColumns(uploaded.length ? uploaded : fallback);
|
||||||
|
return [...historyColumns, ...columns];
|
||||||
});
|
});
|
||||||
|
|
||||||
const duplicateRows = computed(() => rows.value.filter(row => row._duplicate));
|
const duplicateRows = computed(() => rows.value.filter(row => row._duplicate));
|
||||||
@@ -664,6 +752,11 @@ const updateNumberCell = (row, column, value) => {
|
|||||||
if (column.prop === 'transportFee') row.freight = '';
|
if (column.prop === 'transportFee') row.freight = '';
|
||||||
if (column.prop === 'unitPrice') row.transportUnitPrice = '';
|
if (column.prop === 'unitPrice') row.transportUnitPrice = '';
|
||||||
}
|
}
|
||||||
|
if (column.prop === 'settlementAmount') {
|
||||||
|
row.settlementAmount = '';
|
||||||
|
row.freightTotal = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
recalcSettlementAmount(row);
|
recalcSettlementAmount(row);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -682,6 +775,11 @@ const updateNumberCell = (row, column, value) => {
|
|||||||
if (column.prop === 'unitPrice') {
|
if (column.prop === 'unitPrice') {
|
||||||
row.transportUnitPrice = normalized;
|
row.transportUnitPrice = normalized;
|
||||||
}
|
}
|
||||||
|
if (column.prop === 'settlementAmount') {
|
||||||
|
row.freightTotal = normalized;
|
||||||
|
row['运费合计'] = normalized;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
recalcSettlementAmount(row);
|
recalcSettlementAmount(row);
|
||||||
};
|
};
|
||||||
@@ -748,11 +846,22 @@ const normalizeParsedRow = (item, index) => {
|
|||||||
model: item.model ?? item['型号'] ?? '',
|
model: item.model ?? item['型号'] ?? '',
|
||||||
specification: item.specification ?? item['规格'] ?? '',
|
specification: item.specification ?? item['规格'] ?? '',
|
||||||
cargoType: item.cargoType ?? item['*货物类型'] ?? item['货物类型'] ?? '',
|
cargoType: item.cargoType ?? item['*货物类型'] ?? item['货物类型'] ?? '',
|
||||||
|
settlementAmount:
|
||||||
|
item.settlementAmount ?? item['结算金额'] ?? '',
|
||||||
feeItems,
|
feeItems,
|
||||||
};
|
};
|
||||||
|
const vehicleNo =
|
||||||
|
item.vehicleNo !== undefined && item.vehicleNo !== null
|
||||||
|
? readFilledText(item.vehicleNo)
|
||||||
|
: readFilledText(VEHICLE_ALIAS_KEYS.map(key => item[key]).find(value => readFilledText(value)));
|
||||||
|
applyVehicleNo(row, vehicleNo);
|
||||||
row.transportUnitPrice = row.unitPrice;
|
row.transportUnitPrice = row.unitPrice;
|
||||||
row.freight = row.transportFee;
|
row.freight = row.transportFee;
|
||||||
recalcSettlementAmount(row);
|
if (row.settlementAmount === '' || row.settlementAmount === null || row.settlementAmount === undefined) {
|
||||||
|
recalcSettlementAmount(row);
|
||||||
|
} else {
|
||||||
|
row.freightTotal = row.settlementAmount;
|
||||||
|
}
|
||||||
return row;
|
return row;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -863,6 +972,7 @@ const resetUploadState = () => {
|
|||||||
form.file = null;
|
form.file = null;
|
||||||
rows.value = [];
|
rows.value = [];
|
||||||
feeItemNames.value = [];
|
feeItemNames.value = [];
|
||||||
|
parsedHeaders.value = [];
|
||||||
rowSelection.value = [];
|
rowSelection.value = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -918,6 +1028,7 @@ const parseSelectedFile = async (raw, displayName = '') => {
|
|||||||
const response = await api.parseFile(raw);
|
const response = await api.parseFile(raw);
|
||||||
const data = unwrapData(response) || {};
|
const data = unwrapData(response) || {};
|
||||||
feeItemNames.value = Array.isArray(data.feeItemNames) ? data.feeItemNames : [];
|
feeItemNames.value = Array.isArray(data.feeItemNames) ? data.feeItemNames : [];
|
||||||
|
parsedHeaders.value = Array.isArray(data.headers) ? data.headers : [];
|
||||||
const parsedRows = Array.isArray(data.rows) ? data.rows : [];
|
const parsedRows = Array.isArray(data.rows) ? data.rows : [];
|
||||||
rows.value = parsedRows.map((item, index) => normalizeParsedRow(item, index));
|
rows.value = parsedRows.map((item, index) => normalizeParsedRow(item, index));
|
||||||
refreshDuplicateFlags();
|
refreshDuplicateFlags();
|
||||||
@@ -938,22 +1049,115 @@ const parseSelectedFile = async (raw, displayName = '') => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const editRow = row => {
|
const formatCargoTypeLabel = item =>
|
||||||
row._editSnapshot = JSON.parse(JSON.stringify(row));
|
item?.cargoName || item?.name || item?.typeName || item?.label || '';
|
||||||
row._editing = true;
|
|
||||||
|
const buildCargoTypeTree = cargoTypes => {
|
||||||
|
const flatCargoTypes = [];
|
||||||
|
const collectCargoTypes = list => {
|
||||||
|
(list || []).forEach(item => {
|
||||||
|
flatCargoTypes.push({ ...item, children: undefined });
|
||||||
|
if (item.children?.length) collectCargoTypes(item.children);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
collectCargoTypes(cargoTypes);
|
||||||
|
const nodeMap = new Map();
|
||||||
|
const codeMap = new Map();
|
||||||
|
flatCargoTypes.forEach(item => {
|
||||||
|
const id = item.id ?? item.cargoCode ?? item.code;
|
||||||
|
if (id === undefined || id === null) return;
|
||||||
|
const node = {
|
||||||
|
...item,
|
||||||
|
id: String(id),
|
||||||
|
cargoName: formatCargoTypeLabel(item),
|
||||||
|
cargoCode: item.cargoCode || item.code || '',
|
||||||
|
parentId: item.parentId === undefined || item.parentId === null ? '' : String(item.parentId),
|
||||||
|
parentCargoCode: item.parentCargoCode || item.parentCode || '',
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
nodeMap.set(node.id, node);
|
||||||
|
if (node.cargoCode) codeMap.set(String(node.cargoCode), node);
|
||||||
|
});
|
||||||
|
const rootNodes = [];
|
||||||
|
nodeMap.forEach(node => {
|
||||||
|
const parent =
|
||||||
|
nodeMap.get(node.parentId) ||
|
||||||
|
codeMap.get(String(node.parentCargoCode || '')) ||
|
||||||
|
codeMap.get(String(node.parentId || ''));
|
||||||
|
if (parent && parent !== node && Number(node.typeLevel) !== 1) parent.children.push(node);
|
||||||
|
else rootNodes.push(node);
|
||||||
|
});
|
||||||
|
const normalize = (cargoType, level = 1, parentPath = []) => {
|
||||||
|
if (level > 2) return null;
|
||||||
|
const id = String(cargoType.id);
|
||||||
|
const path = [...parentPath, id];
|
||||||
|
const children =
|
||||||
|
level === 1
|
||||||
|
? (cargoType.children || []).map(item => normalize(item, level + 1, path)).filter(Boolean)
|
||||||
|
: [];
|
||||||
|
return {
|
||||||
|
...cargoType,
|
||||||
|
id,
|
||||||
|
cargoName: formatCargoTypeLabel(cargoType),
|
||||||
|
path,
|
||||||
|
leaf: level === 2,
|
||||||
|
children: children.length ? children : undefined,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return rootNodes.map(item => normalize(item, 1)).filter(Boolean);
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveRow = row => {
|
const flattenCargoTypeOptions = options => {
|
||||||
recalcSettlementAmount(row);
|
const result = [];
|
||||||
row._editing = false;
|
const collect = list =>
|
||||||
delete row._editSnapshot;
|
(list || []).forEach(item => {
|
||||||
refreshDuplicateFlags();
|
result.push(item);
|
||||||
|
if (item.children?.length) collect(item.children);
|
||||||
|
});
|
||||||
|
collect(options);
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelRow = row => {
|
const resolveCargoTypePath = row => {
|
||||||
if (row._editSnapshot) Object.assign(row, row._editSnapshot);
|
if (Array.isArray(row.cargoTypePath) && row.cargoTypePath.length) return row.cargoTypePath;
|
||||||
row._editing = false;
|
const current = String(row.cargoType || row.cargoTypeCode || '').trim();
|
||||||
delete row._editSnapshot;
|
if (!current) return [];
|
||||||
|
const matched = cargoTypeFlatOptions.value.find(item =>
|
||||||
|
[item.cargoName, item.name, item.typeName, item.cargoCode, item.code].some(
|
||||||
|
value => String(value || '').trim() === current
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (matched?.path?.length) return matched.path;
|
||||||
|
return [`custom:${current}`];
|
||||||
|
};
|
||||||
|
|
||||||
|
const cargoTypeSelectOptions = row => {
|
||||||
|
const path = resolveCargoTypePath(row);
|
||||||
|
const customId = path.find(value => String(value).startsWith('custom:'));
|
||||||
|
if (!customId) return cargoTypeOptions.value;
|
||||||
|
const name = String(customId).slice('custom:'.length);
|
||||||
|
return [{ id: customId, cargoName: name, leaf: true }, ...cargoTypeOptions.value];
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCargoTypeChange = (row, value) => {
|
||||||
|
const path = Array.isArray(value) ? value : [];
|
||||||
|
const leafId = path.length ? String(path[path.length - 1]) : '';
|
||||||
|
if (!leafId) {
|
||||||
|
row.cargoTypePath = [];
|
||||||
|
row.cargoType = '';
|
||||||
|
row.cargoTypeCode = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (leafId.startsWith('custom:')) {
|
||||||
|
row.cargoTypePath = path;
|
||||||
|
row.cargoType = leafId.slice('custom:'.length);
|
||||||
|
row.cargoTypeCode = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cargoType = cargoTypeFlatOptions.value.find(item => String(item.id) === leafId);
|
||||||
|
row.cargoTypePath = cargoType?.path || path;
|
||||||
|
row.cargoType = cargoType?.cargoName || '';
|
||||||
|
row.cargoTypeCode = cargoType?.cargoCode || cargoType?.code || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteRow = row => {
|
const deleteRow = row => {
|
||||||
@@ -1159,11 +1363,24 @@ const loadTransportTypeOptions = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadCargoTypeOptions = async () => {
|
||||||
|
try {
|
||||||
|
const response = await getCargoTypeList(1, 9999);
|
||||||
|
const body = unwrapData(response) || {};
|
||||||
|
const records = Array.isArray(body) ? body : body.records || body.data || [];
|
||||||
|
cargoTypeOptions.value = buildCargoTypeTree(records);
|
||||||
|
cargoTypeFlatOptions.value = flattenCargoTypeOptions(cargoTypeOptions.value);
|
||||||
|
} catch (error) {
|
||||||
|
cargoTypeOptions.value = [];
|
||||||
|
cargoTypeFlatOptions.value = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const loadOptions = async () => {
|
const loadOptions = async () => {
|
||||||
const response = await api.getOptions();
|
const response = await api.getOptions();
|
||||||
const options = unwrapData(response) || {};
|
const options = unwrapData(response) || {};
|
||||||
projects.value = (options.projects || []).map(normalizeProject).filter(Boolean);
|
projects.value = (options.projects || []).map(normalizeProject).filter(Boolean);
|
||||||
await loadTransportTypeOptions();
|
await Promise.all([loadTransportTypeOptions(), loadCargoTypeOptions()]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadDetail = async id => {
|
const loadDetail = async id => {
|
||||||
@@ -1190,6 +1407,7 @@ const loadDetail = async id => {
|
|||||||
await loadContractsByProject(detail.projectId, detail.projectName || '');
|
await loadContractsByProject(detail.projectId, detail.projectName || '');
|
||||||
}
|
}
|
||||||
feeItemNames.value = Array.isArray(detail.feeItemNames) ? detail.feeItemNames : [];
|
feeItemNames.value = Array.isArray(detail.feeItemNames) ? detail.feeItemNames : [];
|
||||||
|
parsedHeaders.value = [];
|
||||||
const detailRows = Array.isArray(detail.rows) ? detail.rows : [];
|
const detailRows = Array.isArray(detail.rows) ? detail.rows : [];
|
||||||
rows.value = detailRows.map((item, index) => normalizeParsedRow(item, index));
|
rows.value = detailRows.map((item, index) => normalizeParsedRow(item, index));
|
||||||
refreshDuplicateFlags();
|
refreshDuplicateFlags();
|
||||||
|
|||||||
@@ -149,7 +149,7 @@
|
|||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div
|
<div
|
||||||
v-if="isFreightFeeColumn(column)"
|
v-if="isChargeFeeColumn(column)"
|
||||||
class="settlement-detail-page__billing-rule-cell"
|
class="settlement-detail-page__billing-rule-cell"
|
||||||
>
|
>
|
||||||
<span>{{ formatFeeDetailCell(row, column.prop) }}</span>
|
<span>{{ formatFeeDetailCell(row, column.prop) }}</span>
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
v-if="hasBillingRule(row)"
|
v-if="hasBillingRule(row)"
|
||||||
class="settlement-detail-page__billing-rule-icon"
|
class="settlement-detail-page__billing-rule-icon"
|
||||||
title="查看计费规则详情"
|
title="查看计费规则详情"
|
||||||
@click.stop="openBillingRuleInfo(row)"
|
@click.stop="openBillingRuleInfo(row, chargeFeeName(column))"
|
||||||
>
|
>
|
||||||
<InfoFilled />
|
<InfoFilled />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
@@ -376,12 +376,14 @@
|
|||||||
inputmode="decimal"
|
inputmode="decimal"
|
||||||
@input="value => handleAdjustDecimalInput(row, 'transportQuantity', value)"
|
@input="value => handleAdjustDecimalInput(row, 'transportQuantity', value)"
|
||||||
/>
|
/>
|
||||||
|
<span v-else-if="column.prop === 'unitPrice' && isTierUnitPrice(row)">-</span>
|
||||||
<el-input
|
<el-input
|
||||||
v-else-if="column.prop === 'unitPrice'"
|
v-else-if="column.prop === 'unitPrice'"
|
||||||
class="settlement-detail-page__adjust-input"
|
class="settlement-detail-page__adjust-input"
|
||||||
:model-value="row.unitPrice"
|
:model-value="row.unitPrice"
|
||||||
|
:disabled="!isUnitPriceAdjustable(row)"
|
||||||
inputmode="decimal"
|
inputmode="decimal"
|
||||||
@input="value => handleAdjustDecimalInput(row, 'unitPrice', value)"
|
@input="value => handleAdjustDecimalInput(row, 'unitPrice', value, 'unitPrice')"
|
||||||
/>
|
/>
|
||||||
<el-input
|
<el-input
|
||||||
v-else-if="column.prop === 'mileage'"
|
v-else-if="column.prop === 'mileage'"
|
||||||
@@ -1034,6 +1036,7 @@ import { getList as getCargoTypeList } from '@/api/base/cargo-type';
|
|||||||
import { exportBlob } from '@/api/common';
|
import { exportBlob } from '@/api/common';
|
||||||
import { getDictionary } from '@/api/system/dictbiz';
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import { formatContractOptionLabel } from '@/utils/contract';
|
import { formatContractOptionLabel } from '@/utils/contract';
|
||||||
|
import { isTierUnitPrice, isUnitPriceAdjustable } from '@/utils/settlement-adjust-rule';
|
||||||
import { downloadXls } from '@/utils/util';
|
import { downloadXls } from '@/utils/util';
|
||||||
import { createSettlementTransfer } from '@/utils/settlement-transfer';
|
import { createSettlementTransfer } from '@/utils/settlement-transfer';
|
||||||
import {
|
import {
|
||||||
@@ -1221,7 +1224,9 @@ export default {
|
|||||||
return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 4);
|
return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 4);
|
||||||
},
|
},
|
||||||
feeDetailColumns() {
|
feeDetailColumns() {
|
||||||
const baseColumns = feeDetailBaseColumns.filter(column => column.prop !== 'freightAmount');
|
const baseColumns = feeDetailBaseColumns.filter(
|
||||||
|
column => !['freightAmount', 'billingFactor', 'billingType'].includes(column.prop)
|
||||||
|
);
|
||||||
const hasAdjustment = this.feeRows.some(row => Number(row.adjustAmount || 0) !== 0);
|
const hasAdjustment = this.feeRows.some(row => Number(row.adjustAmount || 0) !== 0);
|
||||||
const tailColumns = feeDetailTailColumns.map(col =>
|
const tailColumns = feeDetailTailColumns.map(col =>
|
||||||
col.prop === 'originalAmountText' && hasAdjustment
|
col.prop === 'originalAmountText' && hasAdjustment
|
||||||
@@ -1302,17 +1307,13 @@ export default {
|
|||||||
this.openRouteDetail();
|
this.openRouteDetail();
|
||||||
},
|
},
|
||||||
isBottomPanelOpen() {
|
isBottomPanelOpen() {
|
||||||
this.$nextTick(() => this.updateListTableHeight());
|
this.updateListTableHeight();
|
||||||
},
|
},
|
||||||
searchVisible() {
|
searchVisible() {
|
||||||
if (this.isBottomPanelOpen) {
|
this.updateListTableHeight();
|
||||||
this.$nextTick(() => this.updateListTableHeight());
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
searchExpanded() {
|
searchExpanded() {
|
||||||
if (this.isBottomPanelOpen) {
|
this.updateListTableHeight();
|
||||||
this.$nextTick(() => this.updateListTableHeight());
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
displayTableColumns: {
|
displayTableColumns: {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
@@ -1343,6 +1344,10 @@ export default {
|
|||||||
this.loadPriceUnitOptions();
|
this.loadPriceUnitOptions();
|
||||||
await this.loadTable();
|
await this.loadTable();
|
||||||
await this.openRouteDetail();
|
await this.openRouteDetail();
|
||||||
|
this.updateListTableHeight();
|
||||||
|
},
|
||||||
|
activated() {
|
||||||
|
this.updateListTableHeight();
|
||||||
},
|
},
|
||||||
// 标签切走(keep-alive 缓存)或销毁时,收起 teleport/append-to-body 弹层,避免残留盖住其它页面
|
// 标签切走(keep-alive 缓存)或销毁时,收起 teleport/append-to-body 弹层,避免残留盖住其它页面
|
||||||
deactivated() {
|
deactivated() {
|
||||||
@@ -1353,6 +1358,8 @@ export default {
|
|||||||
this.closeInnerDialogs();
|
this.closeInnerDialogs();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
isTierUnitPrice,
|
||||||
|
isUnitPriceAdjustable,
|
||||||
closeInnerDialogs() {
|
closeInnerDialogs() {
|
||||||
this.closeDetailPanel();
|
this.closeDetailPanel();
|
||||||
this.closeAdjustPanel();
|
this.closeAdjustPanel();
|
||||||
@@ -1363,24 +1370,38 @@ export default {
|
|||||||
this.previewDialog.visible = false;
|
this.previewDialog.visible = false;
|
||||||
this.generateContractDialog.visible = false;
|
this.generateContractDialog.visible = false;
|
||||||
},
|
},
|
||||||
/** 底部面板打开时按剩余视口压缩主表高度,关闭后恢复自适应 */
|
/** 表格和分页撑满剩余可视区域;单据详情展开时贴住详情面板,关闭后恢复 */
|
||||||
updateListTableHeight() {
|
updateListTableHeight() {
|
||||||
if (!this.isBottomPanelOpen) {
|
this.$nextTick(() => {
|
||||||
this.listTableHeight = undefined;
|
window.requestAnimationFrame(() => this.applyListTableHeight());
|
||||||
this.$nextTick(() => this.$refs.mainTableRef?.doLayout?.());
|
});
|
||||||
return;
|
},
|
||||||
|
applyListTableHeight() {
|
||||||
|
const tableElement = this.$refs.mainTableRef?.$el;
|
||||||
|
if (!tableElement) return;
|
||||||
|
const pagination = this.$el?.querySelector?.('.settlement-detail-page__pagination');
|
||||||
|
const paginationHeight = pagination?.offsetHeight || 0;
|
||||||
|
const tableTop = tableElement.getBoundingClientRect().top;
|
||||||
|
const view = this.$el?.closest?.('#avue-view');
|
||||||
|
const viewRect = view?.getBoundingClientRect?.();
|
||||||
|
let bottomEdge = viewRect ? viewRect.bottom - 12 : window.innerHeight;
|
||||||
|
if (this.isBottomPanelOpen) {
|
||||||
|
const panelRect = document
|
||||||
|
.querySelector('.settlement-detail-page__detail-panel')
|
||||||
|
?.getBoundingClientRect?.();
|
||||||
|
if (panelRect && panelRect.height > 0) {
|
||||||
|
bottomEdge = panelRect.top;
|
||||||
|
this.listTableHeightRetry = 0;
|
||||||
|
} else if ((this.listTableHeightRetry || 0) < 5) {
|
||||||
|
this.listTableHeightRetry = (this.listTableHeightRetry || 0) + 1;
|
||||||
|
window.requestAnimationFrame(() => this.applyListTableHeight());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.listTableHeightRetry = 0;
|
||||||
}
|
}
|
||||||
const tablePanel = this.$el?.querySelector?.('.settlement-detail-page__table-panel');
|
const gap = this.isBottomPanelOpen ? 8 : 0;
|
||||||
const toolbar = tablePanel?.querySelector('.settlement-detail-page__toolbar');
|
this.listTableHeight = Math.max(180, Math.floor(bottomEdge - tableTop - paginationHeight - gap));
|
||||||
const pagination = tablePanel?.querySelector('.settlement-detail-page__pagination');
|
|
||||||
const panelTop = tablePanel?.getBoundingClientRect?.()?.top ?? 200;
|
|
||||||
const toolbarHeight = toolbar?.offsetHeight ?? 56;
|
|
||||||
const paginationHeight = pagination?.offsetHeight ?? 64;
|
|
||||||
// 与 .settlement-detail-page__detail-panel 的 max-height: min(60vh, 560px) 对齐
|
|
||||||
const bottomPanelHeight = Math.min(window.innerHeight * 0.6, 560);
|
|
||||||
const available =
|
|
||||||
window.innerHeight - panelTop - toolbarHeight - paginationHeight - bottomPanelHeight - 8;
|
|
||||||
this.listTableHeight = Math.max(180, Math.floor(available));
|
|
||||||
this.$nextTick(() => this.$refs.mainTableRef?.doLayout?.());
|
this.$nextTick(() => this.$refs.mainTableRef?.doLayout?.());
|
||||||
},
|
},
|
||||||
contractCategoryName(value) {
|
contractCategoryName(value) {
|
||||||
@@ -1982,8 +2003,9 @@ export default {
|
|||||||
},
|
},
|
||||||
handleAdjustDecimalInput(row, prop, value, changedField) {
|
handleAdjustDecimalInput(row, prop, value, changedField) {
|
||||||
row[prop] = this.normalizeAdjustDecimal(value);
|
row[prop] = this.normalizeAdjustDecimal(value);
|
||||||
if (!row.manualFee && row.id && ['transportQuantity', 'mileage'].includes(prop)) {
|
if (!row.manualFee && row.id && ['transportQuantity', 'mileage', 'unitPrice'].includes(prop)) {
|
||||||
this.scheduleAdjustFeeCalculation(row);
|
if (prop === 'unitPrice' && !isUnitPriceAdjustable(row)) return;
|
||||||
|
this.scheduleAdjustFeeCalculation(row, prop);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.recalculateAdjustRow(row, changedField);
|
this.recalculateAdjustRow(row, changedField);
|
||||||
@@ -2010,23 +2032,25 @@ export default {
|
|||||||
);
|
);
|
||||||
row.adjustAmount = Number((row.afterAmount - Number(row.originalAmount || 0)).toFixed(2));
|
row.adjustAmount = Number((row.afterAmount - Number(row.originalAmount || 0)).toFixed(2));
|
||||||
},
|
},
|
||||||
scheduleAdjustFeeCalculation(row) {
|
scheduleAdjustFeeCalculation(row, changedField) {
|
||||||
if (row.adjustCalculateTimer) clearTimeout(row.adjustCalculateTimer);
|
if (row.adjustCalculateTimer) clearTimeout(row.adjustCalculateTimer);
|
||||||
row.adjustCalculateVersion += 1;
|
row.adjustCalculateVersion += 1;
|
||||||
row.calculating = true;
|
row.calculating = true;
|
||||||
const version = row.adjustCalculateVersion;
|
const version = row.adjustCalculateVersion;
|
||||||
row.adjustCalculateTimer = setTimeout(() => {
|
row.adjustCalculateTimer = setTimeout(() => {
|
||||||
row.adjustCalculateTimer = null;
|
row.adjustCalculateTimer = null;
|
||||||
this.calculateAdjustRow(row, version);
|
this.calculateAdjustRow(row, version, changedField);
|
||||||
}, 300);
|
}, 300);
|
||||||
},
|
},
|
||||||
async calculateAdjustRow(row, version) {
|
async calculateAdjustRow(row, version, changedField) {
|
||||||
try {
|
try {
|
||||||
const res = await api.calculateAdjustedFee({
|
const res = await api.calculateAdjustedFee({
|
||||||
detailId: this.adjustDialog.row?.id,
|
detailId: this.adjustDialog.row?.id,
|
||||||
feeId: row.id,
|
feeId: row.id,
|
||||||
transportQuantity: Number(row.transportQuantity || 0),
|
transportQuantity: Number(row.transportQuantity || 0),
|
||||||
mileage: Number(row.mileage || 0),
|
mileage: Number(row.mileage || 0),
|
||||||
|
unitPrice: row.unitPrice === null || row.unitPrice === '' ? null : Number(row.unitPrice),
|
||||||
|
changedField,
|
||||||
freightAmount: Number(row.freightAmount || 0),
|
freightAmount: Number(row.freightAmount || 0),
|
||||||
feeItems: Object.fromEntries(
|
feeItems: Object.fromEntries(
|
||||||
Object.entries(row.feeItems || {}).map(([name, amount]) => [name, Number(amount || 0)])
|
Object.entries(row.feeItems || {}).map(([name, amount]) => [name, Number(amount || 0)])
|
||||||
@@ -2034,18 +2058,24 @@ export default {
|
|||||||
});
|
});
|
||||||
if (version !== row.adjustCalculateVersion || !this.adjustDialog.visible) return;
|
if (version !== row.adjustCalculateVersion || !this.adjustDialog.visible) return;
|
||||||
const data = res.data?.data || res.data || res || {};
|
const data = res.data?.data || res.data || res || {};
|
||||||
row.freightAmount = Number(data.freightAmount || 0);
|
this.applyCalculatedAdjustRow(row, data);
|
||||||
row.feeItems = Object.fromEntries(
|
|
||||||
Object.entries(data.feeItems || {}).map(([name, amount]) => [name, Number(amount || 0)])
|
|
||||||
);
|
|
||||||
row.adjustAmount = Number(data.adjustAmount || 0);
|
|
||||||
row.afterAmount = Number(data.afterAmount || 0);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
row.calculateError = error.message;
|
row.calculateError = error.message;
|
||||||
} finally {
|
} finally {
|
||||||
if (version === row.adjustCalculateVersion) row.calculating = false;
|
if (version === row.adjustCalculateVersion) row.calculating = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
applyCalculatedAdjustRow(row, data) {
|
||||||
|
row.freightAmount = Number(data.freightAmount || 0);
|
||||||
|
row.feeItems = Object.fromEntries(
|
||||||
|
Object.entries(data.feeItems || {}).map(([name, amount]) => [name, Number(amount || 0)])
|
||||||
|
);
|
||||||
|
if (!isTierUnitPrice(row) && data.unitPrice !== undefined && data.unitPrice !== null) {
|
||||||
|
row.unitPrice = Number(data.unitPrice);
|
||||||
|
}
|
||||||
|
row.adjustAmount = Number(data.adjustAmount || 0);
|
||||||
|
row.afterAmount = Number(data.afterAmount || 0);
|
||||||
|
},
|
||||||
clearAdjustCalculations() {
|
clearAdjustCalculations() {
|
||||||
this.adjustRows.forEach(row => {
|
this.adjustRows.forEach(row => {
|
||||||
if (row.adjustCalculateTimer) clearTimeout(row.adjustCalculateTimer);
|
if (row.adjustCalculateTimer) clearTimeout(row.adjustCalculateTimer);
|
||||||
@@ -2057,12 +2087,14 @@ export default {
|
|||||||
isFreightFeeItem(name) {
|
isFreightFeeItem(name) {
|
||||||
return String(name || '').includes('运费') || String(name || '').includes('运输费');
|
return String(name || '').includes('运费') || String(name || '').includes('运输费');
|
||||||
},
|
},
|
||||||
isFreightFeeColumn(column = {}) {
|
isChargeFeeColumn(column = {}) {
|
||||||
return (
|
if (column.prop === 'priceUnit') return false;
|
||||||
column.prop === 'freightAmount' ||
|
if (column.prop === 'freightAmount') return true;
|
||||||
this.isFreightFeeItem(column.label) ||
|
return String(column.prop || '').startsWith('feeItem');
|
||||||
this.isFreightFeeItem(column.feeItemName)
|
},
|
||||||
);
|
chargeFeeName(column = {}) {
|
||||||
|
if (column.prop === 'freightAmount') return column.label || '运输费';
|
||||||
|
return column.label || '';
|
||||||
},
|
},
|
||||||
hasBillingRule(row) {
|
hasBillingRule(row) {
|
||||||
const rule = this.normalizeBillingRule(row);
|
const rule = this.normalizeBillingRule(row);
|
||||||
@@ -2078,11 +2110,21 @@ export default {
|
|||||||
rule.matchCondition?.cargoType
|
rule.matchCondition?.cargoType
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
openBillingRuleInfo(row) {
|
openBillingRuleInfo(row, feeName = '') {
|
||||||
this.billingRuleDialog.rule = this.normalizeBillingRule(row);
|
this.billingRuleDialog.rule = this.normalizeBillingRule(row, feeName);
|
||||||
this.billingRuleDialog.visible = true;
|
this.billingRuleDialog.visible = true;
|
||||||
},
|
},
|
||||||
normalizeBillingRule(row) {
|
ruleFeeName(rule = {}) {
|
||||||
|
return String(rule.feeItem || rule.feeItemName || rule.name || '').trim();
|
||||||
|
},
|
||||||
|
feeNamesMatch(left, right) {
|
||||||
|
const source = String(left || '').trim();
|
||||||
|
const target = String(right || '').trim();
|
||||||
|
if (!source || !target) return false;
|
||||||
|
if (source === target) return true;
|
||||||
|
return this.isFreightFeeItem(source) && this.isFreightFeeItem(target);
|
||||||
|
},
|
||||||
|
normalizeBillingRule(row, feeName = '') {
|
||||||
const source = row || {};
|
const source = row || {};
|
||||||
const nested = [
|
const nested = [
|
||||||
source.billingRule,
|
source.billingRule,
|
||||||
@@ -2095,28 +2137,48 @@ export default {
|
|||||||
source.rule,
|
source.rule,
|
||||||
].find(value => value !== undefined && value !== null && value !== '');
|
].find(value => value !== undefined && value !== null && value !== '');
|
||||||
const parsedNested = this.parseJsonValue(nested);
|
const parsedNested = this.parseJsonValue(nested);
|
||||||
const matchedRules = Array.isArray(parsedNested)
|
const parsedRules = Array.isArray(parsedNested)
|
||||||
? parsedNested.filter(item => item && typeof item === 'object')
|
? parsedNested.filter(item => item && typeof item === 'object')
|
||||||
: [];
|
: [];
|
||||||
let rule = this.parseObject(matchedRules[0] || parsedNested || nested);
|
const requestedFeeName = String(feeName || '').trim();
|
||||||
if (!Object.keys(rule).length && typeof nested === 'string') rule = { name: nested };
|
const matchedRules = requestedFeeName
|
||||||
|
? parsedRules.filter(item => this.feeNamesMatch(this.ruleFeeName(item), requestedFeeName))
|
||||||
|
: parsedRules;
|
||||||
|
const sharedRule =
|
||||||
|
parsedRules.length === 1 && !this.ruleFeeName(parsedRules[0]) ? parsedRules[0] : null;
|
||||||
|
const ruleSource =
|
||||||
|
matchedRules[0] || sharedRule || (!requestedFeeName ? parsedNested || nested : null);
|
||||||
|
let rule = this.parseObject(ruleSource);
|
||||||
|
if (!Object.keys(rule).length && typeof nested === 'string' && !requestedFeeName) {
|
||||||
|
rule = { name: nested };
|
||||||
|
}
|
||||||
const matchCondition = this.parseObject(
|
const matchCondition = this.parseObject(
|
||||||
source.matchCondition ?? source.billingMatchCondition ?? rule.matchCondition
|
requestedFeeName
|
||||||
|
? (rule.matchCondition ?? source.matchCondition ?? source.billingMatchCondition)
|
||||||
|
: (source.matchCondition ?? source.billingMatchCondition ?? rule.matchCondition)
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
...rule,
|
...rule,
|
||||||
feeItem: source.billingFeeItem || source.feeItem || rule.feeItem,
|
feeItem: requestedFeeName || source.billingFeeItem || source.feeItem || rule.feeItem,
|
||||||
billingElement:
|
billingElement: requestedFeeName
|
||||||
source.billingElement ||
|
? rule.billingElement || rule.billingFactor || ''
|
||||||
source.billingFactor ||
|
: source.billingElement ||
|
||||||
rule.billingElement ||
|
source.billingFactor ||
|
||||||
rule.billingFactor,
|
rule.billingElement ||
|
||||||
billingType: source.billingType || rule.billingType,
|
rule.billingFactor,
|
||||||
billingUnit: source.billingUnit || source.priceUnit || rule.billingUnit || rule.priceUnit,
|
billingType: requestedFeeName ? rule.billingType || '' : source.billingType || rule.billingType,
|
||||||
unitPrice: source.billingUnitPrice ?? source.unitPrice ?? rule.unitPrice,
|
billingUnit: requestedFeeName
|
||||||
minimumBillingWeight:
|
? rule.billingUnit || rule.priceUnit || ''
|
||||||
source.minimumBillingWeight ?? rule.minimumBillingWeight ?? rule.lowerLimit,
|
: source.billingUnit || source.priceUnit || rule.billingUnit || rule.priceUnit,
|
||||||
remark: source.billingRuleRemark || source.ruleRemark || rule.remark,
|
unitPrice: requestedFeeName
|
||||||
|
? (rule.unitPrice ?? '')
|
||||||
|
: (source.billingUnitPrice ?? source.unitPrice ?? rule.unitPrice),
|
||||||
|
minimumBillingWeight: requestedFeeName
|
||||||
|
? (rule.minimumBillingWeight ?? rule.lowerLimit ?? '')
|
||||||
|
: (source.minimumBillingWeight ?? rule.minimumBillingWeight ?? rule.lowerLimit),
|
||||||
|
remark: requestedFeeName
|
||||||
|
? rule.remark || ''
|
||||||
|
: source.billingRuleRemark || source.ruleRemark || rule.remark,
|
||||||
rules: matchedRules,
|
rules: matchedRules,
|
||||||
matchCondition: {
|
matchCondition: {
|
||||||
...matchCondition,
|
...matchCondition,
|
||||||
|
|||||||
@@ -195,37 +195,17 @@
|
|||||||
>
|
>
|
||||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-if="detail.waybillMode === 'history'"
|
v-for="column in detailDisplayColumns"
|
||||||
prop="waybillNo"
|
:key="column.prop"
|
||||||
label="运单号"
|
:prop="column.fee ? undefined : column.prop"
|
||||||
min-width="130"
|
:label="column.label"
|
||||||
/>
|
:min-width="column.minWidth || 120"
|
||||||
<el-table-column prop="vehicleNo" label="车牌号/航班号/船号/班列号" min-width="170" />
|
show-overflow-tooltip
|
||||||
<el-table-column prop="transportType" label="运输方式" min-width="100" />
|
|
||||||
<el-table-column prop="departureAddress" label="发货地址" min-width="140" show-overflow-tooltip />
|
|
||||||
<el-table-column prop="arrivalAddress" label="到货地址" min-width="140" show-overflow-tooltip />
|
|
||||||
<el-table-column prop="cargoName" label="货物名称" min-width="120" show-overflow-tooltip />
|
|
||||||
<el-table-column prop="cargoType" label="货物类型" min-width="100" />
|
|
||||||
<el-table-column prop="model" label="型号" min-width="100" />
|
|
||||||
<el-table-column prop="quantity" label="数量" width="90" />
|
|
||||||
<el-table-column prop="quantityUnit" label="数量单位" width="90" />
|
|
||||||
<el-table-column prop="mileage" label="里程(km)" width="100" />
|
|
||||||
<el-table-column prop="unitPrice" label="运输单价" width="100" />
|
|
||||||
<el-table-column prop="transportFee" label="运输费" width="100" />
|
|
||||||
<el-table-column
|
|
||||||
v-for="name in detailFeeItemNames"
|
|
||||||
:key="name"
|
|
||||||
:label="name"
|
|
||||||
min-width="100"
|
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ formatFeeAmount(row, name) }}
|
{{ column.fee ? formatFeeAmount(row, column.prop) : formatDetailCell(row, column) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="settlementAmount" label="结算金额" width="110" />
|
|
||||||
<el-table-column prop="actualStartDate" label="实际发货时间" min-width="120" />
|
|
||||||
<el-table-column prop="actualEndDate" label="实际完成时间" min-width="120" />
|
|
||||||
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip />
|
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -359,6 +339,52 @@ export default {
|
|||||||
visibleSearchFields() {
|
visibleSearchFields() {
|
||||||
return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 4);
|
return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 4);
|
||||||
},
|
},
|
||||||
|
detailDisplayColumns() {
|
||||||
|
const before = [
|
||||||
|
{ prop: 'originalNo', label: '原始单号', minWidth: 130 },
|
||||||
|
{ prop: 'loadingIdentifier', label: '配载标识号', minWidth: 140 },
|
||||||
|
{ prop: 'vehicleNo', label: '车牌号/航班号/船号/班列号', minWidth: 190 },
|
||||||
|
{ prop: 'transportType', label: '运输方式', minWidth: 120 },
|
||||||
|
{ prop: 'driverName', label: '司机/船长姓名', minWidth: 130 },
|
||||||
|
{ prop: 'driverPhone', label: '司机/船长手机号', minWidth: 150 },
|
||||||
|
{ prop: 'departureAddress', label: '发货地址', minWidth: 160 },
|
||||||
|
{ prop: 'departureContact', label: '发货联系人', minWidth: 120 },
|
||||||
|
{ prop: 'departurePhone', label: '发货联系人电话', minWidth: 140 },
|
||||||
|
{ prop: 'arrivalAddress', label: '到货地址', minWidth: 160 },
|
||||||
|
{ prop: 'arrivalContact', label: '收货联系人', minWidth: 120 },
|
||||||
|
{ prop: 'arrivalPhone', label: '收货联系人电话', minWidth: 140 },
|
||||||
|
{ prop: 'cargoName', label: '货物名称', minWidth: 140 },
|
||||||
|
{ prop: 'cargoType', label: '货物类型', minWidth: 120 },
|
||||||
|
{ prop: 'packageType', label: '包装', minWidth: 100 },
|
||||||
|
{ prop: 'quantity', label: '数量', minWidth: 90 },
|
||||||
|
{ prop: 'quantityUnit', label: '数量单位', minWidth: 100 },
|
||||||
|
{ prop: 'specification', label: '规格', minWidth: 110 },
|
||||||
|
{ prop: 'model', label: '型号', minWidth: 110 },
|
||||||
|
{ prop: 'mileage', label: '里程(km)', minWidth: 110 },
|
||||||
|
{ prop: 'unitPrice', label: '运输单价', minWidth: 110 },
|
||||||
|
{ prop: 'transportFee', label: '运输费', minWidth: 110, money: true },
|
||||||
|
];
|
||||||
|
const fees = (this.detailFeeItemNames || []).map(name => ({
|
||||||
|
prop: name,
|
||||||
|
label: name,
|
||||||
|
minWidth: 110,
|
||||||
|
fee: true,
|
||||||
|
}));
|
||||||
|
const after = [
|
||||||
|
{ prop: 'settlementAmount', label: '结算金额', minWidth: 120, money: true },
|
||||||
|
{ prop: 'actualStartDate', label: '实际发货时间', minWidth: 140 },
|
||||||
|
{ prop: 'actualEndDate', label: '实际完成时间', minWidth: 140 },
|
||||||
|
{ prop: 'planStartDate', label: '预计发货时间', minWidth: 140 },
|
||||||
|
{ prop: 'planEndDate', label: '预计完成时间', minWidth: 140 },
|
||||||
|
{ prop: 'remark', label: '备注', minWidth: 140 },
|
||||||
|
{ prop: 'waybillIdentifier', label: '同一运单标识号', minWidth: 150 },
|
||||||
|
];
|
||||||
|
const history =
|
||||||
|
this.detail.waybillMode === 'history'
|
||||||
|
? [{ prop: 'waybillNo', label: '运单号', minWidth: 140 }]
|
||||||
|
: [];
|
||||||
|
return [...history, ...before, ...fees, ...after];
|
||||||
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.loadOptions();
|
this.loadOptions();
|
||||||
@@ -418,6 +444,13 @@ export default {
|
|||||||
const body = response?.data || response || {};
|
const body = response?.data || response || {};
|
||||||
return body?.data !== undefined ? body.data : body;
|
return body?.data !== undefined ? body.data : body;
|
||||||
},
|
},
|
||||||
|
formatDetailCell(row, column) {
|
||||||
|
const value = row?.[column.prop];
|
||||||
|
if (value === null || value === undefined || value === '') return '';
|
||||||
|
if (!column.money) return value;
|
||||||
|
const number = Number(value);
|
||||||
|
return Number.isFinite(number) ? number.toFixed(2) : value;
|
||||||
|
},
|
||||||
formatFeeAmount(row, name) {
|
formatFeeAmount(row, name) {
|
||||||
const feeItems = row?.feeItems || {};
|
const feeItems = row?.feeItems || {};
|
||||||
const value = feeItems[name] ?? row?.[name];
|
const value = feeItems[name] ?? row?.[name];
|
||||||
|
|||||||
Reference in New Issue
Block a user