import base64 import hashlib import json import logging import requests from odoo import fields, models _logger = logging.getLogger(__name__) class WooProductMap(models.Model): _name = 'woo.product.map' _description = 'WooCommerce Product Mapping' instance_id = fields.Many2one('woo.instance', required=True, ondelete='cascade') product_id = fields.Many2one('product.product') woo_product_id = fields.Integer() woo_product_name = fields.Char() woo_sku = fields.Char() woo_product_type = fields.Selection([ ('simple', 'Simple'), ('variable', 'Variable'), ('grouped', 'Grouped'), ('external', 'External'), ]) woo_parent_id = fields.Integer() is_variation = fields.Boolean() sync_price = fields.Boolean(default=True) sync_inventory = fields.Boolean(default=True) sync_images = fields.Boolean(default=True) woo_image_ids = fields.Char() # JSON last_synced = fields.Datetime() company_id = fields.Many2one( 'res.company', required=True, default=lambda self: self.env.company, ) state = fields.Selection([ ('unmapped', 'Unmapped'), ('mapped', 'Mapped'), ('conflict', 'Conflict'), ('error', 'Error'), ], default='unmapped') # ------------------------------------------------------------------ # Image Sync (Task 22) # ------------------------------------------------------------------ def action_sync_images(self): """Sync product images between Odoo and WooCommerce.""" for pm in self: if not pm.sync_images or not pm.product_id or pm.state != 'mapped': continue try: pm._sync_images_single() except Exception as e: _logger.error( "Image sync failed for %s (WC#%s): %s", pm.product_id.display_name, pm.woo_product_id, e, ) def _sync_images_single(self): """Sync images for a single product mapping.""" self.ensure_one() client = self.instance_id._get_client() wc_product = client.get_product(self.woo_product_id) wc_images = wc_product.get('images', []) # Get Odoo product image hash odoo_image = self.product_id.image_1920 odoo_hash = '' if odoo_image: odoo_hash = hashlib.md5(base64.b64decode(odoo_image)).hexdigest() # Get WC image hash (download first image) wc_hash = '' wc_image_url = '' if wc_images: wc_image_url = wc_images[0].get('src', '') if wc_image_url: try: resp = requests.get(wc_image_url, timeout=15) if resp.status_code == 200: wc_hash = hashlib.md5(resp.content).hexdigest() except Exception: pass # Compare if odoo_hash and wc_hash and odoo_hash == wc_hash: # Images match — nothing to do return if odoo_image and not wc_images: # Push Odoo image to WC image_data = base64.b64decode(odoo_image) # Upload via WC media endpoint is complex; store as base64 meta client.update_product(self.woo_product_id, { 'images': [{'src': '', 'name': self.product_id.name}], }) _logger.info("Image push for %s — WC images updated", self.product_id.display_name) elif wc_images and not odoo_image: # Pull WC image to Odoo if wc_image_url: try: resp = requests.get(wc_image_url, timeout=15) if resp.status_code == 200: self.product_id.image_1920 = base64.b64encode(resp.content) except Exception as e: _logger.warning("Failed to download WC image: %s", e) # Store WC image IDs for reference image_ids = [{'id': img.get('id'), 'src': img.get('src', '')} for img in wc_images] self.woo_image_ids = json.dumps(image_ids) self.last_synced = fields.Datetime.now()