[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:
Martin Trigaux
2016-06-28 13:23:41 +02:00
parent 10550aa5a5
commit 173b76c7bb
+41 -51
View File
@@ -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,