1、调整配载、总单
2、新增收付款模块
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="title" width="96%" top="2vh" append-to-body destroy-on-close>
|
||||
<component
|
||||
:is="editorContainer"
|
||||
v-bind="editorContainerProps"
|
||||
@update:model-value="visible = $event"
|
||||
>
|
||||
<div v-loading="loading" class="formal-editor">
|
||||
<section-card title="结算基本信息">
|
||||
<el-form
|
||||
@@ -164,15 +168,27 @@
|
||||
</el-table>
|
||||
</section-card>
|
||||
</div>
|
||||
<template #footer>
|
||||
<template v-if="!pageMode" #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button v-if="editable" type="primary" :loading="saving" @click="handleSave"
|
||||
>提交</el-button
|
||||
>
|
||||
</template>
|
||||
<div v-if="pageMode" class="formal-editor__page-actions">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button v-if="editable" type="primary" :loading="saving" @click="handleSave">
|
||||
提交
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="candidate.visible" title="选择预结算单" width="88%" append-to-body>
|
||||
<el-form :model="candidate.query" inline label-position="right" label-width="160px" class="formal-editor__candidate-search">
|
||||
<el-form
|
||||
:model="candidate.query"
|
||||
inline
|
||||
label-position="right"
|
||||
label-width="160px"
|
||||
class="formal-editor__candidate-search"
|
||||
>
|
||||
<el-form-item label="预结算单号"
|
||||
><el-input v-model="candidate.query.preSettlementNo" clearable
|
||||
/></el-form-item>
|
||||
@@ -222,7 +238,13 @@
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="detailCandidate.visible" title="选择结算明细" width="92%" append-to-body>
|
||||
<el-form :model="detailCandidate.query" inline label-position="right" label-width="160px" class="formal-editor__detail-search">
|
||||
<el-form
|
||||
:model="detailCandidate.query"
|
||||
inline
|
||||
label-position="right"
|
||||
label-width="160px"
|
||||
class="formal-editor__detail-search"
|
||||
>
|
||||
<el-form-item label="批次号"
|
||||
><el-input v-model="detailCandidate.query.batchNo" clearable
|
||||
/></el-form-item>
|
||||
@@ -344,7 +366,7 @@
|
||||
></template
|
||||
>
|
||||
</el-dialog>
|
||||
</el-dialog>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -371,7 +393,16 @@ import {
|
||||
|
||||
export default {
|
||||
name: 'FormalSettlementEditor',
|
||||
props: { modelValue: Boolean, recordId: [String, Number], readonly: Boolean },
|
||||
props: {
|
||||
modelValue: Boolean,
|
||||
recordId: [String, Number],
|
||||
readonly: Boolean,
|
||||
pageMode: Boolean,
|
||||
initialData: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'success'],
|
||||
data() {
|
||||
return {
|
||||
@@ -446,6 +477,22 @@ export default {
|
||||
this.$emit('update:modelValue', value);
|
||||
},
|
||||
},
|
||||
editorContainer() {
|
||||
return this.pageMode ? 'div' : 'el-dialog';
|
||||
},
|
||||
editorContainerProps() {
|
||||
if (this.pageMode) {
|
||||
return { class: 'formal-editor-shell formal-editor-shell--page' };
|
||||
}
|
||||
return {
|
||||
modelValue: this.visible,
|
||||
title: this.title,
|
||||
width: '96%',
|
||||
top: '2vh',
|
||||
appendToBody: true,
|
||||
destroyOnClose: true,
|
||||
};
|
||||
},
|
||||
editable() {
|
||||
return !this.readonly;
|
||||
},
|
||||
@@ -454,8 +501,11 @@ export default {
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
modelValue(value) {
|
||||
if (value) this.initialize();
|
||||
modelValue: {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
if (value) this.initialize();
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@@ -468,6 +518,7 @@ export default {
|
||||
await this.loadContracts();
|
||||
if (!this.recordId) {
|
||||
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
|
||||
if (this.initialData) this.applyInitialData();
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
@@ -490,6 +541,55 @@ export default {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
applyInitialData() {
|
||||
const rows = Array.isArray(this.initialData?.rows) ? this.initialData.rows : [];
|
||||
if (!rows.length) return;
|
||||
const first = rows[0];
|
||||
const settlementType = this.initialData.settlementType || first.settlementType || 'payable';
|
||||
const contractId = first.contractId || null;
|
||||
if (!this.contracts.some(item => String(item.id) === String(contractId))) {
|
||||
this.contracts.push({
|
||||
id: contractId,
|
||||
contractNo: first.contractNo,
|
||||
contractName: first.contractName,
|
||||
projectId: first.projectId,
|
||||
projectName: first.projectName,
|
||||
deptId: first.deptId,
|
||||
deptName: first.deptName,
|
||||
payerName: first.payerName,
|
||||
payeeName: first.payeeName,
|
||||
settlementType,
|
||||
});
|
||||
}
|
||||
this.handleContractChange(contractId);
|
||||
Object.assign(this.form, {
|
||||
contractId,
|
||||
contractNo: first.contractNo || this.form.contractNo,
|
||||
contractName: first.contractName || this.form.contractName,
|
||||
projectId: first.projectId || this.form.projectId,
|
||||
projectName: first.projectName || this.form.projectName,
|
||||
deptId: first.deptId || this.form.deptId,
|
||||
deptName: first.deptName || this.form.deptName,
|
||||
payerName: first.payerName || this.form.payerName,
|
||||
payeeName: first.payeeName || this.form.payeeName,
|
||||
settlementType,
|
||||
settlementTypeName: settlementType === 'receivable' ? '应收' : '应付',
|
||||
currency: first.currency || 'RMB',
|
||||
});
|
||||
this.details = rows.map(row => ({
|
||||
...row,
|
||||
sourceDetailId: row.sourceDetailId || row.id,
|
||||
settlementAmountTax:
|
||||
row.settlementAmountTax ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount,
|
||||
}));
|
||||
this.form.sourceDetailIds = this.details.map(item => item.sourceDetailId);
|
||||
this.form.settlementAmount = this.details.reduce(
|
||||
(total, item) => total + Number(item.settlementAmountTax || 0),
|
||||
0
|
||||
);
|
||||
this.form.localSettlementAmount =
|
||||
this.form.settlementAmount * Number(this.form.exchangeRate || 1);
|
||||
},
|
||||
async loadContracts(keyword = '') {
|
||||
const response = await getContractOptions(keyword);
|
||||
this.contracts = this.unwrapData(response) || [];
|
||||
@@ -497,7 +597,9 @@ export default {
|
||||
handleContractChange(id) {
|
||||
const contract = this.contracts.find(item => String(item.id) === String(id));
|
||||
if (!contract) return;
|
||||
const formalSettlementId = this.form.id;
|
||||
Object.assign(this.form, contract, {
|
||||
id: formalSettlementId,
|
||||
contractId: contract.id,
|
||||
settlementTypeName: contract.settlementType === 'receivable' ? '应收' : '应付',
|
||||
});
|
||||
@@ -724,6 +826,13 @@ export default {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.formal-editor__page-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 0 4px;
|
||||
border-top: 1px solid #eff1f7;
|
||||
gap: 8px;
|
||||
}
|
||||
.formal-editor__links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -1,86 +1,82 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="dialogTitle"
|
||||
width="96%"
|
||||
top="2vh"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
class="pre-settlement-editor"
|
||||
<component
|
||||
:is="editorContainer"
|
||||
v-bind="editorContainerProps"
|
||||
@update:model-value="visible = $event"
|
||||
@closed="handleClosed"
|
||||
>
|
||||
<div v-loading="loading" class="pre-settlement-editor__content">
|
||||
<section-card title="结算基本信息">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="formRules"
|
||||
label-position="right"
|
||||
label-width="auto"
|
||||
class="pre-settlement-editor__form"
|
||||
>
|
||||
<el-row :gutter="24">
|
||||
<el-col v-for="field in formFields" :key="field.prop" :span="field.span || 8">
|
||||
<el-form-item :label="field.label" :prop="field.prop">
|
||||
<el-select
|
||||
v-if="field.type === 'contract'"
|
||||
v-model="form.contractId"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
:remote-method="loadContractOptions"
|
||||
:loading="contractLoading"
|
||||
:disabled="!editable || details.length > 0"
|
||||
placeholder="请选择合同"
|
||||
@change="handleContractChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in contractOptions"
|
||||
:key="item.id"
|
||||
:label="`${item.contractName}(${item.contractNo || '-'})`"
|
||||
:value="item.id"
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="formRules"
|
||||
label-position="right"
|
||||
label-width="auto"
|
||||
class="pre-settlement-editor__form"
|
||||
>
|
||||
<el-row :gutter="24">
|
||||
<el-col v-for="field in formFields" :key="field.prop" :span="field.span || 8">
|
||||
<el-form-item :label="field.label" :prop="field.prop">
|
||||
<el-select
|
||||
v-if="field.type === 'contract'"
|
||||
v-model="form.contractId"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
:remote-method="loadContractOptions"
|
||||
:loading="contractLoading"
|
||||
:disabled="!editable || details.length > 0"
|
||||
placeholder="请选择合同"
|
||||
@change="handleContractChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in contractOptions"
|
||||
:key="item.id"
|
||||
:label="`${item.contractName}(${item.contractNo || '-'})`"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-date-picker
|
||||
v-else-if="field.type === 'date'"
|
||||
v-model="form[field.prop]"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
format="YYYY-MM-DD"
|
||||
:disabled="!editable || form.currency === 'RMB'"
|
||||
placeholder="请选择"
|
||||
@change="recalculateLocalAmount"
|
||||
/>
|
||||
</el-select>
|
||||
<el-date-picker
|
||||
v-else-if="field.type === 'date'"
|
||||
v-model="form[field.prop]"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
format="YYYY-MM-DD"
|
||||
:disabled="!editable || form.currency === 'RMB'"
|
||||
placeholder="请选择"
|
||||
@change="recalculateLocalAmount"
|
||||
/>
|
||||
<el-input-number
|
||||
v-else-if="field.type === 'number'"
|
||||
v-model="form[field.prop]"
|
||||
:min="0"
|
||||
:precision="6"
|
||||
:controls="false"
|
||||
:disabled="!editable || form.currency === 'RMB'"
|
||||
@change="recalculateLocalAmount"
|
||||
/>
|
||||
<el-input
|
||||
v-else-if="field.type === 'textarea'"
|
||||
v-model="form[field.prop]"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
:maxlength="field.maxlength"
|
||||
show-word-limit
|
||||
:disabled="!editable"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
<span v-else-if="field.prop === 'settlementType'" class="form-readonly">
|
||||
{{ settlementTypeName }}
|
||||
</span>
|
||||
<span v-else-if="field.money" class="form-readonly">
|
||||
{{ formatMoney(form[field.prop], moneyCurrency(field.prop)) }}
|
||||
</span>
|
||||
<span v-else class="form-readonly">{{ displayValue(form[field.prop]) }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<el-input-number
|
||||
v-else-if="field.type === 'number'"
|
||||
v-model="form[field.prop]"
|
||||
:min="0"
|
||||
:precision="6"
|
||||
:controls="false"
|
||||
:disabled="!editable || form.currency === 'RMB'"
|
||||
@change="recalculateLocalAmount"
|
||||
/>
|
||||
<el-input
|
||||
v-else-if="field.type === 'textarea'"
|
||||
v-model="form[field.prop]"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
:maxlength="field.maxlength"
|
||||
show-word-limit
|
||||
:disabled="!editable"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
<span v-else-if="field.prop === 'settlementType'" class="form-readonly">
|
||||
{{ settlementTypeName }}
|
||||
</span>
|
||||
<span v-else-if="field.money" class="form-readonly">
|
||||
{{ formatMoney(form[field.prop], moneyCurrency(field.prop)) }}
|
||||
</span>
|
||||
<span v-else class="form-readonly">{{ displayValue(form[field.prop]) }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</section-card>
|
||||
|
||||
<section-card title="结算合计">
|
||||
@@ -130,7 +126,6 @@
|
||||
<el-input-number
|
||||
v-else-if="editable && column.prop === 'adjustAmount'"
|
||||
v-model="row.adjustAmount"
|
||||
:min="row.manualFlag === 1 ? 0 : undefined"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
@change="recalculateSummaryRow(row)"
|
||||
@@ -231,7 +226,7 @@
|
||||
<el-table :data="filteredDetails" border class="pre-settlement-editor__detail-table">
|
||||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||||
<el-table-column
|
||||
v-for="column in detailColumns"
|
||||
v-for="column in visibleDetailColumns"
|
||||
:key="column.prop"
|
||||
:label="column.label"
|
||||
:prop="column.prop"
|
||||
@@ -253,6 +248,12 @@
|
||||
<span v-else-if="column.prop === 'transportQuantityText'">
|
||||
{{ formatQuantity(row) }}
|
||||
</span>
|
||||
<span v-else-if="pageMode && column.prop === 'mileage'">
|
||||
{{ formatDetailMileage(row[column.prop]) }}
|
||||
</span>
|
||||
<span v-else-if="pageMode && column.prop === 'transportType'">
|
||||
{{ transportTypeName(row[column.prop]) }}
|
||||
</span>
|
||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -265,7 +266,10 @@
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{
|
||||
formatMoney(parseFeeItems(row.feeItemsJson)[feeItem], row.currency || form.currency)
|
||||
formatMoney(
|
||||
parseFeeItems(row.feeItemsJson)[feeItem],
|
||||
row.currency || form.currency
|
||||
)
|
||||
}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -301,19 +305,11 @@
|
||||
button-text="上传附件"
|
||||
@change="handleAttachmentChange"
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="!attachments.length"
|
||||
@click="downloadAttachments"
|
||||
>
|
||||
<el-button type="primary" :disabled="!attachments.length" @click="downloadAttachments">
|
||||
批量下载
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
:data="attachments"
|
||||
border
|
||||
@selection-change="handleAttachmentSelectionChange"
|
||||
>
|
||||
<el-table :data="attachments" border @selection-change="handleAttachmentSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column label="文件名" min-width="260" show-overflow-tooltip>
|
||||
@@ -327,7 +323,13 @@
|
||||
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="uploadUserName" label="上传人" width="140" align="center" />
|
||||
<el-table-column prop="uploadTime" label="上传时间" width="170" align="center" sortable />
|
||||
<el-table-column
|
||||
prop="uploadTime"
|
||||
label="上传时间"
|
||||
width="170"
|
||||
align="center"
|
||||
sortable
|
||||
/>
|
||||
<el-table-column label="操作" :width="editable ? 150 : 80" fixed="right" align="center">
|
||||
<template #default="{ row, $index }">
|
||||
<div class="pre-settlement-editor__links">
|
||||
@@ -388,7 +390,7 @@
|
||||
</section-card>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<template v-if="!pageMode" #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button v-if="editable" :loading="saving" @click="saveDraft(false)">保存</el-button>
|
||||
<el-button
|
||||
@@ -400,7 +402,19 @@
|
||||
提交
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<div v-if="pageMode" class="pre-settlement-editor__page-actions">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button v-if="editable" :loading="saving" @click="saveDraft(false)">保存</el-button>
|
||||
<el-button
|
||||
v-if="editable && hasPermission('pre_settlement_submit')"
|
||||
type="primary"
|
||||
:loading="submitting"
|
||||
@click="saveDraft(true)"
|
||||
>
|
||||
提交
|
||||
</el-button>
|
||||
</div>
|
||||
</component>
|
||||
|
||||
<el-dialog
|
||||
v-model="candidateDialog.visible"
|
||||
@@ -558,17 +572,6 @@
|
||||
/>
|
||||
</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"
|
||||
:disabled="adjustDialog.readonly"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="200" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" maxlength="200" :disabled="adjustDialog.readonly" />
|
||||
@@ -644,6 +647,14 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
pageMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
initialData: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'success'],
|
||||
data() {
|
||||
@@ -663,6 +674,7 @@ export default {
|
||||
contractOptions: [],
|
||||
feeOptions: [],
|
||||
feeCategoryOptions: [],
|
||||
transportTypeOptions: [],
|
||||
summaryFees: [],
|
||||
details: [],
|
||||
detailCollapsed: false,
|
||||
@@ -735,6 +747,25 @@ export default {
|
||||
this.$emit('update:modelValue', value);
|
||||
},
|
||||
},
|
||||
editorContainer() {
|
||||
return this.pageMode ? 'div' : 'el-dialog';
|
||||
},
|
||||
editorContainerProps() {
|
||||
if (this.pageMode) {
|
||||
return {
|
||||
class: ['pre-settlement-editor', 'pre-settlement-editor--page'],
|
||||
};
|
||||
}
|
||||
return {
|
||||
modelValue: this.visible,
|
||||
title: this.dialogTitle,
|
||||
width: '96%',
|
||||
top: '2vh',
|
||||
appendToBody: true,
|
||||
destroyOnClose: true,
|
||||
class: 'pre-settlement-editor',
|
||||
};
|
||||
},
|
||||
editable() {
|
||||
return !this.readonly && ['draft', 'returned'].includes(this.form.approvalStatus);
|
||||
},
|
||||
@@ -755,7 +786,28 @@ export default {
|
||||
return this.form.settlementType === 'receivable' ? '应收' : '应付';
|
||||
},
|
||||
summaryTotal() {
|
||||
return this.summaryFees.reduce((total, row) => total + Number(row.settlementAmount || 0), 0);
|
||||
if (this.summaryFees.length) {
|
||||
return this.summaryFees.reduce(
|
||||
(total, row) => total + Number(row.settlementAmount || 0),
|
||||
0
|
||||
);
|
||||
}
|
||||
return this.details.reduce(
|
||||
(total, row) =>
|
||||
total +
|
||||
Number(
|
||||
row.settlementAmountTax ??
|
||||
row.totalAmount ??
|
||||
row.afterAmount ??
|
||||
row.settlementAmount ??
|
||||
0
|
||||
),
|
||||
0
|
||||
);
|
||||
},
|
||||
visibleDetailColumns() {
|
||||
if (!this.pageMode) return this.detailColumns;
|
||||
return this.detailColumns.filter(column => column.prop !== 'settlementAmountNoTax');
|
||||
},
|
||||
detailFeeItemNames() {
|
||||
const names = new Set();
|
||||
@@ -783,8 +835,11 @@ export default {
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
modelValue(value) {
|
||||
if (value) this.initialize();
|
||||
modelValue: {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
if (value) this.initialize();
|
||||
},
|
||||
},
|
||||
summaryTotal(value) {
|
||||
this.form.settlementAmount = Number(value || 0).toFixed(2);
|
||||
@@ -800,7 +855,53 @@ export default {
|
||||
await this.loadContractOptions('');
|
||||
await this.loadFeeOptions();
|
||||
await this.loadFeeCategoryOptions();
|
||||
if (this.pageMode) await this.loadTransportTypeOptions();
|
||||
if (this.recordId) await this.loadDetail();
|
||||
else if (this.initialData) this.applyInitialData();
|
||||
},
|
||||
applyInitialData() {
|
||||
const rows = Array.isArray(this.initialData?.rows) ? this.initialData.rows : [];
|
||||
if (!rows.length) return;
|
||||
const first = rows[0];
|
||||
const settlementType = this.initialData.settlementType || first.settlementType || 'payable';
|
||||
const contractId = first.contractId || '';
|
||||
if (!this.contractOptions.some(item => String(item.id) === String(contractId))) {
|
||||
this.contractOptions.push({
|
||||
id: contractId,
|
||||
contractNo: first.contractNo,
|
||||
contractName: first.contractName,
|
||||
projectId: first.projectId,
|
||||
projectName: first.projectName,
|
||||
deptId: first.deptId,
|
||||
deptName: first.deptName,
|
||||
settlementType,
|
||||
partyA: settlementType === 'receivable' ? first.payeeName : first.payerName,
|
||||
partyB: settlementType === 'receivable' ? first.payerName : first.payeeName,
|
||||
});
|
||||
}
|
||||
this.handleContractChange(contractId);
|
||||
Object.assign(this.form, {
|
||||
contractId,
|
||||
contractNo: first.contractNo || this.form.contractNo,
|
||||
contractName: first.contractName || this.form.contractName,
|
||||
projectId: first.projectId || this.form.projectId,
|
||||
projectName: first.projectName || this.form.projectName,
|
||||
deptId: first.deptId || this.form.deptId,
|
||||
deptName: first.deptName || this.form.deptName,
|
||||
payerName: first.payerName || this.form.payerName,
|
||||
payeeName: first.payeeName || this.form.payeeName,
|
||||
settlementType,
|
||||
currency: first.currency || 'RMB',
|
||||
localCurrency: first.localCurrency || 'RMB',
|
||||
});
|
||||
this.details = rows.map(row => ({
|
||||
...row,
|
||||
sourceDetailId: row.sourceDetailId || row.id,
|
||||
settlementAmountTax:
|
||||
row.settlementAmountTax ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount,
|
||||
feeItemsJson: row.feeItemsJson || JSON.stringify(row.feeItems || {}),
|
||||
}));
|
||||
this.buildSummaryFeesFromDetails();
|
||||
},
|
||||
resetEditor() {
|
||||
this.form = emptyPreSettlementForm();
|
||||
@@ -869,6 +970,10 @@ export default {
|
||||
const { data } = await getDictionary({ code: 'fee_category' });
|
||||
this.feeCategoryOptions = data?.data || [];
|
||||
},
|
||||
async loadTransportTypeOptions() {
|
||||
const { data } = await getDictionary({ code: 'transport_type' });
|
||||
this.transportTypeOptions = data?.data || [];
|
||||
},
|
||||
handleContractChange(id) {
|
||||
const contract = this.contractOptions.find(item => String(item.id) === String(id));
|
||||
if (!contract) {
|
||||
@@ -914,12 +1019,12 @@ export default {
|
||||
await submit({ id: this.form.id });
|
||||
this.$message.success('审批流程已发起');
|
||||
this.visible = false;
|
||||
this.$emit('success');
|
||||
this.$emit('success', this.form.id);
|
||||
return;
|
||||
}
|
||||
this.$message.success('保存成功');
|
||||
await this.loadDetail();
|
||||
this.$emit('success');
|
||||
this.$emit('success', this.form.id);
|
||||
} finally {
|
||||
this[stateKey] = false;
|
||||
}
|
||||
@@ -968,6 +1073,17 @@ export default {
|
||||
);
|
||||
return option?.dictValue || value;
|
||||
},
|
||||
transportTypeName(value) {
|
||||
if (value === undefined || value === null || value === '') return '';
|
||||
const option = this.transportTypeOptions.find(
|
||||
item => String(item.dictKey) === String(value) || String(item.dictValue) === String(value)
|
||||
);
|
||||
return option?.dictValue || value;
|
||||
},
|
||||
formatDetailMileage(value) {
|
||||
if (value === undefined || value === null || value === '' || Number(value) === -1) return '';
|
||||
return value;
|
||||
},
|
||||
removeSummaryFee(index) {
|
||||
this.summaryFees.splice(index, 1);
|
||||
},
|
||||
@@ -979,6 +1095,75 @@ export default {
|
||||
this.$message.warning('结算金额不能小于0');
|
||||
}
|
||||
},
|
||||
buildSummaryFeesFromDetails() {
|
||||
const summaryMap = new Map();
|
||||
const defaultFreightItem =
|
||||
this.feeOptions
|
||||
.flatMap(item => item.feeItems || [])
|
||||
.find(name => this.isFreightFeeItem(name)) || '运输费';
|
||||
const appendSummary = (feeItem, value, fallbackFeeType = '') => {
|
||||
const amount = Number(value || 0);
|
||||
if (!feeItem || !Number.isFinite(amount) || Math.abs(amount) < 0.005) return;
|
||||
const feeOption = this.feeOptions.find(item =>
|
||||
(item.feeItems || []).some(name => String(name) === String(feeItem))
|
||||
);
|
||||
const feeType = feeOption?.feeType || fallbackFeeType || '';
|
||||
const key = `${feeType}\u0000${feeItem}`;
|
||||
const current = summaryMap.get(key) || {
|
||||
id: '',
|
||||
feeType,
|
||||
feeItem,
|
||||
originalAmount: 0,
|
||||
adjustAmount: 0,
|
||||
settlementAmount: 0,
|
||||
remark: '',
|
||||
manualFlag: 0,
|
||||
};
|
||||
current.originalAmount = Number((current.originalAmount + amount).toFixed(2));
|
||||
current.settlementAmount = current.originalAmount;
|
||||
summaryMap.set(key, current);
|
||||
};
|
||||
|
||||
this.details.forEach(row => {
|
||||
const feeItems = this.parseFeeItems(row.feeItemsJson || row.feeItems);
|
||||
const feeItemEntries = Object.entries(feeItems);
|
||||
feeItemEntries.forEach(([feeItem, amount]) =>
|
||||
appendSummary(feeItem, amount, row.feeType)
|
||||
);
|
||||
|
||||
let knownAmount = feeItemEntries.reduce(
|
||||
(total, [, amount]) => total + Number(amount || 0),
|
||||
0
|
||||
);
|
||||
if (!feeItemEntries.some(([feeItem]) => this.isFreightFeeItem(feeItem))) {
|
||||
const freightAmount = Number(row.freightAmount || 0);
|
||||
appendSummary(defaultFreightItem, freightAmount, row.feeType);
|
||||
knownAmount += freightAmount;
|
||||
}
|
||||
if (!feeItemEntries.some(([feeItem]) => !this.isFreightFeeItem(feeItem))) {
|
||||
const otherFeeAmount = Number(row.otherFeeAmount || 0);
|
||||
appendSummary('其他费用', otherFeeAmount, row.feeType);
|
||||
knownAmount += otherFeeAmount;
|
||||
}
|
||||
|
||||
const totalAmount = Number(
|
||||
row.settlementAmountTax ??
|
||||
row.totalAmount ??
|
||||
row.afterAmount ??
|
||||
row.settlementAmount ??
|
||||
0
|
||||
);
|
||||
const residualAmount = Number((totalAmount - knownAmount).toFixed(2));
|
||||
if (residualAmount > 0) appendSummary('其他费用', residualAmount, row.feeType);
|
||||
});
|
||||
|
||||
const manualFees = this.summaryFees.filter(row => row.manualFlag === 1);
|
||||
this.summaryFees = [...summaryMap.values(), ...manualFees];
|
||||
this.form.settlementAmount = this.summaryFees
|
||||
.reduce((total, row) => total + Number(row.settlementAmount || 0), 0)
|
||||
.toFixed(2);
|
||||
this.recalculateLocalAmount();
|
||||
},
|
||||
recalculateLocalAmount() {
|
||||
const rate = this.form.currency === 'RMB' ? 1 : Number(this.form.exchangeRate || 0);
|
||||
this.form.localSettlementAmount = (Number(this.form.settlementAmount || 0) * rate).toFixed(2);
|
||||
@@ -1054,12 +1239,13 @@ export default {
|
||||
await this.$confirm('确认将该明细踢出预结算单?', '提示', { type: 'warning' });
|
||||
if (!this.form.id || !row.id || row.id === row.sourceDetailId) {
|
||||
this.details = this.details.filter(item => item !== row);
|
||||
this.buildSummaryFeesFromDetails();
|
||||
return;
|
||||
}
|
||||
await removeDetail(this.form.id, row.id);
|
||||
this.$message.success('已移除');
|
||||
await this.loadDetail();
|
||||
this.$emit('success');
|
||||
this.$emit('success', this.form.id);
|
||||
},
|
||||
handleDetailQuery() {
|
||||
this.appliedDetailQuery = { ...this.detailQuery };
|
||||
@@ -1138,7 +1324,7 @@ export default {
|
||||
this.adjustDialog.visible = false;
|
||||
this.$message.success('结算明细调整成功');
|
||||
await this.loadDetail();
|
||||
this.$emit('success');
|
||||
this.$emit('success', this.form.id);
|
||||
} finally {
|
||||
this.adjustDialog.saving = false;
|
||||
}
|
||||
@@ -1313,6 +1499,19 @@ export default {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&--page &__content {
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
&__page-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 0 4px;
|
||||
border-top: 1px solid #eff1f7;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__form {
|
||||
:deep(.el-select),
|
||||
:deep(.el-date-editor),
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<basic-container class="formal-settlement-form-page">
|
||||
<div class="formal-settlement-form-page__title">{{ pageTitle }}</div>
|
||||
<formal-settlement-editor
|
||||
v-model="editorVisible"
|
||||
page-mode
|
||||
:record-id="recordId"
|
||||
:readonly="readonly"
|
||||
:initial-data="transferPayload"
|
||||
/>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormalSettlementEditor from './components/formal-settlement-editor.vue';
|
||||
import { readSettlementTransfer, removeSettlementTransfer } from '@/utils/settlement-transfer';
|
||||
|
||||
export default {
|
||||
name: 'FormalSettlementForm',
|
||||
components: { FormalSettlementEditor },
|
||||
data() {
|
||||
return {
|
||||
editorVisible: true,
|
||||
transferPayload: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
recordId() {
|
||||
return this.$route.query.id || '';
|
||||
},
|
||||
readonly() {
|
||||
return this.$route.query.mode === 'view';
|
||||
},
|
||||
pageTitle() {
|
||||
return this.readonly ? '查看正式结算' : this.recordId ? '编辑正式结算' : '新增正式结算';
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
editorVisible(value) {
|
||||
if (!value) this.goBack();
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.transferPayload = readSettlementTransfer(this.$route.query.transferToken);
|
||||
this.syncTagTitle();
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
removeSettlementTransfer(this.$route.query.transferToken);
|
||||
this.$router.push('/settlement/formal-settlement');
|
||||
},
|
||||
syncTagTitle() {
|
||||
this.$nextTick(() => {
|
||||
this.$store.commit('SET_TAG', {
|
||||
fullPath: this.$route.fullPath,
|
||||
name: this.pageTitle,
|
||||
});
|
||||
this.$router.$avueRouter.setTitle(this.pageTitle);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.formal-settlement-form-page {
|
||||
&__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 24px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
|
||||
&::before {
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
margin-right: 8px;
|
||||
background: #409eff;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.formal-settlement-form-page.basic-container .basic-container__card > .el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -41,7 +41,11 @@
|
||||
</section>
|
||||
|
||||
<section class="formal-page__table-panel">
|
||||
<el-tabs type="border-card" v-model="activeSettlementType" @tab-change="handleSettlementTypeChange">
|
||||
<el-tabs
|
||||
type="border-card"
|
||||
v-model="activeSettlementType"
|
||||
@tab-change="handleSettlementTypeChange"
|
||||
>
|
||||
<el-tab-pane label="应付" name="payable" />
|
||||
<el-tab-pane label="应收" name="receivable" />
|
||||
</el-tabs>
|
||||
@@ -336,10 +340,16 @@ export default {
|
||||
this.loadTable();
|
||||
},
|
||||
openCreate() {
|
||||
this.editor = { visible: true, id: null, readonly: false };
|
||||
this.$router.push({
|
||||
path: '/settlement/formal-settlement/form',
|
||||
query: { mode: 'add', name: '新增正式结算' },
|
||||
});
|
||||
},
|
||||
openEdit(row) {
|
||||
this.editor = { visible: true, id: row.id, readonly: false };
|
||||
this.$router.push({
|
||||
path: '/settlement/formal-settlement/form',
|
||||
query: { mode: 'edit', id: row.id, name: '编辑正式结算' },
|
||||
});
|
||||
},
|
||||
openView(row) {
|
||||
this.editor = { visible: true, id: row.id, readonly: true };
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<basic-container class="pre-settlement-form-page">
|
||||
<div class="pre-settlement-form-page__title">{{ pageTitle }}</div>
|
||||
<pre-settlement-editor
|
||||
v-model="editorVisible"
|
||||
page-mode
|
||||
:record-id="recordId"
|
||||
:initial-data="transferPayload"
|
||||
@success="handleSuccess"
|
||||
/>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PreSettlementEditor from './components/pre-settlement-editor.vue';
|
||||
import { readSettlementTransfer, removeSettlementTransfer } from '@/utils/settlement-transfer';
|
||||
|
||||
export default {
|
||||
name: 'PreSettlementForm',
|
||||
components: { PreSettlementEditor },
|
||||
data() {
|
||||
return {
|
||||
editorVisible: true,
|
||||
savedRecordId: '',
|
||||
transferPayload: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
recordId() {
|
||||
return this.$route.query.id || '';
|
||||
},
|
||||
pageTitle() {
|
||||
return this.recordId || this.savedRecordId ? '编辑预结算' : '新增预结算';
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
editorVisible(value) {
|
||||
if (!value) this.goBack();
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.transferPayload = readSettlementTransfer(this.$route.query.transferToken);
|
||||
this.syncTagTitle();
|
||||
},
|
||||
methods: {
|
||||
handleSuccess(recordId) {
|
||||
this.savedRecordId = recordId || this.savedRecordId;
|
||||
this.syncTagTitle();
|
||||
},
|
||||
goBack() {
|
||||
removeSettlementTransfer(this.$route.query.transferToken);
|
||||
this.$router.push('/settlement/pre-settlement');
|
||||
},
|
||||
syncTagTitle() {
|
||||
this.$nextTick(() => {
|
||||
this.$store.commit('SET_TAG', {
|
||||
fullPath: this.$route.fullPath,
|
||||
name: this.pageTitle,
|
||||
});
|
||||
this.$router.$avueRouter.setTitle(this.pageTitle);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pre-settlement-form-page {
|
||||
&__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 24px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
|
||||
&::before {
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
margin-right: 8px;
|
||||
background: #409eff;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.pre-settlement-form-page.basic-container .basic-container__card > .el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -473,10 +473,16 @@ export default {
|
||||
this.selection = rows;
|
||||
},
|
||||
openCreate() {
|
||||
this.editor = { visible: true, id: '', readonly: false };
|
||||
this.$router.push({
|
||||
path: '/settlement/pre-settlement/form',
|
||||
query: { mode: 'add', name: '新增预结算' },
|
||||
});
|
||||
},
|
||||
openEdit(row) {
|
||||
this.editor = { visible: true, id: row.id, readonly: false };
|
||||
this.$router.push({
|
||||
path: '/settlement/pre-settlement/form',
|
||||
query: { mode: 'edit', id: row.id, name: '编辑预结算' },
|
||||
});
|
||||
},
|
||||
openView(row) {
|
||||
this.editor = { visible: true, id: row.id, readonly: true };
|
||||
|
||||
@@ -79,10 +79,18 @@
|
||||
<el-table-column label="操作" width="120" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="settlement-detail-page__actions">
|
||||
<el-link v-if="row.settlementStatus === 'pending'" type="primary" @click="openAdjustDialog(row)">
|
||||
<el-link
|
||||
v-if="row.settlementStatus === 'pending'"
|
||||
type="primary"
|
||||
@click="openAdjustDialog(row)"
|
||||
>
|
||||
调整
|
||||
</el-link>
|
||||
<el-link v-if="row.settlementStatus === 'pending'" type="danger" @click="closeRow(row)">
|
||||
<el-link
|
||||
v-if="row.settlementStatus === 'pending'"
|
||||
type="danger"
|
||||
@click="closeRow(row)"
|
||||
>
|
||||
关闭
|
||||
</el-link>
|
||||
<el-link v-else type="primary" disabled>-</el-link>
|
||||
@@ -112,12 +120,7 @@
|
||||
<header class="settlement-detail-page__detail-panel-header">
|
||||
<h3>单据详情:{{ detailDialog.title }}</h3>
|
||||
<el-tooltip content="关闭" placement="top">
|
||||
<el-button
|
||||
:icon="Close"
|
||||
text
|
||||
aria-label="关闭单据详情"
|
||||
@click="closeDetailPanel"
|
||||
/>
|
||||
<el-button :icon="Close" text aria-label="关闭单据详情" @click="closeDetailPanel" />
|
||||
</el-tooltip>
|
||||
</header>
|
||||
<div class="settlement-detail-page__detail-panel-body">
|
||||
@@ -180,12 +183,7 @@
|
||||
<header class="settlement-detail-page__detail-panel-header">
|
||||
<h3>调整费用:{{ adjustDialog.row?.documentNo || '-' }}</h3>
|
||||
<el-tooltip content="关闭" placement="top">
|
||||
<el-button
|
||||
:icon="Close"
|
||||
text
|
||||
aria-label="关闭调整费用"
|
||||
@click="closeAdjustPanel"
|
||||
/>
|
||||
<el-button :icon="Close" text aria-label="关闭调整费用" @click="closeAdjustPanel" />
|
||||
</el-tooltip>
|
||||
</header>
|
||||
<div
|
||||
@@ -346,7 +344,6 @@
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadTransferCandidates">查询</el-button>
|
||||
<el-button type="primary" @click="confirmTransferSelection">确认选择</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -477,11 +474,7 @@
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="generateDialog.visible = false">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="previewDialog.loading"
|
||||
@click="openGeneratePreview"
|
||||
>
|
||||
<el-button type="primary" :loading="previewDialog.loading" @click="openGeneratePreview">
|
||||
生成费用
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -513,7 +506,7 @@
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="previewDialog.visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="generateDialog.visible = true">上一步</el-button>
|
||||
<el-button type="primary" @click="backToGenerateDialog">上一步</el-button>
|
||||
<el-button type="primary" :loading="previewDialog.submitting" @click="submitGenerateFee">
|
||||
提交
|
||||
</el-button>
|
||||
@@ -537,9 +530,11 @@ 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 { getContractOptions as getSettlementContractOptions } from '@/api/settlement/preSettlement';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { getDictionary } from '@/api/system/dictbiz';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
import { createSettlementTransfer } from '@/utils/settlement-transfer';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -567,7 +562,13 @@ export default {
|
||||
rows: [],
|
||||
selection: [],
|
||||
page: { current: 1, size: 10, total: 0 },
|
||||
detailDialog: { visible: false, title: '费用明细', activeTab: 'fee', row: null, loading: false },
|
||||
detailDialog: {
|
||||
visible: false,
|
||||
title: '费用明细',
|
||||
activeTab: 'fee',
|
||||
row: null,
|
||||
loading: false,
|
||||
},
|
||||
feeRows: [],
|
||||
dynamicFeeColumns: [],
|
||||
adjustDialog: { visible: false, loading: false, submitting: false, row: null },
|
||||
@@ -664,7 +665,10 @@ export default {
|
||||
if (column.prop === 'transportType') return this.transportTypeLabel(row[column.prop]);
|
||||
if (column.prop === 'transportQuantity') return this.fixedTwoDecimals(row[column.prop]);
|
||||
if (column.dynamic) {
|
||||
return this.money(this.normalizeFeeItems(row.feeItems)[column.feeItemName], row.currency || 'RMB');
|
||||
return this.money(
|
||||
this.normalizeFeeItems(row.feeItems)[column.feeItemName],
|
||||
row.currency || 'RMB'
|
||||
);
|
||||
}
|
||||
return this.formatDetailCell(row, column.prop);
|
||||
},
|
||||
@@ -673,7 +677,12 @@ export default {
|
||||
this.tableFeeItemNames = [];
|
||||
try {
|
||||
const params = this.buildRequestParams(
|
||||
this.normalizeQuery(this.query, 'generateDateRange', 'generateStartDate', 'generateEndDate')
|
||||
this.normalizeQuery(
|
||||
this.query,
|
||||
'generateDateRange',
|
||||
'generateStartDate',
|
||||
'generateEndDate'
|
||||
)
|
||||
);
|
||||
const res = await api.getList(this.page.current, this.page.size, params);
|
||||
const data = this.unwrapPage(res);
|
||||
@@ -685,8 +694,7 @@ export default {
|
||||
}
|
||||
},
|
||||
async loadContracts() {
|
||||
const contractCategory =
|
||||
this.settlementType === 'payable' ? '承运商合同' : '客户合同';
|
||||
const contractCategory = this.settlementType === 'payable' ? '承运商合同' : '客户合同';
|
||||
this.contractLoading = true;
|
||||
try {
|
||||
const res = await getContractList(1, 999, {
|
||||
@@ -746,7 +754,13 @@ export default {
|
||||
},
|
||||
async openDetailDialog(row) {
|
||||
this.closeAdjustPanel();
|
||||
this.detailDialog = { ...this.detailDialog, visible: true, row, title: row.documentNo, activeTab: 'fee' };
|
||||
this.detailDialog = {
|
||||
...this.detailDialog,
|
||||
visible: true,
|
||||
row,
|
||||
title: row.documentNo,
|
||||
activeTab: 'fee',
|
||||
};
|
||||
await this.loadFeeDetail();
|
||||
},
|
||||
async openAdjustDialog(row) {
|
||||
@@ -778,13 +792,9 @@ export default {
|
||||
originalAmount: Number(item.originalAmount || 0),
|
||||
feeItems,
|
||||
manualFee: item.billingFactor === '手工调整',
|
||||
feeItemName:
|
||||
item.billingFactor === '手工调整' ? Object.keys(feeItems)[0] || '' : '',
|
||||
feeItemName: item.billingFactor === '手工调整' ? Object.keys(feeItems)[0] || '' : '',
|
||||
feeType: item.billingType === '手工扣费' ? 'deduct' : 'charge',
|
||||
amount:
|
||||
item.billingFactor === '手工调整'
|
||||
? Math.abs(Number(item.afterAmount || 0))
|
||||
: 0,
|
||||
amount: item.billingFactor === '手工调整' ? Math.abs(Number(item.afterAmount || 0)) : 0,
|
||||
};
|
||||
if (adjusted.manualFee) this.recalculateManualAdjustRow(adjusted);
|
||||
else this.recalculateAdjustRow(adjusted);
|
||||
@@ -870,7 +880,9 @@ export default {
|
||||
0
|
||||
);
|
||||
const hasFreightItem = Object.keys(row.feeItems).some(this.isFreightFeeItem);
|
||||
row.afterAmount = Number((hasFreightItem ? feeItemTotal : Number(row.freightAmount || 0) + feeItemTotal).toFixed(2));
|
||||
row.afterAmount = Number(
|
||||
(hasFreightItem ? feeItemTotal : Number(row.freightAmount || 0) + feeItemTotal).toFixed(2)
|
||||
);
|
||||
row.adjustAmount = Number((row.afterAmount - Number(row.originalAmount || 0)).toFixed(2));
|
||||
},
|
||||
isFreightFeeItem(name) {
|
||||
@@ -882,7 +894,8 @@ export default {
|
||||
return;
|
||||
}
|
||||
const invalidManualRow = this.adjustRows.find(
|
||||
row => row.manualFee && (!String(row.feeItemName || '').trim() || Number(row.amount || 0) <= 0)
|
||||
row =>
|
||||
row.manualFee && (!String(row.feeItemName || '').trim() || Number(row.amount || 0) <= 0)
|
||||
);
|
||||
if (invalidManualRow) {
|
||||
this.$message.warning('请完整填写手工费用项目和金额');
|
||||
@@ -1050,7 +1063,9 @@ export default {
|
||||
return value !== null && value !== undefined && String(value).trim() !== '';
|
||||
};
|
||||
const normalizeSettlementType = value => {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
const normalized = String(value || '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (normalized === '应收') return 'receivable';
|
||||
if (normalized === '应付') return 'payable';
|
||||
return normalized;
|
||||
@@ -1061,12 +1076,13 @@ export default {
|
||||
const expectedSettlementType = normalizeSettlementType(this.settlementType);
|
||||
const matchesSettlementType =
|
||||
!expectedSettlementType || rowSettlementType === expectedSettlementType;
|
||||
const status = String(row.settlementStatus || '').trim().toLowerCase();
|
||||
const status = String(row.settlementStatus || '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const isClosed =
|
||||
[row.closed, row.isClosed, row.closeFlag, row.closedFlag].some(
|
||||
value => value === true || String(value).trim().toLowerCase() === 'true'
|
||||
) ||
|
||||
['closed', 'close', '已关闭'].includes(status);
|
||||
) || ['closed', 'close', '已关闭'].includes(status);
|
||||
const hasPreSettlement = [
|
||||
row.preSettlementId,
|
||||
row.preSettlementIds,
|
||||
@@ -1087,13 +1103,6 @@ export default {
|
||||
!hasFormalSettlement
|
||||
);
|
||||
},
|
||||
confirmTransferSelection() {
|
||||
if (!this.transferSelection.length) {
|
||||
this.$message.warning('请选择需要转结算的明细');
|
||||
return;
|
||||
}
|
||||
this.$message.success(`已选择${this.transferSelection.length}条明细`);
|
||||
},
|
||||
transferSelectionChange(selection) {
|
||||
this.transferSelection = selection;
|
||||
},
|
||||
@@ -1102,20 +1111,57 @@ export default {
|
||||
this.$message.warning('请选择需要转结算的明细');
|
||||
return;
|
||||
}
|
||||
if (this.transferSelection.some(item => !item.contractId && !item.contractNo)) {
|
||||
this.$message.warning('所选明细缺少合同信息,无法转结算');
|
||||
return;
|
||||
}
|
||||
const contractKeys = new Set(
|
||||
this.transferSelection.map(item =>
|
||||
item.contractNo ? `no:${item.contractNo}` : `id:${item.contractId}`
|
||||
)
|
||||
);
|
||||
if (contractKeys.size > 1) {
|
||||
this.$message.warning('请选择同一合同下的明细进行转结算');
|
||||
return;
|
||||
}
|
||||
this.transferDialog.submitting = true;
|
||||
try {
|
||||
await api.transferSettlement({
|
||||
settlementType: this.settlementType || undefined,
|
||||
settlementBillType: this.transferForm.settlementBillType,
|
||||
ids: this.transferSelection.map(item => item.id),
|
||||
const transferRows = await this.resolveTransferContractIds(this.transferSelection);
|
||||
if (!transferRows) return;
|
||||
const settlementBillType = this.transferForm.settlementBillType;
|
||||
const transferToken = createSettlementTransfer({
|
||||
settlementBillType,
|
||||
settlementType: this.settlementType || transferRows[0]?.settlementType,
|
||||
rows: transferRows,
|
||||
});
|
||||
this.$message.success('转结算成功');
|
||||
const isPreSettlement = settlementBillType === 'pre';
|
||||
this.transferDialog.visible = false;
|
||||
this.loadTable();
|
||||
await this.$router.push({
|
||||
path: isPreSettlement
|
||||
? '/settlement/pre-settlement/form'
|
||||
: '/settlement/formal-settlement/form',
|
||||
query: {
|
||||
mode: 'add',
|
||||
transferToken,
|
||||
name: isPreSettlement ? '新增预结算' : '新增正式结算',
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
this.transferDialog.submitting = false;
|
||||
}
|
||||
},
|
||||
async resolveTransferContractIds(rows) {
|
||||
if (rows.every(item => item.contractId)) return rows;
|
||||
const contractNo = rows.find(item => item.contractNo)?.contractNo;
|
||||
const { data } = await getSettlementContractOptions(contractNo);
|
||||
const contracts = data?.data || [];
|
||||
const contract = contracts.find(item => String(item.contractNo) === String(contractNo));
|
||||
if (!contract?.id) {
|
||||
this.$message.warning('未找到所选明细对应的有效合同,无法转结算');
|
||||
return null;
|
||||
}
|
||||
return rows.map(item => ({ ...item, contractId: item.contractId || contract.id }));
|
||||
},
|
||||
openGenerateDialog() {
|
||||
this.generateDialog.visible = true;
|
||||
this.generateQuery = {};
|
||||
@@ -1127,8 +1173,9 @@ export default {
|
||||
this.loadContracts();
|
||||
},
|
||||
handleGenerateContractChange(contractId) {
|
||||
this.syncBillingPlanOptions(contractId);
|
||||
this.generateQuery.billingPlanId = '';
|
||||
const options = this.syncBillingPlanOptions(contractId);
|
||||
const selected = options.find(item => item.defaultPlan) || options[0];
|
||||
this.generateQuery.billingPlanId = selected?.id || '';
|
||||
},
|
||||
async loadGenerateWaybills() {
|
||||
if (!this.generateQuery.contractId || !this.generateQuery.billingPlanId) {
|
||||
@@ -1138,9 +1185,18 @@ export default {
|
||||
this.generateDialog.loading = true;
|
||||
try {
|
||||
const params = this.buildRequestParams(
|
||||
this.normalizeQuery(this.generateQuery, 'finishDateRange', 'finishStartDate', 'finishEndDate')
|
||||
this.normalizeQuery(
|
||||
this.generateQuery,
|
||||
'finishDateRange',
|
||||
'finishStartDate',
|
||||
'finishEndDate'
|
||||
)
|
||||
);
|
||||
const res = await api.getGenerateWaybills(
|
||||
this.generatePage.current,
|
||||
this.generatePage.size,
|
||||
params
|
||||
);
|
||||
const res = await api.getGenerateWaybills(this.generatePage.current, this.generatePage.size, params);
|
||||
const data = this.unwrapPage(res);
|
||||
this.generateRows = data.records || [];
|
||||
this.generatePage.total = data.total || 0;
|
||||
@@ -1188,12 +1244,49 @@ export default {
|
||||
minWidth: 130,
|
||||
align: 'right',
|
||||
}));
|
||||
const selectedWaybillsById = new Map();
|
||||
const selectedWaybillsByNo = new Map();
|
||||
this.generateSelection.forEach(item => {
|
||||
[item.id, item.waybillId, item.sourceWaybillId].forEach(id => {
|
||||
if (id !== null && id !== undefined && id !== '') {
|
||||
selectedWaybillsById.set(String(id), item);
|
||||
}
|
||||
});
|
||||
if (item.waybillNo) selectedWaybillsByNo.set(String(item.waybillNo), item);
|
||||
});
|
||||
this.previewRows = (data.records || []).map(row => {
|
||||
const dynamic = {};
|
||||
(data.feeItemNames || []).forEach((name, index) => {
|
||||
dynamic[`feeItem${index}`] = row.feeItems?.[name] || '';
|
||||
});
|
||||
return { ...row, ...dynamic };
|
||||
const sourceWaybillId =
|
||||
row.waybillId || row.sourceWaybillId || row.sourceId || row.businessId || row.id;
|
||||
const sourceWaybill =
|
||||
selectedWaybillsById.get(String(sourceWaybillId || '')) ||
|
||||
selectedWaybillsByNo.get(String(row.waybillNo || row.waybillNumber || ''));
|
||||
return {
|
||||
...row,
|
||||
customerName:
|
||||
row.customerName ||
|
||||
row.customerUnitName ||
|
||||
row.clientName ||
|
||||
sourceWaybill?.customerName ||
|
||||
'',
|
||||
waybillNo:
|
||||
row.waybillNo ||
|
||||
row.waybillNumber ||
|
||||
row.sourceWaybillNo ||
|
||||
sourceWaybill?.waybillNo ||
|
||||
'',
|
||||
vehicleNo:
|
||||
row.vehicleNo ||
|
||||
row.plateNo ||
|
||||
row.carNo ||
|
||||
row.vehicleNumber ||
|
||||
sourceWaybill?.vehicleNo ||
|
||||
'',
|
||||
...dynamic,
|
||||
};
|
||||
});
|
||||
this.previewPage.total = data.total || 0;
|
||||
return true;
|
||||
@@ -1207,6 +1300,12 @@ export default {
|
||||
this.previewPage.current = 1;
|
||||
this.loadGeneratePreview();
|
||||
},
|
||||
backToGenerateDialog() {
|
||||
this.previewDialog.visible = false;
|
||||
this.$nextTick(() => {
|
||||
this.generateDialog.visible = true;
|
||||
});
|
||||
},
|
||||
async submitGenerateFee() {
|
||||
this.previewDialog.submitting = true;
|
||||
try {
|
||||
@@ -1227,7 +1326,12 @@ export default {
|
||||
exportBlob(
|
||||
'/blade-transport/receivable-payable-detail/export-receivable-payable-detail',
|
||||
this.buildRequestParams(
|
||||
this.normalizeQuery(this.query, 'generateDateRange', 'generateStartDate', 'generateEndDate')
|
||||
this.normalizeQuery(
|
||||
this.query,
|
||||
'generateDateRange',
|
||||
'generateStartDate',
|
||||
'generateEndDate'
|
||||
)
|
||||
),
|
||||
{ feedback: true }
|
||||
).then(res => {
|
||||
@@ -1254,7 +1358,9 @@ export default {
|
||||
`plan-${index}`,
|
||||
name: item.name || item.planName || item.billingPlanName || `计费方案${index + 1}`,
|
||||
defaultPlan:
|
||||
item.defaultPlan === true || String(item.defaultPlan).toLowerCase() === 'true',
|
||||
item.defaultPlan === true ||
|
||||
item.defaultPlan === 1 ||
|
||||
['true', '1'].includes(String(item.defaultPlan).toLowerCase()),
|
||||
}));
|
||||
return this.billingPlanOptions;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user