133 lines
4.9 KiB
Markdown
133 lines
4.9 KiB
Markdown
# POS Loyalty Extend
|
||
|
||
**Version:** 1.0
|
||
**Author:** Suherdy Yacob
|
||
**License:** LGPL-3
|
||
**Category:** Sales / Point of Sale
|
||
|
||
## Overview
|
||
|
||
`pos_loyalty_extend` is a custom Odoo 19 module that extends the built-in `pos_loyalty` module with additional reward applicability options in the Point of Sale interface.
|
||
|
||
The primary feature added is **"Cheapest Product on Order"** applicability for **Buy X Get Y** loyalty rewards — allowing merchants to automatically discount or give away the cheapest item(s) in a customer's cart based on the number of products purchased.
|
||
|
||
---
|
||
|
||
## Features
|
||
|
||
### 🏷️ Cheapest Product Applicability
|
||
- Adds a new `reward_product_applicability` field (`cheapest`) to `loyalty.reward`.
|
||
- When set, the reward dynamically targets the cheapest product(s) in the current POS order instead of a pre-configured fixed product.
|
||
|
||
### 📊 Proportional Free Item Calculation
|
||
The number of free items scales automatically based on the buy ratio:
|
||
|
||
| Products Bought | Free Items |
|
||
|---|---|
|
||
| 2 | 1 |
|
||
| 3 | 1 |
|
||
| 4 | 2 |
|
||
| 5 | 2 |
|
||
| 6 | 3 |
|
||
|
||
Formula: `floor(points_earned / required_points_per_reward)`
|
||
|
||
### 🛒 Multi-Product Free Lines
|
||
When multiple free items are awarded, each one targets a **different cheapest product** in the order (sorted by unit price ascending), rather than duplicating the single cheapest item:
|
||
|
||
- Buy 4 items → 2 free: **Terong Penyet (Rp 15,000)** + **Telor Penyet (Rp 19,000)** ✅
|
||
- ~~Buy 4 items → 2 free: 2× Terong Penyet (Rp 15,000)~~ ❌
|
||
|
||
### ⚡ Auto-Claim in POS
|
||
Cheapest product rewards are automatically discovered and claimed in the POS UI without requiring manual product configuration on the reward.
|
||
|
||
---
|
||
|
||
## Dependencies
|
||
|
||
| Module | Purpose |
|
||
|---|---|
|
||
| `point_of_sale` | Core POS framework |
|
||
| `pos_loyalty` | Loyalty program UI and logic in POS |
|
||
| `sale_loyalty` | Backend loyalty program models |
|
||
|
||
---
|
||
|
||
## Configuration
|
||
|
||
### Setting Up a "Buy 2 Get 1 Free (Cheapest)" Program
|
||
|
||
1. Go to **Point of Sale → Products → Loyalty Programs**
|
||
2. Create a new program with type **Loyalty Card** or **Promotion**
|
||
3. Under **Conditional Rules**:
|
||
- **Minimum Quantity:** `2`
|
||
- **Grant:** `1 Credit per Unit Paid`
|
||
4. Under **Rewards** → Add a reward:
|
||
- **Reward Type:** `Free Product`
|
||
- **Reward Product Applicability:** `Cheapest Product on Order` *(new field)*
|
||
- **In Exchange of:** `2 Credits` *(1 credit per product, 2 needed → 1 free)*
|
||
- **Quantity Rewarded:** `1`
|
||
|
||
> **Important:** Set "In Exchange of" to `2` (not `1`) to get the correct `buy 2 → 1 free` ratio.
|
||
> The formula is `floor(total_credits / credits_required) = free_items`.
|
||
|
||
---
|
||
|
||
## Technical Details
|
||
|
||
### Modified Files
|
||
|
||
#### Python (Backend)
|
||
| File | Description |
|
||
|---|---|
|
||
| `models/loyalty_reward.py` | Adds `reward_product_applicability` selection field; overrides `_compute_multi_product` to handle cheapest type |
|
||
| `models/sale_order.py` | Overrides reward value computation for server-side cheapest product identification |
|
||
|
||
#### JavaScript (Frontend)
|
||
| File | Description |
|
||
|---|---|
|
||
| `static/src/app/models/pos_order.js` | Core reward logic patches — cheapest product detection, unclaimed qty computation, multi-product reward line generation, `getClaimableRewards` override |
|
||
| `static/src/app/services/pos_store.js` | Patches `getPotentialFreeProductRewards` to surface cheapest rewards with no fixed `reward_product_ids` |
|
||
| `static/src/app/screens/product_screen/control_buttons/control_buttons.js` | Patches `_applyReward` to route cheapest rewards through dynamic product resolution |
|
||
|
||
#### Views
|
||
| File | Description |
|
||
|---|---|
|
||
| `views/loyalty_reward_views.xml` | Adds the `reward_product_applicability` field to the loyalty reward form |
|
||
|
||
### Key Methods
|
||
|
||
- **`_getCheapestProductInOrder(reward)`** — Returns the single cheapest non-reward product in the current order
|
||
- **`_getCheapestProductsInOrder(reward, n)`** — Returns the N cheapest individual items (sorted by unit price), expanding multi-qty lines
|
||
- **`_computeUnclaimedFreeProductQtyForCheapest(...)`** — Calculates how many free items are still unclaimed based on remaining loyalty points
|
||
- **`getClaimableRewards(...)`** — Extended to include cheapest-type rewards that the core skips (due to null `reward_product_id`)
|
||
|
||
---
|
||
|
||
## Installation
|
||
|
||
```bash
|
||
# Copy module to your custom addons path
|
||
cp -r pos_loyalty_extend /path/to/odoo/customaddons/
|
||
|
||
# Update addons list and install
|
||
./odoo-bin -c odoo.conf -u pos_loyalty_extend
|
||
```
|
||
|
||
Or via Odoo UI:
|
||
1. Enable **Developer Mode**
|
||
2. Go to **Apps → Update Apps List**
|
||
3. Search for `POS Loyalty Extend` and install
|
||
|
||
---
|
||
|
||
## Changelog
|
||
|
||
### v1.0
|
||
- Initial release
|
||
- Added `cheapest` reward product applicability
|
||
- Auto-claim cheapest product rewards in POS
|
||
- Proportional multi-product free item generation (N cheapest products, not N× same product)
|
||
- Fixed `getClaimableRewards` and `getPotentialFreeProductRewards` to surface cheapest rewards
|
||
- Fixed `_updateRewardLines` deduplication for multiple reward lines per reward
|