中包排序和大包分配金额由中包汇总

master
weizong song 6 days ago
parent 237f6b8b03
commit 7e8c4005d4

@ -57,6 +57,23 @@ export function batchDeleteBudgetCollectionPackage(ids) {
})
}
// 手工重新计算单个大包的自动汇总金额
export function recalculateBudgetCollectionPackageAmount(id) {
return request({
url: `${BUDGET_API_PREFIX}/budget-collection-package/${id}/recalculate-allocated-amount`,
method: 'post'
})
}
// 手工重新计算一个年度内全部大包的自动汇总金额
export function recalculateBudgetCollectionYearPackageAmounts(yearId) {
return request({
url: `${BUDGET_API_PREFIX}/budget-collection-package/recalculate-year-allocated-amount`,
method: 'post',
data: { year_id: yearId }
})
}
// 获取预算包选择列表(用于下拉框)
export function getBudgetCollectionPackageOptions(params) {
return request({
@ -158,4 +175,3 @@ export function getEconomicClassifications(params) {
params
})
}

@ -131,10 +131,9 @@
<template slot-scope="scope">
<el-input
v-if="!isViewMode && scope.row.selected"
v-model.number="scope.row.amount"
type="number"
:min="0"
:step="0.1"
v-model="scope.row.amount"
type="text"
inputmode="decimal"
placeholder="请输入金额"
@input="validateTotal"
/>
@ -1444,7 +1443,7 @@ export default {
formatCurrency(amount) {
if (amount === null || amount === undefined || amount === '') return '0.00'
const num = Number(amount)
return isFinite(num) ? num.toFixed(1) : '0.0'
return isFinite(num) ? num.toFixed(2) : '0.00'
},
submit() {
@ -1514,7 +1513,7 @@ export default {
otherNotes: this.formData.otherNotes ? this.formData.otherNotes.trim() : '',
economicItems: selectedItems.map(item => ({
categoryId: item.categoryId,
amount: parseFloat(item.amount),
amount: item.amount,
calculationBasis: item.calculationBasis.trim(),
fileIds: item.fileList ? item.fileList.map(f => f.fileId).filter(Boolean) : [] // ID
})),

@ -141,10 +141,9 @@
<el-table-column label="分配金额" width="200" align="center">
<template slot-scope="scope">
<el-input
v-model.number="scope.row.allocatedAmount"
type="number"
:min="0.1"
:step="0.1"
v-model="scope.row.allocatedAmount"
type="text"
inputmode="decimal"
placeholder="请输入分配金额"
@input="validateAllocation"
>
@ -439,7 +438,7 @@ export default {
}
//
if (Math.abs(this.totalAllocated - this.packageData.allocated_amount) > 0.0001) {
if (Math.abs(this.totalAllocated - this.packageData.allocated_amount) > 0.01) {
this.$message.error('分配金额总和必须等于预算分配金额')
return
}
@ -473,7 +472,7 @@ export default {
}
//
if (isNaN(parseFloat(item.allocatedAmount))) {
if (!Number.isFinite(Number(item.allocatedAmount))) {
this.$message.error(`${item.departmentName}的分配金额格式不正确`)
return
}
@ -513,7 +512,7 @@ export default {
const allocation = {
department_id: item.departmentId,
package_id: item.packageId || '',
allocated_amount: parseFloat(item.allocatedAmount),
allocated_amount: item.allocatedAmount,
package_name: item.childPackageName,
description: item.description || ''
}
@ -563,11 +562,11 @@ export default {
},
formatCurrency(amount) {
if (amount === null || amount === undefined || amount === '') return '0.0'
if (amount === null || amount === undefined || amount === '') return '0.00'
const num = Number(amount)
return isFinite(num)
? num.toLocaleString('zh-CN', { minimumFractionDigits: 1, maximumFractionDigits: 1 })
: '0.0'
? num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
: '0.00'
}
}
}
@ -639,4 +638,4 @@ export default {
.form-actions .el-button {
margin-left: 10px;
}
</style>
</style>

@ -157,10 +157,9 @@
<template slot-scope="scope">
<el-input
v-if="!isViewMode && scope.row.selected"
v-model.number="scope.row.amount"
type="number"
:min="0"
:step="0.1"
v-model="scope.row.amount"
type="text"
inputmode="decimal"
placeholder="请输入金额"
@input="validateTotal"
/>
@ -1440,7 +1439,7 @@ export default {
formatCurrency(amount) {
if (amount === null || amount === undefined || amount === '') return '0.00'
const num = Number(amount)
return isFinite(num) ? num.toFixed(1) : '0.0'
return isFinite(num) ? num.toFixed(2) : '0.00'
},
submit() {
@ -1509,7 +1508,7 @@ export default {
otherNotes: this.formData.otherNotes ? this.formData.otherNotes.trim() : '',
economicItems: selectedItems.map(item => ({
categoryId: item.categoryId,
amount: parseFloat(item.amount),
amount: item.amount,
calculationBasis: item.calculationBasis.trim(),
fileIds: item.fileList ? item.fileList.map(f => f.fileId).filter(Boolean) : [] // ID
})),

@ -344,11 +344,11 @@ export default {
},
formatCurrency(amount) {
if (amount === null || amount === undefined || amount === '') return '0.0'
if (amount === null || amount === undefined || amount === '') return '0.00'
const num = Number(amount)
return isFinite(num)
? num.toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 0 })
: '0.0'
? num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
: '0.00'
},
formatDate(dateStr) {

@ -591,11 +591,11 @@ export default {
},
formatCurrency(amount) {
if (amount === null || amount === undefined || amount === '') return '0.0'
if (amount === null || amount === undefined || amount === '') return '0.00'
const num = Number(amount)
return isFinite(num)
? num.toLocaleString('zh-CN', { minimumFractionDigits: 1, maximumFractionDigits: 1 })
: '0.0'
? num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
: '0.00'
},
formatDate(dateStr) {

@ -33,6 +33,7 @@
<Button type="primary" @click="load"></Button>
<Button type="primary" @click="edit()"></Button>
<Button type="error" @click="batchDelete" :disabled="selectedRows.length === 0">批量删除</Button>
<Button type="warning" @click="recalculateYearAmounts"></Button>
<Button type="success" @click="toggleTreeView">{{ showTree ? '' : '' }}</Button>
<Button type="warning" @click="showSummaryView"></Button>
</div>
@ -91,7 +92,13 @@
</template>
</el-table-column>
<el-table-column prop="allocated_amount" label="分配金额" width="120" align="right">
<el-table-column prop="sort_order" label="排序" width="80" align="center">
<template slot-scope="scope">
{{ scope.row.sort_order || '-' }}
</template>
</el-table-column>
<el-table-column prop="allocated_amount" label="分配金额(大包自动汇总)" width="180" align="right">
<template slot-scope="scope">
<span style="color: #409EFF; font-weight: bold;">
{{ formatCurrency(scope.row.allocated_amount) }} 万元
@ -149,15 +156,15 @@
>
删除
</Button>
<Button
<Button
v-if="scope.row.level === 1"
type="warning"
@click="multiPackageSubmit(scope.row)"
type="warning"
@click="recalculatePackageAmount(scope.row)"
size="small"
style="margin-right: 5px;"
ghost
>
多包汇总提交
重新计算金额
</Button>
<Button
type="primary"
@ -225,7 +232,13 @@
</template>
</el-table-column>
<el-table-column prop="allocated_amount" label="分配金额" width="120" align="right">
<el-table-column prop="sort_order" label="排序" width="80" align="center">
<template slot-scope="scope">
{{ scope.row.sort_order || '-' }}
</template>
</el-table-column>
<el-table-column prop="allocated_amount" label="分配金额(大包自动汇总)" width="180" align="right">
<template slot-scope="scope">
<span style="color: #409EFF; font-weight: bold;">
{{ formatCurrency(scope.row.allocated_amount) }} 万元
@ -289,15 +302,15 @@
>
删除
</Button>
<Button
<Button
v-if="scope.row.level === 1"
type="warning"
@click="multiPackageSubmit(scope.row)"
type="warning"
@click="recalculatePackageAmount(scope.row)"
size="small"
style="margin-right: 5px;"
ghost
>
多包汇总提交
重新计算金额
</Button>
<Button
type="primary"
@ -709,7 +722,7 @@
<el-option
v-for="item in yearOptions"
:key="item.value"
:label="item.year + '年'"
:label="item.label || (item.year + '年')"
:value="item.value"
/>
</el-select>
@ -722,6 +735,21 @@
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="同级排序" prop="sort_order">
<el-input-number
v-model="form.sort_order"
:min="1"
:step="1"
controls-position="right"
placeholder="留空则自动排在同级最后"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="上级预算包" prop="parent_id">
@ -731,6 +759,7 @@
style="width: 100%"
clearable
@change="onParentChange"
:disabled="isMiddlePackageParentLocked"
>
<el-option
v-for="item in parentPackageOptions"
@ -743,38 +772,39 @@
</el-col>
<el-col :span="12">
<el-form-item label="层级" prop="level">
<el-select v-model="form.level" placeholder="请选择层级" style="width: 100%" :disabled="!!form.parent_id">
<el-option label="大包" :value="1" />
<el-option label="中包" :value="2" />
</el-select>
<el-input :value="form.level === 1 ? '大包(自动汇总)' : '中包'" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="分配金额" prop="allocated_amount">
<el-form-item v-if="form.level === 1" label="自动汇总金额">
<el-input :value="formatCurrency(form.allocated_amount)" disabled>
<template slot="append">万元</template>
</el-input>
</el-form-item>
<el-form-item v-else label="分配金额" prop="allocated_amount">
<el-input
v-model.number="form.allocated_amount"
v-model="form.allocated_amount"
placeholder="请输入分配金额"
type="number"
:min="0"
:step="0.1"
@blur="formatAmount4('allocated_amount')"
type="text"
inputmode="decimal"
>
<template slot="append">万元</template>
</el-input>
<div v-if="isMiddlePackageDistributed" style="margin-top: 6px; color: #e6a23c; line-height: 18px;">
修改金额后请由牵头科室重新分配执行科室金额
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="分解金额" prop="decomposed_amount">
<el-input
v-model.number="form.decomposed_amount"
v-model="form.decomposed_amount"
placeholder="请输入分解金额(可选)"
type="number"
:min="0"
:step="0.1"
@blur="formatAmount4('decomposed_amount', true)"
type="text"
inputmode="decimal"
>
<template slot="append">万元</template>
</el-input>
@ -1079,6 +1109,8 @@ import {
updateBudgetCollectionPackage,
deleteBudgetCollectionPackage,
batchDeleteBudgetCollectionPackage,
recalculateBudgetCollectionPackageAmount,
recalculateBudgetCollectionYearPackageAmounts,
getBudgetCollectionPackageOptions,
getBudgetCollectionPackageSummary,
getEconomicClassifications
@ -1119,6 +1151,7 @@ export default {
name: '',
parent_id: null,
level: 1,
sort_order: null,
allocated_amount: 0,
decomposed_amount: null,
department_id: null,
@ -1243,6 +1276,18 @@ export default {
return this.mergeMethodByDept
},
isMiddlePackageDistributed() {
return this.form.level === 2 && (
Number(this.form.decomposed_amount || 0) > 0 ||
this.form.hasChildren ||
(Array.isArray(this.form.children) && this.form.children.length > 0)
)
},
isMiddlePackageParentLocked() {
return this.isMiddlePackageDistributed || !!this.form.submission
},
//
economicMergeMethod() {
return this.mergeMethodByEconomic
@ -1267,17 +1312,6 @@ export default {
this.load()
},
methods: {
formatAmount4(key, allowEmpty = false) {
let v = this.form[key]
if (v === '' || v === null || v === undefined) {
if (allowEmpty) return
this.$set(this.form, key, 0)
v = 0
}
const num = Number(v)
if (!isFinite(num)) return
this.$set(this.form, key, Number(num.toFixed(1)))
},
initLoad() {
const clientHeight = document.documentElement.clientHeight
const lxHeaderHeight = 140
@ -1301,24 +1335,28 @@ export default {
try {
//
const yearResponse = await getBudgetCollectionYearOptions()
if (yearResponse && yearResponse.code === 0) {
let yearData = yearResponse.data
// dataJSON
if (typeof yearData === 'string') {
try {
yearData = JSON.parse(yearData)
} catch (e) {
console.error('解析年度选项JSON失败:', e)
yearData = []
}
}
if (Array.isArray(yearData)) {
this.yearOptions = yearData
// request data
let yearData = yearResponse && yearResponse.code === 0
? yearResponse.data
: yearResponse
if (yearData && !Array.isArray(yearData) && Array.isArray(yearData.data)) {
yearData = yearData.data
}
if (typeof yearData === 'string') {
try {
yearData = JSON.parse(yearData)
} catch (e) {
console.error('解析年度选项JSON失败:', e)
yearData = []
}
}
if (Array.isArray(yearData)) {
this.yearOptions = yearData
}
//
const deptResponse = await listdeptNoAuth()
if (deptResponse) {
@ -1465,6 +1503,11 @@ export default {
if (obj) {
this.form = { ...this.form, ...obj }
}
// ID
if (!this.yearOptions.length) {
await this.loadOptions()
}
//
if (!this.form.year_id && this.searchFields.year_id) {
@ -1505,6 +1548,10 @@ export default {
this.submitting = true
try {
const formData = { ...this.form }
if (formData.level === 1) {
//
delete formData.allocated_amount
}
// null
Object.keys(formData).forEach(key => {
if (formData[key] === null || formData[key] === '') {
@ -1568,6 +1615,52 @@ export default {
})
},
recalculatePackageAmount(row) {
this.$Modal.confirm({
title: '确认重新计算',
content: `将按直属中包金额重新计算大包“${row.name}”,是否继续?`,
onOk: async () => {
try {
const response = await recalculateBudgetCollectionPackageAmount(row.id)
const result = response && response.data ? response.data : response
if (result && result.skipped) {
this.$Message.warning(result.reason)
} else if (result) {
this.$Message.success(result.changed ? `计算完成:${result.old_amount}${result.new_amount} 万元` : '金额无需更新')
}
this.load()
} catch (error) {
this.$Message.error((error.response && error.response.data && error.response.data.message) || '重新计算失败')
}
}
})
},
recalculateYearAmounts() {
if (!this.searchFields.year_id) {
this.$Message.warning('请先选择预算年度')
return
}
this.$Modal.confirm({
title: '确认重算年度大包金额',
content: '将重新计算当前年度全部结构正常的大包金额;存在历史层级异常的大包会被安全跳过。',
onOk: async () => {
try {
const response = await recalculateBudgetCollectionYearPackageAmounts(this.searchFields.year_id)
const result = response && response.data ? response.data : response
if (result) {
const skipped = Array.isArray(result.skipped) ? result.skipped.length : 0
this.$Message.success(`已处理${result.processed_count || 0}个大包,更新${result.changed_count || 0}个,跳过${skipped}`)
}
this.load()
} catch (error) {
this.$Message.error((error.response && error.response.data && error.response.data.message) || '批量重新计算失败')
}
}
})
},
batchDelete() {
if (this.selectedRows.length === 0) {
this.$Message.warning('请选择要删除的记录')
@ -1603,6 +1696,7 @@ export default {
name: '',
parent_id: null,
level: 1,
sort_order: null,
allocated_amount: 0,
decomposed_amount: null,
department_id: null,
@ -1611,11 +1705,11 @@ export default {
},
formatCurrency(amount) {
if (amount === null || amount === undefined || amount === '') return '0.0'
if (amount === null || amount === undefined || amount === '') return '0.00'
const num = Number(amount)
return isFinite(num)
? num.toLocaleString('zh-CN', { minimumFractionDigits: 1, maximumFractionDigits: 1 })
: '0.0'
? num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
: '0.00'
},
formatDate(dateStr) {
@ -2386,11 +2480,11 @@ export default {
//
formatAmount(amount) {
if (amount === null || amount === undefined || amount === '') return '0'
if (amount === null || amount === undefined || amount === '') return '0.00'
const num = Number(amount)
return isFinite(num)
? num.toLocaleString('zh-CN', { maximumFractionDigits: 1 })
: '0.0'
? num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
: '0.00'
},
//
@ -3742,4 +3836,4 @@ export default {
word-break: break-all;
white-space: normal;
}
</style>
</style>

@ -167,12 +167,10 @@
<el-col :span="12">
<el-form-item label="总预算(万元)" prop="total_amount">
<el-input
v-model.number="form.total_amount"
v-model="form.total_amount"
placeholder="请输入总预算金额"
type="number"
:min="0"
:step="0.1"
:precision="1"
type="text"
inputmode="decimal"
>
<template slot="prepend"></template>
<template slot="append">万元</template>
@ -788,11 +786,11 @@ export default {
},
formatCurrency(amount) {
if (amount === null || amount === undefined || amount === '') return '0.0000'
if (amount === null || amount === undefined || amount === '') return '0.00'
const num = Number(amount)
return isFinite(num)
? num.toLocaleString('zh-CN', { minimumFractionDigits: 4, maximumFractionDigits: 4 })
: '0.0000'
? num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
: '0.00'
},
formatDate(dateStr) {

Loading…
Cancel
Save