[FIX] point_of_sale: performance and invalidation
Function fields, old-api style, that are not stored do not have cache invalidation mechanisms. This means that they are recomputed every time the field is accessed. Due to the way the cache behaves, the records that are present in the cache will be included when the prefetching is done. In the scenario of the a POS session being closed, this implies that the cache will contains more and more account.move.line records and try to prefetch all (hopefully max 1000) without ever relying on the computed result for the next access. This makes the computation of fields `amount_total` and `price_subtotal` very slow, even for orders with a very low number of lines. Backport the new API migration of these computed field to benefit from depends mechanism and keep the computed value into cache when needed. In master these fields should be stored.
This commit is contained in:
@@ -18,6 +18,7 @@ from openerp.tools import float_is_zero
|
||||
from openerp.tools.translate import _
|
||||
from openerp.exceptions import UserError
|
||||
|
||||
from openerp import api, fields as Fields
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -842,37 +843,11 @@ class pos_order(osv.osv):
|
||||
pricelist = self.pool.get('res.partner').browse(cr, uid, part, context=context).property_product_pricelist.id
|
||||
return {'value': {'pricelist_id': pricelist}}
|
||||
|
||||
def _amount_all(self, cr, uid, ids, name, args, context=None):
|
||||
cur_obj = self.pool.get('res.currency')
|
||||
res = {}
|
||||
for order in self.browse(cr, uid, ids, context=context):
|
||||
res[order.id] = {
|
||||
'amount_paid': 0.0,
|
||||
'amount_return':0.0,
|
||||
'amount_tax':0.0,
|
||||
}
|
||||
val1 = val2 = 0.0
|
||||
cur = order.pricelist_id.currency_id
|
||||
for payment in order.statement_ids:
|
||||
res[order.id]['amount_paid'] += payment.amount
|
||||
res[order.id]['amount_return'] += (payment.amount < 0 and payment.amount or 0)
|
||||
for line in order.lines:
|
||||
val1 += self._amount_line_tax(cr, uid, line, order.fiscal_position_id, context=context)
|
||||
val2 += line.price_subtotal
|
||||
res[order.id]['amount_tax'] = cur_obj.round(cr, uid, cur, val1)
|
||||
amount_untaxed = cur_obj.round(cr, uid, cur, val2)
|
||||
res[order.id]['amount_total'] = res[order.id]['amount_tax'] + amount_untaxed
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Order Ref', required=True, readonly=True, copy=False),
|
||||
'company_id':fields.many2one('res.company', 'Company', required=True, readonly=True),
|
||||
'date_order': fields.datetime('Order Date', readonly=True, select=True),
|
||||
'user_id': fields.many2one('res.users', 'Salesman', help="Person who uses the cash register. It can be a reliever, a student or an interim employee."),
|
||||
'amount_tax': fields.function(_amount_all, string='Taxes', digits=0, multi='all'),
|
||||
'amount_total': fields.function(_amount_all, string='Total', digits=0, multi='all'),
|
||||
'amount_paid': fields.function(_amount_all, string='Paid', states={'draft': [('readonly', False)]}, readonly=True, digits=0, multi='all'),
|
||||
'amount_return': fields.function(_amount_all, string='Returned', digits=0, multi='all'),
|
||||
'lines': fields.one2many('pos.order.line', 'order_id', 'Order Lines', states={'draft': [('readonly', False)]}, readonly=True, copy=True),
|
||||
'statement_ids': fields.one2many('account.bank.statement.line', 'pos_statement_id', 'Payments', states={'draft': [('readonly', False)]}, readonly=True),
|
||||
'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, states={'draft': [('readonly', False)]}, readonly=True),
|
||||
@@ -905,6 +880,24 @@ class pos_order(osv.osv):
|
||||
'fiscal_position_id': fields.many2one('account.fiscal.position', 'Fiscal Position')
|
||||
}
|
||||
|
||||
amount_tax = Fields.Float(compute='_compute_amount_all', string='Taxes', digits=0)
|
||||
amount_total = Fields.Float(compute='_compute_amount_all', string='Total', digits=0)
|
||||
amount_paid = Fields.Float(compute='_compute_amount_all', string='Paid', states={'draft': [('readonly', False)]}, readonly=True, digits=0)
|
||||
amount_return = Fields.Float(compute='_compute_amount_all', string='Returned', digits=0)
|
||||
|
||||
|
||||
@api.depends('statement_ids', 'lines.price_subtotal_incl', 'lines.discount')
|
||||
def _compute_amount_all(self):
|
||||
for order in self:
|
||||
order.amount_paid = order.amount_return = order.amount_tax = 0.0
|
||||
currency = order.pricelist_id.currency_id
|
||||
order.amount_paid = sum(payment.amount for payment in order.statement_ids)
|
||||
order.amount_return = sum(payment.amount < 0 and payment.amount or 0 for payment in order.statement_ids)
|
||||
order.amount_tax = currency.round(sum(self._amount_line_tax(line, order.fiscal_position_id) for line in order.lines))
|
||||
amount_untaxed = currency.round(sum(line.price_subtotal for line in order.lines))
|
||||
order.amount_total = order.amount_tax + amount_untaxed
|
||||
|
||||
|
||||
def _default_session(self, cr, uid, context=None):
|
||||
so = self.pool.get('pos.session')
|
||||
session_ids = so.search(cr, uid, [('state','=', 'opened'), ('user_id','=',uid)], context=context)
|
||||
@@ -1433,29 +1426,6 @@ class pos_order_line(osv.osv):
|
||||
line[2]['tax_ids'] = [(6, 0, [x.id for x in product.taxes_id])]
|
||||
return line
|
||||
|
||||
def _amount_line_all(self, cr, uid, ids, field_names, arg, context=None):
|
||||
res = dict([(i, {}) for i in ids])
|
||||
account_tax_obj = self.pool.get('account.tax')
|
||||
cur_obj = self.pool.get('res.currency')
|
||||
for line in self.browse(cr, uid, ids, context=context):
|
||||
cur = line.order_id.pricelist_id.currency_id
|
||||
taxes = [ tax for tax in line.tax_ids if tax.company_id.id == line.order_id.company_id.id ]
|
||||
fiscal_position_id = line.order_id.fiscal_position_id
|
||||
if fiscal_position_id:
|
||||
taxes = fiscal_position_id.map_tax(taxes)
|
||||
taxes_ids = [ tax.id for tax in taxes ]
|
||||
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
|
||||
res[line.id]['price_subtotal'] = res[line.id]['price_subtotal_incl'] = price * line.qty
|
||||
if taxes_ids:
|
||||
taxes = account_tax_obj.browse(cr, uid, taxes_ids, context).compute_all(price, cur, line.qty, product=line.product_id, partner=line.order_id.partner_id or False)
|
||||
res[line.id]['price_subtotal'] = taxes['total_excluded']
|
||||
res[line.id]['price_subtotal_incl'] = taxes['total_included']
|
||||
|
||||
res[line.id]['price_subtotal'] = cur_obj.round(cr, uid, cur, res[line.id]['price_subtotal'])
|
||||
res[line.id]['price_subtotal_incl'] = cur_obj.round(cr, uid, cur, res[line.id]['price_subtotal_incl'])
|
||||
|
||||
return res
|
||||
|
||||
def onchange_product_id(self, cr, uid, ids, pricelist, product_id, qty=0, partner_id=False, context=None):
|
||||
context = context or {}
|
||||
if not product_id:
|
||||
@@ -1509,8 +1479,6 @@ class pos_order_line(osv.osv):
|
||||
'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], required=True, change_default=True),
|
||||
'price_unit': fields.float(string='Unit Price', digits=0),
|
||||
'qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure')),
|
||||
'price_subtotal': fields.function(_amount_line_all, multi='pos_order_line_amount', digits=0, string='Subtotal w/o Tax'),
|
||||
'price_subtotal_incl': fields.function(_amount_line_all, multi='pos_order_line_amount', digits=0, string='Subtotal'),
|
||||
'discount': fields.float('Discount (%)', digits=0),
|
||||
'order_id': fields.many2one('pos.order', 'Order Ref', ondelete='cascade'),
|
||||
'create_date': fields.datetime('Creation Date', readonly=True),
|
||||
@@ -1518,6 +1486,28 @@ class pos_order_line(osv.osv):
|
||||
'tax_ids_after_fiscal_position': fields.function(_get_tax_ids_after_fiscal_position, type='many2many', relation='account.tax', string='Taxes')
|
||||
}
|
||||
|
||||
price_subtotal = Fields.Float(compute='_compute_amount_line_all', digits=0, string='Subtotal w/o Tax')
|
||||
price_subtotal_incl = Fields.Float(compute='_compute_amount_line_all', digits=0, string='Subtotal')
|
||||
|
||||
@api.depends('price_unit', 'tax_ids', 'qty', 'discount', 'product_id')
|
||||
def _compute_amount_line_all(self):
|
||||
for line in self:
|
||||
currency = line.order_id.pricelist_id.currency_id
|
||||
taxes = line.tax_ids.filtered(lambda tax: tax.company_id.id == line.order_id.company_id.id)
|
||||
fiscal_position_id = line.order_id.fiscal_position_id
|
||||
if fiscal_position_id:
|
||||
taxes = fiscal_position_id.map_tax(taxes)
|
||||
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
|
||||
line.price_subtotal = line.price_subtotal_incl = price * line.qty
|
||||
if taxes:
|
||||
taxes = taxes.compute_all(price, currency, line.qty, product=line.product_id, partner=line.order_id.partner_id or False)
|
||||
line.price_subtotal = taxes['total_excluded']
|
||||
line.price_subtotal_incl = taxes['total_included']
|
||||
|
||||
line.price_subtotal = currency.round(line.price_subtotal)
|
||||
line.price_subtotal_incl = currency.round(line.price_subtotal_incl)
|
||||
|
||||
|
||||
_defaults = {
|
||||
'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').next_by_code(cr, uid, 'pos.order.line', context=context),
|
||||
'qty': lambda *a: 1,
|
||||
|
||||
Reference in New Issue
Block a user