diff --git a/bemade_purchase_warn_supplier_overdue/__manifest__.py b/bemade_purchase_warn_supplier_overdue/__manifest__.py index 096faac..36fb51d 100644 --- a/bemade_purchase_warn_supplier_overdue/__manifest__.py +++ b/bemade_purchase_warn_supplier_overdue/__manifest__.py @@ -1,29 +1,25 @@ { - 'name': 'Bemade Warn Supplier Overdue', - 'version': '1.0', - 'summary': 'Warn supplier when overdue on purchase order confirmation', - 'description': """ + "name": "Bemade Warn Supplier Overdue", + "version": "1.0", + "summary": "Warn supplier when overdue on purchase order confirmation", + "description": """ Warn supplier when overdue on purchase order confirmation -=================================== -This module adds a mail.message to the purchase order confirmation form when the supplier is overdue. +========================================================= +This module adds a mail.activity to the purchase order confirmation form when the supplier is overdue. """, - 'author': 'Benoît Vézina', - 'website': 'https://www.bemade.com', - 'category': 'Purchases', - 'license': 'OPL-1', - 'depends': [ - 'purchase', - 'account', - 'mail', + "author": "Benoît Vézina", + "website": "https://www.bemade.org", + "category": "Purchases", + "license": "LGPL-3", + "depends": [ + "purchase", + "account", + "mail", ], - 'data': [ - 'views/res_config_settings_views.xml', - # 'security/ir.model.access.csv', + "data": [ + "views/res_config_settings_views.xml", ], - 'demo': [ - # List any demo data files here - ], - 'installable': True, - 'application': False, - 'auto_install': False, + "installable": True, + "application": False, + "auto_install": False, } diff --git a/bemade_purchase_warn_supplier_overdue/models/purchase_order.py b/bemade_purchase_warn_supplier_overdue/models/purchase_order.py index fbd54c6..9bfc265 100644 --- a/bemade_purchase_warn_supplier_overdue/models/purchase_order.py +++ b/bemade_purchase_warn_supplier_overdue/models/purchase_order.py @@ -1,21 +1,28 @@ from odoo import models, fields, api, _ + class PurchaseOrder(models.Model): - _inherit = 'purchase.order' + _inherit = "purchase.order" @api.model def _check_supplier_overdue_invoices(self, partner): - """ Vérifie si le fournisseur a des factures impayées en retard """ - overdue_invoices = self.env['account.move'].search([ - ('partner_id', '=', partner.id), - ('move_type', '=', 'in_invoice'), # Facture fournisseur - ('invoice_date_due', '<', fields.Date.today()), # Date d'échéance dépassée - ('payment_state', '!=', 'paid') # Non payée - ]) + """Vérifie si le fournisseur a des factures impayées en retard""" + overdue_invoices = self.env["account.move"].search( + [ + ("partner_id", "=", partner.id), + ("move_type", "=", "in_invoice"), # Facture fournisseur + ( + "invoice_date_due", + "<", + fields.Date.today(), + ), # Date d'échéance dépassée + ("payment_state", "!=", "paid"), # Non payée + ] + ) return len(overdue_invoices) > 0 def button_confirm(self): - """ Surcharger la confirmation de commande pour intégrer l'avertissement """ + """Surcharger la confirmation de commande pour intégrer l'avertissement""" # Appel de la méthode standard de confirmation de commande res = super(PurchaseOrder, self).button_confirm() @@ -24,29 +31,43 @@ class PurchaseOrder(models.Model): company = order.company_id # Vérifier si la fonctionnalité d'avertissement est activée - if company.warn_supplier_overdue: - # Vérifier si l'avertissement s'applique à tous les fournisseurs ou seulement à certains - if company.warn_supplier_scope == 'all' or (company.warn_supplier_scope == 'specific' and supplier in company.warn_supplier_specific_ids): - if self._check_supplier_overdue_invoices(supplier): - # Déterminer quel utilisateur doit être averti - if company.warn_supplier_overdue_user_type == 'current': - user_to_notify = self.env.user - elif company.warn_supplier_overdue_user_type == 'specific': - user_to_notify = company.warn_supplier_overdue_user_id - else: - user_to_notify = self.env.user # Par défaut, utilisateur courant + # et si le fournisseur a des factures en souffrance + warn = company.warn_overdue_for_supplier( + supplier + ) and self._check_supplier_overdue_invoices(supplier) + if warn: + user_to_notify = order._get_user_to_notify() + if user_to_notify: + # Création de l'activité de type "To-Do" (mail.activity) + activity_vals = { + "res_model_id": self.env["ir.model"] + .search([("model", "=", "purchase.order")], limit=1) + .id, + "res_id": order.id, # L'ID du bon de commande + "activity_type_id": self.env.ref( + "mail.mail_activity_data_todo" + ).id, # Type d'activité "To-Do" + "summary": _("Overdue Invoices for Supplier %s") + % supplier.name, + "note": _( + "The supplier %s has overdue invoices. Please follow up before proceeding with the order %s." + ) + % (supplier.name, order.name), + "user_id": user_to_notify.id, # Utilisateur assigné à l'activité + "date_deadline": fields.Date.today(), # La date limite de l'activité + } - if user_to_notify: - # Création de l'activité de type "To-Do" (mail.activity) - activity_vals = { - 'res_model_id': self.env['ir.model'].search([('model', '=', 'purchase.order')], limit=1).id, - 'res_id': order.id, # L'ID du bon de commande - 'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id, # Type d'activité "To-Do" - 'summary': _('Overdue Invoices for Supplier %s') % supplier.name, - 'note': _('The supplier %s has overdue invoices. Please follow up before proceeding with the order %s.') % (supplier.name, order.name), - 'user_id': user_to_notify.id, # Utilisateur assigné à l'activité - 'date_deadline': fields.Date.today() # La date limite de l'activité - } + self.env["mail.activity"].create(activity_vals) + return res - self.env['mail.activity'].create(activity_vals) - return res \ No newline at end of file + def _get_user_to_notify(self): + self.ensure_one() + company = self.company_id + # Déterminer quel utilisateur doit être averti + if company.warn_supplier_overdue_user_type == "current": + user_to_notify = self.env.user + elif company.warn_supplier_overdue_user_type == "specific": + user_to_notify = company.warn_supplier_overdue_user_id + else: + user_to_notify = self.env.user # Par défaut, utilisateur courant + return user_to_notify diff --git a/bemade_purchase_warn_supplier_overdue/models/res_company.py b/bemade_purchase_warn_supplier_overdue/models/res_company.py index 2a2cee3..f993649 100644 --- a/bemade_purchase_warn_supplier_overdue/models/res_company.py +++ b/bemade_purchase_warn_supplier_overdue/models/res_company.py @@ -2,44 +2,58 @@ from odoo import models, fields, api class Company(models.Model): - _inherit = 'res.company' + _inherit = "res.company" warn_supplier_overdue = fields.Boolean( - string='Warn supplier when overdue', + string="Warn when supplier invoice overdue", default=True, - help='Warn user on purchase with overdue vendor', + help="Warn user when purchasing from a vendor with overdue bills.", ) warn_supplier_overdue_user_type = fields.Selection( - string='User Warned Type', + string="Warned User", selection=[ - ('current', 'Current User'), - ('specific', 'Specific User'), + ("current", "Current User"), + ("specific", "Specific User"), ], - default='current', - help='Type of user to warn when supplier is overdue', + default="current", + help="Which user to warn when supplier is overdue", ) warn_supplier_overdue_user_id = fields.Many2one( - string='User', - comodel_name='res.users', - help='Specific User to warn when supplier is overdue', + string="User", + comodel_name="res.users", + help="Specific User to warn when supplier is overdue.", ) warn_supplier_scope = fields.Selection( - string='Warn Scope', + string="Warning Vendor Scope", selection=[ - ('all', 'All Vendors'), - ('specific', 'Specific Vendors'), + ("all", "All Vendors"), + ("specific", "Specific Vendors"), ], - default='all', - help='Choose whether to apply overdue warnings to all vendors or only to specific vendors', + default="all", + help=( + "Choose whether to apply overdue warnings to all vendors or only to " + "specific vendors." + ), ) warn_supplier_specific_ids = fields.Many2many( - comodel_name='res.partner', - domain=[('supplier_rank', '>', 0)], - string='Specific Vendors', - help='Select specific vendors to apply overdue invoice warnings', + comodel_name="res.partner", + domain=[("supplier_rank", ">", 0)], + string="Specific Vendors", + help="Select specific vendors to apply overdue invoice warnings.", ) + def warn_overdue_for_supplier(self, supplier): + """Returns true if the current company settings indicate that the user should + be warned when confirming an order for the given supplier (res.partner).""" + self.ensure_one() + return self.warn_supplier_overdue and ( + self.warn_supplier_scope == "all" + or ( + self.warn_supplier_scope == "specific" + and supplier in self.warn_supplier_specific_ids + ) + ) diff --git a/bemade_purchase_warn_supplier_overdue/models/res_config_settings.py b/bemade_purchase_warn_supplier_overdue/models/res_config_settings.py index 6cbdf7a..5bdaafa 100644 --- a/bemade_purchase_warn_supplier_overdue/models/res_config_settings.py +++ b/bemade_purchase_warn_supplier_overdue/models/res_config_settings.py @@ -1,36 +1,37 @@ from odoo import models, fields + class ResConfigSettings(models.TransientModel): - _inherit = 'res.config.settings' + _inherit = "res.config.settings" warn_supplier_overdue = fields.Boolean( - string='Warn supplier when overdue', - related='company_id.warn_supplier_overdue', + string="Warn supplier when overdue", + related="company_id.warn_supplier_overdue", readonly=False, ) warn_supplier_overdue_user_type = fields.Selection( - string='User Warned Type', - related='company_id.warn_supplier_overdue_user_type', + string="User Warned Type", + related="company_id.warn_supplier_overdue_user_type", readonly=False, ) warn_supplier_overdue_user_id = fields.Many2one( - string='User to warn', - comodel_name='res.users', - related='company_id.warn_supplier_overdue_user_id', + string="User to warn", + comodel_name="res.users", + related="company_id.warn_supplier_overdue_user_id", readonly=False, ) warn_supplier_scope = fields.Selection( - string='Warn Scope', - related='company_id.warn_supplier_scope', + string="Warn Scope", + related="company_id.warn_supplier_scope", readonly=False, ) warn_supplier_specific_ids = fields.Many2many( - string='Specific Vendors', - comodel_name='res.partner', - related='company_id.warn_supplier_specific_ids', + string="Specific Vendors", + comodel_name="res.partner", + related="company_id.warn_supplier_specific_ids", readonly=False, - ) \ No newline at end of file + ) diff --git a/bemade_purchase_warn_supplier_overdue/tests/test_purchase_order_overdue.py b/bemade_purchase_warn_supplier_overdue/tests/test_purchase_order_overdue.py index 88d29c1..37f40c1 100644 --- a/bemade_purchase_warn_supplier_overdue/tests/test_purchase_order_overdue.py +++ b/bemade_purchase_warn_supplier_overdue/tests/test_purchase_order_overdue.py @@ -1,105 +1,144 @@ from odoo.tests import common -from odoo import fields +from odoo import fields, Command from odoo.exceptions import ValidationError +from datetime import timedelta class TestPurchaseOrderOverdue(common.TransactionCase): - def setUp(self): - super(TestPurchaseOrderOverdue, self).setUp() + @classmethod + def setUpClass(cls): + super(TestPurchaseOrderOverdue, cls).setUpClass() # Setup d'une société avec des paramètres personnalisés - self.company = self.env['res.company'].create({ - 'name': 'Test Company', - 'warn_supplier_overdue': True, - 'warn_supplier_overdue_user_type': 'specific', - 'warn_supplier_overdue_user_id': self.env.user.id, # L'utilisateur courant - 'warn_supplier_scope': 'specific', - }) + cls.company = cls.env.company # Créer un partenaire fournisseur avec des factures impayées - self.supplier = self.env['res.partner'].create({ - 'name': 'Test Supplier', - 'supplier_rank': 1, - }) + cls.supplier = cls.env["res.partner"].create( + { + "name": "Test Supplier", + "supplier_rank": 1, + } + ) # Créer une facture fournisseur impayée pour ce fournisseur - self.invoice = self.env['account.move'].create({ - 'partner_id': self.supplier.id, - 'move_type': 'in_invoice', - 'invoice_date_due': fields.Date.today(), - 'company_id': self.company.id, - }) + cls.invoice = cls.env["account.move"].create( + { + "partner_id": cls.supplier.id, + "move_type": "in_invoice", + "invoice_date_due": fields.Date.today() - timedelta(days=20), + "company_id": cls.company.id, + "invoice_date": fields.Date.today() - timedelta(days=50), + "line_ids": [ + Command.create( + { + "name": "Test line", + "price_unit": 1.00, + } + ) + ], + } + ) + cls.invoice.action_post() # Créer un bon de commande pour ce fournisseur - self.purchase_order = self.env['purchase.order'].create({ - 'partner_id': self.supplier.id, - 'company_id': self.company.id, - }) + cls.purchase_order = cls.env["purchase.order"].create( + { + "partner_id": cls.supplier.id, + "company_id": cls.company.id, + } + ) def test_supplier_overdue_invoice_activity_created(self): - """ Teste la création d'une activité 'To-Do' lorsque le fournisseur a des factures en retard """ + """Teste la création d'une activité 'To-Do' lorsque le fournisseur a des factures en retard""" # Confirmer la commande d'achat et vérifier la création de l'activité self.purchase_order.button_confirm() - activities = self.env['mail.activity'].search([ - ('res_model', '=', 'purchase.order'), - ('res_id', '=', self.purchase_order.id), - ('user_id', '=', self.env.user.id) - ]) + activities = self.env["mail.activity"].search( + [ + ("res_model", "=", "purchase.order"), + ("res_id", "=", self.purchase_order.id), + ("user_id", "=", self.env.user.id), + ] + ) # Vérifier qu'une activité a été créée - self.assertEqual(len(activities), 1, 'No activity was created for the overdue supplier.') + self.assertEqual( + len(activities), 1, "No activity was created for the overdue supplier." + ) # Vérifier le contenu de l'activité activity = activities[0] - self.assertEqual(activity.summary, 'Overdue Invoices for Supplier %s' % self.supplier.name) + self.assertEqual( + activity.summary, "Overdue Invoices for Supplier %s" % self.supplier.name + ) self.assertIn(self.supplier.name, activity.note) self.assertIn(self.purchase_order.name, activity.note) def test_no_activity_for_non_overdue_suppliers(self): - """ Teste qu'aucune activité n'est créée si le fournisseur n'a pas de factures impayées """ + """Teste qu'aucune activité n'est créée si le fournisseur n'a pas de factures impayées""" # Marquer la facture comme payée pour annuler l'état de retard - self.invoice.action_post() - self.invoice.button_mark_as_paid() + self.invoice.payment_state = "paid" # Confirmer la commande d'achat self.purchase_order.button_confirm() # Vérifier qu'aucune activité n'a été créée - activities = self.env['mail.activity'].search([ - ('res_model', '=', 'purchase.order'), - ('res_id', '=', self.purchase_order.id), - ]) - self.assertEqual(len(activities), 0, 'An activity was created despite no overdue invoices.') + activities = self.env["mail.activity"].search( + [ + ("res_model", "=", "purchase.order"), + ("res_id", "=", self.purchase_order.id), + ] + ) + self.assertEqual( + len(activities), 0, "An activity was created despite no overdue invoices." + ) def test_activity_for_specific_vendors_only(self): - """ Teste que l'activité est créée seulement pour les fournisseurs spécifiques """ + """Teste que l'activité est créée seulement pour les fournisseurs spécifiques""" # Ajouter le fournisseur à la liste des fournisseurs spécifiques - self.company.write({'warn_supplier_specific_ids': [(4, self.supplier.id)]}) + self.company.write( + { + "warn_supplier_specific_ids": [(4, self.supplier.id)], + "warn_supplier_scope": "specific", + } + ) # Confirmer la commande d'achat self.purchase_order.button_confirm() # Vérifier que l'activité a été créée - activities = self.env['mail.activity'].search([ - ('res_model', '=', 'purchase.order'), - ('res_id', '=', self.purchase_order.id), - ('user_id', '=', self.env.user.id) - ]) - self.assertEqual(len(activities), 1, 'No activity was created for the overdue supplier.') + activities = self.env["mail.activity"].search( + [ + ("res_model", "=", "purchase.order"), + ("res_id", "=", self.purchase_order.id), + ("user_id", "=", self.env.user.id), + ] + ) + self.assertEqual( + len(activities), 1, "No activity was created for the overdue supplier." + ) def test_no_activity_for_non_specific_vendors(self): - """ Teste qu'aucune activité n'est créée si le fournisseur n'est pas dans la liste des fournisseurs spécifiques """ + """Teste qu'aucune activité n'est créée si le fournisseur n'est pas dans la liste des fournisseurs spécifiques""" # Ne pas inclure le fournisseur dans la liste des fournisseurs spécifiques - self.company.write({'warn_supplier_specific_ids': [(3, self.supplier.id)]}) # Retirer le fournisseur + self.company.write( + { + "warn_supplier_specific_ids": [(3, self.supplier.id)], + "warn_supplier_scope": "specific", + } + ) # Retirer le fournisseur # Confirmer la commande d'achat self.purchase_order.button_confirm() # Vérifier qu'aucune activité n'a été créée - activities = self.env['mail.activity'].search([ - ('res_model', '=', 'purchase.order'), - ('res_id', '=', self.purchase_order.id), - ]) - self.assertEqual(len(activities), 0, 'An activity was created for a non-specific supplier.') \ No newline at end of file + activities = self.env["mail.activity"].search( + [ + ("res_model", "=", "purchase.order"), + ("res_id", "=", self.purchase_order.id), + ] + ) + self.assertEqual( + len(activities), 0, "An activity was created for a non-specific supplier." + ) diff --git a/bemade_purchase_warn_supplier_overdue/views/res_config_settings_views.xml b/bemade_purchase_warn_supplier_overdue/views/res_config_settings_views.xml index 6b92fc1..f5d5fc6 100644 --- a/bemade_purchase_warn_supplier_overdue/views/res_config_settings_views.xml +++ b/bemade_purchase_warn_supplier_overdue/views/res_config_settings_views.xml @@ -12,6 +12,7 @@ @@ -21,6 +22,7 @@ id="warn_supplier_overdue_user_type_settings" help="Assign the To Do to a specific user or the current user" invisible="not warn_supplier_overdue" + company_dependent="1" > @@ -30,6 +32,7 @@ id="warn_supplier_overdue_user_settings" help="Specific user to warn when PO is confirmed with a supplier who has overdue bills" invisible="warn_supplier_overdue_user_type != 'specific' or not warn_supplier_overdue" + company_dependent="1" > @@ -39,6 +42,7 @@ id="warn_supplier_scope_settings" help="Choose whether to apply the overdue warning to all vendors or specific vendors" invisible="not warn_supplier_overdue" + company_dependent="1" > @@ -48,6 +52,7 @@ id="warn_supplier_specific_ids_settings" help="Select specific vendors to apply overdue warnings if applicable" invisible="warn_supplier_scope != 'specific' or not warn_supplier_overdue" + company_dependent="1" >