From 3aabeca09a679e3240cd59d95c81bb53a53cb67e Mon Sep 17 00:00:00 2001 From: RemiFr82 Date: Thu, 23 Nov 2023 16:11:53 +0100 Subject: [PATCH] [ADD] purchase_discount_without_stock --- purchase_discount_without_stock/__init__.py | 2 + .../__manifest__.py | 26 ++++ purchase_discount_without_stock/i18n/fr.po | 92 ++++++++++++++ .../models/__init__.py | 3 + .../models/product_supplierinfo.py | 38 ++++++ .../models/purchase_order.py | 118 ++++++++++++++++++ .../models/res_partner.py | 17 +++ .../report/__init__.py | 1 + .../report/purchase_report.py | 34 +++++ .../views/product_supplierinfo_view.xml | 20 +++ .../views/purchase_discount_view.xml | 42 +++++++ .../views/report_purchaseorder.xml | 16 +++ .../views/res_partner_view.xml | 26 ++++ 13 files changed, 435 insertions(+) create mode 100644 purchase_discount_without_stock/__init__.py create mode 100644 purchase_discount_without_stock/__manifest__.py create mode 100644 purchase_discount_without_stock/i18n/fr.po create mode 100644 purchase_discount_without_stock/models/__init__.py create mode 100644 purchase_discount_without_stock/models/product_supplierinfo.py create mode 100644 purchase_discount_without_stock/models/purchase_order.py create mode 100644 purchase_discount_without_stock/models/res_partner.py create mode 100644 purchase_discount_without_stock/report/__init__.py create mode 100644 purchase_discount_without_stock/report/purchase_report.py create mode 100644 purchase_discount_without_stock/views/product_supplierinfo_view.xml create mode 100644 purchase_discount_without_stock/views/purchase_discount_view.xml create mode 100644 purchase_discount_without_stock/views/report_purchaseorder.xml create mode 100644 purchase_discount_without_stock/views/res_partner_view.xml diff --git a/purchase_discount_without_stock/__init__.py b/purchase_discount_without_stock/__init__.py new file mode 100644 index 0000000..bf588bc --- /dev/null +++ b/purchase_discount_without_stock/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import report diff --git a/purchase_discount_without_stock/__manifest__.py b/purchase_discount_without_stock/__manifest__.py new file mode 100644 index 0000000..c40aa37 --- /dev/null +++ b/purchase_discount_without_stock/__manifest__.py @@ -0,0 +1,26 @@ +# © 2004-2009 Tiny SPRL (). +# © 2014-2017 Tecnativa - Pedro M. Baeza +# © 2016 ACSONE SA/NV () +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html +{ + "name": "Purchase order lines with discounts", + "author": "Tiny, " + "Acysos S.L., " + "Tecnativa, " + "ACSONE SA/NV," + "GRAP," + "Odoo Community Association (OCA)," + "RemiFr82", + "version": "14.0.1.1.3", + "category": "Purchase Management", + "website": "https://remifr82.me", + "depends": ["purchase"], + "data": [ + "views/purchase_discount_view.xml", + "views/report_purchaseorder.xml", + "views/product_supplierinfo_view.xml", + "views/res_partner_view.xml", + ], + "license": "AGPL-3", + "installable": True, +} diff --git a/purchase_discount_without_stock/i18n/fr.po b/purchase_discount_without_stock/i18n/fr.po new file mode 100644 index 0000000..a2796db --- /dev/null +++ b/purchase_discount_without_stock/i18n/fr.po @@ -0,0 +1,92 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * purchase_discount_whithout_stock +# +# Translators: +# OCA Transbot , 2018 +# Quentin THEURET , 2018 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 11.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-02-23 17:50+0000\n" +"PO-Revision-Date: 2020-07-22 12:19+0000\n" +"Last-Translator: c2cdidier \n" +"Language-Team: French (https://www.transifex.com/oca/teams/23907/fr/)\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: purchase_discount_whithout_stock +#: model_terms:ir.ui.view,arch_db:purchase_discount_whithout_stock.report_purchaseorder_document +msgid "Disc. (%)" +msgstr "Remise (%)" + +#. module: purchase_discount_whithout_stock +#: model:ir.model,name:purchase_discount_whithout_stock.model_res_partner +msgid "Contact" +msgstr "Contact" + +#. module: purchase_discount_whithout_stock +#: model:ir.model.fields,field_description:purchase_discount_whithout_stock.field_res_partner__default_supplierinfo_discount +#: model:ir.model.fields,field_description:purchase_discount_whithout_stock.field_res_users__default_supplierinfo_discount +msgid "Default Supplier Discount (%)" +msgstr "Remise fournisseur par défaut (%)" + +#. module: purchase_discount_whithout_stock +#: model:ir.model.fields,field_description:purchase_discount_whithout_stock.field_product_supplierinfo__discount +#: model:ir.model.fields,field_description:purchase_discount_whithout_stock.field_purchase_order_line__discount +#: model:ir.model.fields,field_description:purchase_discount_whithout_stock.field_purchase_report__discount +msgid "Discount (%)" +msgstr "Remise (%)" + +#. module: purchase_discount_whithout_stock +#: model:ir.model.constraint,message:purchase_discount_whithout_stock.constraint_purchase_order_line_discount_limit +msgid "Discount must be lower than 100%." +msgstr "La remise doit être inférieure à 100%." + +#. module: purchase_discount_whithout_stock +#: model_terms:ir.ui.view,arch_db:purchase_discount_whithout_stock.res_partner_form_view +msgid "Discount-related settings are managed on" +msgstr "Paramètres des remises sont gérés sur" + +#. module: purchase_discount_whithout_stock +#: model:ir.model,name:purchase_discount_whithout_stock.model_purchase_order +msgid "Purchase Order" +msgstr "Commandes d'achat" + +#. module: purchase_discount_whithout_stock +#: model:ir.model,name:purchase_discount_whithout_stock.model_purchase_order_line +msgid "Purchase Order Line" +msgstr "Ligne de commande d'achat" + +#. module: purchase_discount_whithout_stock +#: model:ir.model,name:purchase_discount_whithout_stock.model_purchase_report +msgid "Purchase Report" +msgstr "Rapport des commandes" + +#. module: purchase_discount_whithout_stock +#: model:ir.model,name:purchase_discount_whithout_stock.model_product_supplierinfo +msgid "Supplier Pricelist" +msgstr "Liste de prix fournisseur" + +#. module: purchase_discount_whithout_stock +#: model:ir.model.fields,help:purchase_discount_whithout_stock.field_res_partner__default_supplierinfo_discount +#: model:ir.model.fields,help:purchase_discount_whithout_stock.field_res_users__default_supplierinfo_discount +msgid "" +"This value will be used as the default one, for each new supplierinfo line " +"depending on that supplier." +msgstr "" +"Cette valeur sera utilisée comme valeur par défaut pour chaque nouvelle " +"information fournisseur de ce fournisseur." + +#. module: purchase_discount_whithout_stock +#: model_terms:ir.ui.view,arch_db:purchase_discount_whithout_stock.res_partner_form_view +msgid "the parent company" +msgstr "Société parente" + +#~ msgid "Invoice" +#~ msgstr "Facture" diff --git a/purchase_discount_without_stock/models/__init__.py b/purchase_discount_without_stock/models/__init__.py new file mode 100644 index 0000000..b6ce024 --- /dev/null +++ b/purchase_discount_without_stock/models/__init__.py @@ -0,0 +1,3 @@ +from . import purchase_order +from . import product_supplierinfo +from . import res_partner diff --git a/purchase_discount_without_stock/models/product_supplierinfo.py b/purchase_discount_without_stock/models/product_supplierinfo.py new file mode 100644 index 0000000..0467f5a --- /dev/null +++ b/purchase_discount_without_stock/models/product_supplierinfo.py @@ -0,0 +1,38 @@ +# Copyright 2016 ACSONE SA/NV () +# Copyright 2014-2019 Tecnativa - Pedro M. Baeza +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import api, fields, models + + +class ProductSupplierInfo(models.Model): + _inherit = "product.supplierinfo" + + discount = fields.Float(string="Discount (%)", digits="Discount") + + @api.onchange("name") + def onchange_name(self): + """Apply the default supplier discount of the selected supplier""" + for supplierinfo in self.filtered("name"): + supplierinfo.discount = supplierinfo.name.default_supplierinfo_discount + + @api.model + def _get_po_to_supplierinfo_synced_fields(self): + """Overwrite this method for adding other fields to be synchronized + with product.supplierinfo. + """ + return ["discount"] + + @api.model_create_multi + def create(self, vals_list): + """Insert discount (or others) from context from purchase.order's + _add_supplier_to_product method""" + for vals in vals_list: + product_tmpl_id = vals.get("product_tmpl_id") + po_line_map = self.env.context.get("po_line_map", {}) + if product_tmpl_id in po_line_map: + po_line = po_line_map[product_tmpl_id] + for field in self._get_po_to_supplierinfo_synced_fields(): + if not vals.get(field): + vals[field] = po_line[field] + return super().create(vals_list) diff --git a/purchase_discount_without_stock/models/purchase_order.py b/purchase_discount_without_stock/models/purchase_order.py new file mode 100644 index 0000000..8290eae --- /dev/null +++ b/purchase_discount_without_stock/models/purchase_order.py @@ -0,0 +1,118 @@ +# Copyright 2004-2009 Tiny SPRL (). +# Copyright 2016 ACSONE SA/NV () +# Copyright 2015-2019 Tecnativa - Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + + +from odoo import api, fields, models + + +class PurchaseOrder(models.Model): + _inherit = "purchase.order" + + def _add_supplier_to_product(self): + """Insert a mapping of products to PO lines to be picked up + in supplierinfo's create()""" + self.ensure_one() + po_line_map = { + line.product_id.product_tmpl_id.id: line for line in self.order_line + } + return super( + PurchaseOrder, self.with_context(po_line_map=po_line_map) + )._add_supplier_to_product() + + +class PurchaseOrderLine(models.Model): + _inherit = "purchase.order.line" + + # adding discount to depends + @api.depends("discount") + def _compute_amount(self): + return super()._compute_amount() + + def _prepare_compute_all_values(self): + vals = super()._prepare_compute_all_values() + vals.update({"price_unit": self._get_discounted_price_unit()}) + return vals + + discount = fields.Float(string="Discount (%)", digits="Discount") + + _sql_constraints = [ + ( + "discount_limit", + "CHECK (discount <= 100.0)", + "Discount must be lower than 100%.", + ) + ] + + def _get_discounted_price_unit(self): + """Inheritable method for getting the unit price after applying + discount(s). + + :rtype: float + :return: Unit price after discount(s). + """ + self.ensure_one() + if self.discount: + return self.price_unit * (1 - self.discount / 100) + return self.price_unit + + @api.onchange("product_qty", "product_uom") + def _onchange_quantity(self): + """ + Check if a discount is defined into the supplier info and if so then + apply it to the current purchase order line + """ + res = super()._onchange_quantity() + if self.product_id: + date = None + if self.order_id.date_order: + date = self.order_id.date_order.date() + seller = self.product_id._select_seller( + partner_id=self.partner_id, + quantity=self.product_qty, + date=date, + uom_id=self.product_uom, + ) + self._apply_value_from_seller(seller) + return res + + @api.model + def _apply_value_from_seller(self, seller): + """Overload this function to prepare other data from seller, + like in purchase_triple_discount module""" + if not seller: + return + self.discount = seller.discount + + def _prepare_account_move_line(self, move=False): + vals = super(PurchaseOrderLine, self)._prepare_account_move_line(move) + vals["discount"] = self.discount + return vals + + @api.model + def _prepare_purchase_order_line( + self, product_id, product_qty, product_uom, company_id, supplier, po + ): + """Apply the discount to the created purchase order""" + res = super()._prepare_purchase_order_line( + product_id, product_qty, product_uom, company_id, supplier, po + ) + partner = supplier.name + uom_po_qty = product_uom._compute_quantity(product_qty, product_id.uom_po_id) + seller = product_id.with_company(company_id)._select_seller( + partner_id=partner, + quantity=uom_po_qty, + date=po.date_order and po.date_order.date(), + uom_id=product_id.uom_po_id, + ) + res.update(self._prepare_purchase_order_line_from_seller(seller)) + return res + + @api.model + def _prepare_purchase_order_line_from_seller(self, seller): + """Overload this function to prepare other data from seller, + like in purchase_triple_discount module""" + if not seller: + return {} + return {"discount": seller.discount} diff --git a/purchase_discount_without_stock/models/res_partner.py b/purchase_discount_without_stock/models/res_partner.py new file mode 100644 index 0000000..4d35d4d --- /dev/null +++ b/purchase_discount_without_stock/models/res_partner.py @@ -0,0 +1,17 @@ +# Copyright 2016 GRAP (http://www.grap.coop) +# Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import fields, models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + default_supplierinfo_discount = fields.Float( + string="Default Supplier Discount (%)", + digits="Discount", + help="This value will be used as the default one, for each new" + " supplierinfo line depending on that supplier.", + tracking=True, + ) diff --git a/purchase_discount_without_stock/report/__init__.py b/purchase_discount_without_stock/report/__init__.py new file mode 100644 index 0000000..84b6eef --- /dev/null +++ b/purchase_discount_without_stock/report/__init__.py @@ -0,0 +1 @@ +from . import purchase_report diff --git a/purchase_discount_without_stock/report/purchase_report.py b/purchase_discount_without_stock/report/purchase_report.py new file mode 100644 index 0000000..11d021f --- /dev/null +++ b/purchase_discount_without_stock/report/purchase_report.py @@ -0,0 +1,34 @@ +# Copyright 2017 Akretion (Alexis de Lattre ) +# Copyright 2017-2019 Tecnativa - Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class PurchaseReport(models.Model): + _inherit = "purchase.report" + + discount = fields.Float( + string="Discount (%)", digits="Discount", group_operator="avg" + ) + + def _select(self): + res = super()._select() + # There are 3 matches + res = res.replace("l.price_unit", self._get_discounted_price_unit_exp()) + res += ", l.discount AS discount" + return res + + def _group_by(self): + res = super()._group_by() + res += ", l.discount" + return res + + def _get_discounted_price_unit_exp(self): + """Inheritable method for getting the SQL expression used for + calculating the unit price with discount(s). + + :rtype: str + :return: SQL expression for discounted unit price. + """ + return "(1.0 - COALESCE(l.discount, 0.0) / 100.0) * l.price_unit" diff --git a/purchase_discount_without_stock/views/product_supplierinfo_view.xml b/purchase_discount_without_stock/views/product_supplierinfo_view.xml new file mode 100644 index 0000000..2f92c9a --- /dev/null +++ b/purchase_discount_without_stock/views/product_supplierinfo_view.xml @@ -0,0 +1,20 @@ + + + product.supplierinfo + + + + + + + + + product.supplierinfo + + + + + + + + diff --git a/purchase_discount_without_stock/views/purchase_discount_view.xml b/purchase_discount_without_stock/views/purchase_discount_view.xml new file mode 100644 index 0000000..85c2689 --- /dev/null +++ b/purchase_discount_without_stock/views/purchase_discount_view.xml @@ -0,0 +1,42 @@ + + + + purchase_discount.order.line.form2 + purchase.order.line + + + + + + + + + purchase_discount.order.line.tree + purchase.order.line + + + + + + + + + purchase_discount.purchase.order.form + purchase.order + + + + + + + + + + + diff --git a/purchase_discount_without_stock/views/report_purchaseorder.xml b/purchase_discount_without_stock/views/report_purchaseorder.xml new file mode 100644 index 0000000..3314c67 --- /dev/null +++ b/purchase_discount_without_stock/views/report_purchaseorder.xml @@ -0,0 +1,16 @@ + + + + diff --git a/purchase_discount_without_stock/views/res_partner_view.xml b/purchase_discount_without_stock/views/res_partner_view.xml new file mode 100644 index 0000000..3c29dc0 --- /dev/null +++ b/purchase_discount_without_stock/views/res_partner_view.xml @@ -0,0 +1,26 @@ + + + res.partner + + + + +
+

Discount-related settings are managed on

+
+
+
+