首页 / 资讯中心 / 文章详情

国企财务管理系统从0到1开发复盘:预算控制、多级审批与权限设计

国企财务管理系统从0到1开发复盘:预算控制、多级审批与权限设计 ★ FEATURED ARTICLE
25007是我自己给这个项目排的编号一个完整的国有企业财务管理系统从前端页面到后端接口再到数据库全部自己写了一遍。最初拿到需求时对方只抛过来一句话把线下那套报销、预算、付款搬到线上要符合公司财务管理的要求。这句话听起来简单真正做起来才发现国企财务系统和常见的通用财务软件相比核心差异基本都不在功能多寡上而在流程约束和权限管控上。这篇复盘我会从需求为什么复杂讲起然后拆解功能模块、数据库设计、核心代码实现再顺便把开发过程中踩过的坑全部交代一遍源码获取方式放在最后。如果你正在准备财务管理类课设或毕设或者刚接触企业信息化项目想了解流程控制的开发者这篇内容应该能帮你省不少事。1. 国企财务管理系统到底特殊在哪1.1 预算不是表单而是刚性约束普通企业的报销系统很多时候就是把费用记录一下、老板签字了就打款。国企完全不同。在项目里我第一个深刻感受就是预算这两个字不是一张统计表而是强硬的执行边界。每个部门在年初要编报部门预算年中允许调整但调整也要走审批。系统里的预算额度一旦确定其他业务操作必须实时对照能不能再申请报销不是看领导愿不愿意签而是看这个科目下还有没有可用余额。可用余额等于年初预算加调整数减去已占用再减去已支出。任何一个环节超了后续的单据都应该被直接拦下来而不是等财务人员人工核对。所以在我的系统里预算不是一张静态表而是一个实时变化的资金池。提交报销单那一刻就要冻结额度审批通过再转为实际支出审批驳回就要释放冻结。这个预占-确认-释放的模型是整个系统最核心的设计。如果你做一个普通信息管理项目可能只需要增删改查但财务系统必须把这种状态变化做对否则后面所有统计报表都是错的。1.2 审批不是单点通过而是多级会签与分级控制国企里的费用审批极少是一级搞定。同样一笔业务金额不同审批链完全不一样。项目里我按金额分了几个层级小额单据由部门负责人审批即可超过一定金额要加财务部门意见再往上还要到分管领导。如果再涉及合同付款还需要资金计划校验。这种多级审批用简单的通过/驳回两个字段根本表达不了。我设计的是当前审批节点 审批记录的组合。单据表里存current_node表示走到哪一级审批记录表每操作一次都插一条包含处理人、处理时间、动作、意见、批注内容。这样不仅流程能走完后续审计时每一笔单据谁在什么时候做了什么决定全部有据可查。还有一个容易忽略的点同一个审批人可能是多级节点。比如财务人员在部门负责人审批后要再看一次但在金额超过阈值时分管领导审批后财务还要做最后支付审核。所以节点不能简单用角色绑定要做成可配置的节点序列。我在系统里用一张审批节点配置表支持按单据类型和金额区间匹配不同的流程这样才能真正贴近实际业务。1.3 财务数据不是全公司透明而是部门隔离加全程留痕普通员工登录后不应该看到别的部门的预算和报销。财务人员登录后能看到所有部门汇总数据。领导角色可以看到分管范围内的汇总。要实现这个效果光靠前端隐藏菜单远远不够后端所有查询接口都要自动带上部门过滤条件。我在系统里专门做了数据权限这一层不是每个接口手动写where dept_id ?而是通过拦截器自动拼参数。同时每个关键操作都会记录操作日志比如预算调整前后额度变化、审批意见修改记录、敏感数据的导出行为等。这套部门隔离 全程留痕的组合才是国企财务系统对外看起来和普通进销存系统最大的区别。没有留痕的财务系统是不合格的。这里说的留痕不是简单日志而是业务级的审计追溯。比如调整预算必须记录原额度、调整值、调整后额度、操作人、操作时间、调整原因。审批时点通过按钮也要记录这个节点的处理人是哪一位。这些都有标准化的字段设计在数据库阶段就要留好不能等上线以后再补。2. 模块拆解到底要做哪些页面2.1 七个核心功能模块如果只做增删改查那叫信息记录不叫财务管理系统。我的功能拆分更多从业务链条出发整体分成七个部分预算管理、费用报销、付款管理、收入管理、固定资产管理、财务凭证、系统管理。模块之间不是独立的预算和报销、报销和付款、付款和凭证都是联动关系。模块功能范围主要使用者预算管理预算编制录入、年中调整、预算科目管理、预算执行台账预算管理员、部门负责人、财务人员费用报销报销单填写、部门初审、财务复审、打款登记员工、部门负责人、财务付款管理付款申请、资金计划核对、支付状态回写业务部门、财务收入管理到账登记、认领、与预算收入科目关联财务固定资产管理资产台账、折旧计算、资产盘点记录资产管理员财务凭证报销审批通过后自动生成记账凭证人工修正财务系统管理用户、角色、菜单、字典、操作日志系统管理员实际开发时最花时间的模块是预算和报销因为它们牵涉的状态最多。收入管理和固定资产在初版可以做简单版本先保证数据能录、能查、能导出。但预算、审批、凭证这三块必须做完整因为这决定了系统能不能真正闭环。2.2 两条主线流程报销和付款报销流程是系统里最重要的场景。员工填报报销单关联预算科目填写金额上传发票等附件提交后进入审批流。部门负责人先审批财务人员再核验发票和预算余额金额超过阈值就继续送分管领导审批。审批通过后进入待支付状态出纳线下付款或对接支付后回写流水号报销单关闭。中间任意节点驳回单据都要退回申请人修改或直接终止同时释放对应的预算占用。付款流程和报销流程有些像但多了一个前置校验资金计划。业务部门提交付款申请时财务要先确认这个月的资金计划有没有覆盖这笔支出没有覆盖就要走资金计划调整。付款审批层级往往更重因为涉及实际资金流出。两类流程我在代码里都实现了但初版没有引入工作流引擎而是用状态机自己控制节点流转这样逻辑清楚出问题也好排查。2.3 角色权限矩阵怎么设计角色有系统管理员、预算管理员、普通员工、部门负责人、财务人员、领导。权限不是把菜单分配好就结束更重要的是操作范围。我画了一个角色权限矩阵核心内容是普通员工只能查看和提交自己的单据修改草稿不能删除已提交的单据部门负责人能看到本部门所有数据但对金额超过阈值的大额单据没有终审权财务人员能看到全公司数据可以调整预算可以把审批通过的单据生成凭证但不应有删除单据的权限领导能看到汇总数据不操作明细除非下钻。在表设计上我做了角色表、菜单表、按钮权限表同时用部门字段做数据范围限制。前端按钮权限用指令控制后端每个接口做权限码校验菜单用动态路由加载。这样不会出现某个普通员工调用接口改别人单据这种安全漏洞。数据权限和按钮权限最好分开设计因为它们是完全不同的维度一个是能不能看到这些数据一个是能不能执行这个操作。3. 技术栈选型与工程结构复盘3.1 为什么选了这套组合后端用 Spring Boot 2.7 MyBatis-Plus 3.5前端用 Vue 3 Element Plus Vite数据库 MySQL 8.0认证授权用 Sa-Token 或者 JWT 都可以。为什么要这么选三个原因。第一这个项目要给别人学习和复用必须选资料最多、问题最容易搜到的方案。Spring Boot加MyBatis-Plus这套组合几乎每个财务系统项目都能找到对应例子遇到报错基本一搜就有解决方案。第二前端Element Plus的表格、表单、树形控件做得非常成熟财务系统全是表格和表单交互这套组件库能省大量工作量。第三Sa-Token 或者 JWT 的方案足够应付角色权限不需要引入太重的工作流引擎。工作流引擎可以演示但自己写的状态机在答辩和讲解时会更有说服力这也是我建议的路线。实际部署时我没有用Docker而是最简单的本地启动方式。前端npm run dev跑在5173端口后端jar跑在8080端口前端通过Vite的proxy代理转发到后端。这种方案对初学者最友好不至于被容器相关概念带偏。如果团队里有熟悉Docker的人再改成docker-compose也不难但初版完全没必要。3.2 后端工程结构后端工程我按功能包加业务模块的方式组织。顶层目录如下fms-server ├── common # 通用返回实体、异常、工具类 ├── config # 全局配置、拦截器注册 ├── controller # 接口层 ├── service # 业务逻辑层 ├── mapper # MyBatis-Plus 接口 ├── entity # 数据库实体 ├── dto # 入参出参对象 └── aspect # 数据权限、操作日志切面我习惯按业务模块再拆包而不是一股脑堆service比如budget、expense、approve、voucher。因为模块多了以后业务代码容易互相引用清晰的分层会好维护很多。比如预算变更接口如果放在expense包里就不太合适因为它的使用方既有预算管理员也有财务所以我单独拆了一个budget包。凡是跨模块的调用统一走service层不直接掉mapper避免后面改表结构时到处联动。3.3 接口规范与统一返回体所有接口统一返回 Result 对象包含code、message、data三个字段。分页请求统一使用PageQuery返回统一用PageResult。前后端联调时特别省心。代码大概是这样的Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.code 200; r.message success; r.data data; return r; } public static T ResultT ok(T data, String message) { ResultT r new Result(); r.code 200; r.message message; r.data data; return r; } public static T ResultT fail(String msg) { ResultT r new Result(); r.code 500; r.message msg; return r; } public static T ResultT fail(Integer code, String msg) { ResultT r new Result(); r.code code; r.message msg; return r; } }这里有个关键点业务异常不能用500应该自定义业务错误码比如预算不足返回4001参数错误返回4000未登录返回401。前端拿到非200的code会单独处理弹窗提示。如果你把所有错误都混成500前端就只能统一弹系统异常用户根本不知道是预算不够还是单据状态不对。财务系统里这类业务错误特别多所以错误码规范一定要建立起来。4. 数据库设计预算表、单据表和凭证表的总统筹4.1 基础表与业务表我把表分为基础表和业务表两层。基础表包含sys_user用户、sys_dept部门、sys_role角色、sys_menu菜单、sys_dict字典。业务表包含budget_subject预算科目、budget_detail部门年度预算、budget_occupy预算占用明细、expense_order报销单、expense_approval审批记录、payment_order付款单、voucher_header凭证头、voucher_line凭证分录。财务系统表名不要起太飘的名字用下划线字段注释写全后面写SQL能省很多事。命名上我建议统一加模块前缀比如报销单用expense_付款单用payment_凭证用voucher_这样一看到表名就知道属于哪个模块。字段类型上金额统一用DECIMAL(18,2)避免浮点误差状态字段用INT不要用字符串便于索引和比较时间字段用DATETIME带上默认值CURRENT_TIMESTAMP。4.2 预算表字段设计预算管理是整个系统的基础所以预算表的设计尤其重要。我简化了建表语句核心是budget_detail表CREATE TABLE budget_detail ( id BIGINT PRIMARY KEY AUTO_INCREMENT, budget_year INT NOT NULL COMMENT 预算年度, dept_id BIGINT NOT NULL COMMENT 部门ID, subject_id BIGINT NOT NULL COMMENT 预算科目ID, initial_amount DECIMAL(18,2) DEFAULT 0 COMMENT 年初预算, adjust_amount DECIMAL(18,2) DEFAULT 0 COMMENT 调整金额, occupy_amount DECIMAL(18,2) DEFAULT 0 COMMENT 已占用金额, expense_amount DECIMAL(18,2) DEFAULT 0 COMMENT 已支出金额, status TINYINT DEFAULT 1 COMMENT 1启用 0停用, UNIQUE KEY uk_year_dept_subject (budget_year, dept_id, subject_id) ) COMMENT 部门年度预算表;可用余额就是initial_amount adjust_amount - occupy_amount - expense_amount其中adjust_amount可以为负数表示调减。很多人会把已占用和已支出合并成一个字段那样做会导致无法区分审批中被冻结的钱和已经实际花掉的钱后续对账会非常痛苦。占用明细表单独建记录每一笔对预算额度的占用、释放、转支出这样每一分钱都能查到来源。预算一旦调整也会在日志表里留下before和after的字段便于审计。4.3 报销单与审批记录表报销单表是流程控制的核心载体。我保留了最关键的字段CREATE TABLE expense_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 单号, applicant_id BIGINT NOT NULL COMMENT 申请人, dept_id BIGINT NOT NULL, subject_id BIGINT NOT NULL COMMENT 预算科目, amount DECIMAL(18,2) NOT NULL, status INT NOT NULL COMMENT 0草稿 1审批中 2通过 3驳回 4已支付, current_node INT NOT NULL COMMENT 当前审批节点, create_time DATETIME, update_time DATETIME, KEY idx_dept_status (dept_id, status) ) COMMENT 报销单表;审批记录表存储每一次操作的痕迹字段包括id、order_id、node、action、operator_id、comment、create_time。action可以是PASS、REJECT、RETURN。current_node和status是两个不同的东西status表示单据整体状态current_node表示审批进度位置。比如审批中状态下current_node2表示财务审核这一步正在处理。这两个字段分开可以让驳回后重做流程的逻辑更清晰。4.4 凭证表与科目对照凭证模块在初版不需要做得很复杂但要保证借贷平衡。凭证头保存voucher_no、voucher_date、order_id、total_debit、total_credit、status、maker_id、auditor_id、create_time。凭证分录保存voucher_line_id、voucher_header_id、subject_code、debit_amount、credit_amount、summary。生成凭证的科目映射存放在sys_dict字典里比如差旅类报销对应管理费用-差旅费借方贷方挂其他应付款-个人。用字典表而不是写死在代码里好处是财务人员可以自己调整科目映射不需要改代码重新发布。我在项目里预置了差旅费、办公费、业务招待费三种常用映射上课演示足够。如果碰到更加复杂的多借多贷场景比如一笔报销同时挂在两个费用科目就需要在表单里支持多行分录开发量会大一些初版可以先不做。5. 关键代码实现预算预占、审批流转、数据权限5.1 预算预占是防止超支的第一道闸门提交报销单时做的第一件事不是保存单据而是预算预占。预占逻辑的核心是先把budget_detail对应行锁住再计算可用余额如果余额够就增加占用金额并插入占用明细。我的核心代码是这样的Transactional public void occupyBudget(BudgetOccupyRequest req) { BudgetDetail detail budgetDetailMapper.selectForUpdate(req.getDeptId(), req.getSubjectId(), req.getYear()); if (detail null) { throw new BusinessException(预算不存在请先配置); } BigDecimal available detail.getInitialAmount() .add(detail.getAdjustAmount()) .subtract(detail.getOccupyAmount()) .subtract(detail.getExpenseAmount()); if (available.compareTo(req.getAmount()) 0) { throw new BusinessException(预算余额不足可用余额为 available); } detail.setOccupyAmount(detail.getOccupyAmount().add(req.getAmount())); budgetDetailMapper.updateById(detail); // 插入占用明细记录orderId、条目标识、占用金额、状态 budgetOccupyMapper.insert(req.toOccupyRecord()); }这段代码有两个关键点。第一Transactional不能省否则selectForUpdate的锁会在方法结束前自动释放等于没有锁。第二MyBatis-Plus自带的selectById不带for update所以selectForUpdate是我在Mapper里手写的SQL加了FOR UPDATE。只有数据库层面的行锁才能真正避免两个人同时提交报销单导致预算超扣。程序里的if判断在并发下是不可靠的。5.2 审批状态机用当前节点加动作决定下一步审批流程我用了一个简单的状态机不引入工作流引擎。核心是ApproverNode枚举值和说明对应审批节点比如DEPT_LEADER(1, 部门负责人)、FINANCE(2, 财务审核)、LEADER(3, 领导审批)。审批方法的简化版如下Transactional public void audit(Long orderId, String action, String comment) { ExpenseOrder order expenseOrderMapper.selectById(orderId); if (order null) { throw new BusinessException(单据不存在); } if (!order.getStatus().equals(1)) { throw new BusinessException(当前单据状态不允许审批); } if (PASS.equals(action)) { // 判断是否需要继续走下一级isLargeAmount按业务规则判断 if (order.getCurrentNode() LEADER.value isLargeAmount(order)) { order.setCurrentNode(order.getCurrentNode() 1); order.setStatus(1); } else { order.setCurrentNode(LEADER.value 1); order.setStatus(2); // 审批通过 generateVoucher(order); // 自动生成凭证 } } else if (REJECT.equals(action)) { order.setStatus(3); // 驳回 releaseBudget(order); // 释放预算占用 } expenseOrderMapper.updateById(order); // 插入一条审批记录 expenseApprovalMapper.insert(ApprovalRecord.of(orderId, order.getCurrentNode(), action, comment)); }isLargeAmount方法里可以做金额阈值判断也可以做成字典配置不写死。比如在sys_config表里配置一个threshold字段修改阈值不需要重新发版。生成凭证的动作放在审批通过的同一个事务里保证要么审批状态和凭证一起成功要么一起回滚。曾经有同事建议把凭证生成放到异步队列里但初版没有必要引入消息中间件同步事务反而更稳。5.3 部门数据权限拦截器自动拼条件不在每个方法里重复写数据权限如果靠开发人员自觉在查询SQL里加dept_id时间一长一定会漏。我采用的方式是定义一个DeptScope注解在Service层的查询方法上标注。然后通过AOP切面拦截在方法执行前从当前登录用户中取出部门范围拼到参数对象中。实际实现时我用一个ThreadLocal保存当前用户信息登录后由过滤器写入。查询参数类都继承一个BaseQuery里面有deptId和isSuper字段。切面里判断当前用户角色如果普通员工强制把deptId改成自己的部门即使前端传了别人的部门ID也会被覆盖如果是财务或管理员isSuper置为true不限制。查询语句统一通过MyBatis-Plus的LambdaQueryWrapper使用deptId字段这样漏掉接口的概率大大降低。这里有个容易被忽略的点直接修改前端传参不可取用户仍然可能绕过前端调接口。所以数据权限的判断必须在后端强制覆盖而不是信任前端传值。我甚至测试过手动传一个别的deptId来请求列表接口结果返回的仍然是本部门数据说明切面条件生效了。这个测试在项目演示时很加分因为能直观说明权限控制不只是页面隐藏。5.4 凭证自动生成借贷平衡是硬约束审批通过后系统要根据报销单生成凭证。最简单的场景是一借一贷。比如差旅费报销借方是管理费用-差旅费贷方是其他应付款-个人。我维护了一张凭证科目映射表字段包含费用类型、借方科目、贷方科目、摘要模板。生成凭证的伪代码如下void generateVoucher(ExpenseOrder order) { VoucherMapping mapping voucherMappingService.getByOrderType(order.getOrderType()); BigDecimal amount order.getAmount(); VoucherHeader header new VoucherHeader(); header.setOrderId(order.getId()); header.setVoucherDate(new Date()); header.setTotalDebit(amount); header.setTotalCredit(amount); header.setStatus(0); // 0草稿 1已审核 voucherHeaderMapper.insert(header); voucherLineMapper.insert(new VoucherLine(header.getId(), mapping.getDebitSubject(), amount, null, 报销 order.getOrderNo())); voucherLineMapper.insert(new VoucherLine(header.getId(), mapping.getCreditSubject(), null, amount, 报销 order.getOrderNo())); }凭证生成后财务人员可以去凭证列表中查看、修改摘要和科目但不能删除只能红字冲销。这个设计是企业财务系统的常见要求初版实现了自动生成已经够用。生成凭证时最好做一个金额汇总校验如果借方合计不等于贷方合计直接抛异常防止脏数据进入凭证表。6. 开发中踩过的坑并发超扣、驳回回滚、金额精度、导出乱码6.1 并发超扣同一个预算科目被用爆了第一次测试时我在两个浏览器窗口同时提交两笔报销单金额加起来正好超过预算结果系统全部通过了。刚开始以为是代码里没判余额后来加日志才发现两个请求几乎同时读到了同一个available值然后各自加占用根本没有相互感知。复现过程让我意识到程序里的判断在并发场景下是没用的必须依赖数据库锁。修复方案是给预算查询加上selectForUpdate同时把预占逻辑放在一个事务里。测试时特意开了两个会话并发提交接口加锁后第二个请求必须等第一个事务提交后才读取数据这样自然会发现余额不足。这里有一个经验加锁后接口响应时间会变长但预占操作本身很快可接受。不要尝试用Redis分布式锁代替数据库行锁因为账务数据最终落在数据库以数据库锁为准是最可靠的。如果以后要做更高的并发再考虑预占队列之类的方案。6.2 驳回后预算没释放占用金额越积越多这个坑出现在审批驳回时。当时我只在审批通过时做了预算占用转支出忽略了驳回时释放占用于是每次驳回都把额度白白冻住。一段时间后预算执行台账里的已占用金额比实际审批中单据加起来的金额大得多排查了很久才发现是历史驳回单据没有释放。修复也不复杂在驳回分支显式调用releaseBudget方法把占用金额减回去同时把占用明细记录的business_status从占用中改成已释放。关键还有一点如果财务人员在审批通过后、打款之前又把单据作废了也要释放或冲销预算占用不能只改单据状态。后来我把所有终止单据状态的操作都统一到一个方法里凡是状态变为非正常结束的都强制检查是否需要释放预算彻底解决了这个问题。6.3 前端金额精度0.1加0.2不是0.3前端页面输入金额时JavaScript的浮点运算会出现0.30000000000000004这种值直接传给后端数据库里就多了一长串小数。第一次发现是用户提交3100.1元报销款系统里存成了3100.1000000000001财务对账怎么都对不上。我处理这个问题的思路有三层第一前端所有金额输入框都校验输入格式最多两位小数提交时转成字符串不要用number类型传输第二后端实体金额字段统一用BigDecimal禁止double和float第三在JSON序列化配置里给BigDecimal加ToString避免后端转JSON时出现科学计数法。处理完这三层金额问题基本绝迹。另外前端不要在JS里做金额加减来计算可报销余额应该直接调用后端接口返回计算结果。6.4 Excel导出中文乱码和列宽问题报表导出我用的是EasyExcel在浏览器下载文件时遇到了中文文件名乱码以及内容中文乱码前后花了半天时间。乱码原因主要是响应头没有设置charset文件名没有URL编码。正确做法是response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setCharacterEncoding(utf-8); String fileName URLEncoder.encode(预算执行表.xlsx, UTF-8); response.setHeader(Content-disposition, attachment;filename*utf-8 fileName);用EasyExcel写Excel内容时默认UTF-8不会乱码所以问题几乎都出在响应头。列宽问题就需要在每个Sheet的列上设置宽度策略或者用 ExcelProperty 注解配合列宽设置类。财务表格列特别多我建议直接定义Excel导出模板类把列名和顺序写好避免每次导出都手动拼列。7. 源码获取与跑通指南7.1 环境准备和初始化步骤运行这套系统只需要常见的开发环境JDK 1.8、Maven 3.6、MySQL 8.0、Node 16Redis用不用都行如果只做普通登录认证可以关掉缓存逻辑。第一步是创建数据库执行项目根目录下的fms.sql它会自动建库建表并插入基础数据和测试账号。第二步是修改后端application.yml里的数据库用户名、密码把Redis配置也检查一遍。第三步是进入前端目录运行 npm install然后 npm run dev。启动顺序是先启动后端再启动前端。后端默认端口8080前端默认5173。如果端口被占用需要去配置文件里改。源码包里自带一个README里面写了详细的环境要求和启动命令。我第一次跑这个项目大概花了十五分钟大部分时间都在等npm下载依赖。7.2 初始化账号与快速测试流程系统预置了几种测试账号方便你直接体验完整业务流程。预算管理员账号登录后先创建预算科目再给部门分配预算额度。然后切换成普通员工账号填一张报销单选择对应的预算科目金额填一个几千块的数。提交后切到部门负责人账号进行审批。审批通过后再登录财务人员账号能看到待复核的单据继续审批。如果报销金额超过设定的阈值你会发现中间还多了一个领导审批节点。当最后的审核节点通过后去财务凭证菜单里就能看到自动生成的凭证借贷两条分录都有。整个流程走完你才算完整跑通了这个项目。建议按这个顺序测试因为每一步都会给下一步生成前置数据不会出现列表空白的问题。7.3 二次开发可以往哪些方向延伸这个系统目前是单体架构适合教学和毕设演示但真要企业内部使用还有很多地方可以完善。第一个方向是引入标准的工作流引擎把审批节点做成可视化流程配置第二个方向是对接电子发票平台和OCR识别用户上传发票图片后自动填入报销信息第三个方向是增加移动端H5或小程序入口让领导能随时随地审批。预算分析方面可以增加同比、环比和预算执行率趋势图把现有的Excel导出升级成BI看板。还有一个小建议如果这个项目要拿去做实际项目演示最好把预算调整、预算占用明细、审批记录这三个功能做成一个完整的演示脚本提前准备好几笔带不同状态的测试数据。面试时讲这套系统重点讲你怎么解决预算并发和审批状态流转的问题比罗列功能列表更有说服力。跑通之后我再回头看财务系统并不神秘核心就是一条数据链路预算占用、审批状态、凭证生成。只要把这条链路的每一步边界条件都理清楚这个项目的基础就非常扎实。源码我已经打包整理好了里面有完整的SQL脚本、后端代码、前端页面和那份环境说明文档需要的朋友直接按上文步骤下载使用就行。
阅读完成 · 觉得有帮助?
咨询建站