——把财务部的“祖传公式”,一次性收编
引子:每个财务部都有几条“祖传公式”
最典型的一幕:有人在部门群里问——“谁有大写金额的公式?”
三分钟内收到五个版本,每个都信誓旦旦“我们一直在用”。没人知道出处,没人测过边界,更没人敢改——出了错只能整段删掉,去群里再要一次。
祖传公式三宗罪:
- 无出处:谁写的、对不对,只有天知道;
- 会变异:每抄一次被顺手改一点,一年后演化出五种方言;
- 无人敢动:没有修订记录这个概念,出问题只能推倒重抄。
上篇我们封装了部门第一条准则(账龄分段 ),走完了从条文到治理的全流程。但一个部门只有一条准则是不够的——立法要成体系。
今天交付十条,全部附完整代码。选取标准只有一条:高频、易错、全员共用的“重灾区”。
版本门槛一句话:需要 Microsoft 365(空单元格输入 =LAM 能补全 LAMBDA 即可),老版本的降级方案见上篇第六步。
入编门槛:什么才配进函数库
**并非什么都值得封装。入编标准,比写代码更重要。**四问:
- 高频吗?——每月都干、天天都干的活,才配占用立法资源。一年一次的披露口径,手工做加底稿复核足矣。
- 易错吗?——有边界、有默认值、有单位坑。越是“人人都会”的活,越容易各干各的。
- 共用吗?——一人用叫快捷方式,全组用才叫准则。
- 稳定吗?——规则本身不常变。天天变的参数(税率、比例),放参数表里查,别铸进函数——上篇说过的“修政策,不必发新准则”。
**反例警示:复杂不是入选理由。**一户一议的特殊计算(投资分成、对赌条款),口径不稳定、使用面窄,封进去等于每月修法。它们是职业判断,不是准则。
总则:十条函数,五条公约
以下代码全部整段复制进:公式 → 名称管理器 → 新建 → 引用位置(以 = 开头),范围选“工作簿”。五条总则对所有函数生效:
- 中文函数名 + 中文参数名——LET 让公式自带注释,参数名就是说明书;
- 异常一律亮牌——返回“税率有误”“日期倒挂”这类文字,禁止 IFERROR 吞掉;
- 先校验参数,再进行计算——每个函数自带一道“凭证审核”;
- 版本 V1.0 起步,修订走函数字典,禁止私改;
- 数组友好——多数函数支持整列传入、自动溢出(365 特性)。
第一组:进料检验——数据入口三道闸
第一篇说过:SUM 算出 0,多半不是函数坏了,是原料坏了。函数库的第一优先级不在计算,在入口。
① 清洗金额
痛点:ERP、网银导出的金额是文本,带千分位逗号、货币符号、看不见的空格。每个人都遇过,每个人处理手法还不一样——有人分列,有人乘 1,有人选择性粘贴。
条文:转得动就转;转不动,亮牌“非数值”,不许静默。
=LAMBDA(原值,
IF(ISNUMBER(原值), 原值,
LET(
去符号, SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(
原值, ",", ""), ",", ""), "¥", ""), "¥", ""),
去空格, TRIM(去符号),
IFERROR(VALUE(去空格), "非数值")
))
)
=清洗金额("1,234.56") → 1234.56
=清洗金额("待补") → 非数值
=清洗金额(A2:A5000) → 整列溢出清洗
坑位:空单元格传进来会变 0(Excel 的默认行为)——它管“脏”,不管“缺”,缺数在源头查。遇到全角空格,加一段 SUBSTITUTE(原值, UNICHAR(12288), "")。
② 拆科目
痛点:总账导出的科目编码是 2202.01.05 一整串,做分析要拆层级。每次“分列”或手数 LEFT/MID 位数,数错是常事。
条文:层级填 1、2、3…取对应级;填 0 取末级(最常用)。分隔符定为"."。
=LAMBDA(科目编码, 层级,
LET(
片段, TEXTSPLIT(科目编码, "."),
取第, IF(层级 = 0, COLUMNS(片段), 层级),
IFERROR(CHOOSECOLS(片段, 取第), "层级有误")
)
)
=拆科目("2202.01.05", 1) → "2202"
=拆科目("2202.01.05", 0) → "05"(末级)
=拆科目(A2:A5000, 0) → 整列末级
设计点:你的 ERP 用"-“分隔?复制一条、改一个字符、另起函数名——但要在函数字典里登记。科目体系不同就是两套科目表,各立各的条文。
③ 期间末日
痛点:“期间”这东西在各家表里有三种长相:202606(数字)、"2026-06"(文本)、一个日期。EOMONTH 只认第三种。每次先手工统一格式,一个月统一三十次。
条文:三种写法都收;认不出的亮牌;返回该期间最后一天。
=LAMBDA(期间,
LET(
末日, IF(
ISNUMBER(期间),
IF(期间 > 100000,
EOMONTH(DATE(INT(期间 / 100), MOD(期间, 100), 1), 0),
EOMONTH(期间, 0)),
IFERROR(
EOMONTH(DATE(VALUE(TEXTBEFORE(期间, "-")), VALUE(TEXTAFTER(期间, "-")), 1), 0),
"期间格式不识别")),
末日)
)
=期间末日(202606) → 2026-06-30
=期间末日("2026-6") → 2026-06-30
=期间末日(B2) → B2 所在月的月末
设计点:那个 100000 是什么?——大于十万的数字只能是“年月码”,小于的只能是日期序列值(现在约四万多),两类值域未来几百年不相撞,一行代码省掉三分之二的格式判断。先想清楚值域,再写判断逻辑——跟抽凭先抽边界样本,是同一种思维。(结果记得设成日期格式。)
第二组:加工车间——四台核心设备
车间是产出价值的地方,也是最容易“差一分钱”的地方。
④ 价税分离
痛点:公式简单,坑不少:税率有人填 13 有人填 0.13;价和税各自四舍五入,分录差一分钱借贷不平,月末翻半天凭证找差异。
条文:税率 13 和 0.13 都收;不含税先算先舍入;税额 = 含税额 − 不含税(已舍入),用倒挤,不用独立舍入。这一条是灵魂:价 + 税 = 含税,永远分毫不差。
=LAMBDA(含税额, 税率, 需求,
LET(
率, IF(税率 > 1, 税率 / 100, 税率),
不含税, ROUND(含税额 / (1 + 率), 2),
税额, ROUND(含税额 - 不含税, 2),
IF(率 < 0, "税率有误",
IF(需求 = "价", 不含税,
IF(需求 = "税", 税额, "需求填 价 或 税")))
)
)
=价税分离(1130, 13, "价") → 1000.00
=价税分离(1130, 13, "税") → 130.00
=价税分离(1000.05, 6, "税") → 56.61(倒挤,勾稽必平)
设计点:为什么倒挤——借贷记账法天然要求平衡,函数也要有记账思维:**先定锚(价),再倒挤(税)。**各自独立舍入是两个口径,倒挤是一个口径。
⑤ 分摊
痛点:费用分摊、奖金分配、成本结转,各项 ROUND 之后合计对不上,差一分到几分。手工调尾差:调谁头上、调几笔、怎么留痕,各有各的土办法。分差异不罕见,罕见的是处理方法统一。
条文:各项独立舍入到分;尾差全部给权重最大的一项;返回数组合计恒等于分摊总额。权重一律纵向排列、非负。
=LAMBDA(总金额, 权重,
IF(SUM(权重) <= 0, "权重有误",
LET(
权重和, SUM(权重),
理论额, 权重 / 权重和 * 总金额,
已舍入, ROUND(理论额, 2),
尾差, ROUND(总金额 - SUM(已舍入), 2),
位置, MATCH(MAX(理论额), 理论额, 0),
已舍入 + (SEQUENCE(ROWS(理论额)) = 位置) * 尾差
))
)
权重放 D2:D4(1、1、1):
=分摊(1000, D2:D4) → 333.34 / 333.33 / 333.33
=SUM(溢出区域) → 1000.00(永远精确)
设计点:尾差为什么给最大项——占比最小、最不显眼;给最后一项是图省事,给最大项是职业判断。你的部门选哪个都行,但必须选一个写进条文——这就是封装的意义。坑位:横向权重会静默失效(ROWS 数不出来),条文里“纵向”两个字就是防这个的。
⑥ 月折旧
痛点:规则人人知道——当月入账次月计提、直线法、提满停提。但几千行的固定资产表每行手写判断,漏提、多提、提满不停,年审时被一条条挑出来。
条文:①当月入账,次月起提;②月折旧 =(原值 − 残值)÷ 年限月数,舍入到分;③提满即停;④末月找齐——最后一月提“原值 − 残值 − 已提”,累计折旧精确归零;⑤残值是金额,不是比例。
=LAMBDA(原值, 残值, 启用日, 年限月, 期末日,
LET(
计提月, EOMONTH(期末日, 0),
首提月, EOMONTH(启用日, 1),
末提月, EOMONTH(启用日, 年限月),
常规月额, ROUND((原值 - 残值) / 年限月, 2),
末月找齐, ROUND(原值 - 残值 - 常规月额 * (年限月 - 1), 2),
月额, IF(计提月 = 末提月, 末月找齐, 常规月额),
IF(OR(年限月 <= 0, 原值 < 残值), "参数有误",
IF(AND(计提月 >= 首提月, 计提月 <= 末提月), 月额, 0))
)
)
边界自测(原值 10100,残值 100,启用 2023-06-10,36 个月):
| 期末日 | 结果 | 考点 |
|---|---|---|
| 2023-06-30 | 0 | 当月入账,次月才提 |
| 2026-05-31 | 277.78 | 常规月 |
| 2026-06-30 | 277.70 | 末月找齐(不是 277.78) |
| 2026-09-30 | 0 | 提满停提 |
设计点:首月、末月、提满后——恰好是折旧错报最爱藏的三条接缝。这张表本身就是上篇第五步“边界抽样”的迷你版,每个函数发文前都该有自己的一张。
⑦ 利息
痛点:借款、资金拆借计息:天数算法(算不算头尾)、年化基准(360 还是 365)、利率单位(3.65 还是 0.0365)——三个坑叠着,谁跟谁都不一样,对账全靠吵架。
条文:①天数 = 止日 − 起日,算头不算尾(信贷通行惯例,写死);②基准三选:“360” / “365” / “自动”(止日在闰年按 366);③利率两种写法都收;④日期倒挂亮牌。
=LAMBDA(本金, 年利率, 起日, 止日, 基准,
LET(
率, IF(年利率 > 1, 年利率 / 100, 年利率),
天数, 止日 - 起日,
年基数, SWITCH(基准,
"360", 360,
"365", 365,
"自动", IF(OR(MOD(YEAR(止日), 400) = 0,
AND(MOD(YEAR(止日), 4) = 0, MOD(YEAR(止日), 100) <> 0)),
366, 365),
"基准不识别"),
IF(天数 < 0, "日期倒挂",
IF(ISNUMBER(年基数), ROUND(本金 * 率 * 天数 / 年基数, 2), 年基数))
)
)
=利息(10000000, 3.65, DATE(2026,1,1), DATE(2026,4,1), "360")
→ 91,250.00(90 天,360 基准)
设计点:“自动”是简化处理(按止日所在年判断闰年)。精算口径的 ACT/ACT 要按年度切分天数——真有这种合同,别封装,逐笔算、留底稿。知道函数的边界,和会用函数一样重要——准则也有适用范围。
第三组:成品包装——报表出口三道工序
报表是给外人看的,出口处的口径漂移最丢人——想想第一篇里华东华南的账龄打架。
⑧ 重分类
痛点:往来科目期末按明细方向重列:应收账款贷方余额转预收/合同负债,应付账款借方余额转预付。规则一句话,执行一屋子 IF,每张表一个写法。
条文:只做一件事——按余额方向取数:方向填“借”,取借方余额(贷方返回 0);填“贷”,取贷方余额(借方返回 0)。科目映射不写进函数,用对照表查。
=LAMBDA(余额, 方向,
IF(方向 = "借", MAX(余额, 0),
IF(方向 = "贷", MAX(-余额, 0),
"方向填 借 或 贷"))
)
客户余额在 B 列:
=重分类(B2:B500, "借") → 借方余额列(应收)
=重分类(B2:B500, "贷") → 贷方余额列(预收)
设计点:十行不到的代码,管住全公司的列报口径。入选函数库的从来不是复杂度,是出错成本。这可能是十条里最短的一条,也可能是报表上最值钱的一条。
⑨ 变动分析
痛点:管理报表的本期对比上期:上期为零或为负时,要么 #DIV/0! 满屏,要么算出一个“看起来很正常、其实很荒谬”的负增长率。前者是报错,后者是披着数字外衣的静默失败——更危险。
条文:上期 > 0 才算率;否则只报金额差,并标注“率不可比”。
=LAMBDA(上期, 本期,
LET(
差额, 本期 - 上期,
差额文, TEXT(差额, "+#,##0;-#,##0;0"),
IF(上期 > 0,
差额文 & "(" & TEXT(差额 / 上期, "+0.0%;-0.0%") & ")",
差额文 & "(上期非正数,率不可比)")
)
)
=变动分析(100, 115) → +15(+15.0%)
=变动分析(0, 500) → +500(上期非正数,率不可比)
设计点:为什么不用 IFERROR 一把梭——因为负基数不是“错误”,是“不可比”。错误该报错,不可比该声明;一把梭会把两者一起埋掉。会计语言里,这叫披露,不叫容错。
⑩ 大写金额
痛点:回到开头,群里那句“谁有大写金额的公式”。那条祖传公式三百字,没人测过边界——而它最大的坑,藏在最想不到的地方。
先做个实验:在单元格里输入 =INT(0.29*100)。你以为结果是 29?**实际是 28。**浮点误差:0.29 在机器里是 0.28999…,INT 一刀切下去,少一分钱。大写公式里全是 INT 和 MOD——一分钱的误差,支票大写直接错一个字。
条文:①先把金额 ×100 整体 ROUND 成“总分”,再拆元角分(浮点免疫);②角分规则四条——角分全零→“整”;分零角有→“X角整”;角零分有→“零X分”;都有→“X角Y分”;③负数、非数值、超万亿亮牌。
=LAMBDA(金额,
IF(NOT(ISNUMBER(金额)), "非数值",
IF(金额 < 0, "负数请人工处理",
IF(金额 >= 10^12, "超出封装范围",
LET(
总分, ROUND(金额 * 100, 0),
元, INT(总分 / 100),
角, MOD(INT(总分 / 10), 10),
分, MOD(总分, 10),
元文, TEXT(元, "[DBNum2]"),
角文, TEXT(角, "[DBNum2]0"),
分文, TEXT(分, "[DBNum2]0"),
尾文, IF(AND(角 = 0, 分 = 0), "整",
IF(角 = 0, "零" & 分文 & "分",
IF(分 = 0, 角文 & "角整",
角文 & "角" & 分文 & "分"))),
元文 & "元" & 尾文
))))
)
=大写金额(1234.56) → 壹仟贰佰叁拾肆元伍角陆分
=大写金额(100.05) → 壹佰元零伍分
=大写金额(100) → 壹佰元整
=大写金额(0.29) → 零元贰角玖分(浮点免疫的受益者)
设计点:一行 ROUND,防住一类错。网上的大写公式几十个版本,差别全在角分补零的写法上——现在你们部门只有一个版本,条文就写在那四行 IF 里。
加餐:月末自动锁定——上篇预告兑现
这一条故意不做成函数,它是三道闸拼出来的内控:
- 参数表放一个格子:锁定日(已结账截止,如 2026-05-31),只有主管能改;
- 录入区“日期”列设数据有效性 → 自定义:
=B2>参数!$B$1——想往已结账的 5 月补一笔?弹窗直接拒绝; - 条件格式:
=$B2<=参数!$B$1整行置灰——已锁区间一眼可辨。
结账 = 主管把锁定日往前推一格。要硬锁,再叠加工作表保护。
设计点:锁定是权限与流程问题,函数管计算、内控管权限——别让一个工具干两种活。知道什么不该封装,和知道什么该封装,一样重要。
收官:函数字典(可直接贴进模板)
这张表就是模板里“函数字典”页的种子,补上制定人和日期即可“发文”:
| # | 函数 | 定位 | 关键条文 | 版本 |
|---|---|---|---|---|
| 1 | 清洗金额 | 文本数字→真数字 | 转不动亮牌;空单元格会变 0 | V1.0 |
| 2 | 拆科目 | 科目编码取任意层级 | 层级 0 = 末级;分隔符”." | V1.0 |
| 3 | 期间末日 | 期间→月末日期 | 三种写法;认不出亮牌 | V1.0 |
| 4 | 价税分离 | 含税额拆价、税 | 倒挤保平,价 + 税 = 含税 | V1.0 |
| 5 | 分摊 | 按权重分摊不差分 | 尾差进最大项;权重纵向 | V1.0 |
| 6 | 月折旧 | 直线法当月折旧 | 次月起提;末月找齐 | V1.0 |
| 7 | 利息 | 计息 | 算头不算尾;360/365/自动 | V1.0 |
| 8 | 重分类 | 往来余额拆借贷列 | 科目映射查表,不进函数 | V1.0 |
| 9 | 变动分析 | 差额 + 变动率 | 上期 ≤ 0,率不可比 | V1.0 |
| 10 | 大写金额 | 人民币大写 | 先成总分再拆;角分四规则 | V1.0 |
治理三动作(细节见上篇):贴字典、做边界抽样、修订留痕。
结语:不上资产负债表的资产
回到开头那个微信群。下次再有人问“谁有大写金额的公式”,你的回复只需要一行:
模板里有。函数名:大写金额。条文见函数字典。
五个祖传公式的时代,到你们部门这一代为止。
会计的世界里,资产大多在折旧——设备越用越旧,技术越用越淘汰。函数库是难得的反向资产:每复用一次,口径稳一分;每修订一次,组织聪明一分。它不上资产负债表,但它让资产负债表更可信。顺手,它还解决了财务部的另一个老大难——从前口径长在人脑子里,人走口径散;现在口径长在模板里,人走,函数还在。离职交接清单上终于可以有一行:“函数字典在模板第三个工作表。”
十段代码加起来不到一百行。而把它们写出来花的功夫,远不如把那十条“条文”想清楚花得多——上篇说过,最难的一步不在 Excel 里,这一篇,十条又验证了一遍。
你封装的从来不是公式,是部门的执行纪律。