银行对账单导入最怕的不是“格式不支持”,而是 导进去的东西看着像对,其实口径已经错了。企业版在 account_bank_statement_import 里下的功夫,主要就在这几个地方:找对 journal、核对币种、补足交易辅助信息,以及保证同一条交易不会重复落账。
主要参考:
enterprise/account_bank_statement_import/models/account_journal.pyenterprise/account_bank_statement_import/models/account_bank_statement.pyenterprise/account_bank_statement_import/wizard/setup_wizards.py
一、入口是附件,真正的核心在 journal 侧的导入链
create_document_from_attachment() 会先判断当前是不是 bank / credit / cash journal,再把附件送去 _import_bank_statement()。这意味着企业版从设计上就把导入看成 journal 语境下的会计动作,而不是一个独立的“文件上传器”。
为什么这很重要?因为同一份文件只有落到正确 journal,后续对账和会计口径才成立。脱离 journal 谈导入,几乎一定会在币种、账户或银行账号上出问题。
二、系统会先校验能不能解释这份文件,而不是急着建 statement
_parse_bank_statement_file() 采用链式责任模式,让不同格式支持模块接力解析。解析完后,_check_parsed_data() 会判断有没有 statement、有没有 transaction。如果文件能读但没有有效交易,系统不会假装成功。
新手常犯的错误是:把“附件上传成功”当成“导入成功”。源码非常明确,真正的成功标准是:文件被识别、账户可匹配、至少有有效交易能够落到 statement line。
三、journal 与币种匹配是整个流程最关键的防呆点
_find_additional_data() 会根据账号与币种找到 journal,并验证 statement currency 和 journal currency 是否一致。它甚至考虑了银行只返回部分账号的情况,以及某些法国银行账号的特殊截断规则。
这背后的设计原则非常企业化:宁可现在卡住,也不要把错误币种或错误账户的流水导进来。因为一旦进了对账流程,后续修复成本远高于前置拦截。
四、去重不是靠“文件名不同”,而是靠 transaction 级唯一键
_complete_bank_statement_vals() 会把导入方提供的 unique_import_id 加上银行账号和 journal id,拼成新的唯一标识。随后 _create_bank_statements() 在创建 statement line 前,会用这个唯一值检查历史是否已经导入。
这也是为什么企业版能处理“同一文件重复上传”或“银行重复推送部分交易”的场景。系统关心的是交易身份,不是附件名字。文件名改了不重要,只要交易唯一键相同,就会被过滤掉。
五、成功后的目标不是列表页,而是 reconciliation widget
导入动作最后跳的是 _action_open_bank_reconciliation_widget(),而不是简单打开 statement。企业版其实在表达一个很务实的观点:导入不是终点,对账才是终点。
如果部分附件失败而部分成功,系统还会在可跳转到对账界面的前提下抛 RedirectWarning。这类设计非常适合真实财务场景——不要因为一份文件有问题,就把其他已成功导入的流水也拖住。
六、新手误区与实战注意事项
- 以为“能上传 xlsx/csv”就等于导入流程成熟。真正成熟的是校验与回滚策略。
- 以为去重靠文件级控制。企业版其实是交易级去重。
- 以为导入后就完成。事实上后续 reconciliation 才决定账务是否闭环。
实战建议:
- 银行账号主数据、journal 默认账户、币种先检查完整。
- 对接外部格式时,尽量稳定输出
unique_import_id,不要只给流水摘要。 - 财务培训时要强调:重复上传不可怕,没有唯一键才可怕。
七、结论
account_bank_statement_import 的价值,不在于“让财务少点几下鼠标”,而在于它把导入这件事放在正确的会计语境里:先解释文件、再匹配 journal、再去重、最后进入对账。这样导入出来的流水,才值得被继续用来做会计处理。
DISCUSSION
评论区