[FIX] {{website_,}sale_,}loyalty: format display points

Steps
-----
1. Create a loyalty card program awarding 1 point per $ spent;
2. make sure "Show points Unit" is enabled;
3. give yourself a loyalty card with 267.39 points on it;
4. create a product with a price of $0.89;
5. go to website and add it to your shopping car;
6. go to checkout.

Issue
-----
> You have 268.28000000000003 Loyalty point(s)

Cause
-----
The number comes from the `_get_real_points_for_coupon` method, which
uses `float_round` by way of `res.currency`.
The `float_round` function isn't suited for raw number display, as it
can make tiny rounding errors due to floating point arithmetic.

Solution
--------
Add a `_format_points` method to `loyalty.card` which will return a
string using the same format the `points_display` field uses.

opw-3705546

closes odoo/odoo#155652

X-original-commit: 767405a6fe8cea0407632acb57aae8bf25d8c001
Signed-off-by: Levi Siuzdak <sile@odoo.com>
Signed-off-by: Victor Feyens (vfe) <vfe@odoo.com>
Co-authored-by: Victor Feyens <vfe@odoo.com>
This commit is contained in:
Levi Siuzdak (sile)
2024-02-29 14:07:46 +00:00
co-authored by Victor Feyens
parent bdd2013fc9
commit 62081e5ec9
5 changed files with 46 additions and 7 deletions
+11 -1
View File
@@ -5,6 +5,8 @@ from uuid import uuid4
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo.tools import format_amount
class LoyaltyCard(models.Model):
_name = 'loyalty.card'
@@ -52,7 +54,15 @@ class LoyaltyCard(models.Model):
@api.depends('points', 'point_name')
def _compute_points_display(self):
for card in self:
card.points_display = "%.2f %s" % (card.points or 0, card.point_name or '')
card.points_display = card._format_points(card.points)
def _format_points(self, points):
self.ensure_one()
if self.point_name == self.program_id.currency_id.symbol:
return format_amount(self.env, points, self.program_id.currency_id)
if points == int(points):
return f"{int(points)} {self.point_name or ''}"
return f"{points:.2f} {self.point_name or ''}"
# Meant to be overriden
def _compute_use_count(self):
@@ -34,7 +34,7 @@
<t t-set="gift_card" t-value="gift_cards[gift_card_idx]"/>
<td class="o_purchased_gift_card">
Gift #<t t-esc="gift_card_idx + 1"/>
(<span t-esc="order._get_real_points_for_coupon(gift_card, post_confirm=True)"/> <span t-esc="gift_card.point_name"/>)
(<span t-esc="gift_card._format_points(order._get_real_points_for_coupon(gift_card, post_confirm=True))"/>)
<strong t-esc="gift_card.code"/>
<button class="btn btn-sm btn-secondary copy-to-clipboard" t-att-data-clipboard-text="gift_card.code">
<span class="fa fa-clipboard"/> Copy
@@ -39,6 +39,11 @@ registry.category("web_tour.tours").add('shop_sale_loyalty', {
trigger: 'div>strong:contains("10.0% discount on total amount")',
run: function () {}, // it's a check
},
{
content: "check loyalty points",
trigger: '.oe_website_sale_gift_card span:contains("372.03 Points")',
run: function () {}, // it's a check
},
/* 2. Add some cabinet to get a free one, play with quantity */
{
content: "go to shop",
@@ -108,6 +108,31 @@ class WebsiteSaleLoyaltyTestUi(TestSaleProductAttributeValueCommon, HttpCase):
})],
})
vip_program = self.env['loyalty.program'].create({
'name': 'VIP',
'trigger': 'auto',
'program_type': 'loyalty',
'portal_visible': True,
'applies_on': 'both',
'rule_ids': [(0, 0, {
'mode': 'auto',
})],
'reward_ids': [(0, 0, {
'reward_type': 'discount',
'discount': 21,
'discount_mode': 'percent',
'discount_applicability': 'order',
'required_points': 50,
})],
})
self.env['loyalty.card'].create({
'partner_id': self.env.ref('base.partner_admin').id,
'program_id': vip_program.id,
'point_name': "Points",
'points': 371.03,
})
self.env.ref("website_sale.reduction_code").write({"active": True})
self.start_tour("/", 'shop_sale_loyalty', login="admin")
@@ -41,13 +41,12 @@
<div class="d-flex flex-row">
<div class="flex-grow-1">
<t t-set="program" t-value="reward.program_id"/>
<t t-set="points" t-value="website_sale_order._get_real_points_for_coupon(coupon)"/>
<t t-set="points" t-value="coupon._format_points(website_sale_order._get_real_points_for_coupon(coupon))"/>
<t t-if="program.program_type not in ['ewallet', 'promo_code'] and (program.trigger == 'with_code' or (program.trigger == 'auto' and program.applies_on == 'future'))">
<t t-if="program.program_type == 'gift_card'">
<strong t-esc="reward.description"/>
<strong> - </strong>
<strong t-esc="points"/>
<strong t-esc="reward.point_name"/>
</t>
<t t-elif="program.program_type == 'coupons'">
<strong>Coupons - </strong><strong t-esc="reward.description"/>
@@ -73,14 +72,14 @@
<strong t-esc="reward.description"/>
<div t-if="program.portal_visible">
<t t-if="not program.is_nominative">
<span t-out="points"/> <span t-out="reward.point_name"/>
<span t-out="points"/>
</t>
<t t-else="">
<span>You have <t t-out="points"/></span> <span t-out="reward.point_name"/>
<span>You have <t t-out="points"/></span>
<span t-if="program.program_type == 'ewallet'"> in your ewallet</span>
<t t-else="">
<br/>
<span>Costs <t t-out="reward.required_points"/></span> <span t-out="reward.point_name"/>
<span>Costs <t t-out="coupon._format_points(reward.required_points)"/></span>
</t>
</t>
</div>