bemade_purchase_warn_supplier_overdue: refactoring and tests passing

This commit is contained in:
Marc Durepos 2024-10-07 09:53:58 -04:00
parent 9503f1febd
commit 786b501c58
6 changed files with 221 additions and 145 deletions

View file

@ -1,29 +1,25 @@
{ {
'name': 'Bemade Warn Supplier Overdue', "name": "Bemade Warn Supplier Overdue",
'version': '1.0', "version": "1.0",
'summary': 'Warn supplier when overdue on purchase order confirmation', "summary": "Warn supplier when overdue on purchase order confirmation",
'description': """ "description": """
Warn supplier when overdue on purchase order confirmation 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', "author": "Benoît Vézina",
'website': 'https://www.bemade.com', "website": "https://www.bemade.org",
'category': 'Purchases', "category": "Purchases",
'license': 'OPL-1', "license": "LGPL-3",
'depends': [ "depends": [
'purchase', "purchase",
'account', "account",
'mail', "mail",
], ],
'data': [ "data": [
'views/res_config_settings_views.xml', "views/res_config_settings_views.xml",
# 'security/ir.model.access.csv',
], ],
'demo': [ "installable": True,
# List any demo data files here "application": False,
], "auto_install": False,
'installable': True,
'application': False,
'auto_install': False,
} }

View file

@ -1,21 +1,28 @@
from odoo import models, fields, api, _ from odoo import models, fields, api, _
class PurchaseOrder(models.Model): class PurchaseOrder(models.Model):
_inherit = 'purchase.order' _inherit = "purchase.order"
@api.model @api.model
def _check_supplier_overdue_invoices(self, partner): def _check_supplier_overdue_invoices(self, partner):
""" Vérifie si le fournisseur a des factures impayées en retard """ """Vérifie si le fournisseur a des factures impayées en retard"""
overdue_invoices = self.env['account.move'].search([ overdue_invoices = self.env["account.move"].search(
('partner_id', '=', partner.id), [
('move_type', '=', 'in_invoice'), # Facture fournisseur ("partner_id", "=", partner.id),
('invoice_date_due', '<', fields.Date.today()), # Date d'échéance dépassée ("move_type", "=", "in_invoice"), # Facture fournisseur
('payment_state', '!=', 'paid') # Non payée (
]) "invoice_date_due",
"<",
fields.Date.today(),
), # Date d'échéance dépassée
("payment_state", "!=", "paid"), # Non payée
]
)
return len(overdue_invoices) > 0 return len(overdue_invoices) > 0
def button_confirm(self): 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 # Appel de la méthode standard de confirmation de commande
res = super(PurchaseOrder, self).button_confirm() res = super(PurchaseOrder, self).button_confirm()
@ -24,29 +31,43 @@ class PurchaseOrder(models.Model):
company = order.company_id company = order.company_id
# Vérifier si la fonctionnalité d'avertissement est activée # Vérifier si la fonctionnalité d'avertissement est activée
if company.warn_supplier_overdue: # et si le fournisseur a des factures en souffrance
# Vérifier si l'avertissement s'applique à tous les fournisseurs ou seulement à certains warn = company.warn_overdue_for_supplier(
if company.warn_supplier_scope == 'all' or (company.warn_supplier_scope == 'specific' and supplier in company.warn_supplier_specific_ids): supplier
if self._check_supplier_overdue_invoices(supplier): ) and self._check_supplier_overdue_invoices(supplier)
# Déterminer quel utilisateur doit être averti if warn:
if company.warn_supplier_overdue_user_type == 'current': user_to_notify = order._get_user_to_notify()
user_to_notify = self.env.user if user_to_notify:
elif company.warn_supplier_overdue_user_type == 'specific': # Création de l'activité de type "To-Do" (mail.activity)
user_to_notify = company.warn_supplier_overdue_user_id activity_vals = {
else: "res_model_id": self.env["ir.model"]
user_to_notify = self.env.user # Par défaut, utilisateur courant .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: self.env["mail.activity"].create(activity_vals)
# 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 return res
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

View file

@ -2,44 +2,58 @@ from odoo import models, fields, api
class Company(models.Model): class Company(models.Model):
_inherit = 'res.company' _inherit = "res.company"
warn_supplier_overdue = fields.Boolean( warn_supplier_overdue = fields.Boolean(
string='Warn supplier when overdue', string="Warn when supplier invoice overdue",
default=True, 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( warn_supplier_overdue_user_type = fields.Selection(
string='User Warned Type', string="Warned User",
selection=[ selection=[
('current', 'Current User'), ("current", "Current User"),
('specific', 'Specific User'), ("specific", "Specific User"),
], ],
default='current', default="current",
help='Type of user to warn when supplier is overdue', help="Which user to warn when supplier is overdue",
) )
warn_supplier_overdue_user_id = fields.Many2one( warn_supplier_overdue_user_id = fields.Many2one(
string='User', string="User",
comodel_name='res.users', comodel_name="res.users",
help='Specific User to warn when supplier is overdue', help="Specific User to warn when supplier is overdue.",
) )
warn_supplier_scope = fields.Selection( warn_supplier_scope = fields.Selection(
string='Warn Scope', string="Warning Vendor Scope",
selection=[ selection=[
('all', 'All Vendors'), ("all", "All Vendors"),
('specific', 'Specific Vendors'), ("specific", "Specific Vendors"),
], ],
default='all', default="all",
help='Choose whether to apply overdue warnings to all vendors or only to specific vendors', help=(
"Choose whether to apply overdue warnings to all vendors or only to "
"specific vendors."
),
) )
warn_supplier_specific_ids = fields.Many2many( warn_supplier_specific_ids = fields.Many2many(
comodel_name='res.partner', comodel_name="res.partner",
domain=[('supplier_rank', '>', 0)], domain=[("supplier_rank", ">", 0)],
string='Specific Vendors', string="Specific Vendors",
help='Select specific vendors to apply overdue invoice warnings', 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
)
)

View file

@ -1,36 +1,37 @@
from odoo import models, fields from odoo import models, fields
class ResConfigSettings(models.TransientModel): class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings' _inherit = "res.config.settings"
warn_supplier_overdue = fields.Boolean( warn_supplier_overdue = fields.Boolean(
string='Warn supplier when overdue', string="Warn supplier when overdue",
related='company_id.warn_supplier_overdue', related="company_id.warn_supplier_overdue",
readonly=False, readonly=False,
) )
warn_supplier_overdue_user_type = fields.Selection( warn_supplier_overdue_user_type = fields.Selection(
string='User Warned Type', string="User Warned Type",
related='company_id.warn_supplier_overdue_user_type', related="company_id.warn_supplier_overdue_user_type",
readonly=False, readonly=False,
) )
warn_supplier_overdue_user_id = fields.Many2one( warn_supplier_overdue_user_id = fields.Many2one(
string='User to warn', string="User to warn",
comodel_name='res.users', comodel_name="res.users",
related='company_id.warn_supplier_overdue_user_id', related="company_id.warn_supplier_overdue_user_id",
readonly=False, readonly=False,
) )
warn_supplier_scope = fields.Selection( warn_supplier_scope = fields.Selection(
string='Warn Scope', string="Warn Scope",
related='company_id.warn_supplier_scope', related="company_id.warn_supplier_scope",
readonly=False, readonly=False,
) )
warn_supplier_specific_ids = fields.Many2many( warn_supplier_specific_ids = fields.Many2many(
string='Specific Vendors', string="Specific Vendors",
comodel_name='res.partner', comodel_name="res.partner",
related='company_id.warn_supplier_specific_ids', related="company_id.warn_supplier_specific_ids",
readonly=False, readonly=False,
) )

View file

@ -1,105 +1,144 @@
from odoo.tests import common from odoo.tests import common
from odoo import fields from odoo import fields, Command
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from datetime import timedelta
class TestPurchaseOrderOverdue(common.TransactionCase): class TestPurchaseOrderOverdue(common.TransactionCase):
def setUp(self): @classmethod
super(TestPurchaseOrderOverdue, self).setUp() def setUpClass(cls):
super(TestPurchaseOrderOverdue, cls).setUpClass()
# Setup d'une société avec des paramètres personnalisés # Setup d'une société avec des paramètres personnalisés
self.company = self.env['res.company'].create({ cls.company = cls.env.company
'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 # Créer un partenaire fournisseur avec des factures impayées
self.supplier = self.env['res.partner'].create({ cls.supplier = cls.env["res.partner"].create(
'name': 'Test Supplier', {
'supplier_rank': 1, "name": "Test Supplier",
}) "supplier_rank": 1,
}
)
# Créer une facture fournisseur impayée pour ce fournisseur # Créer une facture fournisseur impayée pour ce fournisseur
self.invoice = self.env['account.move'].create({ cls.invoice = cls.env["account.move"].create(
'partner_id': self.supplier.id, {
'move_type': 'in_invoice', "partner_id": cls.supplier.id,
'invoice_date_due': fields.Date.today(), "move_type": "in_invoice",
'company_id': self.company.id, "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 # Créer un bon de commande pour ce fournisseur
self.purchase_order = self.env['purchase.order'].create({ cls.purchase_order = cls.env["purchase.order"].create(
'partner_id': self.supplier.id, {
'company_id': self.company.id, "partner_id": cls.supplier.id,
}) "company_id": cls.company.id,
}
)
def test_supplier_overdue_invoice_activity_created(self): 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é # Confirmer la commande d'achat et vérifier la création de l'activité
self.purchase_order.button_confirm() self.purchase_order.button_confirm()
activities = self.env['mail.activity'].search([ activities = self.env["mail.activity"].search(
('res_model', '=', 'purchase.order'), [
('res_id', '=', self.purchase_order.id), ("res_model", "=", "purchase.order"),
('user_id', '=', self.env.user.id) ("res_id", "=", self.purchase_order.id),
]) ("user_id", "=", self.env.user.id),
]
)
# Vérifier qu'une activité a été créée # 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é # Vérifier le contenu de l'activité
activity = activities[0] 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.supplier.name, activity.note)
self.assertIn(self.purchase_order.name, activity.note) self.assertIn(self.purchase_order.name, activity.note)
def test_no_activity_for_non_overdue_suppliers(self): 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 # Marquer la facture comme payée pour annuler l'état de retard
self.invoice.action_post() self.invoice.payment_state = "paid"
self.invoice.button_mark_as_paid()
# Confirmer la commande d'achat # Confirmer la commande d'achat
self.purchase_order.button_confirm() self.purchase_order.button_confirm()
# Vérifier qu'aucune activité n'a été créée # Vérifier qu'aucune activité n'a été créée
activities = self.env['mail.activity'].search([ activities = self.env["mail.activity"].search(
('res_model', '=', 'purchase.order'), [
('res_id', '=', self.purchase_order.id), ("res_model", "=", "purchase.order"),
]) ("res_id", "=", self.purchase_order.id),
self.assertEqual(len(activities), 0, 'An activity was created despite no overdue invoices.') ]
)
self.assertEqual(
len(activities), 0, "An activity was created despite no overdue invoices."
)
def test_activity_for_specific_vendors_only(self): 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 # 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 # Confirmer la commande d'achat
self.purchase_order.button_confirm() self.purchase_order.button_confirm()
# Vérifier que l'activité a été créée # Vérifier que l'activité a été créée
activities = self.env['mail.activity'].search([ activities = self.env["mail.activity"].search(
('res_model', '=', 'purchase.order'), [
('res_id', '=', self.purchase_order.id), ("res_model", "=", "purchase.order"),
('user_id', '=', self.env.user.id) ("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.') ]
)
self.assertEqual(
len(activities), 1, "No activity was created for the overdue supplier."
)
def test_no_activity_for_non_specific_vendors(self): 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 # 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 # Confirmer la commande d'achat
self.purchase_order.button_confirm() self.purchase_order.button_confirm()
# Vérifier qu'aucune activité n'a été créée # Vérifier qu'aucune activité n'a été créée
activities = self.env['mail.activity'].search([ activities = self.env["mail.activity"].search(
('res_model', '=', 'purchase.order'), [
('res_id', '=', self.purchase_order.id), ("res_model", "=", "purchase.order"),
]) ("res_id", "=", self.purchase_order.id),
self.assertEqual(len(activities), 0, 'An activity was created for a non-specific supplier.') ]
)
self.assertEqual(
len(activities), 0, "An activity was created for a non-specific supplier."
)

View file

@ -12,6 +12,7 @@
<setting <setting
id="warn_supplier_overdue_settings" id="warn_supplier_overdue_settings"
help="Warn a user on purchase order confirmation for overdue suppliers" help="Warn a user on purchase order confirmation for overdue suppliers"
company_dependent="1"
> >
<field name="warn_supplier_overdue"/> <field name="warn_supplier_overdue"/>
</setting> </setting>
@ -21,6 +22,7 @@
id="warn_supplier_overdue_user_type_settings" id="warn_supplier_overdue_user_type_settings"
help="Assign the To Do to a specific user or the current user" help="Assign the To Do to a specific user or the current user"
invisible="not warn_supplier_overdue" invisible="not warn_supplier_overdue"
company_dependent="1"
> >
<field name="warn_supplier_overdue_user_type"/> <field name="warn_supplier_overdue_user_type"/>
</setting> </setting>
@ -30,6 +32,7 @@
id="warn_supplier_overdue_user_settings" id="warn_supplier_overdue_user_settings"
help="Specific user to warn when PO is confirmed with a supplier who has overdue bills" 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" invisible="warn_supplier_overdue_user_type != 'specific' or not warn_supplier_overdue"
company_dependent="1"
> >
<field name="warn_supplier_overdue_user_id"/> <field name="warn_supplier_overdue_user_id"/>
</setting> </setting>
@ -39,6 +42,7 @@
id="warn_supplier_scope_settings" id="warn_supplier_scope_settings"
help="Choose whether to apply the overdue warning to all vendors or specific vendors" help="Choose whether to apply the overdue warning to all vendors or specific vendors"
invisible="not warn_supplier_overdue" invisible="not warn_supplier_overdue"
company_dependent="1"
> >
<field name="warn_supplier_scope"/> <field name="warn_supplier_scope"/>
</setting> </setting>
@ -48,6 +52,7 @@
id="warn_supplier_specific_ids_settings" id="warn_supplier_specific_ids_settings"
help="Select specific vendors to apply overdue warnings if applicable" help="Select specific vendors to apply overdue warnings if applicable"
invisible="warn_supplier_scope != 'specific' or not warn_supplier_overdue" invisible="warn_supplier_scope != 'specific' or not warn_supplier_overdue"
company_dependent="1"
> >
<field name="warn_supplier_specific_ids" widget="many2many_tags"/> <field name="warn_supplier_specific_ids" widget="many2many_tags"/>
</setting> </setting>