会计凭证配置使用函数说明
适用页面:财务管理 → 凭证模板
引擎:Aviator 5.4(沙箱,只开放if/return与内置函数)
对照代码:FinanceExpressionServiceImpl、FinanceVoucherTemplateServiceImpl
日期:2026-10-17
凭证模板把业务单据入队时的字段,翻译成借贷分录。金额、摘要、科目编码、适用条件、分录条件、辅助核算取值,都写同一种表达式。变量名直接写字段名,例如 sourceAmount、taxAmount,不要写 ${...},也不要写 payload.amount。
一条模板分录生成凭证上的一行。明细数组 lines 不会自动按行展开。
1. 表达式写在哪
| 位置 | 必填 | 结果类型 | 为空时 |
|---|---|---|---|
| 模板适用条件 | 否 | 布尔 | 当作兜底模板,不参与「第一个命中」 |
| 凭证摘要 | 否 | 文本 | 用第一条分录的摘要 |
| 分录金额 | 是 | 数字 | 保存失败 |
| 分录摘要 | 否 | 文本 | 用来源单号,再没有则用模板名称 |
| 分录条件 | 否 | 布尔 | 本行生成 |
| 科目编码 | 取科目选「表达式」时必填 | 文本,须是本账套已有科目编码 | 空则回退该行上保存的固定科目 |
辅助核算 idExpr / nameExpr | 否 | 编号 / 名称 | 按下文约定字段回填 |
取科目两种方式:
- 固定科目:直接选末级科目。
- 表达式:返回科目编码字符串,例如
fundSubjectCode、'1002'。编码在本账套找不到会生成失败。
保存时只做语法校验,不拿真实单据试算。同一来源类型按「优先级」数字从小到大匹配:先找第一个条件为真的模板;带条件的都没命中时,用条件为空的那张;仍没有,再用勾了「兜底模板」的那张。
勾选自动过账时,系统会同时打开自动审核。
2. 书写约定
文字用单引号。字符串拼接用 +。
'费用报销 ' + sourceNo
'计提' + depreciationPeriod + '折旧'比较与逻辑:
taxAmount > 0
partnerType == 2 && netAmount > 0
direction == 1partnerType:1 客户,2 供应商,3 员工,4 其他。
字段缺失时该变量是 nil,不会报「变量不存在」。nil 不能直接参与加减和拼接,先判断再运算:
taxAmount == nil ? 0 : taxAmount
partnerName == nil ? sourceNo : ('应付 ' + partnerName)分支用三元,或用 if / return(花括号不能省):
taxAmount > 0 ? '22210101' : '2202'if (direction == 1) {
return '1002';
} else {
return '5602';
}数字按十进制计算,避免二进制浮点误差。金额表达式的最终结果四舍五入到 2 位。算出 0 的分录不生成(进项税额为 0 的那一行因此不会出现)。算出负数时,系统取绝对值并调换借贷方向。跳过之后仍须至少留下两行,且借方合计等于贷方合计,否则整张凭证生成失败。
界面上的「为零跳过」当前不改变上述结果:金额为 0 一律不生成。
3. 随时可用的变量
这些变量不依赖具体单据,生成时由队列记录注入:
| 变量 | 含义 |
|---|---|
sourceType | 来源类型编码,见第 6 节 |
sourceId | 来源单据 ID |
sourceNo | 来源单号,摘要优先用它 |
sourceAmount | 入队时的单据主金额,列表核对用的就是它 |
bizDate | 业务日期,yyyy-MM-dd 字符串 |
period | 会计期间,yyyy-MM |
payload | 完整快照。日常取数写平铺字段即可 |
header | 表头对象,与平铺字段内容相同 |
lines | 明细数组。当前不会按行展开成分录 |
deptId | 单据没带部门时,用当前登录人部门补上 |
payload 里 header 的每个字段都会平铺到根上。报销单写 taxAmount 与写 header.taxAmount 等价;推荐写短名。
4. 函数
下面是凭证配置里会用到、且当前沙箱允许的函数。不能写 new、Java 静态方法(如 java.lang.Math.abs)、循环、lambda、eval。
4.1 空值与类型
没有名为 nvl 或 截取 的中文函数。空值用三元,截取用 string.substring。
| 写法 | 作用 | 示例 |
|---|---|---|
a == nil ? 默认值 : a | 字段缺失时给默认值 | offsetLoanAmount == nil ? 0 : offsetLoanAmount |
str(x) | 转成文本 | str(partnerId) |
long(x) | 转成整数 | long(partnerType) |
decimal(x) | 转成金额小数 | decimal(sourceAmount) |
max(a, b, ...) | 取较大值 | max(payAmount, 0) |
min(a, b, ...) | 取较小值 | min(taxAmount, deductTaxAmount) |
4.2 字符串
下标从 0 开始,end 不包含在结果里,与 Java substring 相同。结束位置超过长度会执行失败,先判断长度。
| 函数 | 作用 | 示例 |
|---|---|---|
string.length(s) | 字符数 | string.length(remark) |
string.substring(s, begin) | 从 begin 截到末尾 | string.substring(bizDate, 0, 7) 得到 yyyy-MM |
string.substring(s, begin, end) | 截取 [begin, end) | 见下方摘要截取 |
string.contains(s, target) | 是否包含 | string.contains(remark, '差旅') |
string.startsWith(s, prefix) | 是否以前缀开头 | string.startsWith(billNo, 'BX') |
string.endsWith(s, suffix) | 是否以后缀结尾 | string.endsWith(sourceNo, '01') |
string.indexOf(s, target) | 首次出现位置,没有则 -1 | string.indexOf(remark, '-') |
string.replace_first(s, regex, replacement) | 替换第一处 | string.replace_first(remark, ' ', '') |
string.replace_all(s, regex, replacement) | 全部替换 | string.replace_all(partnerName, '公司', '') |
摘要截到 50 个字(字段可能为空、也可能不足 50 字):
remark == nil ? sourceNo : (string.length(remark) > 50 ? string.substring(remark, 0, 50) : remark)把单号和备注拼起来再截取:
letText = (sourceNo == nil ? '' : sourceNo) + (remark == nil ? '' : remark)沙箱未开放 let 赋值。拼完再截,写成一条表达式:
string.length((sourceNo == nil ? '' : sourceNo) + (remark == nil ? '' : remark)) > 50
? string.substring((sourceNo == nil ? '' : sourceNo) + (remark == nil ? '' : remark), 0, 50)
: (sourceNo == nil ? '' : sourceNo) + (remark == nil ? '' : remark)4.3 数值
分录金额最终会四舍五入到分,中间过程不必自己舍入。
| 函数 | 作用 | 示例 |
|---|---|---|
math.abs(n) | 绝对值。自己取绝对值后,负数不再调换借贷 | math.abs(sourceAmount) |
math.round(n) | 四舍五入到整数 | math.round(totalAmount) |
math.floor(n) | 向下取整 | math.floor(totalAmount) |
math.ceil(n) | 向上取整 | math.ceil(totalAmount) |
价税拆分直接相减即可,尾差由金额列的两位四舍五入消化:
totalAmount - netAmount报销实付(价税合计减冲借款;缺字段按 0):
(totalAmount == nil ? 0 : totalAmount) - (offsetLoanAmount == nil ? 0 : offsetLoanAmount)4.4 日期
业务日期、期间已经是字符串,摘要直接拼接即可,一般不必再格式化。
| 函数 | 作用 |
|---|---|
sysdate() | 当前日期时间 |
now() | 同 sysdate() |
date_to_string(date, 'yyyy-MM-dd') | 日期转字符串 |
string_to_date(text, 'yyyy-MM-dd') | 字符串转日期 |
从业务日期取年月:
string.substring(bizDate, 0, 7)4.5 明细数组
lines 是数组,本行不会按明细拆成多条分录。只在确实要取某一行时使用。filter / map / reduce 依赖 lambda,当前沙箱不可用。
| 函数 | 作用 | 示例 |
|---|---|---|
count(lines) | 明细行数 | count(lines) > 0 |
seq.get(lines, 0) | 取第 1 行(下标从 0) | seq.get(lines, 0).amount |
取第 1 行金额时先确认有明细,否则取空会执行失败:
count(lines) > 0 ? seq.get(lines, 0).netAmount : 05. 辅助核算表达式
科目绑定了辅助核算时才取值。模板里多写的维度会被忽略。写法是 JSON,键为维度编码:
{"DEPT":{"idExpr":"deptId","nameExpr":"deptName"}}也可以只写编号表达式字符串:{"DEPT":"deptId"}。
未写表达式时,按维度从单据字段回填:
| 维度编码 | 编号字段(按顺序取第一个有值的) | 名称字段 |
|---|---|---|
CUSTOMER | partnerId、customerId | partnerName、customerName |
SUPPLIER | partnerId、supplierId | partnerName、supplierName |
DEPT | deptId | deptName |
EMPLOYEE | employeeId、borrowerId、partnerId、userId | employeeName、borrowerName、partnerName、userName |
PROJECT | projectId | projectName |
CONTRACT | contractId | contractName |
费用科目绑了部门、单据又没带 deptId 时,用当前登录人部门,避免费用分录因辅助核算为空而失败。
6. 各来源单据可取的字段
系统缺省模板已经种在账套里(编码 SYS_*)。客户改过的模板不会被再次覆盖。下表是入队时实际平铺的字段,金额表达式优先用「缺省金额列」那一列,与现有模板一致。
| 来源 | 编码 | 缺省模板 | 缺省金额 | 还可使用的字段 |
|---|---|---|---|---|
| 费用报销单 | 1001 | SYS_EXPENSE_CLAIM | sourceAmount | billNo、totalAmount、netAmount、taxAmount、deductTaxAmount、offsetLoanAmount、payAmount、expenseSubjectCode、fundSubjectCode、partnerType、partnerId、partnerName、deptId、remark。明细在 lines:expenseTypeId、amount、netAmount、taxAmount、subjectCode、projectId |
| 付款单 | 1002 | SYS_PAYMENT | sourceAmount | totalAmount、fundSubjectCode、partnerId、partnerName、remark |
| 收款单 | 1003 | SYS_RECEIPT | sourceAmount | totalAmount、fundSubjectCode、partnerId、partnerName |
| 应收单 | 1004 | SYS_RECEIVABLE | 借 totalAmount,贷收入 netAmount,贷税额 taxAmount | billNo、dueDate、partnerId、partnerName、sourceBizType |
| 应付单 | 1005 | SYS_PAYABLE | 借费用 netAmount,借进项税 taxAmount,贷 totalAmount | billNo、dueDate、partnerId、partnerName |
| 借款单 | 1006 | SYS_LOAN | sourceAmount | fundSubjectCode、employeeId、employeeName、partnerId、partnerName |
| 还款单 | 1007 | SYS_LOAN_REPAY | sourceAmount | fundSubjectCode、loanId、employeeId、partnerName |
| 销项发票 | 1008 | SYS_INVOICE_SALES | 借 totalAmount,贷收入 netAmount,贷税额 taxAmount | billNo、partnerName、bizDate |
| 进项发票 | 1009 | SYS_INVOICE_PURCHASE | 借费用 netAmount,借进项税 taxAmount,贷 totalAmount | billNo、partnerName |
| 资产折旧批次 | 1010 | SYS_ASSET_DEPRECIATION | 借 debitExpenseAmount,贷 creditAccumulatedAmount | depreciationPeriod、billNo、companyName |
| 薪资计提与发放 | 1011 | SYS_HRM_SALARY | totalAmount | period |
| 内部转账 | 1013 | SYS_FUND_TRANSFER | 借 totalAmount(转入科目 inSubjectCode),借 feeAmount,贷 sourceAmount(转出科目 outSubjectCode) | outAccountId、inAccountId、remark |
| 手工日记账 | 1014 | 收入 SYS_MANUAL_JOURNAL_IN(条件 direction == 1),支出 SYS_MANUAL_JOURNAL_OUT | sourceAmount | direction(1 收入)、fundSubjectCode、inoutCategoryId、partnerName |
银行流水(1012)、期末结转损益(1090)有来源编码,系统未种缺省模板。新模板的金额、科目表达式按该来源实际入队字段填写,不要套用上表。
科目随业务变化时,用表达式取编码,固定科目只作表达式返回空时的回退:
| 表达式 | 含义 |
|---|---|
expenseSubjectCode | 报销费用类型对应的末级费用科目 |
fundSubjectCode | 资金账户绑定的科目;未绑定时回退库存现金 1001 |
inSubjectCode / outSubjectCode | 内部转账转入、转出账户科目 |
7. 配置示例
费用报销:借费用(科目随费用类型),贷其他应付款。税额为 0 的行不要单列,否则会被跳过并可能借贷不平。
| 方向 | 取科目 | 科目 / 表达式 | 金额 | 摘要 | 条件 | 辅助核算 |
|---|---|---|---|---|---|---|
| 借 | 表达式 | expenseSubjectCode | sourceAmount | '费用报销 ' + sourceNo | 空 | {"DEPT":{"idExpr":"deptId"}} |
| 贷 | 固定科目 | 其他应付款 | sourceAmount | '费用报销挂账 ' + sourceNo | 空 | 空 |
有进项税才生成税额行(条件成立才生成;金额仍须非 0):
| 方向 | 取科目 | 金额 | 条件 |
|---|---|---|---|
| 借 | 固定进项税额 | taxAmount | taxAmount != nil && taxAmount > 0 |
手工日记账拆两张模板,收入模板适用条件:
direction == 1支出模板条件留空,作为同来源类型的兜底。收入模板的优先级数字要小于支出模板,这样收入单据先命中收入模板。
8. 生成失败时先看什么
| 现象 | 常见原因 |
|---|---|
| 保存提示表达式不合法 | 引号不成对、函数名写错、使用了循环或 new |
| 未匹配到凭证模板 | 该来源没有启用模板,或条件全为假且没有空条件和兜底模板 |
| 分录被全部跳过 | 金额都是 0 或条件都不成立,剩下不足两行 |
| 借贷不平 | 借方金额表达式与贷方不是同一口径,或中间有一行因金额为 0 被跳过 |
| 科目不存在 | 表达式返回的编码不是本账套末级科目编码,或带了空格以外的杂质 |
| 期望数字 / 期望布尔 | 金额栏写成了文字,或条件栏写成了金额字段本身 |
| 执行失败 | 对 nil 做了加减或 string.substring 下标越界 |
队列记录上的失败原因会带引擎原文,对照本说明里的函数名和字段名修改模板后重新生成即可。