mirror of
https://github.com/bringout/oca-ocb-pos.git
synced 2026-04-24 04:02:03 +02:00
19.0 vanilla
This commit is contained in:
parent
6e54c1af6c
commit
3ca647e428
1087 changed files with 132065 additions and 108499 deletions
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import barcode_rule
|
||||
|
|
@ -11,4 +10,6 @@ from . import pos_config
|
|||
from . import pos_order_line
|
||||
from . import pos_order
|
||||
from . import pos_session
|
||||
from . import res_config_settings
|
||||
from . import product_product
|
||||
from . import product_template
|
||||
from . import res_partner
|
||||
|
|
|
|||
|
|
@ -1,13 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class LoyaltyCard(models.Model):
|
||||
_inherit = 'loyalty.card'
|
||||
_name = 'loyalty.card'
|
||||
_inherit = ['loyalty.card', 'pos.load.mixin']
|
||||
|
||||
source_pos_order_id = fields.Many2one('pos.order', "PoS Order Reference",
|
||||
help="PoS order where this coupon was generated.")
|
||||
source_pos_order_partner_id = fields.Many2one(
|
||||
'res.partner', "PoS Order Customer",
|
||||
related="source_pos_order_id.partner_id")
|
||||
|
||||
@api.model
|
||||
def _load_pos_data_domain(self, data, config):
|
||||
return False
|
||||
|
||||
@api.model
|
||||
def _load_pos_data_fields(self, config):
|
||||
return ['partner_id', 'code', 'points', 'points_display', 'program_id', 'expiration_date', 'write_date']
|
||||
|
||||
def _has_source_order(self):
|
||||
return super()._has_source_order() or bool(self.source_pos_order_id)
|
||||
|
|
@ -18,8 +31,8 @@ class LoyaltyCard(models.Model):
|
|||
return self.env.ref('pos_loyalty.mail_coupon_template', False)
|
||||
return super()._get_default_template()
|
||||
|
||||
def _get_mail_partner(self):
|
||||
return super()._get_mail_partner() or self.sudo().source_pos_order_id.partner_id
|
||||
def _mail_get_partner_fields(self, introspect_fields=False):
|
||||
return super()._mail_get_partner_fields(introspect_fields=introspect_fields) + ['source_pos_order_partner_id']
|
||||
|
||||
def _get_signature(self):
|
||||
return self.source_pos_order_id.user_id.signature or super()._get_signature()
|
||||
|
|
@ -27,7 +40,28 @@ class LoyaltyCard(models.Model):
|
|||
def _compute_use_count(self):
|
||||
super()._compute_use_count()
|
||||
read_group_res = self.env['pos.order.line']._read_group(
|
||||
[('coupon_id', 'in', self.ids)], ['id'], ['coupon_id'])
|
||||
count_per_coupon = {r['coupon_id'][0]: r['coupon_id_count'] for r in read_group_res}
|
||||
[('coupon_id', 'in', self.ids)], ['coupon_id'], ['__count'])
|
||||
count_per_coupon = {coupon.id: count for coupon, count in read_group_res}
|
||||
for card in self:
|
||||
card.use_count += count_per_coupon.get(card.id, 0)
|
||||
|
||||
@api.model
|
||||
def get_gift_card_status(self, gift_code, config_id):
|
||||
card = self.search([('code', '=', gift_code)], limit=1)
|
||||
is_valid = card.exists() and (not card.expiration_date or card.expiration_date > fields.Date.today()) and card.points > 0
|
||||
is_valid = is_valid and (card.program_id.program_type == 'gift_card') and not card.partner_id
|
||||
is_valid = is_valid and len([id for id in card.history_ids.mapped('order_id') if id != 0]) == 0
|
||||
card_fields = self._load_pos_data_fields(config_id)
|
||||
return {
|
||||
'status': bool(is_valid) or not card.exists(),
|
||||
'data': {
|
||||
'loyalty.card': card.read(card_fields, load=False),
|
||||
}
|
||||
}
|
||||
|
||||
@api.model
|
||||
def get_loyalty_card_partner_by_code(self, code):
|
||||
return self.env['loyalty.card'].search([
|
||||
('code', '=', code),
|
||||
('program_type', '=', 'loyalty'),
|
||||
], limit=1).partner_id or False
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class LoyaltyMail(models.Model):
|
||||
_inherit = 'loyalty.mail'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,41 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.tools import unique
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class LoyaltyProgram(models.Model):
|
||||
_inherit = 'loyalty.program'
|
||||
_name = 'loyalty.program'
|
||||
_inherit = ['loyalty.program', 'pos.load.mixin']
|
||||
|
||||
# NOTE: `pos_config_ids` satisfies an excpeptional use case: when no PoS is specified, the loyalty program is
|
||||
# applied to every PoS. You can access the loyalty programs of a PoS using _get_program_ids() of pos.config
|
||||
pos_config_ids = fields.Many2many('pos.config', compute="_compute_pos_config_ids", store=True, readonly=False, string="Point of Sales", help="Restrict publishing to those shops.")
|
||||
pos_config_ids = fields.Many2many('pos.config', compute="_compute_pos_config_ids", store=True, readonly=False, string="Point of Sales", help="Restrict publishing to those shops. Note: A program will only be used in the shops using the same currency as the program.")
|
||||
pos_order_count = fields.Integer("PoS Order Count", compute='_compute_pos_order_count')
|
||||
pos_ok = fields.Boolean("Point of Sale", default=True)
|
||||
pos_report_print_id = fields.Many2one('ir.actions.report', string="Print Report", domain=[('model', '=', 'loyalty.card')], compute='_compute_pos_report_print_id', inverse='_inverse_pos_report_print_id', readonly=False,
|
||||
help="This is used to print the generated gift cards from PoS.")
|
||||
|
||||
@api.model
|
||||
def _load_pos_data_domain(self, data, config):
|
||||
return [('id', 'in', config._get_program_ids().ids)]
|
||||
|
||||
@api.model
|
||||
def _load_pos_data_fields(self, config):
|
||||
return [
|
||||
'name', 'trigger', 'applies_on', 'program_type', 'pricelist_ids', 'date_from',
|
||||
'date_to', 'limit_usage', 'max_usage', 'total_order_count', 'is_nominative',
|
||||
'portal_visible', 'portal_point_name', 'trigger_product_ids', 'rule_ids', 'reward_ids'
|
||||
]
|
||||
|
||||
@api.model
|
||||
def _load_pos_data_read(self, records, config):
|
||||
return super()._load_pos_data_read(records.sudo(), config)
|
||||
|
||||
def _unrelevant_records(self, config):
|
||||
valid_record = config._get_program_ids()
|
||||
return self.filtered(lambda record: record.id not in valid_record.ids).ids
|
||||
|
||||
@api.depends("communication_plan_ids.pos_report_print_id")
|
||||
def _compute_pos_report_print_id(self):
|
||||
for program in self:
|
||||
|
|
@ -30,7 +50,11 @@ class LoyaltyProgram(models.Model):
|
|||
if not program.mail_template_id:
|
||||
mail_template_label = program._fields.get('mail_template_id').get_description(self.env)['string']
|
||||
pos_report_print_label = program._fields.get('pos_report_print_id').get_description(self.env)['string']
|
||||
raise UserError(_("You must set '%s' before setting '%s'.", mail_template_label, pos_report_print_label))
|
||||
raise UserError(_(
|
||||
"You must set '%(mail_template)s' before setting '%(report)s'.",
|
||||
mail_template=mail_template_label,
|
||||
report=pos_report_print_label,
|
||||
))
|
||||
else:
|
||||
if not program.communication_plan_ids:
|
||||
program.communication_plan_ids = self.env['loyalty.mail'].create({
|
||||
|
|
@ -53,30 +77,21 @@ class LoyaltyProgram(models.Model):
|
|||
|
||||
def _compute_pos_order_count(self):
|
||||
query = """
|
||||
WITH reward_to_orders_count AS (
|
||||
SELECT reward.id AS lr_id,
|
||||
COUNT(DISTINCT pos_order.id) AS orders_count
|
||||
FROM pos_order_line line
|
||||
JOIN pos_order ON line.order_id = pos_order.id
|
||||
JOIN loyalty_reward reward ON line.reward_id = reward.id
|
||||
GROUP BY lr_id
|
||||
),
|
||||
program_to_reward AS (
|
||||
SELECT reward.id AS reward_id,
|
||||
program.id AS program_id
|
||||
FROM loyalty_program program
|
||||
JOIN loyalty_reward reward ON reward.program_id = program.id
|
||||
WHERE program.id = ANY (%s)
|
||||
)
|
||||
SELECT program_to_reward.program_id,
|
||||
SUM(reward_to_orders_count.orders_count)
|
||||
FROM program_to_reward
|
||||
LEFT JOIN reward_to_orders_count ON reward_to_orders_count.lr_id = program_to_reward.reward_id
|
||||
GROUP BY program_to_reward.program_id
|
||||
SELECT program.id, SUM(orders_count)
|
||||
FROM loyalty_program program
|
||||
JOIN loyalty_reward reward ON reward.program_id = program.id
|
||||
JOIN LATERAL (
|
||||
SELECT COUNT(DISTINCT orders.id) AS orders_count
|
||||
FROM pos_order orders
|
||||
JOIN pos_order_line order_lines ON order_lines.order_id = orders.id
|
||||
WHERE order_lines.reward_id = reward.id
|
||||
) agg ON TRUE
|
||||
WHERE program.id = ANY(%s)
|
||||
GROUP BY program.id
|
||||
"""
|
||||
self._cr.execute(query, (self.ids,))
|
||||
res = self._cr.dictfetchall()
|
||||
res = {k['program_id']: k['sum'] for k in res}
|
||||
self.env.cr.execute(query, (self.ids,))
|
||||
res = self.env.cr.dictfetchall()
|
||||
res = {k['id']: k['sum'] for k in res}
|
||||
|
||||
for rec in self:
|
||||
rec.pos_order_count = res.get(rec.id) or 0
|
||||
|
|
@ -85,16 +100,3 @@ class LoyaltyProgram(models.Model):
|
|||
super()._compute_total_order_count()
|
||||
for program in self:
|
||||
program.total_order_count += program.pos_order_count
|
||||
|
||||
def action_view_pos_orders(self):
|
||||
self.ensure_one()
|
||||
pos_order_ids = list(unique(r['order_id'] for r in\
|
||||
self.env['pos.order.line'].search_read([('reward_id', 'in', self.reward_ids.ids)], fields=['order_id'])))
|
||||
return {
|
||||
'name': _("PoS Orders"),
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'pos.order',
|
||||
'type': 'ir.actions.act_window',
|
||||
'domain': [('id', 'in', pos_order_ids)],
|
||||
'context': dict(self._context, create=False),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
from odoo import models, api
|
||||
from odoo.fields import Domain
|
||||
|
||||
import ast
|
||||
import json
|
||||
|
||||
|
||||
class LoyaltyReward(models.Model):
|
||||
_inherit = 'loyalty.reward'
|
||||
_name = 'loyalty.reward'
|
||||
_inherit = ['loyalty.reward', 'pos.load.mixin']
|
||||
|
||||
def _get_discount_product_values(self):
|
||||
res = super()._get_discount_product_values()
|
||||
|
|
@ -12,6 +18,75 @@ class LoyaltyReward(models.Model):
|
|||
vals.update({'taxes_id': False})
|
||||
return res
|
||||
|
||||
@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_tag_domain,
|
||||
]),
|
||||
])
|
||||
|
||||
@api.model
|
||||
def _load_pos_data_fields(self, config):
|
||||
return ['description', 'program_id', 'reward_type', 'required_points', 'clear_wallet', 'currency_id',
|
||||
'discount', 'discount_mode', 'discount_applicability', 'all_discount_product_ids', 'is_global_discount',
|
||||
'discount_max_amount', 'discount_line_product_id', 'reward_product_id',
|
||||
'multi_product', 'reward_product_ids', 'reward_product_qty', 'reward_product_uom_id', 'reward_product_domain']
|
||||
|
||||
@api.model
|
||||
def _load_pos_data_read(self, records, config):
|
||||
read_records = super()._load_pos_data_read(records, config)
|
||||
for reward in read_records:
|
||||
reward['reward_product_domain'] = self._replace_ilike_with_in(reward['reward_product_domain'])
|
||||
return read_records
|
||||
|
||||
def _get_reward_product_domain_fields(self, config):
|
||||
fields = set()
|
||||
search_domain = [('program_id', 'in', config._get_program_ids().ids)]
|
||||
domains = self.search_read(search_domain, fields=['reward_product_domain'], load=False)
|
||||
for domain in filter(lambda d: d['reward_product_domain'] != "null", domains):
|
||||
domain = json.loads(domain['reward_product_domain'])
|
||||
for condition in self._parse_domain(domain).values():
|
||||
field_name, _, _ = condition
|
||||
fields.add(field_name)
|
||||
return fields
|
||||
|
||||
def _replace_ilike_with_in(self, domain_str):
|
||||
if domain_str == "null":
|
||||
return domain_str
|
||||
|
||||
domain = json.loads(domain_str)
|
||||
|
||||
for index, condition in self._parse_domain(domain).items():
|
||||
field_name, operator, value = condition
|
||||
field = self.env['product.product']._fields.get(field_name)
|
||||
|
||||
if field and field.type == 'many2one' and operator in ('ilike', 'not ilike'):
|
||||
comodel = self.env[field.comodel_name]
|
||||
matching_ids = list(comodel._search([('display_name', 'ilike', value)]))
|
||||
|
||||
new_operator = 'in' if operator == 'ilike' else 'not in'
|
||||
domain[index] = [field_name, new_operator, matching_ids]
|
||||
|
||||
return json.dumps(domain)
|
||||
|
||||
def _parse_domain(self, domain):
|
||||
parsed_domain = {}
|
||||
|
||||
for index, condition in enumerate(domain):
|
||||
if isinstance(condition, (list, tuple)) and len(condition) == 3:
|
||||
parsed_domain[index] = condition
|
||||
return parsed_domain
|
||||
|
||||
def unlink(self):
|
||||
if len(self) == 1 and self.env['pos.order.line'].sudo().search_count([('reward_id', 'in', self.ids)], limit=1):
|
||||
return self.action_archive()
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.osv import expression
|
||||
from odoo.tools import ustr
|
||||
from odoo.fields import Domain
|
||||
|
||||
|
||||
class LoyaltyRule(models.Model):
|
||||
_inherit = 'loyalty.rule'
|
||||
_name = 'loyalty.rule'
|
||||
_inherit = ['loyalty.rule', 'pos.load.mixin']
|
||||
|
||||
valid_product_ids = fields.Many2many(
|
||||
'product.product', "Valid Products", compute='_compute_valid_product_ids',
|
||||
|
|
@ -19,25 +19,31 @@ class LoyaltyRule(models.Model):
|
|||
"This is automatically generated when the promo code is changed."
|
||||
)
|
||||
|
||||
@api.depends('product_ids', 'product_category_id', 'product_tag_id') #TODO later: product tags
|
||||
@api.model
|
||||
def _load_pos_data_domain(self, data, config):
|
||||
return [('program_id', 'in', config._get_program_ids().ids)]
|
||||
|
||||
@api.model
|
||||
def _load_pos_data_fields(self, config):
|
||||
return ['program_id', 'valid_product_ids', 'any_product', 'currency_id',
|
||||
'reward_point_amount', 'reward_point_split', 'reward_point_mode',
|
||||
'minimum_qty', 'minimum_amount', 'minimum_amount_tax_mode', 'mode', 'code']
|
||||
|
||||
@api.depends('product_ids', 'product_category_id', 'product_tag_id', 'product_domain') # TODO later: product tags
|
||||
def _compute_valid_product_ids(self):
|
||||
domain_products = {}
|
||||
for rule in self:
|
||||
if rule.product_ids or\
|
||||
rule.product_category_id or\
|
||||
rule.product_tag_id or\
|
||||
rule.product_domain not in ('[]', "[['sale_ok', '=', True]]"):
|
||||
domain = rule._get_valid_product_domain()
|
||||
domain = expression.AND([[('available_in_pos', '=', True)], domain])
|
||||
product_ids = domain_products.get(ustr(domain))
|
||||
if product_ids is None:
|
||||
product_ids = self.env['product.product'].search(domain, order="id")
|
||||
domain_products[ustr(domain)] = product_ids
|
||||
rule.valid_product_ids = product_ids
|
||||
rule.any_product = False
|
||||
for key, rules in self.grouped(lambda rule: (
|
||||
tuple(rule.product_ids.ids),
|
||||
rule.product_category_id.id,
|
||||
rule.product_tag_id.id,
|
||||
'' if rule.product_domain in ('[]', "[['sale_ok', '=', True]]") else rule.product_domain,
|
||||
)).items():
|
||||
if any(key):
|
||||
domain = Domain.AND([[('available_in_pos', '=', True)], rules[:1]._get_valid_product_domain()])
|
||||
rules.valid_product_ids = self.env['product.product'].search(domain, order="id")
|
||||
rules.any_product = False
|
||||
else:
|
||||
rule.any_product = True
|
||||
rule.valid_product_ids = self.env['product.product']
|
||||
rules.valid_product_ids = self.env['product.product']
|
||||
rules.any_product = True
|
||||
|
||||
@api.depends('code')
|
||||
def _compute_promo_barcode(self):
|
||||
|
|
|
|||
|
|
@ -4,22 +4,22 @@
|
|||
from odoo import _, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class PosConfig(models.Model):
|
||||
_inherit = 'pos.config'
|
||||
|
||||
gift_card_settings = fields.Selection(
|
||||
[
|
||||
("create_set", "Generate PDF cards"),
|
||||
("scan_use", "Scan existing cards"),
|
||||
],
|
||||
string="Gift Cards settings",
|
||||
default="create_set",
|
||||
help="Defines the way you want to set your gift cards.",
|
||||
)
|
||||
# NOTE: this funtions acts as a m2m field with loyalty.program model. We do this to handle an excpetional use case:
|
||||
# When no PoS is specified at a loyalty program form, this program is applied to every PoS (instead of none)
|
||||
def _get_program_ids(self):
|
||||
return self.env['loyalty.program'].search(['&', ('pos_ok', '=', True), '|', ('pos_config_ids', '=', self.id), ('pos_config_ids', '=', False)])
|
||||
today = fields.Date.context_today(self)
|
||||
return self.env['loyalty.program'].search([
|
||||
('pos_ok', '=', True),
|
||||
'|', ('pos_config_ids', '=', self.id), ('pos_config_ids', '=', False),
|
||||
'|', ('date_from', '=', False), ('date_from', '<=', today),
|
||||
'|', ('date_to', '=', False), ('date_to', '>=', today),
|
||||
'|', ('pricelist_ids', '=', False), ('pricelist_ids', 'in', self.available_pricelist_ids.ids),
|
||||
('currency_id', '=', self.currency_id.id)
|
||||
]).filtered(lambda p: not p.limit_usage or p.sudo().total_order_count < p.max_usage)
|
||||
|
||||
def _check_before_creating_new_session(self):
|
||||
self.ensure_one()
|
||||
|
|
@ -49,7 +49,7 @@ class PosConfig(models.Model):
|
|||
|
||||
if invalid_reward_products_msg:
|
||||
prefix_error_msg = _("To continue, make the following reward products available in Point of Sale.")
|
||||
raise UserError(f"{prefix_error_msg}\n{invalid_reward_products_msg}")
|
||||
raise UserError(f"{prefix_error_msg}\n{invalid_reward_products_msg}") # pylint: disable=missing-gettext
|
||||
if gift_card_programs:
|
||||
for gc_program in gift_card_programs:
|
||||
# Do not allow a gift card program with more than one rule or reward, and check that they make sense
|
||||
|
|
@ -63,23 +63,23 @@ class PosConfig(models.Model):
|
|||
reward = gc_program.reward_ids
|
||||
if reward.reward_type != 'discount' or reward.discount_mode != 'per_point' or reward.discount != 1:
|
||||
raise UserError(_('Invalid gift card program reward. Use 1 currency per point discount.'))
|
||||
if self.gift_card_settings == "create_set":
|
||||
if not gc_program.mail_template_id:
|
||||
raise UserError(_('There is no email template on the gift card program and your pos is set to print them.'))
|
||||
if not gc_program.pos_report_print_id:
|
||||
raise UserError(_('There is no print report on the gift card program and your pos is set to print them.'))
|
||||
if not gc_program.mail_template_id:
|
||||
raise UserError(_('There is no email template on the gift card program and your pos is set to print them.'))
|
||||
if not gc_program.pos_report_print_id:
|
||||
raise UserError(_('There is no print report on the gift card program and your pos is set to print them.'))
|
||||
|
||||
return super()._check_before_creating_new_session()
|
||||
|
||||
def use_coupon_code(self, code, creation_date, partner_id):
|
||||
def use_coupon_code(self, code, creation_date, partner_id, pricelist_id):
|
||||
self.ensure_one()
|
||||
# Points desc so that in coupon mode one could use a coupon multiple times
|
||||
coupon = self.env['loyalty.card'].search(
|
||||
[('program_id', 'in', self._get_program_ids().ids),
|
||||
'|', ('partner_id', 'in', (False, partner_id)), ('program_type', '=', 'gift_card'),
|
||||
('code', '=', code)],
|
||||
order='points desc', limit=1)
|
||||
if not coupon or not coupon.program_id.active:
|
||||
'|', ('partner_id', 'in', (False, partner_id)), ('program_type', '=', 'gift_card'),
|
||||
('code', '=', code)],
|
||||
order='partner_id, points desc', limit=1)
|
||||
program = coupon.program_id
|
||||
if not coupon or not program.active:
|
||||
return {
|
||||
'successful': False,
|
||||
'payload': {
|
||||
|
|
@ -87,29 +87,42 @@ class PosConfig(models.Model):
|
|||
},
|
||||
}
|
||||
check_date = fields.Date.from_string(creation_date[:11])
|
||||
if (coupon.expiration_date and coupon.expiration_date < check_date) or\
|
||||
(coupon.program_id.date_to and coupon.program_id.date_to < fields.Date.context_today(self)) or\
|
||||
(coupon.program_id.limit_usage and coupon.program_id.total_order_count >= coupon.program_id.max_usage):
|
||||
today_date = fields.Date.context_today(self)
|
||||
error_message = False
|
||||
if (
|
||||
(coupon.expiration_date and coupon.expiration_date < check_date)
|
||||
or (program.date_to and program.date_to < today_date)
|
||||
or (program.limit_usage and program.sudo().total_order_count >= program.max_usage)
|
||||
):
|
||||
error_message = _("This coupon is expired (%s).", code)
|
||||
elif program.date_from and program.date_from > today_date:
|
||||
error_message = _("This coupon is not yet valid (%s).", code)
|
||||
elif (
|
||||
not program.reward_ids or
|
||||
not any(r.required_points <= coupon.points for r in program.reward_ids)
|
||||
):
|
||||
error_message = _("No reward can be claimed with this coupon.")
|
||||
elif program.pricelist_ids and pricelist_id not in program.pricelist_ids.ids:
|
||||
error_message = _("This coupon is not available with the current pricelist.")
|
||||
elif coupon and program.program_type == 'promo_code':
|
||||
error_message = _("This programs requires a code to be applied.")
|
||||
|
||||
if error_message:
|
||||
return {
|
||||
'successful': False,
|
||||
'payload': {
|
||||
'error_message': _('This coupon is expired (%s).', code),
|
||||
},
|
||||
}
|
||||
if not coupon.program_id.reward_ids or not any(reward.required_points <= coupon.points for reward in coupon.program_id.reward_ids):
|
||||
return {
|
||||
'successful': False,
|
||||
'payload': {
|
||||
'error_message': _('No reward can be claimed with this coupon.'),
|
||||
'error_message': error_message,
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
'successful': True,
|
||||
'payload': {
|
||||
'program_id': coupon.program_id.id,
|
||||
'program_id': program.id,
|
||||
'coupon_id': coupon.id,
|
||||
'coupon_partner_id': coupon.partner_id.id,
|
||||
'points': coupon.points,
|
||||
'points_display': coupon.points_display,
|
||||
'has_source_order': coupon._has_source_order(),
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from odoo import _, models
|
|||
from odoo.tools import float_compare
|
||||
import base64
|
||||
|
||||
|
||||
class PosOrder(models.Model):
|
||||
_inherit = 'pos.order'
|
||||
|
||||
|
|
@ -52,6 +53,26 @@ class PosOrder(models.Model):
|
|||
'payload': {},
|
||||
}
|
||||
|
||||
def add_loyalty_history_lines(self, coupon_data, coupon_updates):
|
||||
id_mapping = {item['old_id']: int(item['id']) for item in coupon_updates}
|
||||
history_lines_create_vals = []
|
||||
for coupon in coupon_data:
|
||||
card_id = id_mapping.get(int(coupon['card_id']), False) or int(coupon['card_id'])
|
||||
if not self.env['loyalty.card'].browse(card_id).exists():
|
||||
continue
|
||||
issued = coupon['won']
|
||||
cost = coupon['spent']
|
||||
if (issued or cost) and card_id > 0:
|
||||
history_lines_create_vals.append({
|
||||
'card_id': card_id,
|
||||
'order_model': self._name,
|
||||
'order_id': self.id,
|
||||
'description': _('Onsite %s', self.display_name),
|
||||
'used': cost,
|
||||
'issued': issued,
|
||||
})
|
||||
self.env['loyalty.history'].create(history_lines_create_vals)
|
||||
|
||||
def confirm_coupon_programs(self, coupon_data):
|
||||
"""
|
||||
This is called after the order is created.
|
||||
|
|
@ -60,44 +81,38 @@ class PosOrder(models.Model):
|
|||
|
||||
It will also return the points of all concerned coupons to be updated in the cache.
|
||||
"""
|
||||
get_partner_id = lambda partner_id: partner_id and self.env['res.partner'].browse(partner_id).exists() and partner_id or False
|
||||
# Keys are stringified when using rpc
|
||||
coupon_data = {int(k): v for k, v in coupon_data.items()}
|
||||
|
||||
self._check_existing_loyalty_cards(coupon_data)
|
||||
self._remove_duplicate_coupon_data(coupon_data)
|
||||
self._process_existing_gift_cards(coupon_data)
|
||||
|
||||
# Map negative id to newly created ids.
|
||||
coupon_new_id_map = {k: k for k in coupon_data.keys() if k > 0}
|
||||
|
||||
# Create the coupons that were awarded by the order.
|
||||
coupons_to_create = {k: v for k, v in coupon_data.items() if k < 0 and not v.get('giftCardId')}
|
||||
coupons_to_create = {k: v for k, v in coupon_data.items() if k < 0 and (v.get('points') or v.get('line_codes'))}
|
||||
coupon_create_vals = [{
|
||||
'program_id': p['program_id'],
|
||||
'partner_id': p.get('partner_id', False),
|
||||
'code': p.get('barcode') or self.env['loyalty.card']._generate_code(),
|
||||
'partner_id': get_partner_id(p.get('partner_id', self.partner_id.id)),
|
||||
'code': p.get('code') or p.get('barcode') or self.env['loyalty.card']._generate_code(),
|
||||
'points': 0,
|
||||
'expiration_date': p.get('date_to', False),
|
||||
'source_pos_order_id': self.id,
|
||||
'expiration_date': p.get('expiration_date')
|
||||
} for p in coupons_to_create.values()]
|
||||
|
||||
# Pos users don't have the create permission
|
||||
new_coupons = self.env['loyalty.card'].with_context(action_no_send_mail=True).sudo().create(coupon_create_vals)
|
||||
|
||||
# We update the gift card that we sold when the gift_card_settings = 'scan_use'.
|
||||
gift_cards_to_update = [v for v in coupon_data.values() if v.get('giftCardId')]
|
||||
updated_gift_cards = self.env['loyalty.card']
|
||||
for coupon_vals in gift_cards_to_update:
|
||||
gift_card = self.env['loyalty.card'].browse(coupon_vals.get('giftCardId'))
|
||||
gift_card.write({
|
||||
'points': coupon_vals['points'],
|
||||
'source_pos_order_id': self.id,
|
||||
'partner_id': coupon_vals.get('partner_id', False),
|
||||
})
|
||||
updated_gift_cards |= gift_card
|
||||
|
||||
# Map the newly created coupons
|
||||
for old_id, new_id in zip(coupons_to_create.keys(), new_coupons):
|
||||
coupon_new_id_map[new_id.id] = old_id
|
||||
|
||||
all_coupons = self.env['loyalty.card'].browse(coupon_new_id_map.keys()).exists()
|
||||
# We need a sudo here because this can trigger `_compute_order_count` that require access to `sale.order.line`
|
||||
all_coupons = self.env['loyalty.card'].sudo().browse(coupon_new_id_map.keys()).exists()
|
||||
lines_per_reward_code = defaultdict(lambda: self.env['pos.order.line'])
|
||||
for line in self.lines:
|
||||
if not line.reward_identifier_code:
|
||||
|
|
@ -115,12 +130,32 @@ class PosOrder(models.Model):
|
|||
report_per_program = {}
|
||||
coupon_per_report = defaultdict(list)
|
||||
# Important to include the updated gift cards so that it can be printed. Check coupon_report.
|
||||
for coupon in new_coupons | updated_gift_cards:
|
||||
for coupon in new_coupons:
|
||||
if coupon.program_id not in report_per_program:
|
||||
report_per_program[coupon.program_id] = coupon.program_id.communication_plan_ids.\
|
||||
filtered(lambda c: c.trigger == 'create').pos_report_print_id
|
||||
for report in report_per_program[coupon.program_id]:
|
||||
coupon_per_report[report.id].append(coupon.id)
|
||||
|
||||
# Adding loyalty history lines
|
||||
loyalty_points = [
|
||||
{
|
||||
'order_id': self.id,
|
||||
'card_id': coupon_id,
|
||||
'spent': -coupon_vals['points'] if coupon_vals['points'] < 0 else 0,
|
||||
'won': coupon_vals['points'] if coupon_vals['points'] > 0 else 0,
|
||||
}
|
||||
for coupon_id, coupon_vals in coupon_data.items()
|
||||
]
|
||||
coupon_updates = [
|
||||
{
|
||||
'id': coupon.id,
|
||||
'old_id': coupon_new_id_map[coupon.id],
|
||||
}
|
||||
for coupon in all_coupons
|
||||
]
|
||||
self.add_loyalty_history_lines(loyalty_points, coupon_updates)
|
||||
|
||||
return {
|
||||
'coupon_updates': [{
|
||||
'old_id': coupon_new_id_map[coupon.id],
|
||||
|
|
@ -132,7 +167,7 @@ class PosOrder(models.Model):
|
|||
} for coupon in all_coupons if coupon.program_id.is_nominative],
|
||||
'program_updates': [{
|
||||
'program_id': program.id,
|
||||
'usages': program.total_order_count,
|
||||
'usages': program.sudo().total_order_count,
|
||||
} for program in all_coupons.program_id],
|
||||
'new_coupon_info': [{
|
||||
'program_name': coupon.program_id.name,
|
||||
|
|
@ -142,82 +177,117 @@ class PosOrder(models.Model):
|
|||
coupon.program_id.applies_on == 'future'
|
||||
# Don't send the coupon code for the gift card and ewallet programs.
|
||||
# It should not be printed in the ticket.
|
||||
and coupon.program_id.program_type not in ['gift_card', 'ewallet']
|
||||
and coupon.program_id.sudo().program_type not in ['gift_card', 'ewallet']
|
||||
)],
|
||||
'coupon_report': coupon_per_report,
|
||||
}
|
||||
|
||||
def _process_existing_gift_cards(self, coupon_data):
|
||||
updated_gift_cards = self.env['loyalty.card']
|
||||
coupon_key_to_remove = []
|
||||
for coupon_id, coupon_vals in coupon_data.items():
|
||||
program_id = self.env['loyalty.program'].browse(coupon_vals['program_id'])
|
||||
if program_id.program_type == 'gift_card':
|
||||
updated = False
|
||||
gift_card = self.env['loyalty.card'].search([
|
||||
('|'),
|
||||
('code', '=', coupon_vals.get('code', '')),
|
||||
('id', '=', coupon_vals.get('coupon_id', False))
|
||||
])
|
||||
if not gift_card.exists():
|
||||
continue
|
||||
|
||||
if not gift_card.partner_id and self.partner_id:
|
||||
updated = True
|
||||
gift_card.partner_id = self.partner_id
|
||||
gift_card.history_ids.create({
|
||||
'card_id': gift_card.id,
|
||||
'description': _('Assigning partner %s', self.partner_id.name),
|
||||
'used': 0,
|
||||
'issued': gift_card.points,
|
||||
})
|
||||
|
||||
if len([id for id in gift_card.history_ids.mapped('order_id') if id != 0]) == 0:
|
||||
updated = True
|
||||
gift_card.source_pos_order_id = self.id
|
||||
gift_card.history_ids.create({
|
||||
'card_id': gift_card.id,
|
||||
'order_model': self._name,
|
||||
'order_id': self.id,
|
||||
'description': _('Assigning order %s', self.display_name),
|
||||
'used': 0,
|
||||
'issued': gift_card.points,
|
||||
})
|
||||
|
||||
if coupon_vals.get('points') != gift_card.points:
|
||||
# Coupon vals contains negative points
|
||||
updated = True
|
||||
new_value = gift_card.points + coupon_vals['points']
|
||||
gift_card.points = new_value
|
||||
gift_card.history_ids.create({
|
||||
'card_id': gift_card.id,
|
||||
'order_model': self._name,
|
||||
'order_id': self.id,
|
||||
'description': _('Onsite %s', self.display_name),
|
||||
'used': -coupon_vals['points'] if coupon_vals['points'] < 0 else 0,
|
||||
'issued': coupon_vals['points'] if coupon_vals['points'] > 0 else 0,
|
||||
})
|
||||
|
||||
if updated:
|
||||
updated_gift_cards |= gift_card
|
||||
|
||||
coupon_key_to_remove.append(coupon_id)
|
||||
|
||||
for key in coupon_key_to_remove:
|
||||
coupon_data.pop(key, None)
|
||||
|
||||
return updated_gift_cards
|
||||
|
||||
def _check_existing_loyalty_cards(self, coupon_data):
|
||||
coupon_key_to_modify = []
|
||||
for coupon_id, coupon_vals in coupon_data.items():
|
||||
partner_id = coupon_vals.get('partner_id', False)
|
||||
if partner_id:
|
||||
partner_coupons = self.env['loyalty.card'].search(
|
||||
[('partner_id', '=', partner_id), ('program_type', '=', 'loyalty')])
|
||||
existing_coupon_for_program = partner_coupons.filtered(lambda c: c.program_id.id == coupon_vals['program_id'])
|
||||
existing_coupon_for_program = self.env['loyalty.card'].search(
|
||||
[('partner_id', '=', partner_id), ('program_type', 'in', ['loyalty', 'ewallet']), ('program_id', '=', coupon_vals['program_id'])])
|
||||
if existing_coupon_for_program:
|
||||
coupon_vals['coupon_id'] = existing_coupon_for_program[0].id
|
||||
coupon_key_to_modify.append([coupon_id, existing_coupon_for_program[0].id])
|
||||
for old_key, new_key in coupon_key_to_modify:
|
||||
coupon_data[new_key] = coupon_data.pop(old_key)
|
||||
|
||||
def _remove_duplicate_coupon_data(self, coupon_data):
|
||||
# to prevent duplicates, it is necessary to check if the history line already exists
|
||||
items_to_remove = []
|
||||
for coupon_id, coupon_vals in coupon_data.items():
|
||||
existing_history = self.env['loyalty.history'].search_count([
|
||||
('card_id.program_id', '=', coupon_vals['program_id']),
|
||||
('order_model', '=', self._name),
|
||||
('order_id', '=', self.id),
|
||||
])
|
||||
if existing_history:
|
||||
items_to_remove.append(coupon_id)
|
||||
for item in items_to_remove:
|
||||
coupon_data.pop(item)
|
||||
|
||||
def _get_fields_for_order_line(self):
|
||||
fields = super(PosOrder, self)._get_fields_for_order_line()
|
||||
fields.extend(['is_reward_line', 'reward_id', 'coupon_id', 'reward_identifier_code', 'points_cost'])
|
||||
return fields
|
||||
|
||||
def _prepare_order_line(self, order_line):
|
||||
order_line = super()._prepare_order_line(order_line)
|
||||
for f in ['reward_id', 'coupon_id']:
|
||||
if order_line.get(f):
|
||||
order_line[f] = order_line[f][0]
|
||||
return order_line
|
||||
|
||||
def _add_activated_coupon_to_draft_orders(self, table_orders):
|
||||
table_orders = super()._add_activated_coupon_to_draft_orders(table_orders)
|
||||
|
||||
for order in table_orders:
|
||||
activated_coupon = []
|
||||
|
||||
rewards_list = [{
|
||||
'reward_id': orderline[2]['reward_id'],
|
||||
'coupon_id': orderline[2]['coupon_id']
|
||||
} for orderline in order['lines'] if orderline[2]['is_reward_line'] and orderline[2]['reward_id']
|
||||
]
|
||||
|
||||
order_reward_ids = self.env['loyalty.reward'].browse(set([reward_id['reward_id'] for reward_id in rewards_list]))
|
||||
|
||||
for reward in rewards_list:
|
||||
order_reward_id = order_reward_ids.filtered(lambda order_reward: order_reward.id == reward['reward_id'])
|
||||
|
||||
if order_reward_id:
|
||||
if order_reward_id.program_type in ['gift_card', 'ewallet']:
|
||||
coupon_id = self.env['loyalty.card'].search([('id', '=', reward['coupon_id'])])
|
||||
|
||||
activated_coupon.append({
|
||||
'balance': coupon_id.points,
|
||||
'code': coupon_id.code,
|
||||
'id': coupon_id.id,
|
||||
'program_id': coupon_id.program_id.id,
|
||||
})
|
||||
|
||||
order['codeActivatedCoupons'] = activated_coupon
|
||||
|
||||
return table_orders
|
||||
|
||||
def _add_mail_attachment(self, name, ticket):
|
||||
attachment = super()._add_mail_attachment(name, ticket)
|
||||
def _add_mail_attachment(self, name, ticket, basic_receipt):
|
||||
attachment = super()._add_mail_attachment(name, ticket, basic_receipt)
|
||||
gift_card_programs = self.config_id._get_program_ids().filtered(lambda p: p.program_type == 'gift_card' and
|
||||
p.pos_report_print_id)
|
||||
if gift_card_programs:
|
||||
gift_cards = self.env['loyalty.card'].search([('source_pos_order_id', '=', self.id),
|
||||
('program_id', 'in', gift_card_programs.mapped('id'))])
|
||||
('program_id', 'in', gift_card_programs.ids)])
|
||||
if gift_cards:
|
||||
for program in gift_card_programs:
|
||||
filtered_gift_cards = gift_cards.filtered(lambda gc: gc.program_id == program)
|
||||
if filtered_gift_cards:
|
||||
action_report = program.pos_report_print_id
|
||||
report = action_report._render_qweb_pdf(action_report.report_name, filtered_gift_cards.mapped('id'))
|
||||
report = action_report._render_qweb_pdf(action_report.report_name, filtered_gift_cards.ids)
|
||||
filename = name + '.pdf'
|
||||
gift_card_pdf = self.env['ir.attachment'].create({
|
||||
'name': filename,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class PosOrderLine(models.Model):
|
||||
_inherit = 'pos.order.line'
|
||||
|
|
@ -10,7 +11,7 @@ class PosOrderLine(models.Model):
|
|||
help="Whether this line is part of a reward or not.")
|
||||
reward_id = fields.Many2one(
|
||||
'loyalty.reward', "Reward", ondelete='restrict',
|
||||
help="The reward associated with this line.")
|
||||
help="The reward associated with this line.", index='btree_not_null')
|
||||
coupon_id = fields.Many2one(
|
||||
'loyalty.card', "Coupon", ondelete='restrict',
|
||||
help="The coupon used to claim that reward.")
|
||||
|
|
@ -19,18 +20,8 @@ class PosOrderLine(models.Model):
|
|||
""")
|
||||
points_cost = fields.Float(help="How many point this reward cost on the coupon.")
|
||||
|
||||
def _order_line_fields(self, line, session_id=None):
|
||||
res = super()._order_line_fields(line, session_id)
|
||||
# coupon_id may be negative in case of new coupons, they will be added after validating the order.
|
||||
if 'coupon_id' in res[2] and res[2]['coupon_id'] < 1:
|
||||
res[2].pop('coupon_id')
|
||||
return res
|
||||
|
||||
def _is_not_sellable_line(self):
|
||||
return super().is_not_sellable_line() or self.reward_id
|
||||
|
||||
def _export_for_ui(self, orderline):
|
||||
result = super()._export_for_ui(orderline)
|
||||
result['is_reward_line'] = orderline.is_reward_line
|
||||
result['reward_id'] = orderline.reward_id.id
|
||||
return result
|
||||
@api.model
|
||||
def _load_pos_data_fields(self, config):
|
||||
params = super()._load_pos_data_fields(config)
|
||||
params += ['is_reward_line', 'reward_id', 'reward_identifier_code', 'points_cost', 'coupon_id']
|
||||
return params
|
||||
|
|
|
|||
|
|
@ -1,152 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
from odoo.osv.expression import AND
|
||||
import ast
|
||||
import json
|
||||
from odoo import models, api
|
||||
|
||||
|
||||
class PosSession(models.Model):
|
||||
_inherit = 'pos.session'
|
||||
|
||||
def _pos_ui_models_to_load(self):
|
||||
result = super()._pos_ui_models_to_load()
|
||||
if self.config_id._get_program_ids():
|
||||
result += [
|
||||
'loyalty.program',
|
||||
'loyalty.rule',
|
||||
'loyalty.reward',
|
||||
]
|
||||
return result
|
||||
|
||||
def _loader_params_loyalty_program(self):
|
||||
return {
|
||||
'search_params': {
|
||||
'domain': [('id', 'in', self.config_id._get_program_ids().ids)],
|
||||
'fields': ['name', 'trigger', 'applies_on', 'program_type', 'date_to', 'total_order_count',
|
||||
'limit_usage', 'max_usage', 'is_nominative', 'portal_visible', 'portal_point_name', 'trigger_product_ids'],
|
||||
},
|
||||
}
|
||||
|
||||
def _loader_params_loyalty_rule(self):
|
||||
return {
|
||||
'search_params': {
|
||||
'domain': [('program_id', 'in', self.config_id._get_program_ids().ids)],
|
||||
'fields': ['program_id', 'valid_product_ids', 'any_product', 'currency_id',
|
||||
'reward_point_amount', 'reward_point_split', 'reward_point_mode',
|
||||
'minimum_qty', 'minimum_amount', 'minimum_amount_tax_mode', 'mode', 'code'],
|
||||
}
|
||||
}
|
||||
|
||||
def _loader_params_loyalty_reward(self):
|
||||
domain_products = self.env['loyalty.reward']._get_active_products_domain()
|
||||
return {
|
||||
'search_params': {
|
||||
'domain': AND([[('program_id', 'in', self.config_id._get_program_ids().ids)], domain_products]),
|
||||
'fields': ['description', 'program_id', 'reward_type', 'required_points', 'clear_wallet', 'currency_id',
|
||||
'discount', 'discount_mode', 'discount_applicability', 'all_discount_product_ids', 'is_global_discount',
|
||||
'discount_max_amount', 'discount_line_product_id',
|
||||
'multi_product', 'reward_product_ids', 'reward_product_qty', 'reward_product_uom_id', 'reward_product_domain'],
|
||||
}
|
||||
}
|
||||
|
||||
def _get_pos_ui_loyalty_program(self, params):
|
||||
return self.env['loyalty.program'].search_read(**params['search_params'])
|
||||
|
||||
def _get_pos_ui_loyalty_rule(self, params):
|
||||
return self.env['loyalty.rule'].search_read(**params['search_params'])
|
||||
|
||||
def _get_pos_ui_loyalty_reward(self, params):
|
||||
rewards = self.env['loyalty.reward'].search_read(**params['search_params'])
|
||||
for reward in rewards:
|
||||
reward['reward_product_domain'] = self._replace_ilike_with_in(reward['reward_product_domain'])
|
||||
return rewards
|
||||
|
||||
def _replace_ilike_with_in(self, domain_str):
|
||||
if domain_str == "null":
|
||||
return domain_str
|
||||
|
||||
domain = ast.literal_eval(domain_str)
|
||||
|
||||
for index, condition in enumerate(domain):
|
||||
if isinstance(condition, (list, tuple)) and len(condition) == 3:
|
||||
field_name, operator, value = condition
|
||||
field = self.env['product.product']._fields.get(field_name)
|
||||
|
||||
if field and field.type == 'many2one' and operator in ('ilike', 'not ilike'):
|
||||
comodel = self.env[field.comodel_name]
|
||||
matching_ids = list(comodel._name_search(value, [], operator, limit=None))
|
||||
|
||||
new_operator = 'in' if operator == 'ilike' else 'not in'
|
||||
domain[index] = [field_name, new_operator, matching_ids]
|
||||
|
||||
return json.dumps(domain)
|
||||
|
||||
def _get_pos_ui_product_product(self, params):
|
||||
result = super()._get_pos_ui_product_product(params)
|
||||
self = self.with_context(**params['context'])
|
||||
rewards = self.config_id._get_program_ids().reward_ids
|
||||
products = rewards.discount_line_product_id | rewards.reward_product_ids
|
||||
products |= self.config_id._get_program_ids().filtered(lambda p: p.program_type == 'ewallet').trigger_product_ids
|
||||
# Only load products that are not already in the result
|
||||
products = list(set(products.ids) - set(product['id'] for product in result))
|
||||
products = self.env['product.product'].search_read([('id', 'in', products)], fields=params['search_params']['fields'])
|
||||
self._process_pos_ui_product_product(products)
|
||||
result.extend(products)
|
||||
return result
|
||||
|
||||
def _get_pos_ui_res_partner(self, params):
|
||||
partners = super()._get_pos_ui_res_partner(params)
|
||||
self._set_loyalty_cards(partners)
|
||||
return partners
|
||||
|
||||
def get_pos_ui_res_partner_by_params(self, custom_search_params):
|
||||
partners = super().get_pos_ui_res_partner_by_params(custom_search_params)
|
||||
self._set_loyalty_cards(partners)
|
||||
return partners
|
||||
|
||||
def _set_loyalty_cards(self, partners):
|
||||
# Map partner_id to its loyalty cards from all loyalty programs.
|
||||
loyalty_programs = self.config_id._get_program_ids().filtered(lambda p: p.program_type == 'loyalty')
|
||||
loyalty_card_fields = ['points', 'code', 'program_id']
|
||||
partner_id_to_loyalty_card = {}
|
||||
for group in self.env['loyalty.card'].read_group(
|
||||
domain=[('partner_id', 'in', [p['id'] for p in partners]), ('program_id', 'in', loyalty_programs.ids)],
|
||||
fields=[f"{field_name}:array_agg" for field_name in loyalty_card_fields] + ["ids:array_agg(id)"],
|
||||
groupby=['partner_id']
|
||||
):
|
||||
loyalty_cards = {}
|
||||
for i in range(group['partner_id_count']):
|
||||
loyalty_cards[group['ids'][i]] = {field_name: group[field_name][i] for field_name in loyalty_card_fields}
|
||||
partner_id_to_loyalty_card[group['partner_id'][0]] = loyalty_cards
|
||||
|
||||
# Assign loyalty cards to each partner to load.
|
||||
for partner in partners:
|
||||
partner['loyalty_cards'] = partner_id_to_loyalty_card.get(partner['id'], {})
|
||||
|
||||
return partners
|
||||
|
||||
def _pos_data_process(self, loaded_data):
|
||||
super()._pos_data_process(loaded_data)
|
||||
|
||||
# Additional post processing to link gift card and ewallet programs
|
||||
# to their rules' products.
|
||||
# Important because points from their products are only counted once.
|
||||
product_id_to_program_ids = {}
|
||||
for program in self.config_id._get_program_ids():
|
||||
if program.program_type in ['gift_card', 'ewallet']:
|
||||
for product in program.trigger_product_ids:
|
||||
product_id_to_program_ids.setdefault(product['id'], [])
|
||||
product_id_to_program_ids[product['id']].append(program['id'])
|
||||
|
||||
loaded_data['product_id_to_program_ids'] = product_id_to_program_ids
|
||||
product_product_fields = self.env['product.product'].fields_get(self._loader_params_product_product()['search_params']['fields'])
|
||||
loaded_data['field_types'] = {
|
||||
'product.product': {f:v['type'] for f, v in product_product_fields.items()}
|
||||
}
|
||||
|
||||
def _loader_params_product_product(self):
|
||||
params = super()._loader_params_product_product()
|
||||
# this is usefull to evaluate reward domain in frontend
|
||||
params['search_params']['fields'].append('all_product_tag_ids')
|
||||
return params
|
||||
@api.model
|
||||
def _load_pos_data_models(self, config):
|
||||
data = super()._load_pos_data_models(config)
|
||||
data += ['loyalty.program', 'loyalty.rule', 'loyalty.reward', 'loyalty.card']
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
from odoo import api, models
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
@api.model
|
||||
def _load_pos_data_fields(self, config):
|
||||
params = super()._load_pos_data_fields(config)
|
||||
params += ['all_product_tag_ids']
|
||||
|
||||
# add missing product fields used in the reward_product_domain
|
||||
missing_fields = self.env['loyalty.reward']._get_reward_product_domain_fields(config) - set(params)
|
||||
|
||||
if missing_fields:
|
||||
params.extend([field for field in missing_fields if field in self._fields])
|
||||
|
||||
return params
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
from odoo import models
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = 'product.template'
|
||||
|
||||
def _load_pos_data_search_read(self, data, config):
|
||||
read_data = super()._load_pos_data_search_read(data, config)
|
||||
|
||||
rewards = config._get_program_ids().reward_ids
|
||||
reward_products = rewards.discount_line_product_id | rewards.reward_product_ids | rewards.reward_product_id
|
||||
trigger_products = config._get_program_ids().trigger_product_ids
|
||||
|
||||
loyalty_product_tmpl_ids = set((reward_products | trigger_products)._filtered_access('read').product_tmpl_id.ids)
|
||||
already_loaded_product_tmpl_ids = {template['id'] for template in read_data}
|
||||
|
||||
missing_product_tmpl_ids = list(loyalty_product_tmpl_ids - already_loaded_product_tmpl_ids)
|
||||
fields = self.env['product.template']._load_pos_data_fields(config)
|
||||
|
||||
missing_product_templates = self.env['product.template'].browse(missing_product_tmpl_ids).read(fields=fields, load=False)
|
||||
product_ids_to_hide = reward_products.product_tmpl_id - self.env['product.template'].browse(already_loaded_product_tmpl_ids)
|
||||
|
||||
if self.env.context.get('pos_limited_loading', True):
|
||||
# Filter out products that can be loaded in the PoS but are not loaded yet
|
||||
product_ids_to_hide = product_ids_to_hide - product_ids_to_hide.filtered_domain(self._load_pos_data_domain(data, config))
|
||||
|
||||
config_data = data['pos.config'][0]
|
||||
config_data['_pos_special_products_ids'] += product_ids_to_hide.product_variant_id.ids
|
||||
|
||||
# Identify special loyalty products (e.g., gift cards, e-wallets) to be displayed in the POS
|
||||
loyality_products = config.get_record_by_ref([
|
||||
'loyalty.gift_card_product_50',
|
||||
'loyalty.ewallet_product_50',
|
||||
])
|
||||
special_display_products = self.env['product.product'].search([('id', 'in', loyality_products)])
|
||||
# Include trigger products from loyalty programs of type 'gift_card' or 'ewallet'
|
||||
special_display_products += self.env['loyalty.program'].search([
|
||||
('program_type', 'in', ['ewallet']),
|
||||
('pos_config_ids', 'in', [False, config.id]),
|
||||
]).trigger_product_ids._filtered_access('read')
|
||||
|
||||
config_data['_pos_special_display_products_ids'] = special_display_products.product_tmpl_id.ids
|
||||
|
||||
read_data.extend(missing_product_templates)
|
||||
return read_data
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
pos_gift_card_settings = fields.Selection(related='pos_config_id.gift_card_settings', readonly=False)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
loyalty_card_count = fields.Integer(groups='base.group_user,point_of_sale.group_pos_user')
|
||||
Loading…
Add table
Add a link
Reference in a new issue