预算填报模块迭代

master
weizong song 3 weeks ago
parent 3082e3ac30
commit 4395c4b225

@ -114,6 +114,14 @@ export function reviewDepartmentSubmission(submissionId, data) {
}) })
} }
export function reviewDepartmentSubmissionByManager(submissionId, data) {
return request({
url: `/api/admin/department-submissions/${submissionId}/manager-review`,
method: 'post',
data
})
}
export function getDepartmentSubmissions(params = {}) { export function getDepartmentSubmissions(params = {}) {
return request({ return request({
url: '/api/admin/department-submissions', url: '/api/admin/department-submissions',
@ -122,6 +130,14 @@ export function getDepartmentSubmissions(params = {}) {
}) })
} }
export function getManagerReviewDepartmentSubmissions(params = {}) {
return request({
url: '/api/admin/department-submissions/manager-review-list',
method: 'get',
params
})
}
/** /**
* 编辑第二层预算包包含子项管理 * 编辑第二层预算包包含子项管理
* @param {Number} id 预算包ID * @param {Number} id 预算包ID

@ -119,12 +119,23 @@ export const constantRoutes = [
icon: 's-order' icon: 's-order'
} }
}, },
{
path: 'manager-review',
name: 'DepartmentManagerReview',
component: () => import('@/views/budget/departmentReview/index'),
props: { reviewStage: 'manager' },
meta: {
title: '科室负责人审核',
icon: 's-check'
}
},
{ {
path: 'department-review', path: 'department-review',
name: 'DepartmentReview', name: 'DepartmentReview',
component: () => import('@/views/budget/departmentReview/index'), component: () => import('@/views/budget/departmentReview/index'),
props: { reviewStage: 'leader' },
meta: { meta: {
title: '执行科室分解审核', title: '分管领导审核',
icon: 's-check' icon: 's-check'
} }
} }

@ -1134,7 +1134,7 @@ export default {
? this.packageData.submission.status ? this.packageData.submission.status
: this.packageData.submission_status : this.packageData.submission_status
return !this.isViewMode && !(this.mode === 'edit' && currentStatus === 'submitted') return !this.isViewMode && !(this.mode === 'edit' && currentStatus !== 'draft')
}, },
allocatedAmountNum() { allocatedAmountNum() {

@ -0,0 +1,27 @@
<template>
<div v-loading="loading" class="summary-detail">
<template v-if="summary">
<section class="section">
<h3>基本信息</h3>
<el-row :gutter="20"><el-col :span="8"><p><b>牵头科室</b>{{ packageData.department && packageData.department.name || '-' }}</p></el-col><el-col :span="8"><p><b></b>{{ packageData.budget_year && packageData.budget_year.year }}</p></el-col><el-col :span="8"><p><b></b>{{ money(packageData.allocated_amount) }} </p></el-col><el-col :span="24"><p><b></b>{{ packageData.name || '-' }}</p></el-col></el-row>
</section>
<section class="section"><h3>汇总申请信息</h3><el-row :gutter="20"><el-col v-for="item in applicationItems" :key="item.label" :span="12"><div class="field"><b>{{ item.label }}</b><pre>{{ item.value || '-' }}</pre></div></el-col></el-row></section>
<section class="section"><h3>经济分类汇总</h3><el-table :data="summary.economicSummary || []" border><el-table-column type="index" label="序号" width="60" align="center" /><el-table-column prop="categoryName" label="经济分类" min-width="180" /><el-table-column label="汇总金额(万元)" width="160" align="right"><template slot-scope="{ row }">{{ money(row.totalAmount) }}</template></el-table-column><el-table-column label="" min-width="300"><template slot-scope="{ row }"><pre>{{ row.summaryCalculationBasis || '-' }}</pre></template></el-table-column><el-table-column label="" min-width="220"><template slot-scope="{ row }"><file-links :files="row.files" /></template></el-table-column></el-table></section>
<section class="section"><h3>申请资金汇总</h3><el-table :data="[summary.fundApplication || {}]" border><el-table-column label="新项目总额"><template slot-scope="{ row }">{{ money(row.newProject && row.newProject.totalAmount) }}</template></el-table-column><el-table-column label=""><template slot-scope="{ row }">{{ money(row.newProject && row.newProject.yearCurrent) }}</template></el-table-column><el-table-column label=""><template slot-scope="{ row }">{{ money(row.newProject && row.newProject.yearNext) }}</template></el-table-column><el-table-column label=""><template slot-scope="{ row }">{{ money(row.oldProject && row.oldProject.contractTotal) }}</template></el-table-column><el-table-column label=""><template slot-scope="{ row }">{{ money(row.oldProject && row.oldProject.previousYearsTotal) }}</template></el-table-column><el-table-column label=""><template slot-scope="{ row }">{{ money(row.oldProject && row.oldProject.previousYearBalance) }}</template></el-table-column><el-table-column label=""><template slot-scope="{ row }">{{ money(row.oldProject && row.oldProject.yearCurrent) }}</template></el-table-column><el-table-column label="" min-width="160"><template slot-scope="{ row }">{{ row.oldProject && row.oldProject.contractNumber || '-' }}</template></el-table-column></el-table></section>
<section class="section"><h3>项目实施进度安排汇总</h3><div class="field"><b>实施进度安排</b><pre>{{ summary.implementationSchedule || '-' }}</pre></div><div class="field"><b>当年实施内容</b><pre>{{ schedule.currentYearContent || '-' }}</pre></div><el-table :data="[schedule]" border class="month-table"><el-table-column label="当年金额"><template slot-scope="{ row }">{{ money(row.currentYearAmount) }}</template></el-table-column><el-table-column v-for="month in 12" :key="month" :label="`${month}`"><template slot-scope="{ row }">{{ money(row[`currentYearMonth${month}`]) }}</template></el-table-column></el-table><el-row :gutter="20"><el-col :span="12"><div class="field"><b></b><pre>{{ schedule.nextYearContent || '-' }}</pre></div></el-col><el-col :span="12"><div class="field"><b></b><pre>{{ schedule.thirdYearContent || '-' }}</pre></div></el-col></el-row></section>
<section class="section"><h3>项目预计取得绩效汇总</h3><el-table :data="summary.summaryPerformanceIndicators || []" border><el-table-column prop="level1" label="一级指标" /><el-table-column prop="level2" label="二级指标" /><el-table-column prop="level3" label="三级指标" /><el-table-column prop="symbol" label="指标性质" width="110" /><el-table-column prop="halfYearValue" label="半年目标值" /><el-table-column prop="fullYearValue" label="全年目标值" /><el-table-column prop="unit" label="单位" width="100" /></el-table></section>
<section class="section"><h3>其他说明及附件</h3><div class="field"><b>其他说明</b><pre>{{ summary.otherNotes || '-' }}</pre></div><div class="field"><b>附件</b><file-links :files="summary.generalFiles" /></div></section>
</template>
<el-empty v-else-if="!loading" description="未找到该中包的汇总提交数据" />
</div>
</template>
<script>
import { getLeadDepartmentSummary } from '@/api/budget/leadDepartment'
const FileLinks = { props: { files: { type: Array, default: () => [] }}, methods: { open(file) { if (file.url) window.open(file.url, '_blank') } }, template: '<div v-if="files && files.length"><el-link v-for="(file, index) in files" :key="file.fileId || file.id || index" type="primary" :underline="false" @click="open(file)">{{ file.name || file.originalName || file.original_name || \'附件\' }}<br></el-link></div><span v-else>-</span>' }
export default { name: 'LeadDepartmentSummaryDetail', components: { FileLinks }, props: { packageData: { type: Object, required: true }}, data: () => ({ loading: true, summary: null }), computed: { schedule() { return this.summary && this.summary.implementationScheduleDetail || {} }, applicationItems() { return [{ label: '申请依据', value: this.summary.applicationBasis }, { label: '申请目的', value: this.summary.applicationPurpose }, { label: '申请理由', value: this.summary.applicationReason }, { label: '项目内容', value: this.summary.projectContent }] } }, async created() { try { const response = await getLeadDepartmentSummary(this.packageData.id); this.summary = response.data || response } catch (error) { this.$message.error(error.message || '加载中包汇总详情失败') } finally { this.loading = false } }, methods: { money(value) { const number = Number(value); return Number.isFinite(number) ? number.toFixed(4) : '0.0000' } }}
</script>
<style scoped>
.section { margin: 0 0 20px; padding: 18px 20px; border: 1px solid #ebeef5; border-radius: 4px; }.section h3 { margin: 0 0 16px; padding-bottom: 10px; color: #303133; border-bottom: 2px solid #409eff; font-size: 16px; }.section p { margin: 0 0 12px; }.field { margin-bottom: 14px; }.field b { display: block; margin-bottom: 7px; color: #606266; }pre { margin: 0; white-space: pre-wrap; word-break: break-word; font: inherit; color: #303133; }.month-table { margin: 12px 0; }
</style>

@ -1802,6 +1802,7 @@ export default {
const typeMap = { const typeMap = {
pending: 'warning', pending: 'warning',
submitted: 'primary', submitted: 'primary',
manager_approved: 'primary',
draft: 'warning', draft: 'warning',
approved: 'success' approved: 'success'
} }
@ -1811,9 +1812,10 @@ export default {
getStatusText(status) { getStatusText(status) {
const textMap = { const textMap = {
pending: '待提交', pending: '待提交',
submitted: '已提交', submitted: '待科室负责人审核',
manager_approved: '待分管领导审核',
draft: '已退回', draft: '已退回',
approved: '已确认' approved: '终审通过'
} }
return textMap[status] || '未知' return textMap[status] || '未知'
}, },

@ -200,6 +200,7 @@ export default {
const statusMap = { const statusMap = {
'draft': 'info', 'draft': 'info',
'submitted': 'warning', 'submitted': 'warning',
'manager_approved': 'primary',
'approved': 'success', 'approved': 'success',
'rejected': 'danger' 'rejected': 'danger'
} }
@ -212,8 +213,9 @@ export default {
const statusMap = { const statusMap = {
'draft': '草稿', 'draft': '草稿',
'submitted': '已提交', 'submitted': '待科室负责人审核',
'approved': '已通过', 'manager_approved': '待分管领导审核',
'approved': '终审通过',
'rejected': '已拒绝' 'rejected': '已拒绝'
} }
return statusMap[submission.status] || '未知' return statusMap[submission.status] || '未知'

@ -30,8 +30,9 @@
> >
<el-option label="待提交" value="pending" /> <el-option label="待提交" value="pending" />
<el-option label="草稿/已退回" value="draft" /> <el-option label="草稿/已退回" value="draft" />
<el-option label="已提交" value="submitted" /> <el-option label="待科室负责人审核" value="submitted" />
<el-option label="审核通过" value="approved" /> <el-option label="待分管领导审核" value="manager_approved" />
<el-option label="终审通过" value="approved" />
</el-select> </el-select>
</div> </div>
<div class="actions"> <div class="actions">
@ -123,7 +124,7 @@
</Button> </Button>
<Button <Button
v-if="scope.row.submission && ['draft', 'submitted'].includes(scope.row.submission.status) && canWrite(scope.row)" v-if="scope.row.submission && scope.row.submission.status === 'draft' && canWrite(scope.row)"
type="warning" type="warning"
size="small" size="small"
@click="editSubmissionForm(scope.row)" @click="editSubmissionForm(scope.row)"
@ -329,7 +330,7 @@ export default {
}, },
async editSubmissionForm(packageItem) { async editSubmissionForm(packageItem) {
if (!packageItem.submission || !['draft', 'submitted'].includes(packageItem.submission.status) || !this.canWrite(packageItem)) { if (!packageItem.submission || packageItem.submission.status !== 'draft' || !this.canWrite(packageItem)) {
this.$message.warning('当前申报状态不可修改') this.$message.warning('当前申报状态不可修改')
return return
} }
@ -413,6 +414,7 @@ export default {
pending: 'warning', pending: 'warning',
draft: 'warning', draft: 'warning',
submitted: 'primary', submitted: 'primary',
manager_approved: 'primary',
approved: 'success', approved: 'success',
rejected: 'danger' rejected: 'danger'
} }
@ -423,8 +425,9 @@ export default {
const textMap = { const textMap = {
pending: '待提交', pending: '待提交',
draft: '草稿/已退回', draft: '草稿/已退回',
submitted: '已提交', submitted: '待科室负责人审核',
approved: '审核通过', manager_approved: '待分管领导审核',
approved: '终审通过',
rejected: '已拒绝' rejected: '已拒绝'
} }
return textMap[status] || '未知' return textMap[status] || '未知'
@ -447,6 +450,10 @@ export default {
const actionMap = { const actionMap = {
submit: '提交审核', submit: '提交审核',
resubmit: '重新提交', resubmit: '重新提交',
manager_approve: '科室负责人初审通过',
manager_return: '科室负责人退回修改',
leader_approve: '分管领导终审通过',
leader_return: '分管领导退回修改',
approve: '审核通过', approve: '审核通过',
return: '退回修改' return: '退回修改'
} }

@ -118,8 +118,18 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column fixed="right" label="操作" width="200"> <el-table-column fixed="right" label="操作" width="280">
<template slot-scope="scope"> <template slot-scope="scope">
<Button
v-if="scope.row.level === 2 || scope.row.level === 3"
type="primary"
size="small"
style="margin-right: 5px;"
ghost
@click="viewPackageDetails(scope.row)"
>
查看详情
</Button>
<Button <Button
v-if="scope.row.level <= 2" v-if="scope.row.level <= 2"
type="info" type="info"
@ -240,8 +250,18 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column fixed="right" label="操作" width="200"> <el-table-column fixed="right" label="操作" width="280">
<template slot-scope="scope"> <template slot-scope="scope">
<Button
v-if="scope.row.level === 2 || scope.row.level === 3"
type="primary"
size="small"
style="margin-right: 5px;"
ghost
@click="viewPackageDetails(scope.row)"
>
查看详情
</Button>
<Button <Button
v-if="scope.row.level <= 2" v-if="scope.row.level <= 2"
type="info" type="info"
@ -540,6 +560,30 @@
</div> </div>
</el-dialog> </el-dialog>
<!-- 中包汇总填报小包预算分解表详情 -->
<el-dialog
:title="currentPackageDetail.level === 2 ? '查看汇总填报表' : '查看预算分解表'"
:visible.sync="packageDetailVisible"
width="96%"
top="3vh"
:close-on-click-modal="false"
@closed="currentPackageDetail = {}"
>
<div v-loading="packageDetailLoading" class="package-detail-content">
<lead-department-summary-detail
v-if="currentPackageDetail.id && currentPackageDetail.level === 2"
:package-data="currentPackageDetail"
/>
<budget-submission-form
v-else-if="currentPackageDetail.id && currentPackageDetail.level === 3"
:package-data="currentPackageDetail"
mode="view"
:show-application-info="true"
@cancel="packageDetailVisible = false"
/>
</div>
</el-dialog>
<!-- 编辑对话框 --> <!-- 编辑对话框 -->
<el-dialog :title="dialogTitle" :visible.sync="dialogFormVisible" width="60%"> <el-dialog :title="dialogTitle" :visible.sync="dialogFormVisible" width="60%">
<el-form <el-form
@ -672,8 +716,11 @@
<script> <script>
import LxHeader from '@/components/LxHeader/index.vue' import LxHeader from '@/components/LxHeader/index.vue'
import LeadDepartmentSummaryDetail from './components/LeadDepartmentSummaryDetail.vue'
import BudgetSubmissionForm from './components/BudgetSubmissionForm.vue'
import { import {
getBudgetCollectionPackageList, getBudgetCollectionPackageList,
getBudgetCollectionPackageDetail,
createBudgetCollectionPackage, createBudgetCollectionPackage,
updateBudgetCollectionPackage, updateBudgetCollectionPackage,
deleteBudgetCollectionPackage, deleteBudgetCollectionPackage,
@ -694,11 +741,16 @@ import {
export default { export default {
name: 'BudgetCollectionPackage', name: 'BudgetCollectionPackage',
components: { components: {
LxHeader LxHeader,
LeadDepartmentSummaryDetail,
BudgetSubmissionForm
}, },
data() { data() {
return { return {
dialogFormVisible: false, dialogFormVisible: false,
packageDetailVisible: false,
packageDetailLoading: false,
currentPackageDetail: {},
formLabelWidth: '120px', formLabelWidth: '120px',
loading: false, loading: false,
submitting: false, submitting: false,
@ -1045,6 +1097,24 @@ export default {
}) })
}, },
async viewPackageDetails(row) {
this.packageDetailVisible = true
this.packageDetailLoading = true
this.currentPackageDetail = { ...row }
try {
const response = await getBudgetCollectionPackageDetail(row.id)
const data = response && (response.data || response)
if (data && data.id) {
this.currentPackageDetail = data
}
} catch (error) {
this.$Message.error('加载预算包详情失败')
console.error('加载预算包详情失败:', error)
} finally {
this.packageDetailLoading = false
}
},
async addChild(parent) { async addChild(parent) {
this.form = this.getDefaultForm() this.form = this.getDefaultForm()
this.form.year_id = parent.year_id this.form.year_id = parent.year_id
@ -2199,6 +2269,10 @@ export default {
text-align: right; text-align: right;
} }
.package-detail-content {
min-height: 320px;
}
.el-tag { .el-tag {
margin-right: 5px; margin-right: 5px;
} }

@ -1,7 +1,7 @@
<template> <template>
<div class="container"> <div class="container">
<div ref="lxHeader"> <div ref="lxHeader">
<LxHeader :custom="true" icon="el-icon-s-check" text="执行科室分解审核" style="margin-bottom: 10px; border: 0;"> <LxHeader :custom="true" icon="el-icon-s-check" :text="pageTitle" style="margin-bottom: 10px; border: 0;">
<div> <div>
<div class="search-panel"> <div class="search-panel">
<div class="filters"> <div class="filters">
@ -27,8 +27,9 @@
@change="load" @change="load"
> >
<el-option label="草稿" value="draft" /> <el-option label="草稿" value="draft" />
<el-option label="已提交" value="submitted" /> <el-option label="待科室负责人审核" value="submitted" />
<el-option label="审核通过" value="approved" /> <el-option label="待分管领导审核" value="manager_approved" />
<el-option label="终审通过" value="approved" />
</el-select> </el-select>
</div> </div>
<div class="actions"> <div class="actions">
@ -111,7 +112,7 @@
<div class="action-group"> <div class="action-group">
<el-button type="primary" size="mini" @click="viewDetail(scope.row)"></el-button> <el-button type="primary" size="mini" @click="viewDetail(scope.row)"></el-button>
<el-button <el-button
v-if="scope.row.status === 'submitted'" v-if="scope.row.status === reviewableStatus"
type="warning" type="warning"
size="mini" size="mini"
@click="openReviewDialog(scope.row)" @click="openReviewDialog(scope.row)"
@ -153,7 +154,7 @@
</el-dialog> </el-dialog>
<el-dialog <el-dialog
title="执行科室分解审核" :title="pageTitle"
:visible.sync="reviewDialogVisible" :visible.sync="reviewDialogVisible"
width="96%" width="96%"
:close-on-click-modal="false" :close-on-click-modal="false"
@ -262,7 +263,12 @@ import { formatMoney } from '@/utils/budgetMoney'
import LxHeader from '@/components/LxHeader/index.vue' import LxHeader from '@/components/LxHeader/index.vue'
import BudgetSubmissionForm from '@/views/budget/collection/components/BudgetSubmissionForm.vue' import BudgetSubmissionForm from '@/views/budget/collection/components/BudgetSubmissionForm.vue'
import { getBudgetCollectionYearOptions } from '@/api/budget/budgetCollectionYear.js' import { getBudgetCollectionYearOptions } from '@/api/budget/budgetCollectionYear.js'
import { getDepartmentSubmissions, reviewDepartmentSubmission } from '@/api/budget/leadDepartment.js' import {
getDepartmentSubmissions,
getManagerReviewDepartmentSubmissions,
reviewDepartmentSubmission,
reviewDepartmentSubmissionByManager
} from '@/api/budget/leadDepartment.js'
export default { export default {
name: 'DepartmentReviewManagement', name: 'DepartmentReviewManagement',
@ -270,6 +276,12 @@ export default {
LxHeader, LxHeader,
BudgetSubmissionForm BudgetSubmissionForm
}, },
props: {
reviewStage: {
type: String,
default: 'leader'
}
},
data() { data() {
return { return {
loading: false, loading: false,
@ -296,6 +308,17 @@ export default {
} }
} }
}, },
computed: {
isManagerReview() {
return this.reviewStage === 'manager'
},
pageTitle() {
return this.isManagerReview ? '科室负责人审核' : '分管领导审核'
},
reviewableStatus() {
return this.isManagerReview ? 'submitted' : 'manager_approved'
}
},
created() { created() {
this.initLoad() this.initLoad()
this.loadYearOptions() this.loadYearOptions()
@ -339,7 +362,8 @@ export default {
this.loading = true this.loading = true
try { try {
const response = await getDepartmentSubmissions({ const getList = this.isManagerReview ? getManagerReviewDepartmentSubmissions : getDepartmentSubmissions
const response = await getList({
...this.searchFields, ...this.searchFields,
page_size: this.pageSize, page_size: this.pageSize,
page: this.currentPage page: this.currentPage
@ -350,7 +374,7 @@ export default {
this.currentPage = response ? response.current_page || 1 : 1 this.currentPage = response ? response.current_page || 1 : 1
} catch (error) { } catch (error) {
console.error('获取执行科室分解审核列表失败:', error) console.error('获取执行科室分解审核列表失败:', error)
this.$message.error(error.message || '获取执行科室分解审核列表失败') this.$message.error(error.message || `获取${this.pageTitle}列表失败`)
this.tableData = [] this.tableData = []
this.total = 0 this.total = 0
} finally { } finally {
@ -397,11 +421,14 @@ export default {
this.reviewSubmitting = true this.reviewSubmitting = true
try { try {
await reviewDepartmentSubmission(this.currentReviewRow.id, { const review = this.isManagerReview ? reviewDepartmentSubmissionByManager : reviewDepartmentSubmission
await review(this.currentReviewRow.id, {
action: this.reviewForm.action, action: this.reviewForm.action,
approval_notes: this.reviewForm.approval_notes.trim() approval_notes: this.reviewForm.approval_notes.trim()
}) })
this.$message.success(this.reviewForm.action === 'approve' ? '审核通过' : '已退回执行科室修改') this.$message.success(this.reviewForm.action === 'approve'
? (this.isManagerReview ? '初审通过,已提交分管领导审核' : '终审通过')
: '已退回执行科室修改')
this.reviewDialogVisible = false this.reviewDialogVisible = false
await this.load() await this.load()
} catch (error) { } catch (error) {
@ -460,6 +487,7 @@ export default {
const typeMap = { const typeMap = {
draft: 'info', draft: 'info',
submitted: 'warning', submitted: 'warning',
manager_approved: 'primary',
approved: 'success' approved: 'success'
} }
return typeMap[status] || 'info' return typeMap[status] || 'info'
@ -468,8 +496,9 @@ export default {
getStatusText(status) { getStatusText(status) {
const textMap = { const textMap = {
draft: '草稿', draft: '草稿',
submitted: '已提交', submitted: '待科室负责人审核',
approved: '审核通过' manager_approved: '待分管领导审核',
approved: '终审通过'
} }
return textMap[status] || '未知' return textMap[status] || '未知'
}, },
@ -484,6 +513,10 @@ export default {
const actionMap = { const actionMap = {
submit: '提交审核', submit: '提交审核',
resubmit: '重新提交', resubmit: '重新提交',
manager_approve: '科室负责人初审通过',
manager_return: '科室负责人退回修改',
leader_approve: '分管领导终审通过',
leader_return: '分管领导退回修改',
approve: '审核通过', approve: '审核通过',
return: '退回修改' return: '退回修改'
} }

Loading…
Cancel
Save