Odoo18-Base/addons/sale_gelato/models/product_template.py
2025-03-04 12:23:19 +07:00

202 lines
9.0 KiB
Python

# 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)]])