fix
This commit is contained in:
@@ -19,6 +19,12 @@ export const changeRoute = data =>
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
export const maintainMileage = data =>
|
||||
request({
|
||||
url: `${baseUrl}/maintain-mileage`,
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
export const roadLoading = ids =>
|
||||
request({
|
||||
url: `${baseUrl}/road-loading`,
|
||||
|
||||
@@ -8,6 +8,8 @@ export const getFormalSettlements = keyword =>
|
||||
request({ url: `${baseUrl}/candidate-formal-settlements`, method: 'get', params: { keyword } });
|
||||
export const getFormalDetails = formalSettlementId =>
|
||||
request({ url: `${baseUrl}/formal-details`, method: 'get', params: { formalSettlementId } });
|
||||
export const getFeeOptions = () =>
|
||||
request({ url: `${baseUrl}/fee-options`, method: 'get' });
|
||||
export const save = data => request({ url: `${baseUrl}/save`, method: 'post', data });
|
||||
export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } });
|
||||
export const submit = id => request({ url: `${baseUrl}/submit`, method: 'post', data: { id } });
|
||||
|
||||
@@ -11,8 +11,8 @@ export const formalSettlementFormFields = [
|
||||
{ label: '汇率日期', prop: 'exchangeRateDate', type: 'date', required: true },
|
||||
{ label: '结算汇率', prop: 'exchangeRate', type: 'number', required: true },
|
||||
{ label: '本位币合计', prop: 'localSettlementAmount', readonly: true, money: true },
|
||||
{ label: '申请付款金额(含预付)', prop: 'appliedPaymentAmount', readonly: true, money: true },
|
||||
{ label: '已收/已付合计', prop: 'paidAmount', readonly: true, money: true },
|
||||
{ label: '创建人', prop: 'createUserName', readonly: true },
|
||||
{ label: '创建时间', prop: 'createTime', readonly: true },
|
||||
];
|
||||
|
||||
export const createFormalSettlementForm = () => ({
|
||||
@@ -27,6 +27,8 @@ export const createFormalSettlementForm = () => ({
|
||||
sourceDetailIds: [],
|
||||
exchangeRateDate: '',
|
||||
exchangeRate: 1,
|
||||
createUserName: '',
|
||||
createTime: '',
|
||||
attachmentsJson: '[]',
|
||||
remark: '',
|
||||
});
|
||||
|
||||
@@ -7,11 +7,21 @@ export const approvalStatusOptions = [
|
||||
];
|
||||
|
||||
export const invoiceStatusOptions = [
|
||||
{ label: '未收/开票', value: 'unreceived' },
|
||||
{ label: '部分收/开票', value: 'partial' },
|
||||
{ label: '已收/开票', value: 'completed' },
|
||||
{ label: '未开/收票', value: 'unreceived' },
|
||||
{ label: '部分开/收票', value: 'partial' },
|
||||
{ label: '已开/收票', value: 'completed' },
|
||||
];
|
||||
|
||||
export const invoiceStatusName = (value, settlementType) => {
|
||||
const payable = settlementType === 'payable';
|
||||
const labels = {
|
||||
unreceived: payable ? '未收票' : '未开票',
|
||||
partial: payable ? '部分收票' : '部分开票',
|
||||
completed: payable ? '已收票' : '已开票',
|
||||
};
|
||||
return labels[value] || value || '-';
|
||||
};
|
||||
|
||||
export const paymentStatusOptions = [
|
||||
{ label: '未收/付款', value: 'unpaid' },
|
||||
{ label: '部分收/付款', value: 'partial' },
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
export const settlementAdjustmentFormFields = [
|
||||
{ label: '单据号', prop: 'adjustmentNo' },
|
||||
{ label: '关联结算单号', prop: 'formalSettlementId' },
|
||||
{ label: '客户/客商名称', prop: 'customerName' },
|
||||
{ label: '客户', prop: 'customerName' },
|
||||
{ label: '结算单类型', prop: 'settlementTypeName' },
|
||||
{ label: '关联合同名称', prop: 'contractName' },
|
||||
{ label: '关联项目', prop: 'projectName' },
|
||||
{ label: '原结算金额', prop: 'originalSettlementAmount', money: true },
|
||||
{ label: '调整金额', prop: 'adjustmentAmount', money: true },
|
||||
{ label: '调整后结算金额', prop: 'adjustedSettlementAmount', money: true },
|
||||
{ label: '创建日期', prop: 'createTime' },
|
||||
{ label: '创建人', prop: 'createUserName' },
|
||||
];
|
||||
|
||||
export const createSettlementAdjustmentForm = () => ({
|
||||
@@ -24,6 +26,8 @@ export const createSettlementAdjustmentForm = () => ({
|
||||
adjustmentAmount: 0,
|
||||
originalSettlementAmount: 0,
|
||||
adjustedSettlementAmount: 0,
|
||||
createTime: '',
|
||||
createUserName: '',
|
||||
attachmentsJson: '[]',
|
||||
remark: '',
|
||||
});
|
||||
|
||||
@@ -8,8 +8,6 @@ export const settlementAdjustmentTableColumns = [
|
||||
{ label: '合同编号', prop: 'contractNo', minWidth: 150 },
|
||||
{ label: '合同名称', prop: 'contractName', minWidth: 170 },
|
||||
{ label: '调整金额', prop: 'adjustmentAmount', minWidth: 130, money: true },
|
||||
{ label: '原结算金额', prop: 'originalSettlementAmount', minWidth: 140, money: true },
|
||||
{ label: '调整后结算金额', prop: 'adjustedSettlementAmount', minWidth: 160, money: true },
|
||||
{ label: '审核状态', prop: 'approvalStatusName', minWidth: 110, status: true },
|
||||
{ label: '当前节点', prop: 'currentNode', minWidth: 120 },
|
||||
{ label: '当前处理人', prop: 'currentProcessor', minWidth: 130 },
|
||||
|
||||
@@ -2143,6 +2143,9 @@
|
||||
<el-link type="primary" v-if="canComplete(row)" @click="handleAction('complete', row)">
|
||||
{{ completeActionLabel }}
|
||||
</el-link>
|
||||
<el-link type="primary" v-if="canMaintainMileage(row)" @click="openMileageDialog(row)">
|
||||
维护里程
|
||||
</el-link>
|
||||
<el-link
|
||||
v-for="operation in customOperations(row)"
|
||||
:key="operation.action"
|
||||
@@ -2156,6 +2159,59 @@
|
||||
</template>
|
||||
</component>
|
||||
|
||||
<el-dialog
|
||||
v-model="mileageDialog.visible"
|
||||
width="520px"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
class="business-crud-page__mileage-dialog"
|
||||
@closed="resetMileageDialog"
|
||||
>
|
||||
<template #header>
|
||||
<div class="dialog-section-title">维护里程</div>
|
||||
</template>
|
||||
<el-form
|
||||
ref="mileageFormRef"
|
||||
:model="mileageForm"
|
||||
:rules="mileageRules"
|
||||
label-position="right"
|
||||
label-width="auto"
|
||||
class="business-crud-page__mileage-form"
|
||||
>
|
||||
<el-form-item label="里程(公里)" prop="mileage">
|
||||
<el-input
|
||||
:model-value="mileageForm.mileage"
|
||||
inputmode="numeric"
|
||||
maxlength="10"
|
||||
placeholder="请输入里程"
|
||||
@input="handleMaintainMileageInput"
|
||||
>
|
||||
<template #append>公里</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="mileageRemark">
|
||||
<el-input
|
||||
v-model="mileageForm.mileageRemark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="请输入里程维护备注"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="mileageDialog.visible = false">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="mileageDialog.submitting"
|
||||
@click="submitMileageMaintenance"
|
||||
>
|
||||
提交
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<empty-pagination
|
||||
v-show="!isStandaloneFormPage"
|
||||
:page="page"
|
||||
@@ -5507,6 +5563,26 @@ export default {
|
||||
waybillRouteChangeRecords: [],
|
||||
waybillRouteChangeDragIndex: -1,
|
||||
waybillRouteChangeSaving: false,
|
||||
mileageDialog: {
|
||||
visible: false,
|
||||
submitting: false,
|
||||
row: null,
|
||||
},
|
||||
mileageForm: {
|
||||
id: '',
|
||||
mileage: '',
|
||||
mileageRemark: '',
|
||||
},
|
||||
mileageRules: {
|
||||
mileage: [
|
||||
{ required: true, message: '请输入里程', trigger: 'blur' },
|
||||
{
|
||||
pattern: /^[1-9]\d{0,9}$/,
|
||||
message: '里程必须为不超过10位的正整数',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
waybillCommonAddressBox: false,
|
||||
waybillCommonAddressLoading: false,
|
||||
waybillCommonAddressQuery: {
|
||||
@@ -6717,6 +6793,15 @@ export default {
|
||||
? status === 'dispatching'
|
||||
: status === 'processing';
|
||||
},
|
||||
canMaintainMileage(row) {
|
||||
return (
|
||||
this.config.permission === 'waybill_manage' &&
|
||||
this.hasPermission(`${this.config.permission}_mileage`) &&
|
||||
typeof this.api.maintainMileage === 'function' &&
|
||||
this.statusValue(row) === 'completed' &&
|
||||
row.mileageMaintainable === true
|
||||
);
|
||||
},
|
||||
canReassign(row) {
|
||||
return (
|
||||
this.hasAction('reassign') &&
|
||||
@@ -11572,6 +11657,46 @@ export default {
|
||||
this.onLoad(this.page, this.query);
|
||||
});
|
||||
},
|
||||
openMileageDialog(row) {
|
||||
this.mileageDialog = {
|
||||
visible: true,
|
||||
submitting: false,
|
||||
row,
|
||||
};
|
||||
this.mileageForm = {
|
||||
id: row.id,
|
||||
mileage: row.mileage === null || row.mileage === undefined ? '' : String(row.mileage),
|
||||
mileageRemark: row.mileageRemark || '',
|
||||
};
|
||||
this.$nextTick(() => this.$refs.mileageFormRef?.clearValidate());
|
||||
},
|
||||
handleMaintainMileageInput(value) {
|
||||
this.mileageForm.mileage = String(value || '')
|
||||
.replace(/\D/g, '')
|
||||
.slice(0, 10);
|
||||
},
|
||||
async submitMileageMaintenance() {
|
||||
const valid = await this.$refs.mileageFormRef?.validate().catch(() => false);
|
||||
if (!valid || this.mileageDialog.submitting) return;
|
||||
this.mileageDialog.submitting = true;
|
||||
try {
|
||||
await this.api.maintainMileage({
|
||||
id: this.mileageForm.id,
|
||||
mileage: Number(this.mileageForm.mileage),
|
||||
mileageRemark: String(this.mileageForm.mileageRemark || '').trim(),
|
||||
});
|
||||
this.$message.success('里程维护成功');
|
||||
this.mileageDialog.visible = false;
|
||||
this.onLoad(this.page, this.query);
|
||||
} finally {
|
||||
this.mileageDialog.submitting = false;
|
||||
}
|
||||
},
|
||||
resetMileageDialog() {
|
||||
this.mileageDialog.row = null;
|
||||
this.mileageForm = { id: '', mileage: '', mileageRemark: '' };
|
||||
this.$refs.mileageFormRef?.clearValidate();
|
||||
},
|
||||
handleAction(action, row) {
|
||||
const actionName = {
|
||||
enable: '启用',
|
||||
@@ -15592,6 +15717,18 @@ export default {
|
||||
top: 92px !important;
|
||||
left: 0 !important;
|
||||
}
|
||||
:global(.business-crud-page__mileage-dialog .business-crud-page__mileage-form) {
|
||||
padding: 4px 20px 0;
|
||||
}
|
||||
|
||||
:global(.business-crud-page__mileage-dialog .business-crud-page__mileage-form .el-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
:global(.business-crud-page__mileage-dialog .business-crud-page__mileage-form .el-input),
|
||||
:global(.business-crud-page__mileage-dialog .business-crud-page__mileage-form .el-textarea) {
|
||||
width: 100%;
|
||||
}
|
||||
.el-input__icon {
|
||||
color: #1e90ff !important;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
v-else-if="field.type === 'number' && editable"
|
||||
v-model="form[field.prop]"
|
||||
:min="0.000001"
|
||||
:precision="6"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
/>
|
||||
<span v-else-if="field.money">{{ formatMoney(form[field.prop]) }}</span>
|
||||
@@ -153,8 +153,7 @@
|
||||
<span
|
||||
v-else-if="isSummaryMoney(column.prop)"
|
||||
:class="{
|
||||
'formal-editor__negative-amount':
|
||||
column.prop === 'adjustAmount' && Number(row[column.prop] || 0) < 0,
|
||||
'formal-editor__negative-amount': isNegativeAdjustedAmount(row, column.prop),
|
||||
}"
|
||||
>
|
||||
{{ formatMoney(row[column.prop]) }}
|
||||
@@ -224,7 +223,10 @@
|
||||
<template #default="{ row }">
|
||||
<span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||
<span v-else-if="column.prop === 'transportQuantity'">
|
||||
{{ formatQuantity(row[column.prop]) }}
|
||||
{{ formatDetailTransportQuantity(row[column.prop]) }}
|
||||
</span>
|
||||
<span v-else-if="column.prop === 'mileage'">
|
||||
{{ formatDetailMileage(row[column.prop]) }}
|
||||
</span>
|
||||
<span v-else-if="column.prop === 'transportType'">
|
||||
{{ transportTypeName(row[column.prop]) }}
|
||||
@@ -235,11 +237,9 @@
|
||||
<el-table-column v-if="editable" label="操作" width="150" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="formal-editor__links">
|
||||
<el-link v-if="row.formalSettlementId" type="primary" @click="openAdjustDialog(row)"
|
||||
>调整</el-link
|
||||
>
|
||||
<el-link type="primary" @click="openAdjustDialog(row)">调整</el-link>
|
||||
<el-link v-if="!row.sourcePreSettlementId" type="danger" @click="removeDetail(row)"
|
||||
>删除</el-link
|
||||
>踢出</el-link
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
@@ -254,7 +254,13 @@
|
||||
{{ missingAttachmentTypeText }}
|
||||
</span>
|
||||
</template>
|
||||
<el-table :data="attachments" border>
|
||||
<div class="formal-editor__attachment-actions">
|
||||
<el-button type="primary" :disabled="!attachments.length" @click="downloadAttachments">
|
||||
批量下载
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="attachments" border @selection-change="handleAttachmentSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column label="类型" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
@@ -323,7 +329,7 @@
|
||||
</section-card>
|
||||
|
||||
<section-card title="发票信息">
|
||||
<div v-if="editable" class="formal-editor__invoice-claim">
|
||||
<div class="formal-editor__invoice-claim">
|
||||
<el-form inline label-position="right" label-width="auto" @submit.prevent>
|
||||
<el-form-item label="发票号码">
|
||||
<el-input
|
||||
@@ -394,9 +400,7 @@
|
||||
<div class="formal-editor__links">
|
||||
<el-link type="primary" @click="viewInvoiceAttachment(row)">查看</el-link>
|
||||
<el-link type="primary" @click="downloadInvoiceAttachment(row)">下载</el-link>
|
||||
<el-link v-if="editable" type="danger" @click="removeInvoice($index)">
|
||||
删除
|
||||
</el-link>
|
||||
<el-link type="danger" @click="removeInvoice($index)">删除</el-link>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -626,6 +630,39 @@
|
||||
>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="detailAmountAdjust.visible"
|
||||
title="结算明细调整"
|
||||
width="520px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
:model="detailAmountAdjust"
|
||||
label-position="right"
|
||||
label-width="auto"
|
||||
class="formal-editor__amount-adjust"
|
||||
>
|
||||
<el-form-item label="原金额">
|
||||
<span>{{ formatMoney(detailAmountAdjust.originalAmount) }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="调整金额" required>
|
||||
<el-input-number
|
||||
v-model="detailAmountAdjust.adjustAmount"
|
||||
:precision="2"
|
||||
:step="0.01"
|
||||
:controls="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结算金额">
|
||||
<span>{{ formatMoney(detailAmountAdjustSettlementAmount) }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="detailAmountAdjust.visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmDetailAmountAdjustment">提交</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="adjust.visible" title="结算明细调整" width="92%" append-to-body>
|
||||
<el-table v-loading="adjust.loading" :data="adjust.rows" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
@@ -671,14 +708,6 @@
|
||||
:precision="2"
|
||||
:controls="false" /></template
|
||||
></el-table-column>
|
||||
<el-table-column label="结算金额(不含税)" min-width="180" align="center"
|
||||
><template #default="{ row }"
|
||||
><el-input-number
|
||||
v-model="row.settlementAmountNoTax"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false" /></template
|
||||
></el-table-column>
|
||||
<el-table-column label="备注" min-width="180" align="center"
|
||||
><template #default="{ row }"><el-input v-model="row.remark" maxlength="200" /></template
|
||||
></el-table-column>
|
||||
@@ -723,6 +752,7 @@ import {
|
||||
summaryColumns,
|
||||
} from '@/option/settlement/formalSettlementTable';
|
||||
import { getDictionary } from '@/api/system/dictbiz';
|
||||
import { h } from 'vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { downloadFileByUrl } from '@/utils/util';
|
||||
import * as XLSX from 'xlsx';
|
||||
@@ -752,6 +782,7 @@ export default {
|
||||
adjustments: [],
|
||||
changeRecords: [],
|
||||
attachments: [],
|
||||
selectedAttachmentRows: [],
|
||||
invoices: [],
|
||||
invoiceClaimNo: '',
|
||||
invoiceClaimLoading: false,
|
||||
@@ -815,6 +846,12 @@ export default {
|
||||
reason: '',
|
||||
rows: [],
|
||||
},
|
||||
detailAmountAdjust: {
|
||||
visible: false,
|
||||
row: null,
|
||||
originalAmount: 0,
|
||||
adjustAmount: 0,
|
||||
},
|
||||
detailCollapsed: false,
|
||||
detailQuery: {
|
||||
documentNo: '',
|
||||
@@ -865,6 +902,14 @@ export default {
|
||||
summaryTotal() {
|
||||
return this.summaryFees.reduce((total, row) => total + Number(row.settlementAmount || 0), 0);
|
||||
},
|
||||
detailAmountAdjustSettlementAmount() {
|
||||
return Number(
|
||||
(
|
||||
Number(this.detailAmountAdjust.originalAmount || 0) +
|
||||
Number(this.detailAmountAdjust.adjustAmount || 0)
|
||||
).toFixed(2)
|
||||
);
|
||||
},
|
||||
filteredDetails() {
|
||||
return this.details.filter(row =>
|
||||
Object.entries(this.appliedDetailQuery).every(([field, keyword]) => {
|
||||
@@ -901,7 +946,14 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async initialize() {
|
||||
const newRecordAudit = this.recordId
|
||||
? null
|
||||
: {
|
||||
createUserName: this.userInfo?.realName || this.userInfo?.userName || '',
|
||||
createTime: this.$dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
};
|
||||
this.form = createFormalSettlementForm();
|
||||
if (newRecordAudit) Object.assign(this.form, newRecordAudit);
|
||||
this.sources = [];
|
||||
this.details = [];
|
||||
this.summaryFees = [];
|
||||
@@ -909,9 +961,16 @@ export default {
|
||||
this.adjustments = [];
|
||||
this.changeRecords = [];
|
||||
this.attachments = [];
|
||||
this.selectedAttachmentRows = [];
|
||||
this.invoices = [];
|
||||
this.invoiceClaimNo = '';
|
||||
this.attachmentUploadFiles = [];
|
||||
this.detailAmountAdjust = {
|
||||
visible: false,
|
||||
row: null,
|
||||
originalAmount: 0,
|
||||
adjustAmount: 0,
|
||||
};
|
||||
this.detailCollapsed = false;
|
||||
this.resetDetailQuery(false);
|
||||
await Promise.all([
|
||||
@@ -923,6 +982,7 @@ export default {
|
||||
if (!this.recordId) {
|
||||
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
|
||||
if (this.initialData) await this.applyInitialData();
|
||||
Object.assign(this.form, newRecordAudit);
|
||||
await this.refreshFormalSettlementNo();
|
||||
return;
|
||||
}
|
||||
@@ -945,6 +1005,7 @@ export default {
|
||||
this.adjustments = data.adjustments || [];
|
||||
this.changeRecords = data.changeRecords || [];
|
||||
this.attachments = this.parseAttachments(data.attachmentsJson);
|
||||
this.selectedAttachmentRows = [];
|
||||
this.invoices = data.invoices || [];
|
||||
this.sortAttachments();
|
||||
this.contracts = this.allContracts.filter(
|
||||
@@ -1272,7 +1333,8 @@ export default {
|
||||
this.buildSummaryFeesFromDetails();
|
||||
this.detailCandidate.visible = false;
|
||||
},
|
||||
removeDetail(row) {
|
||||
async removeDetail(row) {
|
||||
await this.$confirm('确认将该明细踢出正式结算单?', '提示', { type: 'warning' });
|
||||
const index = this.details.indexOf(row);
|
||||
if (index < 0) return;
|
||||
this.details.splice(index, 1);
|
||||
@@ -1309,16 +1371,26 @@ export default {
|
||||
isSummaryMoney(prop) {
|
||||
return ['originalAmount', 'adjustAmount', 'settlementAmount'].includes(prop);
|
||||
},
|
||||
isNegativeAdjustedAmount(row, prop) {
|
||||
return (
|
||||
Number(row.adjustAmount || 0) < 0 && ['adjustAmount', 'settlementAmount'].includes(prop)
|
||||
);
|
||||
},
|
||||
recalculateSummaryRow(row) {
|
||||
row.settlementAmount = Number(row.originalAmount || 0) + Number(row.adjustAmount || 0);
|
||||
},
|
||||
getSummarySums({ columns, data }) {
|
||||
const sumProps = ['originalAmount', 'adjustAmount', 'settlementAmount'];
|
||||
const hasNegativeAdjustment = data.some(row => Number(row.adjustAmount || 0) < 0);
|
||||
return columns.map((column, index) => {
|
||||
if (index === 0) return '合计';
|
||||
if (!sumProps.includes(column.property)) return '';
|
||||
const total = data.reduce((sum, row) => sum + Number(row[column.property] || 0), 0);
|
||||
return this.formatMoney(total);
|
||||
const totalText = this.formatMoney(total);
|
||||
if (column.property === 'settlementAmount' && hasNegativeAdjustment) {
|
||||
return h('span', { style: { color: 'var(--el-color-danger)' } }, totalText);
|
||||
}
|
||||
return totalText;
|
||||
});
|
||||
},
|
||||
buildSummaryFeesFromDetails() {
|
||||
@@ -1406,6 +1478,20 @@ export default {
|
||||
);
|
||||
},
|
||||
async openAdjustDialog(row) {
|
||||
if (!row.formalSettlementId) {
|
||||
const originalAmount = Number(
|
||||
row.originalAmount ??
|
||||
row.totalAmount ??
|
||||
Number(row.settlementAmountTax || 0) - Number(row.adjustAmount || 0)
|
||||
);
|
||||
this.detailAmountAdjust = {
|
||||
visible: true,
|
||||
row,
|
||||
originalAmount: Number(originalAmount.toFixed(2)),
|
||||
adjustAmount: Number(row.adjustAmount || 0),
|
||||
};
|
||||
return;
|
||||
}
|
||||
this.adjust = {
|
||||
visible: true,
|
||||
loading: true,
|
||||
@@ -1425,6 +1511,20 @@ export default {
|
||||
this.adjust.loading = false;
|
||||
}
|
||||
},
|
||||
confirmDetailAmountAdjustment() {
|
||||
const settlementAmount = this.detailAmountAdjustSettlementAmount;
|
||||
if (!Number.isFinite(settlementAmount) || settlementAmount < 0) {
|
||||
return this.$message.warning('调整后的结算金额不能小于0');
|
||||
}
|
||||
const row = this.detailAmountAdjust.row;
|
||||
if (!row) return;
|
||||
row.originalAmount = this.detailAmountAdjust.originalAmount;
|
||||
row.adjustAmount = Number(this.detailAmountAdjust.adjustAmount || 0);
|
||||
row.settlementAmountTax = settlementAmount;
|
||||
row.pendingDetailAdjustment = true;
|
||||
this.buildSummaryFeesFromDetails();
|
||||
this.detailAmountAdjust.visible = false;
|
||||
},
|
||||
async saveAdjustment() {
|
||||
if (!this.adjust.reason.trim()) return this.$message.warning('请输入调整原因');
|
||||
this.adjust.saving = true;
|
||||
@@ -1586,6 +1686,17 @@ export default {
|
||||
settlementType: this.form.settlementType,
|
||||
sourcePreSettlementIds: this.form.sourcePreSettlementIds,
|
||||
sourceDetailIds: this.form.sourceDetailIds,
|
||||
detailAdjustments: this.details
|
||||
.filter(row => row.pendingDetailAdjustment)
|
||||
.map(row => ({
|
||||
sourcePreSettlementDetailId:
|
||||
row.sourcePreSettlementDetailId ||
|
||||
(row.sourcePreSettlementId ? row.id : undefined),
|
||||
sourceDetailId: row.sourcePreSettlementId
|
||||
? undefined
|
||||
: row.sourceDetailId || row.id,
|
||||
adjustAmount: Number(row.adjustAmount || 0),
|
||||
})),
|
||||
exchangeRateDate: this.form.exchangeRateDate,
|
||||
exchangeRate: this.form.exchangeRate,
|
||||
summaryFees: this.summaryFees
|
||||
@@ -1639,6 +1750,12 @@ export default {
|
||||
const quantity = Number(value);
|
||||
return Number.isFinite(quantity) ? quantity.toFixed(2) : '-';
|
||||
},
|
||||
formatDetailTransportQuantity(value) {
|
||||
return this.readonly && Number(value) === -1 ? '-' : this.formatQuantity(value);
|
||||
},
|
||||
formatDetailMileage(value) {
|
||||
return this.readonly && Number(value) === -1 ? '-' : this.displayValue(value);
|
||||
},
|
||||
async loadTransportTypeOptions() {
|
||||
const { data } = await getDictionary({ code: 'transport_type' });
|
||||
this.transportTypeOptions = data?.data || [];
|
||||
@@ -1736,12 +1853,46 @@ export default {
|
||||
});
|
||||
this.sortAttachments();
|
||||
},
|
||||
handleAttachmentSelectionChange(rows) {
|
||||
this.selectedAttachmentRows = rows || [];
|
||||
},
|
||||
removeAttachment(index) {
|
||||
this.attachments.splice(index, 1);
|
||||
this.selectedAttachmentRows = this.selectedAttachmentRows.filter(file =>
|
||||
this.attachments.includes(file)
|
||||
);
|
||||
},
|
||||
getAttachmentUrl(file = {}) {
|
||||
return file.url || file.link || file.src || file.domain || '';
|
||||
},
|
||||
getAttachmentName(file = {}) {
|
||||
return (
|
||||
file.originalName || file.name || this.getAttachmentFileName(this.getAttachmentUrl(file))
|
||||
);
|
||||
},
|
||||
downloadAttachment(file) {
|
||||
const url = this.getAttachmentUrl(file);
|
||||
if (!url) {
|
||||
this.$message.warning('附件地址为空,无法下载');
|
||||
return;
|
||||
}
|
||||
downloadFileByUrl(url, this.getAttachmentName(file) || '附件');
|
||||
},
|
||||
downloadAttachments() {
|
||||
const files = this.selectedAttachmentRows.length
|
||||
? this.selectedAttachmentRows
|
||||
: this.attachments;
|
||||
const downloadableFiles = files.filter(file => this.getAttachmentUrl(file));
|
||||
if (!downloadableFiles.length) {
|
||||
this.$message.warning(
|
||||
this.selectedAttachmentRows.length ? '所选附件无法下载' : '暂无可下载附件'
|
||||
);
|
||||
return;
|
||||
}
|
||||
downloadableFiles.forEach((file, index) => {
|
||||
window.setTimeout(() => this.downloadAttachment(file), index * 200);
|
||||
});
|
||||
},
|
||||
previewAttachment(file) {
|
||||
const url = this.getAttachmentUrl(file);
|
||||
if (!url) return this.$message.warning('附件地址为空,无法预览');
|
||||
@@ -1844,7 +1995,7 @@ export default {
|
||||
}
|
||||
.formal-editor__detail-filter-actions {
|
||||
grid-column: 1 / -1;
|
||||
justify-self: start;
|
||||
justify-self: end;
|
||||
}
|
||||
.formal-editor__page-actions {
|
||||
display: flex;
|
||||
@@ -1892,12 +2043,20 @@ export default {
|
||||
.formal-editor__adjust-reason {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.formal-editor__amount-adjust :deep(.el-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
.formal-editor__attachment-missing {
|
||||
margin-left: 12px;
|
||||
color: var(--el-color-danger);
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
}
|
||||
.formal-editor__attachment-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.formal-editor__attachment-upload {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
@@ -77,9 +77,10 @@
|
||||
>{{ displayValue(row[column.prop]) }}</el-tag
|
||||
>
|
||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop], row.currency) }}</span>
|
||||
<span v-else-if="column.prop === 'invoiceStatusName'">{{
|
||||
invoiceName(row.invoiceStatus)
|
||||
}}</span>
|
||||
<span
|
||||
v-else-if="column.prop === 'invoiceStatusName'"
|
||||
:class="['fs-table-panel__invoice-status', `is-${row.invoiceStatus || 'unreceived'}`]"
|
||||
>{{ invoiceName(row.invoiceStatus, row.settlementType) }}</span>
|
||||
<span v-else-if="column.prop === 'paymentStatusName'">{{
|
||||
paymentName(row.paymentStatus)
|
||||
}}</span>
|
||||
@@ -120,7 +121,7 @@
|
||||
<script>
|
||||
import { Refresh } from '@element-plus/icons-vue';
|
||||
import {
|
||||
invoiceStatusOptions,
|
||||
invoiceStatusName,
|
||||
kingdeeSyncStatusOptions,
|
||||
paymentStatusOptions,
|
||||
} from '@/option/settlement/formalSettlementSearch';
|
||||
@@ -210,8 +211,8 @@ export default {
|
||||
''
|
||||
);
|
||||
},
|
||||
invoiceName(value) {
|
||||
return invoiceStatusOptions.find(item => item.value === value)?.label || value || '-';
|
||||
invoiceName(value, settlementType) {
|
||||
return invoiceStatusName(value, settlementType);
|
||||
},
|
||||
paymentName(value) {
|
||||
return paymentStatusOptions.find(item => item.value === value)?.label || value || '-';
|
||||
@@ -247,6 +248,17 @@ export default {
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.fs-table-panel__invoice-status {
|
||||
&.is-unreceived {
|
||||
color: #f56c6c;
|
||||
}
|
||||
&.is-partial {
|
||||
color: #e6a23c;
|
||||
}
|
||||
&.is-completed {
|
||||
color: #67c23a;
|
||||
}
|
||||
}
|
||||
.fs-table-panel__pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
v-else-if="field.type === 'number'"
|
||||
v-model="form[field.prop]"
|
||||
:min="0"
|
||||
:precision="6"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
:disabled="!editable || form.currency === 'RMB'"
|
||||
@change="recalculateLocalAmount"
|
||||
@@ -104,10 +104,10 @@
|
||||
@change="handleManualFeeTypeChange(row)"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in feeOptions"
|
||||
:key="item.feeType"
|
||||
:label="item.feeTypeName || item.feeType"
|
||||
:value="item.feeType"
|
||||
v-for="item in feeCategoryOptions"
|
||||
:key="item.id || item.dictValue"
|
||||
:label="item.dictKey || item.dictValue"
|
||||
:value="item.dictValue"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
@@ -899,14 +899,25 @@ export default {
|
||||
currency: first.currency || 'RMB',
|
||||
localCurrency: first.localCurrency || 'RMB',
|
||||
});
|
||||
this.details = rows.map(row => ({
|
||||
this.details = rows.map(row => this.normalizeSourceDetail(row));
|
||||
this.buildSummaryFeesFromDetails();
|
||||
},
|
||||
normalizeSourceDetail(row) {
|
||||
const originalAmount =
|
||||
row.originalAmount ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount ?? 0;
|
||||
return {
|
||||
...row,
|
||||
sourceDetailId: row.sourceDetailId || row.id,
|
||||
originalAmount,
|
||||
adjustAmount: row.adjustAmount ?? 0,
|
||||
settlementAmountTax:
|
||||
row.settlementAmountTax ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount,
|
||||
row.settlementAmountTax ??
|
||||
row.totalAmount ??
|
||||
row.afterAmount ??
|
||||
row.settlementAmount ??
|
||||
originalAmount,
|
||||
feeItemsJson: row.feeItemsJson || JSON.stringify(row.feeItems || {}),
|
||||
}));
|
||||
this.buildSummaryFeesFromDetails();
|
||||
};
|
||||
},
|
||||
resetEditor() {
|
||||
this.form = emptyPreSettlementForm();
|
||||
@@ -1054,7 +1065,7 @@ export default {
|
||||
sourceDetailIds: this.details.map(row => row.sourceDetailId || row.id),
|
||||
summaryFees: this.summaryFees.map(row => ({
|
||||
id: row.id || undefined,
|
||||
feeType: row.feeType || '',
|
||||
feeType: this.feeCategoryValue(row.feeType),
|
||||
feeItem: row.feeItem,
|
||||
originalAmount: Number(row.originalAmount || 0),
|
||||
adjustAmount: Number(row.adjustAmount || 0),
|
||||
@@ -1079,14 +1090,25 @@ export default {
|
||||
if (!this.manualFeeItems(row.feeType).includes(row.feeItem)) row.feeItem = '';
|
||||
},
|
||||
manualFeeItems(feeType) {
|
||||
return this.feeOptions.find(item => item.feeType === feeType)?.feeItems || [];
|
||||
return (
|
||||
this.feeOptions.find(
|
||||
item => this.feeCategoryValue(item.feeType) === this.feeCategoryValue(feeType)
|
||||
)?.feeItems || []
|
||||
);
|
||||
},
|
||||
feeCategoryValue(value) {
|
||||
if (value === undefined || value === null || value === '') return '';
|
||||
const option = this.feeCategoryOptions.find(
|
||||
item => String(item.dictKey) === String(value) || String(item.dictValue) === String(value)
|
||||
);
|
||||
return option?.dictValue || value;
|
||||
},
|
||||
feeCategoryName(value) {
|
||||
if (value === undefined || value === null || value === '') return '';
|
||||
const option = this.feeCategoryOptions.find(
|
||||
item => String(item.dictKey) === String(value) || String(item.dictValue) === String(value)
|
||||
);
|
||||
return option?.dictValue || value;
|
||||
return option?.dictKey || value;
|
||||
},
|
||||
transportTypeName(value) {
|
||||
if (value === undefined || value === null || value === '') return '';
|
||||
@@ -1124,7 +1146,7 @@ export default {
|
||||
const feeOption = this.feeOptions.find(item =>
|
||||
(item.feeItems || []).some(name => String(name) === String(feeItem))
|
||||
);
|
||||
const feeType = feeOption?.feeType || fallbackFeeType || '';
|
||||
const feeType = this.feeCategoryValue(feeOption?.feeType || fallbackFeeType);
|
||||
const key = `${feeType}\u0000${feeItem}`;
|
||||
const current = summaryMap.get(key) || {
|
||||
id: '',
|
||||
@@ -1239,7 +1261,7 @@ export default {
|
||||
const existingIds = new Set(this.details.map(row => String(row.sourceDetailId || row.id)));
|
||||
this.candidateSelection.forEach(row => {
|
||||
if (!existingIds.has(String(row.id))) {
|
||||
this.details.push({ ...row, sourceDetailId: row.id });
|
||||
this.details.push(this.normalizeSourceDetail(row));
|
||||
}
|
||||
});
|
||||
this.candidateDialog.confirming = true;
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
? settlementTypeName
|
||||
: field.prop === 'formalSettlementId'
|
||||
? form.formalSettlementNo || form.formalSettlementId
|
||||
: field.prop === 'createTime'
|
||||
? formatCreateDate(form[field.prop])
|
||||
: form[field.prop]
|
||||
)
|
||||
}}</span></el-form-item
|
||||
@@ -68,23 +70,55 @@
|
||||
v-if="editable && form.formalSettlementId"
|
||||
type="primary"
|
||||
plain
|
||||
@click="openFeeDialog"
|
||||
@click="addFeeRow"
|
||||
>添加费用</el-button
|
||||
>
|
||||
</template>
|
||||
<el-table :data="details" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" /><el-table-column
|
||||
prop="feeType"
|
||||
label="费用类型"
|
||||
min-width="150"
|
||||
align="center"
|
||||
/><el-table-column
|
||||
prop="feeItem"
|
||||
label="费用项目"
|
||||
min-width="180"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/><el-table-column
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column prop="feeType" label="费用类型" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-if="editable && row.manualFlag === 1"
|
||||
v-model="row.feeType"
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
clearable
|
||||
placeholder="请选择或输入"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in feeOptions"
|
||||
:key="item.feeType"
|
||||
:label="item.feeTypeName || item.feeType"
|
||||
:value="item.feeType"
|
||||
/>
|
||||
</el-select>
|
||||
<span v-else>{{ displayValue(feeTypeName(row.feeType)) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="feeItem" label="费用项目" min-width="200" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-if="editable && row.manualFlag === 1"
|
||||
v-model="row.feeItem"
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
clearable
|
||||
placeholder="请选择或输入"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in feeItemOptions(row.feeType)"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
<span v-else>{{ displayValue(row.feeItem) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="originalAmountTax"
|
||||
label="原金额(含税)"
|
||||
min-width="145"
|
||||
@@ -98,7 +132,9 @@
|
||||
><el-input-number
|
||||
v-if="editable"
|
||||
v-model="row.adjustmentAmountTax"
|
||||
:class="{ negative: Number(row.adjustmentAmountTax) < 0 }"
|
||||
:precision="2"
|
||||
:step="0.01"
|
||||
:controls="false"
|
||||
@change="recalculate"
|
||||
/><span v-else :class="{ negative: Number(row.adjustmentAmountTax) < 0 }">{{
|
||||
@@ -134,7 +170,7 @@
|
||||
></template
|
||||
></el-table-column
|
||||
>
|
||||
<template #empty><el-empty description="请选择调整费用明细" /></template>
|
||||
<template #empty><el-empty description="暂无调整费用" /></template>
|
||||
</el-table>
|
||||
<div class="settlement-adjustment-editor__summary">
|
||||
调整金额合计:<strong :class="{ negative: Number(form.adjustmentAmount) < 0 }">{{
|
||||
@@ -210,53 +246,19 @@
|
||||
</div>
|
||||
<template #footer
|
||||
><el-button @click="visible = false">取消</el-button
|
||||
><el-button
|
||||
v-if="editable"
|
||||
type="primary"
|
||||
plain
|
||||
:loading="syncing"
|
||||
:disabled="!form.formalSettlementId"
|
||||
@click="handleSync"
|
||||
>同步</el-button
|
||||
><el-button v-if="editable" type="primary" :loading="saving" @click="handleSave"
|
||||
>保存</el-button
|
||||
></template
|
||||
>
|
||||
|
||||
<el-dialog v-model="feeDialog.visible" title="选择调整费用" width="88%" append-to-body>
|
||||
<el-table
|
||||
ref="feeTable"
|
||||
:data="feeRows"
|
||||
border
|
||||
@selection-change="feeDialog.selected = $event"
|
||||
><el-table-column type="selection" width="52" align="center" /><el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
width="64"
|
||||
align="center"
|
||||
/><el-table-column
|
||||
prop="documentNo"
|
||||
label="单据号"
|
||||
min-width="150"
|
||||
align="center"
|
||||
/><el-table-column
|
||||
prop="feeType"
|
||||
label="费用类型"
|
||||
min-width="140"
|
||||
align="center"
|
||||
/><el-table-column
|
||||
prop="feeItem"
|
||||
label="费用项目"
|
||||
min-width="180"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/><el-table-column
|
||||
prop="originalAmountTax"
|
||||
label="原金额(含税)"
|
||||
min-width="145"
|
||||
align="center"
|
||||
><template #default="{ row }">{{
|
||||
formatMoney(row.originalAmountTax)
|
||||
}}</template></el-table-column
|
||||
></el-table
|
||||
>
|
||||
<template #footer
|
||||
><el-button @click="feeDialog.visible = false">取消</el-button
|
||||
><el-button type="primary" @click="confirmFees">确定</el-button></template
|
||||
>
|
||||
</el-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
@@ -274,6 +276,7 @@ export default {
|
||||
data: () => ({
|
||||
loading: false,
|
||||
saving: false,
|
||||
syncing: false,
|
||||
form: createSettlementAdjustmentForm(),
|
||||
fields: settlementAdjustmentFormFields,
|
||||
details: [],
|
||||
@@ -296,11 +299,10 @@ export default {
|
||||
'zip',
|
||||
],
|
||||
candidates: [],
|
||||
feeRows: [],
|
||||
feeOptions: [],
|
||||
rules: {
|
||||
formalSettlementId: [{ required: true, message: '请选择关联正式结算单', trigger: 'change' }],
|
||||
},
|
||||
feeDialog: { visible: false, selected: [] },
|
||||
}),
|
||||
computed: {
|
||||
visible: {
|
||||
@@ -333,22 +335,29 @@ export default {
|
||||
methods: {
|
||||
async initialize() {
|
||||
this.form = createSettlementAdjustmentForm();
|
||||
const userInfo = this.$store.getters.userInfo || {};
|
||||
this.form.createTime = this.$dayjs().format('YYYY-MM-DD');
|
||||
this.form.createUserName = userInfo.realName || userInfo.userName || userInfo.account || '';
|
||||
this.details = [];
|
||||
this.attachments = [];
|
||||
this.selectedAttachments = [];
|
||||
this.candidates = [];
|
||||
this.feeRows = [];
|
||||
this.feeOptions = [];
|
||||
if (!this.recordId) {
|
||||
await this.loadFormalSettlements('');
|
||||
await Promise.all([this.loadFormalSettlements(''), this.loadFeeOptions()]);
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await api.getDetail(this.recordId);
|
||||
const [response] = await Promise.all([api.getDetail(this.recordId), this.loadFeeOptions()]);
|
||||
const data = response?.data?.data || response?.data || response || {};
|
||||
this.form = { ...createSettlementAdjustmentForm(), ...data };
|
||||
this.details = (data.details || []).map(item => ({
|
||||
...item,
|
||||
manualFlag:
|
||||
item.formalSettlementDetailId == null && item.formalSettlementDetailFeeId == null
|
||||
? 1
|
||||
: 0,
|
||||
adjustmentAmountTax: Number(item.adjustmentAmountTax || 0),
|
||||
adjustmentAmountNoTax:
|
||||
item.adjustmentAmountNoTax == null ? null : Number(item.adjustmentAmountNoTax),
|
||||
@@ -363,6 +372,10 @@ export default {
|
||||
const { data } = await api.getFormalSettlements(keyword);
|
||||
this.candidates = data?.data || data || [];
|
||||
},
|
||||
async loadFeeOptions() {
|
||||
const { data } = await api.getFeeOptions();
|
||||
this.feeOptions = data?.data || data || [];
|
||||
},
|
||||
async handleFormalChange(id) {
|
||||
if (!id) {
|
||||
Object.assign(this.form, {
|
||||
@@ -397,6 +410,7 @@ export default {
|
||||
if (String(this.form.formalSettlementId) !== String(formalSettlementId)) return;
|
||||
this.details = (data?.data || data || []).map(fee => ({
|
||||
...fee,
|
||||
manualFlag: 0,
|
||||
originalAmountTax: Number(fee.originalAmountTax || 0),
|
||||
adjustmentAmountTax: 0,
|
||||
adjustmentAmountNoTax: null,
|
||||
@@ -407,25 +421,28 @@ export default {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async openFeeDialog() {
|
||||
const { data } = await api.getFormalDetails(this.form.formalSettlementId);
|
||||
const used = new Set(this.details.map(item => String(item.formalSettlementDetailFeeId)));
|
||||
this.feeRows = (data?.data || data || []).filter(
|
||||
item => !used.has(String(item.formalSettlementDetailFeeId))
|
||||
);
|
||||
this.feeDialog = { visible: true, selected: [] };
|
||||
},
|
||||
confirmFees() {
|
||||
this.feeDialog.selected.forEach(item =>
|
||||
addFeeRow() {
|
||||
this.details.push({
|
||||
...item,
|
||||
formalSettlementDetailId: null,
|
||||
formalSettlementDetailFeeId: null,
|
||||
feeType: '',
|
||||
feeItem: '',
|
||||
originalAmountTax: 0,
|
||||
adjustmentAmountTax: 0,
|
||||
adjustmentAmountNoTax: null,
|
||||
remark: '',
|
||||
})
|
||||
manualFlag: 1,
|
||||
});
|
||||
},
|
||||
feeTypeName(value) {
|
||||
return (
|
||||
this.feeOptions.find(item => String(item.feeType) === String(value))?.feeTypeName || value
|
||||
);
|
||||
},
|
||||
feeItemOptions(feeType) {
|
||||
return (
|
||||
this.feeOptions.find(item => String(item.feeType) === String(feeType))?.feeItems || []
|
||||
);
|
||||
this.recalculate();
|
||||
this.feeDialog.visible = false;
|
||||
},
|
||||
recalculate() {
|
||||
const total = this.details.reduce(
|
||||
@@ -437,13 +454,65 @@ export default {
|
||||
(Number(this.form.originalSettlementAmount || 0) + total).toFixed(2)
|
||||
);
|
||||
},
|
||||
async handleSync() {
|
||||
const formalSettlementId = this.form.formalSettlementId;
|
||||
if (!formalSettlementId) return this.$message.warning('请先选择正式结算单');
|
||||
this.syncing = true;
|
||||
try {
|
||||
const [candidateResponse, detailResponse] = await Promise.all([
|
||||
api.getFormalSettlements(this.form.formalSettlementNo || ''),
|
||||
api.getFormalDetails(formalSettlementId),
|
||||
]);
|
||||
if (String(this.form.formalSettlementId) !== String(formalSettlementId)) return;
|
||||
const candidateData = candidateResponse?.data?.data || candidateResponse?.data || [];
|
||||
const formalSettlements = Array.isArray(candidateData) ? candidateData : [];
|
||||
const formalSettlement = formalSettlements.find(
|
||||
item => String(item.id) === String(formalSettlementId)
|
||||
);
|
||||
if (formalSettlement) {
|
||||
const { id, ...formalInformation } = formalSettlement;
|
||||
Object.assign(this.form, formalInformation, {
|
||||
formalSettlementId: id,
|
||||
formalSettlementNo: formalSettlement.formalSettlementNo,
|
||||
originalSettlementAmount: Number(formalSettlement.settlementAmount || 0),
|
||||
settlementTypeName: formalSettlement.settlementTypeName,
|
||||
});
|
||||
}
|
||||
const existingDetailMap = new Map(
|
||||
this.details
|
||||
.filter(item => item.formalSettlementDetailFeeId != null)
|
||||
.map(item => [String(item.formalSettlementDetailFeeId), item])
|
||||
);
|
||||
const manualDetails = this.details.filter(item => Number(item.manualFlag) === 1);
|
||||
const detailData = detailResponse?.data?.data || detailResponse?.data || [];
|
||||
const formalDetails = Array.isArray(detailData) ? detailData : [];
|
||||
this.details = formalDetails
|
||||
.map(fee => {
|
||||
const existing = existingDetailMap.get(String(fee.formalSettlementDetailFeeId));
|
||||
return {
|
||||
...fee,
|
||||
manualFlag: 0,
|
||||
originalAmountTax: Number(fee.originalAmountTax || 0),
|
||||
adjustmentAmountTax: Number(existing?.adjustmentAmountTax || 0),
|
||||
adjustmentAmountNoTax:
|
||||
existing?.adjustmentAmountNoTax == null
|
||||
? null
|
||||
: Number(existing.adjustmentAmountNoTax),
|
||||
remark: existing?.remark || '',
|
||||
};
|
||||
})
|
||||
.concat(manualDetails);
|
||||
this.recalculate();
|
||||
this.$message.success('同步成功');
|
||||
} finally {
|
||||
this.syncing = false;
|
||||
}
|
||||
},
|
||||
async handleSave() {
|
||||
await this.$refs.formRef.validate();
|
||||
if (!this.details.length) return this.$message.warning('请至少添加一条调整费用');
|
||||
this.saving = true;
|
||||
try {
|
||||
await api.save({
|
||||
...(this.recordId ? { id: this.form.id } : {}),
|
||||
const { data } = await api.save({
|
||||
id: this.form.id || undefined,
|
||||
formalSettlementId: this.form.formalSettlementId,
|
||||
attachmentsJson: JSON.stringify(this.attachments || []),
|
||||
remark: this.form.remark,
|
||||
@@ -452,11 +521,13 @@ export default {
|
||||
formalSettlementDetailFeeId: item.formalSettlementDetailFeeId,
|
||||
feeType: item.feeType,
|
||||
feeItem: item.feeItem,
|
||||
originalAmountTax: item.originalAmountTax,
|
||||
adjustmentAmountTax: item.adjustmentAmountTax,
|
||||
adjustmentAmountNoTax: item.adjustmentAmountNoTax,
|
||||
remark: item.remark,
|
||||
})),
|
||||
});
|
||||
this.form.id = data?.data || data || this.form.id;
|
||||
this.$message.success('保存成功');
|
||||
this.visible = false;
|
||||
this.$emit('success');
|
||||
@@ -470,6 +541,11 @@ export default {
|
||||
formatMoney(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
},
|
||||
formatCreateDate(value) {
|
||||
if (!value) return '';
|
||||
const date = this.$dayjs(value);
|
||||
return date.isValid() ? date.format('YYYY-MM-DD') : value;
|
||||
},
|
||||
handleAttachmentChange(files) {
|
||||
const userInfo = this.$store.getters.userInfo || {};
|
||||
const uploadUserName = userInfo.realName || userInfo.userName || '';
|
||||
@@ -573,4 +649,7 @@ export default {
|
||||
.negative {
|
||||
color: #f56c6c;
|
||||
}
|
||||
.settlement-adjustment-editor :deep(.el-input-number.negative .el-input__inner) {
|
||||
color: #f56c6c;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -164,8 +164,8 @@ import { createSettlementTransfer } from '@/utils/settlement-transfer';
|
||||
import * as api from '@/api/settlement/formalSettlement';
|
||||
import * as paymentApi from '@/api/payment/paymentApplication';
|
||||
import {
|
||||
invoiceStatusName,
|
||||
formalSettlementSearchFields,
|
||||
invoiceStatusOptions,
|
||||
paymentStatusOptions,
|
||||
} from '@/option/settlement/formalSettlementSearch';
|
||||
import { formalSettlementTableColumns } from '@/option/settlement/formalSettlementTable';
|
||||
@@ -505,7 +505,7 @@ export default {
|
||||
原币结算金额: item.settlementAmount,
|
||||
本位币结算金额: item.localSettlementAmount,
|
||||
结算汇率: item.exchangeRate,
|
||||
发票状态: this.invoiceName(item.invoiceStatus),
|
||||
发票状态: this.invoiceName(item.invoiceStatus, item.settlementType),
|
||||
收付款状态: this.paymentName(item.paymentStatus),
|
||||
审核状态: item.approvalStatusName,
|
||||
金蝶单据号: item.kingdeeBillNo,
|
||||
@@ -550,8 +550,8 @@ export default {
|
||||
''
|
||||
);
|
||||
},
|
||||
invoiceName(value) {
|
||||
return invoiceStatusOptions.find(item => item.value === value)?.label || value || '-';
|
||||
invoiceName(value, settlementType) {
|
||||
return invoiceStatusName(value, settlementType);
|
||||
},
|
||||
paymentName(value) {
|
||||
return paymentStatusOptions.find(item => item.value === value)?.label || value || '-';
|
||||
|
||||
@@ -190,7 +190,7 @@
|
||||
<div
|
||||
class="settlement-detail-page__detail-panel-body settlement-detail-page__adjust-panel-body"
|
||||
>
|
||||
<div v-if="isReceivable" class="settlement-detail-page__adjust-toolbar">
|
||||
<div v-if="isPayable" class="settlement-detail-page__adjust-toolbar">
|
||||
<el-button type="primary" :disabled="adjustDialog.loading" @click="addAdjustFee">
|
||||
新增费用
|
||||
</el-button>
|
||||
@@ -209,18 +209,26 @@
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span
|
||||
v-if="row.manualFee && ['billingFactor', 'billingType'].includes(column.prop)"
|
||||
>
|
||||
-
|
||||
</span>
|
||||
<el-input
|
||||
v-else-if="column.prop === 'cargoName' && row.manualFee"
|
||||
<el-select
|
||||
v-if="column.prop === 'cargoName' && row.manualFee"
|
||||
v-model="row.cargoName"
|
||||
class="settlement-detail-page__adjust-control"
|
||||
:loading="commonCargoLoading"
|
||||
clearable
|
||||
maxlength="100"
|
||||
placeholder="请输入"
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
placeholder="请选择或输入"
|
||||
@visible-change="visible => visible && loadCommonCargoOptions()"
|
||||
@change="value => handleAdjustCargoNameChange(row, value)"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in manualCargoNameOptions(row)"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<span v-else-if="column.prop === 'cargoName'">
|
||||
{{ formatDetailCell(row, column.prop) }}
|
||||
</span>
|
||||
@@ -241,6 +249,56 @@
|
||||
<span v-else-if="column.prop === 'cargoType'">
|
||||
{{ formatDetailCell(row, column.prop) }}
|
||||
</span>
|
||||
<el-select
|
||||
v-else-if="column.prop === 'specification' && row.manualFee"
|
||||
v-model="row.specification"
|
||||
class="settlement-detail-page__adjust-control"
|
||||
:loading="commonCargoLoading"
|
||||
clearable
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
placeholder="请选择或输入"
|
||||
@visible-change="visible => visible && loadCommonCargoOptions()"
|
||||
@change="value => handleAdjustSpecificationChange(row, value)"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in manualSpecificationOptions(row)"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-else-if="column.prop === 'billingFactor' && row.manualFee"
|
||||
v-model="row.billingFactor"
|
||||
class="settlement-detail-page__adjust-control"
|
||||
clearable
|
||||
placeholder="请选择"
|
||||
@change="handleAdjustBillingFactorChange(row)"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in adjustBillingElements"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-else-if="column.prop === 'billingType' && row.manualFee"
|
||||
v-model="row.billingType"
|
||||
class="settlement-detail-page__adjust-control"
|
||||
:disabled="!row.billingFactor"
|
||||
clearable
|
||||
placeholder="请选择"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in adjustBillingTypes(row)"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-else-if="column.prop === 'priceUnit'"
|
||||
v-model="row.priceUnit"
|
||||
@@ -327,7 +385,12 @@
|
||||
</div>
|
||||
<footer class="settlement-detail-page__detail-panel-footer">
|
||||
<el-button @click="closeAdjustPanel">取消</el-button>
|
||||
<el-button type="primary" :loading="adjustDialog.submitting" @click="saveAdjustFee">
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="adjustDialog.submitting"
|
||||
:disabled="hasAdjustCalculationPending"
|
||||
@click="saveAdjustFee"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
</footer>
|
||||
@@ -392,7 +455,6 @@
|
||||
v-model="transferForm.settlementBillType"
|
||||
@change="handleTransferTypeChange"
|
||||
>
|
||||
<el-radio label="pre">预结算单</el-radio>
|
||||
<el-radio label="formal">正式结算单</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
@@ -621,6 +683,7 @@ import {
|
||||
} from '@/option/settlement/receivable-payable-detail';
|
||||
import * as api from '@/api/settlement/receivable-payable-detail';
|
||||
import { getList as getContractList } from '@/api/business/contract-manage';
|
||||
import { getList as getCommonCargoList } from '@/api/business/common-cargo';
|
||||
import { getContractOptions as getSettlementContractOptions } from '@/api/settlement/preSettlement';
|
||||
import { getList as getCargoTypeList } from '@/api/base/cargo-type';
|
||||
import { exportBlob } from '@/api/common';
|
||||
@@ -628,6 +691,26 @@ import { getDictionary } from '@/api/system/dictbiz';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { createSettlementTransfer } from '@/utils/settlement-transfer';
|
||||
|
||||
const ADJUST_BILLING_ELEMENTS = [
|
||||
'按重量',
|
||||
'按体积',
|
||||
'按车辆',
|
||||
'按里程',
|
||||
'按吨·公里',
|
||||
'固定金额(整单一口价)',
|
||||
'按数量',
|
||||
];
|
||||
|
||||
const ADJUST_BILLING_TYPE_MAP = {
|
||||
按重量: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
||||
按体积: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
||||
按车辆: ['固定单价'],
|
||||
按里程: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
||||
'按吨·公里': ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
||||
'固定金额(整单一口价)': ['固定一口价'],
|
||||
按数量: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
||||
};
|
||||
|
||||
export default {
|
||||
props: {
|
||||
settlementType: {
|
||||
@@ -667,6 +750,10 @@ export default {
|
||||
adjustRows: [],
|
||||
adjustDynamicFeeColumns: [],
|
||||
adjustTextProps: ['specification', 'model', 'billingFactor', 'billingType'],
|
||||
adjustBillingElements: ADJUST_BILLING_ELEMENTS,
|
||||
commonCargoOptions: [],
|
||||
commonCargoLoading: false,
|
||||
commonCargoRequest: null,
|
||||
cargoTypeOptions: [],
|
||||
cargoTypeFlatOptions: [],
|
||||
cargoTypeLoading: false,
|
||||
@@ -718,6 +805,12 @@ export default {
|
||||
isReceivable() {
|
||||
return this.settlementType === 'receivable';
|
||||
},
|
||||
isPayable() {
|
||||
return this.settlementType === 'payable';
|
||||
},
|
||||
hasAdjustCalculationPending() {
|
||||
return this.adjustRows.some(row => row.calculating);
|
||||
},
|
||||
visibleSearchFields() {
|
||||
return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 4);
|
||||
},
|
||||
@@ -725,7 +818,9 @@ export default {
|
||||
return [...feeDetailBaseColumns, ...this.dynamicFeeColumns, ...feeDetailTailColumns];
|
||||
},
|
||||
displayTableColumns() {
|
||||
const columns = [...this.tableColumns];
|
||||
const columns = this.tableColumns.filter(
|
||||
column => this.settlementType !== 'receivable' || column.prop !== 'preSettlementNo'
|
||||
);
|
||||
const totalIndex = columns.findIndex(column => column.prop === 'totalAmountText');
|
||||
const dynamicColumns = this.tableFeeItemNames.map((name, index) => ({
|
||||
label: name,
|
||||
@@ -760,6 +855,9 @@ export default {
|
||||
this.loadPriceUnitOptions();
|
||||
this.loadTable();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.clearAdjustCalculations();
|
||||
},
|
||||
methods: {
|
||||
async loadTransportTypeOptions() {
|
||||
const res = await getDictionary({ code: 'transport_type' });
|
||||
@@ -806,6 +904,82 @@ export default {
|
||||
});
|
||||
return this.priceUnitRequest;
|
||||
},
|
||||
loadCommonCargoOptions() {
|
||||
if (this.commonCargoOptions.length) return Promise.resolve(this.commonCargoOptions);
|
||||
if (this.commonCargoRequest) return this.commonCargoRequest;
|
||||
this.commonCargoLoading = true;
|
||||
this.commonCargoRequest = getCommonCargoList(1, 9999)
|
||||
.then(res => {
|
||||
this.commonCargoOptions = this.extractRecords(res);
|
||||
return this.commonCargoOptions;
|
||||
})
|
||||
.finally(() => {
|
||||
this.commonCargoLoading = false;
|
||||
this.commonCargoRequest = null;
|
||||
});
|
||||
return this.commonCargoRequest;
|
||||
},
|
||||
commonCargoMatches(row) {
|
||||
const cargoType = String(row.cargoType || '').trim();
|
||||
if (!cargoType) return this.commonCargoOptions;
|
||||
const matched = this.commonCargoOptions.filter(item =>
|
||||
[item.secondCargoTypeName, item.firstCargoTypeName, item.cargoType].some(
|
||||
value => String(value || '').trim() === cargoType
|
||||
)
|
||||
);
|
||||
return matched.length ? matched : this.commonCargoOptions;
|
||||
},
|
||||
manualCargoNameOptions(row) {
|
||||
const options = new Map();
|
||||
this.commonCargoMatches(row).forEach(item => {
|
||||
const value = String(item.cargoName || '').trim();
|
||||
if (value && !options.has(value)) options.set(value, { label: value, value });
|
||||
});
|
||||
return Array.from(options.values());
|
||||
},
|
||||
manualSpecificationOptions(row) {
|
||||
const cargoName = String(row.cargoName || '').trim();
|
||||
const records = cargoName
|
||||
? this.commonCargoMatches(row).filter(
|
||||
item => String(item.cargoName || '').trim() === cargoName
|
||||
)
|
||||
: this.commonCargoMatches(row);
|
||||
const options = new Map();
|
||||
records.forEach(item => {
|
||||
const value = String(item.specification || item.spec || '').trim();
|
||||
if (value && !options.has(value)) options.set(value, { label: value, value });
|
||||
});
|
||||
return Array.from(options.values());
|
||||
},
|
||||
handleAdjustCargoNameChange(row, value) {
|
||||
row.cargoName = String(value || '').trim();
|
||||
const cargo = this.commonCargoMatches(row).find(
|
||||
item => String(item.cargoName || '').trim() === row.cargoName
|
||||
);
|
||||
if (cargo) this.applyCommonCargoToAdjustRow(row, cargo);
|
||||
},
|
||||
handleAdjustSpecificationChange(row, value) {
|
||||
row.specification = String(value || '').trim();
|
||||
const cargo = this.commonCargoMatches(row).find(
|
||||
item =>
|
||||
String(item.cargoName || '').trim() === String(row.cargoName || '').trim() &&
|
||||
String(item.specification || item.spec || '').trim() === row.specification
|
||||
);
|
||||
if (cargo) this.applyCommonCargoToAdjustRow(row, cargo);
|
||||
},
|
||||
applyCommonCargoToAdjustRow(row, cargo) {
|
||||
row.cargoName = cargo.cargoName || row.cargoName || '';
|
||||
row.specification = cargo.specification || cargo.spec || row.specification || '';
|
||||
row.model = cargo.model || row.model || '';
|
||||
row.priceUnit = cargo.priceUnit || row.priceUnit || '';
|
||||
row.cargoType =
|
||||
cargo.secondCargoTypeName ||
|
||||
cargo.cargoType ||
|
||||
cargo.firstCargoTypeName ||
|
||||
row.cargoType ||
|
||||
'';
|
||||
row.cargoTypePath = this.resolveCargoTypePath(row.cargoType);
|
||||
},
|
||||
buildCargoTypeTree(cargoTypes = []) {
|
||||
const flatCargoTypes = [];
|
||||
const collectCargoTypes = list => {
|
||||
@@ -920,6 +1094,8 @@ export default {
|
||||
row.cargoTypePath = path;
|
||||
row.cargoType = labels.length ? labels[labels.length - 1] : '';
|
||||
row.cargoName = '';
|
||||
row.specification = '';
|
||||
row.model = '';
|
||||
},
|
||||
formatColumnValue(row, column) {
|
||||
if (column.prop === 'transportType') return this.transportTypeLabel(row[column.prop]);
|
||||
@@ -989,6 +1165,7 @@ export default {
|
||||
this.detailDialog.row = null;
|
||||
},
|
||||
closeAdjustPanel() {
|
||||
this.clearAdjustCalculations();
|
||||
this.adjustDialog.visible = false;
|
||||
this.adjustDialog.row = null;
|
||||
},
|
||||
@@ -1041,6 +1218,7 @@ export default {
|
||||
try {
|
||||
const [res] = await Promise.all([
|
||||
api.getFeeDetail(row.id),
|
||||
this.loadCommonCargoOptions(),
|
||||
this.loadCargoTypeOptions(),
|
||||
this.loadPriceUnitOptions(),
|
||||
]);
|
||||
@@ -1068,9 +1246,14 @@ export default {
|
||||
originalAmount: Number(item.originalAmount || 0),
|
||||
feeItems,
|
||||
dataSource:
|
||||
item.dataSource || (item.billingFactor === '手工调整' ? '手工录入' : '自动生成'),
|
||||
manualFee: item.dataSource === '手工录入' || item.billingFactor === '手工调整',
|
||||
item.dataSource || (item.billingFactor === '手工调整' ? '手动录入' : '自动生成'),
|
||||
manualFee:
|
||||
['手动录入', '手动添加', '手工录入'].includes(item.dataSource) ||
|
||||
item.billingFactor === '手工调整',
|
||||
cargoTypePath: this.resolveCargoTypePath(item.cargoType),
|
||||
calculating: false,
|
||||
adjustCalculateTimer: null,
|
||||
adjustCalculateVersion: 0,
|
||||
};
|
||||
this.recalculateAdjustRow(adjusted);
|
||||
return adjusted;
|
||||
@@ -1080,20 +1263,20 @@ export default {
|
||||
}
|
||||
},
|
||||
addAdjustFee() {
|
||||
if (!this.isReceivable) return;
|
||||
if (!this.isPayable) return;
|
||||
const feeItems = this.adjustDynamicFeeColumns.reduce((items, column) => {
|
||||
items[column.feeItemName] = 0;
|
||||
return items;
|
||||
}, {});
|
||||
this.adjustRows.push({
|
||||
dataSource: '手工录入',
|
||||
dataSource: '手动录入',
|
||||
cargoName: '',
|
||||
cargoType: '',
|
||||
cargoTypePath: [],
|
||||
specification: '',
|
||||
model: '',
|
||||
billingFactor: '-',
|
||||
billingType: '-',
|
||||
billingFactor: '',
|
||||
billingType: '',
|
||||
transportQuantity: '',
|
||||
priceUnit: '',
|
||||
unitPrice: '',
|
||||
@@ -1107,10 +1290,19 @@ export default {
|
||||
manualFee: true,
|
||||
remark: '',
|
||||
changeReason: '',
|
||||
calculating: false,
|
||||
adjustCalculateTimer: null,
|
||||
adjustCalculateVersion: 0,
|
||||
});
|
||||
},
|
||||
feeSourceLabel(value) {
|
||||
return value === '手工录入' ? '手工录入' : '自动生成';
|
||||
return ['手动录入', '手动添加', '手工录入'].includes(value) ? '手动录入' : '自动生成';
|
||||
},
|
||||
adjustBillingTypes(row) {
|
||||
return ADJUST_BILLING_TYPE_MAP[row.billingFactor] || [];
|
||||
},
|
||||
handleAdjustBillingFactorChange(row) {
|
||||
if (!this.adjustBillingTypes(row).includes(row.billingType)) row.billingType = '';
|
||||
},
|
||||
adjustTextMaxlength(prop) {
|
||||
return ['billingFactor', 'billingType'].includes(prop) ? 100 : 255;
|
||||
@@ -1131,6 +1323,10 @@ export default {
|
||||
},
|
||||
handleAdjustDecimalInput(row, prop, value, changedField) {
|
||||
row[prop] = this.normalizeAdjustDecimal(value);
|
||||
if (!row.manualFee && row.id && ['transportQuantity', 'mileage'].includes(prop)) {
|
||||
this.scheduleAdjustFeeCalculation(row);
|
||||
return;
|
||||
}
|
||||
this.recalculateAdjustRow(row, changedField);
|
||||
},
|
||||
handleAdjustFeeItemInput(row, feeItemName, value) {
|
||||
@@ -1155,6 +1351,50 @@ export default {
|
||||
);
|
||||
row.adjustAmount = Number((row.afterAmount - Number(row.originalAmount || 0)).toFixed(2));
|
||||
},
|
||||
scheduleAdjustFeeCalculation(row) {
|
||||
if (row.adjustCalculateTimer) clearTimeout(row.adjustCalculateTimer);
|
||||
row.adjustCalculateVersion += 1;
|
||||
row.calculating = true;
|
||||
const version = row.adjustCalculateVersion;
|
||||
row.adjustCalculateTimer = setTimeout(() => {
|
||||
row.adjustCalculateTimer = null;
|
||||
this.calculateAdjustRow(row, version);
|
||||
}, 300);
|
||||
},
|
||||
async calculateAdjustRow(row, version) {
|
||||
try {
|
||||
const res = await api.calculateAdjustedFee({
|
||||
detailId: this.adjustDialog.row?.id,
|
||||
feeId: row.id,
|
||||
transportQuantity: Number(row.transportQuantity || 0),
|
||||
mileage: Number(row.mileage || 0),
|
||||
freightAmount: Number(row.freightAmount || 0),
|
||||
feeItems: Object.fromEntries(
|
||||
Object.entries(row.feeItems || {}).map(([name, amount]) => [name, Number(amount || 0)])
|
||||
),
|
||||
});
|
||||
if (version !== row.adjustCalculateVersion || !this.adjustDialog.visible) return;
|
||||
const data = res.data?.data || res.data || res || {};
|
||||
row.freightAmount = Number(data.freightAmount || 0);
|
||||
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) {
|
||||
row.calculateError = error.message;
|
||||
} finally {
|
||||
if (version === row.adjustCalculateVersion) row.calculating = false;
|
||||
}
|
||||
},
|
||||
clearAdjustCalculations() {
|
||||
this.adjustRows.forEach(row => {
|
||||
if (row.adjustCalculateTimer) clearTimeout(row.adjustCalculateTimer);
|
||||
row.adjustCalculateTimer = null;
|
||||
row.adjustCalculateVersion += 1;
|
||||
row.calculating = false;
|
||||
});
|
||||
},
|
||||
isFreightFeeItem(name) {
|
||||
return String(name || '').includes('运费') || String(name || '').includes('运输费');
|
||||
},
|
||||
@@ -1163,15 +1403,20 @@ export default {
|
||||
this.$message.warning('没有可调整的费用明细');
|
||||
return;
|
||||
}
|
||||
if (this.hasAdjustCalculationPending) {
|
||||
this.$message.warning('费用正在重新计算,请稍候');
|
||||
return;
|
||||
}
|
||||
if (!this.validateManualAdjustRows()) return;
|
||||
this.adjustDialog.submitting = true;
|
||||
try {
|
||||
await api.adjustFee({
|
||||
detailId: this.adjustDialog.row.id,
|
||||
rows: this.adjustRows.map(row => ({
|
||||
id: row.id,
|
||||
cargoName: row.cargoName,
|
||||
cargoName: String(row.cargoName || '').trim(),
|
||||
cargoType: row.cargoType,
|
||||
specification: row.specification,
|
||||
specification: String(row.specification || '').trim(),
|
||||
model: row.model,
|
||||
billingFactor: row.billingFactor,
|
||||
billingType: row.billingType,
|
||||
@@ -1193,6 +1438,32 @@ export default {
|
||||
this.adjustDialog.submitting = false;
|
||||
}
|
||||
},
|
||||
validateManualAdjustRows() {
|
||||
for (const [index, row] of this.adjustRows.entries()) {
|
||||
if (!row.manualFee) continue;
|
||||
if (!String(row.cargoName || '').trim()) {
|
||||
this.$message.warning(`第${index + 1}行货物名称不能为空`);
|
||||
return false;
|
||||
}
|
||||
if (String(row.cargoName).trim().length > 100) {
|
||||
this.$message.warning(`第${index + 1}行货物名称不能超过100个字`);
|
||||
return false;
|
||||
}
|
||||
if (String(row.specification || '').trim().length > 255) {
|
||||
this.$message.warning(`第${index + 1}行规格不能超过255个字`);
|
||||
return false;
|
||||
}
|
||||
if (!ADJUST_BILLING_ELEMENTS.includes(row.billingFactor)) {
|
||||
this.$message.warning(`第${index + 1}行请选择计费要素`);
|
||||
return false;
|
||||
}
|
||||
if (!this.adjustBillingTypes(row).includes(row.billingType)) {
|
||||
this.$message.warning(`第${index + 1}行请选择计费要素对应的计费类型`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
async loadFeeDetail() {
|
||||
if (!this.detailDialog.row) return;
|
||||
this.detailDialog.loading = true;
|
||||
@@ -1841,6 +2112,7 @@ export default {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.settlement-detail-page__pagination {
|
||||
|
||||
Reference in New Issue
Block a user