From 8217f3f80b443524ddf3527c8f1e97dad4807f2a Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 18 May 2017 14:59:00 +0200 Subject: [PATCH] [FIX+IMP] connector_prestashop: Several things * [FIX] Fix export when no location has flag prestashop_synchronized When there is no location with the flag prestashop_synchronized in the location tree, Current behavior: The `location` key in the context is empty then the qty_available is computed for *every* locations/warehouses. Expected behavior: If there is at least one location flagged, use it, otherwise, use all the locations of the tree, starting from the selected location or selected warehouse location. When no location can be found (for instance because user selected a location with a usage different than 'internal'), then it should just fail and not return the stock of all warehouses. Being able to synchronize without activating 'prestashop_synchronized' is important: when used, this feature will trigger an export every time a quant is changed or created, on a large catalog, we might prefer only using the cron to have less frequent updates and less jobs. A second fix is that when a stock_location_id is set on the backend, it should be used, it was only used for the import of stock. * [FIX] Exclude children from qty computation We already pass the children in 'location', if we don't disable 'compute_child', the qty computation will try to get the children of each child and generates query of 1mio chars. * [IMP] Group computation of qty_available Stats with 6700 products: Before: 570s After: 28s * [FIX] Apply filter on locations for variants quantities It was only applied on export of template quantities. * [IMP] Improve performance on prestashop qty recompute The add/union operation between recordsets is slow, aggregate the ids in a set and call the browse only once on the complete set is dramatically faster. See https://github.com/guewen/connector-magento/pull/9 --- .../models/prestashop_backend/common.py | 26 ++++++++ .../models/product_product/common.py | 29 +++++++-- .../models/product_template/common.py | 61 +++++++------------ 3 files changed, 71 insertions(+), 45 deletions(-) diff --git a/connector_prestashop/models/prestashop_backend/common.py b/connector_prestashop/models/prestashop_backend/common.py index 8c74ed07d..3c5a2bd7b 100644 --- a/connector_prestashop/models/prestashop_backend/common.py +++ b/connector_prestashop/models/prestashop_backend/common.py @@ -295,6 +295,32 @@ def import_record(self, model_name, ext_id): import_record(session, model_name, self.id, ext_id) return True + @api.multi + def _get_locations_for_stock_quantities(self): + root_location = (self.stock_location_id or + self.warehouse_id.lot_stock_id) + locations = self.env['stock.location'].search([ + ('id', 'child_of', root_location.id), + ('prestashop_synchronized', '=', True), + ('usage', '=', 'internal'), + ]) + # if we choosed a location but none where flagged + # 'prestashop_synchronized', consider we want all of them in the tree + if not locations: + locations = self.env['stock.location'].search([ + ('id', 'child_of', root_location.id), + ('usage', '=', 'internal'), + ]) + if not locations: + # we must not pass an empty location or we would have the + # stock for every warehouse, which is the last thing we + # expect + raise exceptions.UserError( + _('No internal location found to compute the product ' + 'quantity.') + ) + return locations + class PrestashopShopGroup(models.Model): _name = 'prestashop.shop.group' diff --git a/connector_prestashop/models/product_product/common.py b/connector_prestashop/models/product_product/common.py index c8ff8a33a..86b429a1f 100644 --- a/connector_prestashop/models/product_product/common.py +++ b/connector_prestashop/models/product_product/common.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from collections import defaultdict + from odoo import api, fields, models from odoo.addons import decimal_precision as dp @@ -152,14 +154,29 @@ class PrestashopProductCombination(models.Model): @api.multi def recompute_prestashop_qty(self): - for product_binding in self: - if product_binding.quantity != product_binding.qty_available: - product_binding.quantity = product_binding.qty_available + # group products by backend + backends = defaultdict(set) + for product in self: + backends[product.backend_id].add(product.id) + + for backend, product_ids in backends.iteritems(): + products = self.browse(product_ids) + products._recompute_prestashop_qty_backend(backend) return True - @api.model - def _prestashop_qty(self, product): - return product.qty_available + @api.multi + def _recompute_prestashop_qty_backend(self, backend): + locations = backend._get_locations_for_stock_quantities() + self_loc = self.with_context(location=locations.ids, + compute_child=False) + for product_binding in self_loc: + new_qty = product_binding._prestashop_qty() + if product_binding.quantity != new_qty: + product_binding.quantity = new_qty + return True + + def _prestashop_qty(self): + return self.qty_available @job(default_channel='root.prestashop') def export_inventory(self, backend, fields=None, **kwargs): diff --git a/connector_prestashop/models/product_template/common.py b/connector_prestashop/models/product_template/common.py index 07da481de..c704f826c 100644 --- a/connector_prestashop/models/product_template/common.py +++ b/connector_prestashop/models/product_template/common.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) -from odoo import api, fields, models +from collections import defaultdict + +from odoo import _, api, exceptions, fields, models from odoo.addons import decimal_precision as dp from odoo.addons.queue_job.job import job @@ -113,48 +115,29 @@ class PrestashopProductTemplate(models.Model): @api.multi def recompute_prestashop_qty(self): - for product_binding in self: - new_qty = product_binding._prestashop_qty() - if product_binding.quantity != new_qty: - product_binding.quantity = new_qty + # group products by backend + backends = defaultdict(set) + for product in self: + backends[product.backend_id].add(product.id) + + for backend, product_ids in backends.iteritems(): + products = self.browse(product_ids) + products._recompute_prestashop_qty_backend(backend) return True - def _prestashop_qty(self): - locations = self.env['stock.location'].search([ - ('id', 'child_of', self.backend_id.warehouse_id.lot_stock_id.id), - ('prestashop_synchronized', '=', True), - ('usage', '=', 'internal'), - ]) - return self.with_context(location=locations.ids).qty_available - - @job(default_channel='root.prestashop') - def import_products(self, backend, since_date=None, **kwargs): - filters = None - if since_date: - filters = {'date': '1', 'filter[date_upd]': '>[%s]' % (since_date)} - now_fmt = fields.Datetime.now() - self.env['prestashop.product.category'].with_delay( - priority=15 - ).import_batch(backend=backend, filters=filters, **kwargs) - self.env['prestashop.product.template'].with_delay( - priority=15 - ).import_batch(backend, filters, **kwargs) - backend.import_products_since = now_fmt + @api.multi + def _recompute_prestashop_qty_backend(self, backend): + locations = backend._get_locations_for_stock_quantities() + self_loc = self.with_context(location=locations.ids, + compute_child=False) + for product in self_loc: + new_qty = product._prestashop_qty() + if product.quantity != new_qty: + product.quantity = new_qty return True - @job(default_channel='root.prestashop') - def export_inventory(self, backend, fields=None, **kwargs): - """ Export the inventory configuration and quantity of a product. """ - env = backend.get_environment(self._name) - inventory_exporter = env.get_connector_unit(ProductInventoryExporter) - return inventory_exporter.run(self.id, fields, **kwargs) - - @api.model - @job(default_channel='root.prestashop') - def export_product_quantities(self, backend): - self.search([ - ('backend_id', 'in', self.env.backend.ids), - ]).recompute_prestashop_qty() + def _prestashop_qty(self): + return self.qty_available @prestashop