first commit
This commit is contained in:
commit
ccdc2f95df
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Editors and IDEs
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
.DS_Store
|
||||||
28
README.md
Normal file
28
README.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# POS Loyalty Member Customization
|
||||||
|
|
||||||
|
This custom Odoo module enhances the POS Loyalty interface and partner creation wizard, optimizing workflow efficiency and resolving critical multi-company operational constraints.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
1. **Strict Customer Selection Filter:**
|
||||||
|
- Limits customer loading and search lists in the POS UI strictly to real individuals who hold an active membership or loyalty card (`is_loyalty_member = True`).
|
||||||
|
- Automatically excludes cashier accounts, user accounts, and company partners from being selected as customers.
|
||||||
|
|
||||||
|
2. **Simplified Customer Creation Wizard:**
|
||||||
|
- Overrides the POS Customer Edit/Creation screen to show only essential details:
|
||||||
|
* Name
|
||||||
|
* Phone
|
||||||
|
* Email
|
||||||
|
* Address (Street, Zip, City, State, Country)
|
||||||
|
* Birthday
|
||||||
|
|
||||||
|
3. **Automatic Lowest Level Membership Assignment:**
|
||||||
|
- Automatically provisions new partners with a loyalty card for the lowest membership tier (`Membership Silver`) immediately upon registration.
|
||||||
|
|
||||||
|
4. **Robust Multi-Company Loyalty Processing:**
|
||||||
|
- Solves a core Odoo 19 multi-company operational bug where cashiers in branch companies encounter "Access Errors" when validating orders for customers whose loyalty cards belong to the parent company.
|
||||||
|
- Bypasses company-specific record rules during POS loyalty audit logging using standard and safe `sudo` environment execution.
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
- **JS OWL Patching:** Overrides POS frontend partner management safely using clean, standard owl-level class overrides.
|
||||||
|
- **Python Inheritance:** Customizes backend models (`res.partner`, `pos.order`) to clean loading domains and securely provision cross-company data.
|
||||||
1
__init__.py
Normal file
1
__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from . import models
|
||||||
32
__manifest__.py
Normal file
32
__manifest__.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
'name': 'POS Loyalty Member Customizations',
|
||||||
|
'version': '19.0.1.0.0',
|
||||||
|
'summary': 'Limit customer search to loyalty members, simplify partner POS form, and auto-assign silver membership to new partners.',
|
||||||
|
'description': """
|
||||||
|
Custom POS Loyalty and Membership management features:
|
||||||
|
1. Limit customer search in POS UI to loyalty program members (who have a loyalty card).
|
||||||
|
2. Simplify new partner/edit partner wizard in POS to only require Name, Phone, Email, Address, and Birthday.
|
||||||
|
3. Automatically assign new customers to the lowest membership program level (Membership Silver) by creating a loyalty card.
|
||||||
|
""",
|
||||||
|
'category': 'Sales/Point of Sale',
|
||||||
|
'author': 'Suherdy Yacob',
|
||||||
|
'depends': [
|
||||||
|
'base',
|
||||||
|
'point_of_sale',
|
||||||
|
'loyalty',
|
||||||
|
'pos_loyalty',
|
||||||
|
'res_partner_extended',
|
||||||
|
'pos_loyalty_multi_level',
|
||||||
|
],
|
||||||
|
'data': [
|
||||||
|
'views/res_partner_views.xml',
|
||||||
|
],
|
||||||
|
'assets': {
|
||||||
|
'point_of_sale._assets_pos': [
|
||||||
|
'pos_loyalty_member_custom/static/src/app/screens/partner_list_patch.js',
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'installable': True,
|
||||||
|
'application': False,
|
||||||
|
'license': 'LGPL-3',
|
||||||
|
}
|
||||||
2
models/__init__.py
Normal file
2
models/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from . import res_partner
|
||||||
|
from . import pos_order
|
||||||
9
models/pos_order.py
Normal file
9
models/pos_order.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from odoo import models
|
||||||
|
|
||||||
|
class PosOrder(models.Model):
|
||||||
|
_inherit = 'pos.order'
|
||||||
|
|
||||||
|
def add_loyalty_history_lines(self, coupon_data, coupon_updates):
|
||||||
|
# Override to execute with sudo() to bypass multi-company record rules
|
||||||
|
# when cashier in one company updates a card belonging to another company
|
||||||
|
return super(PosOrder, self.sudo()).add_loyalty_history_lines(coupon_data, coupon_updates)
|
||||||
69
models/res_partner.py
Normal file
69
models/res_partner.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
class ResPartner(models.Model):
|
||||||
|
_inherit = 'res.partner'
|
||||||
|
|
||||||
|
loyalty_card_ids = fields.One2many(
|
||||||
|
'loyalty.card',
|
||||||
|
'partner_id',
|
||||||
|
string='Loyalty Cards'
|
||||||
|
)
|
||||||
|
|
||||||
|
is_loyalty_member = fields.Boolean(
|
||||||
|
string='Is Loyalty Member',
|
||||||
|
compute='_compute_is_loyalty_member',
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends('loyalty_card_ids')
|
||||||
|
def _compute_is_loyalty_member(self):
|
||||||
|
for partner in self:
|
||||||
|
partner.is_loyalty_member = bool(partner.loyalty_card_ids)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _load_pos_data_domain(self, data, config):
|
||||||
|
domain = super()._load_pos_data_domain(data, config)
|
||||||
|
# OR together the standard POS loaded partners (cashier, active orders) with all individual loyalty members
|
||||||
|
return ['|'] + domain + ['&', ('is_company', '=', False), ('loyalty_card_ids', '!=', False)]
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _load_pos_data_fields(self, config):
|
||||||
|
fields = super()._load_pos_data_fields(config)
|
||||||
|
if 'is_loyalty_member' not in fields:
|
||||||
|
fields.append('is_loyalty_member')
|
||||||
|
return fields
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def get_new_partner(self, config_id, domain, offset):
|
||||||
|
# Limit active searches strictly to individual customers who are loyalty members
|
||||||
|
domain.extend([('is_company', '=', False), ('loyalty_card_ids', '!=', False)])
|
||||||
|
return super().get_new_partner(config_id, domain, offset)
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
partners = super().create(vals_list)
|
||||||
|
for partner in partners:
|
||||||
|
if partner.is_company:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Find the lowest membership program level (Membership Silver)
|
||||||
|
lowest_program = self.env['loyalty.program'].sudo().search(
|
||||||
|
[('multi_level_membership', '=', True)],
|
||||||
|
order='minimum_spend asc',
|
||||||
|
limit=1
|
||||||
|
)
|
||||||
|
if lowest_program:
|
||||||
|
existing_card = self.env['loyalty.card'].sudo().search([
|
||||||
|
('partner_id', '=', partner.id),
|
||||||
|
('program_id', '=', lowest_program.id)
|
||||||
|
], limit=1)
|
||||||
|
if not existing_card:
|
||||||
|
self.env['loyalty.card'].sudo().create({
|
||||||
|
'partner_id': partner.id,
|
||||||
|
'program_id': lowest_program.id,
|
||||||
|
'points': 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
if hasattr(partner, 'membership_level_id') and not partner.membership_level_id:
|
||||||
|
partner.sudo().write({'membership_level_id': lowest_program.id})
|
||||||
|
|
||||||
|
return partners
|
||||||
12
static/src/app/screens/partner_list_patch.js
Normal file
12
static/src/app/screens/partner_list_patch.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import { PartnerList } from "@point_of_sale/app/screens/partner_list/partner_list";
|
||||||
|
import { patch } from "@web/core/utils/patch";
|
||||||
|
|
||||||
|
patch(PartnerList.prototype, {
|
||||||
|
getPartners(partners) {
|
||||||
|
// Filter the partner list to strictly only display partners who are loyalty members
|
||||||
|
const filteredPartners = partners.filter((p) => p.is_loyalty_member);
|
||||||
|
return super.getPartners(filteredPartners);
|
||||||
|
}
|
||||||
|
});
|
||||||
36
views/res_partner_views.xml
Normal file
36
views/res_partner_views.xml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_partner_form_pos_simplified" model="ir.ui.view">
|
||||||
|
<field name="name">res.partner.form.pos.simplified</field>
|
||||||
|
<field name="model">res.partner</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Customer">
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<group string="Personal Information">
|
||||||
|
<field name="name" required="1" placeholder="Customer Name"/>
|
||||||
|
<field name="phone" placeholder="Phone Number"/>
|
||||||
|
<field name="email" placeholder="Email Address"/>
|
||||||
|
<field name="birth_date" string="Birthday"/>
|
||||||
|
</group>
|
||||||
|
<group string="Address Information">
|
||||||
|
<label for="street" string="Address"/>
|
||||||
|
<div class="o_address_format">
|
||||||
|
<field name="street" placeholder="Street..." class="o_address_street"/>
|
||||||
|
<field name="street2" placeholder="Street 2..." class="o_address_street"/>
|
||||||
|
<field name="city" placeholder="City" class="o_address_city"/>
|
||||||
|
<field name="state_id" class="o_address_state" placeholder="State" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="zip" placeholder="ZIP" class="o_address_zip"/>
|
||||||
|
<field name="country_id" placeholder="Country" class="o_address_country" options="{'no_open': True, 'no_create': True}"/>
|
||||||
|
</div>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="point_of_sale.res_partner_action_edit_pos" model="ir.actions.act_window">
|
||||||
|
<field name="view_id" ref="view_partner_form_pos_simplified"/>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
Loading…
Reference in New Issue
Block a user