pos_loyalty_extend/README.md
2026-05-28 14:50:21 +07:00

133 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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