# Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import Command, _, api, fields, models from odoo.exceptions import UserError from odoo.osv import expression from odoo.addons.sale_gelato import utils class ProductTemplate(models.Model): _inherit = 'product.template' gelato_template_ref = fields.Char( string="Gelato Template Reference", help="Synchronize to fetch variants from Gelato", ) gelato_product_uid = fields.Char( string="Gelato Product UID", compute='_compute_gelato_product_uid', inverse='_inverse_gelato_product_uid', readonly=True, ) gelato_image_ids = fields.One2many( string="Gelato Print Images", comodel_name='product.document', inverse_name='res_id', domain=[('is_gelato', '=', True)], readonly=True, ) gelato_missing_images = fields.Boolean( string="Missing Print Images", compute='_compute_gelato_missing_images', ) # === COMPUTE METHODS === # @api.depends('product_variant_ids.gelato_product_uid') def _compute_gelato_product_uid(self): self._compute_template_field_from_variant_field('gelato_product_uid') def _inverse_gelato_product_uid(self): self._set_product_variant_field('gelato_product_uid') @api.depends('gelato_image_ids') def _compute_gelato_missing_images(self): for product in self: product.gelato_missing_images = any( not image.datas for image in product.gelato_image_ids ) # === ACTION METHODS === # def action_sync_gelato_template_info(self): """ Fetch the template information from Gelato and update the product template accordingly. :return: The action to display a toast notification to the user. :rtype: dict """ # Fetch the template info from Gelato. try: endpoint = f'templates/{self.gelato_template_ref}' template_info = utils.make_request( self.env.company.sudo().gelato_api_key, 'ecommerce', 'v1', endpoint, method='GET' ) # In sudo mode to read the API key from the company. except UserError as e: return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'type': 'danger', 'title': _("Could not synchronize with Gelato"), 'message': str(e), 'sticky': True, } } # Apply the necessary changes on the product template. self._create_attributes_from_gelato_info(template_info) self._create_print_images_from_gelato_info(template_info) # Display a toaster notification to the user if all went well. return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'type': 'success', 'title': _("Successfully synchronized with Gelato"), 'message': _("Missing product variants and images have been successfully created."), 'sticky': False, 'next': { 'type': 'ir.actions.client', 'tag': 'soft_reload' } } } # === BUSINESS METHODS === # def _create_attributes_from_gelato_info(self, template_info): """ Create attributes for the current product template. :param dict template_info: The template information fetched from Gelato. :return: None """ if len(template_info['variants']) == 1: # The template has no attribute. self.gelato_product_uid = template_info['variants'][0]['productUid'] else: # The template has multiple attributes. # Iterate over the variants to find and create the possible attributes. for variant_data in template_info['variants']: current_variant_pavs = self.env['product.attribute.value'] for attribute_data in variant_data['variantOptions']: # Attribute name and value. # Search for the existing attribute with the proper variant creation policy and # create it if not found. attribute = self.env['product.attribute'].search( [('name', '=', attribute_data['name']), ('create_variant', '=', 'always')], limit=1, ) if not attribute: attribute = self.env['product.attribute'].create({ 'name': attribute_data['name'] }) # Search for the existing attribute value and create it if not found. attribute_value = self.env['product.attribute.value'].search([ ('name', '=', attribute_data['value']), ('attribute_id', '=', attribute.id), ], limit=1) if not attribute_value: attribute_value = self.env['product.attribute.value'].create({ 'name': attribute_data['value'], 'attribute_id': attribute.id }) current_variant_pavs += attribute_value # Search for the existing PTAL and create it if not found. ptal = self.env['product.template.attribute.line'].search( [('product_tmpl_id', '=', self.id), ('attribute_id', '=', attribute.id)], limit=1, ) if not ptal: self.env['product.template.attribute.line'].create({ 'product_tmpl_id': self.id, 'attribute_id': attribute.id, 'value_ids': [Command.link(attribute_value.id)] }) else: # The PTAL already exists. ptal.value_ids = [Command.link(attribute_value.id)] # Link the value. # Find the variant that was automatically created and set the Gelato UID. for variant in self.product_variant_ids: corresponding_ptavs = variant.product_template_attribute_value_ids corresponding_pavs = corresponding_ptavs.product_attribute_value_id if corresponding_pavs == current_variant_pavs: variant.gelato_product_uid = variant_data['productUid'] break # Delete the incompatible variants that were created but not allowed by Gelato. variants_without_gelato = self.env['product.product'].search([ ('product_tmpl_id', '=', self.id), ('gelato_product_uid', '=', False) ]) variants_without_gelato.unlink() def _create_print_images_from_gelato_info(self, template_info): """ Create print image for the current product template. :param dict template_info: The template information fetched from Gelato. :return: None """ # Iterate over the print image data listed in the info of the first variant, as we don't # support varying image placements between variants. for print_image_data in template_info['variants'][0]['imagePlaceholders']: # Gelato might send image placements that are named '1' or 'front' that are not accepted # by their API when placing order. if print_image_data['printArea'].lower() in ('1', 'front'): print_image_data['printArea'] = 'default' # Use 'default' which is accepted. # Gelato might send several print images for the same placement if several layers were # defined, but we keep only one because their API only accepts one image per placement. print_image_found = bool(self.env['product.document'].search_count([ ('name', 'ilike', print_image_data['printArea']), ('res_id', '=', self.id), ('res_model', '=', 'product.template'), ('is_gelato', '=', True), # Avoid finding regular documents with the same name. ])) if not print_image_found: self.gelato_image_ids = [Command.create({ 'name': print_image_data['printArea'].lower(), 'res_id': self.id, 'res_model': 'product.template', 'is_gelato': True, })] # === GETTER METHODS === # def _get_related_fields_variant_template(self): """ Override of `product` to add `gelato_product_uid` as a related field. """ return super()._get_related_fields_variant_template() + ['gelato_product_uid'] def _get_product_document_domain(self): """ Override of `product` to filter out gelato print images. """ domain = super()._get_product_document_domain() return expression.AND([domain, [('is_gelato', '=', False)]])