“纺织企业”这四个字放到财务系统前面,往往意味着代码量没多多少,但业务的坑会翻一倍。原料品种多、批次杂,坯布成品规格多,外协加工频繁,再加上供应商和客户之间的应收应付账期,财务人员每天处理的不是“借贷必相等”的漂亮账目,而是一堆对不上的采购单、出库单和月底成本分摊。所以当你看到一套 SpringBoot + Vue + MySQL 实现的纺织企业财务管理系统时,第一反应应该是:这不是普通进销存,而是一套能跑通“采购-生产-销售-资金往来-成本-报表”全链路的小型ERP财务核心。
这个项目做毕业设计、课程设计或者Java后端学习项目都非常合适。技术栈是主流的 SpringBoot + Vue 前后端分离,配合 MySQL 存储数据,由 Java 提供标准化的 RESTful API,业务上覆盖了凭证管理、应收应付、成本核算、报表统计等财务核心模块。如果你正在纠结选题,或者刚学完 Spring Boot 不知道从哪里下手,这套系统的拆解思路和落地代码可以直接拿来当骨架,往里面填自己的业务就行。
我尽量不给你讲一堆概念,只讲这个纺织财务系统到底怎么做、为什么这么做、哪些地方容易翻车,以及最终怎么部署上线。
1. 纺织企业财务管理的需求挖法与整体设计思路
1.1 财务场景的特殊性,决定了系统不能只做记账
普通商贸公司的财务软件,核心是“进货单-销售单-收付款”,商品维度简单,成本核算基本靠进价。但纺织企业的流程会变成:原料采购(棉纱、化纤、辅料)→ 外协加工或自产 → 产成品(坯布、色布、家纺面料)→ 销售 → 回款。每一步都牵扯资金,而且经常是“先拿货后结账”“先发货后开票”,应收应付账期从半个月到三个月不等。
这直接决定了财务管理系统不能只做凭证和账本,必须把业务单据和财务数据串起来。比如销售出库单审核之后,系统要自动生成一笔应收账款,同时减少对应成品库存;采购入库单审核后,自动生成应付账款,同时增加原料库存。月底还要把人工、水电、设备折旧等费用分摊到产成品上,算出一个相对准确的单位成本。这些需求意味着数据库设计不能只围绕“账”来建表,还要关联“单”和“量”。
我在设计这套项目时,模块上拆成了用户权限、基础资料、采购管理、销售管理、库存、应收应付、凭证账务、财务报表。但在代码层,我故意没有过度耦合,各模块通过统一的接口和数据表去联动,这样课设答辩时既能讲清楚业务闭环,又不会因为代码纠缠导致自己都维护不动。
1.2 技术选型:SpringBoot + Vue + MySQL 为什么是最稳的组合
先说后端。SpringBoot 在国内Java学习路线里几乎是必选项,它把Spring原有的XML配置、Tomcat部署、依赖管理全部收敛成“一个启动类 + 一份application.yml”,你可以把精力集中在写业务逻辑而不是搭环境。Maven 统一管理 jar 包,也正好和课程里教的“maven项目构建方法”无缝衔接。对于财务系统这种以CRUD和报表为主的场景,SpringBoot 很少会有性能瓶颈,反而是它成熟的生态能节省大量时间:Spring Data JPA 或 MyBatis-Plus 持久层任选,Spring Security 或 JWT 做权限,Actuator 做健康检查,几乎都是现成的。
前端用Vue,根本原因是组件化开发让页面编码变得非常直白。财务系统页面多,列表多,表单多,弹窗也多。用Vue的话,一个“供应商管理”页面可以被拆成搜索区、表格区、分页区、新增编辑弹窗,每个区域都是一个组件,改起来互不干扰。而且Vue对初学者非常友好,模板语法接近HTML,数据响应式不需要手动操作DOM,配合Element UI/Element Plus能迅速做出专业感很强的后台界面。
数据库选MySQL没有争议。纺织企业的数据量没有互联网高并发那么大,一张订单明细表、一张流水表,正常中小型厂一年也就几十万条记录,MySQL的InnoDB引擎配合合理的索引,报表查询也能在毫秒级响应。更重要的是,MySQL安装、备份、可视化工具(比如Navicat)的资料多,毕设答辩现场就算老师突然问数据库原理,你也能从容应对。
这套组合还有个隐含优势:前后端完全分离,开发时可以并行。后端的接口设计好了,前端可以mock数据先做页面;前端页面出来了,后端可以拿着 Postman 测接口。项目演示时也更像真实企业级开发流程,比JSP时代那种前后端混在一起的老古董高出一个层次。
2. 数据库设计与每个财务字段的讲究
2.1 核心数据表怎么拆才不踩坑
我见过不少同学做课设,第一版只有五张表:用户表、供应商表、客户表、商品表、订单表,然后所有金额都塞在订单表里。这么做代码量少,但财务上根本说不通。一套能拿得出手的系统,至少要有以下几类表:
- 用户与权限:用户表(user),角色表(role),用户角色关联表(user_role)。权限不搞太复杂,用角色控制菜单和按钮即可,够毕业设计用。
- 基础资料:供应商表(supplier)、客户表(customer)、原料表(raw_material)、成品表(finished_product)、仓库表(warehouse)。
- 业务单据:采购入库单(purchase_order)和明细(purchase_order_item)、销售出库单(sale_order)和明细(sale_order_item)、外协加工单(outsourcing_order)。
- 财务核心:会计凭证(voucher)和凭证明细(voucher_item),应收明细(receivable),应付明细(payable),成本核算表(cost_calculation)。
- 报表统计:这个不用单独建表,通过SQL视图或逻辑查询汇总就行,但可以考虑建一张月度汇总表缓存结果,减少每次报表查询的压力。
这些表的关联关系要保持清爽。比如供应商表和采购单是一对多,采购单和采购明细是一对多,采购明细里的原料ID再关联原料表,这样查询“我在2024年3月从某供应商买了多少棉纱”只需要JOIN三层,索引能命中,速度很快。
2.2 金额、状态、时间字段这种细节最能看出功底
财务系统最忌讳用浮点数存金额。Java里的float/double、MySQL里的float/double,在二进制运算中都可能出现类似 0.1 + 0.2 = 0.30000000000000004 的问题。账算错了,哪怕差一分钱,财务都会炸。所以金额字段一律用 DECIMAL,比如decimal(14,2),表示最多12位整数、2位小数,足够支撑千万级别的业务。Java实体里对应类型用 BigDecimal,千万不要用 Double。
状态字段用tinyint而不是varchar。比如单据状态:0草稿、1已审核、2已作废。在枚举里定义好,前端展示时再翻译成文字。这种做法的好处是数据库存储紧凑、查询效率高,也杜绝了“已审”“审核中”“Audited”这类五花八门的脏数据。
时间字段统一用datetime,在Java里对应LocalDateTime。但这里有个经典坑:MySQL连接URL里如果不加serverTimezone=Asia/Shanghai,当服务器时区不是中国时区时,你存入的时间会和实际差8小时。项目初始化时一定要在JDBC连接串里写清楚。
软删除也是必须的。财务单据不能物理删,一旦删了账就断了。建议给所有业务表加deleted字段,0正常1已删除,MyBatis-Plus的@TableLogic注解能自动处理查询条件带上deleted=0。
2.3 事务边界和表关系怎么定
财务模块的事务不能只考虑单表操作。典型场景是“审核销售出库单”:第一步更新销售单的状态为已审核,第二步根据明细写入应收款,第三步扣减成品库存。这三步必须在一个事务里完成,否则就会出“单子审核了但应收没生成”这种悬浮数据。在MySQL的InnoDB引擎下,Spring的@Transactional能把这三步绑在一起,要么全成功,要么全回滚。
设计表关系时,还要注意主外键的取舍。外键约束能保证数据一致性,但也带来了插入时的额外校验和锁竞争。我的建议是:在数据库层面不强制加物理外键,而是通过代码逻辑去维护关联,但是所有关联字段(比如sale_order_id、customer_id)都必须建普通索引。这样既保持了可扩展性,又不会因为外键导致超卖或死锁问题。如果你觉得不加外键心里没底,那就建外键,但在初始化数据时要小心删数据的顺序。
3. SpringBoot 后端核心实现:分层、金额计算、权限与事务
3.1 项目分包结构和统一接口返回
后端项目我会建一个com.example.textilefinance包,下面按功能分包:controller、service、mapper、entity、dto、vo、config、common、exception。这种分层是面试官和答辩老师最喜欢看到的“标准工程结构”,也方便你日后扩展。
所有接口返回值统一封装成Result<T>,包含code、message、data三个字段。不要每个接口返回类型都不一样,否则前端处理会非常痛苦。代码大概长这样:
@Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } }配合全局异常处理,Controller里就不需要到处写try-catch。使用@RestControllerAdvice拦截BusinessException和Exception,把异常转换成统一的Result返回。这样前端看到code===200才认为成功,否则弹错误提示。
3.2 持久层选择:MyBatis-Plus 还是 Spring Data JPA
两者都能用,但我更推荐 MyBatis-Plus。原因很简单:财务系统有很多复杂的多表查询和统计报表,MyBatis-Plus 允许你直接写SQL,报表的GROUP BY、SUM、CASE WHEN都能很直观地控制。而JPA虽然也能写@Query,但对于习惯SELECT联表的人反而绕。MyBatis-Plus 的Wrapper条件构造器也大大简化了比如“查询未审核的采购单”这类需求:
LambdaQueryWrapper<PurchaseOrder> wrapper = Wrappers.<PurchaseOrder>lambdaQuery() .eq(PurchaseOrder::getStatus, 0) .ge(PurchaseOrder::getCreateTime, beginDate) .le(PurchaseOrder::getCreateTime, endDate) .orderByDesc(PurchaseOrder::getCreateTime); IPage<PurchaseOrder> page = purchaseOrderMapper.selectPage( new Page<>(pageNum, pageSize), wrapper);3.3 金额计算用 BigDecimal,别给自己挖坑
财务系统里最核心的计算逻辑集中在成本核算和报表汇总。比如每个月月底要算“原料加权平均成本”,公式是:
(期初结存金额 + 本期入库金额)÷(期初结存数量 + 本期入库数量)
写代码时一定要注意BigDecimal的除法和舍入规则。我见过太多同学用 double 去除,发现除不尽就产生一堆小数,然后报表里数字对不上。正确写法是:
BigDecimal totalAmount = initialAmount.add(periodInAmount); BigDecimal totalQuantity = initialQty.add(periodInQty); BigDecimal avgCost = totalAmount.divide(totalQuantity, 4, RoundingMode.HALF_UP);scale=4表示保留四位小数,最后乘以本月出库数量时,再把结果舍入到2位小数作为库存发出成本。这种分步舍入的方式能最大程度减少累计误差,也是财务系统里标准的处理思路。
还有一点,BigDecimal不要直接用new BigDecimal(0.1),因为二进制浮点数转过来会带着很长的小数。应该用BigDecimal.valueOf(0.1)或者字符串构造。业务里最好把金额都定义成字符串传入,避免精度损失。
3.4 JWT 权限认证和接口安全
权限设计不需要很重,Spring Security 的过滤器链对新手来说理解成本高。我建议用一个轻量级方案:登录成功后用 JWT 生成 token,前端请求时放在Authorization头里,后端写一个拦截器校验 token,并把用户信息放到ThreadLocal供后续Service使用。
关键点有两个。第一,token 里不要存敏感信息,只存 user_id、login_name、role_id。第二,拦截器要排除登录接口和静态资源。参考配置:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns("/**") .excludePathPatterns("/api/auth/login", "/error"); } }角色控制可以在拦截器或者Service层做。最简单的做法是定义一个@RequireRole("ADMIN")注解,再写一个切面,在方法执行前判断当前用户的角色是否匹配。这样财务人员只能访问财务相关接口,管理员才能操作基础资料。
3.5 事务、防重和乐观锁
财务模块的事务上面提过,这里补充一个最常见的失败案例:在同一个类中,一个方法调用另一个@Transactional方法,事务会失效。因为Spring事务是通过AOP代理实现的,内部调用不会经过代理。解决办法是拆到两个Service里互相调用,或者自己注入自己。
并发场景下,比如同一笔应收款被两个人同时审核,可能产生重复记录。单机部署时可以用数据库乐观锁解决:在单据表加version字段,更新时检查version:
UPDATE sale_order SET status = 1, version = version + 1 WHERE id = #{id} AND version = #{version}如果更新行数为0,说明数据被其他人改过了,直接抛出冲突异常。这个技巧虽然简单,但在答辩时会让你显得很专业。
4. 前端 Vue 项目实现:页面结构、Axios封装与可视化报表
4.1 使用 Vue + Element UI 搭建后台管理界面
前端项目我习惯用 Vue + Vue Router + Pinia(或Vuex)+ Element UI 组合。如果你是新手,直接使用 Vue2 + Element UI 资料最多;如果项目要求现代化一点,就用 Vue3 + Element Plus,写法上大同小异,关键点是组件语法和API调用的轻微差异。
后台界面采用经典布局:顶部导航加左侧菜单,右侧内容区放路由视图。菜单根据角色动态生成,比如管理员能看到“系统管理”,普通财务只能看到“财务模块”。这个功能可以通过后端返回菜单树、前端动态渲染el-menu实现。菜单项不要写死,写死的话换个人就要改代码,不够灵活。
路由方面,如果是开发环境调试,建议路由使用createWebHashHistory,打包后部署不需要额外支持服务端history回退。如果你执意要用history模式,那么部署到Nginx时必须配置try_files $uri $uri/ /index.html;,否则刷新页面就是404。
4.2 Axios 全局配置和拦截器
前后端接口联调,最需要封装的就是Axios。请求拦截器里统一从 localStorage 取token并加到请求头,响应拦截器里判断返回码,如果token失效就跳转登录页。
service.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers['Authorization'] = 'Bearer ' + token; } config.headers['Content-Type'] = 'application/json;charset=UTF-8'; return config; }); service.interceptors.response.use(response => { const res = response.data; if (res.code === 200) { return res; } if (res.code === 401) { router.push('/login'); return Promise.reject('未登录'); } Message.error(res.message); return Promise.reject(res.message); }, error => { Message.error('网络请求失败'); return Promise.reject(error); });这里有个隐患:后端接口很多会返回分页对象,不同接口data结构不一样,所以响应拦截器不要一刀切return response.data,而是返回整个response,然后在具体API方法里再取res.data.records。否则你写泛型时会把类型搞乱。
4.3 财务报表页面的 ECharts 数据可视化
财务系统离不了图表。常见的有月度收支趋势图、应收账款账龄分布饼图、库存金额Top10条形图、销售毛利统计图等。ECharts 在前端图表里是事实标准,Vue 中使用非常方便:
npm install echarts --save页面中引入:
import * as echarts from 'echarts'; mounted() { this.initChart(); }, methods: { initChart() { const chart = echarts.init(this.$refs.chartRef); chart.setOption({ tooltip: { trigger: 'axis' }, legend: { data: ['收入', '支出'] }, xAxis: { type: 'category', data: ['1月', '2月', '3月'] }, yAxis: { type: 'value' }, series: [ { name: '收入', type: 'line', data: [120000, 150000, 130000] } ] }); window.addEventListener('resize', () => chart.resize()); } }要注意一个性能问题:图表的数据如果是从后端接口拿的,setOption时尽量使用notMerge: true,否则切换查询条件后旧数据可能残留。同时,页面销毁时要调用chart.dispose(),不然会有内存泄漏。
4.4 表单校验和金额输入的细节
前端表单校验重点有两类:必填项和金额格式。Element UI的el-form自带rules校验,比如金额字段校验:
const rules = { amount: [ { required: true, message: '请输入金额', trigger: 'blur' }, { pattern: /^(([1-9]\d{0,9})|0)(\.\d{1,2})?$/, message: '金额最多两位小数且不能为负', trigger: 'blur' } ] };后端接收金额时同样要校验,不能只依赖前端。现实中财务系统的金额字段经常被粘贴复制出千分位分隔符,所以后端接口最好统一用字符串接收金额,自己解析成BigDecimal,这样可以防止奇怪的科学计数法字符串被识别成数字。
5. 环境搭建、联调部署与避坑实操记录
5.1 本地开发环境配置清单
先把环境装好,这是很多人一开始就被卡住的地方。我列出我常用的版本组合,对应教材也很适配:
- JDK 8 或 11:不要直接上JDK 17,除非你想折腾新版Lombok兼容问题。
- Maven 3.6.3:镜像源换成阿里云,不然下载依赖慢到怀疑人生。
- MySQL 8.0:安装时选utf8mb4字符集,用户名root密码记好。
- Node.js 14 到 16:Vue CLI 5 搭配 Node 14 很稳。
- 开发IDE:IDEA 社区版足够,VSCode也可以。
数据库初始化时,不要直接手敲建表语句,写一份schema.sql放到项目resources里,让SpringBoot启动时自动执行。也可以用spring.sql.init.mode=always,但线上环境要关掉,防止每次启动清库。
5.2 从jar包依赖到数据库连接配置
pom.xml里除了spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java、lombok、jjwt,其他都按需引入。MySQL8版本的驱动类名是com.mysql.cj.jdbc.Driver,和MySQL5不一样。application.yml配置示例:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/textile_finance?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0有个特别经典的坑:MySQL8默认的认证插件是caching_sha2_password,老版本的连接驱动会报Unable to load authentication plugin 'caching_sha2_password'。如果遇到这个问题,要么升级驱动,要么在MySQL里给用户改成mysql_native_password,但新版驱动下这个错很少见了。
5.3 前后端联调的CORS跨域解决
开发时,前端跑在localhost:8081,后端跑在localhost:8080,不配置跨域接口根本调不通。最简单的方案是利用 Vite 或 Vue CLI 的 proxy 代理:
// vite.config.js server: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') } } }这样前端请求/api/auth/login,最终会被转发到后端/auth/login。如果你不用代理,也可以在后端加CORS配置:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true); } }注意allowCredentials(true)时不能搭配allowedOrigins("*"),会报错,必须用allowedOriginPatterns("*")。
5.4 打包部署到Linux服务器的完整流程
后端打包很简单:
mvn clean package -DskipTests生成的目标jar包,放到服务器上执行:
nohup java -jar textile-finance.jar --spring.profiles.active=prod > app.log 2>&1 &前端打包:
npm run build生成dist目录,把它上传到Nginx的html目录。Nginx做一个反向代理,把/api请求转发到后端的8080端口:
server { listen 80; server_name your.domain.com; root /var/www/textile; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }一个特别容易被忽略的坑:前端打包后的接口前缀,和后端Controller的路径必须一致。如果前端请求/api/sale/list,后端Controller就应该是/sale/list或者通过server.servlet.context-path统一加前缀。我建议习惯上用/api前缀,Nginx里就不用改路径,省事。
6. 常见问题、性能优化与从课设到上线的经验
6.1 高频问题速查表
下面是我开发这类系统时遇到频次最高的几个问题,直接列成对照表,方便你排查:
| 现象 | 常见原因 | 解决方案 |
|---|---|---|
| 前后端联调时,接口报CORS跨域 | 后端未配置跨域或配置用了allowedOrigins("*")与allowCredentials冲突 | 改用allowedOriginPatterns("*"),或使用前端代理 |
| BigDecimal数值传到前端变成科学计数法 | 默认Json序列化不支持大数字友好格式 | 在DTO的金额字段上使用@JsonFormat(shape = JsonFormat.Shape.STRING),或者配置Jackson全局序列化 |
| LocalDateTime 前后端显示差8小时 | 数据库时区/Jackson时区未设置GMT+8 | JDBC URL加serverTimezone=Asia/Shanghai,Spring配置中设置time-zone: GMT+8 |
| 刷新页面404 | Vue Router使用history模式,Nginx未配置路由回退 | Nginx增加try_files $uri $uri/ /index.html; |
| MySQL连接报Public Key Retrieval is not allowed | 客户端与MySQL8的caching_sha2_password认证交互问题 | JDBC URL加allowPublicKeyRetrieval=true和useSSL=false |
| 用MyBatis-Plus分页查询发现没返回total | 忘记配置分页插件 | 添加PaginationInnerInterceptor的Bean配置 |
| 事务方法内部调用不生效 | 同类内部调用绕过代理 | 拆分成不同的Service注入调用,或通过AopContext获取代理对象 |
6.2 多表报表查询慢,怎么加索引
财务系统最典型的慢SQL是“按日期范围统计各客户回款金额”。比如:
SELECT customer_id, SUM(amount) FROM receivable WHERE due_date BETWEEN '2024-01-01' AND '2024-12-31' GROUP BY customer_id;如果没有索引,数据量一大,全表扫描必然慢。解决办法是给receivable表的due_date和customer_id建联合索引:
ALTER TABLE receivable ADD INDEX idx_customer_due (customer_id, due_date);这里要注意索引的顺序。查询条件是日期范围,但GROUP BY是customer_id,联合索引中customer_id放前面,能最大程度减少扫描范围。这是数据库优化的基本功,答辩时能讲清楚这一点,会瞬间拉高你的印象分。
6.3 从课程设计到真实项目,还可以继续补哪些东西
如果你做完这套系统还有精力,我强烈建议往下面几个方向扩展,每扩展一项都能在简历上多写一句:
- 引入POI或EasyExcel,把财务报表导出成Excel。热词里有人问“java poi word能生成图表吗”,其实POI不只处理Word,用来生成Excel表格、设置样式、写几十行的数据完全没问题。财务报表导出是财务人员的刚需。
- 增加Redis缓存基础资料。客户列表、物料列表这些数据量不大但读取频繁,每次查数据库没必要。用Redis做一级缓存,设置10分钟过期,可以明显降低数据库压力。
- 增加数据权限。财务经理能看到所有数据,会计只能看到自己负责的客户或供应商。数据权限比菜单权限更难,但做出来非常加分。
- 引入Docker Compose部署。把MySQL、Redis、后端、前端一次性编排起来,到任何新服务器上都能快速还原环境。
当然,作为毕设或课设,先不要贪多。核心功能和稳定性最重要,宁可少一个图表模块,也不要让整套系统跑起来全是Bug。
收个尾:这套系统做下来,我最真实的体会
我实际做完这套纺织企业财务系统,最大的感受是“财务系统的难点不在写代码,而在搞清楚业务流”。如果你只把精力花在SpringBoot语法上,做出来的东西很可能是一个漂亮的CRUD壳子,老师一问“这个应付账款的账龄是怎么从合同日期和发票日期算出来的”,你答不上来,分数就上不去。反过来,你肯花两个晚上去理解纺织企业的采购、加工、销售流程,你会发现数据库表和Service层代码自己就长出来了。
另外,别怕在答辩前发现Bug,最怕的是藏着不修。我记得第一次跑通“审核销售单自动生成应收”的时候,忘了在事务里扣减库存,销售出库十几笔,库存一点没少。查了一晚上,最后发现Service方法没有加@Transactional,事务边界错了。那次教训之后,我写任何涉及多表更新的逻辑都会先画一张“数据流转图”,再动手写代码。
如果你正在做或准备做类似项目,建议先把凭证模块和应收应付模块打通,因为这是财务系统的灵魂。报表模块反而是其次,只要有正确的基础数据,报表就是几行SQL的事。最后再说一个小技巧:给前端Amount类字段都配一个字符串类型转化,防止BigDecimal序列化后精度丢失,这一个小动作,能省掉后面无数个对不上账的深夜。