From 173b76c7bb343a5bf3437cc53ddcaebfce4fccb1 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Fri, 3 Jun 2016 15:24:41 +0200 Subject: [PATCH] [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. --- addons/point_of_sale/point_of_sale.py | 92 ++++++++++++--------------- 1 file changed, 41 insertions(+), 51 deletions(-) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 1d785ea7c08..de213986d9b 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -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,