Odoo 15 使用 down payment 时如何传递新字段值?
结论
普通销售订单行开票时,重写 sale.order.line._prepare_invoice_line() 就能传递新增字段。但使用 Down Payment 向导创建首张预付款发票时,Odoo 15 走的是 sale.advance.payment.inv 的专用流程,所以还要重写向导中的 _prepare_so_line() 和 _prepare_invoice_values()。
预付款通常对应整张销售订单,并不对应某一条原始订单行。如果各订单行的字段值不同,需要先确定预付款应采用哪个值,不能按一一对应关系处理。
定义目标字段
假设要传递的字段是 project_code,需要分别在销售订单行和发票行中定义:
from odoo import fields, models
class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
project_code = fields.Char(string="Project Code")
class AccountMoveLine(models.Model):
_inherit = "account.move.line"
project_code = fields.Char(string="Project Code")
两个字段可以使用不同名称,不过名称相同通常更方便维护。
处理普通发票行
正常为销售订单开票时,Odoo 通过 _prepare_invoice_line() 准备发票行数据:
from odoo import models
class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
def _prepare_invoice_line(self, **optional_values):
values = super()._prepare_invoice_line(**optional_values)
values["project_code"] = self.project_code
return values
这样可以处理普通订单行开票,也能处理最终发票中由普通销售订单行生成的明细。
处理 Down Payment 发票
Odoo 15 创建预付款时,会先生成一条标记为 is_downpayment 的销售订单行,再由 sale.advance.payment.inv 创建预付款发票。这个流程通常不只依赖 _prepare_invoice_line()。
先把字段值写入新生成的预付款订单行:
from odoo import models
class SaleAdvancePaymentInv(models.TransientModel):
_inherit = "sale.advance.payment.inv"
def _prepare_so_line(
self, order, analytic_tag_ids, tax_ids, amount
):
values = super()._prepare_so_line(
order, analytic_tag_ids, tax_ids, amount
)
source_lines = order.order_line.filtered(
lambda line: not line.is_downpayment
)
project_codes = list(dict.fromkeys(
code for code in source_lines.mapped("project_code") if code
))
# 只有所有有效来源都指向同一个值时才自动传递。
values["project_code"] = (
project_codes[0] if len(project_codes) == 1 else False
)
return values
然后把预付款销售订单行上的字段值写入预付款发票行:
from odoo import models
class SaleAdvancePaymentInv(models.TransientModel):
_inherit = "sale.advance.payment.inv"
def _prepare_invoice_values(self, order, name, amount, so_line):
values = super()._prepare_invoice_values(
order, name, amount, so_line
)
for command in values.get("invoice_line_ids", []):
if command[0] == 0:
command[2]["project_code"] = so_line.project_code
return values
这两个方法也可以写在同一个 SaleAdvancePaymentInv 扩展类中。
如果字段本质上属于整张订单
如果 project_code、项目编号或合同号等字段在整张销售订单中都相同,更合适的设计是把字段放在 sale.order 上,而不是根据多个 sale.order.line 推断:
class SaleOrder(models.Model):
_inherit = "sale.order"
project_code = fields.Char(string="Project Code")
此时,_prepare_so_line() 可以直接读取订单字段:
def _prepare_so_line(self, order, analytic_tag_ids, tax_ids, amount):
values = super()._prepare_so_line(
order, analytic_tag_ids, tax_ids, amount
)
values["project_code"] = order.project_code
return values
这样可以避免同一张订单出现多个不同字段值,导致无法确定预付款发票行该使用哪个值。
注意事项
- 必须调用
super(),否则可能丢失税、分析账户、销售订单行关联等标准数据。 - 不建议复制并改写整个预付款创建方法。优先重写准备数据的方法,更容易兼容后续补丁和其他模块。
invoice_line_ids使用 ORM command,其中command[0] == 0表示创建新的发票行。- 如果同一订单的多个普通订单行包含不同字段值,需要制定明确的处理策略,例如留空、报错、使用订单级字段,或按照业务规则选择某一行。
- 不能只修改数据库字段定义。新增 Python 字段后,还需要升级自定义模块。
- 上述方法签名适用于标准 Odoo 15 的典型实现。如果系统中安装了会计本地化模块或其他销售扩展,应以当前环境里
sale.advance.payment.inv的实际方法签名为准。
备注:内容仅供参考。
