Update Method with Total Spend
This commit is contained in:
parent
8816c44c4c
commit
3282f7ab1b
17
README.md
17
README.md
@ -12,7 +12,7 @@ This module extends Odoo 19's Point of Sale to support **automatic multi-level l
|
||||
Customer pays / gets refund at POS
|
||||
│
|
||||
▼
|
||||
Sum all paid pos.order amounts for this customer
|
||||
Update customer's total_spend field
|
||||
│
|
||||
▼
|
||||
Fetch loyalty.program records where
|
||||
@ -34,7 +34,7 @@ Compare with current res.partner.membership_level_id
|
||||
|
||||
1. **Trigger** — The module hooks into `action_pos_order_paid()`, which is called every time a POS order is finalized (both regular sales and refunds).
|
||||
|
||||
2. **Calculate Total Purchases** — Sums the `amount_total` field from all `pos.order` records with state `paid` or `done` for the customer. Refund orders naturally have a negative `amount_total`, so they reduce the total automatically.
|
||||
2. **Track Total Spend** — Increments or decrements the `total_spend` field on the customer's `res.partner` profile by the order's `amount_total`. Refund orders naturally have a negative `amount_total`, so they reduce the total automatically.
|
||||
|
||||
3. **Fetch Loyalty Levels** — Retrieves all `loyalty.program` records where the `multi_level_membership` field (Boolean) is `True`, sorted by `minimum_spend` in ascending order.
|
||||
|
||||
@ -78,18 +78,21 @@ Suppose you have three loyalty tiers configured:
|
||||
## Module Structure
|
||||
|
||||
```
|
||||
pos_loyalty_auto_level/
|
||||
pos_loyalty_auto_level_new/
|
||||
├── __init__.py
|
||||
├── __manifest__.py
|
||||
├── README.md
|
||||
└── models/
|
||||
├── __init__.py
|
||||
└── pos_order.py # Core logic: overrides action_pos_order_paid()
|
||||
├── models/
|
||||
│ ├── __init__.py
|
||||
│ ├── pos_order.py # Core logic: overrides action_pos_order_paid()
|
||||
│ └── res_partner.py # Adds total_spend field to res.partner
|
||||
└── views/
|
||||
└── res_partner_views.xml # Displays total_spend field on partner form
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
1. Place the `pos_loyalty_auto_level` folder in your Odoo `custom/` addons directory.
|
||||
1. Place the `pos_loyalty_auto_level_new` folder in your Odoo `custom/` addons directory.
|
||||
2. Update the apps list: **Settings → Apps → Update Apps List**.
|
||||
3. Search for **"POS Loyalty Auto Level Update"** and click **Install**.
|
||||
|
||||
|
||||
@ -11,14 +11,16 @@ POS Loyalty Auto Level Update
|
||||
This module automates the management of customer loyalty membership tiers directly within the Point of Sale system.
|
||||
|
||||
Key Features:
|
||||
1. **Automated Lifetime Spend Tracking**: After every POS sale or refund, the module calculates the customer's cumulative purchase amount from all `paid` or `done` `pos.order` records.
|
||||
2. **Tier Evaluation**: Compares the customer's total spend against the `minimum_spend` thresholds defined on `loyalty.program` records (specifically those marked as `multi_level_membership`).
|
||||
3. **Seamless Upgrades/Downgrades**: Automatically determines the highest qualifying membership level and updates the customer's `membership_level_id` on their `res.partner` profile.
|
||||
4. **Intelligent Point Consolidation**: If a customer changes tiers, the module seamlessly sweeps loyalty points from any of their older tier cards and consolidates them into the new membership card. It hooks directly into the POS frontend's point confirmation (`confirm_coupon_programs`) to ensure that even points earned in the current transaction are properly swept over.
|
||||
5. **Detailed Logging**: Provides developer-level console logs in the browser to trace the loyalty processing and synchronization steps during checkout.
|
||||
1. **Automated Lifetime Spend Tracking**: After every POS sale or refund, the module increments or decrements a new `total_spend` field on the customer's `res.partner` profile.
|
||||
2. **Tier Evaluation**: Compares the customer's `total_spend` against the `minimum_spend` thresholds defined on `loyalty.program` records (specifically those marked as `multi_level_membership`).
|
||||
3. **Seamless Upgrades/Downgrades**: Automatically determines the highest qualifying membership level and updates the customer's `membership_level_id` synchronously.
|
||||
4. **Intelligent Point Consolidation**: If a customer changes tiers, the module seamlessly sweeps loyalty points from any of their older tier cards and consolidates them into the new membership card. This happens synchronously to ensure all points are perfectly transferred without race conditions.
|
||||
5. **High Performance**: Evaluates membership instantly without recalculating the entire transaction history by leveraging the `total_spend` field.
|
||||
""",
|
||||
'depends': ['point_of_sale', 'pos_loyalty'],
|
||||
'data': [],
|
||||
'data': [
|
||||
'views/res_partner_views.xml',
|
||||
],
|
||||
'assets': {
|
||||
'point_of_sale._assets_pos': [
|
||||
'pos_loyalty_auto_level/static/src/app/**/*',
|
||||
|
||||
@ -1 +1,2 @@
|
||||
from . import pos_order
|
||||
from . import res_partner
|
||||
|
||||
@ -1,25 +1,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
import threading
|
||||
from odoo import api, models, fields
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
def _threaded_membership_update(registry, uid, context, order_id):
|
||||
"""Background worker to update membership levels without blocking the payment transaction."""
|
||||
try:
|
||||
with registry.cursor() as new_cr:
|
||||
new_env = api.Environment(new_cr, uid, context)
|
||||
order = new_env['pos.order'].browse(order_id)
|
||||
if not order.exists() or not order.partner_id:
|
||||
return
|
||||
|
||||
order._update_customer_membership_level_logic()
|
||||
new_cr.commit()
|
||||
except Exception as e:
|
||||
_logger.error("Background membership update failed: %s", e)
|
||||
|
||||
class PosOrder(models.Model):
|
||||
_inherit = 'pos.order'
|
||||
|
||||
@ -29,13 +11,11 @@ class PosOrder(models.Model):
|
||||
|
||||
for order in self:
|
||||
if order.partner_id:
|
||||
# Dispatch to background thread
|
||||
thread = threading.Thread(
|
||||
target=_threaded_membership_update,
|
||||
args=(self.env.registry, self.env.uid, self.env.context, order.id)
|
||||
)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
# Update total spend
|
||||
order.partner_id.sudo().total_spend += order.amount_total
|
||||
|
||||
# Update membership level synchronously
|
||||
order._update_customer_membership_level_logic()
|
||||
return res
|
||||
|
||||
def confirm_coupon_programs(self, coupon_data):
|
||||
@ -43,12 +23,7 @@ class PosOrder(models.Model):
|
||||
res = super().confirm_coupon_programs(coupon_data)
|
||||
for order in self:
|
||||
if order.partner_id:
|
||||
thread = threading.Thread(
|
||||
target=_threaded_membership_update,
|
||||
args=(self.env.registry, self.env.uid, self.env.context, order.id)
|
||||
)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
order._update_customer_membership_level_logic()
|
||||
return res
|
||||
|
||||
def _update_customer_membership_level_logic(self):
|
||||
@ -56,8 +31,8 @@ class PosOrder(models.Model):
|
||||
self.ensure_one()
|
||||
partner = self.partner_id
|
||||
|
||||
# 1. Calculate total purchases (Optimized SQL SUM)
|
||||
total_purchases = self._get_customer_total_purchases(partner)
|
||||
# 1. Get total purchases from the new total_spend field
|
||||
total_purchases = partner.total_spend
|
||||
|
||||
# 2. Get multi-level programs
|
||||
loyalty_programs = self.env['loyalty.program'].sudo().search(
|
||||
@ -115,9 +90,3 @@ class PosOrder(models.Model):
|
||||
new_card.points += pts
|
||||
old_card.points = 0
|
||||
|
||||
@api.model
|
||||
def _get_customer_total_purchases(self, partner):
|
||||
"""SQL SUM aggregation for high performance."""
|
||||
domain = [('partner_id', '=', partner.id), ('state', 'in', ('paid', 'done'))]
|
||||
res = self.env['pos.order'].sudo()._read_group(domain, aggregates=['amount_total:sum'])
|
||||
return res[0][0] if res else 0.0
|
||||
|
||||
8
models/res_partner.py
Normal file
8
models/res_partner.py
Normal file
@ -0,0 +1,8 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
total_spend = fields.Float(string='Total Spend')
|
||||
13
views/res_partner_views.xml
Normal file
13
views/res_partner_views.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_partner_form_inherit_total_spend" model="ir.ui.view">
|
||||
<field name="name">res.partner.form.inherit.total.spend</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page[@name='sales_purchases']//group[@name='sale']" position="inside">
|
||||
<field name="total_spend"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Loading…
Reference in New Issue
Block a user