pos_loyalty_extend/models/loyalty_reward.py

99 lines
4.6 KiB
Python

# -*- coding: utf-8 -*-
import json
from odoo import models, fields, api
from odoo.fields import Domain
class LoyaltyReward(models.Model):
_inherit = 'loyalty.reward'
reward_product_applicability = fields.Selection([
('specific', 'Specific Product(s)'),
('cheapest', 'Cheapest Product on Order')
], string='Reward Product Applicability', default='specific', required=True)
reward_product_category_id = fields.Many2one(
'product.category', string='Reward Product Category',
help='If specified, only products under this category (including subcategories) will be considered for the cheapest product reward.'
)
reward_product_ids_json = fields.Char(
compute='_compute_reward_product_ids_json',
string='Reward Product IDs JSON'
)
@api.depends('reward_product_id', 'reward_product_tag_id', 'reward_type', 'reward_product_applicability', 'reward_product_category_id')
def _compute_multi_product(self):
# Override completely (no super()) to avoid recursive field invalidation.
# In Odoo 19, reassigning a computed field from within its own compute method
# (after calling super()) triggers cache invalidation and re-computation,
# causing a RecursionError via WeakSet.__iter__ during environment cleanup.
for reward in self:
tag_products = reward.reward_product_tag_id.product_ids.filtered(
lambda product: product.type != 'combo'
)
if reward.reward_type == 'product' and reward.reward_product_applicability == 'cheapest':
# For 'cheapest' applicability: include direct product + tag products,
# but not category products (to avoid large sets and reference cycles).
products = reward.reward_product_id + tag_products
reward.multi_product = False
reward.reward_product_ids = products
else:
# Default behaviour (mirrors the standard Odoo computation)
products = reward.reward_product_id + tag_products
reward.multi_product = reward.reward_type == 'product' and len(products) > 1
reward.reward_product_ids = (
products if reward.reward_type == 'product'
else self.env['product.product']
)
@api.depends('reward_product_id', 'reward_product_tag_id', 'reward_type', 'reward_product_applicability', 'reward_product_category_id')
def _compute_reward_product_ids_json(self):
for reward in self:
if reward.reward_type == 'product' and reward.reward_product_applicability == 'cheapest':
products = reward.reward_product_id + reward.reward_product_tag_id.product_ids.filtered(
lambda product: product.type != 'combo'
)
if reward.reward_product_category_id:
category_ids = self.env['product.category'].search([
('id', 'child_of', reward.reward_product_category_id.ids)
]).ids
cat_products = self.env['product.product'].search([
('categ_id', 'in', category_ids),
('type', '!=', 'combo')
])
products |= cat_products
reward.reward_product_ids_json = json.dumps(products.ids)
else:
reward.reward_product_ids_json = json.dumps([])
@api.model
def _load_pos_data_fields(self, config):
fields = super()._load_pos_data_fields(config)
if 'reward_product_applicability' not in fields:
fields.append('reward_product_applicability')
if 'reward_product_category_id' not in fields:
fields.append('reward_product_category_id')
if 'reward_product_ids_json' not in fields:
fields.append('reward_product_ids_json')
return fields
@api.model
def _load_pos_data_domain(self, data, config):
reward_product_tag_domain = [
('reward_product_tag_id', '!=', False),
'|',
('reward_product_tag_id.product_template_ids.active', '=', True),
('reward_product_tag_id.product_product_ids.active', '=', True),
]
return Domain.AND([
[('program_id', 'in', config._get_program_ids().ids)],
Domain.OR([
[('reward_type', '!=', 'product')],
[('reward_product_id.active', '=', True)],
[('reward_product_applicability', '=', 'cheapest')],
reward_product_tag_domain,
]),
])