fix bug
This commit is contained in:
@@ -13,7 +13,8 @@ export const getCandidates = (current, size, params) =>
|
|||||||
});
|
});
|
||||||
export const getContractOptions = (keyword, projectId) =>
|
export const getContractOptions = (keyword, projectId) =>
|
||||||
request({ url: `${baseUrl}/contract-options`, method: 'get', params: { keyword, projectId } });
|
request({ url: `${baseUrl}/contract-options`, method: 'get', params: { keyword, projectId } });
|
||||||
export const getNextNo = () => request({ url: `${baseUrl}/next-no`, method: 'get' });
|
export const getNextNo = settlementType =>
|
||||||
|
request({ url: `${baseUrl}/next-no`, method: 'get', params: { settlementType } });
|
||||||
export const getFeeOptions = () => request({ url: `${baseUrl}/fee-options`, method: 'get' });
|
export const getFeeOptions = () => request({ url: `${baseUrl}/fee-options`, method: 'get' });
|
||||||
export const getCandidateDetails = (current, size, params) =>
|
export const getCandidateDetails = (current, size, params) =>
|
||||||
request({
|
request({
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
export const formalSettlementTableColumns = [
|
export const formalSettlementTableColumns = [
|
||||||
{ label: '结算单号', prop: 'formalSettlementNo', minWidth: 160, link: true, fixed: 'left' },
|
{ label: '结算单号', prop: 'formalSettlementNo', minWidth: 160, link: true, fixed: 'left' },
|
||||||
{ label: '预结算单号', prop: 'preSettlementNos', minWidth: 190 },
|
|
||||||
{ label: '来源', prop: 'sourceType', minWidth: 110 },
|
{ label: '来源', prop: 'sourceType', minWidth: 110 },
|
||||||
{ label: '付款方', prop: 'payerName', minWidth: 150 },
|
{ label: '付款方', prop: 'payerName', minWidth: 150 },
|
||||||
{ label: '收款方', prop: 'payeeName', minWidth: 150 },
|
{ label: '收款方', prop: 'payeeName', minWidth: 150 },
|
||||||
@@ -12,13 +11,16 @@ export const formalSettlementTableColumns = [
|
|||||||
{ label: '本位币结算金额', prop: 'localSettlementAmount', minWidth: 155, money: true },
|
{ label: '本位币结算金额', prop: 'localSettlementAmount', minWidth: 155, money: true },
|
||||||
{ label: '结算汇率', prop: 'exchangeRate', minWidth: 110 },
|
{ label: '结算汇率', prop: 'exchangeRate', minWidth: 110 },
|
||||||
{ label: '申请付款金额(含预付)', prop: 'appliedPaymentAmount', minWidth: 185, money: true },
|
{ label: '申请付款金额(含预付)', prop: 'appliedPaymentAmount', minWidth: 185, money: true },
|
||||||
{ label: '已收/已付合计', prop: 'paidAmount', minWidth: 145, money: true },
|
{ label: '已收/已付合计(含预付)', prop: 'paidAmount', minWidth: 200, money: true },
|
||||||
|
{ label: '剩余可付金额', prop: 'remainingPayableAmount', minWidth: 145, money: true },
|
||||||
|
{ label: '发票金额', prop: 'invoiceAmount', minWidth: 130, money: true },
|
||||||
{ label: '发票状态', prop: 'invoiceStatusName', minWidth: 120 },
|
{ label: '发票状态', prop: 'invoiceStatusName', minWidth: 120 },
|
||||||
{ label: '收付款状态', prop: 'paymentStatusName', minWidth: 120 },
|
{ label: '收付款状态', prop: 'paymentStatusName', minWidth: 120 },
|
||||||
{ label: '审核状态', prop: 'approvalStatusName', minWidth: 110, status: true },
|
{ label: '审核状态', prop: 'approvalStatusName', minWidth: 110, status: true },
|
||||||
{ label: '当前节点', prop: 'currentNode', minWidth: 130 },
|
{ label: '当前节点', prop: 'currentNode', minWidth: 130 },
|
||||||
{ label: '当前处理人', prop: 'currentProcessor', minWidth: 130 },
|
{ label: '当前处理人', prop: 'currentProcessor', minWidth: 130 },
|
||||||
{ label: '金蝶单据号', prop: 'kingdeeBillNo', minWidth: 150 },
|
{ label: '金蝶单据号', prop: 'kingdeeBillNo', minWidth: 150 },
|
||||||
|
{ label: '金蝶单据状态', prop: 'kingdeeSyncStatus', minWidth: 135 },
|
||||||
{ label: '创建人', prop: 'createUserName', minWidth: 110 },
|
{ label: '创建人', prop: 'createUserName', minWidth: 110 },
|
||||||
{ label: '创建时间', prop: 'createTime', minWidth: 170 },
|
{ label: '创建时间', prop: 'createTime', minWidth: 170 },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -57,14 +57,13 @@ export const feeDetailBaseColumns = [
|
|||||||
{ label: '货物类型', prop: 'cargoType', minWidth: 130 },
|
{ label: '货物类型', prop: 'cargoType', minWidth: 130 },
|
||||||
{ label: '规格', prop: 'specification', minWidth: 120 },
|
{ label: '规格', prop: 'specification', minWidth: 120 },
|
||||||
{ label: '型号', prop: 'model', minWidth: 120 },
|
{ label: '型号', prop: 'model', minWidth: 120 },
|
||||||
{ label: '运费计费要素', prop: 'billingFactor', minWidth: 130 },
|
{ label: '计费要素', prop: 'billingFactor', minWidth: 130 },
|
||||||
{ label: '运费计费类型', prop: 'billingType', minWidth: 130 },
|
{ label: '计费类型', prop: 'billingType', minWidth: 130 },
|
||||||
{ label: '运输量', prop: 'transportQuantityText', minWidth: 120 },
|
{ label: '运输量', prop: 'transportQuantityText', minWidth: 120 },
|
||||||
{ label: '运费计算单位', prop: 'priceUnit', minWidth: 130 },
|
{ label: '运费计算单位', prop: 'priceUnit', minWidth: 130 },
|
||||||
{ label: '运输单价', prop: 'unitPrice', minWidth: 120 },
|
{ label: '运输单价', prop: 'unitPrice', minWidth: 120 },
|
||||||
{ label: '里程(KM)', prop: 'mileage', minWidth: 120 },
|
{ label: '里程(KM)', prop: 'mileage', minWidth: 120 },
|
||||||
{ label: '运输费', prop: 'freightAmount', minWidth: 180 }
|
{ label: '运输费', prop: 'freightAmount', minWidth: 180 },
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const feeDetailTailColumns = [
|
export const feeDetailTailColumns = [
|
||||||
|
|||||||
@@ -24,5 +24,6 @@ export const createSettlementAdjustmentForm = () => ({
|
|||||||
adjustmentAmount: 0,
|
adjustmentAmount: 0,
|
||||||
originalSettlementAmount: 0,
|
originalSettlementAmount: 0,
|
||||||
adjustedSettlementAmount: 0,
|
adjustedSettlementAmount: 0,
|
||||||
|
attachmentsJson: '[]',
|
||||||
remark: '',
|
remark: '',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -136,6 +136,9 @@
|
|||||||
<el-input-number
|
<el-input-number
|
||||||
v-else-if="editable && column.prop === 'adjustAmount'"
|
v-else-if="editable && column.prop === 'adjustAmount'"
|
||||||
v-model="row.adjustAmount"
|
v-model="row.adjustAmount"
|
||||||
|
:class="{
|
||||||
|
'formal-editor__negative-amount': Number(row.adjustAmount || 0) < 0,
|
||||||
|
}"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
:step="0.01"
|
:step="0.01"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
@@ -147,7 +150,13 @@
|
|||||||
maxlength="50"
|
maxlength="50"
|
||||||
show-word-limit
|
show-word-limit
|
||||||
/>
|
/>
|
||||||
<span v-else-if="isSummaryMoney(column.prop)">
|
<span
|
||||||
|
v-else-if="isSummaryMoney(column.prop)"
|
||||||
|
:class="{
|
||||||
|
'formal-editor__negative-amount':
|
||||||
|
column.prop === 'adjustAmount' && Number(row[column.prop] || 0) < 0,
|
||||||
|
}"
|
||||||
|
>
|
||||||
{{ formatMoney(row[column.prop]) }}
|
{{ formatMoney(row[column.prop]) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="column.prop === 'feeType'">{{ feeCategoryName(row.feeType) }}</span>
|
<span v-else-if="column.prop === 'feeType'">{{ feeCategoryName(row.feeType) }}</span>
|
||||||
@@ -214,6 +223,9 @@
|
|||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
<span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||||
|
<span v-else-if="column.prop === 'transportType'">
|
||||||
|
{{ transportTypeName(row[column.prop]) }}
|
||||||
|
</span>
|
||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -232,15 +244,79 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="附件">
|
<section-card>
|
||||||
|
<template #title>
|
||||||
|
<span>附件</span>
|
||||||
|
<span v-if="missingAttachmentTypeText" class="formal-editor__attachment-missing">
|
||||||
|
{{ missingAttachmentTypeText }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<el-table :data="attachments" border>
|
||||||
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
|
<el-table-column label="类型" min-width="180" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-select
|
||||||
|
v-if="editable"
|
||||||
|
v-model="row.type"
|
||||||
|
filterable
|
||||||
|
placeholder="请选择类型"
|
||||||
|
@change="sortAttachments"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in attachmentTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<span v-else>{{ attachmentTypeName(row.type) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="文件名" min-width="220" align="left" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-link
|
||||||
|
:disabled="!getAttachmentUrl(row)"
|
||||||
|
type="primary"
|
||||||
|
@click="previewAttachment(row)"
|
||||||
|
>
|
||||||
|
{{ row.originalName || row.name || '-' }}
|
||||||
|
</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="uploadUserName" label="上传人" min-width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ displayValue(row.uploadUserName) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="uploadTime" label="上传时间" min-width="170" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ displayValue(row.uploadTime) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="size" label="文件大小" min-width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ displayValue(row.size) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column v-if="editable" label="操作" width="90" align="center">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
<el-link type="danger" @click="removeAttachment($index)">删除</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<div v-if="editable" class="formal-editor__attachment-upload">
|
||||||
<vehicle-attachment-upload
|
<vehicle-attachment-upload
|
||||||
v-model="attachments"
|
v-model="attachmentUploadFiles"
|
||||||
:readonly="!editable"
|
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:limit="20"
|
:limit="20"
|
||||||
:max-size="500"
|
:max-size="500"
|
||||||
:file-types="attachmentFileTypes"
|
:file-types="attachmentFileTypes"
|
||||||
|
button-text="上传附件"
|
||||||
|
:show-file-list="false"
|
||||||
|
:show-uploading="true"
|
||||||
|
@success="handleAttachmentUploadSuccess"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card v-if="readonly && payments.length" title="付款信息">
|
<section-card v-if="readonly && payments.length" title="付款信息">
|
||||||
@@ -313,10 +389,13 @@
|
|||||||
v-bind="column"
|
v-bind="column"
|
||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
<template #default="{ row }"
|
<template #default="{ row }">
|
||||||
><span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span
|
<span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||||
><span v-else>{{ displayValue(row[column.prop]) }}</span></template
|
<span v-else-if="column.prop === 'transportType'">
|
||||||
>
|
{{ transportTypeName(row[column.prop]) }}
|
||||||
|
</span>
|
||||||
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<div class="formal-editor__pagination">
|
<div class="formal-editor__pagination">
|
||||||
@@ -493,6 +572,8 @@ import {
|
|||||||
sourceColumns,
|
sourceColumns,
|
||||||
summaryColumns,
|
summaryColumns,
|
||||||
} from '@/option/settlement/formalSettlementTable';
|
} from '@/option/settlement/formalSettlementTable';
|
||||||
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -518,6 +599,8 @@ export default {
|
|||||||
summaryFees: [],
|
summaryFees: [],
|
||||||
payments: [],
|
payments: [],
|
||||||
attachments: [],
|
attachments: [],
|
||||||
|
attachmentUploadFiles: [],
|
||||||
|
attachmentTypeOptions: [],
|
||||||
attachmentFileTypes: [
|
attachmentFileTypes: [
|
||||||
'pdf',
|
'pdf',
|
||||||
'bmp',
|
'bmp',
|
||||||
@@ -539,6 +622,7 @@ export default {
|
|||||||
allContracts: [],
|
allContracts: [],
|
||||||
projects: [],
|
projects: [],
|
||||||
feeOptions: [],
|
feeOptions: [],
|
||||||
|
transportTypeOptions: [],
|
||||||
fields: formalSettlementFormFields,
|
fields: formalSettlementFormFields,
|
||||||
sourceTableColumns: sourceColumns,
|
sourceTableColumns: sourceColumns,
|
||||||
summaryTableColumns: summaryColumns,
|
summaryTableColumns: summaryColumns,
|
||||||
@@ -592,6 +676,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapGetters(['userInfo']),
|
||||||
visible: {
|
visible: {
|
||||||
get() {
|
get() {
|
||||||
return this.modelValue;
|
return this.modelValue;
|
||||||
@@ -635,6 +720,10 @@ export default {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
missingAttachmentTypeText() {
|
||||||
|
const missing = this.getMissingAttachmentTypes();
|
||||||
|
return missing.length ? `未上传:${missing.join('、')}` : '';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
@@ -663,14 +752,19 @@ export default {
|
|||||||
this.summaryFees = [];
|
this.summaryFees = [];
|
||||||
this.payments = [];
|
this.payments = [];
|
||||||
this.attachments = [];
|
this.attachments = [];
|
||||||
|
this.attachmentUploadFiles = [];
|
||||||
this.detailCollapsed = false;
|
this.detailCollapsed = false;
|
||||||
this.resetDetailQuery(false);
|
this.resetDetailQuery(false);
|
||||||
await Promise.all([this.loadAllContracts(), this.loadFeeOptions()]);
|
await Promise.all([
|
||||||
|
this.loadAllContracts(),
|
||||||
|
this.loadFeeOptions(),
|
||||||
|
this.loadAttachmentTypeOptions(),
|
||||||
|
this.loadTransportTypeOptions(),
|
||||||
|
]);
|
||||||
if (!this.recordId) {
|
if (!this.recordId) {
|
||||||
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
|
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
|
||||||
const response = await getNextNo();
|
|
||||||
this.form.formalSettlementNo = this.unwrapData(response) || '';
|
|
||||||
if (this.initialData) await this.applyInitialData();
|
if (this.initialData) await this.applyInitialData();
|
||||||
|
await this.refreshFormalSettlementNo();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
@@ -690,6 +784,7 @@ export default {
|
|||||||
this.summaryFees = data.summaryFees || [];
|
this.summaryFees = data.summaryFees || [];
|
||||||
this.payments = data.payments || [];
|
this.payments = data.payments || [];
|
||||||
this.attachments = this.parseAttachments(data.attachmentsJson);
|
this.attachments = this.parseAttachments(data.attachmentsJson);
|
||||||
|
this.sortAttachments();
|
||||||
this.contracts = this.allContracts.filter(
|
this.contracts = this.allContracts.filter(
|
||||||
item => String(item.projectId) === String(this.form.projectId)
|
item => String(item.projectId) === String(this.form.projectId)
|
||||||
);
|
);
|
||||||
@@ -697,6 +792,14 @@ export default {
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async refreshFormalSettlementNo() {
|
||||||
|
if (this.form.id || !this.form.settlementType) {
|
||||||
|
if (!this.form.id) this.form.formalSettlementNo = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = await getNextNo(this.form.settlementType);
|
||||||
|
this.form.formalSettlementNo = this.unwrapData(response) || '';
|
||||||
|
},
|
||||||
async applyInitialData() {
|
async applyInitialData() {
|
||||||
const sourcePreSettlements = Array.isArray(this.initialData?.sourcePreSettlements)
|
const sourcePreSettlements = Array.isArray(this.initialData?.sourcePreSettlements)
|
||||||
? this.initialData.sourcePreSettlements
|
? this.initialData.sourcePreSettlements
|
||||||
@@ -739,7 +842,7 @@ export default {
|
|||||||
settlementType,
|
settlementType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.handleContractChange(contractId);
|
this.handleContractChange(contractId, false);
|
||||||
Object.assign(this.form, {
|
Object.assign(this.form, {
|
||||||
contractId,
|
contractId,
|
||||||
contractNo: first.contractNo || this.form.contractNo,
|
contractNo: first.contractNo || this.form.contractNo,
|
||||||
@@ -781,7 +884,7 @@ export default {
|
|||||||
settlementType,
|
settlementType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.handleContractChange(contractId);
|
this.handleContractChange(contractId, false);
|
||||||
this.sources = rows.map(row => ({
|
this.sources = rows.map(row => ({
|
||||||
...row,
|
...row,
|
||||||
preSettlementId: row.preSettlementId || row.id,
|
preSettlementId: row.preSettlementId || row.id,
|
||||||
@@ -869,7 +972,7 @@ export default {
|
|||||||
this.form.sourceDetailIds = [];
|
this.form.sourceDetailIds = [];
|
||||||
this.loadContracts();
|
this.loadContracts();
|
||||||
},
|
},
|
||||||
handleContractChange(id) {
|
handleContractChange(id, refreshSettlementNo = true) {
|
||||||
const contract = this.contracts.find(item => String(item.id) === String(id));
|
const contract = this.contracts.find(item => String(item.id) === String(id));
|
||||||
if (!contract) return;
|
if (!contract) return;
|
||||||
const formalSettlementId = this.form.id;
|
const formalSettlementId = this.form.id;
|
||||||
@@ -889,6 +992,7 @@ export default {
|
|||||||
this.summaryFees = [];
|
this.summaryFees = [];
|
||||||
this.form.sourcePreSettlementIds = [];
|
this.form.sourcePreSettlementIds = [];
|
||||||
this.form.sourceDetailIds = [];
|
this.form.sourceDetailIds = [];
|
||||||
|
if (refreshSettlementNo) this.refreshFormalSettlementNo();
|
||||||
},
|
},
|
||||||
openCandidateDialog() {
|
openCandidateDialog() {
|
||||||
this.candidate.visible = true;
|
this.candidate.visible = true;
|
||||||
@@ -933,6 +1037,7 @@ export default {
|
|||||||
contractId: this.sources[0].contractId,
|
contractId: this.sources[0].contractId,
|
||||||
formalSettlementNo: this.form.formalSettlementNo,
|
formalSettlementNo: this.form.formalSettlementNo,
|
||||||
});
|
});
|
||||||
|
this.refreshFormalSettlementNo();
|
||||||
}
|
}
|
||||||
this.form.settlementAmount = this.sources.reduce(
|
this.form.settlementAmount = this.sources.reduce(
|
||||||
(sum, item) => sum + Number(item.settlementAmount || 0),
|
(sum, item) => sum + Number(item.settlementAmount || 0),
|
||||||
@@ -1187,6 +1292,7 @@ export default {
|
|||||||
if (invalidManualFee) {
|
if (invalidManualFee) {
|
||||||
return this.$message.warning('请完善手工费用的费用类型和费用项');
|
return this.$message.warning('请完善手工费用的费用类型和费用项');
|
||||||
}
|
}
|
||||||
|
if (!this.validateAttachments()) return;
|
||||||
this.saving = true;
|
this.saving = true;
|
||||||
try {
|
try {
|
||||||
await save({
|
await save({
|
||||||
@@ -1213,7 +1319,7 @@ export default {
|
|||||||
remark: row.remark,
|
remark: row.remark,
|
||||||
manualFlag: row.manualFlag || 0,
|
manualFlag: row.manualFlag || 0,
|
||||||
})),
|
})),
|
||||||
attachmentsJson: JSON.stringify(this.attachments || []),
|
attachmentsJson: this.stringifyAttachments(this.attachments),
|
||||||
remark: this.form.remark,
|
remark: this.form.remark,
|
||||||
});
|
});
|
||||||
this.$message.success('保存成功');
|
this.$message.success('保存成功');
|
||||||
@@ -1233,6 +1339,142 @@ export default {
|
|||||||
formatMoney(value) {
|
formatMoney(value) {
|
||||||
return `${Number(value || 0).toFixed(2)} RMB`;
|
return `${Number(value || 0).toFixed(2)} RMB`;
|
||||||
},
|
},
|
||||||
|
async loadTransportTypeOptions() {
|
||||||
|
const { data } = await getDictionary({ code: 'transport_type' });
|
||||||
|
this.transportTypeOptions = data?.data || [];
|
||||||
|
},
|
||||||
|
transportTypeName(value) {
|
||||||
|
if (value === undefined || value === null || value === '') return '';
|
||||||
|
const normalizedValue = String(value).trim().toLocaleLowerCase();
|
||||||
|
const option = this.transportTypeOptions.find(
|
||||||
|
item =>
|
||||||
|
String(item.dictKey ?? '')
|
||||||
|
.trim()
|
||||||
|
.toLocaleLowerCase() === normalizedValue ||
|
||||||
|
String(item.dictValue ?? '')
|
||||||
|
.trim()
|
||||||
|
.toLocaleLowerCase() === normalizedValue
|
||||||
|
);
|
||||||
|
return option?.dictValue || value;
|
||||||
|
},
|
||||||
|
async loadAttachmentTypeOptions() {
|
||||||
|
const response = await getDictionary({ code: 'settle_attachment_types' });
|
||||||
|
const data = response?.data?.data || [];
|
||||||
|
this.attachmentTypeOptions = data.map(item => ({
|
||||||
|
label: item.dictValue,
|
||||||
|
value: item.dictValue,
|
||||||
|
}));
|
||||||
|
this.sortAttachments();
|
||||||
|
},
|
||||||
|
resolveAttachmentType(fileName) {
|
||||||
|
const normalizedName = String(fileName || '').toLocaleLowerCase();
|
||||||
|
const matchedType = [...this.attachmentTypeOptions]
|
||||||
|
.filter(item => item?.label || item?.value)
|
||||||
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
String(b.label || b.value || '').length - String(a.label || a.value || '').length
|
||||||
|
)
|
||||||
|
.find(item => {
|
||||||
|
const typeText = String(item.label || item.value || '').toLocaleLowerCase();
|
||||||
|
return typeText && normalizedName.includes(typeText);
|
||||||
|
});
|
||||||
|
if (matchedType?.value) return matchedType.value;
|
||||||
|
const otherType = this.attachmentTypeOptions.find(item =>
|
||||||
|
String(item.label || item.value || '').includes('其他')
|
||||||
|
);
|
||||||
|
if (otherType?.value) return otherType.value;
|
||||||
|
this.attachmentTypeOptions.push({ label: '其他附件', value: '其他附件' });
|
||||||
|
return '其他附件';
|
||||||
|
},
|
||||||
|
getAttachmentTypeOrder(type) {
|
||||||
|
const normalizedType = String(type || '').trim();
|
||||||
|
const index = this.attachmentTypeOptions.findIndex(
|
||||||
|
item =>
|
||||||
|
String(item.value || '').trim() === normalizedType ||
|
||||||
|
String(item.label || '').trim() === normalizedType
|
||||||
|
);
|
||||||
|
return index === -1 ? Number.MAX_SAFE_INTEGER : index;
|
||||||
|
},
|
||||||
|
sortAttachments() {
|
||||||
|
this.attachments = (this.attachments || [])
|
||||||
|
.map((file, index) => ({ file, index }))
|
||||||
|
.sort((a, b) => {
|
||||||
|
const orderDifference =
|
||||||
|
this.getAttachmentTypeOrder(a.file.type) - this.getAttachmentTypeOrder(b.file.type);
|
||||||
|
return orderDifference || a.index - b.index;
|
||||||
|
})
|
||||||
|
.map(item => item.file);
|
||||||
|
},
|
||||||
|
getMissingAttachmentTypes(files = this.attachments) {
|
||||||
|
const uploadedTypes = new Set(
|
||||||
|
(files || [])
|
||||||
|
.filter(item => this.getAttachmentUrl(item))
|
||||||
|
.map(item => String(item.type || '').trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
);
|
||||||
|
return this.attachmentTypeOptions
|
||||||
|
.filter(item => String(item.label || item.value || '').trim() !== '其他附件')
|
||||||
|
.filter(item => !uploadedTypes.has(String(item.value || item.label || '').trim()))
|
||||||
|
.map(item => item.label || item.value);
|
||||||
|
},
|
||||||
|
validateAttachments(files = this.attachments) {
|
||||||
|
const missing = this.getMissingAttachmentTypes(files);
|
||||||
|
if (!missing.length) return true;
|
||||||
|
this.$message.warning(`请先上传附件:${missing.join('、')}`);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
handleAttachmentUploadSuccess(file) {
|
||||||
|
const originalName = file.originalName || file.name || '附件';
|
||||||
|
this.attachments.push({
|
||||||
|
type: this.resolveAttachmentType(originalName),
|
||||||
|
originalName,
|
||||||
|
name: originalName,
|
||||||
|
uploadUserName: this.userInfo.realName || this.userInfo.userName || '',
|
||||||
|
uploadTime: this.$dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
size: this.formatAttachmentSize(file.size),
|
||||||
|
url: file.url || file.link || '',
|
||||||
|
});
|
||||||
|
this.sortAttachments();
|
||||||
|
},
|
||||||
|
removeAttachment(index) {
|
||||||
|
this.attachments.splice(index, 1);
|
||||||
|
},
|
||||||
|
getAttachmentUrl(file = {}) {
|
||||||
|
return file.url || file.link || file.src || file.domain || '';
|
||||||
|
},
|
||||||
|
previewAttachment(file) {
|
||||||
|
const url = this.getAttachmentUrl(file);
|
||||||
|
if (!url) return this.$message.warning('附件地址为空,无法预览');
|
||||||
|
window.open(url, '_blank');
|
||||||
|
},
|
||||||
|
attachmentTypeName(type) {
|
||||||
|
const option = this.attachmentTypeOptions.find(
|
||||||
|
item => String(item.value) === String(type) || String(item.label) === String(type)
|
||||||
|
);
|
||||||
|
return this.displayValue(option?.label || type);
|
||||||
|
},
|
||||||
|
formatAttachmentSize(size) {
|
||||||
|
if (!size) return '';
|
||||||
|
const byteSize = Number(size);
|
||||||
|
if (!Number.isFinite(byteSize)) return String(size);
|
||||||
|
const mb = byteSize / 1024 / 1024;
|
||||||
|
if (mb >= 1) return `${mb.toFixed(2)} MB`;
|
||||||
|
return `${(byteSize / 1024).toFixed(2)} KB`;
|
||||||
|
},
|
||||||
|
stringifyAttachments(list) {
|
||||||
|
const files = (list || [])
|
||||||
|
.filter(item => this.getAttachmentUrl(item))
|
||||||
|
.map(item => ({
|
||||||
|
type: String(item.type || '').trim(),
|
||||||
|
originalName: String(item.originalName || item.name || '').trim(),
|
||||||
|
name: String(item.originalName || item.name || '').trim(),
|
||||||
|
uploadUserName: String(item.uploadUserName || '').trim(),
|
||||||
|
uploadTime: String(item.uploadTime || '').trim(),
|
||||||
|
size: String(item.size || '').trim(),
|
||||||
|
url: String(this.getAttachmentUrl(item)).trim(),
|
||||||
|
}));
|
||||||
|
return files.length ? JSON.stringify(files) : '';
|
||||||
|
},
|
||||||
parseFeeItems(value) {
|
parseFeeItems(value) {
|
||||||
if (!value) return {};
|
if (!value) return {};
|
||||||
if (typeof value === 'object') return value;
|
if (typeof value === 'object') return value;
|
||||||
@@ -1244,13 +1486,39 @@ export default {
|
|||||||
},
|
},
|
||||||
parseAttachments(value) {
|
parseAttachments(value) {
|
||||||
if (!value) return [];
|
if (!value) return [];
|
||||||
if (Array.isArray(value)) return value;
|
let attachments = value;
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(value);
|
attachments = JSON.parse(value);
|
||||||
return Array.isArray(parsed) ? parsed : [];
|
|
||||||
} catch {
|
} catch {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (!Array.isArray(attachments)) return [];
|
||||||
|
return attachments.map(item => {
|
||||||
|
const url = this.getAttachmentUrl(item);
|
||||||
|
const originalName =
|
||||||
|
item.originalName || item.name || this.getAttachmentFileName(url) || '附件';
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
type: item.type || this.resolveAttachmentType(originalName),
|
||||||
|
originalName,
|
||||||
|
name: originalName,
|
||||||
|
uploadUserName: item.uploadUserName || item.createUserName || item.uploadUser || '',
|
||||||
|
uploadTime: item.uploadTime || item.createTime || '',
|
||||||
|
size: this.formatAttachmentSize(item.size || item.attachSize),
|
||||||
|
url,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getAttachmentFileName(url) {
|
||||||
|
if (!url) return '';
|
||||||
|
const path = String(url).split('?')[0];
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(path.substring(path.lastIndexOf('/') + 1));
|
||||||
|
} catch {
|
||||||
|
return path.substring(path.lastIndexOf('/') + 1);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -1305,6 +1573,21 @@ export default {
|
|||||||
.formal-editor__adjust-reason {
|
.formal-editor__adjust-reason {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
.formal-editor__attachment-missing {
|
||||||
|
margin-left: 12px;
|
||||||
|
color: var(--el-color-danger);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.formal-editor__attachment-upload {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
.formal-editor__negative-amount {
|
||||||
|
color: var(--el-color-danger);
|
||||||
|
}
|
||||||
|
.formal-editor :deep(.formal-editor__negative-amount .el-input__inner) {
|
||||||
|
color: var(--el-color-danger);
|
||||||
|
}
|
||||||
/* 选择预结算单 / 选择结算明细 弹窗搜索区白底 */
|
/* 选择预结算单 / 选择结算明细 弹窗搜索区白底 */
|
||||||
.formal-editor__candidate-search,
|
.formal-editor__candidate-search,
|
||||||
.formal-editor__detail-search {
|
.formal-editor__detail-search {
|
||||||
|
|||||||
@@ -49,9 +49,9 @@
|
|||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
:data="rows"
|
:data="rows"
|
||||||
border
|
border
|
||||||
@selection-change="handleSelectionChange"
|
highlight-current-row
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
>
|
>
|
||||||
<el-table-column type="selection" width="52" fixed="left" align="center" />
|
|
||||||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-for="column in columns"
|
v-for="column in columns"
|
||||||
@@ -61,12 +61,18 @@
|
|||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-link v-if="column.link && row[column.prop]" type="primary" @click="emitAction('view', row)">{{
|
<el-link
|
||||||
row[column.prop]
|
v-if="column.link && row[column.prop]"
|
||||||
}}</el-link>
|
type="primary"
|
||||||
<el-tag v-else-if="column.status" :type="statusType(row.approvalStatus)" class="status-text">{{
|
@click="emitAction('view', row)"
|
||||||
displayValue(row[column.prop])
|
>{{ row[column.prop] }}</el-link
|
||||||
}}</el-tag>
|
>
|
||||||
|
<el-tag
|
||||||
|
v-else-if="column.status"
|
||||||
|
:type="statusType(row.approvalStatus)"
|
||||||
|
class="status-text"
|
||||||
|
>{{ displayValue(row[column.prop]) }}</el-tag
|
||||||
|
>
|
||||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop], row.currency) }}</span>
|
<span v-else-if="column.money">{{ formatMoney(row[column.prop], row.currency) }}</span>
|
||||||
<span v-else-if="column.prop === 'invoiceStatusName'">{{
|
<span v-else-if="column.prop === 'invoiceStatusName'">{{
|
||||||
invoiceName(row.invoiceStatus)
|
invoiceName(row.invoiceStatus)
|
||||||
@@ -74,10 +80,13 @@
|
|||||||
<span v-else-if="column.prop === 'paymentStatusName'">{{
|
<span v-else-if="column.prop === 'paymentStatusName'">{{
|
||||||
paymentName(row.paymentStatus)
|
paymentName(row.paymentStatus)
|
||||||
}}</span>
|
}}</span>
|
||||||
|
<span v-else-if="column.prop === 'kingdeeSyncStatus'">{{
|
||||||
|
kingdeeSyncName(row.kingdeeSyncStatus)
|
||||||
|
}}</span>
|
||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="120" fixed="right" align="center">
|
<el-table-column label="操作" width="320" fixed="right" align="center">
|
||||||
<template #default="{ row }"
|
<template #default="{ row }"
|
||||||
><div class="fs-table-panel__links">
|
><div class="fs-table-panel__links">
|
||||||
<el-link
|
<el-link
|
||||||
@@ -109,6 +118,7 @@
|
|||||||
import { Refresh } from '@element-plus/icons-vue';
|
import { Refresh } from '@element-plus/icons-vue';
|
||||||
import {
|
import {
|
||||||
invoiceStatusOptions,
|
invoiceStatusOptions,
|
||||||
|
kingdeeSyncStatusOptions,
|
||||||
paymentStatusOptions,
|
paymentStatusOptions,
|
||||||
} from '@/option/settlement/formalSettlementSearch';
|
} from '@/option/settlement/formalSettlementSearch';
|
||||||
|
|
||||||
@@ -129,9 +139,9 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleSelectionChange(value) {
|
handleCurrentChange(row) {
|
||||||
this.selection = value;
|
this.selection = row ? [row] : [];
|
||||||
this.$emit('update:selection', value);
|
this.$emit('update:selection', this.selection);
|
||||||
},
|
},
|
||||||
emitAction(type, row) {
|
emitAction(type, row) {
|
||||||
this.$emit('action', { type, row });
|
this.$emit('action', { type, row });
|
||||||
@@ -180,6 +190,9 @@ export default {
|
|||||||
paymentName(value) {
|
paymentName(value) {
|
||||||
return paymentStatusOptions.find(item => item.value === value)?.label || value || '-';
|
return paymentStatusOptions.find(item => item.value === value)?.label || value || '-';
|
||||||
},
|
},
|
||||||
|
kingdeeSyncName(value) {
|
||||||
|
return kingdeeSyncStatusOptions.find(item => item.value === value)?.label || value || '-';
|
||||||
|
},
|
||||||
displayValue(value) {
|
displayValue(value) {
|
||||||
return value === null || value === undefined || value === '' ? '-' : value;
|
return value === null || value === undefined || value === '' ? '-' : value;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -419,7 +419,7 @@
|
|||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="candidateDialog.visible"
|
v-model="candidateDialog.visible"
|
||||||
title="添加应收应付明细进预结算单"
|
title="选择结算明细"
|
||||||
width="92%"
|
width="92%"
|
||||||
append-to-body
|
append-to-body
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
@@ -466,6 +466,9 @@
|
|||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="column.money">{{ formatMoney(row[column.prop], row.currency) }}</span>
|
<span v-if="column.money">{{ formatMoney(row[column.prop], row.currency) }}</span>
|
||||||
|
<span v-else-if="column.prop === 'transportType'">
|
||||||
|
{{ transportTypeName(row[column.prop]) }}
|
||||||
|
</span>
|
||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -1086,8 +1089,15 @@ export default {
|
|||||||
},
|
},
|
||||||
transportTypeName(value) {
|
transportTypeName(value) {
|
||||||
if (value === undefined || value === null || value === '') return '';
|
if (value === undefined || value === null || value === '') return '';
|
||||||
|
const normalizedValue = String(value).trim().toLocaleLowerCase();
|
||||||
const option = this.transportTypeOptions.find(
|
const option = this.transportTypeOptions.find(
|
||||||
item => String(item.dictKey) === String(value) || String(item.dictValue) === String(value)
|
item =>
|
||||||
|
String(item.dictKey ?? '')
|
||||||
|
.trim()
|
||||||
|
.toLocaleLowerCase() === normalizedValue ||
|
||||||
|
String(item.dictValue ?? '')
|
||||||
|
.trim()
|
||||||
|
.toLocaleLowerCase() === normalizedValue
|
||||||
);
|
);
|
||||||
return option?.dictValue || value;
|
return option?.dictValue || value;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -102,14 +102,13 @@
|
|||||||
}}</span></template
|
}}</span></template
|
||||||
></el-table-column
|
></el-table-column
|
||||||
>
|
>
|
||||||
<el-table-column label="调整金额(不含税)" min-width="170" align="center"
|
<el-table-column
|
||||||
|
v-if="!editable"
|
||||||
|
label="调整金额(不含税)"
|
||||||
|
min-width="170"
|
||||||
|
align="center"
|
||||||
><template #default="{ row }"
|
><template #default="{ row }"
|
||||||
><el-input-number
|
><span>{{ formatMoney(row.adjustmentAmountNoTax) }}</span></template
|
||||||
v-if="editable"
|
|
||||||
v-model="row.adjustmentAmountNoTax"
|
|
||||||
:precision="2"
|
|
||||||
:controls="false"
|
|
||||||
/><span v-else>{{ formatMoney(row.adjustmentAmountNoTax) }}</span></template
|
|
||||||
></el-table-column
|
></el-table-column
|
||||||
>
|
>
|
||||||
<el-table-column label="备注" min-width="180" align="center"
|
<el-table-column label="备注" min-width="180" align="center"
|
||||||
@@ -140,6 +139,60 @@
|
|||||||
>;调整后结算金额:<strong>{{ formatMoney(form.adjustedSettlementAmount) }}</strong>
|
>;调整后结算金额:<strong>{{ formatMoney(form.adjustedSettlementAmount) }}</strong>
|
||||||
</div>
|
</div>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
|
<section-card title="附件材料">
|
||||||
|
<el-table :data="attachments" border>
|
||||||
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
|
<el-table-column label="文件名" min-width="220" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-link type="primary" @click="previewAttachment(row)">
|
||||||
|
{{ attachmentName(row) }}
|
||||||
|
</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="附件描述" min-width="240">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input
|
||||||
|
v-if="editable"
|
||||||
|
v-model="row.description"
|
||||||
|
maxlength="200"
|
||||||
|
placeholder="请输入附件描述"
|
||||||
|
/>
|
||||||
|
<span v-else>{{ displayValue(row.description) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="文件大小" width="120" align="center">
|
||||||
|
<template #default="{ row }">{{ formatFileSize(row) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="uploadUserName" label="上传人" width="140" align="center" />
|
||||||
|
<el-table-column prop="uploadTime" label="上传时间" width="170" align="center" />
|
||||||
|
<el-table-column label="操作" :width="editable ? 130 : 70" fixed="right" align="center">
|
||||||
|
<template #default="{ row, $index }">
|
||||||
|
<div class="settlement-adjustment-editor__links">
|
||||||
|
<el-link type="primary" @click="downloadAttachment(row)">下载</el-link>
|
||||||
|
<el-link v-if="editable" type="danger" @click="removeAttachment($index)">
|
||||||
|
删除
|
||||||
|
</el-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<template #empty><el-empty description="暂无附件材料" /></template>
|
||||||
|
</el-table>
|
||||||
|
<div v-show="editable" class="settlement-adjustment-editor__attachment-upload">
|
||||||
|
<vehicle-attachment-upload
|
||||||
|
ref="attachmentUploadRef"
|
||||||
|
v-model="attachments"
|
||||||
|
:readonly="!editable"
|
||||||
|
:multiple="true"
|
||||||
|
:limit="20"
|
||||||
|
:max-size="500"
|
||||||
|
:file-types="attachmentFileTypes"
|
||||||
|
:show-file-list="false"
|
||||||
|
button-text="上传附件"
|
||||||
|
@change="handleAttachmentChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section-card>
|
||||||
</div>
|
</div>
|
||||||
<template #footer
|
<template #footer
|
||||||
><el-button @click="visible = false">取消</el-button
|
><el-button @click="visible = false">取消</el-button
|
||||||
@@ -199,6 +252,7 @@ import {
|
|||||||
createSettlementAdjustmentForm,
|
createSettlementAdjustmentForm,
|
||||||
settlementAdjustmentFormFields,
|
settlementAdjustmentFormFields,
|
||||||
} from '@/option/settlement/settlementAdjustmentForm';
|
} from '@/option/settlement/settlementAdjustmentForm';
|
||||||
|
import { downloadFileByUrl } from '@/utils/util';
|
||||||
export default {
|
export default {
|
||||||
name: 'SettlementAdjustmentEditor',
|
name: 'SettlementAdjustmentEditor',
|
||||||
props: { modelValue: Boolean, recordId: [String, Number], readonly: Boolean },
|
props: { modelValue: Boolean, recordId: [String, Number], readonly: Boolean },
|
||||||
@@ -209,6 +263,23 @@ export default {
|
|||||||
form: createSettlementAdjustmentForm(),
|
form: createSettlementAdjustmentForm(),
|
||||||
fields: settlementAdjustmentFormFields,
|
fields: settlementAdjustmentFormFields,
|
||||||
details: [],
|
details: [],
|
||||||
|
attachments: [],
|
||||||
|
attachmentFileTypes: [
|
||||||
|
'pdf',
|
||||||
|
'bmp',
|
||||||
|
'jpeg',
|
||||||
|
'png',
|
||||||
|
'jpg',
|
||||||
|
'doc',
|
||||||
|
'docx',
|
||||||
|
'ppt',
|
||||||
|
'pptx',
|
||||||
|
'xlsx',
|
||||||
|
'xls',
|
||||||
|
'eml',
|
||||||
|
'msg',
|
||||||
|
'zip',
|
||||||
|
],
|
||||||
candidates: [],
|
candidates: [],
|
||||||
feeRows: [],
|
feeRows: [],
|
||||||
rules: {
|
rules: {
|
||||||
@@ -248,6 +319,7 @@ export default {
|
|||||||
async initialize() {
|
async initialize() {
|
||||||
this.form = createSettlementAdjustmentForm();
|
this.form = createSettlementAdjustmentForm();
|
||||||
this.details = [];
|
this.details = [];
|
||||||
|
this.attachments = [];
|
||||||
this.candidates = [];
|
this.candidates = [];
|
||||||
this.feeRows = [];
|
this.feeRows = [];
|
||||||
if (!this.recordId) {
|
if (!this.recordId) {
|
||||||
@@ -264,6 +336,7 @@ export default {
|
|||||||
adjustmentAmountNoTax:
|
adjustmentAmountNoTax:
|
||||||
item.adjustmentAmountNoTax == null ? null : Number(item.adjustmentAmountNoTax),
|
item.adjustmentAmountNoTax == null ? null : Number(item.adjustmentAmountNoTax),
|
||||||
}));
|
}));
|
||||||
|
this.attachments = this.parseAttachments(data.attachmentsJson);
|
||||||
await this.loadFormalSettlements(this.form.formalSettlementNo || '');
|
await this.loadFormalSettlements(this.form.formalSettlementNo || '');
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
@@ -276,8 +349,9 @@ export default {
|
|||||||
async handleFormalChange(id) {
|
async handleFormalChange(id) {
|
||||||
const item = this.candidates.find(row => String(row.id) === String(id));
|
const item = this.candidates.find(row => String(row.id) === String(id));
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
Object.assign(this.form, item, {
|
const { id: formalSettlementId, ...formalSettlement } = item;
|
||||||
formalSettlementId: id,
|
Object.assign(this.form, formalSettlement, {
|
||||||
|
formalSettlementId,
|
||||||
formalSettlementNo: item.formalSettlementNo,
|
formalSettlementNo: item.formalSettlementNo,
|
||||||
originalSettlementAmount: Number(item.settlementAmount || 0),
|
originalSettlementAmount: Number(item.settlementAmount || 0),
|
||||||
settlementTypeName: item.settlementTypeName,
|
settlementTypeName: item.settlementTypeName,
|
||||||
@@ -321,8 +395,9 @@ export default {
|
|||||||
this.saving = true;
|
this.saving = true;
|
||||||
try {
|
try {
|
||||||
await api.save({
|
await api.save({
|
||||||
id: this.form.id,
|
...(this.recordId ? { id: this.form.id } : {}),
|
||||||
formalSettlementId: this.form.formalSettlementId,
|
formalSettlementId: this.form.formalSettlementId,
|
||||||
|
attachmentsJson: JSON.stringify(this.attachments || []),
|
||||||
remark: this.form.remark,
|
remark: this.form.remark,
|
||||||
details: this.details.map(item => ({
|
details: this.details.map(item => ({
|
||||||
formalSettlementDetailId: item.formalSettlementDetailId,
|
formalSettlementDetailId: item.formalSettlementDetailId,
|
||||||
@@ -347,15 +422,77 @@ export default {
|
|||||||
formatMoney(value) {
|
formatMoney(value) {
|
||||||
return Number(value || 0).toFixed(2);
|
return Number(value || 0).toFixed(2);
|
||||||
},
|
},
|
||||||
|
handleAttachmentChange(files) {
|
||||||
|
const userInfo = this.$store.getters.userInfo || {};
|
||||||
|
const uploadUserName = userInfo.realName || userInfo.userName || '';
|
||||||
|
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
this.attachments = (files || []).map(file => ({
|
||||||
|
...file,
|
||||||
|
description: file.description || '',
|
||||||
|
uploadUserName: file.uploadUserName || uploadUserName,
|
||||||
|
uploadTime: file.uploadTime || uploadTime,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
parseAttachments(value) {
|
||||||
|
if (!value) return [];
|
||||||
|
if (Array.isArray(value)) return value;
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
return Array.isArray(parsed) ? parsed : [];
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
attachmentName(file = {}) {
|
||||||
|
return file.originalName || file.name || file.fileName || '附件';
|
||||||
|
},
|
||||||
|
attachmentUrl(file = {}) {
|
||||||
|
return file.url || file.link || file.fileUrl || file.domain || '';
|
||||||
|
},
|
||||||
|
formatFileSize(file = {}) {
|
||||||
|
const bytes = Number(file.size || file.fileSize || file.attachSize || 0);
|
||||||
|
if (!bytes) return '-';
|
||||||
|
if (bytes < 1024) return `${bytes}B`;
|
||||||
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
||||||
|
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
||||||
|
},
|
||||||
|
previewAttachment(file) {
|
||||||
|
this.$refs.attachmentUploadRef?.handlePreview(file);
|
||||||
|
},
|
||||||
|
downloadAttachment(file) {
|
||||||
|
const url = this.attachmentUrl(file);
|
||||||
|
if (!url) {
|
||||||
|
this.$message.warning('附件地址为空');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
downloadFileByUrl(url, this.attachmentName(file));
|
||||||
|
},
|
||||||
|
removeAttachment(index) {
|
||||||
|
this.attachments.splice(index, 1);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.settlement-adjustment-editor {
|
||||||
|
max-height: 72vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
.settlement-adjustment-editor__summary {
|
.settlement-adjustment-editor__summary {
|
||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
.settlement-adjustment-editor__attachment-upload {
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
.settlement-adjustment-editor__links {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
.settlement-adjustment-editor :deep(.el-form-item) {
|
.settlement-adjustment-editor :deep(.el-form-item) {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,8 +200,14 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
'$route.query.detailNo'(detailNo) {
|
||||||
|
if (detailNo) this.openRouteDetail(detailNo);
|
||||||
|
},
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.loadTable();
|
this.loadTable();
|
||||||
|
this.openRouteDetail();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
hasPermission(code) {
|
hasPermission(code) {
|
||||||
@@ -267,6 +273,19 @@ export default {
|
|||||||
openView(row) {
|
openView(row) {
|
||||||
this.editor = { visible: true, id: row.id, readonly: true };
|
this.editor = { visible: true, id: row.id, readonly: true };
|
||||||
},
|
},
|
||||||
|
async openRouteDetail(detailNo = this.$route.query.detailNo) {
|
||||||
|
const formalSettlementNo = String(detailNo || '').trim();
|
||||||
|
if (!formalSettlementNo) return;
|
||||||
|
const response = await api.getList(1, 10, { formalSettlementNo });
|
||||||
|
const data = this.unwrapData(response);
|
||||||
|
const row = (data.records || []).find(item => item.formalSettlementNo === formalSettlementNo);
|
||||||
|
if (!row) {
|
||||||
|
this.$message.warning('未找到对应的正式结算单');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.activeSettlementType = row.settlementType || this.activeSettlementType;
|
||||||
|
this.openView(row);
|
||||||
|
},
|
||||||
async handleDelete(row) {
|
async handleDelete(row) {
|
||||||
await this.$confirm('确认删除该正式结算草稿?', '提示', { type: 'warning' });
|
await this.$confirm('确认删除该正式结算草稿?', '提示', { type: 'warning' });
|
||||||
await api.remove(row.id);
|
await api.remove(row.id);
|
||||||
|
|||||||
@@ -118,7 +118,11 @@
|
|||||||
>
|
>
|
||||||
{{ row[column.prop] }}
|
{{ row[column.prop] }}
|
||||||
</el-link>
|
</el-link>
|
||||||
<el-tag v-else-if="column.status" :type="statusTagType(row.approvalStatus)" class="status-text">
|
<el-tag
|
||||||
|
v-else-if="column.status"
|
||||||
|
:type="statusTagType(row.approvalStatus)"
|
||||||
|
class="status-text"
|
||||||
|
>
|
||||||
{{ row[column.prop] || '-' }}
|
{{ row[column.prop] || '-' }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
<span v-else-if="column.money">
|
<span v-else-if="column.money">
|
||||||
@@ -426,8 +430,14 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
'$route.query.detailNo'(detailNo) {
|
||||||
|
if (detailNo) this.openRouteDetail(detailNo);
|
||||||
|
},
|
||||||
|
},
|
||||||
created() {
|
created() {
|
||||||
this.loadTable();
|
this.loadTable();
|
||||||
|
this.openRouteDetail();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
hasPermission(code) {
|
hasPermission(code) {
|
||||||
@@ -487,6 +497,18 @@ export default {
|
|||||||
openView(row) {
|
openView(row) {
|
||||||
this.editor = { visible: true, id: row.id, readonly: true };
|
this.editor = { visible: true, id: row.id, readonly: true };
|
||||||
},
|
},
|
||||||
|
async openRouteDetail(detailNo = this.$route.query.detailNo) {
|
||||||
|
const preSettlementNo = String(detailNo || '').trim();
|
||||||
|
if (!preSettlementNo) return;
|
||||||
|
const { data } = await getList(1, 10, { preSettlementNo });
|
||||||
|
const records = data?.data?.records || [];
|
||||||
|
const row = records.find(item => item.preSettlementNo === preSettlementNo);
|
||||||
|
if (!row) {
|
||||||
|
this.$message.warning('未找到对应的预结算单');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.openView(row);
|
||||||
|
},
|
||||||
isEditable(row) {
|
isEditable(row) {
|
||||||
return ['draft', 'returned'].includes(row.approvalStatus);
|
return ['draft', 'returned'].includes(row.approvalStatus);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -190,8 +190,16 @@
|
|||||||
<div
|
<div
|
||||||
class="settlement-detail-page__detail-panel-body settlement-detail-page__adjust-panel-body"
|
class="settlement-detail-page__detail-panel-body settlement-detail-page__adjust-panel-body"
|
||||||
>
|
>
|
||||||
|
<div v-if="isReceivable" class="settlement-detail-page__adjust-toolbar">
|
||||||
|
<el-button type="primary" :disabled="adjustDialog.loading" @click="addAdjustFee">
|
||||||
|
新增费用
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
<el-table v-loading="adjustDialog.loading" :data="adjustRows" border>
|
<el-table v-loading="adjustDialog.loading" :data="adjustRows" border>
|
||||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
|
<el-table-column label="来源" width="110" align="center">
|
||||||
|
<template #default="{ row }">{{ feeSourceLabel(row.dataSource) }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-for="column in adjustFeeColumns"
|
v-for="column in adjustFeeColumns"
|
||||||
:key="column.prop || column.feeItemName"
|
:key="column.prop || column.feeItemName"
|
||||||
@@ -201,29 +209,92 @@
|
|||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
<span
|
||||||
|
v-if="row.manualFee && ['billingFactor', 'billingType'].includes(column.prop)"
|
||||||
|
>
|
||||||
|
-
|
||||||
|
</span>
|
||||||
<el-input
|
<el-input
|
||||||
v-if="column.prop === 'transportQuantityText' && !row.manualFee"
|
v-else-if="column.prop === 'cargoName' && row.manualFee"
|
||||||
|
v-model="row.cargoName"
|
||||||
|
clearable
|
||||||
|
maxlength="100"
|
||||||
|
placeholder="请输入"
|
||||||
|
/>
|
||||||
|
<span v-else-if="column.prop === 'cargoName'">
|
||||||
|
{{ formatDetailCell(row, column.prop) }}
|
||||||
|
</span>
|
||||||
|
<el-cascader
|
||||||
|
v-else-if="column.prop === 'cargoType' && row.manualFee"
|
||||||
|
v-model="row.cargoTypePath"
|
||||||
|
class="settlement-detail-page__adjust-control"
|
||||||
|
:options="transportCargoTypeOptions"
|
||||||
|
:props="cargoTypeCascaderProps"
|
||||||
|
:loading="cargoTypeLoading"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
:filter-method="filterCargoType"
|
||||||
|
placeholder="请选择到二级"
|
||||||
|
@visible-change="visible => visible && loadCargoTypeOptions()"
|
||||||
|
@change="value => handleAdjustCargoTypeChange(row, value)"
|
||||||
|
/>
|
||||||
|
<span v-else-if="column.prop === 'cargoType'">
|
||||||
|
{{ formatDetailCell(row, column.prop) }}
|
||||||
|
</span>
|
||||||
|
<el-select
|
||||||
|
v-else-if="column.prop === 'priceUnit'"
|
||||||
|
v-model="row.priceUnit"
|
||||||
|
class="settlement-detail-page__adjust-control"
|
||||||
|
:loading="priceUnitLoading"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择"
|
||||||
|
@visible-change="visible => visible && loadPriceUnitOptions()"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in priceUnitOptions"
|
||||||
|
:key="item.id || item.dictKey || item.dictValue"
|
||||||
|
:label="item.dictValue"
|
||||||
|
:value="item.dictValue"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-input
|
||||||
|
v-else-if="adjustTextProps.includes(column.prop)"
|
||||||
|
v-model="row[column.prop]"
|
||||||
|
clearable
|
||||||
|
:maxlength="adjustTextMaxlength(column.prop)"
|
||||||
|
placeholder="请输入"
|
||||||
|
/>
|
||||||
|
<el-input
|
||||||
|
v-else-if="column.prop === 'transportQuantityText'"
|
||||||
class="settlement-detail-page__adjust-input"
|
class="settlement-detail-page__adjust-input"
|
||||||
:model-value="row.transportQuantity"
|
:model-value="row.transportQuantity"
|
||||||
inputmode="decimal"
|
inputmode="decimal"
|
||||||
@input="value => handleAdjustDecimalInput(row, 'transportQuantity', value)"
|
@input="value => handleAdjustDecimalInput(row, 'transportQuantity', value)"
|
||||||
/>
|
/>
|
||||||
<el-input
|
<el-input
|
||||||
v-else-if="column.prop === 'mileage' && !row.manualFee"
|
v-else-if="column.prop === 'unitPrice'"
|
||||||
|
class="settlement-detail-page__adjust-input"
|
||||||
|
:model-value="row.unitPrice"
|
||||||
|
inputmode="decimal"
|
||||||
|
@input="value => handleAdjustDecimalInput(row, 'unitPrice', value)"
|
||||||
|
/>
|
||||||
|
<el-input
|
||||||
|
v-else-if="column.prop === 'mileage'"
|
||||||
class="settlement-detail-page__adjust-input"
|
class="settlement-detail-page__adjust-input"
|
||||||
:model-value="row.mileage"
|
:model-value="row.mileage"
|
||||||
inputmode="decimal"
|
inputmode="decimal"
|
||||||
@input="value => handleAdjustDecimalInput(row, 'mileage', value)"
|
@input="value => handleAdjustDecimalInput(row, 'mileage', value)"
|
||||||
/>
|
/>
|
||||||
<el-input
|
<el-input
|
||||||
v-else-if="column.prop === 'freightAmount' && !row.manualFee"
|
v-else-if="column.prop === 'freightAmount'"
|
||||||
class="settlement-detail-page__adjust-input"
|
class="settlement-detail-page__adjust-input"
|
||||||
:model-value="row.freightAmount"
|
:model-value="row.freightAmount"
|
||||||
inputmode="decimal"
|
inputmode="decimal"
|
||||||
@input="value => handleAdjustDecimalInput(row, 'freightAmount', value, 'freight')"
|
@input="value => handleAdjustDecimalInput(row, 'freightAmount', value, 'freight')"
|
||||||
/>
|
/>
|
||||||
<el-input
|
<el-input
|
||||||
v-else-if="column.dynamic && !row.manualFee"
|
v-else-if="column.dynamic"
|
||||||
class="settlement-detail-page__adjust-input"
|
class="settlement-detail-page__adjust-input"
|
||||||
:model-value="row.feeItems[column.feeItemName]"
|
:model-value="row.feeItems[column.feeItemName]"
|
||||||
inputmode="decimal"
|
inputmode="decimal"
|
||||||
@@ -551,6 +622,7 @@ import {
|
|||||||
import * as api from '@/api/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 getContractList } from '@/api/business/contract-manage';
|
||||||
import { getContractOptions as getSettlementContractOptions } from '@/api/settlement/preSettlement';
|
import { getContractOptions as getSettlementContractOptions } from '@/api/settlement/preSettlement';
|
||||||
|
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 { downloadXls } from '@/utils/util';
|
import { downloadXls } from '@/utils/util';
|
||||||
@@ -594,7 +666,20 @@ export default {
|
|||||||
adjustDialog: { visible: false, loading: false, submitting: false, row: null },
|
adjustDialog: { visible: false, loading: false, submitting: false, row: null },
|
||||||
adjustRows: [],
|
adjustRows: [],
|
||||||
adjustDynamicFeeColumns: [],
|
adjustDynamicFeeColumns: [],
|
||||||
adjustCalculateTimers: {},
|
adjustTextProps: ['specification', 'model', 'billingFactor', 'billingType'],
|
||||||
|
cargoTypeOptions: [],
|
||||||
|
cargoTypeFlatOptions: [],
|
||||||
|
cargoTypeLoading: false,
|
||||||
|
cargoTypeRequest: null,
|
||||||
|
cargoTypeCascaderProps: {
|
||||||
|
label: 'cargoName',
|
||||||
|
value: 'id',
|
||||||
|
children: 'children',
|
||||||
|
emitPath: true,
|
||||||
|
},
|
||||||
|
priceUnitOptions: [],
|
||||||
|
priceUnitLoading: false,
|
||||||
|
priceUnitRequest: null,
|
||||||
changeRows: [],
|
changeRows: [],
|
||||||
changePage: { current: 1, size: 10, total: 0 },
|
changePage: { current: 1, size: 10, total: 0 },
|
||||||
changeDialog: { loading: false },
|
changeDialog: { loading: false },
|
||||||
@@ -630,6 +715,9 @@ export default {
|
|||||||
if (this.settlementType === 'payable') return '应付';
|
if (this.settlementType === 'payable') return '应付';
|
||||||
return '应收应付';
|
return '应收应付';
|
||||||
},
|
},
|
||||||
|
isReceivable() {
|
||||||
|
return this.settlementType === 'receivable';
|
||||||
|
},
|
||||||
visibleSearchFields() {
|
visibleSearchFields() {
|
||||||
return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 4);
|
return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 4);
|
||||||
},
|
},
|
||||||
@@ -662,9 +750,14 @@ export default {
|
|||||||
{ label: '变更原因', prop: 'changeReason', minWidth: 220 },
|
{ label: '变更原因', prop: 'changeReason', minWidth: 220 },
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
transportCargoTypeOptions() {
|
||||||
|
return this.cargoTypeOptions.filter(item => item.children?.length);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.loadTransportTypeOptions();
|
this.loadTransportTypeOptions();
|
||||||
|
this.loadCargoTypeOptions();
|
||||||
|
this.loadPriceUnitOptions();
|
||||||
this.loadTable();
|
this.loadTable();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -682,6 +775,152 @@ export default {
|
|||||||
this.transportTypeOptions.find(item => String(item.value) === String(value))?.label || value
|
this.transportTypeOptions.find(item => String(item.value) === String(value))?.label || value
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
loadCargoTypeOptions() {
|
||||||
|
if (this.cargoTypeOptions.length) return Promise.resolve(this.cargoTypeOptions);
|
||||||
|
if (this.cargoTypeRequest) return this.cargoTypeRequest;
|
||||||
|
this.cargoTypeLoading = true;
|
||||||
|
this.cargoTypeRequest = getCargoTypeList(1, 9999)
|
||||||
|
.then(res => {
|
||||||
|
this.cargoTypeOptions = this.buildCargoTypeTree(this.extractRecords(res));
|
||||||
|
this.cargoTypeFlatOptions = this.flattenCargoTypeOptions(this.cargoTypeOptions);
|
||||||
|
return this.cargoTypeOptions;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.cargoTypeLoading = false;
|
||||||
|
this.cargoTypeRequest = null;
|
||||||
|
});
|
||||||
|
return this.cargoTypeRequest;
|
||||||
|
},
|
||||||
|
loadPriceUnitOptions() {
|
||||||
|
if (this.priceUnitOptions.length) return Promise.resolve(this.priceUnitOptions);
|
||||||
|
if (this.priceUnitRequest) return this.priceUnitRequest;
|
||||||
|
this.priceUnitLoading = true;
|
||||||
|
this.priceUnitRequest = getDictionary({ code: 'unit_fee' })
|
||||||
|
.then(res => {
|
||||||
|
this.priceUnitOptions = this.extractRecords(res);
|
||||||
|
return this.priceUnitOptions;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.priceUnitLoading = false;
|
||||||
|
this.priceUnitRequest = null;
|
||||||
|
});
|
||||||
|
return this.priceUnitRequest;
|
||||||
|
},
|
||||||
|
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: this.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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return rootNodes.map(item => this.normalizeCargoTypeNode(item, 1)).filter(Boolean);
|
||||||
|
},
|
||||||
|
normalizeCargoTypeNode(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 => this.normalizeCargoTypeNode(item, level + 1, path))
|
||||||
|
.filter(Boolean)
|
||||||
|
: [];
|
||||||
|
return {
|
||||||
|
...cargoType,
|
||||||
|
id,
|
||||||
|
cargoName: this.formatCargoTypeLabel(cargoType),
|
||||||
|
path,
|
||||||
|
leaf: level === 2,
|
||||||
|
children: children.length ? children : undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
flattenCargoTypeOptions(options = []) {
|
||||||
|
const result = [];
|
||||||
|
const collectOptions = list => {
|
||||||
|
(list || []).forEach(item => {
|
||||||
|
result.push(item);
|
||||||
|
if (item.children?.length) collectOptions(item.children);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
collectOptions(options);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
formatCargoTypeLabel(item = {}) {
|
||||||
|
return item.cargoName || item.name || item.typeName || item.label || '';
|
||||||
|
},
|
||||||
|
filterCargoType(node, keyword) {
|
||||||
|
const text = String(keyword || '').trim();
|
||||||
|
if (!text) return true;
|
||||||
|
const labels = node.pathLabels?.length ? node.pathLabels : [node.label];
|
||||||
|
return (
|
||||||
|
labels.some(label => String(label || '').includes(text)) ||
|
||||||
|
String(node.text || '').includes(text)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
getCargoTypePathLabels(path = []) {
|
||||||
|
let options = this.cargoTypeOptions;
|
||||||
|
const labels = [];
|
||||||
|
(path || []).forEach(value => {
|
||||||
|
const option = (options || []).find(item => String(item.id) === String(value));
|
||||||
|
if (!option) return;
|
||||||
|
labels.push(option.cargoName || '');
|
||||||
|
options = option.children || [];
|
||||||
|
});
|
||||||
|
return labels.filter(Boolean);
|
||||||
|
},
|
||||||
|
resolveCargoTypePath(cargoTypeName) {
|
||||||
|
const name = String(cargoTypeName || '').trim();
|
||||||
|
if (!name) return [];
|
||||||
|
const cargoType = this.cargoTypeFlatOptions.find(item => {
|
||||||
|
const label = this.formatCargoTypeLabel(item);
|
||||||
|
return item.path?.length >= 2 && (label === name || name.endsWith(label));
|
||||||
|
});
|
||||||
|
return cargoType?.path || [];
|
||||||
|
},
|
||||||
|
handleAdjustCargoTypeChange(row, value) {
|
||||||
|
const path =
|
||||||
|
Array.isArray(value) && value.length >= 2
|
||||||
|
? value.slice(0, 2).map(item => String(item))
|
||||||
|
: [];
|
||||||
|
const labels = this.getCargoTypePathLabels(path);
|
||||||
|
row.cargoTypePath = path;
|
||||||
|
row.cargoType = labels.length ? labels[labels.length - 1] : '';
|
||||||
|
row.cargoName = '';
|
||||||
|
},
|
||||||
formatColumnValue(row, column) {
|
formatColumnValue(row, column) {
|
||||||
if (column.prop === 'transportType') return this.transportTypeLabel(row[column.prop]);
|
if (column.prop === 'transportType') return this.transportTypeLabel(row[column.prop]);
|
||||||
if (column.prop === 'transportQuantity') return this.fixedTwoDecimals(row[column.prop]);
|
if (column.prop === 'transportQuantity') return this.fixedTwoDecimals(row[column.prop]);
|
||||||
@@ -750,8 +989,6 @@ export default {
|
|||||||
this.detailDialog.row = null;
|
this.detailDialog.row = null;
|
||||||
},
|
},
|
||||||
closeAdjustPanel() {
|
closeAdjustPanel() {
|
||||||
Object.values(this.adjustCalculateTimers).forEach(timer => clearTimeout(timer));
|
|
||||||
this.adjustCalculateTimers = {};
|
|
||||||
this.adjustDialog.visible = false;
|
this.adjustDialog.visible = false;
|
||||||
this.adjustDialog.row = null;
|
this.adjustDialog.row = null;
|
||||||
},
|
},
|
||||||
@@ -760,8 +997,23 @@ export default {
|
|||||||
this.openWaybillDetail(row);
|
this.openWaybillDetail(row);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this.settlementType === 'payable' && column.prop === 'preSettlementNo') {
|
||||||
|
this.openSettlementDetail('/settlement/pre-settlement', row.preSettlementNo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.settlementType === 'payable' && column.prop === 'formalSettlementNo') {
|
||||||
|
this.openSettlementDetail('/settlement/formal-settlement', row.formalSettlementNo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.openDetailDialog(row);
|
this.openDetailDialog(row);
|
||||||
},
|
},
|
||||||
|
openSettlementDetail(path, detailNo) {
|
||||||
|
if (!detailNo) {
|
||||||
|
this.$message.info('当前记录未关联结算单详情');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$router.push({ path, query: { detailNo } });
|
||||||
|
},
|
||||||
openWaybillDetail(row) {
|
openWaybillDetail(row) {
|
||||||
if (!row.waybillId) {
|
if (!row.waybillId) {
|
||||||
this.$message.info('当前记录未关联运单详情');
|
this.$message.info('当前记录未关联运单详情');
|
||||||
@@ -787,7 +1039,11 @@ export default {
|
|||||||
this.closeDetailPanel();
|
this.closeDetailPanel();
|
||||||
this.adjustDialog = { ...this.adjustDialog, visible: true, row, loading: true };
|
this.adjustDialog = { ...this.adjustDialog, visible: true, row, loading: true };
|
||||||
try {
|
try {
|
||||||
const res = await api.getFeeDetail(row.id);
|
const [res] = await Promise.all([
|
||||||
|
api.getFeeDetail(row.id),
|
||||||
|
this.loadCargoTypeOptions(),
|
||||||
|
this.loadPriceUnitOptions(),
|
||||||
|
]);
|
||||||
const data = res.data?.data || res.data || res || {};
|
const data = res.data?.data || res.data || res || {};
|
||||||
this.adjustDynamicFeeColumns = (data.feeItemNames || []).map(name => ({
|
this.adjustDynamicFeeColumns = (data.feeItemNames || []).map(name => ({
|
||||||
label: name,
|
label: name,
|
||||||
@@ -811,23 +1067,53 @@ export default {
|
|||||||
freightAmount: Number(item.freightAmount || 0),
|
freightAmount: Number(item.freightAmount || 0),
|
||||||
originalAmount: Number(item.originalAmount || 0),
|
originalAmount: Number(item.originalAmount || 0),
|
||||||
feeItems,
|
feeItems,
|
||||||
manualFee: item.billingFactor === '手工调整',
|
dataSource:
|
||||||
feeItemName: item.billingFactor === '手工调整' ? Object.keys(feeItems)[0] || '' : '',
|
item.dataSource || (item.billingFactor === '手工调整' ? '手工录入' : '自动生成'),
|
||||||
feeType: item.billingType === '手工扣费' ? 'deduct' : 'charge',
|
manualFee: item.dataSource === '手工录入' || item.billingFactor === '手工调整',
|
||||||
amount: item.billingFactor === '手工调整' ? Math.abs(Number(item.afterAmount || 0)) : 0,
|
cargoTypePath: this.resolveCargoTypePath(item.cargoType),
|
||||||
};
|
};
|
||||||
if (adjusted.manualFee) this.recalculateManualAdjustRow(adjusted);
|
this.recalculateAdjustRow(adjusted);
|
||||||
else this.recalculateAdjustRow(adjusted);
|
|
||||||
return adjusted;
|
return adjusted;
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
this.adjustDialog.loading = false;
|
this.adjustDialog.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
recalculateManualAdjustRow(row) {
|
addAdjustFee() {
|
||||||
const amount = Number(row.amount || 0);
|
if (!this.isReceivable) return;
|
||||||
row.afterAmount = Number((row.feeType === 'deduct' ? -amount : amount).toFixed(2));
|
const feeItems = this.adjustDynamicFeeColumns.reduce((items, column) => {
|
||||||
row.adjustAmount = Number((row.afterAmount - Number(row.originalAmount || 0)).toFixed(2));
|
items[column.feeItemName] = 0;
|
||||||
|
return items;
|
||||||
|
}, {});
|
||||||
|
this.adjustRows.push({
|
||||||
|
dataSource: '手工录入',
|
||||||
|
cargoName: '',
|
||||||
|
cargoType: '',
|
||||||
|
cargoTypePath: [],
|
||||||
|
specification: '',
|
||||||
|
model: '',
|
||||||
|
billingFactor: '-',
|
||||||
|
billingType: '-',
|
||||||
|
transportQuantity: '',
|
||||||
|
priceUnit: '',
|
||||||
|
unitPrice: '',
|
||||||
|
mileage: null,
|
||||||
|
freightAmount: '',
|
||||||
|
originalAmount: 0,
|
||||||
|
originalAmountText: '-',
|
||||||
|
adjustAmount: 0,
|
||||||
|
afterAmount: 0,
|
||||||
|
feeItems,
|
||||||
|
manualFee: true,
|
||||||
|
remark: '',
|
||||||
|
changeReason: '',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
feeSourceLabel(value) {
|
||||||
|
return value === '手工录入' ? '手工录入' : '自动生成';
|
||||||
|
},
|
||||||
|
adjustTextMaxlength(prop) {
|
||||||
|
return ['billingFactor', 'billingType'].includes(prop) ? 100 : 255;
|
||||||
},
|
},
|
||||||
fixedTwoDecimals(value) {
|
fixedTwoDecimals(value) {
|
||||||
return Number(value || 0).toFixed(2);
|
return Number(value || 0).toFixed(2);
|
||||||
@@ -845,48 +1131,12 @@ export default {
|
|||||||
},
|
},
|
||||||
handleAdjustDecimalInput(row, prop, value, changedField) {
|
handleAdjustDecimalInput(row, prop, value, changedField) {
|
||||||
row[prop] = this.normalizeAdjustDecimal(value);
|
row[prop] = this.normalizeAdjustDecimal(value);
|
||||||
if (['transportQuantity', 'mileage'].includes(prop)) {
|
|
||||||
this.scheduleAdjustedFeeCalculation(row);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.recalculateAdjustRow(row, changedField);
|
this.recalculateAdjustRow(row, changedField);
|
||||||
},
|
},
|
||||||
handleAdjustFeeItemInput(row, feeItemName, value) {
|
handleAdjustFeeItemInput(row, feeItemName, value) {
|
||||||
row.feeItems[feeItemName] = this.normalizeAdjustDecimal(value);
|
row.feeItems[feeItemName] = this.normalizeAdjustDecimal(value);
|
||||||
this.recalculateAdjustRow(row, feeItemName);
|
this.recalculateAdjustRow(row, feeItemName);
|
||||||
},
|
},
|
||||||
scheduleAdjustedFeeCalculation(row) {
|
|
||||||
const key = String(row.id);
|
|
||||||
clearTimeout(this.adjustCalculateTimers[key]);
|
|
||||||
row.calculateSequence = Number(row.calculateSequence || 0) + 1;
|
|
||||||
const sequence = row.calculateSequence;
|
|
||||||
this.adjustCalculateTimers[key] = setTimeout(
|
|
||||||
() => this.calculateAdjustedFee(row, sequence),
|
|
||||||
350
|
|
||||||
);
|
|
||||||
},
|
|
||||||
async calculateAdjustedFee(row, sequence) {
|
|
||||||
if (!this.adjustDialog.row || !row.id) return;
|
|
||||||
row.calculating = true;
|
|
||||||
try {
|
|
||||||
const res = await api.calculateAdjustedFee({
|
|
||||||
detailId: this.adjustDialog.row.id,
|
|
||||||
feeId: row.id,
|
|
||||||
transportQuantity: row.transportQuantity,
|
|
||||||
mileage: row.mileage,
|
|
||||||
freightAmount: row.freightAmount,
|
|
||||||
feeItems: row.feeItems,
|
|
||||||
});
|
|
||||||
if (sequence !== row.calculateSequence || !this.adjustDialog.visible) return;
|
|
||||||
const data = res.data?.data || res.data || res || {};
|
|
||||||
row.freightAmount = Number(data.freightAmount || 0);
|
|
||||||
row.feeItems = { ...row.feeItems, ...(data.feeItems || {}) };
|
|
||||||
row.adjustAmount = Number(data.adjustAmount || 0);
|
|
||||||
row.afterAmount = Number(data.afterAmount || 0);
|
|
||||||
} finally {
|
|
||||||
if (sequence === row.calculateSequence) row.calculating = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
recalculateAdjustRow(row, changedField) {
|
recalculateAdjustRow(row, changedField) {
|
||||||
if (changedField && changedField !== 'freight' && this.isFreightFeeItem(changedField)) {
|
if (changedField && changedField !== 'freight' && this.isFreightFeeItem(changedField)) {
|
||||||
row.freightAmount = Number(row.feeItems[changedField] || 0);
|
row.freightAmount = Number(row.feeItems[changedField] || 0);
|
||||||
@@ -913,28 +1163,25 @@ export default {
|
|||||||
this.$message.warning('没有可调整的费用明细');
|
this.$message.warning('没有可调整的费用明细');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const invalidManualRow = this.adjustRows.find(
|
|
||||||
row =>
|
|
||||||
row.manualFee && (!String(row.feeItemName || '').trim() || Number(row.amount || 0) <= 0)
|
|
||||||
);
|
|
||||||
if (invalidManualRow) {
|
|
||||||
this.$message.warning('请完整填写手工费用项目和金额');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.adjustDialog.submitting = true;
|
this.adjustDialog.submitting = true;
|
||||||
try {
|
try {
|
||||||
await api.adjustFee({
|
await api.adjustFee({
|
||||||
detailId: this.adjustDialog.row.id,
|
detailId: this.adjustDialog.row.id,
|
||||||
rows: this.adjustRows.map(row => ({
|
rows: this.adjustRows.map(row => ({
|
||||||
id: row.id,
|
id: row.id,
|
||||||
|
cargoName: row.cargoName,
|
||||||
|
cargoType: row.cargoType,
|
||||||
|
specification: row.specification,
|
||||||
|
model: row.model,
|
||||||
|
billingFactor: row.billingFactor,
|
||||||
|
billingType: row.billingType,
|
||||||
transportQuantity: row.transportQuantity,
|
transportQuantity: row.transportQuantity,
|
||||||
|
priceUnit: row.priceUnit,
|
||||||
|
unitPrice: row.unitPrice,
|
||||||
mileage: row.mileage,
|
mileage: row.mileage,
|
||||||
freightAmount: row.freightAmount,
|
freightAmount: row.freightAmount,
|
||||||
feeItems: row.feeItems,
|
feeItems: row.feeItems,
|
||||||
manualFee: row.manualFee === true,
|
manualFee: row.manualFee === true,
|
||||||
feeItemName: row.feeItemName,
|
|
||||||
feeType: row.feeType,
|
|
||||||
amount: row.amount,
|
|
||||||
remark: row.remark,
|
remark: row.remark,
|
||||||
changeReason: row.changeReason,
|
changeReason: row.changeReason,
|
||||||
})),
|
})),
|
||||||
@@ -1392,16 +1639,15 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleExport() {
|
handleExport() {
|
||||||
|
const params = this.buildRequestParams(
|
||||||
|
this.normalizeQuery(this.query, 'generateDateRange', 'generateStartDate', 'generateEndDate')
|
||||||
|
);
|
||||||
|
if (this.selection.length) {
|
||||||
|
params.ids = this.selection.map(item => item.id).join(',');
|
||||||
|
}
|
||||||
exportBlob(
|
exportBlob(
|
||||||
'/blade-transport/receivable-payable-detail/export-receivable-payable-detail',
|
'/blade-transport/receivable-payable-detail/export-receivable-payable-detail',
|
||||||
this.buildRequestParams(
|
params,
|
||||||
this.normalizeQuery(
|
|
||||||
this.query,
|
|
||||||
'generateDateRange',
|
|
||||||
'generateStartDate',
|
|
||||||
'generateEndDate'
|
|
||||||
)
|
|
||||||
),
|
|
||||||
{ feedback: true }
|
{ feedback: true }
|
||||||
).then(res => {
|
).then(res => {
|
||||||
downloadXls(
|
downloadXls(
|
||||||
@@ -1508,6 +1754,14 @@ export default {
|
|||||||
const data = res.data?.data || res.data || res || {};
|
const data = res.data?.data || res.data || res || {};
|
||||||
return data.records ? data : { records: data.records || [], total: data.total || 0 };
|
return data.records ? data : { records: data.records || [], total: data.total || 0 };
|
||||||
},
|
},
|
||||||
|
extractRecords(res) {
|
||||||
|
const data = res?.data?.data || res?.data || res;
|
||||||
|
if (Array.isArray(data)) return data;
|
||||||
|
if (Array.isArray(data?.records)) return data.records;
|
||||||
|
if (Array.isArray(data?.data)) return data.data;
|
||||||
|
if (Array.isArray(data?.data?.records)) return data.data.records;
|
||||||
|
return [];
|
||||||
|
},
|
||||||
noop() {},
|
noop() {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -1625,6 +1879,18 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settlement-detail-page__adjust-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-detail-page__adjust-control {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.settlement-detail-page__generate-search {
|
.settlement-detail-page__generate-search {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
padding: 12px 12px 4px;
|
padding: 12px 12px 4px;
|
||||||
|
|||||||
Reference in New Issue
Block a user