diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000000..180fc49789b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +# generated from manifests external_dependencies +openupgradelib diff --git a/sale_invoice_policy/README.rst b/sale_invoice_policy/README.rst new file mode 100644 index 00000000000..17ae5044938 --- /dev/null +++ b/sale_invoice_policy/README.rst @@ -0,0 +1,138 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +=================== +Sale invoice Policy +=================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:92833c38d476bc1f03727290a81ccb0609e0955b1a905f3f68ce3a96a910858f + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/license-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--workflow-lightgray.png?logo=github + :target: https://github.com/OCA/sale-workflow/tree/19.0/sale_invoice_policy + :alt: OCA/sale-workflow +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/sale-workflow-19-0/sale-workflow-19-0-sale_invoice_policy + :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-workflow&target_branch=19.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds an invoicing policy on sale order level in order to +apply that invoicing policy on the whole sale order. + +That invoicing policy can take three values: + +- Products Invoicing Policy: The sale order will follow the standard + behavior and apply the policy depending on products configurations. +- Ordered Quantities: The sale order will invoice the ordered + quantities. +- Delivered Quantities: The sale order will invoice the delivered + quantities. + +Following the chosen policy, the quantity to invoice and the amount to +invoice on each line will be computed accordingly. + +You will be able also to define a default invoicing policy (globally per +company) that can be different than the default invoicing policy for new +products. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +In Odoo, products have their own invoicing policy that can be: + +- Invoicing on ordered quantities +- Invoicing on ordered quantities + +Following that configuration, when trying to create invoices from sale +orders, each line of product will apply its invoicing policy. + +In some cases, user needs to apply an invoicing policy on a whole sale +order. + +The solution proposed here is to add an invoicing policy on sale order +level. + +Configuration +============= + +- Go to Sale > Configuration > Settings > Sale Invoice Policy +- Choose the one that fits your needs. + +Usage +===== + +- Create Sale Order +- Select Invoicing Policy on Sale Order or let it on Products Invoicing + Policy +- The created invoices will use the configuration on sale order. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub 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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* ACSONE SA/NV + +Contributors +------------ + +- Cédric Pigeon +- François Honoré +- Denis Roussel +- Alexei Rivera +- Luis J. Salvatierra +- Alejandro Ji Cheung +- Ioan Galan +- Laurent Mignon +- Marie Lejeune +- Jacques-Etienne Baudoux (BCIM) <> +- Thomas Herbin + +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. + +This module is part of the `OCA/sale-workflow `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/sale_invoice_policy/__init__.py b/sale_invoice_policy/__init__.py new file mode 100644 index 00000000000..548c73eb1a1 --- /dev/null +++ b/sale_invoice_policy/__init__.py @@ -0,0 +1,2 @@ +from .hooks import pre_init_hook +from . import models diff --git a/sale_invoice_policy/__manifest__.py b/sale_invoice_policy/__manifest__.py new file mode 100644 index 00000000000..91ea3040ad2 --- /dev/null +++ b/sale_invoice_policy/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2017 ACSONE SA/NV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + "name": "Sale invoice Policy", + "summary": """ + Sales Management: let the user choose the invoice policy on the + order""", + "author": "ACSONE SA/NV, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/sale-workflow", + "category": "Sales Management", + "version": "19.0.1.0.0", + "license": "AGPL-3", + "depends": ["base", "sale_stock", "base_partition"], + "external_dependencies": {"python": ["openupgradelib"]}, + "data": [ + "views/res_config_settings_view.xml", + "views/sale_view.xml", + ], + "pre_init_hook": "pre_init_hook", +} diff --git a/sale_invoice_policy/hooks.py b/sale_invoice_policy/hooks.py new file mode 100644 index 00000000000..ee07c7e619f --- /dev/null +++ b/sale_invoice_policy/hooks.py @@ -0,0 +1,22 @@ +# Copyright 2024 ACSONE SA/NV () +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from openupgradelib import openupgrade + + +def pre_init_hook(env): + """ + Create the sale order invoice policy with the "product" policy (standard) + but with a postgres query to avoid an update on all sale order records + """ + field_spec = [ + ( + "invoice_policy", + "sale.order", + False, + "selection", + False, + "sale_invoice_policy", + "product", + ) + ] + openupgrade.add_fields(env, field_spec=field_spec) diff --git a/sale_invoice_policy/i18n/ca.po b/sale_invoice_policy/i18n/ca.po new file mode 100644 index 00000000000..5ed6cb86196 --- /dev/null +++ b/sale_invoice_policy/i18n/ca.po @@ -0,0 +1,109 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_invoice_policy +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2020-02-26 18:13+0000\n" +"Last-Translator: georginaf \n" +"Language-Team: none\n" +"Language: ca\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 3.10\n" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_res_config_settings +msgid "Config Settings" +msgstr "Configuració" + +#. module: sale_invoice_policy +#: model:ir.model.fields.selection,name:sale_invoice_policy.selection__sale_order__invoice_policy__delivery +msgid "Delivered quantities" +msgstr "Quantitats entregades" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_sale_order__invoice_policy +msgid "Invoice Policy" +msgstr "Política de Facturació" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_sale_order__invoice_policy_required +msgid "Invoice Policy Required" +msgstr "Política de Facturació" + +#. module: sale_invoice_policy +#: model_terms:ir.ui.view,arch_db:sale_invoice_policy.view_sales_config +msgid "Invoice Policy required in Sale Orders" +msgstr "Política de Facturació" + +#. module: sale_invoice_policy +#: model:ir.model.fields,help:sale_invoice_policy.field_sale_order__invoice_policy +msgid "" +"Ordered Quantity: Invoice based on the quantity the customer ordered.\n" +"Delivered Quantity: Invoiced based on the quantity the vendor delivered " +"(time or deliveries)." +msgstr "" +"Quantitat demanada: Factura basada en la quantitat demanada pel client.\n" +"Quantitat entregada: Factura basada en funció de la quantitat entregada pel " +"proveïdor ( temps o entregues)." + +#. module: sale_invoice_policy +#: model:ir.model.fields.selection,name:sale_invoice_policy.selection__sale_order__invoice_policy__order +msgid "Ordered quantities" +msgstr "Quantitats Demanades" + +#. module: sale_invoice_policy +#: model_terms:ir.ui.view,arch_db:sale_invoice_policy.view_sales_config +msgid "Sale Invoice Policy" +msgstr "" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_res_config_settings__sale_invoice_policy_required +msgid "Sale Invoice Policy Required" +msgstr "Política de Facturació" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_sale_order_line +msgid "Sales Order Line" +msgstr "Línia de comanda de venda" + +#. module: sale_invoice_policy +#: model:ir.model.fields,help:sale_invoice_policy.field_res_config_settings__sale_invoice_policy_required +msgid "This makes Invoice Policy required on Sale Orders" +msgstr "La política de facturació és necessària a les comandes de venda" + +#~ msgid "Default Invoicing Policy" +#~ msgstr "Política de Facturació" + +#~ msgid "Default Sale Invoice Policy" +#~ msgstr "Política de Facturació" + +#~ msgid "Default Sale Order Invoice Policy" +#~ msgstr "Política de Facturació" + +#~ msgid "Invoicing Policy" +#~ msgstr "Política de Facturació" + +#~ msgid "" +#~ "Ordered Quantity: Invoice quantities ordered by the customer.\n" +#~ "Delivered Quantity: Invoice quantities delivered to the customer." +#~ msgstr "" +#~ "Quantitat demanada: Factura basada en la quantitat demanada pel client.\n" +#~ "Quantitat entregada: Facturada en funció de la quantitat entregada pel " +#~ "proveïdor ( temps o entregues)." + +#~ msgid "Product Template" +#~ msgstr "Plantilla del Producte" + +#~ msgid "Sale Order" +#~ msgstr "Comanda de Venda" diff --git a/sale_invoice_policy/i18n/es.po b/sale_invoice_policy/i18n/es.po new file mode 100644 index 00000000000..56a30cb9c5e --- /dev/null +++ b/sale_invoice_policy/i18n/es.po @@ -0,0 +1,105 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_invoice_policy +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-03-22 14:12+0000\n" +"PO-Revision-Date: 2023-03-22 14:12+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_res_config_settings +msgid "Config Settings" +msgstr "Opciones de configuración" + +#. module: sale_invoice_policy +#: model:ir.model.fields.selection,name:sale_invoice_policy.selection__sale_order__invoice_policy__delivery +msgid "Delivered quantities" +msgstr "Cantidades entregadas" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_sale_order__invoice_policy +msgid "Invoice Policy" +msgstr "Política de facturación" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_sale_order__invoice_policy_required +msgid "Invoice Policy Required" +msgstr "Política de facturación requerida" + +#. module: sale_invoice_policy +#: model_terms:ir.ui.view,arch_db:sale_invoice_policy.view_sales_config +msgid "Invoice Policy required in Sale Orders" +msgstr "Política de facturación requerida en pedidos de venta" + +#. module: sale_invoice_policy +#: model:ir.model.fields,help:sale_invoice_policy.field_sale_order__invoice_policy +msgid "" +"Ordered Quantity: Invoice based on the quantity the customer ordered.\n" +"Delivered Quantity: Invoiced based on the quantity the vendor delivered " +"(time or deliveries)." +msgstr "" +"Cantidad pedida: facturada en función de la cantidad pedida por el cliente.\n" +"Cantidad entregada: facturada en función de la cantidad entregada por el " +"proveedor (tiempo o entregas)." + +#. module: sale_invoice_policy +#: model:ir.model.fields.selection,name:sale_invoice_policy.selection__sale_order__invoice_policy__order +msgid "Ordered quantities" +msgstr "Cantidades pedidas" + +#. module: sale_invoice_policy +#: model_terms:ir.ui.view,arch_db:sale_invoice_policy.view_sales_config +msgid "Sale Invoice Policy" +msgstr "Política de facturación para ventas" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_res_config_settings__sale_invoice_policy_required +msgid "Sale Invoice Policy Required" +msgstr "Política de facturación requerida para ventas" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_sale_order +msgid "Sales Order" +msgstr "Pedido de venta" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_sale_order_line +msgid "Sales Order Line" +msgstr "Línea de pedido de venta" + +#. module: sale_invoice_policy +#: model:ir.model.fields,help:sale_invoice_policy.field_res_config_settings__sale_invoice_policy_required +msgid "This makes Invoice Policy required on Sale Orders" +msgstr "Hace que la política de facturación sea requerida en pedidos de venta" + +#~ msgid "Default Invoicing Policy" +#~ msgstr "Política de facturación por defecto" + +#~ msgid "Default Sale Invoice Policy" +#~ msgstr "Política de facturación por defecto para ventas" + +#~ msgid "Default Sale Order Invoice Policy" +#~ msgstr "Política de facturación por defecto en pedidos de venta" + +#~ msgid "Invoicing Policy" +#~ msgstr "Política de facturación" + +#~ msgid "" +#~ "Ordered Quantity: Invoice quantities ordered by the customer.\n" +#~ "Delivered Quantity: Invoice quantities delivered to the customer." +#~ msgstr "" +#~ "Cantidad pedida: facturar cantidades pedidas por el cliente.\n" +#~ "Cantidad entregada: facturar cantidades entregadas al cliente." + +#~ msgid "Product" +#~ msgstr "Producto" diff --git a/sale_invoice_policy/i18n/es_MX.po b/sale_invoice_policy/i18n/es_MX.po new file mode 100644 index 00000000000..41716af6f25 --- /dev/null +++ b/sale_invoice_policy/i18n/es_MX.po @@ -0,0 +1,106 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_invoice_policy +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2021-06-01 21:48+0000\n" +"Last-Translator: Jesús Alan Ramos Rodríguez \n" +"Language-Team: none\n" +"Language: es_MX\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.3.2\n" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_res_config_settings +msgid "Config Settings" +msgstr "Configuración" + +#. module: sale_invoice_policy +#: model:ir.model.fields.selection,name:sale_invoice_policy.selection__sale_order__invoice_policy__delivery +msgid "Delivered quantities" +msgstr "Cantidades entregadas" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_sale_order__invoice_policy +msgid "Invoice Policy" +msgstr "Política de Facturación" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_sale_order__invoice_policy_required +msgid "Invoice Policy Required" +msgstr "Política de facturación requerida" + +#. module: sale_invoice_policy +#: model_terms:ir.ui.view,arch_db:sale_invoice_policy.view_sales_config +msgid "Invoice Policy required in Sale Orders" +msgstr "Política de facturas requerida en órdenes de venta" + +#. module: sale_invoice_policy +#: model:ir.model.fields,help:sale_invoice_policy.field_sale_order__invoice_policy +msgid "" +"Ordered Quantity: Invoice based on the quantity the customer ordered.\n" +"Delivered Quantity: Invoiced based on the quantity the vendor delivered " +"(time or deliveries)." +msgstr "" +"Cantidad pedida: Factura basada en la cantidad solicitada por el cliente.\n" +"Cantidad entregada: Facturada en base a la cantidad entregada por el " +"proveedor (tiempo o entregas)." + +#. module: sale_invoice_policy +#: model:ir.model.fields.selection,name:sale_invoice_policy.selection__sale_order__invoice_policy__order +msgid "Ordered quantities" +msgstr "Cantidades ordenadas" + +#. module: sale_invoice_policy +#: model_terms:ir.ui.view,arch_db:sale_invoice_policy.view_sales_config +msgid "Sale Invoice Policy" +msgstr "" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_res_config_settings__sale_invoice_policy_required +msgid "Sale Invoice Policy Required" +msgstr "Política de facturación en ventas requerido" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_sale_order +msgid "Sales Order" +msgstr "Pedido de Ventas" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_sale_order_line +msgid "Sales Order Line" +msgstr "Línea de Pedido de Venta" + +#. module: sale_invoice_policy +#: model:ir.model.fields,help:sale_invoice_policy.field_res_config_settings__sale_invoice_policy_required +msgid "This makes Invoice Policy required on Sale Orders" +msgstr "" +"Esto hace que la política de facturas sea obligatoria en los pedidos de venta" + +#~ msgid "Default Invoicing Policy" +#~ msgstr "Política de Facturación por Defecto" + +#~ msgid "Default Sale Invoice Policy" +#~ msgstr "Política de Facturación de Ventas por Defecto" + +#~ msgid "Default Sale Order Invoice Policy" +#~ msgstr "Política de factura de pedido de venta predeterminada" + +#~ msgid "Invoicing Policy" +#~ msgstr "Política de facturación" + +#~ msgid "" +#~ "Ordered Quantity: Invoice quantities ordered by the customer.\n" +#~ "Delivered Quantity: Invoice quantities delivered to the customer." +#~ msgstr "" +#~ "Cantidad ordenada: cantidades de factura ordenadas por el cliente.\n" +#~ "Cantidad entregada: cantidades de facturas entregadas al cliente." + +#~ msgid "Product Template" +#~ msgstr "Plantilla de producto" diff --git a/sale_invoice_policy/i18n/fr.po b/sale_invoice_policy/i18n/fr.po new file mode 100644 index 00000000000..de4fccd3454 --- /dev/null +++ b/sale_invoice_policy/i18n/fr.po @@ -0,0 +1,112 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_invoice_policy +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-09-11 13:12+0000\n" +"PO-Revision-Date: 2020-10-20 11:08+0000\n" +"Last-Translator: Quentin Dupont \n" +"Language-Team: \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 3.10\n" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_res_config_settings +msgid "Config Settings" +msgstr "Paramètres de configuration" + +#. module: sale_invoice_policy +#: model:ir.model.fields.selection,name:sale_invoice_policy.selection__sale_order__invoice_policy__delivery +msgid "Delivered quantities" +msgstr "Quantités Livrées" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_sale_order__invoice_policy +msgid "Invoice Policy" +msgstr "Politique de facturation" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_sale_order__invoice_policy_required +msgid "Invoice Policy Required" +msgstr "Politique de facturation requise" + +#. module: sale_invoice_policy +#: model_terms:ir.ui.view,arch_db:sale_invoice_policy.view_sales_config +msgid "Invoice Policy required in Sale Orders" +msgstr "Politique de facturation requise dans les commandes de vente" + +#. module: sale_invoice_policy +#: model:ir.model.fields,help:sale_invoice_policy.field_sale_order__invoice_policy +msgid "" +"Ordered Quantity: Invoice based on the quantity the customer ordered.\n" +"Delivered Quantity: Invoiced based on the quantity the vendor delivered " +"(time or deliveries)." +msgstr "" +"Quantités commandées : facturation basée sur les quantités commandées par le " +"client.\n" +"Quantités livrées : facturation basée sur les quantités que le fournisseur a " +"livré (temps passé dans le cas de prestations)." + +#. module: sale_invoice_policy +#: model:ir.model.fields.selection,name:sale_invoice_policy.selection__sale_order__invoice_policy__order +msgid "Ordered quantities" +msgstr "Quantités Commandées" + +#. module: sale_invoice_policy +#: model_terms:ir.ui.view,arch_db:sale_invoice_policy.view_sales_config +msgid "Sale Invoice Policy" +msgstr "" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_res_config_settings__sale_invoice_policy_required +msgid "Sale Invoice Policy Required" +msgstr "Politique de facturation des ventes requise" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_sale_order_line +msgid "Sales Order Line" +msgstr "Ligne de bon de commande" + +#. module: sale_invoice_policy +#: model:ir.model.fields,help:sale_invoice_policy.field_res_config_settings__sale_invoice_policy_required +msgid "This makes Invoice Policy required on Sale Orders" +msgstr "La politique de facturation est requise sur les bons de commandes" + +#~ msgid "Default Invoicing Policy" +#~ msgstr "Politique de facturation par défaut" + +#~ msgid "Default Sale Invoice Policy" +#~ msgstr "Politique de facturation des ventes par défaut" + +#~ msgid "Default Sale Order Invoice Policy" +#~ msgstr "Politique de facturation par défaut des ventes" + +#~ msgid "Invoicing Policy" +#~ msgstr "Politique de facturation" + +#~ msgid "" +#~ "Ordered Quantity: Invoice quantities ordered by the customer.\n" +#~ "Delivered Quantity: Invoice quantities delivered to the customer." +#~ msgstr "" +#~ "Quantités commandées : facturation basée sur les quantités commandées par " +#~ "le client.\n" +#~ "Quantités livrées : facturation basée sur les quantités que le " +#~ "fournisseur a livré (temps passé dans le cas de prestations)." + +#~ msgid "Product Template" +#~ msgstr "Modèle de produit" + +#~ msgid "Sale Order" +#~ msgstr "Bon de commande" diff --git a/sale_invoice_policy/i18n/it.po b/sale_invoice_policy/i18n/it.po new file mode 100644 index 00000000000..5eec55cb100 --- /dev/null +++ b/sale_invoice_policy/i18n/it.po @@ -0,0 +1,106 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_invoice_policy +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-12-13 12:34+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_res_config_settings +msgid "Config Settings" +msgstr "Impostazioni configurazione" + +#. module: sale_invoice_policy +#: model:ir.model.fields.selection,name:sale_invoice_policy.selection__sale_order__invoice_policy__delivery +msgid "Delivered quantities" +msgstr "Quantità consegnate" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_sale_order__invoice_policy +msgid "Invoice Policy" +msgstr "Politica fatturazione" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_sale_order__invoice_policy_required +msgid "Invoice Policy Required" +msgstr "Richiesta politica fatturazione" + +#. module: sale_invoice_policy +#: model_terms:ir.ui.view,arch_db:sale_invoice_policy.view_sales_config +msgid "Invoice Policy required in Sale Orders" +msgstr "Politica fatturazione richiesta negli ordini di vendita" + +#. module: sale_invoice_policy +#: model:ir.model.fields,help:sale_invoice_policy.field_sale_order__invoice_policy +msgid "" +"Ordered Quantity: Invoice based on the quantity the customer ordered.\n" +"Delivered Quantity: Invoiced based on the quantity the vendor delivered " +"(time or deliveries)." +msgstr "" +"Quantità ordinate: fattura basata sulle quantità ordinate dal cliente.\n" +"Quantità consegnate: fattura basata sulle quantità consegnate da fornitore " +"(tempo o consegne)." + +#. module: sale_invoice_policy +#: model:ir.model.fields.selection,name:sale_invoice_policy.selection__sale_order__invoice_policy__order +msgid "Ordered quantities" +msgstr "Quantità ordinate" + +#. module: sale_invoice_policy +#: model_terms:ir.ui.view,arch_db:sale_invoice_policy.view_sales_config +msgid "Sale Invoice Policy" +msgstr "Plitica fatturazione vendite" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_res_config_settings__sale_invoice_policy_required +msgid "Sale Invoice Policy Required" +msgstr "Richiesta politica fatturazione vendite" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_sale_order +msgid "Sales Order" +msgstr "Ordine di vendita" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_sale_order_line +msgid "Sales Order Line" +msgstr "Riga ordine di vendita" + +#. module: sale_invoice_policy +#: model:ir.model.fields,help:sale_invoice_policy.field_res_config_settings__sale_invoice_policy_required +msgid "This makes Invoice Policy required on Sale Orders" +msgstr "" +"Questo rende obbligatoria la politica di fatturazione negli ordini di vendita" + +#~ msgid "Default Invoicing Policy" +#~ msgstr "Politica fatturazione predefinita" + +#~ msgid "Default Sale Invoice Policy" +#~ msgstr "Politica fatturazione vendite predefinita" + +#~ msgid "Default Sale Order Invoice Policy" +#~ msgstr "Politica fatturazione ordini di vendita predefinita" + +#~ msgid "Invoicing Policy" +#~ msgstr "Politica fatturazione" + +#~ msgid "" +#~ "Ordered Quantity: Invoice quantities ordered by the customer.\n" +#~ "Delivered Quantity: Invoice quantities delivered to the customer." +#~ msgstr "" +#~ "Quantità ordinata: fattura quantità ordinate dal cliente.\n" +#~ "Quantità consegnata: fattura quantità consegnate al cliente." + +#~ msgid "Product" +#~ msgstr "Prodotto" diff --git a/sale_invoice_policy/i18n/pt.po b/sale_invoice_policy/i18n/pt.po new file mode 100644 index 00000000000..f09e8e5fb98 --- /dev/null +++ b/sale_invoice_policy/i18n/pt.po @@ -0,0 +1,111 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_invoice_policy +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2020-09-04 01:00+0000\n" +"Last-Translator: Pedro Castro Silva \n" +"Language-Team: none\n" +"Language: pt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 3.10\n" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_res_config_settings +msgid "Config Settings" +msgstr "Parâmetros de Configuração" + +#. module: sale_invoice_policy +#: model:ir.model.fields.selection,name:sale_invoice_policy.selection__sale_order__invoice_policy__delivery +msgid "Delivered quantities" +msgstr "Quantidades entregues" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_sale_order__invoice_policy +msgid "Invoice Policy" +msgstr "Política de Faturação" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_sale_order__invoice_policy_required +msgid "Invoice Policy Required" +msgstr "Política de Faturação Requerida" + +#. module: sale_invoice_policy +#: model_terms:ir.ui.view,arch_db:sale_invoice_policy.view_sales_config +msgid "Invoice Policy required in Sale Orders" +msgstr "Política de Faturação requerida em Encomendas de Venda" + +#. module: sale_invoice_policy +#: model:ir.model.fields,help:sale_invoice_policy.field_sale_order__invoice_policy +msgid "" +"Ordered Quantity: Invoice based on the quantity the customer ordered.\n" +"Delivered Quantity: Invoiced based on the quantity the vendor delivered " +"(time or deliveries)." +msgstr "" +"Quantidade Encomendada: Faturar com base na quantidade que o cliente " +"encomendou.\n" +"Quantidade Entregue: Faturar tendo em conta a quantidade que o cliente " +"recebeu (tempo ou materiais)." + +#. module: sale_invoice_policy +#: model:ir.model.fields.selection,name:sale_invoice_policy.selection__sale_order__invoice_policy__order +msgid "Ordered quantities" +msgstr "Quantidades encomendadas" + +#. module: sale_invoice_policy +#: model_terms:ir.ui.view,arch_db:sale_invoice_policy.view_sales_config +msgid "Sale Invoice Policy" +msgstr "" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_res_config_settings__sale_invoice_policy_required +msgid "Sale Invoice Policy Required" +msgstr "Política de Faturação de Vendas Requerida" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_sale_order_line +msgid "Sales Order Line" +msgstr "Linha de Encomenda de Venda" + +#. module: sale_invoice_policy +#: model:ir.model.fields,help:sale_invoice_policy.field_res_config_settings__sale_invoice_policy_required +msgid "This makes Invoice Policy required on Sale Orders" +msgstr "" +"Isto faz com que a Política de Faturação seja requerida em Encomendas de " +"Venda" + +#~ msgid "Default Invoicing Policy" +#~ msgstr "Política de Faturação Predefinida" + +#~ msgid "Default Sale Invoice Policy" +#~ msgstr "Política de Faturação de Vendas Predefinida" + +#~ msgid "Default Sale Order Invoice Policy" +#~ msgstr "Política Predefinida de Faturação de Encomendas de Vendas" + +#~ msgid "Invoicing Policy" +#~ msgstr "Política de Faturação" + +#~ msgid "" +#~ "Ordered Quantity: Invoice quantities ordered by the customer.\n" +#~ "Delivered Quantity: Invoice quantities delivered to the customer." +#~ msgstr "" +#~ "Quantidade Encomendada: Faturar quantidades encomendadas pelo cliente.\n" +#~ "Quantidade Entregue: Faturar quantidades entregues ao cliente." + +#~ msgid "Product Template" +#~ msgstr "Modelo de Produto" + +#~ msgid "Sale Order" +#~ msgstr "Encomenda de Venda" diff --git a/sale_invoice_policy/i18n/sale_invoice_policy.pot b/sale_invoice_policy/i18n/sale_invoice_policy.pot new file mode 100644 index 00000000000..8ef4fcb98a9 --- /dev/null +++ b/sale_invoice_policy/i18n/sale_invoice_policy.pot @@ -0,0 +1,83 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_invoice_policy +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \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_invoice_policy +#: model_terms:ir.ui.view,arch_db:sale_invoice_policy.view_sales_config +msgid "" +"Choose the default policy that will be applied for new sale orders (the " +"'Invoicing Policy' configuration is the policy for new products)." +msgstr "" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_res_company +msgid "Companies" +msgstr "" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: sale_invoice_policy +#: model:ir.model.fields.selection,name:sale_invoice_policy.selection__res_company__sale_default_invoice_policy__delivery +#: model:ir.model.fields.selection,name:sale_invoice_policy.selection__sale_order__invoice_policy__delivery +msgid "Delivered quantities" +msgstr "" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_sale_order__invoice_policy +msgid "Invoice Policy" +msgstr "" + +#. module: sale_invoice_policy +#: model:ir.model.fields,help:sale_invoice_policy.field_sale_order__invoice_policy +msgid "" +"Ordered Quantity: Invoice based on the quantity the customer ordered.\n" +"Delivered Quantity: Invoiced based on the quantity the vendor delivered (time or deliveries)." +msgstr "" + +#. module: sale_invoice_policy +#: model:ir.model.fields.selection,name:sale_invoice_policy.selection__res_company__sale_default_invoice_policy__order +#: model:ir.model.fields.selection,name:sale_invoice_policy.selection__sale_order__invoice_policy__order +msgid "Ordered quantities" +msgstr "" + +#. module: sale_invoice_policy +#: model:ir.model.fields.selection,name:sale_invoice_policy.selection__res_company__sale_default_invoice_policy__product +#: model:ir.model.fields.selection,name:sale_invoice_policy.selection__sale_order__invoice_policy__product +msgid "Products Invoice Policy" +msgstr "" + +#. module: sale_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_res_company__sale_default_invoice_policy +#: model:ir.model.fields,field_description:sale_invoice_policy.field_res_config_settings__sale_default_invoice_policy +msgid "Sale Default Invoice Policy" +msgstr "" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: sale_invoice_policy +#: model:ir.model,name:sale_invoice_policy.model_sale_order_line +msgid "Sales Order Line" +msgstr "" + +#. module: sale_invoice_policy +#: model:ir.model.fields,help:sale_invoice_policy.field_res_company__sale_default_invoice_policy +#: model:ir.model.fields,help:sale_invoice_policy.field_res_config_settings__sale_default_invoice_policy +msgid "This will be the default invoice policy for sale orders." +msgstr "" diff --git a/sale_invoice_policy/models/__init__.py b/sale_invoice_policy/models/__init__.py new file mode 100644 index 00000000000..c7862adb115 --- /dev/null +++ b/sale_invoice_policy/models/__init__.py @@ -0,0 +1,4 @@ +from . import res_config_settings +from . import sale_order +from . import sale_order_line +from . import res_company diff --git a/sale_invoice_policy/models/res_company.py b/sale_invoice_policy/models/res_company.py new file mode 100644 index 00000000000..144352c26d4 --- /dev/null +++ b/sale_invoice_policy/models/res_company.py @@ -0,0 +1,19 @@ +# Copyright 2024 ACSONE SA/NV () +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + sale_default_invoice_policy = fields.Selection( + [ + ("product", "Products Invoice Policy"), + ("order", "Ordered quantities"), + ("delivery", "Delivered quantities"), + ], + default="product", + required=True, + help="This will be the default invoice policy for sale orders.", + ) diff --git a/sale_invoice_policy/models/res_config_settings.py b/sale_invoice_policy/models/res_config_settings.py new file mode 100644 index 00000000000..7ba62af605c --- /dev/null +++ b/sale_invoice_policy/models/res_config_settings.py @@ -0,0 +1,13 @@ +# Copyright 2018 ACSONE SA/NV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + sale_default_invoice_policy = fields.Selection( + related="company_id.sale_default_invoice_policy", + readonly=False, + ) diff --git a/sale_invoice_policy/models/sale_order.py b/sale_invoice_policy/models/sale_order.py new file mode 100644 index 00000000000..09fc6e3fc62 --- /dev/null +++ b/sale_invoice_policy/models/sale_order.py @@ -0,0 +1,41 @@ +# Copyright 2017 ACSONE SA/NV () +# Copyright 2025 Jacques-Etienne Baudoux (BCIM) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + invoice_policy = fields.Selection( + [ + ("product", "Products Invoice Policy"), + ("order", "Ordered quantities"), + ("delivery", "Delivered quantities"), + ], + compute="_compute_invoice_policy", + store=True, + readonly=False, + required=True, + precompute=True, + help="Ordered Quantity: Invoice based on the quantity the customer " + "ordered.\n" + "Delivered Quantity: Invoiced based on the quantity the vendor " + "delivered (time or deliveries).", + ) + + @api.depends("company_id") + def _compute_invoice_policy(self) -> None: + """ + Get default sale order invoice policy + """ + for company, sale_orders in self.partition("company_id").items(): + sale_orders.invoice_policy = company.sale_default_invoice_policy + + def _force_lines_to_invoice_policy_order(self): + # When a SO is fully paid by a payment transaction and the automatic + # invoicing is enabled, the lines are forced to policy on order. + # Reflect this on the SO policy. + self.invoice_policy = "order" + return super()._force_lines_to_invoice_policy_order() diff --git a/sale_invoice_policy/models/sale_order_line.py b/sale_invoice_policy/models/sale_order_line.py new file mode 100644 index 00000000000..840f8d285f7 --- /dev/null +++ b/sale_invoice_policy/models/sale_order_line.py @@ -0,0 +1,65 @@ +# Copyright 2017 ACSONE SA/NV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from contextlib import contextmanager + +from odoo import api, models + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + @contextmanager + def _sale_invoice_policy(self, lines): + """Apply the sale invoice policy to the products + + This method must be called with lines sharing the same invoice policy + """ + invoice_policy = set(lines.mapped("order_id.invoice_policy")) + if len(invoice_policy) > 1: + raise Exception( + "The method _sale_invoice_policy() must be called with lines " + "sharing the same invoice policy" + ) + invoice_policy = next(iter(invoice_policy)) + invoice_policy_field = self.env["product.product"]._fields["invoice_policy"] + products = lines.product_id + with self.env.protecting([invoice_policy_field], products): + old_values = {} + for product in products: + old_values[product] = product.invoice_policy + product.invoice_policy = invoice_policy + yield + for product, invoice_policy in old_values.items(): + product.invoice_policy = invoice_policy + + @api.depends("order_id.invoice_policy") + def _compute_qty_to_invoice(self): + """ + Exclude lines that have their order invoice policy filled in + """ + other_lines = self.filtered( + lambda line: line.product_id.type == "service" + or line.order_id.invoice_policy == "product" + ) + super(SaleOrderLine, other_lines)._compute_qty_to_invoice() + for line in self - other_lines: + invoice_policy = line.order_id.invoice_policy + if invoice_policy == "order": + line.qty_to_invoice = line.product_uom_qty - line.qty_invoiced + else: + line.qty_to_invoice = line.qty_delivered - line.qty_invoiced + return True + + @api.depends("order_id.invoice_policy") + def _compute_untaxed_amount_to_invoice(self) -> None: + other_lines = self.filtered( + lambda line: line.product_id.type == "service" + or line.order_id.invoice_policy == "product" + or line.order_id.invoice_policy == line.product_id.invoice_policy + or line.state not in ["sale", "done"] + ) + super(SaleOrderLine, other_lines)._compute_untaxed_amount_to_invoice() + for lines in (self - other_lines).partition("order_id.invoice_policy").values(): + with self._sale_invoice_policy(lines): + super(SaleOrderLine, lines)._compute_untaxed_amount_to_invoice() + return diff --git a/sale_invoice_policy/pyproject.toml b/sale_invoice_policy/pyproject.toml new file mode 100644 index 00000000000..4231d0cccb3 --- /dev/null +++ b/sale_invoice_policy/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/sale_invoice_policy/readme/CONFIGURE.md b/sale_invoice_policy/readme/CONFIGURE.md new file mode 100644 index 00000000000..f3e972f2b48 --- /dev/null +++ b/sale_invoice_policy/readme/CONFIGURE.md @@ -0,0 +1,2 @@ +- Go to Sale \> Configuration \> Settings \> Sale Invoice Policy +- Choose the one that fits your needs. diff --git a/sale_invoice_policy/readme/CONTEXT.md b/sale_invoice_policy/readme/CONTEXT.md new file mode 100644 index 00000000000..85b997949b8 --- /dev/null +++ b/sale_invoice_policy/readme/CONTEXT.md @@ -0,0 +1,13 @@ +In Odoo, products have their own invoicing policy that can be: + +- Invoicing on ordered quantities +- Invoicing on ordered quantities + +Following that configuration, when trying to create invoices from sale +orders, each line of product will apply its invoicing policy. + +In some cases, user needs to apply an invoicing policy on a whole sale +order. + +The solution proposed here is to add an invoicing policy on sale order +level. diff --git a/sale_invoice_policy/readme/CONTRIBUTORS.md b/sale_invoice_policy/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..7ca7af06384 --- /dev/null +++ b/sale_invoice_policy/readme/CONTRIBUTORS.md @@ -0,0 +1,11 @@ +- Cédric Pigeon \<\> +- François Honoré \<\> +- Denis Roussel \<\> +- Alexei Rivera \<\> +- Luis J. Salvatierra \<\> +- Alejandro Ji Cheung \<\> +- Ioan Galan \<\> +- Laurent Mignon \<\> +- Marie Lejeune \<\> +- Jacques-Etienne Baudoux (BCIM) \<\> +- Thomas Herbin \<\> diff --git a/sale_invoice_policy/readme/DESCRIPTION.md b/sale_invoice_policy/readme/DESCRIPTION.md new file mode 100644 index 00000000000..170fb6e08c8 --- /dev/null +++ b/sale_invoice_policy/readme/DESCRIPTION.md @@ -0,0 +1,18 @@ +This module adds an invoicing policy on sale order level in order to +apply that invoicing policy on the whole sale order. + +That invoicing policy can take three values: + +- Products Invoicing Policy: The sale order will follow the standard + behavior and apply the policy depending on products configurations. +- Ordered Quantities: The sale order will invoice the ordered + quantities. +- Delivered Quantities: The sale order will invoice the delivered + quantities. + +Following the chosen policy, the quantity to invoice and the amount to +invoice on each line will be computed accordingly. + +You will be able also to define a default invoicing policy (globally per +company) that can be different than the default invoicing policy for new +products. diff --git a/sale_invoice_policy/readme/USAGE.md b/sale_invoice_policy/readme/USAGE.md new file mode 100644 index 00000000000..fc46d77b22d --- /dev/null +++ b/sale_invoice_policy/readme/USAGE.md @@ -0,0 +1,4 @@ +- Create Sale Order +- Select Invoicing Policy on Sale Order or let it on Products Invoicing + Policy +- The created invoices will use the configuration on sale order. diff --git a/sale_invoice_policy/static/description/icon.png b/sale_invoice_policy/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/sale_invoice_policy/static/description/icon.png differ diff --git a/sale_invoice_policy/static/description/index.html b/sale_invoice_policy/static/description/index.html new file mode 100644 index 00000000000..4bae6ec2334 --- /dev/null +++ b/sale_invoice_policy/static/description/index.html @@ -0,0 +1,487 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Sale invoice Policy

+ +

Beta License: AGPL-3 OCA/sale-workflow Translate me on Weblate Try me on Runboat

+

This module adds an invoicing policy on sale order level in order to +apply that invoicing policy on the whole sale order.

+

That invoicing policy can take three values:

+
    +
  • Products Invoicing Policy: The sale order will follow the standard +behavior and apply the policy depending on products configurations.
  • +
  • Ordered Quantities: The sale order will invoice the ordered +quantities.
  • +
  • Delivered Quantities: The sale order will invoice the delivered +quantities.
  • +
+

Following the chosen policy, the quantity to invoice and the amount to +invoice on each line will be computed accordingly.

+

You will be able also to define a default invoicing policy (globally per +company) that can be different than the default invoicing policy for new +products.

+

Table of contents

+ +
+

Use Cases / Context

+

In Odoo, products have their own invoicing policy that can be:

+
    +
  • Invoicing on ordered quantities
  • +
  • Invoicing on ordered quantities
  • +
+

Following that configuration, when trying to create invoices from sale +orders, each line of product will apply its invoicing policy.

+

In some cases, user needs to apply an invoicing policy on a whole sale +order.

+

The solution proposed here is to add an invoicing policy on sale order +level.

+
+
+

Configuration

+
    +
  • Go to Sale > Configuration > Settings > Sale Invoice Policy
  • +
  • Choose the one that fits your needs.
  • +
+
+
+

Usage

+
    +
  • Create Sale Order
  • +
  • Select Invoicing Policy on Sale Order or let it on Products Invoicing +Policy
  • +
  • The created invoices will use the configuration on sale order.
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub 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.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • ACSONE SA/NV
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +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.

+

This module is part of the OCA/sale-workflow project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+
+ + diff --git a/sale_invoice_policy/tests/__init__.py b/sale_invoice_policy/tests/__init__.py new file mode 100644 index 00000000000..f6ba0a75874 --- /dev/null +++ b/sale_invoice_policy/tests/__init__.py @@ -0,0 +1 @@ +from . import test_sale_invoice_policy diff --git a/sale_invoice_policy/tests/test_sale_invoice_policy.py b/sale_invoice_policy/tests/test_sale_invoice_policy.py new file mode 100644 index 00000000000..bbb6fee51b8 --- /dev/null +++ b/sale_invoice_policy/tests/test_sale_invoice_policy.py @@ -0,0 +1,188 @@ +# Copyright 2017 Acsone SA/NV (http://www.acsone.eu) +# Copyright 2025 Jacques-Etienne Baudoux (BCIM) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import odoo.tests.common as common + + +class TestSaleOrderInvoicePolicy(common.TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.product_obj = cls.env["product.product"] + cls.sale_obj = cls.env["sale.order"] + cls.partner = cls.env["res.partner"].create( + { + "name": "Bob", + } + ) + cls.product = cls.product_obj.create( + {"name": "Test", "type": "consu", "list_price": 20.0} + ) + cls.product2 = cls.product_obj.create( + {"name": "Test 2", "type": "consu", "list_price": 45.0} + ) + cls.product3 = cls.product_obj.create( + { + "name": "Test 3 (service)", + "type": "service", + "list_price": 850.5, + } + ) + + def test_sale_order_invoice_order(self): + """Test invoicing based on ordered quantities""" + so = self.env["sale.order"].create( + { + "partner_id": self.partner.id, + "order_line": [ + (0, 0, {"product_id": self.product.id, "product_uom_qty": 2.0}), + (0, 0, {"product_id": self.product2.id, "product_uom_qty": 3.0}), + ], + "invoice_policy": "order", + } + ) + self.assertEqual(so.invoice_policy, "order") + + # SO is not confirmed yet + self.assertEqual([0.0, 0.0], so.order_line.mapped("untaxed_amount_to_invoice")) + + so.action_confirm() + + self.assertEqual(len(so.picking_ids), 1) + + picking = so.picking_ids + picking.action_assign() + self.assertEqual(picking.state, "assigned") + so_line = so.order_line[0] + self.assertEqual(so_line.qty_to_invoice, 2) + self.assertEqual(so_line.invoice_status, "to invoice") + self.assertEqual(so_line.product_id.invoice_policy, "order") + + so_line = so.order_line[1] + self.assertEqual(so_line.qty_to_invoice, 3) + self.assertEqual(so_line.invoice_status, "to invoice") + self.assertEqual(so_line.product_id.invoice_policy, "order") + + def test_sale_order_invoice_deliver(self): + """Test invoicing based on delivered quantities""" + self.assertEqual("order", self.product.invoice_policy) + so = self.env["sale.order"].create( + { + "partner_id": self.partner.id, + "invoice_policy": "delivery", + "order_line": [ + (0, 0, {"product_id": self.product.id, "product_uom_qty": 2.0}), + (0, 0, {"product_id": self.product2.id, "product_uom_qty": 3.0}), + ], + } + ) + self.assertEqual(so.invoice_policy, "delivery") + + # SO is not confirmed yet + self.assertEqual([0.0, 0.0], so.order_line.mapped("untaxed_amount_to_invoice")) + + so.action_confirm() + + # SO is not delivered + self.assertEqual([0.0, 0.0], so.order_line.mapped("untaxed_amount_to_invoice")) + + self.assertEqual(len(so.picking_ids), 1) + + picking = so.picking_ids + picking.action_assign() + self.assertEqual(picking.state, "assigned") + + so_line = so.order_line[0] + self.assertEqual(so_line.qty_to_invoice, 0) + self.assertEqual(so_line.invoice_status, "no") + + so_line = so.order_line[1] + self.assertEqual(so_line.qty_to_invoice, 0) + self.assertEqual(so_line.invoice_status, "no") + + for mv in picking.move_line_ids: + mv.quantity = mv.quantity_product_uom + picking.button_validate() + self.assertEqual(picking.state, "done") + + so_line = so.order_line[0] + self.assertEqual(so_line.qty_to_invoice, 2) + self.assertEqual(so_line.invoice_status, "to invoice") + + self.assertEqual(40.0, so_line.untaxed_amount_to_invoice) + # Check that product has still its original invoice policy + self.assertEqual("order", self.product.invoice_policy) + + so_line = so.order_line[1] + self.assertEqual(so_line.qty_to_invoice, 3) + self.assertEqual(so_line.invoice_status, "to invoice") + + self.assertEqual(135.0, so_line.untaxed_amount_to_invoice) + + def test_settings(self): + # delivery policy is the default + settings = self.env["res.config.settings"].create({}) + settings.sale_default_invoice_policy = "delivery" + settings.execute() + so = self.env["sale.order"].create({"partner_id": self.partner.id}) + self.assertEqual(so.invoice_policy, "delivery") + # order policy is the default + settings.sale_default_invoice_policy = "order" + settings.execute() + so = self.env["sale.order"].create({"partner_id": self.partner.id}) + self.assertEqual(so.invoice_policy, "order") + + def test_context_manager_exception(self): + """Check the exception is well managed when called with several + invoice policies""" + self.assertEqual("order", self.product.invoice_policy) + so = self.env["sale.order"].create( + { + "partner_id": self.partner.id, + "invoice_policy": "delivery", + "order_line": [ + (0, 0, {"product_id": self.product.id, "product_uom_qty": 2.0}), + (0, 0, {"product_id": self.product2.id, "product_uom_qty": 3.0}), + ], + } + ) + + so2 = self.env["sale.order"].create( + { + "partner_id": self.partner.id, + "invoice_policy": "order", + "order_line": [ + (0, 0, {"product_id": self.product.id, "product_uom_qty": 2.0}), + (0, 0, {"product_id": self.product2.id, "product_uom_qty": 3.0}), + ], + } + ) + lines = (so | so2).order_line + with self.assertRaises(Exception) as exc: + with self.env["sale.order.line"]._sale_invoice_policy( + lines + ): # pragma: no cover + pass + self.assertEqual( + "The method _sale_invoice_policy() must be called with lines sharing " + "the same invoice policy", + exc.exception.args[0], + ) + + def test_force_lines_to_invoice_policy_order(self): + """Check when a SO is fully paid by a payment transaction and the automatic + invoicing is enabled, that the policy is changed on order.""" + so = self.env["sale.order"].create( + { + "partner_id": self.partner.id, + "order_line": [ + (0, 0, {"product_id": self.product.id, "product_uom_qty": 2.0}), + (0, 0, {"product_id": self.product2.id, "product_uom_qty": 3.0}), + ], + "invoice_policy": "delivery", + } + ) + so.action_confirm() + self.assertEqual(so.invoice_policy, "delivery") + so._force_lines_to_invoice_policy_order() + self.assertEqual(so.invoice_policy, "order") diff --git a/sale_invoice_policy/views/res_config_settings_view.xml b/sale_invoice_policy/views/res_config_settings_view.xml new file mode 100644 index 00000000000..da3432e6eb8 --- /dev/null +++ b/sale_invoice_policy/views/res_config_settings_view.xml @@ -0,0 +1,21 @@ + + + + sale settings + res.config.settings + + + + + + + + + + diff --git a/sale_invoice_policy/views/sale_view.xml b/sale_invoice_policy/views/sale_view.xml new file mode 100644 index 00000000000..9fb0e0024a5 --- /dev/null +++ b/sale_invoice_policy/views/sale_view.xml @@ -0,0 +1,14 @@ + + + + sale.order.form (abi_sale_invoice_policy) + sale.order + + 50 + + + + + + + diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 00000000000..d1d3d0512e0 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1 @@ +odoo-addon-base_partition @ git+https://github.com/OCA/server-tools.git@refs/pull/3465/head#subdirectory=base_partition