简介:赤龙ERP是一款面向中小企业及开发者的技术人员的免费开源企业级ERP系统,聚焦财务业务一体化管理,解决传统系统模块割裂、数据不互通、定制成本高等痛点,适用于进销存、财务核算、工作流协同等典型企业应用场景。资源包共2000个文件,主体为802个Java后端逻辑、465个JavaScript前端交互、209个HTML页面结构及196个JSP服务端渲染文件,辅以SQL数据库脚本、XML配置与CSS样式资源,整体压缩包53.26MB,结构完整、分层清晰,便于二次开发与模块化学习。已有88人下载学习,可直接获取一套涵盖计划预算、订单履约、出入库、发票收付款、凭证总账等全链路功能的可运行系统源码,包含Bootstrap与Font Awesome等成熟UI组件集成,以及完整的MVC分层目录结构与配置说明,适合Java Web开发者深入理解企业级ERP架构设计与财务业务耦合实现。
1. 赤龙ERP不是又一个“开源玩具”:它要啃下财务业务一体化这个硬骨头,专治ERP落地时账实不符、凭证断链、预算失灵的顽疾
你试过上线一套开源ERP后,仓库明明出库了,财务系统里却没生成对应凭证?或者销售订单已确认,但采购计划迟迟不联动,生产排程全靠Excel人工拉通?赤龙ERP的标题里那句“实现真正的财务业务一体化”,不是口号——它直指国内中小企业ERP落地最痛的三根刺:业务单据流不到财务、财务分录对不上业务动因、预算控制卡不住实际发生。它不堆砌模块,而是用“计划预算→订单→出入库→发票→收付款→凭证→总账”这条刚性数据链,把管理流、信息流、数据流拧成一股绳。适合两类人:一是被商业ERP年费和定制开发拖垮的中小制造/贸易企业IT负责人,二是想真正吃透业财融合底层逻辑的开发者——它开源、可审计、模块解耦,但绝不牺牲闭环校验能力。这不是拿来即用的CMS,而是一套需要你理解“为什么凭证必须由出入库单自动生成”“为什么付款单必须反向校验应付余额”的实战型架构。
2. 从零跑通赤龙ERP最小闭环:用Docker Compose启动核心服务并验证订单→入库→凭证链路
赤龙ERP采用微服务架构,但官方提供了开箱即用的Docker Compose部署方案。我们跳过源码编译,直接用容器快速验证其业财闭环能力。关键不是“跑起来”,而是验证业务单据能否自动触发财务动作——这才是它区别于普通进销存系统的分水岭。
2.1 下载与初始化:只取必要组件,避开前端构建陷阱
赤龙ERP的GitHub仓库(chilong-erp)中,docker-compose.yml文件定义了核心服务依赖。注意:不要克隆整个仓库,官方在/deploy/docker目录下提供了精简版部署包(约42MB),仅含backend、database、redis三个必需服务镜像及初始化SQL脚本。执行以下命令:
# 创建独立部署目录,避免污染全局环境 mkdir -p chilong-quickstart && cd chilong-quickstart # 下载精简部署包(非完整源码) curl -L https://github.com/chilong-erp/deploy/releases/download/v1.3.0/docker-deploy-v1.3.0.tar.gz | tar -xz # 启动服务(后台运行) docker-compose up -d # 等待数据库初始化完成(约90秒) sleep 90提示:官方镜像基于PostgreSQL 15 + Spring Boot 3.2构建,
docker-compose.yml中backend服务的depends_on已配置健康检查,无需手动等待DB就绪。若docker-compose logs backend | grep "Started Application"未出现,说明初始化SQL执行失败——此时应检查init.sql中CREATE EXTENSION IF NOT EXISTS "uuid-ossp";是否被注释(常见于某些PostgreSQL镜像变体)。
2.2 创建测试单据:用API模拟真实业务流,绕过前端UI干扰
赤龙ERP的REST API设计严格遵循业财事件驱动原则。我们跳过前端页面,直接用curl触发最小闭环:销售订单 → 仓库入库 → 自动生成凭证。这三步必须全部成功,才证明其“一体化”非空谈。
# 步骤1:创建销售订单(携带客户、商品、数量) curl -X POST http://localhost:8080/api/v1/orders \ -H "Content-Type: application/json" \ -d '{ "customerCode": "CUST001", "orderItems": [{ "productCode": "PROD001", "quantity": 10, "unitPrice": 100.00 }] }' | jq '.id' # 记录返回的order_id,如"ORD20240501001" # 步骤2:基于该订单创建入库单(模拟仓库收货) curl -X POST http://localhost:8080/api/v1/warehouses/inbound \ -H "Content-Type: application/json" \ -d '{ "sourceOrderType": "SALE_ORDER", "sourceOrderId": "ORD20240501001", "inboundItems": [{ "productCode": "PROD001", "quantity": 10, "warehouseCode": "WH_MAIN" }] }' # 步骤3:查询凭证表,验证是否自动生成(关键!) curl "http://localhost:8080/api/v1/finance/vouchers?sourceType=WAREHOUSE_INBOUND&sourceId=INB20240501001" | jq '.data[0].voucherNumber'逻辑说明与参数深挖:
sourceOrderType必须为SALE_ORDER(而非PURCHASE_ORDER),这是触发“销售驱动库存”逻辑的开关;若填错,系统将拒绝关联凭证。inboundItems中的warehouseCode需与系统预设仓库编码一致(默认WH_MAIN),否则入库单状态变为INVALID_WAREHOUSE,后续凭证生成被拦截。- 第三步的
sourceType参数是业财链路的“锚点”:只有当凭证的sourceType等于业务单据类型(如WAREHOUSE_INBOUND),且sourceId匹配,才证明凭证由业务单据主动触发,而非财务人员手工录入——这才是闭环的本质。
3. 财务凭证引擎深度解析:为什么赤龙ERP的凭证生成不是CRUD,而是规则驱动的状态机
赤龙ERP的凭证生成模块(finance-voucher-engine)不是简单的“单据→插入凭证表”,而是一个基于业务单据状态变迁的规则引擎。它把财务动作拆解为三类原子事件:CREATE(创建)、ADJUST(调整)、VOID(作废),每种事件对应不同的会计科目映射规则和借贷方向校验逻辑。理解这点,才能调参、排错、二次开发。
3.1 凭证规则配置:用YAML定义科目映射,而非硬编码
凭证生成逻辑集中在/config/voucher-rules/目录下的YAML文件。以入库单为例,warehouse-inbound.yaml定义了如下核心规则:
# warehouse-inbound.yaml event: CREATE sourceType: WAREHOUSE_INBOUND accountingRules: - condition: "item.productCategory == 'FINISHED_GOODS'" debitAccount: "1405.01" # 库存商品-产成品 creditAccount: "2202.01" # 应付账款-暂估 - condition: "item.productCategory == 'RAW_MATERIAL'" debitAccount: "1403.01" # 原材料 creditAccount: "2202.02" # 应付账款-材料款 validation: - rule: "sum(debitAmount) == sum(creditAmount)" message: "借贷不平衡" - rule: "sourceOrder.status == 'CONFIRMED'" message: "源订单未确认,禁止生成凭证"参数说明:
condition支持SpEL表达式,可访问item(入库明细)、sourceOrder(源订单)、currentUser等上下文对象。这是灵活适配不同行业科目的关键——比如贸易公司可将RAW_MATERIAL条件改为"item.isImported == true",自动映射进口关税科目。validation区块是安全阀:第二条规则强制要求源订单状态为CONFIRMED,若有人绕过前端直接调API创建入库单,此校验会直接拦截,避免凭证与业务脱钩。- 注意:所有YAML文件需放在
backend服务的/config/voucher-rules/路径下,修改后无需重启服务,引擎会监听文件变更并热加载(日志中可见Reloaded voucher rules for WAREHOUSE_INBOUND)。
3.2 凭证状态机:从业务单据到财务凭证的7个状态跃迁
赤龙ERP为每张凭证维护独立状态机,状态流转严格绑定业务事件。以下是入库凭证的典型路径:
| 状态(State) | 触发事件 | 业务含义 | 财务约束 |
|---|---|---|---|
DRAFT | 入库单保存 | 凭证草稿,可编辑 | 无 |
GENERATED | 入库单审核通过 | 系统自动生成,不可修改金额 | 必须满足validation规则 |
POSTED | 财务主管点击“过账” | 正式计入总账,影响余额 | debitAmount/creditAmount锁定 |
ADJUSTED | 关联采购发票后调整 | 补充进项税额,生成调整分录 | 新分录sourceType=ADJUSTMENT |
REVERSED | 入库单作废 | 生成红字凭证冲销原分录 | reversedVoucherId指向原凭证 |
关键洞察:POSTED状态是业财分界线——此前业务部门可协同修改,此后财务数据进入不可逆流程。这种设计迫使业务单据必须在审核前完成所有财务要素校验(如税率、币种、结算方式),杜绝“先入库后补票”的乱象。
4. 避坑指南:赤龙ERP落地时90%团队踩过的5个业财一致性陷阱
赤龙ERP的强校验机制是双刃剑:它能守住财务底线,但若业务流程未对齐,就会频繁报错、阻塞操作。以下是我在3个制造业客户现场踩出的血泪经验,按发生频率排序:
4.1 现象:入库单审核成功,但凭证状态始终为DRAFT,日志显示No matching voucher rule found
原因:入库单明细中的productCategory字段为空,或值不在warehouse-inbound.yaml的condition列表中(如填了"MATERIAL"但规则里写的是"RAW_MATERIAL")。
解决:
- 检查入库单API请求体,确保
inboundItems包含"productCategory": "RAW_MATERIAL"; - 在数据库
product表中确认该商品的category_code与规则YAML中字符串完全一致(区分大小写、空格); - 临时调试:在YAML中添加兜底规则
- condition: "true",观察是否生成凭证,再逐步收紧条件。
4.2 现象:凭证生成成功,但总账余额与业务单据金额不符,差额恒为0.01元
原因:多币种场景下,系统默认使用BigDecimal.ROUND_HALF_UP进行金额四舍五入,但前端传入的单价/数量可能含多余小数位(如100.0000),导致中间计算溢出。
解决:
- 在
application.yml中配置finance.precision: 2(强制金额保留2位小数); - 前端提交时,对
unitPrice、quantity字段做Math.round(value * 100) / 100处理; - 切记:不要在数据库层面用
DECIMAL(10,4)存储金额——赤龙ERP要求所有金额字段为DECIMAL(18,2),否则凭证引擎校验失败。
4.3 现象:采购订单生成应付凭证后,销售订单收款时无法核销该应付,系统提示Unmatched payable amount
原因:赤龙ERP的应收应付核销基于业务单据源头关联,而非单纯金额匹配。采购订单(PO)与销售订单(SO)必须通过businessRelation字段显式绑定(如PO的relatedSoId指向SO ID),否则视为独立债务关系。
解决:
- 在创建采购订单API中,显式传递
"relatedSoId": "SO20240501001"; - 若历史数据未绑定,需执行SQL更新:
UPDATE purchase_order SET related_so_id = 'SO20240501001' WHERE id = 'PO20240501001'; - 核销操作必须调用
/api/v1/finance/reconcile接口,传入{soId, poId, amount},不能直接修改receivable_balance字段。
4.4 现象:预算控制失效,超支采购订单仍能审核通过
原因:预算模块默认启用budgetCheckMode: LOOSE(宽松模式),仅在凭证过账时校验,而非订单审核时拦截。
解决:
- 修改
application.yml:budget.check.mode: STRICT; - 在
/config/budget-rules/下新增purchase-order.yaml,定义"budgetItem": "MATERIAL_COST"与采购品类映射; - 重要:预算科目必须在总账科目表中启用
isBudgetControl: true,否则规则不生效。
4.5 现象:Redis缓存击穿导致高并发下单时凭证重复生成
原因:凭证生成前会读取voucher:lock:{sourceId}锁,但若Redis节点故障,锁未释放,后续请求因获取锁超时(默认5秒)而跳过校验直接生成。
解决:
- 调整
application.yml:redis.lock.timeout: 30s(延长锁等待); - 在
VoucherService.generate()方法中,增加if (voucherRepo.existsBySourceIdAndSourceType(sourceId, sourceType)) { return; }双重校验; - 生产环境必须部署Redis哨兵模式,禁用单节点。
5. 进阶技巧:用自定义凭证模板实现“一单多账”,解决制造业委外加工场景的业财断点
制造业委外加工是业财一体化的经典难点:一张委外加工单,需同时生成三类凭证——
① 发出原材料(借:委托加工物资,贷:原材料)
② 支付加工费(借:委托加工物资,应交税费-进项税,贷:银行存款)
③ 加工完成入库(借:库存商品,贷:委托加工物资)
赤龙ERP原生不支持“一单生成多张凭证”,但可通过扩展凭证规则引擎实现,无需修改核心代码。
5.1 构建委外加工凭证模板:用Groovy脚本替代YAML规则
在/config/voucher-rules/下新建outsourcing-process.groovy,内容如下:
// outsourcing-process.groovy def generateVouchers(source) { def vouchers = [] // 凭证1:发出原材料 vouchers << [ voucherType: "OUTSOURCING_ISSUE", debitAccount: "1407.01", // 委托加工物资 creditAccount: "1403.01", // 原材料 amount: source.issueAmount ] // 凭证2:支付加工费 vouchers << [ voucherType: "OUTSOURCING_FEE", debitAccount: "1407.01", // 委托加工物资 debitAccount2: "2221.01", // 应交税费-进项税(可选) creditAccount: "1002.01", // 银行存款 amount: source.feeAmount, taxAmount: source.taxAmount ] // 凭证3:加工完成入库 vouchers << [ voucherType: "OUTSOURCING_RECEIVE", debitAccount: "1405.01", // 库存商品 creditAccount: "1407.01", // 委托加工物资 amount: source.receiveAmount ] return vouchers }关键配置:
- 在
application.yml中启用Groovy支持:voucher.engine.script-enabled: true; - 将
outsourcing-process.groovy放入backend服务的/config/voucher-rules/目录; - 委外加工单的
sourceType必须设为OUTSOURCING_PROCESS,引擎会自动匹配此脚本。
5.2 验证模板有效性:用单元测试保障业财逻辑不被破坏
赤龙ERP提供VoucherRuleTest基类,可在test/java下编写验证:
@Test void testOutsourcingVoucherGeneration() { // 构造委外加工单测试数据 OutsourcingProcess source = new OutsourcingProcess(); source.setIssueAmount(new BigDecimal("5000.00")); source.setFeeAmount(new BigDecimal("1000.00")); source.setTaxAmount(new BigDecimal("130.00")); source.setReceiveAmount(new BigDecimal("6130.00")); // 执行脚本生成凭证 List<Map<String, Object>> vouchers = voucherEngine.generateVouchers( "OUTSOURCING_PROCESS", source); // 断言三张凭证生成 assertEquals(3, vouchers.size()); assertEquals("OUTSOURCING_ISSUE", vouchers.get(0).get("voucherType")); assertEquals("OUTSOURCING_FEE", vouchers.get(1).get("voucherType")); assertEquals("OUTSOURCING_RECEIVE", vouchers.get(2).get("voucherType")); // 断言借贷平衡(总借=总贷) BigDecimal totalDebit = vouchers.stream() .map(v -> (BigDecimal) v.get("amount")) .reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal totalCredit = vouchers.stream() .map(v -> (BigDecimal) v.get("amount")) .reduce(BigDecimal.ZERO, BigDecimal::add); assertTrue(totalDebit.compareTo(totalCredit) == 0); }我的习惯:每次上线新凭证规则前,必须跑通这个测试。曾有一次因Groovy脚本中
debitAccount2拼写错误为debitAccout2,测试直接报NullPointerException,避免了生产环境凭证生成失败。业财一体化容不得“差不多”,每个科目编码、每笔金额流向,都得有测试用例钉死。希望帮到你。
本文还有配套的精品资源,点击获取