1、新增结算模块

2、调整业务模块
3、调整客商模块
This commit is contained in:
2026-08-20 06:33:01 +08:00
parent 4199255186
commit fd5b1484e6
59 changed files with 11745 additions and 1104 deletions
@@ -0,0 +1,396 @@
<template>
<el-dialog v-model="visible" :title="title" width="94%" top="3vh" append-to-body destroy-on-close>
<div v-loading="loading" class="settlement-adjustment-editor">
<section class="settlement-adjustment-editor__section">
<div class="dialog-section-title">基本信息</div>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="right"
label-width="auto"
>
<el-row :gutter="24">
<el-col v-for="field in fields" :key="field.prop" :span="8"
><el-form-item :label="field.label" :prop="field.prop"
><el-select
v-if="field.prop === 'formalSettlementId' && editable"
v-model="form.formalSettlementId"
filterable
remote
clearable
:remote-method="loadFormalSettlements"
placeholder="请选择正式结算单"
@change="handleFormalChange"
><el-option
v-for="item in candidates"
:key="item.id"
:label="`${item.formalSettlementNo} / ${item.projectName || ''}`"
:value="item.id" /></el-select
><el-input
v-else-if="field.prop === 'adjustmentNo'"
v-model="form[field.prop]"
disabled
/><el-input-number
v-else-if="field.money && editable"
v-model="form[field.prop]"
:precision="2"
:controls="false"
disabled
/><span v-else>{{
displayValue(
field.prop === 'settlementTypeName' ? settlementTypeName : form[field.prop]
)
}}</span></el-form-item
></el-col
>
<el-col :span="24"
><el-form-item label="备注" prop="remark"
><el-input
v-if="editable"
v-model="form.remark"
type="textarea"
maxlength="200"
show-word-limit
/><span v-else>{{ displayValue(form.remark) }}</span></el-form-item
></el-col
>
</el-row>
</el-form>
</section>
<section class="settlement-adjustment-editor__section">
<div class="settlement-adjustment-editor__section-head">
<div class="dialog-section-title">调整费用</div>
<el-button
v-if="editable && form.formalSettlementId"
type="primary"
plain
@click="openFeeDialog"
>添加费用</el-button
>
</div>
<el-table :data="details" border>
<el-table-column type="index" label="序号" width="64" align="center" /><el-table-column
prop="feeType"
label="费用类型"
min-width="150"
align="center"
/><el-table-column
prop="feeItem"
label="费用项目"
min-width="180"
align="center"
show-overflow-tooltip
/><el-table-column
prop="originalAmountTax"
label="原金额(含税)"
min-width="145"
align="center"
><template #default="{ row }">{{
formatMoney(row.originalAmountTax)
}}</template></el-table-column
>
<el-table-column label="调整金额(含税)" min-width="160" align="center"
><template #default="{ row }"
><el-input-number
v-if="editable"
v-model="row.adjustmentAmountTax"
:precision="2"
:controls="false"
@change="recalculate"
/><span v-else :class="{ negative: Number(row.adjustmentAmountTax) < 0 }">{{
formatMoney(row.adjustmentAmountTax)
}}</span></template
></el-table-column
>
<el-table-column label="调整金额(不含税)" min-width="170" align="center"
><template #default="{ row }"
><el-input-number
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 label="备注" min-width="180" align="center"
><template #default="{ row }"
><el-input v-if="editable" v-model="row.remark" maxlength="200" /><span v-else>{{
displayValue(row.remark)
}}</span></template
></el-table-column
>
<el-table-column v-if="editable" label="操作" width="90" align="center"
><template #default="{ $index }"
><el-link
type="danger"
@click="
details.splice($index, 1);
recalculate();
"
>删除</el-link
></template
></el-table-column
>
<template #empty><el-empty description="请选择调整费用明细" /></template>
</el-table>
<div class="settlement-adjustment-editor__summary">
调整金额合计:<strong :class="{ negative: Number(form.adjustmentAmount) < 0 }">{{
formatMoney(form.adjustmentAmount)
}}</strong
>;调整后结算金额:<strong>{{ formatMoney(form.adjustedSettlementAmount) }}</strong>
</div>
</section>
</div>
<template #footer
><el-button @click="visible = false">取消</el-button
><el-button v-if="editable" type="primary" :loading="saving" @click="handleSave"
>保存</el-button
></template
>
<el-dialog v-model="feeDialog.visible" title="选择调整费用" width="88%" append-to-body>
<el-table
ref="feeTable"
:data="feeRows"
border
@selection-change="feeDialog.selected = $event"
><el-table-column type="selection" width="52" align="center" /><el-table-column
type="index"
label="序号"
width="64"
align="center"
/><el-table-column
prop="documentNo"
label="单据号"
min-width="150"
align="center"
/><el-table-column
prop="feeType"
label="费用类型"
min-width="140"
align="center"
/><el-table-column
prop="feeItem"
label="费用项目"
min-width="180"
align="center"
show-overflow-tooltip
/><el-table-column
prop="originalAmountTax"
label="原金额(含税)"
min-width="145"
align="center"
><template #default="{ row }">{{
formatMoney(row.originalAmountTax)
}}</template></el-table-column
></el-table
>
<template #footer
><el-button @click="feeDialog.visible = false">取消</el-button
><el-button type="primary" @click="confirmFees">确定</el-button></template
>
</el-dialog>
</el-dialog>
</template>
<script>
import * as api from '@/api/settlement/settlementAdjustment';
import {
createSettlementAdjustmentForm,
settlementAdjustmentFormFields,
} from '@/option/settlement/settlementAdjustmentForm';
export default {
name: 'SettlementAdjustmentEditor',
props: { modelValue: Boolean, recordId: [String, Number], readonly: Boolean },
emits: ['update:modelValue', 'success'],
data: () => ({
loading: false,
saving: false,
form: createSettlementAdjustmentForm(),
fields: settlementAdjustmentFormFields,
details: [],
candidates: [],
feeRows: [],
rules: {
formalSettlementId: [{ required: true, message: '请选择关联正式结算单', trigger: 'change' }],
},
feeDialog: { visible: false, selected: [] },
}),
computed: {
visible: {
get() {
return this.modelValue;
},
set(value) {
this.$emit('update:modelValue', value);
},
},
editable() {
return !this.readonly;
},
title() {
return this.readonly ? '查看结算调整单' : this.recordId ? '编辑结算调整单' : '新增结算调整单';
},
settlementTypeName() {
return this.form.settlementType === 'receivable'
? '应收'
: this.form.settlementType === 'payable'
? '应付'
: '';
},
},
watch: {
modelValue(value) {
if (value) this.initialize();
},
},
methods: {
async initialize() {
this.form = createSettlementAdjustmentForm();
this.details = [];
this.candidates = [];
this.feeRows = [];
if (!this.recordId) {
await this.loadFormalSettlements('');
return;
}
this.loading = true;
try {
const { data } = await api.getDetail(this.recordId);
this.form = { ...createSettlementAdjustmentForm(), ...data };
this.details = (data.details || []).map(item => ({
...item,
adjustmentAmountTax: Number(item.adjustmentAmountTax || 0),
adjustmentAmountNoTax:
item.adjustmentAmountNoTax == null ? null : Number(item.adjustmentAmountNoTax),
}));
await this.loadFormalSettlements(this.form.formalSettlementNo || '');
} finally {
this.loading = false;
}
},
async loadFormalSettlements(keyword = '') {
const { data } = await api.getFormalSettlements(keyword);
this.candidates = data?.data || data || [];
},
async handleFormalChange(id) {
const item = this.candidates.find(row => String(row.id) === String(id));
if (!item) return;
Object.assign(this.form, item, {
formalSettlementId: id,
formalSettlementNo: item.formalSettlementNo,
originalSettlementAmount: Number(item.settlementAmount || 0),
settlementTypeName: item.settlementTypeName,
});
this.details = [];
this.recalculate();
},
async openFeeDialog() {
const { data } = await api.getFormalDetails(this.form.formalSettlementId);
const used = new Set(this.details.map(item => String(item.formalSettlementDetailFeeId)));
this.feeRows = (data?.data || data || []).filter(
item => !used.has(String(item.formalSettlementDetailFeeId))
);
this.feeDialog = { visible: true, selected: [] };
},
confirmFees() {
this.feeDialog.selected.forEach(item =>
this.details.push({
...item,
adjustmentAmountTax: 0,
adjustmentAmountNoTax: null,
remark: '',
})
);
this.recalculate();
this.feeDialog.visible = false;
},
recalculate() {
const total = this.details.reduce(
(sum, row) => sum + Number(row.adjustmentAmountTax || 0),
0
);
this.form.adjustmentAmount = Number(total.toFixed(2));
this.form.adjustedSettlementAmount = Number(
(Number(this.form.originalSettlementAmount || 0) + total).toFixed(2)
);
},
async handleSave() {
await this.$refs.formRef.validate();
if (!this.details.length) return this.$message.warning('请至少添加一条调整费用');
this.saving = true;
try {
await api.save({
id: this.form.id,
formalSettlementId: this.form.formalSettlementId,
remark: this.form.remark,
details: this.details.map(item => ({
formalSettlementDetailId: item.formalSettlementDetailId,
formalSettlementDetailFeeId: item.formalSettlementDetailFeeId,
feeType: item.feeType,
feeItem: item.feeItem,
adjustmentAmountTax: item.adjustmentAmountTax,
adjustmentAmountNoTax: item.adjustmentAmountNoTax,
remark: item.remark,
})),
});
this.$message.success('保存成功');
this.visible = false;
this.$emit('success');
} finally {
this.saving = false;
}
},
displayValue(value) {
return value === null || value === undefined || value === '' ? '-' : value;
},
formatMoney(value) {
return Number(value || 0).toFixed(2);
},
},
};
</script>
<style scoped lang="scss">
.settlement-adjustment-editor__section {
margin-bottom: 24px;
}
.settlement-adjustment-editor__section-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.settlement-adjustment-editor__summary {
padding: 12px 0;
text-align: right;
}
.settlement-adjustment-editor :deep(.el-form-item) {
margin-bottom: 16px;
}
.settlement-adjustment-editor :deep(.el-table) {
--el-table-border-color: #eff1f7;
}
.settlement-adjustment-editor :deep(.el-table__body tr:nth-child(even) > td.el-table__cell) {
background: #fafafa;
}
.negative {
color: #f56c6c;
}
.dialog-section-title {
display: flex;
align-items: center;
min-height: 22px;
font-size: 16px;
font-weight: 600;
}
.dialog-section-title::before {
width: 4px;
height: 18px;
margin-right: 8px;
background: #409eff;
content: '';
}
</style>