diff --git a/sale_channel/models/sale_channel.py b/sale_channel/models/sale_channel.py index 4c3d4626..ff7448a9 100644 --- a/sale_channel/models/sale_channel.py +++ b/sale_channel/models/sale_channel.py @@ -21,15 +21,17 @@ class SaleChannel(models.Model): def _scheduler_export(self): for record in self: for struct_key in record._get_struct_to_export(): - for items in record._get_items_to_export(struct_key): - description = "Export {} from {} whose id is {}".format( - items, + for i, items in enumerate(record._get_items_to_export(struct_key)): + key = f"{i}_export_items_{struct_key}_for_channel_{record.id}" + description = "Export part {} from {}({}) whose id is {}".format( + i, record.name, + struct_key, record.id, ) - record.with_delay(description=description)._job_trigger_export( - struct_key, items - ) + record.with_delay( + description=description, identity_key=key + )._job_trigger_export(struct_key, items) def _job_trigger_export(self, struct_key, items): """ @@ -44,7 +46,7 @@ def _get_struct_to_export(self): """ Retrieves the item types to export """ - NotImplementedError("Something is missing") + return [] def _map_items(self, struct_key, items): """ @@ -64,7 +66,7 @@ def _get_items_to_export(self, struct_key): :return: A list of several lists of Odoo objects to export split according to a predefined size """ - raise NotImplementedError("Nothing found to export with %s" % struct_key) + return [] def _trigger_export(self, struct_key, mapped_items): """ @@ -79,19 +81,20 @@ def _trigger_export(self, struct_key, mapped_items): @abstractmethod def _scheduler_import(self): for record in self: - for struct_key in record._get_struct_to_import(): + for i, struct_key in enumerate(record._get_struct_to_import()): + key = f"{i}import_{struct_key}_for_channel{record.id}" description = "Import {} from {} whose id is {}".format( struct_key, record.name, record.id, ) - record.with_delay(description=description)._job_trigger_import( - struct_key - ) + record.with_delay( + description=description, identity_key=key + )._job_trigger_import(struct_key) def _get_struct_to_import(self): """Retrieves the item types to import""" - NotImplementedError("Something is missing") + return [] def _job_trigger_import(self, struct_key): """ diff --git a/sale_channel/models/sale_channel_relation.py b/sale_channel/models/sale_channel_relation.py index 0ed5cd7c..4b73ccc2 100644 --- a/sale_channel/models/sale_channel_relation.py +++ b/sale_channel/models/sale_channel_relation.py @@ -17,3 +17,7 @@ class SaleChannelRelation(models.AbstractModel): sale_channel_sync_date = fields.Datetime( help="Date of last import sync for the related record" ) + + sale_channel_id = fields.Many2one( + comodel_name="sale.channel", string="sale channel", required=True + ) diff --git a/sale_channel/models/sale_order_sale_channel_rel.py b/sale_channel/models/sale_order_sale_channel_rel.py index 40abffb8..cb2724a1 100644 --- a/sale_channel/models/sale_order_sale_channel_rel.py +++ b/sale_channel/models/sale_order_sale_channel_rel.py @@ -7,8 +7,14 @@ class SaleOrderSaleChannelRel(models.Model): _inherit = "sale.channel.relation" _description = " sale order sale channel relation" - sale_channel_id = fields.Many2one( - comodel_name="sale.channel", string="sale channel" + sale_order_id = fields.Many2one( + comodel_name="sale.order", string="sale order", required=True ) - sale_order_id = fields.Many2one(comodel_name="sale.order", string="sale order") + _sql_constraints = [ + ( + "unique_sale_order_sale_channel", + "unique(sale_channel_id, sale_order_id)", + "The combination of Sale Channel and Sale Order must be unique", + ) + ] diff --git a/sale_channel_account/models/__init__.py b/sale_channel_account/models/__init__.py index d7234b58..61877425 100644 --- a/sale_channel_account/models/__init__.py +++ b/sale_channel_account/models/__init__.py @@ -1,4 +1 @@ -from . import account_fiscal_position -from . import account_analytic_account -from . import account_payment_mode from . import sale_channel diff --git a/sale_channel_account/models/account_analytic_account.py b/sale_channel_account/models/account_analytic_account.py deleted file mode 100644 index 5a799397..00000000 --- a/sale_channel_account/models/account_analytic_account.py +++ /dev/null @@ -1,9 +0,0 @@ -from odoo import fields, models - - -class AccountAnalyticAccount(models.Model): - _inherit = "account.analytic.account" - - sale_channel_ids = fields.One2many( - comodel_name="sale.channel", inverse_name="analytic_account_id" - ) diff --git a/sale_channel_account/models/account_fiscal_position.py b/sale_channel_account/models/account_fiscal_position.py deleted file mode 100644 index 31da4f00..00000000 --- a/sale_channel_account/models/account_fiscal_position.py +++ /dev/null @@ -1,11 +0,0 @@ -from odoo import fields, models - - -class AccountFiscalPosition(models.Model): - _name = "account.fiscal.position" - _inherit = [_name, "sale.channel.owner"] - - channel_ids = fields.Many2many( - comodel_name="sale.channel", - string="Binded Sale Channels", - ) diff --git a/sale_channel_account/models/account_payment_mode.py b/sale_channel_account/models/account_payment_mode.py deleted file mode 100644 index 71ffa699..00000000 --- a/sale_channel_account/models/account_payment_mode.py +++ /dev/null @@ -1,9 +0,0 @@ -from odoo import fields, models - - -class AccountPaymentMode(models.Model): - _inherit = "account.payment.mode" - - sale_channel_ids = fields.One2many( - comodel_name="sale.channel", inverse_name="payment_mode_id" - ) diff --git a/sale_channel_account/models/sale_channel.py b/sale_channel_account/models/sale_channel.py index c6b3abdc..eaebb3c9 100644 --- a/sale_channel_account/models/sale_channel.py +++ b/sale_channel_account/models/sale_channel.py @@ -11,8 +11,6 @@ class SaleChannel(models.Model): analytic_account_id = fields.Many2one( comodel_name="account.analytic.account", string="Analytic account", - help="If specified, this analytic account will be used to fill the " - "field on the sale order created by the connector.", ) payment_mode_id = fields.Many2one( diff --git a/sale_channel_mirakl/mirakl_mapper/country_builder.py b/sale_channel_mirakl/mirakl_mapper/country_builder.py deleted file mode 100644 index cef13e8c..00000000 --- a/sale_channel_mirakl/mirakl_mapper/country_builder.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel - - -class CountryBuilder(BaseModel): - shipping_zone_code: Optional[str] = None # not required, Can be None - - def build_country(self, sale_channel): - country = super().build_country(sale_channel) - if self.shipping_zone_code: - country = ( - sale_channel.env["res.country"].search( - [("code", "=", self.shipping_zone_code)], - limit=1, - ) - or country - ) - return country diff --git a/sale_channel_mirakl/mirakl_mapper/mirakl_billing_address.py b/sale_channel_mirakl/mirakl_mapper/mirakl_billing_address.py index be33df0e..ab9bd640 100644 --- a/sale_channel_mirakl/mirakl_mapper/mirakl_billing_address.py +++ b/sale_channel_mirakl/mirakl_mapper/mirakl_billing_address.py @@ -5,3 +5,7 @@ class MiraklBillingAddress(MiraklImportMapper, MiraklPartnerAddress): customer_notification_email: str = "" + + def build_country(self, sale_channel): + country = sale_channel.default_country_id + return country diff --git a/sale_channel_mirakl/mirakl_mapper/mirakl_catalog.py b/sale_channel_mirakl/mirakl_mapper/mirakl_catalog.py index c336bada..c46c3514 100644 --- a/sale_channel_mirakl/mirakl_mapper/mirakl_catalog.py +++ b/sale_channel_mirakl/mirakl_mapper/mirakl_catalog.py @@ -14,12 +14,6 @@ class MiraklCatalog(MiraklExportMapper): @classmethod def map_item(cls, mirakl_channel, product): - """ - Build a mirakl record from an odoo record - :param mirakl_channel: Mirakl channel on which the product is attached - :param product: product to map - :return: a pydantic object corresponding to the form expected by mirakl - """ cat = product.categ_id.display_name or "" cat = cat.replace( "/", ">" diff --git a/sale_channel_mirakl/mirakl_mapper/mirakl_customer.py b/sale_channel_mirakl/mirakl_mapper/mirakl_customer.py index 4082f054..606b0419 100644 --- a/sale_channel_mirakl/mirakl_mapper/mirakl_customer.py +++ b/sale_channel_mirakl/mirakl_mapper/mirakl_customer.py @@ -1,17 +1,19 @@ -from .country_builder import CountryBuilder +from typing import Optional + from .mirakl_billing_address import MiraklBillingAddress from .mirakl_import_mapper import MiraklImportMapper from .mirakl_shipping_address import MiraklShippingAddress from .res_partner_builder import ResPartnerBuilder -class MiraklCustomer(MiraklImportMapper, ResPartnerBuilder, CountryBuilder): +class MiraklCustomer(MiraklImportMapper, ResPartnerBuilder): _odoo_model = "res.partner" _identity_key = "customer_id" billing_address: MiraklBillingAddress locale: str = "" shipping_address: MiraklShippingAddress + shipping_zone_code: Optional[str] = None def __init__(self, **kwargs): """ @@ -30,15 +32,3 @@ def __init__(self, **kwargs): shipping_address["customer_id"] = kwargs.get("customer_id", "") + "_shipping" kwargs["shipping_address"] = shipping_address super().__init__(**kwargs) - - def build_country(self, sale_channel): - country = super().build_country(sale_channel) - if self.shipping_zone_code: - country = ( - sale_channel.env["res.country"].search( - [("code", "=", self.shipping_zone_code)], - limit=1, - ) - or country - ) - return country diff --git a/sale_channel_mirakl/mirakl_mapper/mirakl_export_mapper.py b/sale_channel_mirakl/mirakl_mapper/mirakl_export_mapper.py index 75e51f10..d1e810f0 100644 --- a/sale_channel_mirakl/mirakl_mapper/mirakl_export_mapper.py +++ b/sale_channel_mirakl/mirakl_mapper/mirakl_export_mapper.py @@ -4,6 +4,8 @@ class MiraklExportMapper(BaseModel): + _identity_key = None + def get_key(self): return getattr(self, self._identity_key, "") @@ -14,6 +16,15 @@ def to_json(self): as keys and the appropriate values to add to the export file """ + @classmethod + def map_item(cls, mirakl_channel, product): + """ + Build a mirakl record from an odoo record + :param mirakl_channel: Mirakl channel on which the product is attached + :param product: product to map + :return: a pydantic object corresponding to the form expected by mirakl + """ + @classmethod def get_file_header(cls): """ diff --git a/sale_channel_mirakl/mirakl_mapper/mirakl_offer.py b/sale_channel_mirakl/mirakl_mapper/mirakl_offer.py index 73641bd7..82a29d74 100644 --- a/sale_channel_mirakl/mirakl_mapper/mirakl_offer.py +++ b/sale_channel_mirakl/mirakl_mapper/mirakl_offer.py @@ -10,12 +10,6 @@ class MiraklOffer(MiraklExportMapper): @classmethod def map_item(cls, mirakl_channel, product): - """ - Build a mirakl record from an odoo record - :param mirakl_channel: Mirakl channel on which the product is attached - :param product: product to map - :return: a pydantic object corresponding to the form expected by mirakl - """ relation_prod_channel = product.product_tmpl_id.prod_sale_channel_ids.filtered( lambda r: r.sale_channel_id == mirakl_channel.channel_id ) diff --git a/sale_channel_mirakl/mirakl_mapper/mirakl_product.py b/sale_channel_mirakl/mirakl_mapper/mirakl_product.py index c4508062..b8e8eac0 100644 --- a/sale_channel_mirakl/mirakl_mapper/mirakl_product.py +++ b/sale_channel_mirakl/mirakl_mapper/mirakl_product.py @@ -11,12 +11,6 @@ class MiraklProduct(MiraklExportMapper): @classmethod def map_item(cls, mirakl_channel, product): - """ - Build a mirakl record from an odoo record - :param mirakl_channel: Mirakl channel on which the product is attached - :param product: product to map - :return: a pydantic object corresponding to the form expected by mirakl - """ cat = product.categ_id.display_name or "" cat = cat.replace( "/", ">" diff --git a/sale_channel_mirakl/mirakl_mapper/mirakl_sale_order.py b/sale_channel_mirakl/mirakl_mapper/mirakl_sale_order.py index b0948b23..fab03a80 100644 --- a/sale_channel_mirakl/mirakl_mapper/mirakl_sale_order.py +++ b/sale_channel_mirakl/mirakl_mapper/mirakl_sale_order.py @@ -81,12 +81,10 @@ def get_pricelist(self, mirakl_channel): return product_pricelist def get_order_lines(self, mirakl_channel): - order_lines_values = [] - for order_line in self.order_lines: - order_lines_values.append( - Command.create(order_line.odoo_model_dump(mirakl_channel)) - ) - return order_lines_values + return [ + Command.create(order_line.odoo_model_dump(mirakl_channel)) + for order_line in self.order_lines + ] def odoo_model_dump(self, mirakl_channel): """ diff --git a/sale_channel_mirakl/mirakl_mapper/mirakl_shipping_address.py b/sale_channel_mirakl/mirakl_mapper/mirakl_shipping_address.py index 311228ad..883adc6a 100644 --- a/sale_channel_mirakl/mirakl_mapper/mirakl_shipping_address.py +++ b/sale_channel_mirakl/mirakl_mapper/mirakl_shipping_address.py @@ -1,21 +1,8 @@ -from .country_builder import CountryBuilder from .mirakl_import_mapper import MiraklImportMapper from .mirakl_partner_address import MiraklPartnerAddress -class MiraklShippingAddress(MiraklImportMapper, MiraklPartnerAddress, CountryBuilder): +class MiraklShippingAddress(MiraklImportMapper, MiraklPartnerAddress): additional_info: str shipping_zone_code: str = "" - - def build_country(self, sale_channel): - country = super().build_country(sale_channel) - if self.shipping_zone_code: - country = ( - sale_channel.env["res.country"].search( - [("code", "=", self.shipping_zone_code)], - limit=1, - ) - or country - ) - return country diff --git a/sale_channel_mirakl/mirakl_mapper/res_partner_builder.py b/sale_channel_mirakl/mirakl_mapper/res_partner_builder.py index 902a4a5f..a042f4db 100644 --- a/sale_channel_mirakl/mirakl_mapper/res_partner_builder.py +++ b/sale_channel_mirakl/mirakl_mapper/res_partner_builder.py @@ -23,13 +23,15 @@ def build_name(self): return name def build_country(self, sale_channel): - if self.country_iso_code: - country = sale_channel.env["res.country"].search( - [("code", "=", self.country_iso_code)], - limit=1, + country = sale_channel.default_country_id + if self.shipping_zone_code: + country = ( + sale_channel.env["res.country"].search( + [("code", "=", self.shipping_zone_code)], + limit=1, + ) + or country ) - else: - country = sale_channel.default_country_id return country def odoo_model_dump(self, mirakl_channel): diff --git a/sale_channel_mirakl/models/__init__.py b/sale_channel_mirakl/models/__init__.py index 26e047f9..f4920085 100644 --- a/sale_channel_mirakl/models/__init__.py +++ b/sale_channel_mirakl/models/__init__.py @@ -1,15 +1,11 @@ -from . import mirakl_binding from . import sale_channel_owner from . import mirakl_importer from . import mirakl_res_partner_importer -from . import mirakl_customer_importer from . import mirakl_sale_order_importer from . import product_template -from . import product_product from . import res_partner_sale_channel_rel from . import res_partner from . import sale_channel from . import sale_channel_mirakl from . import mirakl_sale_order_line_importer from . import sale_order -from . import product_pricelist diff --git a/sale_channel_mirakl/models/mirakl_binding.py b/sale_channel_mirakl/models/mirakl_binding.py deleted file mode 100644 index 7aab2527..00000000 --- a/sale_channel_mirakl/models/mirakl_binding.py +++ /dev/null @@ -1,9 +0,0 @@ -from odoo import fields, models - - -# A bouger dans SCOwner -class MiraklBinding(models.AbstractModel): - _name = "mirakl.binding" - _description = "used to attache several items to mirakl sale channel" - - is_from_mirakl = fields.Boolean() diff --git a/sale_channel_mirakl/models/mirakl_customer_importer.py b/sale_channel_mirakl/models/mirakl_customer_importer.py deleted file mode 100644 index 7561ef1c..00000000 --- a/sale_channel_mirakl/models/mirakl_customer_importer.py +++ /dev/null @@ -1,37 +0,0 @@ -import logging - -from odoo import fields, models, tools - -_logger = logging.getLogger(__name__) - - -class MiraklCustomerImporter(models.Model): - _name = "mirakl.customer.importer" - _inherit = "mirakl.importer" - _description = "Mirakl customer importer" - - def _get_binding(self, sale_channel, mirakl_record): - external_id = mirakl_record.get_key() - binding_model = mirakl_record._odoo_model - - if binding_model == "res.partner": - binding = self.env[binding_model].search( - [ - ( - "res_partner_sale_channel_ids.sale_channel_external_code", - "=", - tools.ustr(external_id), - ), - ("channel_ids", "in", sale_channel.channel_id.id), - ], - limit=2, - ) - - if len(binding) > 1: - _logger.warning( - "there are many records linked to the same mirakl record" - ) - binding = fields.first(binding) - return binding - else: - return super()._get_binding(sale_channel, mirakl_record) diff --git a/sale_channel_mirakl/models/mirakl_importer.py b/sale_channel_mirakl/models/mirakl_importer.py index c33aa7da..1d30b360 100644 --- a/sale_channel_mirakl/models/mirakl_importer.py +++ b/sale_channel_mirakl/models/mirakl_importer.py @@ -55,7 +55,7 @@ def _get_binding(self, sale_channel, mirakl_record): def _get_importers(self): importers = { - MiraklCustomer: "mirakl.customer.importer", + MiraklCustomer: "mirakl.res.partner.importer", MiraklBillingAddress: "mirakl.res.partner.importer", MiraklShippingAddress: "mirakl.res.partner.importer", MiraklSaleOrder: "mirakl.sale.order.importer", diff --git a/sale_channel_mirakl/models/mirakl_res_partner_importer.py b/sale_channel_mirakl/models/mirakl_res_partner_importer.py index b66cbf05..15adf901 100644 --- a/sale_channel_mirakl/models/mirakl_res_partner_importer.py +++ b/sale_channel_mirakl/models/mirakl_res_partner_importer.py @@ -5,7 +5,7 @@ _logger = logging.getLogger(__name__) -class MiraklResPartnerImporter(models.Model): +class MiraklResPartnerImporter(models.AbstractModel): _name = "mirakl.res.partner.importer" _inherit = "mirakl.importer" _description = "Mirakl res partner importer" @@ -14,24 +14,19 @@ def _get_binding(self, sale_channel, mirakl_record): external_id = mirakl_record.get_key() binding_model = mirakl_record._odoo_model - if binding_model == "res.partner": - binding = self.env[binding_model].search( - [ - ( - "res_partner_sale_channel_ids.sale_channel_external_code", - "=", - tools.ustr(external_id), - ), - ("channel_ids", "in", sale_channel.channel_id.id), - ], - limit=2, - ) + binding = self.env[binding_model].search( + [ + ( + "res_partner_sale_channel_ids.sale_channel_external_code", + "=", + tools.ustr(external_id), + ), + ("channel_ids", "in", sale_channel.channel_id.id), + ], + limit=2, + ) - if len(binding) > 1: - _logger.warning( - "there are many records linked to the same mirakl record" - ) - binding = fields.first(binding) - return binding - else: - return super()._get_binding(sale_channel, mirakl_record) + if len(binding) > 1: + _logger.warning("there are many records linked to the same mirakl record") + binding = fields.first(binding) + return binding diff --git a/sale_channel_mirakl/models/mirakl_sale_order_importer.py b/sale_channel_mirakl/models/mirakl_sale_order_importer.py index 039a7536..93efc9b2 100644 --- a/sale_channel_mirakl/models/mirakl_sale_order_importer.py +++ b/sale_channel_mirakl/models/mirakl_sale_order_importer.py @@ -198,13 +198,14 @@ def _import_sale_orders_batch(self, sale_channel, filters=None): ) imported_orders = result["orders"] or [] - created_or_updated_orders = [] + created_or_updated_orders = self.env["sale.order"].browse() for mirakl_sale_order in self._map_orders(imported_orders): if not self._get_binding( sale_channel, mirakl_sale_order, ): - created_or_updated_orders.append( - self.create_or_update_record(sale_channel, mirakl_sale_order) + created_or_updated_orders |= self.create_or_update_record( + sale_channel, mirakl_sale_order ) + return created_or_updated_orders diff --git a/sale_channel_mirakl/models/mirakl_sale_order_line_importer.py b/sale_channel_mirakl/models/mirakl_sale_order_line_importer.py index b81fb23b..1baedb54 100644 --- a/sale_channel_mirakl/models/mirakl_sale_order_line_importer.py +++ b/sale_channel_mirakl/models/mirakl_sale_order_line_importer.py @@ -5,10 +5,20 @@ _logger = logging.getLogger(__name__) -class MiraklSaleOrderLineImporter(models.Model): +class MiraklSaleOrderLineImporter(models.AbstractModel): _name = "mirakl.sale.order.line.importer" _description = "sale order line importer" _inherit = "mirakl.importer" def _create_record(self, binding_model, odoo_data): + """ + this method has been overridden because contrary to the super class, + we create the odoo record with a 'create' method which ensures that all the + necessary fields are provided. + + Except for the creation of a sale order, it is imperative to have + lines and for lines, you must have an id for the sale order except with a + 'new' method, we leave it to the ORM to create the order and its lines. + each instance as it should be by attaching them to each other + """ return self.env[binding_model].new(odoo_data) diff --git a/sale_channel_mirakl/models/product_pricelist.py b/sale_channel_mirakl/models/product_pricelist.py deleted file mode 100644 index 3d7ed1d1..00000000 --- a/sale_channel_mirakl/models/product_pricelist.py +++ /dev/null @@ -1,10 +0,0 @@ -from odoo import fields, models - - -class ProductPricelist(models.Model): - _name = "product.pricelist" - _inherit = [_name, "sale.channel.owner"] - - channel_ids = fields.Many2many( - comodel_name="sale.channel", string="Binded Sale Channels" - ) diff --git a/sale_channel_mirakl/models/product_product.py b/sale_channel_mirakl/models/product_product.py deleted file mode 100644 index 124d1e7c..00000000 --- a/sale_channel_mirakl/models/product_product.py +++ /dev/null @@ -1,6 +0,0 @@ -from odoo import models - - -class ProductProduct(models.Model): - _name = "product.product" - _inherit = ["mirakl.binding", _name] diff --git a/sale_channel_mirakl/models/res_partner.py b/sale_channel_mirakl/models/res_partner.py index 5d74d446..57b2c4a4 100644 --- a/sale_channel_mirakl/models/res_partner.py +++ b/sale_channel_mirakl/models/res_partner.py @@ -3,7 +3,7 @@ class ResPartner(models.Model): _name = "res.partner" - _inherit = [_name, "mirakl.binding", "sale.channel.owner"] + _inherit = [_name, "sale.channel.owner"] channel_ids = fields.Many2many( comodel_name="sale.channel", diff --git a/sale_channel_mirakl/models/res_partner_sale_channel_rel.py b/sale_channel_mirakl/models/res_partner_sale_channel_rel.py index 4ca8da75..201b0c04 100644 --- a/sale_channel_mirakl/models/res_partner_sale_channel_rel.py +++ b/sale_channel_mirakl/models/res_partner_sale_channel_rel.py @@ -11,6 +11,4 @@ class ResPartnerSaleChannelRel(models.Model): _description = "Res partner sale channel Relation" _inherit = "sale.channel.relation" - sale_channel_id = fields.Many2one("sale.channel", string="Sale Channel") - - res_partner_id = fields.Many2one("res.partner", string="Res Partner") + res_partner_id = fields.Many2one("res.partner", string="Res Partner", required=True) diff --git a/sale_channel_mirakl/models/sale_channel.py b/sale_channel_mirakl/models/sale_channel.py index ad8d3b5e..3c97e6b3 100644 --- a/sale_channel_mirakl/models/sale_channel.py +++ b/sale_channel_mirakl/models/sale_channel.py @@ -1,6 +1,5 @@ from odoo import _, api, fields, models from odoo.exceptions import ValidationError -from odoo.tools import split_every MIRAKL = "mirakl" @@ -36,38 +35,40 @@ def _check_uniqueness(self): ) def _get_struct_to_export(self): + struct_keys = super()._get_struct_to_export() if self.channel_type == MIRAKL: - for channel in self.mirakl_channel_ids: - yield channel.data_to_export - else: - super()._get_struct_to_export() - - def split_products(self, products): - """ - constructs a list of product lists whose length of e - ach sublist depends on a given parameter. - This is to avoid launching the export of too many products at once. - :param products: list of products to split - :return: A generator that returns each sublist of products one by one - """ - return split_every(self.max_items_to_export, products) + struct_keys.extend([c.data_to_export for c in self.mirakl_channel_ids]) + return struct_keys def _get_items_to_export(self, struct_key): if self.channel_type == MIRAKL: - products = self.env["product.product"].search( - [("product_tmpl_id.channel_ids", "in", self.id)] - ) - products_list = self.split_products(products) # List of lists of products - return products_list + yield from self._get_items_to_export_mirakl_product() return super()._get_items_to_export(struct_key) + def _get_items_to_export_mirakl_product(self): + domain = [("product_tmpl_id.channel_ids", "in", self.id)] + if self.max_items_to_export <= 0: + products = self.env["product.product"].search(domain) + yield products + else: + products = self.env["product.product"].search( + domain, limit=self.max_items_to_export + ) + already_loaded = self.max_items_to_export + while products: + yield products + products = self.env["product.product"].search( + domain, limit=self.max_items_to_export, offset=already_loaded + ) + already_loaded += self.max_items_to_export + def _map_items(self, struct_key, items): if self.channel_type == MIRAKL: for item in self.mirakl_channel_ids._map_items(struct_key, items): yield item else: - super()._map_items(struct_key, items) + return super()._map_items(struct_key, items) def _trigger_export(self, struct_key, pydantic_items): if self.channel_type == MIRAKL: @@ -75,15 +76,15 @@ def _trigger_export(self, struct_key, pydantic_items): lambda r: r.data_to_export == struct_key ) return mirakl_channel._export_data(pydantic_items) - return super()._trigger_export(struct_key, pydantic_items) def _get_struct_to_import(self): + struct_keys = super()._get_struct_to_import() if self.channel_type == MIRAKL: - for record in self.mirakl_channel_ids: - yield record.data_to_import - else: - super()._get_struct_to_import() + struct_keys.extend( + record.data_to_import for record in self.mirakl_channel_ids + ) + return struct_keys def _job_trigger_import(self, struct_key): if self.channel_type == MIRAKL: diff --git a/sale_channel_mirakl/models/sale_channel_mirakl.py b/sale_channel_mirakl/models/sale_channel_mirakl.py index 01013e29..4b067133 100644 --- a/sale_channel_mirakl/models/sale_channel_mirakl.py +++ b/sale_channel_mirakl/models/sale_channel_mirakl.py @@ -263,10 +263,8 @@ def _create_and_fill_csv_file(self, pydantic_items): # deletion_day = fields.Datetime.now() + timedelta( # days=self.attachment_delation_day # ) - # - # attachment.with_delay(eta=deletion_day).unlink() Currently, I have to - # comment because having skipped the execution of the jobs for my tests, - # the attachment is deleted before being returned + # attachment.with_delay(eta=deletion_day).unlink() + # TODO : Patch the ir_attachment 'unlink' method in tests return attachment @@ -274,10 +272,11 @@ def _export_data(self, pydantic_items): """ Super class data export method adapted to Mirakl (Export products or offers) - :param items: items to export. + :param pydantic_items: items to export. """ self.ensure_one() attachment = self._create_and_fill_csv_file(pydantic_items) + self.post(attachment) def _get_mappers(self): diff --git a/sale_channel_mirakl/models/sale_channel_owner.py b/sale_channel_mirakl/models/sale_channel_owner.py index c7ac8928..1d7866be 100644 --- a/sale_channel_mirakl/models/sale_channel_owner.py +++ b/sale_channel_mirakl/models/sale_channel_owner.py @@ -1,5 +1,7 @@ from odoo import api, fields, models +from .sale_channel import MIRAKL + class SaleChannelOwner(models.AbstractModel): _inherit = "sale.channel.owner" @@ -13,8 +15,12 @@ class SaleChannelOwner(models.AbstractModel): store=True, help="Date of last import sync for the related record", ) + is_from_mirakl = fields.Boolean( + compute="_compute_is_from_mirakl", + store=True, + ) - def _get_values_for_updating(self, field_name, only_single_result=False): + def _get_values_for_updating(self, field_name): """ :param field_name: field to update :param only_single_result: set to True if we want the values to @@ -47,17 +53,9 @@ def _get_values_for_updating(self, field_name, only_single_result=False): fields=[relation_field, f"{field_name}s:array_agg({field_name})"], groupby=[relation_field], ) - if only_single_result: - values_for_updating = { - x[relation_field][0]: x.get(f"{field_name}s", []) - for x in result - if x.get(relation_field + "_count", 0) == 1 - } - else: - values_for_updating = { - x[relation_field][0]: x.get(f"{field_name}s", []) - for x in result - } + values_for_updating = { + x[relation_field][0]: x.get(f"{field_name}s", []) for x in result + } return values_for_updating @@ -84,9 +82,13 @@ def _compute_sync_date(self): field_name = "sale_channel_sync_date" self.update({field_name: False}) - values_for_updating = self._get_values_for_updating( - field_name, only_single_result=True - ) + values_for_updating = self._get_values_for_updating(field_name) if values_for_updating: for record in self: record.sale_channel_sync_date = values_for_updating.get(record.id)[0] + + @api.depends("channel_ids") + def _compute_is_from_mirakl(self): + for record in self: + if any(x.channel_type == MIRAKL for x in record.channel_ids): + record.is_from_mirakl = True diff --git a/sale_channel_mirakl/models/sale_order.py b/sale_channel_mirakl/models/sale_order.py index 44912098..0c284d48 100644 --- a/sale_channel_mirakl/models/sale_order.py +++ b/sale_channel_mirakl/models/sale_order.py @@ -2,8 +2,7 @@ class SaleOrder(models.Model): - _name = "sale.order" - _inherit = ["mirakl.binding", _name] + _inherit = "sale.order" channel_ids = fields.Many2many( comodel_name="sale.channel", diff --git a/sale_channel_mirakl/security/ir.model.access.csv b/sale_channel_mirakl/security/ir.model.access.csv index fcf3d687..811bb4e0 100644 --- a/sale_channel_mirakl/security/ir.model.access.csv +++ b/sale_channel_mirakl/security/ir.model.access.csv @@ -1,11 +1,8 @@ "id","name","model_id/id","group_id/id","perm_read","perm_write","perm_create","perm_unlink" -access_mirakl_binding,access.mirakl.binding,model_mirakl_binding,base.group_user,1,1,1,1 access_mirakl_sale_order_importer,access.mirakl.sale.order.importer,model_mirakl_sale_order_importer,base.group_user,1,1,1,1 -access_product_product,access.product.product,model_product_product,base.group_user,1,1,1,1 access_sale_channel,access.sale.channel,model_sale_channel,base.group_user,1,1,1,0 access_sale_channel_mirakl,access.sale.channel.mirakl,model_sale_channel_mirakl,base.group_user,1,1,1,0 access_sale_order,access.sale.order,model_sale_order,base.group_user,1,0,1,0 access_res_partner_sale_channel_rel,access.res.partner.sale.channel.rel,model_res_partner_sale_channel_rel,base.group_user,1,0,0,0 access_mirakl_res_partner_importer,access.mirakl.res.partner.importer,model_mirakl_res_partner_importer,base.group_user,1,0,0,0 -access_mirakl_customer_importer,access.mirakl.customer.importer,model_mirakl_customer_importer,base.group_user,1,0,0,0 access_mirakl_sale_order_line_importer,access_mirakl_sale_order_line_importer,model_mirakl_sale_order_line_importer,base.group_user,1,0,0,0 diff --git a/sale_channel_mirakl/tests/common.py b/sale_channel_mirakl/tests/common.py index 4e1edb6b..0b1842e3 100644 --- a/sale_channel_mirakl/tests/common.py +++ b/sale_channel_mirakl/tests/common.py @@ -1,5 +1,6 @@ import csv from base64 import b64decode +from contextlib import contextmanager from io import StringIO from odoo import Command @@ -40,9 +41,10 @@ def setUpClass(cls): cls.product2 = cls.env.ref("product.product_product_8") cls.product3 = cls.env.ref("product.product_product_5") cls.product4 = cls.env.ref("product.product_product_11b") - cls.product_pricelist = cls.env.ref("product.list0") cls.currency = cls.env.ref("base.EUR") - cls.product_pricelist.write({"currency_id": cls.currency.id}) + cls.product_pricelist = cls.env.ref("product.list0").copy( + {"currency_id": cls.currency.id} + ) cls.payment_mode = cls.env.ref("account_payment_mode.payment_mode_outbound_ct1") cls.product1.write( @@ -216,6 +218,63 @@ def setUpClass(cls): } ) + @contextmanager + def _patch_process_request(self, sale_channel): + def _mock_process_request( + self_local, + url, + headers=None, + params=None, + data=None, + files=None, + ignore_result=False, + request_type=None, + ): + self.url = url + self.headers = headers + self.files = files + self.request_type = request_type + + sale_channel._patch_method("_process_request", _mock_process_request) + yield + sale_channel._revert_method("_process_request") + + @contextmanager + def _patch_call_request(self, sale_channel): + def _sub_function( + self_local, + url, + headers=None, + params=None, + data=None, + files=None, + ignore_result=False, + request_type=None, + ): + return self.mirakl_sale_orders + + sale_channel._patch_method("_process_request", _sub_function) + yield + sale_channel._revert_method("_process_request") + + @contextmanager + def _patch_import_one_sale_order(self, sale_channel): + def _only_one_sale_order( + self_local, + url, + headers=None, + params=None, + data=None, + files=None, + ignore_result=False, + request_type=None, + ): + return self.a_sale_order + + sale_channel._patch_method("_process_request", _only_one_sale_order) + yield + sale_channel._revert_method("_process_request") + def setUp(self): super().setUp() self.a_sale_order = { diff --git a/sale_channel_mirakl/tests/test_products_and_offers_exporter.py b/sale_channel_mirakl/tests/test_products_and_offers_exporter.py index e5ee3498..035e4732 100644 --- a/sale_channel_mirakl/tests/test_products_and_offers_exporter.py +++ b/sale_channel_mirakl/tests/test_products_and_offers_exporter.py @@ -17,9 +17,26 @@ PROD_ID_TYPE = "SHOP_SKU" EMPTY_STRING = "" CARRIAGE_RETURN = "\r\n" +PRODUCT_FILE_HEADER = ( + '"sku";"ean";"PRODUCT_TITLE";"PRODUCT_DESCRIPTION";"PRODUCT_CAT_CODE"' +) +OFFER_FILE_HEADER = '"sku";"product-id";"product-id-type";"state"' +CATALOG_FILE_HEADER = ( + '"sku";"ean";"PRODUCT_TITLE";"PRODUCT_DESCRIPTION";' + '"PRODUCT_CAT_CODE";"product-id";"product-id-type";"state"' +) class TestProductOfferExporter(common.SetUpMiraklBase): + @contextmanager + def _patch_unlink_attachment(self, attachment): + def _local_unlink(self_local): + return True + + attachment._patch_method("unlink", _local_unlink) + yield + attachment._revert_method("unlink") + def test_make_product_file(self): struct_key = self.mirakl_sc_for_product.data_to_export @@ -113,33 +130,12 @@ def _check_parameters_test(self, url, files, request_type): self.assertDictEqual(self.files, files) self.assertEqual(self.request_type, request_type) - @contextmanager - def _patch_process_request(self, sale_channel): - def _mock_process_request( - self_local, - url, - headers=None, - params=None, - data=None, - files=None, - ignore_result=False, - request_type=None, - ): - self.url = url - self.headers = headers - self.files = files - self.request_type = request_type - - sale_channel._patch_method("_process_request", _mock_process_request) - yield - sale_channel._revert_method("_process_request") - def test_post_products_file_on_mirakl(self): expected_filename = "{}_{}".format( "Product", self.mirakl_sc_for_product.offer_filename ) expected_file_content = ( - '"sku";"ean";"PRODUCT_TITLE";"PRODUCT_DESCRIPTION";"PRODUCT_CAT_CODE"\r\n' + PRODUCT_FILE_HEADER + "\r\n" '"{p2_dfl}";"{p2_brcd}";"{p2_name}";"{p2_desc}";"{cat}"{car_return}' '"{p1_dfl}";"{empty}";"{p1_name}";"{p1_name}";"{cat}"{car_return}'.format( p2_dfl=self.product2.default_code, @@ -167,7 +163,7 @@ def test_post_offers_file_on_mirakl(self): "Offer", self.mirakl_sc_for_offer.offer_filename ) expected_file_content = ( - '"sku";"product-id";"product-id-type";"state"\r\n' + OFFER_FILE_HEADER + "\r\n" '"{p2_dfl}";"{p2_brcd}";"{ean}";"{state}"{car_return}' '"{p1_dfl}";"{p1_dfl}";"{prod_id_type}";"{state}"{car_return}'.format( p2_dfl=self.product2.default_code, @@ -194,8 +190,7 @@ def test_post_offers_file_on_mirakl(self): def test_post_catalog_file_on_mirakl(self): expected_filename = self.mirakl_sc_for_offer.offer_filename expected_file_content = ( - '"sku";"ean";"PRODUCT_TITLE";"PRODUCT_DESCRIPTION";"PRODUCT_CAT_CODE"' - ';"product-id";"product-id-type";"state"\r\n' + CATALOG_FILE_HEADER + "\r\n" '"{p2_dfl}";"{p2_brcd}";"{p2_name}";"{p2_desc}";"{cat}";"{p2_brcd}";' '"{ean}";"{state}"{car_return}' '"{p1_dfl}";"{empty}";"{p1_name}";"{p1_name}";"{cat}";"{p1_dfl}";' @@ -231,51 +226,14 @@ def test_get_struct_to_import(self): for struct_key in self.mirakl_sc_import.channel_id._get_struct_to_import(): self.assertEqual(struct_key, SALE_ORDER) - @contextmanager - def _patch_call_request(self, sale_channel): - def _sub_function( - self_local, - url, - headers=None, - params=None, - data=None, - files=None, - ignore_result=False, - request_type=None, - ): - - return self.mirakl_sale_orders - - sale_channel._patch_method("_process_request", _sub_function) - yield - sale_channel._revert_method("_process_request") - def test_sale_orders_import(self): with self._patch_call_request(self.mirakl_sc_import): self.mirakl_sc_import.channel_id._scheduler_import() - @contextmanager - def _patch_import_one_sale_order(self, sale_channel): - def _only_one_sale_order( - self_local, - url, - headers=None, - params=None, - data=None, - files=None, - ignore_result=False, - request_type=None, - ): - return self.a_sale_order - - sale_channel._patch_method("_process_request", _only_one_sale_order) - yield - sale_channel._revert_method("_process_request") - def test_import_one_sale_order(self): with self._patch_import_one_sale_order(self.mirakl_sc_import): orders = self.sale_channel_4._job_trigger_import(SALE_ORDER) self.assertEqual(1, len(orders)) - self.assertEqual(type(orders[0]), self.env[SALE_ORDER].__class__) - self.assertTrue(orders[0].is_from_mirakl) + self.assertEqual(orders._name, SALE_ORDER) + self.assertTrue(orders.is_from_mirakl) diff --git a/sale_channel_mirakl/views/sale_channel_mirakl.xml b/sale_channel_mirakl/views/sale_channel_mirakl.xml index 921a9819..8ddea513 100644 --- a/sale_channel_mirakl/views/sale_channel_mirakl.xml +++ b/sale_channel_mirakl/views/sale_channel_mirakl.xml @@ -6,10 +6,15 @@ mirakl.backend.tree sale.channel.mirakl - - - - + + + + + + + + + @@ -19,17 +24,30 @@ sale.channel.mirakl
- - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/sale_channel_product/models/product_template_sale_channel_rel.py b/sale_channel_product/models/product_template_sale_channel_rel.py index dee62b77..4f1e51c1 100644 --- a/sale_channel_product/models/product_template_sale_channel_rel.py +++ b/sale_channel_product/models/product_template_sale_channel_rel.py @@ -7,6 +7,6 @@ class ProductTemplateSaleChannelRel(models.Model): _description = "Product template sale channel Relation" _inherit = "sale.channel.relation" - sale_channel_id = fields.Many2one("sale.channel", string="Sale Channel") - - product_template_id = fields.Many2one("product.template", string="Product Template") + product_template_id = fields.Many2one( + "product.template", string="Product Template", required=True + )