feat: Group 100% loyalty discount reward lines for unified display in the POS UI and receipts while preserving individual lines for accurate accounting.
This commit is contained in:
parent
b381cbc779
commit
98a739b9d3
@ -10,7 +10,7 @@ This module modifies the loyalty reward discount calculation in POS to apply dis
|
||||
|
||||
3. **Proper Accounting Entries**: Accounting entries are created with tax only on the credit side, ensuring compliance with accounting standards.
|
||||
|
||||
4. **100% Discount Rounding Fix**: Ensures exact zero-total for 100% discounts by generating individual reward lines for each item, preventing tax rounding discrepancies.
|
||||
4. **100% Discount Unified Grouping**: Ensures exact zero-total for 100% discounts by generating individual lines (to prevent rounding errors) but displays them as a single aggregated line in both the POS screen and printed receipt.
|
||||
|
||||
5. **Zero-Value Journal Entries**: Automatically creates journal entries for orders with a 0.00 total (e.g., 100% discount) to track the "Foregone Income" and "Discount Expense". This is configurable via settings.
|
||||
|
||||
@ -19,7 +19,8 @@ This module modifies the loyalty reward discount calculation in POS to apply dis
|
||||
### JavaScript Changes
|
||||
|
||||
- Modified `static/src/overrides/models/loyalty.js` to calculate discounts on tax-exclusive amounts.
|
||||
- Implemented "Line-by-Line Cancellation" for 100% discounts to fix rounding issues.
|
||||
- Implemented "Line-by-Line Cancellation" for 100% discounts to fix rounding issues, with UI-level grouping for a clean display.
|
||||
- Added `Orderline` and `Order` overrides (`static/src/overrides/models/orderline.js`) to handle unified grouping, receipt filtering, and persistence.
|
||||
|
||||
### Backend Changes
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
"assets": {
|
||||
"point_of_sale._assets_pos": [
|
||||
'pos_loyalty_discount_before_tax/static/src/overrides/models/loyalty.js',
|
||||
'pos_loyalty_discount_before_tax/static/src/overrides/models/orderline.js',
|
||||
]
|
||||
},
|
||||
"installable": True,
|
||||
|
||||
@ -207,8 +207,9 @@ patch(Order.prototype, {
|
||||
const discountFactor = totalDiscountableWithoutTax ? Math.min(1, maxDiscount / totalDiscountableWithoutTax) : 1;
|
||||
|
||||
if ((1 - discountFactor) < 0.00001) {
|
||||
// 100% Discount: Generate one reward line per original line to prevent rounding aggregation errors
|
||||
return formattedLines.map(line => {
|
||||
// 100% Discount: Generate one reward line per original line to prevent rounding aggregation errors (0.01 issues)
|
||||
// Tags added for UI-level grouping
|
||||
return formattedLines.map((line, index) => {
|
||||
const taxKey = ['ewallet', 'gift_card'].includes(reward.program_id.program_type)
|
||||
? line.get_taxes().map((t) => t.id)
|
||||
: line.get_taxes().filter((t) => t.amount_type !== 'fixed').map((t) => t.id);
|
||||
@ -224,6 +225,10 @@ patch(Order.prototype, {
|
||||
reward_identifier_code: rewardCode,
|
||||
tax_ids: taxKey, // Use IDs, not tax objects
|
||||
merge: false,
|
||||
is_reward_group_member: true,
|
||||
reward_group_id: rewardCode,
|
||||
is_reward_group_head: index === 0,
|
||||
reward_group_count: formattedLines.length,
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -259,5 +264,15 @@ patch(Order.prototype, {
|
||||
result[0]["points_cost"] = pointCost;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
set_orderline_options(line, options) {
|
||||
super.set_orderline_options(...arguments);
|
||||
if (options && options.is_reward_group_member) {
|
||||
line.is_reward_group_member = options.is_reward_group_member;
|
||||
line.reward_group_id = options.reward_group_id;
|
||||
line.is_reward_group_head = options.is_reward_group_head;
|
||||
line.reward_group_count = options.reward_group_count;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
70
static/src/overrides/models/orderline.js
Normal file
70
static/src/overrides/models/orderline.js
Normal file
@ -0,0 +1,70 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Order, Orderline } from "@point_of_sale/app/store/models";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
patch(Order.prototype, {
|
||||
export_for_printing() {
|
||||
const result = super.export_for_printing(...arguments);
|
||||
if (result.orderlines) {
|
||||
result.orderlines = result.orderlines.filter((lineData) => {
|
||||
const line = this.orderlines.find((l) => l.cid === lineData.cid);
|
||||
if (line && line.is_reward_group_member && !line.is_reward_group_head) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
patch(Orderline.prototype, {
|
||||
getDisplayClasses() {
|
||||
const classes = super.getDisplayClasses();
|
||||
if (this.is_reward_group_member && !this.is_reward_group_head) {
|
||||
classes['d-none'] = true;
|
||||
}
|
||||
return classes;
|
||||
},
|
||||
getDisplayData() {
|
||||
const data = super.getDisplayData();
|
||||
data.cid = this.cid;
|
||||
if (this.is_reward_group_head) {
|
||||
// Group and sum all lines in this reward group for display
|
||||
const groupLines = this.order.get_orderlines().filter(
|
||||
line => line.reward_group_id === this.reward_group_id
|
||||
);
|
||||
|
||||
let totalDisplayPrice = 0;
|
||||
let totalQty = 0;
|
||||
for (const line of groupLines) {
|
||||
totalDisplayPrice += line.get_display_price();
|
||||
totalQty += line.get_quantity();
|
||||
}
|
||||
|
||||
data.price = this.env.utils.formatCurrency(totalDisplayPrice, this.pos.currency);
|
||||
data.qty = totalQty.toString();
|
||||
}
|
||||
return data;
|
||||
},
|
||||
export_as_JSON() {
|
||||
const result = super.export_as_JSON(...arguments);
|
||||
if (this.is_reward_group_member) {
|
||||
result.is_reward_group_member = this.is_reward_group_member;
|
||||
result.reward_group_id = this.reward_group_id;
|
||||
result.is_reward_group_head = this.is_reward_group_head;
|
||||
result.reward_group_count = this.reward_group_count;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
init_from_JSON(json) {
|
||||
if (json.is_reward_group_member) {
|
||||
this.is_reward_group_member = json.is_reward_group_member;
|
||||
this.reward_group_id = json.reward_group_id;
|
||||
this.is_reward_group_head = json.is_reward_group_head;
|
||||
this.reward_group_count = json.reward_group_count;
|
||||
}
|
||||
super.init_from_JSON(...arguments);
|
||||
}
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user