From 58721592743179844677ffe2ba38b6d077e02a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phan=20Sainl=C3=A9ger?= Date: Fri, 2 Dec 2022 09:45:43 +0100 Subject: [PATCH] [ADD] create sale_order_invoice_untaxed_amount add-on --- sale_order_invoice_untaxed_amount/.gitignore | 2 + sale_order_invoice_untaxed_amount/README.rst | 43 +++++++ sale_order_invoice_untaxed_amount/__init__.py | 4 + .../__manifest__.py | 37 ++++++ sale_order_invoice_untaxed_amount/i18n/README | 1 + sale_order_invoice_untaxed_amount/i18n/fr.po | 56 +++++++++ .../sale_order_invoice_untaxed_amount.pot | 56 +++++++++ .../models/__init__.py | 1 + .../models/sale_order.py | 43 +++++++ .../tests/__init__.py | 1 + .../test_sale_order_invoice_untaxed_amount.py | 117 ++++++++++++++++++ .../views/sale_order_view.xml | 29 +++++ 12 files changed, 390 insertions(+) create mode 100644 sale_order_invoice_untaxed_amount/.gitignore create mode 100644 sale_order_invoice_untaxed_amount/README.rst create mode 100644 sale_order_invoice_untaxed_amount/__init__.py create mode 100644 sale_order_invoice_untaxed_amount/__manifest__.py create mode 100644 sale_order_invoice_untaxed_amount/i18n/README create mode 100644 sale_order_invoice_untaxed_amount/i18n/fr.po create mode 100644 sale_order_invoice_untaxed_amount/i18n/sale_order_invoice_untaxed_amount.pot create mode 100644 sale_order_invoice_untaxed_amount/models/__init__.py create mode 100644 sale_order_invoice_untaxed_amount/models/sale_order.py create mode 100644 sale_order_invoice_untaxed_amount/tests/__init__.py create mode 100644 sale_order_invoice_untaxed_amount/tests/test_sale_order_invoice_untaxed_amount.py create mode 100644 sale_order_invoice_untaxed_amount/views/sale_order_view.xml diff --git a/sale_order_invoice_untaxed_amount/.gitignore b/sale_order_invoice_untaxed_amount/.gitignore new file mode 100644 index 0000000..6da5887 --- /dev/null +++ b/sale_order_invoice_untaxed_amount/.gitignore @@ -0,0 +1,2 @@ +*.*~ +*pyc diff --git a/sale_order_invoice_untaxed_amount/README.rst b/sale_order_invoice_untaxed_amount/README.rst new file mode 100644 index 0000000..3eff1c8 --- /dev/null +++ b/sale_order_invoice_untaxed_amount/README.rst @@ -0,0 +1,43 @@ +================================= +sale_order_invoice_untaxed_amount +================================= + +Display the invoiced and uninvoiced untaxed total in the sale order + +Installation +============ + +Use Odoo normal module installation procedure to install +``sale_order_invoice_untaxed_amount``. + +Known issues / Roadmap +====================== + +None yet. +Bug Tracker +=========== + +Bugs are tracked on `our issues website `_. In case of +trouble, please check there if your issue has already been +reported. If you spotted it first, help us smashing it by providing a +detailed and welcomed feedback. + +Credits +======= + +Contributors +------------ + +* Stéphan Sainléger + +Funders +------- + +The development of this module has been financially supported by: +* Elabore (https://elabore.coop) + + +Maintainer +---------- + +This module is maintained by Elabore. \ No newline at end of file diff --git a/sale_order_invoice_untaxed_amount/__init__.py b/sale_order_invoice_untaxed_amount/__init__.py new file mode 100644 index 0000000..77bbdbd --- /dev/null +++ b/sale_order_invoice_untaxed_amount/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import models + diff --git a/sale_order_invoice_untaxed_amount/__manifest__.py b/sale_order_invoice_untaxed_amount/__manifest__.py new file mode 100644 index 0000000..dcf1344 --- /dev/null +++ b/sale_order_invoice_untaxed_amount/__manifest__.py @@ -0,0 +1,37 @@ +# Copyright 2022 Stéphan Sainléger (Elabore) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "sale_order_invoice_untaxed_amount", + "version": "14.0.1.0.0", + "author": "Elabore", + "website": "https://elabore.coop", + "maintainer": "Stéphan Sainléger", + "license": "AGPL-3", + "category": "Tools", + "summary": "Display the invoiced and uninvoiced untaxed total in the sale order", + # any module necessary for this one to work correctly + "depends": [ + "account", + "sale", + ], + "qweb": [ + # "static/src/xml/*.xml", + ], + "external_dependencies": { + "python": [], + }, + # always loaded + "data": [ + "views/sale_order_view.xml", + ], + # only loaded in demonstration mode + "demo": [], + "js": [], + "css": [], + "installable": True, + # Install this module automatically if all dependency have been previously + # and independently installed. Used for synergetic or glue modules. + "auto_install": False, + "application": False, +} \ No newline at end of file diff --git a/sale_order_invoice_untaxed_amount/i18n/README b/sale_order_invoice_untaxed_amount/i18n/README new file mode 100644 index 0000000..62197a1 --- /dev/null +++ b/sale_order_invoice_untaxed_amount/i18n/README @@ -0,0 +1 @@ +This directory should contain the *.po for Odoo translation. diff --git a/sale_order_invoice_untaxed_amount/i18n/fr.po b/sale_order_invoice_untaxed_amount/i18n/fr.po new file mode 100644 index 0000000..707f5f7 --- /dev/null +++ b/sale_order_invoice_untaxed_amount/i18n/fr.po @@ -0,0 +1,56 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_order_invoice_untaxed_amount +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-12-02 08:32+0000\n" +"PO-Revision-Date: 2022-12-02 08:32+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: sale_order_invoice_untaxed_amount +#: model:ir.model.fields,field_description:sale_order_invoice_untaxed_amount.field_sale_order__display_name +msgid "Display Name" +msgstr "Nom affiché" + +#. module: sale_order_invoice_untaxed_amount +#: model:ir.model.fields,field_description:sale_order_invoice_untaxed_amount.field_sale_order__id +msgid "ID" +msgstr "" + +#. module: sale_order_invoice_untaxed_amount +#: model:ir.model.fields,field_description:sale_order_invoice_untaxed_amount.field_sale_order__invoiced_untaxed_amount +msgid "Invoiced Untaxed Amount" +msgstr "Facturé HT" + +#. module: sale_order_invoice_untaxed_amount +#: model_terms:ir.ui.view,arch_db:sale_order_invoice_untaxed_amount.view_order_tree_invoiced_untaxed_amount +msgid "Invoiced Untaxed Total" +msgstr "Facturé HT Total" + +#. module: sale_order_invoice_untaxed_amount +#: model:ir.model.fields,field_description:sale_order_invoice_untaxed_amount.field_sale_order____last_update +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: sale_order_invoice_untaxed_amount +#: model:ir.model,name:sale_order_invoice_untaxed_amount.model_sale_order +msgid "Sales Order" +msgstr "Bon de commande" + +#. module: sale_order_invoice_untaxed_amount +#: model:ir.model.fields,field_description:sale_order_invoice_untaxed_amount.field_sale_order__uninvoiced_untaxed_amount +msgid "Uninvoiced Untaxed Amount" +msgstr "Non-facturé HT" + +#. module: sale_order_invoice_untaxed_amount +#: model_terms:ir.ui.view,arch_db:sale_order_invoice_untaxed_amount.view_order_tree_invoiced_untaxed_amount +msgid "Uninvoiced Untaxed Total" +msgstr "Non-facturé HT Total" diff --git a/sale_order_invoice_untaxed_amount/i18n/sale_order_invoice_untaxed_amount.pot b/sale_order_invoice_untaxed_amount/i18n/sale_order_invoice_untaxed_amount.pot new file mode 100644 index 0000000..51939a2 --- /dev/null +++ b/sale_order_invoice_untaxed_amount/i18n/sale_order_invoice_untaxed_amount.pot @@ -0,0 +1,56 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_order_invoice_untaxed_amount +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-12-02 08:33+0000\n" +"PO-Revision-Date: 2022-12-02 08:33+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: sale_order_invoice_untaxed_amount +#: model:ir.model.fields,field_description:sale_order_invoice_untaxed_amount.field_sale_order__display_name +msgid "Display Name" +msgstr "" + +#. module: sale_order_invoice_untaxed_amount +#: model:ir.model.fields,field_description:sale_order_invoice_untaxed_amount.field_sale_order__id +msgid "ID" +msgstr "" + +#. module: sale_order_invoice_untaxed_amount +#: model:ir.model.fields,field_description:sale_order_invoice_untaxed_amount.field_sale_order__invoiced_untaxed_amount +msgid "Invoiced Untaxed Amount" +msgstr "" + +#. module: sale_order_invoice_untaxed_amount +#: model_terms:ir.ui.view,arch_db:sale_order_invoice_untaxed_amount.view_order_tree_invoiced_untaxed_amount +msgid "Invoiced Untaxed Total" +msgstr "" + +#. module: sale_order_invoice_untaxed_amount +#: model:ir.model.fields,field_description:sale_order_invoice_untaxed_amount.field_sale_order____last_update +msgid "Last Modified on" +msgstr "" + +#. module: sale_order_invoice_untaxed_amount +#: model:ir.model,name:sale_order_invoice_untaxed_amount.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: sale_order_invoice_untaxed_amount +#: model:ir.model.fields,field_description:sale_order_invoice_untaxed_amount.field_sale_order__uninvoiced_untaxed_amount +msgid "Uninvoiced Untaxed Amount" +msgstr "" + +#. module: sale_order_invoice_untaxed_amount +#: model_terms:ir.ui.view,arch_db:sale_order_invoice_untaxed_amount.view_order_tree_invoiced_untaxed_amount +msgid "Uninvoiced Untaxed Total" +msgstr "" diff --git a/sale_order_invoice_untaxed_amount/models/__init__.py b/sale_order_invoice_untaxed_amount/models/__init__.py new file mode 100644 index 0000000..6aacb75 --- /dev/null +++ b/sale_order_invoice_untaxed_amount/models/__init__.py @@ -0,0 +1 @@ +from . import sale_order diff --git a/sale_order_invoice_untaxed_amount/models/sale_order.py b/sale_order_invoice_untaxed_amount/models/sale_order.py new file mode 100644 index 0000000..639253e --- /dev/null +++ b/sale_order_invoice_untaxed_amount/models/sale_order.py @@ -0,0 +1,43 @@ +# Copyright (C) 2021 ForgeFlow S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) + +from odoo import api, fields, models + + +class SaleOrder(models.Model): + + _inherit = "sale.order" + + invoiced_untaxed_amount = fields.Monetary( + string="Invoiced Untaxed Amount", + compute="_compute_invoice_untaxed_amount", + store=True, + ) + + uninvoiced_untaxed_amount = fields.Monetary( + string="Uninvoiced Untaxed Amount", + compute="_compute_invoice_untaxed_amount", + store=True, + ) + + @api.depends( + "state", + "invoice_ids", + "invoice_ids.amount_untaxed_signed", + "amount_total", + "invoice_ids.state", + ) + def _compute_invoice_untaxed_amount(self): + for rec in self: + if rec.state != "cancel" and rec.invoice_ids: + rec.invoiced_untaxed_amount = 0.0 + for invoice in rec.invoice_ids: + if invoice.state != "cancel": + rec.invoiced_untaxed_amount += invoice.amount_untaxed_signed + rec.uninvoiced_untaxed_amount = max(0, rec.amount_untaxed - rec.invoiced_untaxed_amount) + else: + rec.invoiced_untaxed_amount = 0.0 + if rec.state in ["draft", "sent", "cancel"]: + rec.uninvoiced_untaxed_amount = 0.0 + else: + rec.uninvoiced_untaxed_amount = rec.amount_untaxed diff --git a/sale_order_invoice_untaxed_amount/tests/__init__.py b/sale_order_invoice_untaxed_amount/tests/__init__.py new file mode 100644 index 0000000..7dd777d --- /dev/null +++ b/sale_order_invoice_untaxed_amount/tests/__init__.py @@ -0,0 +1 @@ +from . import test_sale_order_invoice_untaxed_amount diff --git a/sale_order_invoice_untaxed_amount/tests/test_sale_order_invoice_untaxed_amount.py b/sale_order_invoice_untaxed_amount/tests/test_sale_order_invoice_untaxed_amount.py new file mode 100644 index 0000000..0d236e3 --- /dev/null +++ b/sale_order_invoice_untaxed_amount/tests/test_sale_order_invoice_untaxed_amount.py @@ -0,0 +1,117 @@ +# Copyright (C) 2021 ForgeFlow S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) + +from odoo.tests import common + + +class TestSaleOrderInvoiceUntaxedAmount(common.SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + # Partners + cls.res_partner_1 = cls.env["res.partner"].create({"name": "Wood Corner"}) + cls.res_partner_address_1 = cls.env["res.partner"].create( + {"name": "Willie Burke", "parent_id": cls.res_partner_1.id} + ) + cls.res_partner_2 = cls.env["res.partner"].create({"name": "Partner 12"}) + + # Products + cls.product_1 = cls.env["product.product"].create( + {"name": "Desk Combination", "type": "product", "invoice_policy": "order"} + ) + cls.product_2 = cls.env["product.product"].create( + {"name": "Conference Chair", "type": "product", "invoice_policy": "order"} + ) + cls.product_3 = cls.env["product.product"].create( + {"name": "Repair Services", "type": "service", "invoice_policy": "order"} + ) + + # Location + cls.stock_warehouse = cls.env["stock.warehouse"].search( + [("company_id", "=", cls.env.company.id)], limit=1 + ) + cls.stock_location_14 = cls.env["stock.location"].create( + {"name": "Shelf 2", "location_id": cls.stock_warehouse.lot_stock_id.id} + ) + # Replenish products + cls.env["stock.quant"]._update_available_quantity( + cls.product_1, cls.stock_location_14, 10 + ) + cls.env["stock.quant"]._update_available_quantity( + cls.product_2, cls.stock_location_14, 10 + ) + # Sale Order + cls.tax = cls.env["account.tax"].create( + {"name": "Tax 15", "type_tax_use": "sale", "amount": 21} + ) + cls.sale_order_1 = cls.env["sale.order"].create( + {"partner_id": cls.res_partner_1.id} + ) + cls.order_line_1 = cls.env["sale.order.line"].create( + { + "order_id": cls.sale_order_1.id, + "product_id": cls.product_1.id, + "product_uom": cls.product_1.uom_id.id, + "product_uom_qty": 10.0, + "price_unit": 10.0, + "tax_id": cls.tax, + } + ) + cls.order_line_2 = cls.env["sale.order.line"].create( + { + "order_id": cls.sale_order_1.id, + "product_id": cls.product_2.id, + "product_uom": cls.product_2.uom_id.id, + "product_uom_qty": 25.0, + "price_unit": 4.0, + "tax_id": cls.tax, + } + ) + cls.order_line_3 = cls.env["sale.order.line"].create( + { + "order_id": cls.sale_order_1.id, + "product_id": cls.product_3.id, + "product_uom": cls.product_3.uom_id.id, + "product_uom_qty": 20.0, + "price_unit": 5.0, + "tax_id": cls.tax, + } + ) + + def test_sale_order_invoiced_amount(self): + + self.assertEqual( + self.sale_order_1.invoiced_amount, + 0.0, + "Invoiced Amount should be 0.0", + ) + context_payment = { + "active_ids": [self.sale_order_1.id], + "active_id": self.sale_order_1.id, + } + payment = ( + self.env["sale.advance.payment.inv"] + .with_context(context_payment) + .create({"advance_payment_method": "fixed", "fixed_amount": 100}) + ) + + payment.create_invoices() + self.assertEqual( + self.sale_order_1.invoiced_untaxed_amount, + 100.0, + "Invoiced Untaxed Amount should be 100", + ) + self.assertEqual( + self.sale_order_1.uninvoiced_untaxed_amount, + 200.0, + "Uninvoiced Untaxed Amount should be 263", + ) + + self.sale_order_1.action_confirm() + self.sale_order_1._create_invoices(final=True) + self.assertEqual( + self.sale_order_1.invoiced_amount, + 300.0, + "Invoiced Untaxed Amount should be calculated", + ) diff --git a/sale_order_invoice_untaxed_amount/views/sale_order_view.xml b/sale_order_invoice_untaxed_amount/views/sale_order_view.xml new file mode 100644 index 0000000..e6b3397 --- /dev/null +++ b/sale_order_invoice_untaxed_amount/views/sale_order_view.xml @@ -0,0 +1,29 @@ + + + + sale.order.form.invoiced.untaxed.amount + sale.order + + + + + + + + + + sale.order.tree.invoiced.untaxed.amount + sale.order + + + + + + + + + \ No newline at end of file