大数跨境

SpringBoot+Vue 自动生成会计凭证:一张报销单怎样变成借贷分录

SpringBoot+Vue 自动生成会计凭证:一张报销单怎样变成借贷分录 企业软件源码
2026-11-03
17
导读:结合RuoYi Office源码与真实页面,追踪报销快照、Aviator模板、科目和辅助维度如何生成借贷分录;用可复算的拆税案例解释金额精度、平衡校验、来源幂等及失败重试,明确缺省模板与实际验收边界。

SpringBoot+Vue 自动生成会计凭证:一张报销单怎样变成借贷分录

自动制证的核心,是把已经确认的业务事实,按可解释的规则转换为分录。

图1|AI生成的转换关系示意,1500×1000。1,060/1,000/60及图中的人员、日期、科目是独立假设;拆税是配置示例,不是当前缺省报销模板效果。图中“符合会计准则”表示目标,实际政策与抵扣资格须经企业财务确认。

一、先看产品能交接什么,再问分录怎样生成

报销页面已经有费用、发票、部门和借款冲抵,为什么财务还要重新录凭证?

研发常给出的答案是“审批通过后插两条分录”。但同一张单据可能同时包含可抵扣税额、不同费用类型、多个承担部门和借款冲抵。只把票面总额复制两次,借贷可能平衡,业务口径却可能错。

RuoYi Office 当前有业务来源队列、凭证模板、表达式科目、辅助核算取值与凭证分录这些衔接能力。实施人员能查看模板取数,研发能沿来源追到业务快照,财务能决定什么时候生成。

本文沿“报销事实 → 来源快照 → 模板映射 → 分录校验”拆解。账套与科目的基础模型见第10篇,审核、过账和冲销留给第13篇。

读者需要解决的问题
本篇交付
证据范围
业务交给财务哪些数据
字段契约和精简ER
当前工作树源码、schema基线
为什么金额、科目和部门会取错
模板规则与映射边界
核心方法及真实模板配置
拆税之后怎么复算
独立假设分录与舍入反例
表达式服务隔离执行、金额模型
生成失败后能否重试
失败路径与专项验收表
调用链核对;未实操制证

本次证据截止2026年11月1日。 当前运行后端是资产教程专用包,未由最新源码重建。“工作树逻辑”“现成页面观察”“独立假设与模型验证”分别表述,不合并为最新版全链路测试通过。

二、把报销金额拆清楚:费用、税额和待付款不是同一个数

报销快照应保存各金额的业务含义,而不是只传一个模糊的amount。

图2|本次独立只读会话打开报销id=3,单号FI504-2026081500003:500.00报销、200.00冲抵、300.00应付,明细净额442.48、税额57.52。保留“流程实例不存在”提示;页面能读,不代表这条历史流程完整。未提交、审批或付款。

这张图解释了三个不同问题:费用发生多少、其中税额多少、还要付员工多少。不能因为本次只付300,就把500的费用事实缩成300;也不能看到57.52税额,就未经资格确认全部记为可抵扣进项税。

当前fillAmounts累计每行总额、净额和税额,并仅对deductible=true的行累计deductTaxAmount。当净额和税额同时提供时,会核对二者相加是否等于该行两位小数的总额;缺省净额取总额、税额取零。

这属于金额结构校验,不能代替税务资格判断,也不能扩写为“所有缺失或异常金额组合已经拒绝”。

来源快照保存什么

来源快照是制证取数时保存的业务字段集合。 模板不应依赖今天重新查询业务主表得到的任意新值。

报销enqueueVoucher先整理表头,再保存lines。字段不只是供页面显示,后续表达式需要按约定名称读取。

字段组
当前报销快照中的字段
转成分录时的责任
业务身份
来源类型、主键、单号、版本、业务日期
定位事项、推导期间、追溯来源
金额
totalAmount/netAmount/taxAmount/deductTaxAmount
区分票面总额、净额与确认可抵扣额
结算
offsetLoanAmount/payAmount/fundAccountId
解释冲抵与待付;不自动决定分录
归属
companyId/deptId/partnerType/partnerId/partnerName
为账套、部门及员工等映射提供输入
科目提示
expenseSubjectCode/fundSubjectCode
表达式返回编码后,在目标账套查科目
明细
lines[].expenseTypeId/deptId/projectId/amount/netAmount/taxAmount/deductible/subjectCode
留下明细事实;是否逐行展开取决于引擎能力

payload顶层复制表头,同时保留header和lines。于是totalAmount和payload.header.totalAmount都可有来源,但二者不是允许随意冲突的两套口径。

下面这段真实代码同时给出三个重要事实:来源版本固定为1,来源主金额取报销总额,报销请求明确关闭即时生成。

源码摘录1:FinanceReimburseServiceImpl.java904—920行,连续原文节选;完整路径与文件SHA-256见来源审计。

        payload.putAll(header);        payload.put("header", header);        payload.put("lines", lines);        FinanceVoucherSourceReqDTO req = new FinanceVoucherSourceReqDTO();        req.setSourceType(FinanceVoucherSourceTypeEnum.EXPENSE_CLAIM.getType());        req.setSourceId(bill.getId());        req.setSourceNo(bill.getBillCode());        req.setSourceVersion(1);        req.setCompanyId(bill.getCompanyId());        req.setBookId(bill.getBookId());        req.setBizDate(bill.getBillDate() != null ? bill.getBillDate() : LocalDate.now());        req.setAmount(bill.getTotalAmount());        req.setPayload(payload);        req.setAutoGenerate(false);        req.setRemark(StrUtil.blankToDefault(bill.getRemark(), "费用报销"));        voucherSourceService.submitSource(req);    }

入队时机,需要沿调用点核对

这份实现并不是所有报销一审批通过就立刻入队。

updateProcessStatus审批通过后先处理借款冲抵并计算待付:待付大于零时进入待付款;待付小于等于零时,处理票据和预算转实际并调用enqueueVoucher。

需要继续付款的路径,在addPaidAmount累计已付达到应付额后才调用enqueueVoucher;部分付款分支不做这一步。付款服务本身也提交PAYMENT来源,因此一次付款执行可能涉及两类业务事实。

判断
报销来源的实际行为
不能据此推导
审批通过,仍需付款
进入待付款分支
已入队、已制证
审批通过,无需再付款
调用报销来源入队
已生成、已过账
累计付款达到应付额
调用报销来源入队
当前付出的现金等于全部费用
来源提交autoGenerate=false
保存来源,等财务控制生成
模板自动审核开关已被执行

精简ER:来源与结果必须接得回去

图3|按当前数据对象和schema整理的逻辑ER。连线表示应用关联,不宣称有物理外键或全部唯一约束;快照字段保存在JSON中。

来源队列的唯一键基线包含tenant_id + book_id + source_type + source_id + source_version + deleted。凭证保存sourceQueueId/templateId,把取数依据与选中的模板身份接回去。

但templateId不是模板内容版本。当前模板更新会替换模板明细,生成时读取当前模板;业务快照可重复读取,并不等于未来任意一次重试都使用同一份规则。

设计建议: 正式投产时将模板变更审批、版本快照、变更前后的试算样本一同纳入实施流程。本文没有把这些建议写成当前已交付的模板版本管理。

三、模板真正读什么:sourceAmount与payload要一起核对

当前执行代码把来源主金额注入sourceAmount,因此它确实参与缺省制证。

buildEnv先复制payload顶层,再将header中尚未出现的键补进去,最后注入来源元数据。相同业务键的优先级是:顶层payload优先于header;保留变量如sourceNo/sourceAmount随后由来源对象覆盖。

源码摘录2:FinanceVoucherTemplateServiceImpl.java373—388行,连续原文节选;完整路径与文件SHA-256见来源审计。

        env.put("payload", source.getPayload() == null ? Map.of() : source.getPayload());        env.put("sourceType", source.getSourceType());        env.put("sourceId", source.getSourceId());        env.put("sourceNo", source.getSourceNo());        env.put("bizDate", source.getBizDate());        env.put("period", source.getPeriod());        env.put("sourceAmount", source.getAmount());        // 单据未带部门时,用当前登录人部门填 DEPT 辅助核算,避免 5602 等费用科目生成失败        if (env.get("deptId") == null) {            Long deptId = SecurityFrameworkUtils.getLoginUserDeptId();            if (deptId != null) {                env.put("deptId", deptId);            }        }        return env;    }

这解释了一个容易被注释误导的地方:FinanceVoucherSourceDO.amount注释仍写“仅用于列表展示与核对”,但当前buildEnv实际将它暴露为sourceAmount,缺省报销及付款模板也使用这个变量。本文按执行链描述,不沿用只读字段注释得出的旧判断。

另一方面,amount这个普通变量并不自动等于sourceAmount。报销表头约定的是totalAmount,不能因为表单提示写了“例如amount”,就认为每种来源都提供amount。

表达式
在当前报销来源中的含义
配置注意点
sourceAmount
来源对象主金额,即报销总额
缺省报销模板两边都用它
totalAmount
payload表头报销总额
应与来源主金额核对一致
deductTaxAmount
业务已标识可抵扣行税额之和
不等于全部票面税额
totalAmount - deductTaxAmount
本文拆税示例的费用金额
前提是企业确认该归集口径
expenseSubjectCode
来源侧选出的费用科目编码
返回编码字符串,不是科目ID
deptId
来源部门;缺失时可能补登录人部门
制证人的部门未必是费用承担部门

表达式能够算数,不代表它负责认定业务

当前求值使用Aviator,BOM声明版本5.4.3。服务把浮点字面量按BigDecimal解析,限制语法特性、可访问类和求值时间,并在取不到属性时允许返回nil。

这些设置的目标是纯取数计算,不是让模板调用任意Java能力,也不是本次完成了表达式沙箱安全审计。

模板保存时校验语法;但totalAmount拼成另一个合法变量名,语法仍可通过。金额求值为null后,渲染器会按零处理。因此,模板保存成功与业务字段齐全必须分开验收。

配置契约应列出每种来源的必需字段、类型和金额口径。缺关键字段时拒绝事实或给出明确错误,是建议补齐的产品契约,不能默认认为表达式引擎已经替业务做完了。

四、科目与辅助维度怎样映射,哪些细节不能想当然

当前渲染按模板行生成分录,不是自动遍历所有业务明细。

图4|本次只读打开P1操作学习账的SYS_EXPENSE_CLAIM既有编辑弹窗,未改值、未保存。两行均取sourceAmount:借方表达式expenseSubjectCode,贷方固定2241其他应付款;自动审核与自动过账显示“是”。这张配置图不代表本次执行了制证。

matchTemplate读取本账套、同来源类型的启用模板,按sort、ID排序。条件命中时取第一个;无条件模板先记为兜底,遍历完有条件规则后再采用;仍无兜底时尝试显式默认标记。

因此“sort小”并不是无条件模板永远先抢走全部业务。模板级条件决定选哪张模板,行条件决定选中模板里哪些分录参与,两者应分开配置。

设计决策
当前实现
为什么这样核对
模板优先级
同来源类型启用模板排序,再匹配条件
防止误认为模板名决定适用范围
科目选择
固定ID或表达式返回编码
编码必须在目标账套解析
表达式返回空科目
有固定subjectId可回退;否则失败
空值回退与错误编码不是同一情况
费用表头科目提示
取第一条有映射的费用类型;缺失再找差旅科目
不等于多费用自动拆分
辅助类型
只处理科目实际绑定的类型
模板多配的维度被忽略
缺辅助ID
尝试约定字段回填;保存再核对必填
不能把名称或登录默认值当完整主数据校验

科目编码不是科目ID

expenseSubjectCode应返回“560203”这样的编码字符串。表达式不空但找不到科目时,立即抛异常,不继续走固定科目回退;空值且模板带固定ID时,才使用固定科目。

源码摘录4:FinanceVoucherTemplateServiceImpl.java456—474行,连续原文节选;完整路径与文件SHA-256见来源审计。

    private FinanceSubjectDO resolveSubject(FinanceBookDO book, FinanceVoucherTemplateItemDO templateItem,                                            Map<String, Object> env) {        if (!FinanceTemplateSubjectModeEnum.isExpression(templateItem.getSubjectMode())) {            return requireSubject(book, templateItem.getSubjectId());        }        String code = expressionService.evaluateString(templateItem.getSubjectExpr(), env);        if (StrUtil.isNotBlank(code)) {            FinanceSubjectDO subject = subjectMapper.selectByCode(book.getId(), StrUtil.trim(code));            if (subject == null) {                throw exception(VOUCHER_TEMPLATE_SUBJECT_NOT_FOUND, code);            }            return subject;        }        // 历史队列未平铺科目编码时,回退到缺省模板写入的固定科目        if (templateItem.getSubjectId() != null) {            return requireSubject(book, templateItem.getSubjectId());        }        throw exception(VOUCHER_TEMPLATE_SUBJECT_NOT_FOUND, "表达式返回空值");    }

这一步只解决科目解析。正式保存时,凭证服务还核对账套、末级与启用状态;不能因表达式查到了科目,就说试算已经通过全部记账条件。

当前报销表头的resolveExpenseSubjectCode取第一笔映射成功的明细。虽然每个lines中都保存了subjectCode,缺省模板只有一个费用借方行,金额取整单主金额。

所以,住宿与交通映射不同科目时,不能把“两条明细均带科目”写成“自动生成两条费用分录”。 多费用、多部门、多项目需要明确分组契约及对应模板或扩展实现,并做专项验收。当前render没有业务明细自动展开循环。

辅助维度取值有顺序,也有边界

resolveAuxData只遍历科目绑定的auxTypes:先执行模板的idExpr/nameExpr,缺ID时再尝试约定字段。

科目绑定维度
当前约定回填字段
需要单独验的业务含义
DEPT
deptId/deptName
来源部门还是承担部门;缺失时登录人回填是否合理
EMPLOYEE
ID依次尝试employeeId/borrowerId/partnerId/userId
partnerId是否确实对应员工身份
PROJECT
projectId/projectName
报销表头未自动汇总lines中的多个项目
CUSTOMER / SUPPLIER
partnerId
及对应客户/供应商备用字段
ID存在不等于对象类型与法人归属正确

凭证validateAndBuildAuxKey会检查科目声明的各辅助ID是否齐全,再构造组合键。辅助对象存在、有效、属于正确账套或业务范围,仍需结合主数据和权限专项验证。

这也是为什么“帮忙补当前登录人部门”只能解释技术取值,不能证明费用已经分到正确承担部门。

五、用一个独立案例复算拆税,不拿演示记录凑成功链

以下数字是独立假设,不对应图2或图5的真实记录。

假设只有一类费用、一个承担部门、一名员工,没有借款冲抵;报销总额1,060.00,净额1,000.00,税额60.00,企业财务已经确认本例60.00可抵扣。这里不根据税率或发票类别自动推断抵扣资格。

来源输入可抽象为:

{  "sourceAmount": "1060.00",  "totalAmount": "1060.00",  "netAmount": "1000.00",  "taxAmount": "60.00",  "deductTaxAmount": "60.00",  "expenseSubjectCode": "660201",  "deptId": 103,  "partnerId": 1001}

这是阅读示例,金额字符串方便精确展示;隔离执行时将金额构造成BigDecimal。sourceAmount是引擎注入变量,不是要求业务在payload里再复制一个同名字段。660201及下表其他科目必须由目标账套实际配置,本文不创建它们。

先明确希望得到的分录

方向
示例科目
金额表达式
辅助取值
结果
借
管理费用明细660201
totalAmount - deductTaxAmount
DEPT取deptId=103
1,000.00
借
已配置的进项税明细
deductTaxAmount
按该科目实际声明
60.00
贷
其他应付款员工明细
sourceAmount
若绑定EMPLOYEE,取partnerId=1001
1,060.00

复算结果:1,060.00 - 60.00 = 1,000.00;借方1,000.00 + 60.00 = 1,060.00,与贷方一致。

若本例60.00全部不可抵扣,把deductTaxAmount改为0:费用为1,060.00,税额分录为0而跳过,贷方仍为1,060.00。

这说明拆税时更重要的输入是经业务确认的可抵扣额。把netAmount无条件放进费用借方、同时只借可抵扣税额,可能漏掉不可抵扣部分。

本例是基于已有金额字段和模板行能力的配置方案,未保存到真实系统,也未执行真实来源试算或生成。当前缺省报销模板实际仍是总额费用借方、其他应付款贷方两行,不应把上表说成开箱默认效果。

单张平衡之后,还要检查来源组合

当前缺省报销与付款模板的设计方向分别是:

来源类型
借方
贷方
当前默认金额
EXPENSE_CLAIM(1001)
费用科目
其他应付款
报销来源sourceAmount
PAYMENT(1002)
其他应付款
资金科目
付款来源sourceAmount

对于无冲抵、全额付款1,060的假设,两张凭证合起来应付相抵,费用只确认一次,资金只减少一次。不是让报销和付款两张模板都借费用、贷银行。

源码还有对旧缺省报销贷方的定向调整:仅命中默认标记且贷方仍是fundSubjectCode时,改挂其他应付款。因此也不能从模板备注“不会覆盖”推断系统永远不会调整任何旧缺省行。

但是图2展示的是500总额、200借款冲抵、300应付。若仅按上表生成“报销挂应付500”和“付款清应付300”,会残留200应付;它需要与借款核算一起说明冲抵如何结清。

这只是缺少冲抵分录时的算术检查,不是已确认运行库存在200错误余额。 本次没有串联该单的凭证、借款余额和实际付款,也不推断其他链路是否已经补记。

交付验收应把借款原放款、报销费用、冲抵和现金付款并排复算,核对费用、其他应收、其他应付与资金。只检查“每一张借贷平衡”,发现不了少记或重复确认。

六、两位小数、零金额与借贷平衡,谁在哪一层负责

金额先按分录行舍入,再累计借贷合计。

模板渲染时金额用BigDecimal,保留2位、HALF_UP;空值视为零,零金额行直接跳过;负金额会翻转借贷方向并取绝对值。

源码摘录3:FinanceVoucherTemplateServiceImpl.java405—428行,连续原文节选;完整路径与文件SHA-256见来源审计。

            BigDecimal amount = expressionService.evaluateDecimal(templateItem.getAmountExpr(), env);            amount = amount == null ? BigDecimal.ZERO : amount.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);            previewItem.setAmount(amount);            if (amount.compareTo(BigDecimal.ZERO) == 0) {                // 金额为零的分录没有账务意义(如免税报销的税额行),直接不生成                previewItem.setSkipReason("金额取数为 0");                continue;            }            Integer direction = templateItem.getDirection();            if (amount.compareTo(BigDecimal.ZERO) < 0) {                // 取数为负说明业务方向与模板配置相反(如冲减折扣),翻方向比记负数更符合会计习惯                direction = FinanceBalanceDirectionEnum.isDebit(direction)                        ? FinanceBalanceDirectionEnum.CREDIT.getDirection()                        : FinanceBalanceDirectionEnum.DEBIT.getDirection();                amount = amount.abs();                previewItem.setDirection(direction);                previewItem.setAmount(amount);            }            FinanceSubjectDO subject = resolveSubject(book, templateItem, env);            previewItem.setSubjectCode(subject.getCode());            previewItem.setSubjectName(StrUtil.blankToDefault(subject.getFullName(), subject.getName()));            Map<String, Object> auxData = resolveAuxData(subject, templateItem, env);            previewItem.setAuxData(auxData);

这里需要特别看执行代码:数据对象和页面有skipIfZero,但本次核对的render零值分支没有读取这个开关,实际遇零即跳过。不能把取消勾选描述成已经验证可保留零行。

负金额翻方向是普通模板渲染规则,也不等于红字冲销凭证的生命周期处理,两者不要混用。

舍入不是把最后一行偷偷改平

独立反例:两条借方分别取0.005,逐行舍入后各0.01,合计0.02;贷方取0.010,舍入后0.01,差0.01。

原始数学关系0.005 + 0.005 = 0.010成立,最终分录仍然不平。

检查位置
当前代码做什么
实施应如何处理
业务单据金额
汇总报销总额净额税额,金额保留2位
来源应尽量交付已确定的币种精度口径
模板行
逐行两位HALF_UP;零跳过、负数翻方向
测边界值、空字段与负值
模板汇总
少于2条有效分录拒绝;正式生成不平衡拒绝
不依赖数据库截断来修正差额
模板试算
可返回不平衡的借贷合计及balanced=false
试算能显示结果不等于能保存凭证
凭证保存
再验科目期间辅助字段、借贷相等且总额非零
正式保存是第二道业务检查

当前核对路径没有“自动补差一分钱”的通用逻辑。若业务需要尾差分配,应预先定义分配对象、顺序与记录依据,再专项实现和验收,不把差额悄悄塞给某个费用科目。

看一张已有结果,确认输出形态

图5|本次只读打开既有2026-08转-0001:借560203差旅费500、贷2241其他应付款500,辅助显示DEPT 103。来源FI504-2026090600008,与图2报销id=3不同,不能拼成同一单据制证成功链。页面的已过账状态属于既有数据,本次没有生成、审核或过账。

这个输出形态有助于核查科目、金额、辅助值和来源备注;“借贷平衡”只回答金额相等,不能证明税额处理、确认时点和重复来源组合都正确。

七、失败与重试:先分清重推事实,还是重新生成

重复提交同一来源版本,会返回已有来源,不会覆盖快照,也不会自动重新执行制证。

submitSource先按账套、来源类型、来源ID和版本查询,命中即返回。这对串行重复提交有明确行为;schema唯一键提供数据库约束基线,但并发冲突的返回语义仍要专项测试。

当前报销和付款入队调用都固定sourceVersion=1。单据内容变更后仍用同一身份重推,不能指望队列自动更新payload。业务版本递增、更正旧凭证和保留快照,应作为变更契约一起设计。

图6|当前主路径时序示意。实线表示调用顺序;本次没有执行这些写操作。autoGenerate=true是另一分支,当前报销和付款调用均传false。

三个开关,不要用一个“自动”概括

配置或入口
控制什么
当前观察
来源请求autoGenerate
入队后是否尝试自动生成
报销与付款传false
模板autoAudit
生成后是否调用自动审核入口
缺省报销、付款设置true;既有页面也显示“是”
模板autoPost
自动审核后是否继续过账
缺省设置true;执行仍受期间科目等约束
submitAndGenerate
同步提交并生成的另一服务入口
不能拿它代替报销当前submitSource调用

因此,当前模板也不能统一描述为“只生成暂存凭证”。generateVoucher创建凭证后,模板autoAudit=true时会调用autoAuditAndPost并传入autoPost;关闭相应配置时,结果状态才不同。

本篇只确认触发关系,不展开审核与过账账簿更新,更没有在本次环境点击这些操作。

不同入口的失败记录并不相同

来源服务有事务提交后自动尝试生成的分支。自动路径捕获Exception后调用自代理写失败;财务批量生成则每条通过自代理进入事务,捕获业务异常后记录失败原因。

源码摘录5:FinanceVoucherSourceServiceImpl.java347—364行,连续原文节选;完整路径与文件SHA-256见来源审计。

    public FinanceVoucherBatchRespVO batchGenerateVoucher(Collection<Long> ids) {        FinanceVoucherBatchRespVO respVO = new FinanceVoucherBatchRespVO();        if (CollUtil.isEmpty(ids)) {            return respVO;        }        for (Long id : ids) {            try {                // 逐条独立事务:一条模板没配好不该让整批生成失败                self.generateVoucher(id, null);                respVO.addSuccess();            } catch (ServiceException ex) {                FinanceVoucherSourceDO source = voucherSourceMapper.selectById(id);                respVO.addFailure(id, source == null ? null : source.getSourceNo(), ex.getMessage());                self.markVoucherFailed(id, ex.getMessage());            }        }        return respVO;    }

上面的批量代码只捕获ServiceException,不能扩写成“任何未知异常都记录失败并继续全部任务”。单条Controller直接调用generate,也没有在该入口统一捕获并更新失败原因。

失败点
本次源码核对到的行为
处理与重试边界
无法解析目标账套
可抛异常,无法正常入队
先检查显式账套或法人默认账套
来源期间缺失或不可记账
仍插入来源,标记FAILED和原因
财务按制度处理期间,再从生成入口重试
无匹配模板、科目不存在、辅助缺失
正式生成抛业务异常,事务回滚
核对模板与快照,不重新付款来触发
金额缺失变零、有效分录不足
可能跳过后不足2行而拒绝
回查字段名;也要防止部分缺失仍巧合平衡
借贷不平衡
正式模板渲染拒绝;凭证保存再检查
明确税额与尾差口径,不静默补平
自动生成业务或未知异常
自动路径catch Exception后标失败
不代表已有定时恢复或进程崩溃补偿
批量生成业务异常
该条失败,记录原因,继续下一条
未知异常需另外检查任务与日志
单条生成失败
异常返回;未统一写FAILED
页面错误与持久化failReason要分别验
已生成来源再次生成
服务拒绝
串行防重不等于并发生成已验收

自动生成安排在事务提交后,是为了避免生成异常把业务更新一起标记回滚;但它不是持久消息任务。业务提交后进程退出、提交回调中的事务资源行为、失败记录能否持久化,都需要独立集成验证。

本次也没有证明生成入口具备完整的并发抢占、行锁或状态条件更新。不要由来源入队唯一键直接推导出“同一来源并发点生成只会得到一张凭证”。

八、把配置合同变成可执行的验收清单

先交付来源样本与预期分录,再验按钮。 下面表格可直接作为研发、实施和财务共同评审的起点;“待验”不代表已测试通过。

验收场景
输入与观察点
应核对的结果
本次状态
全额可抵扣单费用
1060总额、60确认可抵扣
借费用1000、借税60、贷应付1060
表达式隔离执行通过;真实制证待验
不可抵扣税额
可抵扣额为0
费用包含全部1060,税行跳过
表达式与独立金额检查通过;真实制证待验
普通字段缺失
拼错税额变量
明确缺字段风险;不能只看语法
nil行为已隔离确认;业务拦截待验
逐行舍入
两借0.005、一贷0.010
识别0.01差额,正式生成拒绝
金额模型确认;真实入口待验
多费用类型
两条明细映射不同科目
不丢失分组,不全部落首条科目
当前缺省不自动展开;扩展方案待验
多部门多项目
行维度不同,表头为空
不误用制证人部门
源码边界已确认;运行结果待验
借款冲抵
500总额、200冲抵、300付款
费用/借款/应付/资金整体解释一致
仅展示既有单据与算术反例;组合待验
来源重复推送
同账套类型ID版本重复
同一来源身份不覆盖快照
源码核对;并发返回语义待验
改单再推
仍版本1与递增版本对照
有清楚的更正与历史规则
当前调用固定1;集成待验
失败后重试
修配置,不重付款
旧快照按选定规则重试且无重复结果
调用链核对;失败注入待验
模板变更
相同来源、变更前后模板
可解释规则版本,不假设完全复现
当前templateId非内容版本;待建设评审
手工指定模板
跨账套/类型/停用模板ID
服务端拒绝不适用模板
当前显式ID路径主要校验存在,专项待验
自动配置组合
false/true与审核过账开关
入队、生成、审核、过账分别判断
真实配置只读观察;执行待验

本次实际验证了哪些内容

本篇在过程目录内用Java17重新编译当前FinanceExpressionServiceImpl,使用BOM对应的Aviator5.4.3执行金额、字符串科目、部门ID、缺失变量与条件表达式。

共17项检查通过,其中前13项调用原始表达式服务并校验返回或结果;后4项是自建金额舍入与冲抵残留模型。没有启动Spring上下文,没有调用模板render、制证服务、数据库或完整审批链。

源码片段为5段逐字摘录,每段不超过50行;完整路径、定位行号、文件SHA-256和代码核对结果随稿保存。三张真实系统截图分别覆盖业务单据、模板配置和既有分录,均为独立会话只读现成数据。

快速体验与采购核对

本地已启动环境可在财务菜单查看费用报销、凭证模板和凭证详情,先对照本文字段表读取已有记录。模板编辑弹窗能查看配置,正式环境的保存与生成应按企业权限和验收计划执行。

需要从源码启动时,按交付包的初始化与配置说明准备数据库、Redis、账套、科目和期间;后端Java17以yudao-server为单体入口,管理端主应用在ruoyi-office-vben/apps/web-antd,常用pnpm dev:antd。环境依赖和财务模块范围以实际交付为准。

商业源码按实际采购范围交付,公开仓库不代表包含全部财务模块。验收时除“能构建、能登录”,还应包含模板样本、来源契约、科目与辅助配置、错误处理和组合核算结果。

FAQ

1. 报销审批通过,是不是自动生成凭证了?

当前不是。仍需付款的报销先进入待付款分支;无需再付款或累计付款达到应付额时才提交来源,且报销调用autoGenerate=false。入队不等于生成,模板审核过账开关也不会在仅入队时执行。

2. 为什么模板写amount不一定能取到报销金额?

因为变量来自具体来源契约。当前报销表头提供totalAmount,引擎将来源主金额注入sourceAmount,并未自动创建通用amount别名;字段缺失可能返回nil,再被金额渲染当成零。

3. 有发票税额,缺省模板就会自动拆税吗?

不会据此推导。当前缺省报销是总额费用与其他应付款两行。拆税需明确经确认的可抵扣额、费用口径和模板行,本篇1060/1000/60属于独立配置案例。

4. 多条报销明细,会自动各生成一条分录吗?

当前render遍历模板行,没有自动展开lines。报销表头费用科目提示还会取第一条有映射的费用类型。多科目、多部门、多项目要单独设计并验证分组。

5. 修好模板后重推原单据,为什么未必重新生成?

同一来源身份与版本命中时直接返回已有记录,不覆盖payload、不重新触发生成。应区分“重推业务事实”和“对已保存来源重试生成”,改单还涉及版本及旧凭证更正策略。

来源与本次边界

依据当前本地报销、付款、来源服务、模板渲染、表达式服务、凭证组装以及schema基线核对;完整定位与图片记录见资料与配图来源。本篇未引用外部产品图片。

相关文章可继续阅读账套、科目与辅助核算建模、从业务单据到总账的模块划分。本篇聚焦转换契约,后续生命周期另文展开。

保留的限制:报销id=3历史流程不存在;P1学习账2026-08报表差额-500未定性、损益未结转阻断月结,本文未修复或重新验收。既有凭证与该报销不同记录,不能作为同一业务闭环。并发生成、模板变更复现、权限隔离、失败持久化与完整核算仍待专项验证。

本次只交付本地图文和验证材料,没有创建、保存或提交财务单据,没有付款、核销、生成、审核、过账、结转或改库。双端预览通过只表示文章布局与图片加载正常,不代表财务产品验收通过。

你们的自动制证最难落地的是金额口径、明细分组,还是来源变更后的历史解释?

【声明】内容源于网络
0
0
企业软件源码
RuoyiOffice 是一套基于 Spring Boot + Vue3 +Uniapp 的企业一体化管理平台,集 OA、CRM、ERP、工作流、HR、资产、合同、项目、AI应用等业务于一体,帮助企业用一个系统协同管理多类核心业务。
内容 182
粉丝 0
企业软件源码 RuoyiOffice 是一套基于 Spring Boot + Vue3 +Uniapp 的企业一体化管理平台,集 OA、CRM、ERP、工作流、HR、资产、合同、项目、AI应用等业务于一体,帮助企业用一个系统协同管理多类核心业务。
总阅读4.8k
粉丝0
内容182