Odoo 10 - 单例错误 - 无法获取订单行中特定字段的总和
Odoo 10 - Singleton Error - Cannot get sum of specific field in order lines
我目前在我的模块中实现一项功能时遇到困难,该功能计算订单行的总税额。我的模块有一个订单行部分(很像销售模块中的那个),它允许添加多个产品并在它们旁边显示它们的价格。看看我是如何添加这个功能的 。在底部,我有一个显示三个字段的小计页脚:amount_untaxed、amount_tax 和 amount_total。税收计算工作正常(存储在 amount_tax 中),但我无法在该页脚中显示税收金额。这是相关代码:
models.py
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from odoo.addons import decimal_precision as dp
class mymodule(models.Model):
_name = 'mymodule.mymodule'
_description = 'My Module'
currency_id = fields.Many2one('res.currency', string='Currency')
operations = fields.One2many('mymodule.line', 'mymodule_id', 'Order Lines')
amount_untaxed = fields.Monetary(string='Untaxed Amount',
default=0,
compute='_compute_untaxed',
currency_field='currency_id')
amount_tax = fields.Monetary(string='Taxes',
default=0,
compute='_compute_taxes',
currency_field='currency_id')
amount_total = fields.Monetary(string='Total',
default=0,
compute='_compute_total',
currency_field='currency_id')
# Sum all prices into untaxed total
@api.depends('operations.price')
def _compute_untaxed(self):
for record in self:
record.amount_untaxed = sum(line.price for line in record.operations)
# Sum all prices into taxed total
@api.depends('operations.total')
def _compute_total(self):
for record in self:
record.amount_total = sum(line.total for line in record.operations)
# Sum all taxes
@api.depends('operations.taxes')
def _compute_taxes(self):
for record in self:
record.amount_tax = sum(line.taxes for line in record.operations)
class mymodule_line(models.Model):
_name = 'mymodule.line'
_description = 'Order Line'
mymodule_id = fields.Many2one('mymodule.mymodule')
product_id = fields.Many2one('product.template',
string='Product',
required=False) # Product name
tax_id = fields.Many2many('account.tax',
string='Taxes',
store=False) # Product default taxes
taxes = fields.Float(string='Taxes', readonly=False)
price = fields.Float(string='Price')
total = fields.Float(string='Total', readonly=False)
# Display default values based on product selected
@api.onchange('product_id')
def _onchange_product_id(self):
self.price = self.product_id.lst_price
self.tax_id = self.product_id.taxes_id
# Compute total and tax amount for selected product
@api.onchange('price', 'tax_id')
@api.depends('total', 'tax_id.amount', 'price', 'taxes')
def _compute_total(self):
self.taxes = (0.01 * self.tax_id.amount * self.price)
self.total = self.taxes + self.price
views.xml
<record id="mymodule.form" model="ir.ui.view">
<field name="name">My Module Form</field>
<field name="model">mymodule.mymodule</field>
<field name="arch" type="xml">
<form>
<sheet>
<notebook>
<!-- Order Lines -->
<page string="Operations">
<group class="oe_subtotal_footer oe_right" colspan="2" name="sale_total">
<field name="amount_untaxed"/>
<field name="amount_tax"/>
<div class="oe_subtotal_footer_separator oe_inline o_td_label">
</div>
<field name="amount_total" nolabel="1" class="oe_subtotal_footer_separator"/>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
有趣的是,如果我这样更改以下行,添加计算参数,税额将正确显示。
taxes = fields.Float(string='Taxes', readonly=False, compute='_compute_total')
但是,如果我在订单行中添加了多个产品,然后尝试保存记录,则会出现单例错误。
ValueError: Expected singleton: mymodule.line(346, 347)
在我的 _compute_total 函数上方添加 @api.one 装饰器然后允许我保存记录,但是一旦保存,税收计算就会消失。
这是我的小计页脚应该是什么样子的图片以供参考:Subtotal Footer
感谢您的帮助!
让我们来看看这在 Odoo 核心中是如何完成的,例如在 sale
module:
_name = "sale.order"
@api.depends('order_line.price_total')
def _amount_all(self):
"""
Compute the total amounts of the SO.
"""
for order in self:
amount_untaxed = amount_tax = 0.0
for line in order.order_line:
amount_untaxed += line.price_subtotal
# FORWARDPORT UP TO 10.0
if order.company_id.tax_calculation_rounding_method == 'round_globally':
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
taxes = line.tax_id.compute_all(price, line.order_id.currency_id, line.product_uom_qty, product=line.product_id, partner=line.order_id.partner_id)
amount_tax += sum(t.get('amount', 0.0) for t in taxes.get('taxes', []))
else:
amount_tax += line.price_tax
order.update({
'amount_untaxed': order.pricelist_id.currency_id.round(amount_untaxed),
'amount_tax': order.pricelist_id.currency_id.round(amount_tax),
'amount_total': amount_untaxed + amount_tax,
})
amount_untaxed = fields.Monetary(string='Untaxed Amount', store=True, readonly=True, compute='_amount_all', track_visibility='always')
amount_tax = fields.Monetary(string='Taxes', store=True, readonly=True, compute='_amount_all', track_visibility='always')
amount_total = fields.Monetary(string='Total', store=True, readonly=True, compute='_amount_all', track_visibility='always')
首先,请注意所有字段都可以在一个方法中计算,这比多次循环相同的记录要高效一些。
其次,注意字段定义都是readonly=True
。默认情况下,计算字段为 readonly
,不应手动创建 readonly=False
。 这可能就是您的字段未按预期显示的原因。
如果您想让计算域可编辑,您需要为该域设置一个 inverse
method。您似乎不希望此字段可编辑,因为它应该在您的每个 record.operations
.
上进行管理
这只是您设置中可能存在缺陷的一方面。再次查看销售模块,但这次查看 order lines are set up:
_name = 'sale.order.line'
@api.depends('product_uom_qty', 'discount', 'price_unit', 'tax_id')
def _compute_amount(self):
"""
Compute the amounts of the SO line.
"""
for line in self:
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
taxes = line.tax_id.compute_all(price, line.order_id.currency_id, line.product_uom_qty, product=line.product_id, partner=line.order_id.partner_id)
line.update({
'price_tax': taxes['total_included'] - taxes['total_excluded'],
'price_total': taxes['total_included'],
'price_subtotal': taxes['total_excluded'],
})
price_subtotal = fields.Monetary(compute='_compute_amount', string='Subtotal', readonly=True, store=True)
price_tax = fields.Monetary(compute='_compute_amount', string='Taxes', readonly=True, store=True)
price_total = fields.Monetary(compute='_compute_amount', string='Total', readonly=True, store=True)
请注意,这些字段中的每一个都会被计算,而您的字段是由 onchange
方法驱动的。此外,这些字段是 readonly=True
(默认情况下所有 computed
字段),这可以防止它们被手动更改。
ValueError: Expected singleton: mymodule.line(346, 347)
Adding the @api.one decorator above my _compute_total function then allows me to save the record, but the tax calculation disappears once it is saved.
@api.depends('operations.total')
def _compute_total(self):
for record in self:
将 @api.one
装饰器添加到上述方法是不合逻辑的,因为它用于指定您的方法应该只用于计算单个记录(而不是记录集)。您的方法已经设置为处理多条记录(for record in self
意味着可能会有多条记录)因此您应该使用 @api.multi
装饰器(如果有的话)。
综上所述,我强烈建议重新审视您的模型设计以反映 sale
模块中使用的模型,因为它们在设计时考虑了效率和数据安全性。
我想补充一些 travisw 的回答没有描述的内容:您正在组合使用 api.change
和 api.depends
装饰器。这可以解释奇怪的行为,比如单例错误,而不是用额外的 api.one
.
保存记录
api.onchange
仅适用于单例,我想这就是为什么您使用 self 而不是循环自身来编写方法的原因。 api.depends
也适用于单例,但在许多情况下不限制 Odoo,它将与一组记录一起使用。
我建议你像这样划分你的方法:
@api.onchange('price', 'tax_id')
def onchange_total(self):
self._compute_total()
@api.depends('total', 'tax_id.amount', 'price', 'taxes')
def compute_total(self):
self._compute_total()
@api.multi
def _compute_total(self):
for line in self:
line.taxes = (0.01 * line.tax_id.amount * line.price)
line.total = line.taxes + line.price
我目前在我的模块中实现一项功能时遇到困难,该功能计算订单行的总税额。我的模块有一个订单行部分(很像销售模块中的那个),它允许添加多个产品并在它们旁边显示它们的价格。看看我是如何添加这个功能的
models.py
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from odoo.addons import decimal_precision as dp
class mymodule(models.Model):
_name = 'mymodule.mymodule'
_description = 'My Module'
currency_id = fields.Many2one('res.currency', string='Currency')
operations = fields.One2many('mymodule.line', 'mymodule_id', 'Order Lines')
amount_untaxed = fields.Monetary(string='Untaxed Amount',
default=0,
compute='_compute_untaxed',
currency_field='currency_id')
amount_tax = fields.Monetary(string='Taxes',
default=0,
compute='_compute_taxes',
currency_field='currency_id')
amount_total = fields.Monetary(string='Total',
default=0,
compute='_compute_total',
currency_field='currency_id')
# Sum all prices into untaxed total
@api.depends('operations.price')
def _compute_untaxed(self):
for record in self:
record.amount_untaxed = sum(line.price for line in record.operations)
# Sum all prices into taxed total
@api.depends('operations.total')
def _compute_total(self):
for record in self:
record.amount_total = sum(line.total for line in record.operations)
# Sum all taxes
@api.depends('operations.taxes')
def _compute_taxes(self):
for record in self:
record.amount_tax = sum(line.taxes for line in record.operations)
class mymodule_line(models.Model):
_name = 'mymodule.line'
_description = 'Order Line'
mymodule_id = fields.Many2one('mymodule.mymodule')
product_id = fields.Many2one('product.template',
string='Product',
required=False) # Product name
tax_id = fields.Many2many('account.tax',
string='Taxes',
store=False) # Product default taxes
taxes = fields.Float(string='Taxes', readonly=False)
price = fields.Float(string='Price')
total = fields.Float(string='Total', readonly=False)
# Display default values based on product selected
@api.onchange('product_id')
def _onchange_product_id(self):
self.price = self.product_id.lst_price
self.tax_id = self.product_id.taxes_id
# Compute total and tax amount for selected product
@api.onchange('price', 'tax_id')
@api.depends('total', 'tax_id.amount', 'price', 'taxes')
def _compute_total(self):
self.taxes = (0.01 * self.tax_id.amount * self.price)
self.total = self.taxes + self.price
views.xml
<record id="mymodule.form" model="ir.ui.view">
<field name="name">My Module Form</field>
<field name="model">mymodule.mymodule</field>
<field name="arch" type="xml">
<form>
<sheet>
<notebook>
<!-- Order Lines -->
<page string="Operations">
<group class="oe_subtotal_footer oe_right" colspan="2" name="sale_total">
<field name="amount_untaxed"/>
<field name="amount_tax"/>
<div class="oe_subtotal_footer_separator oe_inline o_td_label">
</div>
<field name="amount_total" nolabel="1" class="oe_subtotal_footer_separator"/>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
有趣的是,如果我这样更改以下行,添加计算参数,税额将正确显示。
taxes = fields.Float(string='Taxes', readonly=False, compute='_compute_total')
但是,如果我在订单行中添加了多个产品,然后尝试保存记录,则会出现单例错误。
ValueError: Expected singleton: mymodule.line(346, 347)
在我的 _compute_total 函数上方添加 @api.one 装饰器然后允许我保存记录,但是一旦保存,税收计算就会消失。
这是我的小计页脚应该是什么样子的图片以供参考:Subtotal Footer
感谢您的帮助!
让我们来看看这在 Odoo 核心中是如何完成的,例如在 sale
module:
_name = "sale.order"
@api.depends('order_line.price_total')
def _amount_all(self):
"""
Compute the total amounts of the SO.
"""
for order in self:
amount_untaxed = amount_tax = 0.0
for line in order.order_line:
amount_untaxed += line.price_subtotal
# FORWARDPORT UP TO 10.0
if order.company_id.tax_calculation_rounding_method == 'round_globally':
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
taxes = line.tax_id.compute_all(price, line.order_id.currency_id, line.product_uom_qty, product=line.product_id, partner=line.order_id.partner_id)
amount_tax += sum(t.get('amount', 0.0) for t in taxes.get('taxes', []))
else:
amount_tax += line.price_tax
order.update({
'amount_untaxed': order.pricelist_id.currency_id.round(amount_untaxed),
'amount_tax': order.pricelist_id.currency_id.round(amount_tax),
'amount_total': amount_untaxed + amount_tax,
})
amount_untaxed = fields.Monetary(string='Untaxed Amount', store=True, readonly=True, compute='_amount_all', track_visibility='always')
amount_tax = fields.Monetary(string='Taxes', store=True, readonly=True, compute='_amount_all', track_visibility='always')
amount_total = fields.Monetary(string='Total', store=True, readonly=True, compute='_amount_all', track_visibility='always')
首先,请注意所有字段都可以在一个方法中计算,这比多次循环相同的记录要高效一些。
其次,注意字段定义都是readonly=True
。默认情况下,计算字段为 readonly
,不应手动创建 readonly=False
。 这可能就是您的字段未按预期显示的原因。
如果您想让计算域可编辑,您需要为该域设置一个 inverse
method。您似乎不希望此字段可编辑,因为它应该在您的每个 record.operations
.
这只是您设置中可能存在缺陷的一方面。再次查看销售模块,但这次查看 order lines are set up:
_name = 'sale.order.line'
@api.depends('product_uom_qty', 'discount', 'price_unit', 'tax_id')
def _compute_amount(self):
"""
Compute the amounts of the SO line.
"""
for line in self:
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
taxes = line.tax_id.compute_all(price, line.order_id.currency_id, line.product_uom_qty, product=line.product_id, partner=line.order_id.partner_id)
line.update({
'price_tax': taxes['total_included'] - taxes['total_excluded'],
'price_total': taxes['total_included'],
'price_subtotal': taxes['total_excluded'],
})
price_subtotal = fields.Monetary(compute='_compute_amount', string='Subtotal', readonly=True, store=True)
price_tax = fields.Monetary(compute='_compute_amount', string='Taxes', readonly=True, store=True)
price_total = fields.Monetary(compute='_compute_amount', string='Total', readonly=True, store=True)
请注意,这些字段中的每一个都会被计算,而您的字段是由 onchange
方法驱动的。此外,这些字段是 readonly=True
(默认情况下所有 computed
字段),这可以防止它们被手动更改。
ValueError: Expected singleton: mymodule.line(346, 347)
Adding the @api.one decorator above my _compute_total function then allows me to save the record, but the tax calculation disappears once it is saved.
@api.depends('operations.total')
def _compute_total(self):
for record in self:
将 @api.one
装饰器添加到上述方法是不合逻辑的,因为它用于指定您的方法应该只用于计算单个记录(而不是记录集)。您的方法已经设置为处理多条记录(for record in self
意味着可能会有多条记录)因此您应该使用 @api.multi
装饰器(如果有的话)。
综上所述,我强烈建议重新审视您的模型设计以反映 sale
模块中使用的模型,因为它们在设计时考虑了效率和数据安全性。
我想补充一些 travisw 的回答没有描述的内容:您正在组合使用 api.change
和 api.depends
装饰器。这可以解释奇怪的行为,比如单例错误,而不是用额外的 api.one
.
api.onchange
仅适用于单例,我想这就是为什么您使用 self 而不是循环自身来编写方法的原因。 api.depends
也适用于单例,但在许多情况下不限制 Odoo,它将与一组记录一起使用。
我建议你像这样划分你的方法:
@api.onchange('price', 'tax_id')
def onchange_total(self):
self._compute_total()
@api.depends('total', 'tax_id.amount', 'price', 'taxes')
def compute_total(self):
self._compute_total()
@api.multi
def _compute_total(self):
for line in self:
line.taxes = (0.01 * line.tax_id.amount * line.price)
line.total = line.taxes + line.price