源代码版本:Odoo社区版V18
源代码位置:odoo\addons\sale\models\sale_order.py
sale_order.py这段代码是 Odoo 销售模块的核心,实现了从报价创建、确认、开票到支付的全流程管理,支持多公司、门户集成、财务对账和营销跟踪等功能。关键逻辑包括:
- 状态机管理:订单状态(
state
)和发票状态(invoice_status
)的自动更新。 - 金额与税收计算:结合价格表、税收规则和支付条款动态计算。
- 合作伙伴与地址管理:自动同步客户信息,支持多地址。
- 支付与发票集成:通过
payment.transaction
和account.move
实现闭环。
下面我们来分节讲解下sale_order.py中的重点代码。
一、代码结构与继承关系
模型定义
class SaleOrder(models.Model):
_name = 'sale.order'
_inherit = [
'portal.mixin', 'product.catalog.mixin', 'mail.thread',
'mail.activity.mixin', 'utm.mixin'
]
_description = "Sales Order"
_order = 'date_order desc, id desc'
_check_company_auto = True
- 继承关系(_inherit):
portal.mixin
:支持门户访问功能(如客户查看订单)。mail.thread
/mail.activity.mixin
:支持消息线程和活动管理(如邮件通知、任务提醒)。product.catalog.mixin
:集成产品目录功能(如从目录添加产品到订单)。utm.mixin
:支持 UTM 跟踪(营销渠道分析)。
二、核心字段解析
状态与流程字段
我们来看看在代码中,定义的核心字段的意义。
state = fields.Selection(
selection=SALE_ORDER_STATE, # ['draft', 'sent', 'sale', 'cancel']
string="Status",
readonly=True,
tracking=3,
default='draft'
)
invoice_status = fields.Selection(
selection=INVOICE_STATUS, # ['upselling', 'invoiced', 'to invoice', 'no']
string="Invoice Status",
compute='_compute_invoice_status',
store=True
)
state
:订单状态,从草稿(draft
)到已确认(sale
)、取消(cancel
)等。invoice_status
:发票状态,根据行项目的开票情况计算,如 “待开票”(to invoice
)、“已完全开票”(invoiced
)。
财务与金额字段
amount_untaxed = fields.Monetary(compute='_compute_amounts')
amount_tax = fields.Monetary(compute='_compute_amounts')
amount_total = fields.Monetary(compute='_compute_amounts')
amount_paid = fields.Float(compute='_compute_amount_paid', compute_sudo=True)
- 通过
_compute_amounts
方法计算订单金额(未税、税费、总额),结合税收规则和支付条款。 amount_paid
汇总关联的支付交易金额,涉及支付模块的集成。
合作伙伴与地址字段
partner_id = fields.Many2one('res.partner', string="Customer", required=True)
partner_invoice_id = fields.Many2one(
'res.partner', string="Invoice Address",
compute='_compute_partner_invoice_id', store=True
)
partner_shipping_id = fields.Many2one(
'res.partner', string="Delivery Address",
compute='_compute_partner_shipping_id', store=True
)
- 自动根据客户信息获取发票和送货地址,支持多公司和地址验证。
三、关键方法解析
计算方法(Compute Methods)
-
_compute_invoice_status
根据订单行的开票状态(invoice_status
)计算整单的发票状态,逻辑包括:- 若存在 “待开票” 行,则订单状态为
to invoice
。 - 若所有行已开票,则状态为
invoiced
。 - 若存在追加销售机会(
upselling
),则状态为upselling
。
- 若存在 “待开票” 行,则订单状态为
-
_compute_amounts
调用account.tax
计算税收和总额,处理支付条款中的提前付款折扣(_add_base_lines_for_early_payment_discount
)。 -
_compute_pricelist_id
根据客户的默认价格表(property_product_pricelist
)自动设置订单价格表,支持多公司场景。
业务逻辑方法
-
action_confirm
确认订单,触发以下操作:- 验证订单行(如产品是否存在、分析账户分配)。
- 发送确认邮件(通过
_send_order_confirmation_mail
)。 - 根据配置自动锁定订单(
action_lock
)。
-
_create_invoices
生成发票,支持按分组(grouped
)或按订单生成,处理预付款(Down Payment)和最终发票(final
)。 -
_get_portal_last_transaction
获取门户用户的最后一次支付交易,用于支付状态跟踪和重定向。
约束与验证方法
@api.constrains('company_id', 'order_line')
def _check_order_line_company_id(self):
# 验证订单行产品所属公司是否与订单公司兼容
invalid_companies = order.order_line.product_id.company_id.filtered(
lambda c: order.company_id not in c._accessible_branches()
)
if invalid_companies:
raise ValidationError(...)
- 确保订单行产品的公司与订单公司在权限范围内(多公司场景)。
四、门户与支付集成
门户访问
def _compute_access_url(self):
super()._compute_access_url()
for order in self:
order.access_url = f'/my/orders/{order.id}'
- 生成客户门户访问链接(如
/my/orders/1
),支持在线签名和支付。
支付流程
require_payment = fields.Boolean(compute='_compute_require_payment')
prepayment_percent = fields.Float(compute='_compute_prepayment_percent')
amount_paid = fields.Float(compute='_compute_amount_paid', compute_sudo=True)
require_payment
:根据公司配置决定是否需要在线支付确认订单。prepayment_percent
:预付款比例(如支付 30% 确认订单)。amount_paid
:通过payment.transaction
关联支付记录,支持授权(authorized
)和完成(done
)状态。
五、发票与财务集成
发票生成
def _prepare_invoice(self):
# 准备发票数据,包括客户、货币、支付条款、交易记录等
values = {
'move_type': 'out_invoice',
'partner_id': self.partner_invoice_id.id,
'fiscal_position_id': self.fiscal_position_id.id,
'transaction_ids': [Command.set(txs_to_be_linked.ids)],
# ...其他字段
}
return values
- 关联订单与发票,处理税收规则(
fiscal_position_id
)和支付交易(transaction_ids
)。
开票状态跟踪
def _get_invoiced(self):
# 通过订单行的发票行关联发票记录
invoices = order.order_line.invoice_lines.move_id.filtered(
lambda r: r.move_type in ('out_invoice', 'out_refund')
)
order.invoice_ids = invoices
- 支持正向发票(
out_invoice
)和退款(out_refund
)的跟踪。
六、多公司与权限管理
company_id = fields.Many2one('res.company', required=True, default=lambda self: self.env.company)
@api.constrains('company_id', 'order_line')
def _check_order_line_company_id(self):
# 验证产品公司是否属于订单公司的可访问分支
if invalid_companies:
raise ValidationError(...)
- 强制订单必须属于某个公司,并验证产品公司与订单公司的兼容性(通过
_accessible_branches
)。
七、扩展与本地化支持
可扩展点
_get_order_edi_decoder
:支持 EDI 文件解析(如 XML/CSV 订单导入)。_filter_product_documents
:根据订单状态(报价 / 销售订单)过滤附件。
本地化修改
- 通过继承
_get_name_portal_content_view
和_get_name_tax_totals_view
调整门户页面和税收显示。
总结
理解sale_order.py代码中上面这些关键逻辑,才能有助于开发人员在Odoo上针对销售业务进行二次开发(如定制化状态、扩展支付方式、修改开票规则等),Odoo的二次开发要建立在现有业务逻辑的基础上才能真正发挥价值。