去年五月到六月,我们在新发地联玉商贸的财务部坐了四个下午。桌上摊着三个月的原始单据,A4 的、手写的、复写纸褪了色的,还有几张是微信截图打印出来的。要做的东西听起来不复杂:月底给每个客户出一张对账单。
难就难在这张单。
先说他们的生意是怎么跑的
联玉做生鲜批发,下游是几十家二级批发商和一批食堂客户。一天出货几百单,一单里常有七八个品类,斤重、单价、件数都不同。司机早上装车,随车带一式两联的送货单,客户签收后回来一联,录入员下午录进系统。
听着挺标准。真翻单据才发现几件事:
- 一张送货单不一定一次结清。有的客户攒一周结一次,于是一张送货单对应两三张收款凭证,我们后来管这叫一单多票。
- 退货不删原单。当天送去的菜有烂的,第二天拉回来,开一张红字单冲掉,原单还留着。
- 抹零是常态。金额算出来 3278.6,实收 3270,那 8 块 6 账上没人记,但财务心里有本账。
- 有的客户按斤,有的按件,同一品类两种单位,对账时要折算。
原来这些全靠一位做了十几年的会计用 Excel 手工拢。她有自己一套颜色标记:黄色是未结,绿色是结清,红色是退货。我们接手时,那张表有 2000 多行,17 个字段。
第一版为什么不对
第一版我们按「订单」建模:一张订单一条记录,付款挂到订单上,一个 paid_amount 字段。跑出来的对账单,客户看了一眼就说不对,少了三笔。
少的是那些一单多票的。一张单收了两次款,第二次把第一次覆盖了。
第二版改成付款单独一张表,跟订单一对多。这回数量对了,但金额对不上,差在退货和抹零。红字单我们当时当成负金额订单处理,可它冲的是三个月前那张单,一跨月,本月对账单上就凭空多一笔负数。客户说这单我这个月没退过东西。
第三版才想明白:对账单不是「订单汇总」,是「某个客户在某段时间里,应收、已收、调整三件事的流水账」。跟订单表不是一回事,得单独算。
最后拆成的 3 张表
- bill_items:对账明细行。每个客户每月每个品类一行,存期初、本期应收、本期退货、抹零、期末应收,5 个金额字段。
- settlements:收款记录。一单多票就多条,每条带凭证号、日期、金额,跟订单是软关联,不强制。
- adjustments:调整单。退货冲红、抹零、坏账都走这里,带一个原单号和原因,跨月也能挂回去。
对账单由定时任务在每月 1 号凌晨 2 点生成,锁表跑,跑完写一张 bill_header 存汇总。之前踩过定时任务跑两遍的坑,flock 和 run_id 都是现成抄过来的。
生成逻辑不复杂,但有三条规矩是那四个下午对着纸单定下来的:
退货只冲原单所在月份的应收,不进当月流水;抹零单独一列,不悄悄摊进单价;一单多票按凭证日期归到各自月份,不按订单日期。
第三条争得最久。会计的意思是按订单日期算,业务那边说按钱到账算。最后按凭证日期,因为银行流水是按那个走的,对得上。
那 17 个字段
我们把她 Excel 的 17 列打印出来,一条条问「这列月底对账用不用」。跟签证表那次一样,右边挪走了 6 列:司机名、车牌、装车时间、客户联系人、备注、录入员。不是没用,是不该出现在对账单上。留 11 列,其中 3 列由系统算,人只填 8 列。
她盯着那张打印纸看了一会儿,说:车牌那列你别删,我查账有时候要靠它找单。于是车牌留在了明细行里,只是不参与金额计算。这条我们没写进需求文档,写在了一张便利贴上,贴在显示器边。
上线那天
6 月 5 号,第一次自动生成。我们把系统出的单和她手工做的那张并排放在桌上,一条条核,核了大概两小时。差了两处:一处是某笔抹零她记在 5 月、系统按凭证记在 6 月;另一处是一张 4 月的红字单,系统冲回了 4 月,她习惯挂在 5 月。都是口径问题,不是算错。改了口径,重跑,对上了。
她没说什么,把系统那张单打印出来,拿笔在右上角签了个字,夹进文件夹。那个文件夹里还留着之前几个月的黄色绿色红色。
后来
月底对账从两天缩到半天,这是能看见的。但真正让我们记住的不是这个,是那句「车牌那列你别删」。系统里每一行金额都能算,唯有那一列,是给人留的。
后来这套 ERP 加过门店、加过盘点、加过 Excel 导出,表从 3 张长到十几张。每次动对账这块,我们还是会先把那三张纸单摊开看看。
问我们这套最难的地方在哪,我一般不说数据库,说那四个下午。

