19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:30:27 +01:00
parent d1963a3c3a
commit 2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import base_partner_merge
from . import loyalty_card_update_balance
from . import loyalty_generate_wizard

View file

@ -0,0 +1,45 @@
from odoo import models
class BasePartnerMergeAutomaticWizard(models.TransientModel):
_inherit = 'base.partner.merge.automatic.wizard'
def _update_foreign_keys(self, src_partners, dst_partner):
""" Override of base to merge corresponding nominative loyalty cards."""
self._merge_loyalty_cards(src_partners, dst_partner)
super()._update_foreign_keys(src_partners, dst_partner)
def _merge_loyalty_cards(self, src_partners, dst_partner):
""" Merge nominative loyalty cards.
:param src_partners: recordset of source res.partner records to merge
:param dst_partner: destination res.partner record
"""
LoyaltyCard = self.env['loyalty.card'].sudo()
cards_per_program = dict(
LoyaltyCard._read_group(
domain=[
('partner_id', 'in', src_partners.ids),
'|',
('program_id.applies_on', '=', 'both'),
'&',
('program_id.program_type', 'in', ('ewallet', 'loyalty')),
('program_id.applies_on', '=', 'future'),
],
groupby=['program_id'],
aggregates=['id:recordset'],
)
)
for program, cards in cards_per_program.items():
total_points = sum(card.points for card in cards)
dst_card = LoyaltyCard.search([
('partner_id', '=', dst_partner.id),
('program_id', '=', program.id),
], limit=1)
if dst_card:
final_card = dst_card
total_points += dst_card.points
else:
final_card = cards[0]
final_card.sudo().write({'partner_id': dst_partner.id, 'points': total_points})
(cards - final_card).sudo().write({'points': 0, 'active': False})

View file

@ -0,0 +1,39 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, fields, models
from odoo.exceptions import ValidationError
class LoyaltyCardUpdateBalance(models.TransientModel):
_name = 'loyalty.card.update.balance'
_description = "Update Loyalty Card Points"
card_id = fields.Many2one(
comodel_name='loyalty.card',
required=True,
readonly=True,
)
old_balance = fields.Float(related='card_id.points')
new_balance = fields.Float()
description = fields.Char(required=True)
def action_update_card_point(self):
if self.old_balance == self.new_balance or self.new_balance < 0:
raise ValidationError(
_("New Balance should be positive and different then old balance.")
)
difference = self.new_balance - self.old_balance
used = 0
issued = 0
if difference > 0:
issued = difference
else:
used = abs(difference)
self.env['loyalty.history'].create({
'card_id': self.card_id.id,
'description': self.description or _("Gift for customer"),
'used': used,
'issued': issued,
})
self.card_id.points = self.new_balance

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="loyalty_card_update_balance_form" model="ir.ui.view">
<field name="name">loyalty.card.update.balance.view.form</field>
<field name="model">loyalty.card.update.balance</field>
<field name="arch" type="xml">
<form string="Update Balance">
<sheet>
<h3>You are about to change the balance of the card</h3>
<group>
<field name="card_id" invisible="1"/>
<field name="old_balance"/>
<field name="new_balance"/>
<field name="description" placeholder="Example: Gift for customer"/>
</group>
</sheet>
<footer>
<button
name="action_update_card_point"
type="object"
class="btn-primary"
data-hotkey="s"
>
Confirm
</button>
<button special="cancel" data-hotkey="x">Cancel</button>
</footer>
</form>
</field>
</record>
</odoo>

View file

@ -1,43 +1,56 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo.osv import expression
from odoo.fields import Domain
class LoyaltyGenerateWizard(models.TransientModel):
_name = 'loyalty.generate.wizard'
_description = 'Generate Coupons'
_description = "Generate Coupons"
program_id = fields.Many2one('loyalty.program', required=True, default=lambda self: self.env.context.get('active_id', False) or self.env.context.get('default_program_id', False))
program_type = fields.Selection(related='program_id.program_type')
mode = fields.Selection([
('anonymous', 'Anonymous Customers'),
('selected', 'Selected Customers')],
('anonymous', "Anonymous Customers"),
('selected', "Selected Customers")],
string='For', required=True, default='anonymous'
)
customer_ids = fields.Many2many('res.partner', string='Customers')
customer_tag_ids = fields.Many2many('res.partner.category', string='Customer Tags')
coupon_qty = fields.Integer('Quantity',
coupon_qty = fields.Integer("Quantity",
compute='_compute_coupon_qty', readonly=False, store=True)
points_granted = fields.Float('Grant', required=True, default=1)
points_name = fields.Char(related='program_id.portal_point_name', readonly=True)
valid_until = fields.Date()
will_send_mail = fields.Boolean(compute='_compute_will_send_mail')
confirmation_message = fields.Char(compute='_compute_confirmation_message')
description = fields.Text(string="Description")
def _get_partners(self):
self.ensure_one()
if self.mode != 'selected':
return self.env['res.partner']
domain = []
domains = []
if self.customer_ids:
domain = [('id', 'in', self.customer_ids.ids)]
domains.append([('id', 'in', self.customer_ids.ids)])
if self.customer_tag_ids:
domain = expression.OR([domain, [('category_id', 'in', self.customer_tag_ids.ids)]])
return self.env['res.partner'].search(domain)
domains.append([('category_id', 'in', self.customer_tag_ids.ids)])
return self.env['res.partner'].search(Domain.OR(domains) if domains else Domain.TRUE)
@api.depends('program_type', 'points_granted', 'coupon_qty')
def _compute_confirmation_message(self):
self.confirmation_message = False
for wizard in self:
program_desc = dict(wizard._fields['program_type']._description_selection(wizard.env))
wizard.confirmation_message = _("You're about to generate %(program_type)s with a value of %(value)s for %(customer_number)i customers",
program_type=program_desc[wizard.program_type],
value=wizard.points_granted,
customer_number=wizard.coupon_qty,
)
@api.depends('customer_ids', 'customer_tag_ids', 'mode')
def _compute_coupon_qty(self):
@ -71,5 +84,12 @@ class LoyaltyGenerateWizard(models.TransientModel):
customers = wizard._get_partners() or range(wizard.coupon_qty)
for partner in customers:
coupon_create_vals.append(wizard._get_coupon_values(partner))
self.env['loyalty.card'].create(coupon_create_vals)
return True
coupons = self.env['loyalty.card'].create(coupon_create_vals)
self.env['loyalty.history'].create([
{
'description': self.description or _("Gift For Customer"),
'card_id': coupon.id,
'issued': self.points_granted,
} for coupon in coupons
])
return coupons

View file

@ -11,40 +11,47 @@
<field name="program_id" invisible="1"/>
<field name="will_send_mail" invisible="1"/>
<field name="program_type" invisible="1"/>
<field name="mode" widget="radio" attrs="{'invisible': [('program_type', '=', 'ewallet')]}"/>
<field name="customer_ids" widget="many2many_tags" attrs="{'invisible': [('mode', '=', 'anonymous')]}"/>
<field name="customer_tag_ids" widget="many2many_tags" attrs="{'invisible': [('mode', '=', 'anonymous')]}" options="{'color_field': 'color'}"/>
<field name="coupon_qty" string="Quantity to generate" attrs="{'readonly': [('mode', '=', 'selected')], 'required': [('mode', '=', 'anonymous')]}"/>
<label string="Coupon value" for="points_granted" groups="base.group_no_one" attrs="{'invisible': [('program_type', '!=', 'coupons')]}"/>
<span class="d-inline-block" groups="base.group_no_one" attrs="{'invisible': [('program_type', '!=', 'coupons')]}">
<field name="mode" widget="radio" invisible="program_type == 'ewallet'"/>
<field name="customer_ids" widget="many2many_tags" placeholder="For all customers" invisible="mode == 'anonymous'"/>
<field name="customer_tag_ids" widget="many2many_tags" invisible="mode == 'anonymous'" options="{'color_field': 'color'}"/>
<field name="coupon_qty" string="Quantity to generate" readonly="mode == 'selected'" required="mode == 'anonymous'"/>
<label string="Coupon value" for="points_granted" groups="base.group_no_one" invisible="program_type != 'coupons'"/>
<span class="d-inline-block" groups="base.group_no_one" invisible="program_type != 'coupons'">
<field name="points_granted" class="oe_inline"/>
<field name="points_name" class="oe_inline"/>
</span>
<label string="Gift Card value" for="points_granted" attrs="{'invisible': [('program_type', '!=', 'gift_card')]}"/>
<span class="d-inline-block" attrs="{'invisible': [('program_type', '!=', 'gift_card')]}">
<label string="Gift Card value" for="points_granted" invisible="program_type != 'gift_card'"/>
<span class="d-inline-block" invisible="program_type != 'gift_card'">
<field name="points_granted" class="w-auto oe_inline me-1"/>
<field name="points_name" class="d-inline" no_label="1"/>
</span>
<label string="eWallet value" for="points_granted" attrs="{'invisible': [('program_type', '!=', 'ewallet')]}"/>
<span class="d-inline-block" attrs="{'invisible': [('program_type', '!=', 'ewallet')]}">
<label string="eWallet value" for="points_granted" invisible="program_type != 'ewallet'"/>
<span class="d-inline-block" invisible="program_type != 'ewallet'">
<field name="points_granted" class="w-auto oe_inline me-1"/>
<field name="points_name" class="d-inline" no_label="1"/>
</span>
<field name="valid_until"/>
</group>
<group class="d-flex flex-column">
<div class="border-bottom w-100 fw-bold">Description</div>
<field name="description" nolabel="1" placeholder="Example: Gift for customer" required="True"/>
</group>
</group>
<div class="alert alert-warning text-center" invisible="customer_ids or mode == 'anonymous'" role="alert">
<field name="confirmation_message"/>
</div>
</sheet>
<footer>
<button name="generate_coupons" type="object" class="btn-primary" data-hotkey="q">
<span attrs="{'invisible': [('will_send_mail', '=', False)]}">
<span invisible="not will_send_mail">
Generate and Send
</span>
<span attrs="{'invisible': [('will_send_mail', '=', True)]}">
<span invisible="will_send_mail">
Generate
</span>
<field name="program_type" nolabel="1"/>
</button>
<button special="cancel" data-hotkey="z" string="Cancel"/>
<button special="cancel" data-hotkey="x" string="Cancel"/>
</footer>
</form>
</field>