diff --git a/sale_margin_delivered/__manifest__.py b/sale_margin_delivered/__manifest__.py index e8682ada..d056f316 100644 --- a/sale_margin_delivered/__manifest__.py +++ b/sale_margin_delivered/__manifest__.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { "name": "Sale Margin Delivered", - "version": "16.0.1.0.5", + "version": "16.0.2.0.0", "author": "Tecnativa, Odoo Community Association (OCA)", "website": "https://github.com/OCA/margin-analysis", "category": "Sales", diff --git a/sale_margin_delivered/migrations/16.0.2.0.0/post-migration.py b/sale_margin_delivered/migrations/16.0.2.0.0/post-migration.py new file mode 100644 index 00000000..64d5bf8a --- /dev/null +++ b/sale_margin_delivered/migrations/16.0.2.0.0/post-migration.py @@ -0,0 +1,15 @@ +import logging + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version=None): + """Store margin delivered precentage as a fraction of 1""" + cr.execute( + """ + UPDATE sale_order_line + SET margin_delivered_percent = margin_delivered_percent / 100 + WHERE margin_delivered_percent > 0 + """ + ) + _logger.info("Updated %d records", cr.rowcount) diff --git a/sale_margin_delivered/models/sale_margin.py b/sale_margin_delivered/models/sale_margin.py index 50ceb550..29682716 100644 --- a/sale_margin_delivered/models/sale_margin.py +++ b/sale_margin_delivered/models/sale_margin.py @@ -22,7 +22,7 @@ class SaleOrderLine(models.Model): help="Margin percent between the Unit Price with discounts and " "Delivered Unit Cost.\n\n" "Formula: ((Unit Price with Discounts - Average Unit Cost of " - "delivered products) / Unit Price with Discounts) * 100.0", + "delivered products) / Unit Price with Discounts)", ) purchase_price_delivery = fields.Float( compute="_compute_margin_delivered", @@ -72,6 +72,14 @@ def _compute_margin_delivered(self): self.margin_delivered_percent = 0.0 self.purchase_price_delivery = 0.0 for line in self.filtered("qty_delivered"): + # we need to compute the price reduce to avoid rounding issues + # the one stored in the line is rounded to the product price precision + price_reduce_taxexcl = ( + line.price_subtotal / line.product_uom_qty + if line.product_uom_qty + else 0.0 + ) + if line.product_id.type != "product": currency = line.order_id.pricelist_id.currency_id price = line.purchase_price @@ -91,13 +99,11 @@ def _compute_margin_delivered(self): ) # Inverse qty_delivered because outgoing quantities are negative line.margin_delivered = -qty_delivered * ( - line.price_reduce - line.purchase_price_delivery + price_reduce_taxexcl - line.purchase_price_delivery ) # compute percent margin based on delivered quantities or ordered # quantities - if line.price_reduce: + if price_reduce_taxexcl: line.margin_delivered_percent = ( - (line.price_reduce - line.purchase_price_delivery) - / line.price_reduce - * 100.0 - ) + price_reduce_taxexcl - line.purchase_price_delivery + ) / price_reduce_taxexcl diff --git a/sale_margin_delivered/static/description/index.html b/sale_margin_delivered/static/description/index.html index ed03cc6b..746e3daa 100644 --- a/sale_margin_delivered/static/description/index.html +++ b/sale_margin_delivered/static/description/index.html @@ -8,10 +8,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -274,7 +275,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -300,7 +301,7 @@ span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -452,7 +453,9 @@

Contributors

Maintainers

This module is maintained by the OCA.

-Odoo Community Association + +Odoo Community Association +

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.

diff --git a/sale_margin_delivered/tests/test_sale_margin_delivered.py b/sale_margin_delivered/tests/test_sale_margin_delivered.py index bd0c5f9c..bd9ebb59 100644 --- a/sale_margin_delivered/tests/test_sale_margin_delivered.py +++ b/sale_margin_delivered/tests/test_sale_margin_delivered.py @@ -91,7 +91,7 @@ def test_sale_margin_delivered(self): picking._action_done() order_line = sale_order.order_line[:1] self.assertEqual(order_line.margin_delivered, 30.0) - self.assertEqual(order_line.margin_delivered_percent, 50.0) + self.assertEqual(order_line.margin_delivered_percent, 0.5) self.assertEqual(order_line.purchase_price_delivery, order_line.purchase_price) def test_sale_margin_delivered_excess(self): @@ -104,7 +104,7 @@ def test_sale_margin_delivered_excess(self): picking._action_done() order_line = sale_order.order_line[:1] self.assertEqual(order_line.margin_delivered, 120.0) - self.assertEqual(order_line.margin_delivered_percent, 50.0) + self.assertEqual(order_line.margin_delivered_percent, 0.5) self.assertEqual(order_line.purchase_price_delivery, order_line.purchase_price) def test_sale_margin_zero(self): @@ -142,7 +142,7 @@ def test_sale_margin_delivered_return_to_refund(self): picking_return._action_done() order_line = sale_order.order_line[:1] self.assertEqual(order_line.margin_delivered, 30.0) - self.assertEqual(order_line.margin_delivered_percent, 50.0) + self.assertEqual(order_line.margin_delivered_percent, 0.5) self.assertEqual(order_line.purchase_price_delivery, order_line.purchase_price) def test_sale_margin_delivered_return_to_refund_excess(self): @@ -155,7 +155,7 @@ def test_sale_margin_delivered_return_to_refund_excess(self): picking_return._action_done() order_line = sale_order.order_line[:1] self.assertEqual(order_line.margin_delivered, 90.0) - self.assertEqual(order_line.margin_delivered_percent, 50.0) + self.assertEqual(order_line.margin_delivered_percent, 0.5) self.assertEqual(order_line.purchase_price_delivery, order_line.purchase_price) def test_sale_margin_delivered_return_no_refund(self): @@ -168,7 +168,7 @@ def test_sale_margin_delivered_return_no_refund(self): picking_return._action_done() order_line = sale_order.order_line[:1] self.assertEqual(order_line.margin_delivered, 60.0) - self.assertEqual(order_line.margin_delivered_percent, 50.0) + self.assertEqual(order_line.margin_delivered_percent, 0.5) self.assertEqual(order_line.purchase_price_delivery, order_line.purchase_price) def test_sale_margin_delivered_return_no_refund_excess(self): @@ -181,5 +181,40 @@ def test_sale_margin_delivered_return_no_refund_excess(self): picking_return._action_done() order_line = sale_order.order_line[:1] self.assertEqual(order_line.margin_delivered, 120.0) - self.assertEqual(order_line.margin_delivered_percent, 50.0) + self.assertEqual(order_line.margin_delivered_percent, 0.5) + self.assertEqual(order_line.purchase_price_delivery, order_line.purchase_price) + + def test_sale_margin_delivered_precision(self): + self.product.standard_price = 10.30 + self.product.list_price = 20.17 + sale_order = self._new_sale_order() + sale_order.order_line[:1].discount = 17.0 + sale_order.action_confirm() + picking = sale_order.picking_ids + picking.action_assign() + picking.move_line_ids.qty_done = 6.0 + picking._action_done() + order_line = sale_order.order_line[:1] + # price_subtotal is rounded + self.assertEqual(order_line.price_subtotal, 100.45) + # the unit reduce price will be computed as 100.45 / 6 = 16.741666666666667 + # it should not be rounded to 16.74 + # margin_delivered: round(6 * ((100.45 /6) - 10.30)) != round(6 * (16.74 - 10.30)) + self.assertEqual(order_line.margin_delivered, 38.65) + self.assertAlmostEqual(order_line.margin_delivered_percent, 0.38476854156296) + self.assertEqual(order_line.purchase_price_delivery, order_line.purchase_price) + + def test_sale_margin_no_cost(self): + self.product.standard_price = 0.0 + self.product.list_price = 20 + sale_order = self._new_sale_order() + sale_order.action_confirm() + picking = sale_order.picking_ids + picking.action_assign() + picking.move_line_ids.qty_done = 6.0 + picking._action_done() + order_line = sale_order.order_line[:1] + # price_subtotal is rounded + self.assertEqual(order_line.margin_delivered, 120) + self.assertAlmostEqual(order_line.margin_delivered_percent, 1) self.assertEqual(order_line.purchase_price_delivery, order_line.purchase_price) diff --git a/sale_margin_delivered/views/sale_margin_delivered_view.xml b/sale_margin_delivered/views/sale_margin_delivered_view.xml index 696a40c3..b07662ac 100644 --- a/sale_margin_delivered/views/sale_margin_delivered_view.xml +++ b/sale_margin_delivered/views/sale_margin_delivered_view.xml @@ -6,7 +6,7 @@ sale.order - + diff --git a/sale_margin_delivered_dropshipping/tests/test_sale_margin_delivered_dropship.py b/sale_margin_delivered_dropshipping/tests/test_sale_margin_delivered_dropship.py index 296d1d88..b986bfab 100644 --- a/sale_margin_delivered_dropshipping/tests/test_sale_margin_delivered_dropship.py +++ b/sale_margin_delivered_dropshipping/tests/test_sale_margin_delivered_dropship.py @@ -48,5 +48,5 @@ def test_sale_margin_delivered_dropship(self): picking_return._action_done() order_line = sale_order.order_line[:1] self.assertEqual(order_line.margin_delivered, 30.0) - self.assertEqual(order_line.margin_delivered_percent, 50.0) + self.assertEqual(order_line.margin_delivered_percent, 0.5) self.assertEqual(order_line.purchase_price_delivery, order_line.purchase_price)