Skip to content

会计凭证配置使用函数说明 ​

适用页面:财务管理 → 凭证模板
引擎:Aviator 5.4(沙箱,只开放 if / return 与内置函数)
对照代码:FinanceExpressionServiceImpl、FinanceVoucherTemplateServiceImpl
日期:2026-10-17

凭证模板把业务单据入队时的字段,翻译成借贷分录。金额、摘要、科目编码、适用条件、分录条件、辅助核算取值,都写同一种表达式。变量名直接写字段名,例如 sourceAmount、taxAmount,不要写 ${...},也不要写 payload.amount。

一条模板分录生成凭证上的一行。明细数组 lines 不会自动按行展开。


1. 表达式写在哪 ​

位置必填结果类型为空时
模板适用条件否布尔当作兜底模板,不参与「第一个命中」
凭证摘要否文本用第一条分录的摘要
分录金额是数字保存失败
分录摘要否文本用来源单号,再没有则用模板名称
分录条件否布尔本行生成
科目编码取科目选「表达式」时必填文本,须是本账套已有科目编码空则回退该行上保存的固定科目
辅助核算 idExpr / nameExpr否编号 / 名称按下文约定字段回填

取科目两种方式:

  • 固定科目:直接选末级科目。
  • 表达式:返回科目编码字符串,例如 fundSubjectCode、'1002'。编码在本账套找不到会生成失败。

保存时只做语法校验,不拿真实单据试算。同一来源类型按「优先级」数字从小到大匹配:先找第一个条件为真的模板;带条件的都没命中时,用条件为空的那张;仍没有,再用勾了「兜底模板」的那张。

勾选自动过账时,系统会同时打开自动审核。


2. 书写约定 ​

文字用单引号。字符串拼接用 +。

text
'费用报销 ' + sourceNo
'计提' + depreciationPeriod + '折旧'

比较与逻辑:

text
taxAmount > 0
partnerType == 2 && netAmount > 0
direction == 1

partnerType:1 客户,2 供应商,3 员工,4 其他。

字段缺失时该变量是 nil,不会报「变量不存在」。nil 不能直接参与加减和拼接,先判断再运算:

text
taxAmount == nil ? 0 : taxAmount
partnerName == nil ? sourceNo : ('应付 ' + partnerName)

分支用三元,或用 if / return(花括号不能省):

text
taxAmount > 0 ? '22210101' : '2202'
text
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)首次出现位置,没有则 -1string.indexOf(remark, '-')
string.replace_first(s, regex, replacement)替换第一处string.replace_first(remark, ' ', '')
string.replace_all(s, regex, replacement)全部替换string.replace_all(partnerName, '公司', '')

摘要截到 50 个字(字段可能为空、也可能不足 50 字):

text
remark == nil ? sourceNo : (string.length(remark) > 50 ? string.substring(remark, 0, 50) : remark)

把单号和备注拼起来再截取:

text
letText = (sourceNo == nil ? '' : sourceNo) + (remark == nil ? '' : remark)

沙箱未开放 let 赋值。拼完再截,写成一条表达式:

text
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)

价税拆分直接相减即可,尾差由金额列的两位四舍五入消化:

text
totalAmount - netAmount

报销实付(价税合计减冲借款;缺字段按 0):

text
(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')字符串转日期

从业务日期取年月:

text
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 行金额时先确认有明细,否则取空会执行失败:

text
count(lines) > 0 ? seq.get(lines, 0).netAmount : 0

5. 辅助核算表达式 ​

科目绑定了辅助核算时才取值。模板里多写的维度会被忽略。写法是 JSON,键为维度编码:

json
{"DEPT":{"idExpr":"deptId","nameExpr":"deptName"}}

也可以只写编号表达式字符串:{"DEPT":"deptId"}。

未写表达式时,按维度从单据字段回填:

维度编码编号字段(按顺序取第一个有值的)名称字段
CUSTOMERpartnerId、customerIdpartnerName、customerName
SUPPLIERpartnerId、supplierIdpartnerName、supplierName
DEPTdeptIddeptName
EMPLOYEEemployeeId、borrowerId、partnerId、userIdemployeeName、borrowerName、partnerName、userName
PROJECTprojectIdprojectName
CONTRACTcontractIdcontractName

费用科目绑了部门、单据又没带 deptId 时,用当前登录人部门,避免费用分录因辅助核算为空而失败。


6. 各来源单据可取的字段 ​

系统缺省模板已经种在账套里(编码 SYS_*)。客户改过的模板不会被再次覆盖。下表是入队时实际平铺的字段,金额表达式优先用「缺省金额列」那一列,与现有模板一致。

来源编码缺省模板缺省金额还可使用的字段
费用报销单1001SYS_EXPENSE_CLAIMsourceAmountbillNo、totalAmount、netAmount、taxAmount、deductTaxAmount、offsetLoanAmount、payAmount、expenseSubjectCode、fundSubjectCode、partnerType、partnerId、partnerName、deptId、remark。明细在 lines:expenseTypeId、amount、netAmount、taxAmount、subjectCode、projectId
付款单1002SYS_PAYMENTsourceAmounttotalAmount、fundSubjectCode、partnerId、partnerName、remark
收款单1003SYS_RECEIPTsourceAmounttotalAmount、fundSubjectCode、partnerId、partnerName
应收单1004SYS_RECEIVABLE借 totalAmount,贷收入 netAmount,贷税额 taxAmountbillNo、dueDate、partnerId、partnerName、sourceBizType
应付单1005SYS_PAYABLE借费用 netAmount,借进项税 taxAmount,贷 totalAmountbillNo、dueDate、partnerId、partnerName
借款单1006SYS_LOANsourceAmountfundSubjectCode、employeeId、employeeName、partnerId、partnerName
还款单1007SYS_LOAN_REPAYsourceAmountfundSubjectCode、loanId、employeeId、partnerName
销项发票1008SYS_INVOICE_SALES借 totalAmount,贷收入 netAmount,贷税额 taxAmountbillNo、partnerName、bizDate
进项发票1009SYS_INVOICE_PURCHASE借费用 netAmount,借进项税 taxAmount,贷 totalAmountbillNo、partnerName
资产折旧批次1010SYS_ASSET_DEPRECIATION借 debitExpenseAmount,贷 creditAccumulatedAmountdepreciationPeriod、billNo、companyName
薪资计提与发放1011SYS_HRM_SALARYtotalAmountperiod
内部转账1013SYS_FUND_TRANSFER借 totalAmount(转入科目 inSubjectCode),借 feeAmount,贷 sourceAmount(转出科目 outSubjectCode)outAccountId、inAccountId、remark
手工日记账1014收入 SYS_MANUAL_JOURNAL_IN(条件 direction == 1),支出 SYS_MANUAL_JOURNAL_OUTsourceAmountdirection(1 收入)、fundSubjectCode、inoutCategoryId、partnerName

银行流水(1012)、期末结转损益(1090)有来源编码,系统未种缺省模板。新模板的金额、科目表达式按该来源实际入队字段填写,不要套用上表。

科目随业务变化时,用表达式取编码,固定科目只作表达式返回空时的回退:

表达式含义
expenseSubjectCode报销费用类型对应的末级费用科目
fundSubjectCode资金账户绑定的科目;未绑定时回退库存现金 1001
inSubjectCode / outSubjectCode内部转账转入、转出账户科目

7. 配置示例 ​

费用报销:借费用(科目随费用类型),贷其他应付款。税额为 0 的行不要单列,否则会被跳过并可能借贷不平。

方向取科目科目 / 表达式金额摘要条件辅助核算
借表达式expenseSubjectCodesourceAmount'费用报销 ' + sourceNo空{"DEPT":{"idExpr":"deptId"}}
贷固定科目其他应付款sourceAmount'费用报销挂账 ' + sourceNo空空

有进项税才生成税额行(条件成立才生成;金额仍须非 0):

方向取科目金额条件
借固定进项税额taxAmounttaxAmount != nil && taxAmount > 0

手工日记账拆两张模板,收入模板适用条件:

text
direction == 1

支出模板条件留空,作为同来源类型的兜底。收入模板的优先级数字要小于支出模板,这样收入单据先命中收入模板。


8. 生成失败时先看什么 ​

现象常见原因
保存提示表达式不合法引号不成对、函数名写错、使用了循环或 new
未匹配到凭证模板该来源没有启用模板,或条件全为假且没有空条件和兜底模板
分录被全部跳过金额都是 0 或条件都不成立,剩下不足两行
借贷不平借方金额表达式与贷方不是同一口径,或中间有一行因金额为 0 被跳过
科目不存在表达式返回的编码不是本账套末级科目编码,或带了空格以外的杂质
期望数字 / 期望布尔金额栏写成了文字,或条件栏写成了金额字段本身
执行失败对 nil 做了加减或 string.substring 下标越界

队列记录上的失败原因会带引擎原文,对照本说明里的函数名和字段名修改模板后重新生成即可。