# -*- coding: utf-8 -*- from odoo import models, api, _ from odoo.exceptions import UserError from odoo.tools.float_utils import float_round from odoo.fields import Command import random def _generate_random_reward_code(): return f"REWARD-{random.randint(100000, 999999)}" class SaleOrder(models.Model): _inherit = 'sale.order' def _get_cheapest_matching_product(self, reward): self.ensure_one() candidate_lines = self.order_line.filtered( lambda l: not l.is_reward_line and l.product_uom_qty > 0 and l.price_unit > 0 ) if reward.reward_product_id or reward.reward_product_tag_id or reward.reward_product_category_id: allowed_products = reward.reward_product_id if reward.reward_product_tag_id: allowed_products |= reward.reward_product_tag_id.product_ids category_ids = [] if reward.reward_product_category_id: category_ids = self.env['product.category'].search([ ('id', 'child_of', reward.reward_product_category_id.ids) ]).ids candidate_lines = candidate_lines.filtered( lambda l: l.product_id in allowed_products or ( category_ids and l.product_id.categ_id.id in category_ids ) ) if not candidate_lines: return None cheapest_line = min(candidate_lines, key=lambda l: l.price_unit) return cheapest_line.product_id def _get_reward_values_product(self, reward, coupon, product=None, **kwargs): self.ensure_one() if reward.reward_product_applicability != 'cheapest': return super()._get_reward_values_product(reward, coupon, product=product, **kwargs) # Cheapest product applicability logic if not product: product = self._get_cheapest_matching_product(reward) if not product: # Fallback if no eligible product in cart allowed_products = reward.reward_product_id if reward.reward_product_tag_id: allowed_products |= reward.reward_product_tag_id.product_ids if reward.reward_product_category_id: category_ids = self.env['product.category'].search([ ('id', 'child_of', reward.reward_product_category_id.ids) ]).ids allowed_products |= self.env['product.product'].search([ ('categ_id', 'in', category_ids), ('type', '!=', 'combo') ], limit=1) if allowed_products: product = allowed_products[:1] else: first_line = self.order_line.filtered(lambda l: not l.is_reward_line and l.product_uom_qty > 0)[:1] product = first_line.product_id if not product: raise UserError(_("No eligible product in the cart to apply the cheapest product reward.")) # Compute reward values directly to support global cheapest reward taxes = self.fiscal_position_id.map_tax(product.taxes_id._filter_taxes_by_company(self.company_id)) points = self._get_real_points_for_coupon(coupon) claimable_count = float_round(points / reward.required_points, precision_rounding=1, rounding_method='DOWN') if not reward.clear_wallet else 1 cost = points if reward.clear_wallet else claimable_count * reward.required_points return [{ 'name': reward.description or _("Free Product (Cheapest)"), 'product_id': product.id, 'discount': 100, 'product_uom_qty': reward.reward_product_qty * claimable_count, 'reward_id': reward.id, 'coupon_id': coupon.id, 'points_cost': cost, 'reward_identifier_code': _generate_random_reward_code(), 'sequence': max(self.order_line.filtered(lambda x: not x.is_reward_line).mapped('sequence'), default=10) + 1, 'tax_ids': [Command.clear()] + [Command.link(tax.id) for tax in taxes], }]