From 8e2a5eaf9b08539d9c443278f096ba7092ff3c98 Mon Sep 17 00:00:00 2001 From: Quentin Groulard Date: Mon, 24 Jul 2023 10:25:46 +0200 Subject: [PATCH] [MIG] shopinvader: Migration to 16.0 Notable Odoo changes: * plan_id (analytic plan) required on account.analytic.account * onchange methods have been transformed into compute: => product_id_change() -> _compute_tax_id() => _onchange_discount() -> _compute_discount() * Field 'price' removed on product * Method _get_real_price_currency() removed on sale order lines => Not possible anymore to compute the price without a real so line * currency_id is now required to play onchanges on sale order lines * Price of demo data 'product_product_11' has changed (16.5 -> 33.0) * Name of demo data 'product_product_4' has changed (Customizable Desk (CONFIG) -> Customizable Desk) * Register payment wizard (account.payment.wizard) takes now a 'account.payment.method.line' instead of a 'account.payment.method' --- .pre-commit-config.yaml | 1 - requirements.txt | 2 + setup/shopinvader/odoo/addons/shopinvader | 1 + setup/shopinvader/setup.py | 6 ++ shopinvader/__manifest__.py | 4 +- shopinvader/data/mail_activity_data.xml | 16 +++-- shopinvader/demo/account_demo.xml | 5 ++ shopinvader/demo/email_demo.xml | 56 +++++++++--------- .../migrations/14.0.2.2.0/pre-migration.py | 8 --- .../migrations/14.0.4.0.0/post-migrate.py | 22 ------- .../migrations/14.0.4.1.0/post-migrate.py | 40 ------------- .../migrations/14.0.5.14.1/post-migrate.py | 25 -------- shopinvader/models/product_product.py | 2 +- shopinvader/models/product_template.py | 10 ++-- shopinvader/models/res_partner.py | 2 +- shopinvader/models/sale.py | 4 +- shopinvader/models/shopinvader_backend.py | 8 +-- shopinvader/models/shopinvader_binding.py | 2 +- shopinvader/models/shopinvader_category.py | 7 ++- .../models/shopinvader_notification.py | 1 - shopinvader/models/shopinvader_partner.py | 10 ++-- shopinvader/models/shopinvader_product.py | 14 ++--- shopinvader/models/shopinvader_variant.py | 59 ++++++++----------- shopinvader/services/abstract_download.py | 6 +- shopinvader/services/cart.py | 5 +- shopinvader/services/sale.py | 2 +- shopinvader/services/service.py | 10 +++- shopinvader/tests/common.py | 15 +++-- shopinvader/tests/test_cart.py | 5 +- shopinvader/tests/test_cart_item.py | 8 +-- shopinvader/tests/test_invoice.py | 3 + shopinvader/tests/test_product.py | 19 +++--- shopinvader/tests/test_product_filter.py | 4 +- shopinvader/tests/test_res_partner.py | 4 +- shopinvader/tests/test_sale.py | 3 + shopinvader/tests/test_sale_cancel.py | 2 +- shopinvader/tests/test_settings.py | 6 +- .../tests/test_shopinvader_category.py | 4 +- shopinvader/tests/test_shopinvader_partner.py | 6 +- .../tests/test_shopinvader_partner_binding.py | 4 +- ...test_shopinvader_variant_binding_wizard.py | 4 +- .../test_shopinvader_variant_seo_title.py | 2 +- shopinvader/tests/test_utils.py | 2 +- shopinvader/views/product_filter_view.xml | 2 +- .../views/shopinvader_cart_step_view.xml | 2 +- .../views/shopinvader_category_view.xml | 2 +- .../views/shopinvader_partner_view.xml | 3 +- .../views/shopinvader_product_view.xml | 1 + .../views/shopinvader_variant_view.xml | 3 +- 49 files changed, 179 insertions(+), 253 deletions(-) create mode 120000 setup/shopinvader/odoo/addons/shopinvader create mode 100644 setup/shopinvader/setup.py delete mode 100644 shopinvader/migrations/14.0.2.2.0/pre-migration.py delete mode 100644 shopinvader/migrations/14.0.4.0.0/post-migrate.py delete mode 100644 shopinvader/migrations/14.0.4.1.0/post-migrate.py delete mode 100644 shopinvader/migrations/14.0.5.14.1/post-migrate.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d1bb653b06..c0682511d0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,6 @@ exclude: | # NOT INSTALLABLE ADDONS ^partner_contact_company/| ^product_online_category/| - ^shopinvader/| ^shopinvader_algolia/| ^shopinvader_assortment/| ^shopinvader_auth_api_key/| diff --git a/requirements.txt b/requirements.txt index 1cfd61ad0e..a5b24c18aa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ # generated from manifests external_dependencies +cerberus openupgradelib python-slugify +unidecode diff --git a/setup/shopinvader/odoo/addons/shopinvader b/setup/shopinvader/odoo/addons/shopinvader new file mode 120000 index 0000000000..b6335c1331 --- /dev/null +++ b/setup/shopinvader/odoo/addons/shopinvader @@ -0,0 +1 @@ +../../../../shopinvader \ No newline at end of file diff --git a/setup/shopinvader/setup.py b/setup/shopinvader/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/shopinvader/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/shopinvader/__manifest__.py b/shopinvader/__manifest__.py index a12f1b09cd..ce36c5e1df 100644 --- a/shopinvader/__manifest__.py +++ b/shopinvader/__manifest__.py @@ -5,13 +5,13 @@ { "name": "Shopinvader", "summary": "Shopinvader", - "version": "14.0.5.24.12", + "version": "16.0.1.0.0", "category": "e-commerce", "website": "https://github.com/shopinvader/odoo-shopinvader", "author": "Akretion", "license": "AGPL-3", "application": True, - "installable": False, + "installable": True, "external_dependencies": {"python": ["cerberus", "unidecode"], "bin": []}, "depends": [ "base_rest", diff --git a/shopinvader/data/mail_activity_data.xml b/shopinvader/data/mail_activity_data.xml index f8c64713e7..a29ebe12d6 100644 --- a/shopinvader/data/mail_activity_data.xml +++ b/shopinvader/data/mail_activity_data.xml @@ -1,11 +1,9 @@ - - - - Shop - Validate customer - fa-tasks - 3 - warning - - + + + Shop - Validate customer + fa-tasks + 3 + warning + diff --git a/shopinvader/demo/account_demo.xml b/shopinvader/demo/account_demo.xml index d1551c3ea5..c178048512 100644 --- a/shopinvader/demo/account_demo.xml +++ b/shopinvader/demo/account_demo.xml @@ -59,7 +59,12 @@ + + ShopInvader Analytic Plan + + ShopInvader Analytic + diff --git a/shopinvader/demo/email_demo.xml b/shopinvader/demo/email_demo.xml index 1427cd2433..3886f77898 100644 --- a/shopinvader/demo/email_demo.xml +++ b/shopinvader/demo/email_demo.xml @@ -3,67 +3,67 @@ Cart notification - ${(object.user_id.email or '')|safe} - Cart notification ${object.name} - ${object.partner_id.id} + {{ (object.user_id.email or '') }} + Cart notification {{ object.name }} + {{ object.partner_id.id }} - ${object.partner_id.lang} + {{ object.partner_id.lang }} Sale notification - ${(object.user_id.email or '')|safe} - Sale notification ${object.name} - ${object.partner_id.id} + {{ (object.user_id.email or '') }} + Sale notification {{ object.name }} + {{ object.partner_id.id }} - ${object.partner_id.lang} + {{ object.partner_id.lang }} Invoice notification - ${(object.user_id.email or '')|safe} - Invoice notification ${object.number} - ${object.partner_id.id} + {{ (object.user_id.email or '') }} + Invoice notification {{ object.name }} + {{ object.partner_id.id }} - ${object.partner_id.lang} + {{ object.partner_id.lang }} Welcome notification - ${(object.user_id.email or '')|safe} - Welcome notification ${object.name} - ${object.id} + {{ (object.user_id.email or '') }} + Welcome notification {{ object.name }} + {{ object.id }} - ${object.lang} + {{ object.lang }} Customer updated notification - ${(object.user_id.email or '')|safe} - Notification ${object.name} - Customer modified - ${object.id} + {{ (object.user_id.email or '') }} + Notification {{ object.name }} - Customer modified + {{ object.id }} - ${object.lang} + {{ object.lang }} Address created notification - ${(object.user_id.email or '')|safe} - Notification ${object.name} - Address created - ${object.id} + {{ (object.user_id.email or '') }} + Notification {{ object.name }} - Address created + {{ object.id }} - ${object.lang} + {{ object.lang }} @@ -71,12 +71,12 @@ Address updated notification - ${(object.user_id.email or '')|safe} - Notification ${object.name} - Address modified - ${object.id} + {{ (object.user_id.email or '') }} + Notification {{ object.name }} - Address modified + {{ object.id }} - ${object.lang} + {{ object.lang }} diff --git a/shopinvader/migrations/14.0.2.2.0/pre-migration.py b/shopinvader/migrations/14.0.2.2.0/pre-migration.py deleted file mode 100644 index a175962295..0000000000 --- a/shopinvader/migrations/14.0.2.2.0/pre-migration.py +++ /dev/null @@ -1,8 +0,0 @@ -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) - -from odoo.tools.sql import column_exists, rename_column - - -def migrate(cr, version): - if column_exists(cr, "res_partner", "shopinvader_enabled"): - rename_column(cr, "res_partner", "shopinvader_enabled", "is_shopinvader_active") diff --git a/shopinvader/migrations/14.0.4.0.0/post-migrate.py b/shopinvader/migrations/14.0.4.0.0/post-migrate.py deleted file mode 100644 index 4036d96981..0000000000 --- a/shopinvader/migrations/14.0.4.0.0/post-migrate.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2020 ACSONE SA/NV -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -import logging - -from odoo import SUPERUSER_ID, api - -_logger = logging.getLogger(__name__) - - -def migrate(cr, version): - _logger.info("Install module shopinvader_auth_api_key") - if not version: - return - - env = api.Environment(cr, SUPERUSER_ID, {}) - module = env["ir.module.module"].search( - [("name", "=", "shopinvader_auth_api_key"), ("state", "=", "uninstalled")] - ) - if module: - module.write({"state": "to install"}) - return diff --git a/shopinvader/migrations/14.0.4.1.0/post-migrate.py b/shopinvader/migrations/14.0.4.1.0/post-migrate.py deleted file mode 100644 index 1f808a7f47..0000000000 --- a/shopinvader/migrations/14.0.4.1.0/post-migrate.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2021 Camptocamp (http://www.camptocamp.com). -# @author Iván Todorovich -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import SUPERUSER_ID, api - - -def _fix_multicompany_ir_rules(env): - """Fix Multi-Company ir.rules - - These rules are set as noupdate=1, but they weren't migrated properly - as they don't account for multiple companies set in context. - """ - refs = [ - "shopinvader_backend_comp_rule", - "shopinvader_category_comp_rule", - "shopinvader_partner_comp_rule", - "shopinvader_product_comp_rule", - "shopinvader_variant_comp_rule", - ] - for ref in refs: - xmlid = "shopinvader.{}".format(ref) - rule = env.ref(xmlid, raise_if_not_found=False) - if not rule: - continue - rule.domain_force = """ - [ - '|', - ('company_id', '=', False), - ('company_id', 'in', company_ids), - ] - """ - - -def migrate(cr, version): - if not version: - return - with api.Environment.manage(): - env = api.Environment(cr, SUPERUSER_ID, {}) - _fix_multicompany_ir_rules(env) diff --git a/shopinvader/migrations/14.0.5.14.1/post-migrate.py b/shopinvader/migrations/14.0.5.14.1/post-migrate.py deleted file mode 100644 index 82b749f041..0000000000 --- a/shopinvader/migrations/14.0.5.14.1/post-migrate.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2022 ACSONE SA/NV () -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from odoo import SUPERUSER_ID, api - - -def _upgrade_shopinvader_model_access(env): - """ - Upgrade the "shopinvader partner binding edit" access rights - (as it's not update) - """ - sec_group = env.ref("shopinvader.group_shopinvader_partner_binding") - env.ref("shopinvader.access_shopinvader_partner_edit").write( - { - "group_id": sec_group.id, - } - ) - - -def migrate(cr, version): - if not version: - return - with api.Environment.manage(): - env = api.Environment(cr, SUPERUSER_ID, {}) - _upgrade_shopinvader_model_access(env) diff --git a/shopinvader/models/product_product.py b/shopinvader/models/product_product.py index 8c90bdbfde..81ae6adb6b 100644 --- a/shopinvader/models/product_product.py +++ b/shopinvader/models/product_product.py @@ -30,7 +30,7 @@ def _search_shopinvader_backend_ids(self, operator, value): "Shopinvader binded", compute="_compute_is_shopinvader_binded", store=True, - index=True, + index="btree", help="Technical field to know if this product is related by a" "(at least one) shopinvader backend", ) diff --git a/shopinvader/models/product_template.py b/shopinvader/models/product_template.py index 7e6333e1ba..7aa4d3ed12 100644 --- a/shopinvader/models/product_template.py +++ b/shopinvader/models/product_template.py @@ -37,13 +37,11 @@ def unlink(self): record.shopinvader_bind_ids.unlink() return super(ProductTemplate, self).unlink() - @api.model - def create(self, vals): + @api.model_create_multi + def create(self, vals_list): """Due to the order in which product.template, product.product, and bindings, are created, this is to handle the case in which a product.template + its bindings are created in one function call""" - result = super().create(vals) - bindings = result.shopinvader_bind_ids - if bindings: - bindings.active = True + result = super().create(vals_list) + result.mapped("shopinvader_bind_ids").active = True return result diff --git a/shopinvader/models/res_partner.py b/shopinvader/models/res_partner.py index 70bf99214e..3db14c9775 100644 --- a/shopinvader/models/res_partner.py +++ b/shopinvader/models/res_partner.py @@ -59,7 +59,7 @@ def _is_partner_duplicate_prevented(self): def _check_unique_email(self): if not self._is_partner_duplicate_prevented(): return True - self.env["res.partner"].flush(["email", "shopinvader_bind_ids"]) + self.env["res.partner"].flush_model(["email", "shopinvader_bind_ids"]) self.env.cr.execute( """ SELECT diff --git a/shopinvader/models/sale.py b/shopinvader/models/sale.py index 744a4ec17e..759c0ed8a6 100644 --- a/shopinvader/models/sale.py +++ b/shopinvader/models/sale.py @@ -129,5 +129,5 @@ class SaleOrderLine(models.Model): def reset_price_tax(self): for line in self: - line.product_id_change() - line._onchange_discount() + line._compute_tax_id() + line._compute_discount() diff --git a/shopinvader/models/shopinvader_backend.py b/shopinvader/models/shopinvader_backend.py index e584629def..483c0c65f7 100644 --- a/shopinvader/models/shopinvader_backend.py +++ b/shopinvader/models/shopinvader_backend.py @@ -551,10 +551,10 @@ def _send_notification(self, notification, record): ("notification_type", "=", notification), ] ) - description = _("Notify %s for %s,%s") % ( - notification, - record._name, - record.id, + description = _("Notify {notification} for {name},{id}").format( + notification=notification, + name=record._name, + id=record.id, ) for notif in notifs: notif.with_delay(description=description).send(record.id) diff --git a/shopinvader/models/shopinvader_binding.py b/shopinvader/models/shopinvader_binding.py index b8adcf23cd..bffe263b81 100644 --- a/shopinvader/models/shopinvader_binding.py +++ b/shopinvader/models/shopinvader_binding.py @@ -11,7 +11,7 @@ class ShopinvaderBinding(models.AbstractModel): backend_id = fields.Many2one("shopinvader.backend", string="Backend", required=True) company_id = fields.Many2one( - related="backend_id.company_id", store=True, index=True + related="backend_id.company_id", store=True, index="btree" ) external_id = fields.Char(string="External ID") sync_date = fields.Datetime(string="Last synchronization date") diff --git a/shopinvader/models/shopinvader_category.py b/shopinvader/models/shopinvader_category.py index fe1dced815..55aecf09d2 100644 --- a/shopinvader/models/shopinvader_category.py +++ b/shopinvader/models/shopinvader_category.py @@ -23,7 +23,7 @@ class ShopinvaderCategory(models.Model): "product.category", required=True, ondelete="cascade", - index=True, + index="btree", ) sequence = fields.Integer() meta_description = fields.Char() @@ -36,7 +36,7 @@ class ShopinvaderCategory(models.Model): "Shopinvader Parent", compute="_compute_parent_category", store=True, - index=True, + index="btree", compute_sudo=True, ) shopinvader_child_ids = fields.Many2many( @@ -44,13 +44,14 @@ class ShopinvaderCategory(models.Model): "Shopinvader Childs", compute="_compute_child_category", ) - level = fields.Integer(compute="_compute_level") + level = fields.Integer(compute="_compute_level", recursive=True) active = fields.Boolean( default=True, compute="_compute_active", store=True, readonly=False, ) + automatic_url_key = fields.Char(recursive=True) _sql_constraints = [ ( diff --git a/shopinvader/models/shopinvader_notification.py b/shopinvader/models/shopinvader_notification.py index 5025c4090f..be581197e4 100644 --- a/shopinvader/models/shopinvader_notification.py +++ b/shopinvader/models/shopinvader_notification.py @@ -13,7 +13,6 @@ class ShopinvaderNotification(models.Model): backend_id = fields.Many2one("shopinvader.backend", "Backend", required=False) notification_type = fields.Selection( selection="_selection_notification_type", - string="Notification Type", required=True, ) model_id = fields.Many2one("ir.model", "Model", required=True, ondelete="cascade") diff --git a/shopinvader/models/shopinvader_partner.py b/shopinvader/models/shopinvader_partner.py index b38e15e2aa..ae8fa28b93 100644 --- a/shopinvader/models/shopinvader_partner.py +++ b/shopinvader/models/shopinvader_partner.py @@ -72,10 +72,12 @@ def _compute_role(self): def _get_role(self): return self.backend_id.customer_default_role - @api.model - def create(self, vals): - vals = self._prepare_create_params(vals) - return super(ShopinvaderPartner, self).create(vals) + @api.model_create_multi + def create(self, vals_list): + new_vals_list = [] + for vals in vals_list: + new_vals_list.append(self._prepare_create_params(vals)) + return super(ShopinvaderPartner, self).create(new_vals_list) @api.model def _prepare_create_params(self, vals): diff --git a/shopinvader/models/shopinvader_product.py b/shopinvader/models/shopinvader_product.py index 1cbad2eadf..6343766312 100644 --- a/shopinvader/models/shopinvader_product.py +++ b/shopinvader/models/shopinvader_product.py @@ -16,7 +16,7 @@ class ShopinvaderProduct(models.Model): "product.template", required=True, ondelete="cascade", - index=True, + index="btree", check_company=True, ) meta_description = fields.Char() @@ -37,7 +37,6 @@ class ShopinvaderProduct(models.Model): related="backend_id.use_shopinvader_product_name", store=True ) shopinvader_name = fields.Char( - string="Shopinvader Name", help="Name for shopinvader, if not set the product name will be used.", ) shopinvader_display_name = fields.Char(compute="_compute_name", readonly=True) @@ -142,12 +141,13 @@ def _create_shopinvader_variant(self): shopinv_variants |= shopinv_variant_obj.create(vals) return shopinv_variants - @api.model - def create(self, vals): - binding = super(ShopinvaderProduct, self).create(vals) + @api.model_create_multi + def create(self, vals_list): + bindings = super(ShopinvaderProduct, self).create(vals_list) if self.env.context.get("map_children"): - binding._create_shopinvader_variant() - return binding + for binding in bindings: + binding._create_shopinvader_variant() + return bindings def _get_url_keywords(self): self.ensure_one() diff --git a/shopinvader/models/shopinvader_variant.py b/shopinvader/models/shopinvader_variant.py index 2fbe510120..f20979bfb0 100644 --- a/shopinvader/models/shopinvader_variant.py +++ b/shopinvader/models/shopinvader_variant.py @@ -27,14 +27,14 @@ class ShopinvaderVariant(models.Model): "shopinvader.product", required=True, ondelete="cascade", - index=True, + index="btree", check_company=True, ) tmpl_record_id = fields.Many2one( string="Product template", related="shopinvader_product_id.record_id", store=True, - index=True, + index="btree", check_company=True, ) record_id = fields.Many2one( @@ -42,7 +42,7 @@ class ShopinvaderVariant(models.Model): comodel_name="product.product", required=True, ondelete="cascade", - index=True, + index="btree", check_company=True, ) variant_count = fields.Integer( @@ -217,7 +217,7 @@ def _get_price( # Always filter taxes by the company taxes = product.taxes_id.filtered(lambda tax: tax.company_id == company) # Apply fiscal position - taxes = fposition.map_tax(taxes, product) if fposition else taxes + taxes = fposition.map_tax(taxes) if fposition else taxes # Set context. Some of the methods used here depend on these values product_context = dict( self.env.context, @@ -228,9 +228,11 @@ def _get_price( ) product = product.with_context(**product_context) pricelist = pricelist.with_context(**product_context) if pricelist else None - # If we have a pricelist, use product.price as it already accounts - # for pricelist rules and quantity (in context) - price_unit = product.price if pricelist else product.lst_price + price_unit = ( + pricelist._get_product_price(product, qty, date=date) + if pricelist + else product.lst_price + ) price_unit = AccountTax._fix_tax_included_price_company( price_unit, product.taxes_id, taxes, company ) @@ -246,26 +248,12 @@ def _get_price( # Handle pricelists.discount_policy == "without_discount" if pricelist and pricelist.discount_policy == "without_discount": # Get the price rule - price_unit, rule_id = pricelist.get_product_price_rule( - product, qty, None, date=date + price_unit, rule_id = pricelist._get_product_price_rule( + product, qty, date=date ) # Get the price before applying the pricelist - SaleOrderLine = self.env["sale.order.line"].with_context(**product_context) - original_price_unit, currency = SaleOrderLine._get_real_price_currency( - product, rule_id, qty, product.uom_id, pricelist.id - ) + original_price_unit = product.lst_price price_dp = self.env["decimal.precision"].precision_get("Product Price") - # Convert currency if necessary - if ( - not float_is_zero(original_price_unit, precision_digits=price_dp) - and pricelist.currency_id != currency - ): - original_price_unit = currency._convert( - original_price_unit, - pricelist.currency_id, - company or self.env.company, - fields.Date.today(), - ) # Compute discount if not float_is_zero( original_price_unit, precision_digits=price_dp @@ -297,7 +285,11 @@ def _compute_main_product(self): # Respect same order. order_by = [x.strip() for x in self.env["product.product"]._order.split(",")] backends = self.mapped("backend_id") - fields_to_read = ["shopinvader_product_id", "backend_id", "lang_id"] + order_by + fields_to_read = [ + "shopinvader_product_id", + "backend_id", + "lang_id", + ] + [f.split(" ")[0] for f in order_by] product_ids = self.mapped("shopinvader_product_id").ids # Use sudo to bypass permissions (we don't care) _variants = self.sudo().search( @@ -315,22 +307,23 @@ def _compute_main_product(self): ) def pick_1st_variant(variants): - # NOTE: if the order is changed by adding `asc/desc` this can be broken - # but it's very unlikely that the default order for product.product - # will be changed. def get_value(record, key): if record[key] is False and self._fields[key].type in ("char", "text"): return "" else: return record[key] - ordered = sorted( - variants, key=lambda var: [get_value(var, x) for x in order_by] - ) - return ordered[0].get("id") if ordered else None + for order_key in reversed(order_by): + order_key_split = order_key.split(" ") + reverse = len(order_key_split) > 1 and order_key_split[1] == "desc" + variants.sort( + key=lambda var: get_value(var, order_key_split[0]), + reverse=reverse, + ) + return variants[0].get("id") if variants else None main_by_product = { - product: pick_1st_variant(tuple(variants)) + product: pick_1st_variant(list(variants)) for product, variants in var_by_product } for record in self: diff --git a/shopinvader/services/abstract_download.py b/shopinvader/services/abstract_download.py index b672633318..381811c628 100644 --- a/shopinvader/services/abstract_download.py +++ b/shopinvader/services/abstract_download.py @@ -69,10 +69,10 @@ def _get_binary_content(self, target, params=None): target_report_def = self._get_report_action(target, params=params) report_name = target_report_def.get("report_name") report_type = target_report_def.get("report_type") - report = self._get_report(report_name, report_type) - content, extension = report._render( - target.ids, data={"report_type": report_type} + content, extension = self.env["ir.actions.report"]._render( + report_name, target.ids, data={"report_type": report_type} ) + report = self._get_report(report_name, report_type) filename = self._get_binary_content_filename( target, report, extension, params=params ) diff --git a/shopinvader/services/cart.py b/shopinvader/services/cart.py index 6bc7770506..d48c160ce6 100644 --- a/shopinvader/services/cart.py +++ b/shopinvader/services/cart.py @@ -243,7 +243,7 @@ def _upgrade_cart_item_quantity(self, cart, item, params, action="replace"): cart._cache["order_line"] = tuple(real_line_ids) vals.update(new_values) item.order_id.write({"order_line": [(1, item.id, vals)]}) - cart.recompute() + cart.flush_recordset() def _do_clear_cart_cancel(self, cart): """ @@ -304,7 +304,7 @@ def _add_item(self, cart, params): else: with self.env.norecompute(): item = self._create_cart_line(cart, params) - cart.recompute() + cart.flush_recordset() return item def _create_cart_line(self, cart, params): @@ -513,6 +513,7 @@ def _prepare_cart_item(self, params, cart): "product_uom_qty": params["item_qty"], "order_id": cart.id, "sequence": max(cart.order_line.mapped("sequence"), default=0) + 1, + "currency_id": cart.currency_id.id, } ) return _params diff --git a/shopinvader/services/sale.py b/shopinvader/services/sale.py index c5d2ef4521..3ff515d2de 100644 --- a/shopinvader/services/sale.py +++ b/shopinvader/services/sale.py @@ -142,7 +142,7 @@ def _is_cancel_allowed(self, sale): def _cancel(self, sale, reset_to_cart=False): if not self._is_cancel_allowed(sale): raise UserError(_("This order cannot be cancelled")) - sale.action_cancel() + sale.with_context(disable_cancel_warning=True).action_cancel() if reset_to_cart: sale.action_draft() sale.typology = "cart" diff --git a/shopinvader/services/service.py b/shopinvader/services/service.py index 703ce0be94..c98eaf729a 100644 --- a/shopinvader/services/service.py +++ b/shopinvader/services/service.py @@ -97,7 +97,11 @@ def _scope_to_domain(self, scope): domain.append((key, op, value)) return domain except Exception as e: - raise UserError(_("Invalid scope %s, error: %s") % (str(scope), str(e))) + raise UserError( + _("Invalid scope {scope}, error: {error}").format( + scope=str(scope), error=str(e) + ) + ) from e # Validator def _default_validator_search(self): @@ -171,7 +175,9 @@ def _get(self, _id): record = self._exposed_model.search(domain) if not record: raise MissingError( - _("The record %s %s does not exist") % (self._expose_model, _id) + _("The record {model} {id} does not exist").format( + model=self._expose_model, id=_id + ) ) else: return record diff --git a/shopinvader/tests/common.py b/shopinvader/tests/common.py index f45c545277..ae117191df 100644 --- a/shopinvader/tests/common.py +++ b/shopinvader/tests/common.py @@ -4,12 +4,11 @@ # pylint: disable=method-required-super from contextlib import contextmanager - -import mock +from unittest import mock from odoo import fields from odoo.exceptions import MissingError -from odoo.tests import SavepointCase +from odoo.tests import TransactionCase from odoo.addons.base_rest.controllers.main import RestController from odoo.addons.base_rest.core import _rest_controllers_per_module @@ -137,7 +136,7 @@ def _perform_created_job(self): self._perform_job(job) -class CommonCase(SavepointCase, CommonMixin): +class CommonCase(TransactionCase, CommonMixin): # by default disable tracking suite-wise, it's a time saver :) tracking_disable = True @@ -160,7 +159,7 @@ class ControllerTest(RestController): # TODO FIXME # It seem that setUpComponent / setUpRegistry loose stuff from # the cache so we do an explicit flush here to avoid losing data - cls.env["base"].flush() + cls.env["base"].flush_model() cls.setUpComponent() cls.setUpRegistry() @@ -170,7 +169,7 @@ def tearDownClass(cls): _rest_controllers_per_module["shopinvader"] = [] def setUp(self): - SavepointCase.setUp(self) + TransactionCase.setUp(self) CommonMixin.setUp(self) shopinvader_response.set_testmode(True) @@ -310,12 +309,12 @@ def _make_payment(self, invoice): """ self._ensure_posted(invoice) ctx = {"active_ids": invoice.ids, "active_model": "account.move"} - wizard_obj = self.register_payments_obj.with_context(ctx) + wizard_obj = self.register_payments_obj.with_context(**ctx) register_payments = wizard_obj.create( { "payment_date": fields.Date.today(), "journal_id": self.bank_journal_euro.id, - "payment_method_id": self.payment_method_manual_in.id, + "payment_method_line_id": self.payment_method_line_manual_in.id, } ) register_payments._create_payments() diff --git a/shopinvader/tests/test_cart.py b/shopinvader/tests/test_cart.py index eeb0a752dc..85d89a3b1b 100644 --- a/shopinvader/tests/test_cart.py +++ b/shopinvader/tests/test_cart.py @@ -1,6 +1,7 @@ # Copyright 2017 Akretion (http://www.akretion.com). # @author Sébastien BEAU # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from datetime import timedelta from odoo import fields from odoo.tools import mute_logger @@ -186,7 +187,7 @@ def test_cart_pricelist_apply(self): "price_surcharge": -100, "base_pricelist_id": first_pricelist.id, "date_start": fields.Date.today(), - "date_end": fields.Date.today(), + "date_end": fields.Date.today() + timedelta(days=1), }, ) ], @@ -456,7 +457,7 @@ def test_cart_misc_data_update(self): def test_writing_note(self): res = self.service.dispatch("update", params={"note": "FOO"}) self.assertIn("note", res["data"]) - self.assertEqual("FOO", res["data"]["note"]) + self.assertEqual("

FOO

", res["data"]["note"]) class ConnectedCartNoTaxCase(CartCase): diff --git a/shopinvader/tests/test_cart_item.py b/shopinvader/tests/test_cart_item.py index 64a0d74780..bf90751239 100644 --- a/shopinvader/tests/test_cart_item.py +++ b/shopinvader/tests/test_cart_item.py @@ -170,18 +170,18 @@ def test_pricelist_product_price_unit_without_discount(self): self.pricelist.discount_policy = "without_discount" amount = self._test_pricelist_product() # into the cart, the price must be the price without discount - self.assertEqual(amount["price"], 16.5) + self.assertEqual(amount["price"], 33) # but the total for the line into the cart info must be the price with # discount - self.assertEqual(amount["total"], 14.85) + self.assertEqual(amount["total"], 29.7) def test_pricelist_product_price_unit_with_discount(self): self.pricelist.discount_policy = "with_discount" amount = self._test_pricelist_product() # into the cart, the price must be the price with discount - self.assertEqual(amount["price"], 14.85) + self.assertEqual(amount["price"], 29.7) # same for the total - self.assertEqual(amount["total"], 14.85) + self.assertEqual(amount["total"], 29.7) def test_upgrade_last_update_date(self): last_external_update_date = self._get_last_external_update_date(self.cart) diff --git a/shopinvader/tests/test_invoice.py b/shopinvader/tests/test_invoice.py index 1d1528a9f7..c90b5beed4 100644 --- a/shopinvader/tests/test_invoice.py +++ b/shopinvader/tests/test_invoice.py @@ -15,6 +15,9 @@ def setUpClass(cls): cls.payment_method_manual_in = cls.env.ref( "account.account_payment_method_manual_in" ) + cls.payment_method_line_manual_in = cls.env[ + "account.payment.method.line" + ].search([("payment_method_id", "=", cls.payment_method_manual_in.id)], limit=1) cls.bank_journal_euro = cls.journal_obj.create( {"name": "Bank", "type": "bank", "code": "BNK627"} ) diff --git a/shopinvader/tests/test_product.py b/shopinvader/tests/test_product.py index 7ba0488256..ad041506d1 100644 --- a/shopinvader/tests/test_product.py +++ b/shopinvader/tests/test_product.py @@ -66,7 +66,7 @@ def test_product_price_rounding(self): "percent_price": 50, } ) - self.variant.price = 423.4 + self.variant.list_price = 423.4 self.assertEqual(self.shopinvader_variant.price["default"]["value"], 211.70) @contextmanager @@ -79,7 +79,7 @@ def _check_url(self, shopinvader_variants): shopinv_variant_names = {r: r.name for r in shopinvader_variants} shopinv_variant_urls = {r: r.url_url_ids for r in shopinvader_variants} yield - shopinvader_variants.refresh() + shopinvader_variants.invalidate_recordset() for shopinv_variant in shopinvader_variants: existing_urls = shopinv_variant_urls.get(shopinv_variant) new_url = shopinv_variant.url_url_ids.filtered( @@ -342,7 +342,7 @@ def test_product_category_with_two_lang(self): lang = self._install_lang("base.lang_fr") product = self.env.ref("product.product_product_4") product.with_context(lang="fr_FR").name = "Bureau Personnalisable" - product.flush() + product.flush_recordset() self.backend.lang_ids |= lang self.backend.bind_all_category() self.backend.bind_all_product() @@ -353,7 +353,7 @@ def test_product_category_with_two_lang(self): if binding.lang_id.code == "fr_FR": self.assertEqual(binding.url_key, "bureau-personnalisable") elif binding.lang_id.code == "en_US": - self.assertEqual(binding.url_key, "customizable-desk-config") + self.assertEqual(binding.url_key, "customizable-desk") def test_create_product_binding1(self): """ @@ -770,11 +770,11 @@ def test_product_url1(self): self.assertEqual(urls.model_id, bind_product) bind_product.write({"active": False}) - bind_product.flush() + bind_product.flush_recordset() self.assertEqual(urls.model_id, bind_categ) bind_product.write({"active": True}) - bind_product.flush() + bind_product.flush_recordset() self.assertEqual(urls.model_id, bind_product) def test_product_url2(self): @@ -834,11 +834,11 @@ def test_product_url2(self): self.assertEqual(urls.model_id, bind_product) bind_product.write({"active": False}) - bind_product.flush() + bind_product.flush_recordset() self.assertEqual(urls.model_id, bind_categ2) bind_product.write({"active": True}) - bind_product.flush() + bind_product.flush_recordset() self.assertEqual(urls.model_id, bind_product) @contextmanager @@ -962,7 +962,8 @@ def test_main_product(self): # change order tmpl.product_variant_ids[0].default_code = "ZZZZZZZ" tmpl.product_variant_ids[0].name = "ZZZZZZ" - tmpl.product_variant_ids.invalidate_cache() + tmpl.product_variant_ids.invalidate_recordset() + invader_variants.invalidate_recordset() main_variant1 = tmpl.product_variant_ids[0] self.assertNotEqual(main_variant, main_variant1) self.assertTrue( diff --git a/shopinvader/tests/test_product_filter.py b/shopinvader/tests/test_product_filter.py index 00fc1186a5..9421cd3378 100644 --- a/shopinvader/tests/test_product_filter.py +++ b/shopinvader/tests/test_product_filter.py @@ -86,7 +86,7 @@ def test_product_filter_field_constrains(self): with self.assertRaises(exceptions.UserError) as err: self.filter_on_field.write({"field_id": False}) self.assertEqual( - err.exception.name, + err.exception.args[0], "Product filter ID=%d is based on field: " "requires a field!" % self.filter_on_field.id, ) @@ -95,7 +95,7 @@ def test_product_filter_attr_constrains(self): with self.assertRaises(exceptions.UserError) as err: self.filter_on_attr.write({"variant_attribute_id": False}) self.assertEqual( - err.exception.name, + err.exception.args[0], "Product filter ID=%d is based on variant attribute: " "requires an attribute!" % self.filter_on_attr.id, ) diff --git a/shopinvader/tests/test_res_partner.py b/shopinvader/tests/test_res_partner.py index bb2661ad1d..87ea1fe94b 100644 --- a/shopinvader/tests/test_res_partner.py +++ b/shopinvader/tests/test_res_partner.py @@ -5,10 +5,10 @@ from odoo.exceptions import ValidationError -from odoo.addons.component.tests.common import SavepointComponentCase +from odoo.addons.component.tests.common import TransactionComponentCase -class TestResPartner(SavepointComponentCase): +class TestResPartner(TransactionComponentCase): @classmethod def setUpClass(cls): super(TestResPartner, cls).setUpClass() diff --git a/shopinvader/tests/test_sale.py b/shopinvader/tests/test_sale.py index 1fa2df6979..e6b6e455a6 100644 --- a/shopinvader/tests/test_sale.py +++ b/shopinvader/tests/test_sale.py @@ -20,6 +20,9 @@ def setUpClass(cls): cls.payment_method_manual_in = cls.env.ref( "account.account_payment_method_manual_in" ) + cls.payment_method_line_manual_in = cls.env[ + "account.payment.method.line" + ].search([("payment_method_id", "=", cls.payment_method_manual_in.id)], limit=1) cls.bank_journal_euro = cls.journal_obj.create( {"name": "Bank", "type": "bank", "code": "BNK6278"} ) diff --git a/shopinvader/tests/test_sale_cancel.py b/shopinvader/tests/test_sale_cancel.py index eb64de07d3..02eaa34657 100644 --- a/shopinvader/tests/test_sale_cancel.py +++ b/shopinvader/tests/test_sale_cancel.py @@ -21,7 +21,7 @@ def test_sale_cancel_fail_if_delivered(self): self.sale.action_confirm() # deliver only 1 line self.sale.order_line[0].write({"qty_delivered": 1}) - self.env["sale.order.line"].flush(records=self.sale.order_line) + self.sale.order_line.flush_recordset() with self.assertRaises(UserError): self.service.dispatch("cancel", self.sale.id) self.assertEqual("sale", self.sale.typology) diff --git a/shopinvader/tests/test_settings.py b/shopinvader/tests/test_settings.py index bbe7e7a1c5..353bbaa40e 100644 --- a/shopinvader/tests/test_settings.py +++ b/shopinvader/tests/test_settings.py @@ -19,7 +19,7 @@ "Professor", ) EXPECTED_GET_INDUSTRY = ( - "Administrative", + "Administrative/Utilities", "Agriculture", "Construction", "Education", @@ -27,7 +27,7 @@ "Entertainment", "Extraterritorial", "Finance/Insurance", - "Food", + "Food/Hospitality", "Health/Social", "Households", "IT/Communication", @@ -37,7 +37,7 @@ "Public Administration", "Real Estate", "Scientific", - "Transportation", + "Transportation/Logistics", "Water supply", "Wholesale/Retail", ) diff --git a/shopinvader/tests/test_shopinvader_category.py b/shopinvader/tests/test_shopinvader_category.py index c49f0f87d0..5039cab10a 100644 --- a/shopinvader/tests/test_shopinvader_category.py +++ b/shopinvader/tests/test_shopinvader_category.py @@ -1,11 +1,11 @@ # Copyright 2020 Camptocamp (http://www.camptocamp.com). # @author Simone Orsi -from odoo.addons.component.tests.common import SavepointComponentCase +from odoo.addons.component.tests.common import TransactionComponentCase from .common import CommonMixin -class TestShopinvaderCategoryBase(SavepointComponentCase, CommonMixin): +class TestShopinvaderCategoryBase(TransactionComponentCase, CommonMixin): @classmethod def setUpClass(cls): super(TestShopinvaderCategoryBase, cls).setUpClass() diff --git a/shopinvader/tests/test_shopinvader_partner.py b/shopinvader/tests/test_shopinvader_partner.py index dc78f107cd..4a8f151edb 100644 --- a/shopinvader/tests/test_shopinvader_partner.py +++ b/shopinvader/tests/test_shopinvader_partner.py @@ -7,10 +7,10 @@ from odoo.tools import mute_logger -from odoo.addons.component.tests.common import SavepointComponentCase +from odoo.addons.component.tests.common import TransactionComponentCase -class TestShopinvaderPartner(SavepointComponentCase): +class TestShopinvaderPartner(TransactionComponentCase): @classmethod def setUpClass(cls): super(TestShopinvaderPartner, cls).setUpClass() @@ -124,7 +124,7 @@ def test_partner_no_duplicate_child(self): } ) self.assertEqual(partner, binding.record_id) - partner.refresh() + partner.invalidate_recordset() self.assertTrue(partner.child_ids) self.assertEqual(1, len(partner.child_ids)) child = partner.child_ids diff --git a/shopinvader/tests/test_shopinvader_partner_binding.py b/shopinvader/tests/test_shopinvader_partner_binding.py index becc5d1a93..9f21a497c5 100644 --- a/shopinvader/tests/test_shopinvader_partner_binding.py +++ b/shopinvader/tests/test_shopinvader_partner_binding.py @@ -31,7 +31,7 @@ def test_binding1(self): "active_model": self.partner._name, } ) - wizard_obj = self.binding_wiz_obj.with_context(context) + wizard_obj = self.binding_wiz_obj.with_context(**context) fields_list = wizard_obj.fields_get().keys() values = wizard_obj.default_get(fields_list) values.update({"shopinvader_backend_id": self.backend.id}) @@ -40,7 +40,7 @@ def test_binding1(self): wizard.binding_lines.write({"bind": False}) with self.assertRaises(exceptions.UserError) as e: wizard.action_apply() - self.assertIn("unbind is not implemented", e.exception.name) + self.assertIn("unbind is not implemented", e.exception.args[0]) shopinv_partner = self.partner._get_invader_partner(self.backend) # As we set bind = False, we check if the binding is not executed. self.assertFalse(shopinv_partner) diff --git a/shopinvader/tests/test_shopinvader_variant_binding_wizard.py b/shopinvader/tests/test_shopinvader_variant_binding_wizard.py index e648b2b2df..bf44549ed7 100644 --- a/shopinvader/tests/test_shopinvader_variant_binding_wizard.py +++ b/shopinvader/tests/test_shopinvader_variant_binding_wizard.py @@ -2,12 +2,12 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo.tools import mute_logger -from odoo.addons.component.tests.common import SavepointComponentCase +from odoo.addons.component.tests.common import TransactionComponentCase @mute_logger("odoo.models.unlink") @mute_logger("odoo.addons.base.models.ir_model") -class TestShopinvaderVariantBindingWizard(SavepointComponentCase): +class TestShopinvaderVariantBindingWizard(TransactionComponentCase): @classmethod def setUpClass(cls): super(TestShopinvaderVariantBindingWizard, cls).setUpClass() diff --git a/shopinvader/tests/test_shopinvader_variant_seo_title.py b/shopinvader/tests/test_shopinvader_variant_seo_title.py index 5bbd69eb7a..6c98ac0bea 100644 --- a/shopinvader/tests/test_shopinvader_variant_seo_title.py +++ b/shopinvader/tests/test_shopinvader_variant_seo_title.py @@ -46,7 +46,7 @@ def test_public_name_empty(self): self.assertEqual(variant.seo_title, title) self.assertEqual(variant.manual_seo_title, title) # Invalidate cache to ensure data stay in memory - self.shopinvader_variants.invalidate_cache() + self.shopinvader_variants.invalidate_recordset() self._check_expected_seo_name(self.backend, self.shopinvader_variants) def test_public_name_normal(self): diff --git a/shopinvader/tests/test_utils.py b/shopinvader/tests/test_utils.py index 799c2f4df6..89a19f7929 100644 --- a/shopinvader/tests/test_utils.py +++ b/shopinvader/tests/test_utils.py @@ -2,7 +2,7 @@ # @author Simone Orsi # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import mock +from unittest import mock from odoo.addons.shopinvader import utils # pylint: disable=W7950 diff --git a/shopinvader/views/product_filter_view.xml b/shopinvader/views/product_filter_view.xml index 65fc096873..42d1e9fbde 100644 --- a/shopinvader/views/product_filter_view.xml +++ b/shopinvader/views/product_filter_view.xml @@ -32,7 +32,7 @@ product.filter - + diff --git a/shopinvader/views/shopinvader_cart_step_view.xml b/shopinvader/views/shopinvader_cart_step_view.xml index 9ff56e4cd4..b0f5849cba 100644 --- a/shopinvader/views/shopinvader_cart_step_view.xml +++ b/shopinvader/views/shopinvader_cart_step_view.xml @@ -4,7 +4,7 @@ shopinvader.cart.step - + diff --git a/shopinvader/views/shopinvader_category_view.xml b/shopinvader/views/shopinvader_category_view.xml index d725123132..58bd7a72a7 100644 --- a/shopinvader/views/shopinvader_category_view.xml +++ b/shopinvader/views/shopinvader_category_view.xml @@ -74,7 +74,7 @@ shopinvader.category - + diff --git a/shopinvader/views/shopinvader_partner_view.xml b/shopinvader/views/shopinvader_partner_view.xml index 6eb81aff88..64f4d1cbfe 100644 --- a/shopinvader/views/shopinvader_partner_view.xml +++ b/shopinvader/views/shopinvader_partner_view.xml @@ -23,6 +23,7 @@ + shopinvader.partner - + + shopinvader.variant.tree (in shopinvader) shopinvader.variant - + @@ -105,6 +105,7 @@ /> +