[PERF] pos_sale: memoize _compute_currency_rate of pos.order

Description:
Saving a new `res.config.settings` (for ex: checking "Use QR code on
ticket") can be extremely slow on database with a large number
of `pos.order`.

Analysis:
Saving a new `res.config.settings` creates a new instance of the config.
This will invoke a check on the python constraints calling
eventually `_check_currencies` of `pos.config`, which will invoke the
compute of `currency_id` setting the field as dirty.
The `_compute_currency_rate` of `pos.order` depends on the `currency_id`
of the current `pos.config` (via a related).
This means the compute will be called with *all* `pos.order` ever made.
As the compute itself makes a few calls to `_get_conversion_rate`, which
makes a query, it can become slow when the method is called frequently.

Solution:
Memoize the call to `_get_conversion_rate`, as similarly done
in `account.move.line` from d8d47f9ff8
to reduce the number queries necessary.

Benchmark:
The `create` request to `res.config.settings` on a database with over
240k `pos.order` took

|             | Before    | After    |
|-------------|-----------|----------|
| Timing      | 36.9 secs | 4.3 secs |
| Query count | 47k       | 627      |

Reference:
opw-3632273

closes odoo/odoo#148136

X-original-commit: 1c6f7f23b2c0dbd009d8d56689c7a98792990f7f
Signed-off-by: Joseph Caburnay (jcb) <jcb@odoo.com>
Signed-off-by: Piryns Victor (pivi) <pivi@odoo.com>
This commit is contained in:
Victor Piryns (pivi)
2024-01-04 18:17:33 +00:00
parent a6d24df028
commit 01e09bb2c2
2 changed files with 24 additions and 3 deletions
+12 -2
View File
@@ -3,7 +3,7 @@
import logging
from datetime import datetime
from markupsafe import Markup
from functools import partial
from functools import partial, lru_cache
from itertools import groupby
from collections import defaultdict
@@ -418,8 +418,18 @@ class PosOrder(models.Model):
@api.depends('date_order', 'company_id', 'currency_id', 'company_id.currency_id')
def _compute_currency_rate(self):
@lru_cache
def get_rate(from_currency, to_currency, company, date):
return self.env['res.currency']._get_conversion_rate(
from_currency=from_currency,
to_currency=to_currency,
company=company,
date=date,
)
for order in self:
order.currency_rate = self.env['res.currency']._get_conversion_rate(order.company_id.currency_id, order.currency_id, order.company_id, order.date_order)
# date_order is a datetime, but the rates are looked up on a date basis,
# therefor converting the date_order to a date helps with sharing entries in the lru_cache
order.currency_rate = get_rate(order.company_id.currency_id, order.currency_id, order.company_id, order.date_order.date())
@api.depends('lines.is_total_cost_computed')
def _compute_is_total_cost_computed(self):
+12 -1
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from functools import lru_cache
from odoo import api, fields, models, _
from odoo.tools import float_compare, float_is_zero
@@ -24,9 +25,19 @@ class PosOrder(models.Model):
@api.depends('date_order', 'company_id')
def _compute_currency_rate(self):
@lru_cache
def get_rate(from_currency, to_currency, company, date):
return self.env['res.currency']._get_conversion_rate(
from_currency=from_currency,
to_currency=to_currency,
company=company,
date=date,
)
for order in self:
date_order = order.date_order or fields.Datetime.now()
order.currency_rate = self.env['res.currency']._get_conversion_rate(order.company_id.currency_id, order.currency_id, order.company_id, date_order)
# date_order is a datetime, but the rates are looked up on a date basis,
# therefor converting the date_order to a date helps with sharing entries in the lru_cache
order.currency_rate = get_rate(order.company_id.currency_id, order.currency_id, order.company_id, date_order.date())
def _prepare_invoice_vals(self):
invoice_vals = super(PosOrder, self)._prepare_invoice_vals()