Skip to content

Commit

Permalink
Merge PR OCA#859 into 16.0
Browse files Browse the repository at this point in the history
Signed-off-by legalsylvain
  • Loading branch information
OCA-git-bot committed Sep 17, 2023
2 parents e5a1182 + 006b4df commit db80b95
Show file tree
Hide file tree
Showing 25 changed files with 454 additions and 0 deletions.
Empty file added pos_discount_all/README.rst
Empty file.
1 change: 1 addition & 0 deletions pos_discount_all/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
33 changes: 33 additions & 0 deletions pos_discount_all/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright (C) 2022-Today GRAP (http://www.grap.coop)
# @author Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html

{
"name": "Point of Sale - Display All Discounts",
"summary": "Display discount amount on PoS cashier screen and print it on ticket"
"calculated from the difference between a sale with default pricelist",
"version": "16.0.1.0.0",
"category": "Point of Sale",
"maintainers": ["legalsylvain"],
"author": "GRAP,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/pos",
"license": "AGPL-3",
"depends": ["point_of_sale"],
"data": [
"views/view_product_template.xml",
],
"assets": {
"point_of_sale.assets": [
"pos_discount_all/static/src/js/models.js",
"pos_discount_all/static/src/xml/OrderSummary.xml",
],
"web.assets_tests": [
"pos_discount_all/tests/tours/PosDiscountAllTour.tour.js",
],
},
"demo": [
"demo/product_product.xml",
"demo/res_groups.xml",
],
"installable": True,
}
15 changes: 15 additions & 0 deletions pos_discount_all/demo/product_product.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright (C) 2022-Today GRAP (http://www.grap.coop)
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
-->
<odoo>
<record id="product_discount" model="product.product">
<field name="name">Discount Product</field>
<field name="is_discount" eval="True" />
<field name="list_price">-1.0</field>
<field name="taxes_id" eval="[(6,0,[])]" />
<field name="available_in_pos" eval="True" />
</record>
</odoo>
15 changes: 15 additions & 0 deletions pos_discount_all/demo/res_groups.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright (C) 2022-Today GRAP (http://www.grap.coop)
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
-->
<odoo>
<record id="product.group_sale_pricelist" model="res.groups">
<field name="users" eval="[(4, ref('base.user_admin'))]" />
</record>

<record id="product.group_product_pricelist" model="res.groups">
<field name="users" eval="[(4, ref('base.user_admin'))]" />
</record>
</odoo>
2 changes: 2 additions & 0 deletions pos_discount_all/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import pos_session
from . import product_template
14 changes: 14 additions & 0 deletions pos_discount_all/models/pos_session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright (C) 2022-Today GRAP (http://www.grap.coop)
# @author Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html

from odoo import models


class PosSession(models.Model):
_inherit = "pos.session"

def _loader_params_product_product(self):
res = super()._loader_params_product_product()
res["search_params"]["fields"].append("is_discount")
return res
19 changes: 19 additions & 0 deletions pos_discount_all/models/product_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright (C) 2022-Today GRAP (http://www.grap.coop)
# @author Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html

from odoo import fields, models


class ProductTemplate(models.Model):
_inherit = "product.template"

is_discount = fields.Boolean(
string="Is a Discount",
help="Check this box if you use this product to realize"
" discount on sale. If check the sale lines will be"
" ignored when computing the amount without discount."
" If you use 'Pos Discount' Odoo module, you should"
" check this box for the product you configured"
" as the 'Discount Product' on your PoS config.",
)
5 changes: 5 additions & 0 deletions pos_discount_all/readme/CONFIGURE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
* Go to "Point of Sale > Products"
* Create or edit your discount products
* Check the box "Is a Discount"

.. image:: ../static/description/product_template_form.png
1 change: 1 addition & 0 deletions pos_discount_all/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Sylvain LE GAL (https://twitter.com/legalsylvain)
3 changes: 3 additions & 0 deletions pos_discount_all/readme/CREDITS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The development of this module has been financially supported by:

* UGESS, Union Nationale des Groupements des épiceries Sociales et Solidaires (https://ugess.org/)
14 changes: 14 additions & 0 deletions pos_discount_all/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
This module extends the functionality of point of sale module, to
display on the PoS ticket, the amount of the discount applied for this sale.
Contrary to the logic of Odoo and the OCA
(https://github.com/OCA/sale-workflow/tree/14.0/sale_discount_display_amount)
the amount of the discount is the difference between the theoretical sale with the default price list and the actual sale amount.
So it take into accounts:

- explicit discount set on pos.order.line. (as for odoo ``sale`` module)
- fixed price set on pos.order.line
- discount generated by specific pricelist

Also the module ignores in the computation of undiscounted amount lines, the
lines with a 'Discount Product'.
(see the configure section.)
21 changes: 21 additions & 0 deletions pos_discount_all/readme/DEVELOP.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
**Note**

In the javascript file, we could write

.. code-block:: javascript
const PosDiscountAllOrder = () =>
class extends Order {
}
However, this code doesn't work if ``pos_sale`` module is installed. For that
reason we code the declaration as Odoo does, and add eslint exception.


.. code-block:: javascript
// eslint-disable-next-line no-shadow
const PosDiscountAllOrder = (Order) =>
// eslint-disable-next-line no-shadow
class PosDiscountAllOrder extends Order {
}
14 changes: 14 additions & 0 deletions pos_discount_all/readme/USAGE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Open your point of Sale

Make an order. for exemple,
- select the product 'Conference chair (39.40$)'
- select the pricelist -10%
- add a discount of 1$

The total discount is 1$ + 10% * 39.40 = 4.94$

.. image:: ../static/description/order_summary.png

Note, the discount displayed on the ticket is updated, to take into account all the discount.

.. image:: ../static/description/pos_receipt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
101 changes: 101 additions & 0 deletions pos_discount_all/static/src/js/models.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
Copyright (C) 2022-Today GRAP (http://www.grap.coop)
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
*/

odoo.define("pos_discount_all.models", function (require) {
"use strict";

const {Order, Orderline} = require("point_of_sale.models");
const Registries = require("point_of_sale.Registries");
const {round_precision: round_pr} = require("web.utils");

// eslint-disable-next-line no-shadow
const PosDiscountAllOrder = (Order) =>
// eslint-disable-next-line no-shadow
class PosDiscountAllOrder extends Order {
// eslint-disable-line no-shadow
// @override
_get_ignored_product_ids_total_discount() {
const productIds = super._get_ignored_product_ids_total_discount(
...arguments
);
_.map(this.pos.db.product_by_id, function (product) {
if (product.is_discount) {
productIds.push(product.id);
}
});
return productIds;
}

get_total_with_tax_without_any_discount() {
return round_pr(
this.orderlines.reduce(function (sum, orderLine) {
return (
sum +
orderLine.get_total_without_any_discount().total_included
);
}, 0),
this.pos.currency.rounding
);
}

get_discount_amount_with_tax_without_any_discount() {
return round_pr(
this.get_total_with_tax_without_any_discount() -
this.get_total_with_tax(),
this.pos.currency.rounding
);
}

export_for_printing() {
var receipt = super.export_for_printing(...arguments);
receipt.total_discount =
this.get_discount_amount_with_tax_without_any_discount();
return receipt;
}
};

Registries.Model.extend(Order, PosDiscountAllOrder);

// eslint-disable-next-line no-shadow
const PosDiscountAllOrderLine = (Orderline) =>
// eslint-disable-next-line no-shadow
class PosDiscountAllOrderLine extends Orderline {
// eslint-disable-line no-shadow
get_total_without_any_discount() {
var product = this.get_product();
const ignored_product_ids =
this.order._get_ignored_product_ids_total_discount();
if (ignored_product_ids.includes(product.id)) {
return {
total_excluded: 0.0,
total_included: 0.0,
};
}
var price_unit_without_any_discount = product.get_price(
this.pos.default_pricelist,
this.get_quantity()
);
var taxes_ids = this.tax_ids || product.taxes_id;
taxes_ids = _.filter(taxes_ids, (t) => t in this.pos.taxes_by_id);
var product_taxes = this.pos.get_taxes_after_fp(
taxes_ids,
this.order.fiscal_position
);
var all_taxes_without_any_discount = this.compute_all(
product_taxes,
price_unit_without_any_discount,
this.get_quantity(),
this.pos.currency.rounding
);
return {
total_excluded: all_taxes_without_any_discount.total_excluded,
total_included: all_taxes_without_any_discount.total_included,
};
}
};

Registries.Model.extend(Orderline, PosDiscountAllOrderLine);
});
29 changes: 29 additions & 0 deletions pos_discount_all/static/src/xml/OrderSummary.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright (C) 2022-Today GRAP (http://www.grap.coop)
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
-->
<templates id="template" xml:space="preserve">

<t
t-name="OrderSummary"
t-inherit="point_of_sale.OrderSummary"
t-inherit-mode="extension"
owl="1"
>
<xpath expr="//div[@t-if='_tax.hasTax']" position="after">
<t
t-set="_discount_amount"
t-value="props.order.get_discount_amount_with_tax_without_any_discount()"
/>
<div t-if="_discount_amount" class="subentry">
Discount Amount:
<span class="value discount-amount">
<t t-esc="env.pos.format_currency(_discount_amount)" />
</span>
</div>
</xpath>
</t>

</templates>
1 change: 1 addition & 0 deletions pos_discount_all/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_module
61 changes: 61 additions & 0 deletions pos_discount_all/tests/test_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Copyright (C) 2022-Today GRAP (http://www.grap.coop)
# @author Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html


from odoo.tests import tagged

from odoo.addons.point_of_sale.tests.test_frontend import TestPointOfSaleHttpCommon


@tagged("post_install", "-at_install")
class TestUi(TestPointOfSaleHttpCommon):
def test_pos_discount_all(self):

pricelist = self.env["product.pricelist"].create(
{
"name": "Pricelist -10%",
}
)
self.env["product.pricelist.item"].create(
{
"pricelist_id": pricelist.id,
"name": "Pricelist Item -10%",
"applied_on": "3_global",
"compute_price": "percentage",
"percent_price": 10,
}
)
self.main_pos_config.write(
{
"use_pricelist": True,
"available_pricelist_ids": [(4, pricelist.id)],
}
)

self.env["product.product"].create(
{
"name": "Generic Product",
"available_in_pos": True,
"list_price": 10.0,
"taxes_id": False,
}
)

self.env["product.product"].create(
{
"name": "Discount Product",
"is_discount": True,
"available_in_pos": True,
"list_price": -1.0,
"taxes_id": False,
}
)

self.main_pos_config.open_ui()

self.start_tour(
f"/pos/ui?config_id={self.main_pos_config.id}",
"PosDiscountAllTour",
login="accountman",
)
Loading

0 comments on commit db80b95

Please sign in to comment.