diff --git a/bemade_purchase_warn_supplier_overdue/__init__.py b/bemade_purchase_warn_supplier_overdue/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/bemade_purchase_warn_supplier_overdue/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/bemade_purchase_warn_supplier_overdue/__manifest__.py b/bemade_purchase_warn_supplier_overdue/__manifest__.py new file mode 100644 index 0000000..096faac --- /dev/null +++ b/bemade_purchase_warn_supplier_overdue/__manifest__.py @@ -0,0 +1,29 @@ +{ + '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. +""", + 'author': 'Benoît Vézina', + 'website': 'https://www.bemade.com', + 'category': 'Purchases', + 'license': 'OPL-1', + 'depends': [ + 'purchase', + 'account', + 'mail', + ], + 'data': [ + 'views/res_config_settings_views.xml', + # 'security/ir.model.access.csv', + ], + 'demo': [ + # List any demo data files here + ], + 'installable': True, + 'application': False, + 'auto_install': False, +} diff --git a/bemade_purchase_warn_supplier_overdue/models/__init__.py b/bemade_purchase_warn_supplier_overdue/models/__init__.py new file mode 100644 index 0000000..374b34a --- /dev/null +++ b/bemade_purchase_warn_supplier_overdue/models/__init__.py @@ -0,0 +1,3 @@ +from . import res_company +from . import res_config_settings +from . import purchase_order diff --git a/bemade_purchase_warn_supplier_overdue/models/purchase_order.py b/bemade_purchase_warn_supplier_overdue/models/purchase_order.py new file mode 100644 index 0000000..fbd54c6 --- /dev/null +++ b/bemade_purchase_warn_supplier_overdue/models/purchase_order.py @@ -0,0 +1,52 @@ +from odoo import models, fields, api, _ + +class PurchaseOrder(models.Model): + _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 + ]) + return len(overdue_invoices) > 0 + + def button_confirm(self): + """ 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() + + for order in self: + supplier = order.partner_id + 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 + + 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 \ No newline at end of file diff --git a/bemade_purchase_warn_supplier_overdue/models/res_company.py b/bemade_purchase_warn_supplier_overdue/models/res_company.py new file mode 100644 index 0000000..2a2cee3 --- /dev/null +++ b/bemade_purchase_warn_supplier_overdue/models/res_company.py @@ -0,0 +1,45 @@ +from odoo import models, fields, api + + +class Company(models.Model): + _inherit = 'res.company' + + warn_supplier_overdue = fields.Boolean( + string='Warn supplier when overdue', + default=True, + help='Warn user on purchase with overdue vendor', + ) + + warn_supplier_overdue_user_type = fields.Selection( + string='User Warned Type', + selection=[ + ('current', 'Current User'), + ('specific', 'Specific User'), + ], + default='current', + help='Type of 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', + ) + + warn_supplier_scope = fields.Selection( + string='Warn Scope', + selection=[ + ('all', 'All Vendors'), + ('specific', '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', + ) + diff --git a/bemade_purchase_warn_supplier_overdue/models/res_config_settings.py b/bemade_purchase_warn_supplier_overdue/models/res_config_settings.py new file mode 100644 index 0000000..6cbdf7a --- /dev/null +++ b/bemade_purchase_warn_supplier_overdue/models/res_config_settings.py @@ -0,0 +1,36 @@ +from odoo import models, fields + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + warn_supplier_overdue = fields.Boolean( + 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', + 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', + readonly=False, + ) + + warn_supplier_scope = fields.Selection( + 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', + readonly=False, + ) \ No newline at end of file diff --git a/bemade_purchase_warn_supplier_overdue/tests/__init__.py b/bemade_purchase_warn_supplier_overdue/tests/__init__.py new file mode 100644 index 0000000..cb1d3f0 --- /dev/null +++ b/bemade_purchase_warn_supplier_overdue/tests/__init__.py @@ -0,0 +1 @@ +from . import test_purchase_order_overdue \ 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 new file mode 100644 index 0000000..88d29c1 --- /dev/null +++ b/bemade_purchase_warn_supplier_overdue/tests/test_purchase_order_overdue.py @@ -0,0 +1,105 @@ +from odoo.tests import common +from odoo import fields +from odoo.exceptions import ValidationError + + +class TestPurchaseOrderOverdue(common.TransactionCase): + + def setUp(self): + super(TestPurchaseOrderOverdue, self).setUp() + + # 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', + }) + + # Créer un partenaire fournisseur avec des factures impayées + self.supplier = self.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, + }) + + # 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, + }) + + 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 """ + # 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) + ]) + + # Vérifier qu'une activité a été créée + 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.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 """ + # Marquer la facture comme payée pour annuler l'état de retard + self.invoice.action_post() + self.invoice.button_mark_as_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.') + + def test_activity_for_specific_vendors_only(self): + """ 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)]}) + + # 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.') + + 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 """ + # 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 + + # 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 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 new file mode 100644 index 0000000..6b92fc1 --- /dev/null +++ b/bemade_purchase_warn_supplier_overdue/views/res_config_settings_views.xml @@ -0,0 +1,70 @@ + + + + res.config.settings.view.form.inherit.purchase.overdue + res.config.settings + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Supplier Overdue Settings + res.config.settings + form + new + + + + \ No newline at end of file