Skip to content

Commit

Permalink
[ADD] sale_loyalty_general_discount_promo_code
Browse files Browse the repository at this point in the history
  • Loading branch information
natuan9 committed Feb 17, 2025
1 parent 9a141ef commit ff007d2
Show file tree
Hide file tree
Showing 17 changed files with 1,089 additions and 0 deletions.
150 changes: 150 additions & 0 deletions sale_loyalty_general_discount_promo_code/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
========================================
Sale Loyalty General Discount Promo Code
========================================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:f607e90aa781cba94a03d02423c322fb46de6faf028ce7deb5389e0635d6a29e
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsale--promotion-lightgray.png?logo=github
:target: https://github.com/OCA/sale-promotion/tree/16.0/sale_loyalty_general_discount_promo_code
:alt: OCA/sale-promotion
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/sale-promotion-16-0/sale-promotion-16-0-sale_loyalty_general_discount_promo_code
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/sale-promotion&target_branch=16.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module automatically applies the promo code discount percentage to
each sales order line when the promo code is activated.

**Table of contents**

.. contents::
:local:

Use Cases / Context
===================

Currently when a discount code is added to a sales order in Odoo, the
discount is added as a single sales order line. This causes issues with
the way taxes are calculated on the order, leading to potential tax
errors and audit issues.

Configuration
=============

1. Go to **Sales** > **Configuration** > **Settings**.
2. Scroll down to the **Pricing** section.
3. Enable the checkbox **Automatically apply promo code discount
percentage**.
4. The checkbox **Discounts** should be disabled by default
5. Click **Save**.

Usage
=====

1. Create a Coupon Program
--------------------------

- Go to **Sale > Products > Discount & Loyalty**.
- Create a **program** with the **program type** set to ``Coupons`` or
``Discount`` or ``Promotions``.

2. Define Rewards
-----------------

- Create **rewards** for the program with:

- **Reward Type**: ``Discount``
- **Discount Mode**: ``Percent``

3. Generate Coupons
-------------------

- Generate **coupon codes** for the program.

4. Apply Coupon to a Quotation
------------------------------

- Create a **new quotation** and add products.
- Apply the **coupon code** to the quotation.

5. Verify Discount Application
------------------------------

- The **discount column (``disc``)** will be **recomputed**.
- The **reward description** will display the **amount of savings**
applied by the discount code.

--------------

Notes
~~~~~

- Ensure the program is correctly configured before applying coupons.
- The discount applies based on the program settings (``order``,
``specific``, or ``cheapest``).

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/sale-promotion/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/sale-promotion/issues/new?body=module:%20sale_loyalty_general_discount_promo_code%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* Kencove

Contributors
------------

- `Trobz <https://trobz.com>`__:

- Tuan Nguyen <[email protected]>

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

.. |maintainer-natuan9| image:: https://github.com/natuan9.png?size=40px
:target: https://github.com/natuan9
:alt: natuan9

Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-natuan9|

This module is part of the `OCA/sale-promotion <https://github.com/OCA/sale-promotion/tree/16.0/sale_loyalty_general_discount_promo_code>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
1 change: 1 addition & 0 deletions sale_loyalty_general_discount_promo_code/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
19 changes: 19 additions & 0 deletions sale_loyalty_general_discount_promo_code/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2025 Kencove
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
{
"name": "Sale Loyalty General Discount Promo Code",
"summary": "Apply the promo code discount percentage to each sales order line",
"version": "16.0.1.0.0",
"category": "web",
"website": "https://github.com/OCA/sale-promotion",
"author": "Kencove, Odoo Community Association (OCA)",
"maintainers": ["natuan9"],
"license": "AGPL-3",
"depends": [
"sale_loyalty",
],
"data": [
"views/res_config_settings_views.xml",
],
"assets": {},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import sale_order, res_config_settings
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from odoo import fields, models


class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"

automatically_apply_promo_code_discount_percentage = fields.Boolean(
config_parameter=(
"sale_loyalty_general_discount_promo_code."
"automatically_apply_promo_code_discount_percentage"
),
)
158 changes: 158 additions & 0 deletions sale_loyalty_general_discount_promo_code/models/sale_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
from odoo import _, models
from odoo.tools import format_amount


class SaleOrder(models.Model):
_inherit = "sale.order"

def _get_total_saving_specific(self, reward):
self.ensure_one()
assert reward.discount_applicability == "specific"

order_lines = self.order_line - self._get_no_effect_on_threshold_lines()
lines_to_discount = order_lines.filtered(
lambda line: not line.reward_id
and line.product_uom_qty
and line.price_total
and line.product_id.filtered_domain(reward._get_discount_product_domain())
)

return sum(
line.price_unit * line.product_uom_qty * (reward.discount / 100)
for line in lines_to_discount
)

def _get_total_saving_cheapest(self, reward):
self.ensure_one()
assert reward.discount_applicability == "cheapest"

cheapest_line = self._cheapest_line()
if not cheapest_line:
return 0

Check warning on line 31 in sale_loyalty_general_discount_promo_code/models/sale_order.py

View check run for this annotation

Codecov / codecov/patch

sale_loyalty_general_discount_promo_code/models/sale_order.py#L31

Added line #L31 was not covered by tests
return (
cheapest_line.price_unit
* cheapest_line.product_uom_qty
* (reward.discount / 100)
)

def _get_total_saving_order(self, reward):
self.ensure_one()
assert reward.discount_applicability == "order"
total_saving = 0
for line in self.order_line:
if line.reward_id:
continue

Check warning on line 44 in sale_loyalty_general_discount_promo_code/models/sale_order.py

View check run for this annotation

Codecov / codecov/patch

sale_loyalty_general_discount_promo_code/models/sale_order.py#L44

Added line #L44 was not covered by tests
total_saving += (
line.price_unit * line.product_uom_qty * (reward.discount / 100)
)
return total_saving

def _get_total_saved_amount(self, reward):
total_saving = 0
reward_applies_on = reward.discount_applicability
if reward_applies_on == "order":
total_saving = self._get_total_saving_order(reward)
elif reward_applies_on == "specific":
total_saving = self._get_total_saving_specific(reward)

Check warning on line 56 in sale_loyalty_general_discount_promo_code/models/sale_order.py

View check run for this annotation

Codecov / codecov/patch

sale_loyalty_general_discount_promo_code/models/sale_order.py#L56

Added line #L56 was not covered by tests
elif reward_applies_on == "cheapest":
total_saving = self._get_total_saving_cheapest(reward)

Check warning on line 58 in sale_loyalty_general_discount_promo_code/models/sale_order.py

View check run for this annotation

Codecov / codecov/patch

sale_loyalty_general_discount_promo_code/models/sale_order.py#L58

Added line #L58 was not covered by tests

return format_amount(self.env, total_saving, self.pricelist_id.currency_id)

def _get_reward_values_discount(self, reward, coupon, **kwargs):
rewards = super()._get_reward_values_discount(reward, coupon, **kwargs)

if (
not self.env["ir.config_parameter"]
.sudo()
.get_param(
"sale_loyalty_general_discount_promo_code."
"automatically_apply_promo_code_discount_percentage"
)
):
return rewards

for reward_line in rewards:
if (
reward.reward_type == "discount"
and reward.discount_mode == "percent"
and (
reward.program_id.program_type
in ["coupons", "promo_code", "promotion"]
)
):
# Get the saved amount
# Cannot use the reward_line.price_unit for the saved amount
# because _get_reward_line_values is called twice.
# Once in _apply_program_reward and once in _update_programs_and_rewards
saved_amount = self._get_total_saved_amount(reward)
reward_line.update(
{
"display_type": "line_note",
"name": _(
f"You saved {saved_amount} with promo {reward.program_id.name}"
),
"price_unit": 0,
"product_uom_qty": 0,
"product_id": None,
"product_uom": None,
}
)

return rewards

def update_discount_percentage(self):
self.ensure_one()
reward_lines = self.order_line.filtered(lambda line: line.reward_id)
order_lines = self.order_line - reward_lines

reward_groups = {"cheapest": [], "specific": [], "order": []}
for reward_line in reward_lines:
reward = reward_line.reward_id
if reward.discount_mode == "percent":
reward_groups[reward.discount_applicability].append(reward)

cheapest_line = self._cheapest_line() if reward_groups["cheapest"] else None
specific_domains = {
reward: reward._get_discount_product_domain()
for reward in reward_groups["specific"]
}

for line in order_lines:
line.discount = 0

# Update discount based on `order` rewards
for reward in reward_groups["order"]:
line.discount = 100 - (100 - line.discount) * (
1 - reward.discount / 100
)

# Update discount based on `cheapest` rewards
if line == cheapest_line:
for reward in reward_groups["cheapest"]:
line.discount = 100 - (100 - line.discount) * (

Check warning on line 133 in sale_loyalty_general_discount_promo_code/models/sale_order.py

View check run for this annotation

Codecov / codecov/patch

sale_loyalty_general_discount_promo_code/models/sale_order.py#L133

Added line #L133 was not covered by tests
1 - reward.discount / 100
)

# Update discount based on `specific` rewards
for reward, domain in specific_domains.items():
if line.product_id.filtered_domain(domain):
line.discount = 100 - (100 - line.discount) * (

Check warning on line 140 in sale_loyalty_general_discount_promo_code/models/sale_order.py

View check run for this annotation

Codecov / codecov/patch

sale_loyalty_general_discount_promo_code/models/sale_order.py#L140

Added line #L140 was not covered by tests
1 - reward.discount / 100
)

def _write_vals_from_reward_vals(self, reward_vals, old_lines, delete=True):
result = super()._write_vals_from_reward_vals(
reward_vals, old_lines, delete=delete
)
if (
not self.env["ir.config_parameter"]
.sudo()
.get_param(
"sale_loyalty_general_discount_promo_code."
"automatically_apply_promo_code_discount_percentage"
)
):
return result
self.update_discount_percentage()
return result
5 changes: 5 additions & 0 deletions sale_loyalty_general_discount_promo_code/readme/CONFIGURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
1. Go to **Sales** > **Configuration** > **Settings**.
2. Scroll down to the **Pricing** section.
3. Enable the checkbox **Automatically apply promo code discount percentage**.
4. The checkbox **Discounts** should be disabled by default
4. Click **Save**.
1 change: 1 addition & 0 deletions sale_loyalty_general_discount_promo_code/readme/CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Currently when a discount code is added to a sales order in Odoo, the discount is added as a single sales order line. This causes issues with the way taxes are calculated on the order, leading to potential tax errors and audit issues.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- [Trobz](https://trobz.com):
- Tuan Nguyen \<<[email protected]>\>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This module automatically applies the promo code discount percentage to each sales order line when the promo code is activated.
25 changes: 25 additions & 0 deletions sale_loyalty_general_discount_promo_code/readme/USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## 1. Create a Coupon Program
- Go to **Sale > Products > Discount & Loyalty**.
- Create a **program** with the **program type** set to `Coupons` or `Discount` or `Promotions`.

## 2. Define Rewards
- Create **rewards** for the program with:
- **Reward Type**: `Discount`
- **Discount Mode**: `Percent`

## 3. Generate Coupons
- Generate **coupon codes** for the program.

## 4. Apply Coupon to a Quotation
- Create a **new quotation** and add products.
- Apply the **coupon code** to the quotation.

## 5. Verify Discount Application
- The **discount column (`disc`)** will be **recomputed**.
- The **reward description** will display the **amount of savings** applied by the discount code.

---

### Notes
- Ensure the program is correctly configured before applying coupons.
- The discount applies based on the program settings (`order`, `specific`, or `cheapest`).
Loading

0 comments on commit ff007d2

Please sign in to comment.