Initial commit: Odoo 18.0-20251222 extra-addons
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
tests / Detect unreleased dependencies (push) Has been cancelled
tests / test with OCB (push) Has been cancelled
tests / test with Odoo (push) Has been cancelled

This commit is contained in:
tocmo0nlord
2026-03-13 20:43:25 +00:00
parent 36e847a7df
commit adbe430761
9472 changed files with 1265727 additions and 0 deletions

119
sale_global_discount/README.rst Executable file
View File

@@ -0,0 +1,119 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association
====================
Sale Global Discount
====================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:0eb7725122ba2ca05d2f6a97d79d139caa962ff2d902b7833ad0ce79c81e08ab
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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/18.0/sale_global_discount
: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-18-0/sale-workflow-18-0-sale_global_discount
: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=18.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
Apply global financial discounts to sales that will be transmited to
invoices and accounting.
**Table of contents**
.. contents::
:local:
Configuration
=============
To configure this module please refer to configure section of the
base_global_discount module.
Usage
=====
To use this module, you need to:
1. See usage section of the base_global_discount module.
2. Create a new sale order and choose a partner.
3. If the partner has customer global discounts set, those will be
applied to the order by default.
4. Otherwise, you can set them manually from the header of the sale
order.
5. In the order footer, you can see the computed discounts.
6. When you create an invoice from the order, the proper global
discounts will be applied on it.
Known issues / Roadmap
======================
- Not all the taxes combination can be compatible with global discounts.
An error is raised in that cases.
- Currently, taxes in invoice lines are mandatory with global discounts.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/sale-workflow/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/sale-workflow/issues/new?body=module:%20sale_global_discount%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* Tecnativa
Contributors
------------
- `Tecnativa <https://www.tecnativa.com>`__
- David Vidal
- Pedro M. Baeza
- Omar Castiñeira <omar@comunitea.com>
- `Studio73 <https://www.studio73.es>`__
- Miguel Gandia
- Eugenio Micó
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 <https://github.com/OCA/sale-workflow/tree/18.0/sale_global_discount>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,2 @@
from . import models
from .hooks import _pre_init_global_discount_fields

View File

@@ -0,0 +1,16 @@
# Copyright 2020 Tecnativa - David Vidal
# Copyright 2020 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
"name": "Sale Global Discount",
"version": "18.0.1.0.0",
"category": "Sales Management",
"author": "Tecnativa," "Odoo Community Association (OCA)",
"website": "https://github.com/OCA/sale-workflow",
"license": "AGPL-3",
"depends": ["account_global_discount", "sale"],
"data": ["views/sale_order_views.xml", "views/report_sale_order.xml"],
"application": False,
"installable": True,
"pre_init_hook": "_pre_init_global_discount_fields",
}

42
sale_global_discount/hooks.py Executable file
View File

@@ -0,0 +1,42 @@
from odoo.tools.sql import column_exists
def _pre_init_global_discount_fields(env):
if not column_exists(env.cr, "sale_order", "amount_global_discount"):
env.cr.execute(
"""
ALTER TABLE "sale_order"
ADD COLUMN "amount_global_discount" double precision DEFAULT 0
"""
)
env.cr.execute(
"""
ALTER TABLE "sale_order" ALTER COLUMN "amount_global_discount" DROP DEFAULT
"""
)
if not column_exists(
env.cr, "sale_order", "amount_untaxed_before_global_discounts"
):
env.cr.execute(
"""
ALTER TABLE "sale_order"
ADD COLUMN "amount_untaxed_before_global_discounts" double precision
"""
)
env.cr.execute(
"""
update sale_order set amount_untaxed_before_global_discounts = amount_untaxed
"""
)
if not column_exists(env.cr, "sale_order", "amount_total_before_global_discounts"):
env.cr.execute(
"""
ALTER TABLE "sale_order"
ADD COLUMN "amount_total_before_global_discounts" double precision
"""
)
env.cr.execute(
"""
update sale_order set amount_total_before_global_discounts = amount_total
"""
)

86
sale_global_discount/i18n/ca.po Executable file
View File

@@ -0,0 +1,86 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_global_discount
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 17.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-06-03 16:25+0000\n"
"Last-Translator: Francisco Javier Galera Martínez <javier.galera@sygel.es>\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 5.10.4\n"
#. module: sale_global_discount
#: model_terms:ir.ui.view,arch_db:sale_global_discount.report_saleorder_document
msgid ""
"<strong>Global Discounts</strong>\n"
" <br/>"
msgstr ""
"<strong>Descomptes globals</strong>\n"
" <br/>"
#. module: sale_global_discount
#: model_terms:ir.ui.view,arch_db:sale_global_discount.report_saleorder_document
msgid "<strong>Subtl. before disc.</strong>"
msgstr "<strong>Subtl. abans del disc.</strong>"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_total_before_global_discounts
msgid "Amount Total Before Discounts"
msgstr "Import total abans dels descomptes"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_untaxed_before_global_discounts
msgid "Amount Untaxed Before Discounts"
msgstr "Import no gravat abans dels descomptes"
#. module: sale_global_discount
#. odoo-python
#: code:addons/sale_global_discount/models/sale_order.py:0
#, python-format
msgid "Incompatible taxes found for global discounts."
msgstr "S'han trobat impostos incompatibles per a descomptes globals."
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__global_discount_ids
#: model_terms:ir.ui.view,arch_db:sale_global_discount.view_order_form
msgid "Sale Global Discounts"
msgstr "Venda Descomptes globals"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__global_discount_ids_readonly
msgid "Sale Global Discounts (readonly)"
msgstr "Rebaixes i descomptes globals (només lectura)"
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_sale_order
msgid "Sales Order"
msgstr "Comanda de venda"
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_sale_order_line
msgid "Sales Order Line"
msgstr "Línia de comanda de vendes"
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_account_tax
msgid "Tax"
msgstr "Impost"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_global_discount
msgid "Total Global Discounts"
msgstr "Descomptes globals totals"
#. module: sale_global_discount
#. odoo-python
#: code:addons/sale_global_discount/models/sale_order.py:0
#, python-format
msgid "With global discounts, taxes in lines are required."
msgstr "Amb descomptes globals, cal afegir impostos a les línies."

View File

@@ -0,0 +1,82 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_global_discount
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 17.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: ca_ES\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"
#. module: sale_global_discount
#: model_terms:ir.ui.view,arch_db:sale_global_discount.report_saleorder_document
msgid ""
"<strong>Global Discounts</strong>\n"
" <br/>"
msgstr ""
#. module: sale_global_discount
#: model_terms:ir.ui.view,arch_db:sale_global_discount.report_saleorder_document
msgid "<strong>Subtl. before disc.</strong>"
msgstr ""
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_total_before_global_discounts
msgid "Amount Total Before Discounts"
msgstr ""
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_untaxed_before_global_discounts
msgid "Amount Untaxed Before Discounts"
msgstr ""
#. module: sale_global_discount
#. odoo-python
#: code:addons/sale_global_discount/models/sale_order.py:0
#, python-format
msgid "Incompatible taxes found for global discounts."
msgstr ""
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__global_discount_ids
#: model_terms:ir.ui.view,arch_db:sale_global_discount.view_order_form
msgid "Sale Global Discounts"
msgstr ""
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__global_discount_ids_readonly
msgid "Sale Global Discounts (readonly)"
msgstr ""
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_sale_order
msgid "Sales Order"
msgstr ""
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_sale_order_line
msgid "Sales Order Line"
msgstr ""
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_account_tax
msgid "Tax"
msgstr ""
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_global_discount
msgid "Total Global Discounts"
msgstr ""
#. module: sale_global_discount
#. odoo-python
#: code:addons/sale_global_discount/models/sale_order.py:0
#, python-format
msgid "With global discounts, taxes in lines are required."
msgstr ""

99
sale_global_discount/i18n/es.po Executable file
View File

@@ -0,0 +1,99 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_global_discount
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 11.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-06 14:38+0000\n"
"PO-Revision-Date: 2025-05-12 18:23+0000\n"
"Last-Translator: DantePereyra <dante.pereyra@ciclosmontecastelo.com>\n"
"Language-Team: \n"
"Language: es\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 5.10.4\n"
#. module: sale_global_discount
#: model_terms:ir.ui.view,arch_db:sale_global_discount.report_saleorder_document
msgid ""
"<strong>Global Discounts</strong>\n"
" <br/>"
msgstr ""
"<strong>Descuentos globales</strong>\n"
" <br/>"
#. module: sale_global_discount
#: model_terms:ir.ui.view,arch_db:sale_global_discount.report_saleorder_document
msgid "<strong>Subtl. before disc.</strong>"
msgstr "<strong>Subtl. antes desc.</strong>"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_total_before_global_discounts
msgid "Amount Total Before Discounts"
msgstr "Base imponible sin descuentos"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_untaxed_before_global_discounts
msgid "Amount Untaxed Before Discounts"
msgstr "Base imponible sin descuentos"
#. module: sale_global_discount
#. odoo-python
#: code:addons/sale_global_discount/models/sale_order.py:0
#, python-format
msgid "Incompatible taxes found for global discounts."
msgstr "Se han encontrado impuestos incompatibles para descuentos globales."
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__global_discount_ids
#: model_terms:ir.ui.view,arch_db:sale_global_discount.view_order_form
msgid "Sale Global Discounts"
msgstr "Descuentos de venta globales"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__global_discount_ids_readonly
msgid "Sale Global Discounts (readonly)"
msgstr "Descuentos globales de venta (solo lectura)"
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_sale_order
msgid "Sales Order"
msgstr "Pedido de venta"
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_sale_order_line
msgid "Sales Order Line"
msgstr "Línea de pedido de venta"
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_account_tax
msgid "Tax"
msgstr "Impuesto"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_global_discount
msgid "Total Global Discounts"
msgstr "Descuentos globales totales"
#. module: sale_global_discount
#. odoo-python
#: code:addons/sale_global_discount/models/sale_order.py:0
#, python-format
msgid "With global discounts, taxes in lines are required."
msgstr "Con descuentos globales, se requieren impuestos en líneas."
#~ msgid "<strong>Global Discounts</strong><br/>"
#~ msgstr "<strong>Descuentos Globales</strong><br/>"
#~ msgid "Sale Order"
#~ msgstr "Pedido de venta"
#~ msgid "Quotation"
#~ msgstr "Presupuesto"
#~ msgid "<strong>Global Discount</strong>"
#~ msgstr "<strong>Descuento Global</strong>"

84
sale_global_discount/i18n/fr.po Executable file
View File

@@ -0,0 +1,84 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_global_discount
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-09-17 12:42+0000\n"
"Last-Translator: matnoublanche <log-github@thieu.biz>\n"
"Language-Team: none\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 5.10.4\n"
#. module: sale_global_discount
#: model_terms:ir.ui.view,arch_db:sale_global_discount.report_saleorder_document
msgid ""
"<strong>Global Discounts</strong>\n"
" <br/>"
msgstr ""
"<strong>Remises globales</strong>\n"
" <br/>"
#. module: sale_global_discount
#: model_terms:ir.ui.view,arch_db:sale_global_discount.report_saleorder_document
msgid "<strong>Subtl. before disc.</strong>"
msgstr "<strong>Sous-total sans remise</strong>"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_total_before_global_discounts
msgid "Amount Total Before Discounts"
msgstr "Montant total avant remises"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_untaxed_before_global_discounts
msgid "Amount Untaxed Before Discounts"
msgstr "Montant hors taxes avant remises"
#. module: sale_global_discount
#. odoo-python
#: code:addons/sale_global_discount/models/sale_order.py:0
msgid "Incompatible taxes found for global discounts."
msgstr "Taxes incompatibles trouvées pour les remises globales."
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__global_discount_ids
#: model_terms:ir.ui.view,arch_db:sale_global_discount.view_order_form
msgid "Sale Global Discounts"
msgstr "Remises globales sur les ventes"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__global_discount_ids_readonly
msgid "Sale Global Discounts (readonly)"
msgstr "Remises globales sur les ventes (lecture seule)"
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_sale_order
msgid "Sales Order"
msgstr "Bon de commande"
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_sale_order_line
msgid "Sales Order Line"
msgstr "Ligne de commande"
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_account_tax
msgid "Tax"
msgstr "Taxe"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_global_discount
msgid "Total Global Discounts"
msgstr "Total des remises globales"
#. module: sale_global_discount
#. odoo-python
#: code:addons/sale_global_discount/models/sale_order.py:0
msgid "With global discounts, taxes in lines are required."
msgstr "Avec les remises globales, les taxes sur les lignes sont requises."

95
sale_global_discount/i18n/it.po Executable file
View File

@@ -0,0 +1,95 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_global_discount
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-07-08 08:59+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\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 5.6.2\n"
#. module: sale_global_discount
#: model_terms:ir.ui.view,arch_db:sale_global_discount.report_saleorder_document
msgid ""
"<strong>Global Discounts</strong>\n"
" <br/>"
msgstr ""
"<strong>Sconti Globali</strong>\n"
" <br/>"
#. module: sale_global_discount
#: model_terms:ir.ui.view,arch_db:sale_global_discount.report_saleorder_document
msgid "<strong>Subtl. before disc.</strong>"
msgstr "<strong>Subtot. prima degli sconti</strong>"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_total_before_global_discounts
msgid "Amount Total Before Discounts"
msgstr "Importo Totale prima degli sconti"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_untaxed_before_global_discounts
msgid "Amount Untaxed Before Discounts"
msgstr "Imponibile prima degli sconti"
#. module: sale_global_discount
#. odoo-python
#: code:addons/sale_global_discount/models/sale_order.py:0
#, python-format
msgid "Incompatible taxes found for global discounts."
msgstr "Sono presenti tasse incompatibili per gli sconti globali."
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__global_discount_ids
#: model_terms:ir.ui.view,arch_db:sale_global_discount.view_order_form
msgid "Sale Global Discounts"
msgstr "Sconti Globali Vendite"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__global_discount_ids_readonly
msgid "Sale Global Discounts (readonly)"
msgstr "Sconti Globali Vendite (sola lettura)"
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_sale_order
msgid "Sales Order"
msgstr "Ordine di vendita"
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_sale_order_line
msgid "Sales Order Line"
msgstr "Riga ordine di vendita"
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_account_tax
msgid "Tax"
msgstr "Imposta"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_global_discount
msgid "Total Global Discounts"
msgstr "Totale Sconti Globali"
#. module: sale_global_discount
#. odoo-python
#: code:addons/sale_global_discount/models/sale_order.py:0
#, python-format
msgid "With global discounts, taxes in lines are required."
msgstr "Con gli sconti globali, sono necessarie le tasse sulle righe ordine."
#~ msgid "Display Name"
#~ msgstr "Nome da visualizzare"
#~ msgid "ID"
#~ msgstr "ID"
#~ msgid "Last Modified on"
#~ msgstr "Ultima modifica il"

90
sale_global_discount/i18n/pt.po Executable file
View File

@@ -0,0 +1,90 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_global_discount
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2021-01-05 14:44+0000\n"
"Last-Translator: Pedro Castro Silva <pedrocs@exo.pt>\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 4.3.2\n"
#. module: sale_global_discount
#: model_terms:ir.ui.view,arch_db:sale_global_discount.report_saleorder_document
msgid ""
"<strong>Global Discounts</strong>\n"
" <br/>"
msgstr ""
#. module: sale_global_discount
#: model_terms:ir.ui.view,arch_db:sale_global_discount.report_saleorder_document
msgid "<strong>Subtl. before disc.</strong>"
msgstr "<strong>Subtotal s/ Desc.</strong>"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_total_before_global_discounts
msgid "Amount Total Before Discounts"
msgstr "Total sem Descontos"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_untaxed_before_global_discounts
msgid "Amount Untaxed Before Discounts"
msgstr "Total sem Impostos antes de Descontos"
#. module: sale_global_discount
#. odoo-python
#: code:addons/sale_global_discount/models/sale_order.py:0
#, python-format
msgid "Incompatible taxes found for global discounts."
msgstr "Foram encontrados impostos incompatíveis para descontos globais."
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__global_discount_ids
#: model_terms:ir.ui.view,arch_db:sale_global_discount.view_order_form
msgid "Sale Global Discounts"
msgstr "Descontos Globais de Venda"
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__global_discount_ids_readonly
msgid "Sale Global Discounts (readonly)"
msgstr "Descontos Globais de Venda (apenas leitura)"
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_sale_order
msgid "Sales Order"
msgstr ""
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_sale_order_line
msgid "Sales Order Line"
msgstr ""
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_account_tax
msgid "Tax"
msgstr ""
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_global_discount
msgid "Total Global Discounts"
msgstr "Total de Descontos Globais"
#. module: sale_global_discount
#. odoo-python
#: code:addons/sale_global_discount/models/sale_order.py:0
#, python-format
msgid "With global discounts, taxes in lines are required."
msgstr "Com descontos globais, é obrigatório ter impostos nas linhas."
#~ msgid "<strong>Global Discounts</strong><br/>"
#~ msgstr "<strong>Descontos Globais</strong><br/>"
#~ msgid "Sale Order"
#~ msgstr "Encomenda de Venda"

View File

@@ -0,0 +1,79 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_global_discount
#
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_global_discount
#: model_terms:ir.ui.view,arch_db:sale_global_discount.report_saleorder_document
msgid ""
"<strong>Global Discounts</strong>\n"
" <br/>"
msgstr ""
#. module: sale_global_discount
#: model_terms:ir.ui.view,arch_db:sale_global_discount.report_saleorder_document
msgid "<strong>Subtl. before disc.</strong>"
msgstr ""
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_total_before_global_discounts
msgid "Amount Total Before Discounts"
msgstr ""
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_untaxed_before_global_discounts
msgid "Amount Untaxed Before Discounts"
msgstr ""
#. module: sale_global_discount
#. odoo-python
#: code:addons/sale_global_discount/models/sale_order.py:0
msgid "Incompatible taxes found for global discounts."
msgstr ""
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__global_discount_ids
#: model_terms:ir.ui.view,arch_db:sale_global_discount.view_order_form
msgid "Sale Global Discounts"
msgstr ""
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__global_discount_ids_readonly
msgid "Sale Global Discounts (readonly)"
msgstr ""
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_sale_order
msgid "Sales Order"
msgstr ""
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_sale_order_line
msgid "Sales Order Line"
msgstr ""
#. module: sale_global_discount
#: model:ir.model,name:sale_global_discount.model_account_tax
msgid "Tax"
msgstr ""
#. module: sale_global_discount
#: model:ir.model.fields,field_description:sale_global_discount.field_sale_order__amount_global_discount
msgid "Total Global Discounts"
msgstr ""
#. module: sale_global_discount
#. odoo-python
#: code:addons/sale_global_discount/models/sale_order.py:0
msgid "With global discounts, taxes in lines are required."
msgstr ""

View File

@@ -0,0 +1,3 @@
from . import account_tax
from . import sale_order_line
from . import sale_order

View File

@@ -0,0 +1,27 @@
# Copyright 2023 Studio73 - Ferran Mora
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import models
class AccountTax(models.Model):
_inherit = "account.tax"
def _prepare_base_line_for_taxes_computation(self, record, **kwargs):
if (
not isinstance(record, models.Model)
or record._name != "sale.order.line"
or not record.order_id.global_discount_ids
or not self.env.context.get("from_tax_calculation", False)
):
return super()._prepare_base_line_for_taxes_computation(record, **kwargs)
price_unit = kwargs.get("price_unit", 0.0)
discounted_price_unit = price_unit
if not record.product_id.bypass_global_discount:
discounts = record.order_id.global_discount_ids.mapped("discount")
discounted_price_unit = record.order_id.get_discounted_global(
price_unit, discounts.copy()
)
kwargs.update({"price_unit": discounted_price_unit})
return super()._prepare_base_line_for_taxes_computation(record, **kwargs)

View File

@@ -0,0 +1,248 @@
# Copyright 2020 Tecnativa - David Vidal
# Copyright 2020 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, exceptions, fields, models
from odoo.tools.misc import formatLang
class SaleOrder(models.Model):
_inherit = "sale.order"
global_discount_ids = fields.Many2many(
comodel_name="global.discount",
string="Sale Global Discounts",
domain="[('discount_scope', '=', 'sale'), "
"('account_id', '!=', False), '|', "
"('company_id', '=', company_id), ('company_id', '=', False)]",
compute="_compute_global_discount_ids",
store=True,
readonly=False,
)
# HACK: Looks like UI doesn't behave well with Many2many fields and
# negative groups when the same field is shown. In this case, we want to
# show the readonly version to any not in the global discount group.
# TODO: Check if it's fixed in future versions
global_discount_ids_readonly = fields.Many2many(
related="global_discount_ids",
string="Sale Global Discounts (readonly)",
readonly=True,
)
amount_global_discount = fields.Monetary(
string="Total Global Discounts",
compute="_compute_amounts", # pylint: disable=C8108
currency_field="currency_id",
compute_sudo=True, # Odoo core fields are storable so compute_sudo is True
readonly=True,
store=True,
)
amount_untaxed_before_global_discounts = fields.Monetary(
string="Amount Untaxed Before Discounts",
compute="_compute_amounts", # pylint: disable=C8108
currency_field="currency_id",
compute_sudo=True, # Odoo core fields are storable so compute_sudo is True
readonly=True,
store=True,
)
amount_total_before_global_discounts = fields.Monetary(
string="Amount Total Before Discounts",
compute="_compute_amounts", # pylint: disable=C8108
currency_field="currency_id",
compute_sudo=True, # Odoo core fields are storable so compute_sudo is True
readonly=True,
store=True,
)
@api.model
def get_discounted_global(self, price=0, discounts=None):
if not discounts:
return price
discounted_price = price
for discount in discounts:
discounted_price *= 1 - (discount / 100)
return discounted_price
def _check_global_discounts_sanity(self):
"""Perform a sanity check for discarding cases that will lead to
incorrect data in discounts.
"""
self.ensure_one()
if not self.global_discount_ids:
return True
taxes_keys = {}
for line in self.order_line.filtered(
lambda _line: not _line.display_type
and _line.product_id
and not _line.product_id.bypass_global_discount
):
if not line.tax_id:
raise exceptions.UserError(
self.env._("With global discounts, taxes in lines are required.")
)
for key in taxes_keys:
if key == line.tax_id:
break
elif key & line.tax_id:
raise exceptions.UserError(
self.env._("Incompatible taxes found for global discounts.")
)
else:
taxes_keys[line.tax_id] = True
@api.depends(
"order_line.product_id.bypass_global_discount",
"order_line.price_subtotal",
"order_line.price_tax",
"order_line.price_total",
"global_discount_ids",
)
def _compute_amounts(self):
res = super()._compute_amounts()
for order in self:
order._check_global_discounts_sanity()
amount_untaxed_before_global_discounts = order.amount_untaxed
amount_total_before_global_discounts = order.amount_total
discounts = order.global_discount_ids.mapped("discount")
amount_discounted_untaxed = amount_discounted_tax = 0
for line in order.order_line:
discounted_subtotal = line.price_subtotal
if not line.product_id.bypass_global_discount:
discounted_subtotal = self.get_discounted_global(
line.price_subtotal, discounts.copy()
)
amount_discounted_untaxed += discounted_subtotal
discounted_tax = line.tax_id.with_context(
force_price_include=False
).compute_all(
discounted_subtotal,
line.order_id.currency_id,
1.0,
product=line.product_id,
partner=line.order_id.partner_shipping_id,
)
amount_discounted_tax += sum(
t.get("amount", 0.0) for t in discounted_tax.get("taxes", [])
)
order.update(
{
"amount_untaxed_before_global_discounts": (
amount_untaxed_before_global_discounts
),
"amount_total_before_global_discounts": (
amount_total_before_global_discounts
),
"amount_global_discount": (
amount_untaxed_before_global_discounts
- amount_discounted_untaxed
),
"amount_untaxed": amount_discounted_untaxed,
"amount_tax": amount_discounted_tax,
"amount_total": (amount_discounted_untaxed + amount_discounted_tax),
}
)
return res
def _compute_tax_totals(self):
res = super()._compute_tax_totals()
for order in self:
amount_discount_by_group = {}
cumulative_discount_rate = 1.0
currency = order.currency_id
# Calculate cumulative discount rate
for gbl_disc in order.global_discount_ids:
discount_rate = gbl_disc.discount / 100
cumulative_discount_rate *= 1 - discount_rate
base_amount = 0.0
# Calculate the total discount amount and discount by tax group
for line in order.order_line:
if line.display_type or not line.product_id:
continue
# Apply cumulative discount rate only if bypass_global_discount is False
if not line.product_id.bypass_global_discount:
discounted_price_subtotal = (
line.price_subtotal * cumulative_discount_rate
)
else:
discounted_price_subtotal = line.price_subtotal
base_amount += discounted_price_subtotal
# Calculate tax amounts for each tax group based on the discounted
# subtotal
for tax in line.tax_id:
tax_group_id = tax.tax_group_id.id
if tax_group_id not in amount_discount_by_group:
amount_discount_by_group[tax_group_id] = 0.0
# Compute taxes on the correct base amount
discounted_tax_vals = tax.with_context(
force_price_include=False
).compute_all(
discounted_price_subtotal,
currency,
1.0,
product=line.product_id,
partner=order.partner_shipping_id,
)
amount_discount_by_group[tax_group_id] += sum(
t.get("amount", 0.0)
for t in discounted_tax_vals.get("taxes", [])
)
# Calculate the final amount total
total_amount = base_amount + sum(amount_discount_by_group.values())
base_amount_currency = formatLang(
self.env, base_amount, currency_obj=currency
)
order.tax_totals.update(
{
"base_amount": base_amount,
"total_amount": total_amount,
"base_amount_currency": base_amount,
"total_amount_currency": total_amount,
}
)
# Update subtotals and groups by subtotal
for group in order.tax_totals["subtotals"]:
group.update(
{
"base_amount": base_amount,
"base_amount_currency": base_amount,
"amount": base_amount,
"formatted_amount": base_amount_currency,
}
)
for tax_group in group["tax_groups"]:
discounted_tax_amount = amount_discount_by_group.get(
tax_group["id"], 0.0
)
tax_group.update(
{
"base_amount": base_amount,
"base_amount_currency": base_amount,
"tax_amount": discounted_tax_amount,
"tax_amount_currency": discounted_tax_amount,
}
)
return res
@api.depends("partner_id", "company_id")
def _compute_global_discount_ids(self):
for order in self:
commercial_global_disc = (
order.partner_id.commercial_partner_id.customer_global_discount_ids
)
_discounts = (
commercial_global_disc
if commercial_global_disc
else order.partner_id.customer_global_discount_ids
)
discounts = self.env["global.discount"]
for discount in _discounts:
if discount.company_id == order.company_id:
discounts |= discount
order.global_discount_ids = discounts
def _prepare_invoice(self):
invoice_vals = super()._prepare_invoice()
if self.global_discount_ids:
invoice_vals.update(
{"global_discount_ids": [(6, 0, self.global_discount_ids.ids)]}
)
return invoice_vals

View File

@@ -0,0 +1,14 @@
# Copyright 2023 Studio73 - Ethan Hildick <ethan@studio73.es>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, models
class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
@api.depends("product_uom_qty", "discount", "price_unit", "tax_id")
def _compute_amount(self):
return super(
SaleOrderLine, self.with_context(from_tax_calculation=False)
)._compute_amount()

View File

@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View File

@@ -0,0 +1,2 @@
To configure this module please refer to configure section of the
base_global_discount module.

View File

@@ -0,0 +1,8 @@
- [Tecnativa](https://www.tecnativa.com)
- David Vidal
- Pedro M. Baeza
- Omar Castiñeira \<<omar@comunitea.com>\>
- [Studio73](https://www.studio73.es)
- Miguel Gandia
- Eugenio Micó

View File

@@ -0,0 +1,2 @@
Apply global financial discounts to sales that will be transmited to
invoices and accounting.

View File

@@ -0,0 +1,3 @@
- Not all the taxes combination can be compatible with global discounts.
An error is raised in that cases.
- Currently, taxes in invoice lines are mandatory with global discounts.

View File

@@ -0,0 +1,11 @@
To use this module, you need to:
1. See usage section of the base_global_discount module.
2. Create a new sale order and choose a partner.
3. If the partner has customer global discounts set, those will be
applied to the order by default.
4. Otherwise, you can set them manually from the header of the sale
order.
5. In the order footer, you can see the computed discounts.
6. When you create an invoice from the order, the proper global
discounts will be applied on it.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,471 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>README.rst</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
: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.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
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 }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document">
<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
</a>
<div class="section" id="sale-global-discount">
<h1>Sale Global Discount</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:0eb7725122ba2ca05d2f6a97d79d139caa962ff2d902b7833ad0ce79c81e08ab
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/sale-workflow/tree/18.0/sale_global_discount"><img alt="OCA/sale-workflow" src="https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/sale-workflow-18-0/sale-workflow-18-0-sale_global_discount"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>Apply global financial discounts to sales that will be transmited to
invoices and accounting.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="toc-entry-1">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="toc-entry-2">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="toc-entry-3">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-4">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-6">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-7">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-8">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h2><a class="toc-backref" href="#toc-entry-1">Configuration</a></h2>
<p>To configure this module please refer to configure section of the
base_global_discount module.</p>
</div>
<div class="section" id="usage">
<h2><a class="toc-backref" href="#toc-entry-2">Usage</a></h2>
<p>To use this module, you need to:</p>
<ol class="arabic simple">
<li>See usage section of the base_global_discount module.</li>
<li>Create a new sale order and choose a partner.</li>
<li>If the partner has customer global discounts set, those will be
applied to the order by default.</li>
<li>Otherwise, you can set them manually from the header of the sale
order.</li>
<li>In the order footer, you can see the computed discounts.</li>
<li>When you create an invoice from the order, the proper global
discounts will be applied on it.</li>
</ol>
</div>
<div class="section" id="known-issues-roadmap">
<h2><a class="toc-backref" href="#toc-entry-3">Known issues / Roadmap</a></h2>
<ul class="simple">
<li>Not all the taxes combination can be compatible with global discounts.
An error is raised in that cases.</li>
<li>Currently, taxes in invoice lines are mandatory with global discounts.</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#toc-entry-4">Bug Tracker</a></h2>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/sale-workflow/issues">GitHub Issues</a>.
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
<a class="reference external" href="https://github.com/OCA/sale-workflow/issues/new?body=module:%20sale_global_discount%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h2><a class="toc-backref" href="#toc-entry-5">Credits</a></h2>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-6">Authors</a></h3>
<ul class="simple">
<li>Tecnativa</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-7">Contributors</a></h3>
<ul class="simple">
<li><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a><ul>
<li>David Vidal</li>
<li>Pedro M. Baeza</li>
</ul>
</li>
<li>Omar Castiñeira &lt;<a class="reference external" href="mailto:omar&#64;comunitea.com">omar&#64;comunitea.com</a>&gt;</li>
<li><a class="reference external" href="https://www.studio73.es">Studio73</a><ul>
<li>Miguel Gandia</li>
<li>Eugenio Micó</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-8">Maintainers</a></h3>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>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.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/sale-workflow/tree/18.0/sale_global_discount">OCA/sale-workflow</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,2 @@
from . import test_sale_global_discount
from . import test_account_tax

View File

@@ -0,0 +1,55 @@
# Copyright 2025 Studio73 - Eugenio Micó <eugenio@studio73.es>
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
from odoo.addons.base.tests.common import BaseCommon
class TestAccountTaxGlobalDiscount(BaseCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner = cls.env["res.partner"].create({"name": "Cliente Test"})
cls.product = cls.env["product.product"].create(
{
"name": "Producto Test",
"list_price": 100,
}
)
cls.order = cls.env["sale.order"].create(
{
"partner_id": cls.partner.id,
}
)
cls.discount = cls.env["global.discount"].create(
{
"name": "Descuento 10%",
"discount": 10,
}
)
cls.order.global_discount_ids = [(6, 0, [cls.discount.id])]
cls.order_line = cls.env["sale.order.line"].create(
{
"order_id": cls.order.id,
"product_id": cls.product.id,
"product_uom_qty": 1,
"price_unit": 100,
}
)
cls.tax = cls.env["account.tax"].create(
{
"name": "IVA 21%",
"amount": 21,
"type_tax_use": "sale",
}
)
def test_prepare_base_line_for_taxes_computation_with_global_discount(self):
ctx = dict(self.env.context, from_tax_calculation=True)
with self.env.cr.savepoint():
tax = self.tax.with_context(**ctx).sudo()
kwargs = {"price_unit": 100}
res = tax._prepare_base_line_for_taxes_computation(
self.order_line, **kwargs
)
discounted = self.order.get_discounted_global(100, [self.discount.discount])
self.assertEqual(res["price_unit"], discounted)

View File

@@ -0,0 +1,273 @@
# Copyright 2020 Tecnativa - David Vidal
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import exceptions
from odoo.tests import Form, tagged
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
@tagged("post_install", "-at_install")
class TestSaleGlobalDiscount(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass()
cls.chart_template = chart_template_ref
cls.env.ref("base_global_discount.group_global_discount").write(
{"users": [(4, cls.env.user.id)]}
)
cls.main_company = cls.env.ref("base.main_company")
cls.account = cls.env["account.account"].create(
{
"name": "Test account Global Discount",
"code": "TEST99999",
"account_type": "asset_current",
"reconcile": True,
}
)
cls.global_discount_obj = cls.env["global.discount"]
cls.global_discount_1 = cls.global_discount_obj.create(
{
"name": "Test Discount 1",
"sequence": 1,
"discount_scope": "sale",
"discount": 20,
"account_id": cls.account.id,
}
)
cls.global_discount_2 = cls.global_discount_obj.create(
{
"name": "Test Discount 2",
"sequence": 2,
"discount_scope": "sale",
"discount": 30,
"account_id": cls.account.id,
}
)
cls.global_discount_3 = cls.global_discount_obj.create(
{
"name": "Test Discount 3",
"sequence": 3,
"discount_scope": "sale",
"discount": 50,
"account_id": cls.account.id,
}
)
cls.pricelist = cls.env["product.pricelist"].create(
{"name": "Public Pricelist", "sequence": 1}
)
cls.partner_1 = cls.env["res.partner"].create(
{"name": "Mr. Odoo", "property_product_pricelist": cls.pricelist.id}
)
cls.partner_2 = cls.env["res.partner"].create(
{"name": "Mrs. Odoo", "property_product_pricelist": cls.pricelist.id}
)
cls.partner_2.customer_global_discount_ids = (
cls.global_discount_2 + cls.global_discount_3
)
cls.product_1 = cls.env["product.product"].create(
{"name": "Test Product 1", "type": "service"}
)
cls.product_2 = cls.env["product.product"].create(
{"name": "Test Product 2", "type": "service"}
)
cls.tax_group_5pc = cls.env["account.tax.group"].create(
{"name": "Test Tax Group 5%", "sequence": 1}
)
cls.tax_group_15pc = cls.env["account.tax.group"].create(
{"name": "Test Tax Group 15%", "sequence": 2}
)
cls.tax_1 = cls.tax_sale_a
cls.tax_1.amount = 15.0
cls.tax_2 = cls.tax_sale_b
cls.tax_2.amount = 5.0
cls.sale_journal0 = cls.env["account.journal"].create(
{
"name": "Sale Journal",
"type": "sale",
"code": "SJT0",
}
)
sale_form = Form(cls.env["sale.order"])
sale_form.partner_id = cls.partner_1
with sale_form.order_line.new() as order_line:
order_line.product_id = cls.product_1
order_line.tax_id.clear()
order_line.tax_id.add(cls.tax_1)
order_line.tax_id.add(cls.tax_2)
order_line.product_uom_qty = 2
order_line.price_unit = 75
with sale_form.order_line.new() as order_line:
order_line.product_id = cls.product_2
order_line.tax_id.clear()
order_line.tax_id.add(cls.tax_1)
order_line.tax_id.add(cls.tax_2)
order_line.product_uom_qty = 3
order_line.price_unit = 33.33
cls.sale = sale_form.save()
@staticmethod
def get_taxes_widget_total_tax(order):
tax_amount_total = 0.0
for subtotal in order.tax_totals["subtotals"]:
tax_amount_total = sum(
tax_group["tax_amount"] for tax_group in subtotal["tax_groups"]
)
return tax_amount_total
def test_01_global_sale_successive_discounts(self):
"""Add global discounts to the sale order"""
self.assertAlmostEqual(self.sale.amount_total, 299.99)
self.assertAlmostEqual(self.sale.amount_tax, 50)
self.assertAlmostEqual(
self.get_taxes_widget_total_tax(self.sale), self.sale.amount_tax
)
self.assertAlmostEqual(self.sale.amount_untaxed, 249.99)
# Apply a single 20% global discount
self.sale.global_discount_ids = self.global_discount_1
self.sale._compute_tax_totals()
# Discount is computed over the base and global taxes are computed
# according to it line by line with the core method
self.assertAlmostEqual(self.sale.amount_global_discount, 50)
self.assertAlmostEqual(self.sale.amount_untaxed, 199.99)
self.assertAlmostEqual(self.sale.amount_untaxed_before_global_discounts, 249.99)
self.assertAlmostEqual(self.sale.amount_total, 239.99)
self.assertAlmostEqual(self.sale.amount_total_before_global_discounts, 299.99)
self.assertAlmostEqual(self.sale.amount_tax, 40)
self.assertAlmostEqual(
self.get_taxes_widget_total_tax(self.sale), self.sale.amount_tax
)
# Apply an additional 30% global discount
self.sale.global_discount_ids += self.global_discount_2
self.sale._compute_tax_totals()
self.assertAlmostEqual(self.sale.amount_global_discount, 110)
self.assertAlmostEqual(self.sale.amount_untaxed, 139.99)
self.assertAlmostEqual(self.sale.amount_untaxed_before_global_discounts, 249.99)
self.assertAlmostEqual(self.sale.amount_total, 167.99)
self.assertAlmostEqual(self.sale.amount_total_before_global_discounts, 299.99)
self.assertAlmostEqual(self.sale.amount_tax, 28)
self.assertAlmostEqual(
self.get_taxes_widget_total_tax(self.sale), self.sale.amount_tax
)
# The account move should look like this
# credit debit name
# ======== ======= ===============================================
# 150 0 Test Product 1
# 99.99 0 Test Product 2
# 13.13 0 Test TAX 15%
# 4.38 0 TAX 5%
# 0 105.01
# 0 75 Test Discount 2 (30.00%) - Test TAX 15%, TAX 5%
# 0 87.49 Test Discount 3 (50.00%) - Test TAX 15%, TAX 5%
# ======== ======= ===============================================
def test_02_global_sale_discounts_from_partner(self):
"""Change the partner and his global discounts go to the invoice"""
# (30% then 50%)
self.sale.partner_id = self.partner_2
self.sale._compute_tax_totals()
self.assertAlmostEqual(self.sale.amount_global_discount, 162.49)
self.assertAlmostEqual(self.sale.amount_untaxed, 87.5)
self.assertAlmostEqual(self.sale.amount_untaxed_before_global_discounts, 249.99)
self.assertAlmostEqual(self.sale.amount_total, 105.01)
self.assertAlmostEqual(self.sale.amount_total_before_global_discounts, 299.99)
self.assertAlmostEqual(self.sale.amount_tax, 17.51)
self.assertAlmostEqual(
self.get_taxes_widget_total_tax(self.sale), self.sale.amount_tax
)
def test_03_global_sale_discounts_to_invoice(self):
"""All the discounts go to the invoice"""
self.sale.partner_id = self.partner_2
self.sale._compute_tax_totals()
self.sale.order_line.mapped("product_id").write({"invoice_policy": "order"})
self.sale.action_confirm()
move = self.sale._create_invoices()
# Check the invoice relevant fields
self.assertEqual(len(move.invoice_global_discount_ids), 2)
discount_amount = sum(
move.invoice_global_discount_ids.mapped("discount_amount")
)
self.assertAlmostEqual(discount_amount, 162.49)
self.assertAlmostEqual(move.amount_untaxed_before_global_discounts, 249.99)
self.assertAlmostEqual(move.amount_untaxed, 87.5)
self.assertAlmostEqual(move.amount_total, 105.01)
# Expected Journal Entry
# credit debit account
# ======== ======= =========
# 150 0 400000 (line 1)
# 99.99 0 400000 (line 2)
# 13.13 0 400000 (line_tax_1)
# 4.38 0 400000 (line_tax_2)
# 0 105.01 121000 (Base)
# 0 75 TEST99999 (Global discount 1)
# 0 87.49 TEST99999 (Global discount 2)
# 267.50 267.50 <- Balance
line_tax_1 = move.line_ids.filtered(lambda x: x.tax_line_id == self.tax_1)
line_tax_2 = move.line_ids.filtered(lambda x: x.tax_line_id == self.tax_2)
self.assertAlmostEqual(line_tax_1.credit, 13.13)
self.assertAlmostEqual(line_tax_2.credit, 4.38)
term_line = move.line_ids.filtered(
lambda x: x.account_id.account_type == "asset_receivable"
)
self.assertAlmostEqual(term_line.debit, 105.01)
discount_lines = move.line_ids.filtered("invoice_global_discount_id")
self.assertEqual(len(discount_lines), 2)
self.assertAlmostEqual(sum(discount_lines.mapped("debit")), 162.49)
def test_04_incompatible_taxes(self):
# Line 1 with tax 1 and tax 2
# Line 2 with only tax 2
self.sale.order_line[1].tax_id = [(6, 0, self.tax_1.ids)]
with self.assertRaises(exceptions.UserError):
self.sale.global_discount_ids = self.global_discount_1
self.sale._compute_amounts()
def test_05_no_taxes(self):
self.sale.order_line[1].tax_id = False
with self.assertRaises(exceptions.UserError):
self.sale.global_discount_ids = self.global_discount_1
self.sale._compute_amounts()
def test_06_discounted_line(self):
self.sale.global_discount_ids = self.global_discount_1
line = self.sale.order_line[0]
line.discount = 10
self.assertAlmostEqual(line.price_subtotal, 135)
self.assertAlmostEqual(self.sale.amount_untaxed_before_global_discounts, 234.99)
self.assertAlmostEqual(self.sale.amount_untaxed, 187.99)
def test_07_bypass_global_discounts(self):
self.sale.global_discount_ids = self.global_discount_2 + self.global_discount_3
self.sale.order_line[0].product_id.bypass_global_discount = True
self.assertAlmostEqual(self.sale.amount_untaxed_before_global_discounts, 249.99)
self.assertAlmostEqual(self.sale.amount_untaxed, 185.00)
self.assertAlmostEqual(self.sale.amount_total, 222.00)
self.assertAlmostEqual(self.sale.amount_tax, 37.00)
self.assertAlmostEqual(
self.get_taxes_widget_total_tax(self.sale), self.sale.amount_tax
)
def test_08_global_discount_with_tax_included(self):
tax_included = self.env["account.tax"].create(
{
"name": "IVA 21% included",
"amount": 21,
"amount_type": "percent",
"type_tax_use": "sale",
"price_include": True,
"include_base_amount": True,
}
)
for line in self.sale.order_line:
line.tax_id = [(6, 0, tax_included.ids)]
self.sale._compute_tax_totals()
self.sale.global_discount_ids = self.global_discount_1
self.sale._compute_tax_totals()
self.assertTrue(self.sale.amount_tax > 0)
self.assertTrue(
self.sale.amount_total < self.sale.amount_total_before_global_discounts
)
self.assertAlmostEqual(
self.get_taxes_widget_total_tax(self.sale), self.sale.amount_tax
)

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<template
id="report_saleorder_document"
inherit_id="sale.report_saleorder_document"
>
<xpath expr="//div[@name='total']/div/table/t[1]" position="before">
<t t-if="doc.global_discount_ids">
<tr>
<td>
<strong>Subtl. before disc.</strong>
</td>
<td class="text-end">
<span
t-field="doc.amount_untaxed_before_global_discounts"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
/>
</td>
</tr>
<tr style="border-bottom:1px solid #dddddd;">
<td>
<strong>Global Discounts</strong>
<br />
<t
t-out="'→'.join(['{:.2f}%'.format(x.discount) for x in doc.global_discount_ids])"
/>
</td>
<td class="text-end">
<span
t-field="doc.amount_global_discount"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
/>
</td>
</tr>
</t>
</xpath>
</template>
</odoo>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" ?>
<!-- Copyright 2020 Tecnativa - David Vidal
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="view_order_form" model="ir.ui.view">
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form" />
<field name="arch" type="xml">
<field name="currency_id" position="before">
<field
name="global_discount_ids"
widget="many2many_tags"
groups="base_global_discount.group_global_discount"
/>
<field
name="global_discount_ids_readonly"
string="Sale Global Discounts"
widget="many2many_tags"
groups="!base_global_discount.group_global_discount"
/>
</field>
<xpath expr="//group[@name='sale_total']" position="before">
<group colspan="2" class="oe_subtotal_footer oe_right">
<field name="amount_untaxed_before_global_discounts" />
<field name="amount_global_discount" />
</group>
</xpath>
</field>
</record>
</odoo>