# Copyright 2025 - Today Criptomart # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) from odoo import api from odoo import fields from odoo import models class ProductProduct(models.Model): _inherit = "product.product" group_order_ids = fields.Many2many( "group.order", "group_order_product_rel", "product_id", "order_id", string="Group Orders", readonly=True, help="Group orders where this product is available", ) is_out_of_stock = fields.Boolean( compute="_compute_stock_ribbons", store=False, help="True if virtual_available <= 0", ) is_low_stock = fields.Boolean( compute="_compute_stock_ribbons", store=False, help="True if 0 < virtual_available <= threshold", ) dynamic_ribbon_id = fields.Many2one( "product.ribbon", string="Dynamic Stock Ribbon", compute="_compute_stock_ribbons", store=False, help="Auto-assigned ribbon based on stock levels", ) @api.depends("virtual_available", "type", "allow_out_of_stock_order") def _compute_stock_ribbons(self): """Compute stock-based ribbons dynamically. A product is considered out of stock only when: - It is a storable product (type='consu') - virtual_available <= 0 - allow_out_of_stock_order is False If allow_out_of_stock_order is True, the product can always be sold regardless of stock level. """ # Obtener ribbons (usar sudo para evitar permisos) out_of_stock_ribbon = self.env.ref( "website_sale_aplicoop.out_of_stock_ribbon", raise_if_not_found=False ) low_stock_ribbon = self.env.ref( "website_sale_aplicoop.low_stock_ribbon", raise_if_not_found=False ) # Obtener threshold de configuración threshold = float( self.env["ir.config_parameter"] .sudo() .get_param("website_sale_aplicoop.low_stock_threshold", default="5.0") ) for product in self: # Solo para productos almacenables (type='consu' en Odoo 18) if product.type != "consu": product.is_out_of_stock = False product.is_low_stock = False product.dynamic_ribbon_id = False continue qty = product.virtual_available # Check if product allows selling when out of stock # If True, never block add-to-cart based on stock allow_oos = getattr(product, "allow_out_of_stock_order", True) if qty <= 0 and not allow_oos: # Out of stock and NOT allowed to sell without stock product.is_out_of_stock = True product.is_low_stock = False product.dynamic_ribbon_id = out_of_stock_ribbon elif qty <= 0 and allow_oos: # Out of stock but allowed to sell anyway product.is_out_of_stock = False product.is_low_stock = False product.dynamic_ribbon_id = False elif qty <= threshold: product.is_out_of_stock = False product.is_low_stock = True product.dynamic_ribbon_id = low_stock_ribbon else: product.is_out_of_stock = False product.is_low_stock = False product.dynamic_ribbon_id = False @api.model def _get_products_for_group_order(self, order_id): """Backward-compatible delegation to `group.order` discovery. The canonical discovery logic lives on `group.order` to keep responsibilities together. Keep this wrapper so existing callers on `product.product` keep working. """ order = self.env["group.order"].browse(order_id) if not order.exists(): return self.browse() return order._get_products_for_group_order(order.id) class ProductTemplate(models.Model): _inherit = "product.template" group_order_ids = fields.Many2many( "group.order", compute="_compute_group_order_ids", string="Consumer Group Orders", readonly=True, help="Consumer group orders where variants of this product are available", ) @api.depends("product_variant_ids.group_order_ids") def _compute_group_order_ids(self): for template in self: variants = template.product_variant_ids template.group_order_ids = variants.mapped("group_order_ids")