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',
'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,
}

View file

@ -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
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):
_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
)
)

View file

@ -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,
)
)

View file

@ -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.')
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."
)

View file

@ -12,6 +12,7 @@
<setting
id="warn_supplier_overdue_settings"
help="Warn a user on purchase order confirmation for overdue suppliers"
company_dependent="1"
>
<field name="warn_supplier_overdue"/>
</setting>
@ -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"
>
<field name="warn_supplier_overdue_user_type"/>
</setting>
@ -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"
>
<field name="warn_supplier_overdue_user_id"/>
</setting>
@ -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"
>
<field name="warn_supplier_scope"/>
</setting>
@ -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"
>
<field name="warn_supplier_specific_ids" widget="many2many_tags"/>
</setting>