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"
>