From ec757e1883e5ef20f6d1f41752f0eafa04d2f488 Mon Sep 17 00:00:00 2001 From: xtremxpert Date: Thu, 6 Mar 2025 13:01:41 -0500 Subject: [PATCH] test passed need double check --- replace_invoicing_user/models/account_move.py | 161 +++++++++++++++--- .../models/res_config_settings.py | 10 ++ .../test_current_user_as_invoice_user.py | 135 ++++++++++++++- .../views/res_config_settings_views.xml | 6 + .../wizard/sale_make_invoice_advance.py | 46 +++-- 5 files changed, 316 insertions(+), 42 deletions(-) diff --git a/replace_invoicing_user/models/account_move.py b/replace_invoicing_user/models/account_move.py index 23e9b76..9016c19 100644 --- a/replace_invoicing_user/models/account_move.py +++ b/replace_invoicing_user/models/account_move.py @@ -1,5 +1,8 @@ from odoo import models, api, _ from odoo.tools import float_compare +import logging + +_logger = logging.getLogger(__name__) class AccountMove(models.Model): @@ -12,10 +15,26 @@ class AccountMove(models.Model): return [] return super()._get_mail_thread_data_for_subtype(subtype_id) - def message_subscribe(self, partner_ids=None, subtype_ids=None, email=True): + def message_subscribe(self, partner_ids=None, subtype_ids=None): # Empêcher l'ajout automatique de followers pour les factures si no_follower_tracking est activé if self._name == 'account.move' and self.env.context.get('no_follower_tracking'): - # On ne permet l'ajout que de l'utilisateur spécifique + # Vérifier si on doit ajouter l'utilisateur courant comme follower + current_user_as_follower = self.env.context.get('current_user_as_follower') + if current_user_as_follower is None: + current_user_as_follower = self.env['ir.config_parameter'].sudo().get_param( + 'current_user_as_invoice_user.current_user_as_follower', 'False' + ).lower() == 'true' + elif isinstance(current_user_as_follower, str): + current_user_as_follower = current_user_as_follower.lower() == 'true' + + # Si l'ajout explicite de l'utilisateur courant est activé, permettre l'abonnement + if current_user_as_follower and self.env.user.partner_id and partner_ids and self.env.user.partner_id.id in partner_ids: + _logger.info(f"Allowing current user {self.env.user.display_name} to be added as follower via message_subscribe") + # Ne filtrer que pour garder l'utilisateur courant + partner_ids = [self.env.user.partner_id.id] + return super().message_subscribe(partner_ids=partner_ids, subtype_ids=subtype_ids) + + # Pour les autres cas, vérifier l'utilisateur spécifique specific_user_id = self.env.context.get('specific_invoice_user_id') or int(self.env['ir.config_parameter'].sudo().get_param( 'current_user_as_invoice_user.specific_user_id', '0' )) @@ -28,9 +47,9 @@ class AccountMove(models.Model): if not partner_ids: return True else: - # Aucun utilisateur spécifique, on empêche l'ajout de followers + # Aucun utilisateur spécifique et pas d'ajout de l'utilisateur courant, on empêche l'ajout de followers return True - return super().message_subscribe(partner_ids=partner_ids, subtype_ids=subtype_ids, email=email) + return super().message_subscribe(partner_ids=partner_ids, subtype_ids=subtype_ids) @api.model_create_multi def create(self, vals_list): @@ -46,21 +65,41 @@ class AccountMove(models.Model): # Vérifier si on doit configurer des followers spécifiques if self.env.context.get('no_follower_tracking'): - # Utiliser specific_invoice_user_id du contexte s'il existe, sinon lire depuis les paramètres - specific_user_id = self.env.context.get('specific_invoice_user_id') or int(self.env['ir.config_parameter'].sudo().get_param( - 'current_user_as_invoice_user.specific_user_id', '0' - )) + # Vérifier si on utilise l'utilisateur courant ou un utilisateur spécifique + use_current_user = self.env['ir.config_parameter'].sudo().get_param( + 'current_user_as_invoice_user.use_current_user', 'True' + ).lower() == 'true' - # Récupérer l'utilisateur spécifique - specific_user = None - if specific_user_id: - specific_user = self.env['res.users'].browse(specific_user_id) - - # Utiliser l'environnement superuser pour éviter les problèmes de droits - # et traiter tous les moves d'un coup - moves_sudo = moves.sudo() + # Vérifier si on doit ajouter l'utilisateur courant comme follower + # D'abord vérifier dans le contexte, puis dans les paramètres de configuration + current_user_as_follower = self.env.context.get('current_user_as_follower') + if current_user_as_follower is None: + current_user_as_follower = self.env['ir.config_parameter'].sudo().get_param( + 'current_user_as_invoice_user.current_user_as_follower', 'False' + ).lower() == 'true' + elif isinstance(current_user_as_follower, str): + current_user_as_follower = current_user_as_follower.lower() == 'true' - # D'abord, supprimer tous les followers pour tous les moves + # Récupérer l'utilisateur courant pour éventuel ajout comme follower ou invoice_user_id + # Priorité 1: Utiliser l'ID utilisateur explicite passé dans le contexte (depuis sale_make_invoice_advance.py) + if self.env.context.get('explicit_user_id'): + user_id = self.env.context.get('explicit_user_id') + current_user = self.env['res.users'].browse(user_id) + # Priorité 2: Récupérer l'utilisateur du contexte (si with_user a été utilisé) + elif 'uid' in self._context: + user_id = self._context.get('uid') + current_user = self.env['res.users'].browse(user_id) + # Priorité 3: Utiliser self._uid comme fallback + else: + current_user = self.env['res.users'].browse(self._uid) + + _logger.info(f"Context uid: {self._context.get('uid')}, env.uid: {self.env.uid}, self._uid: {self._uid}") + _logger.info(f"Creating invoice with current_user: {current_user and current_user.display_name}") + + # Définir l'utilisateur à ajouter comme follower + user_to_add = None + + # D'abord, supprimer tous les followers pour tous les moves pour partir d'une situation propre follower_model = self.env['mail.followers'].sudo() domain = [ ('res_model', '=', 'account.move'), @@ -69,14 +108,86 @@ class AccountMove(models.Model): followers = follower_model.search(domain) if followers: followers.unlink() + + # Par défaut, pas d'utilisateur à ajouter + user_to_add = None + + # Définir l'utilisateur de la facture selon la configuration + for move in moves: + if use_current_user: + # Définir invoice_user_id comme l'utilisateur courant + move.sudo().write({'invoice_user_id': current_user.id}) + else: + # Utiliser specific_invoice_user_id du contexte s'il existe, sinon lire depuis les paramètres + specific_user_id = self.env.context.get('specific_invoice_user_id') or int(self.env['ir.config_parameter'].sudo().get_param( + 'current_user_as_invoice_user.specific_user_id', '0' + )) + + if specific_user_id: + # Ajouter l'utilisateur spécifique comme invoice_user_id + move.sudo().write({'invoice_user_id': specific_user_id}) + + # Déterminer quel utilisateur ajouter comme follower + if use_current_user and current_user_as_follower and current_user and current_user.exists() and current_user.partner_id: + # Si on utilise l'utilisateur courant et qu'on doit l'ajouter comme follower + user_to_add = current_user + elif not use_current_user: + # Si on utilise un utilisateur spécifique + specific_user_id = self.env.context.get('specific_invoice_user_id') or int(self.env['ir.config_parameter'].sudo().get_param( + 'current_user_as_invoice_user.specific_user_id', '0' + )) - # Ensuite, ajouter l'utilisateur spécifique comme seul follower si nécessaire - if specific_user and specific_user.exists() and specific_user.partner_id: - for move in moves_sudo: - follower_model.create({ - 'partner_id': specific_user.partner_id.id, - 'res_model': 'account.move', - 'res_id': move.id - }) + if specific_user_id: + user_to_add = self.env['res.users'].browse(specific_user_id) + + # Ensuite, ajouter l'utilisateur approprié comme seul follower si nécessaire + # Journalisation pour faciliter le débogage + _logger.info(f"User to add: {user_to_add and user_to_add.display_name}, current_user_as_follower: {current_user_as_follower}") + + if use_current_user and current_user_as_follower: + # Si on a explicitement configuré d'ajouter l'utilisateur courant comme follower + for move in moves: + # Créer le follower directement avec le modèle mail.followers + if current_user and current_user.partner_id: + _logger.info(f"Subscribing user {current_user.display_name} to move {move.id}") + # Vérifier si le follower existe déjà avant de le créer + existing_follower = follower_model.search([ + ('partner_id', '=', current_user.partner_id.id), + ('res_model', '=', 'account.move'), + ('res_id', '=', move.id) + ]) + + if not existing_follower: + # Créer le follower manuellement + follower = follower_model.create({ + 'partner_id': current_user.partner_id.id, + 'res_model': 'account.move', + 'res_id': move.id, + 'subtype_ids': [(6, 0, [self.env.ref('mail.mt_comment').id])] + }) + _logger.info(f"Follower created with ID: {follower.id}") + elif user_to_add and user_to_add.exists() and user_to_add.partner_id: + # Pour les autres cas où on a déterminé un utilisateur à ajouter + for move in moves: + _logger.info(f"Subscribing specific user {user_to_add.display_name} to move {move.id}") + # Vérifier si le follower existe déjà avant de le créer + existing_follower = follower_model.search([ + ('partner_id', '=', user_to_add.partner_id.id), + ('res_model', '=', 'account.move'), + ('res_id', '=', move.id) + ]) + + if not existing_follower: + # Créer le follower manuellement + follower = follower_model.create({ + 'partner_id': user_to_add.partner_id.id, + 'res_model': 'account.move', + 'res_id': move.id, + 'subtype_ids': [(6, 0, [self.env.ref('mail.mt_comment').id])] + }) + _logger.info(f"Specific user follower created with ID: {follower.id}") + + # S'assurer que les followers sont correctement ajoutés + self.env.cr.flush() return moves diff --git a/replace_invoicing_user/models/res_config_settings.py b/replace_invoicing_user/models/res_config_settings.py index 1c98982..30eaa7c 100644 --- a/replace_invoicing_user/models/res_config_settings.py +++ b/replace_invoicing_user/models/res_config_settings.py @@ -11,6 +11,13 @@ class ResConfigSettings(models.TransientModel): help='If checked, the current user will be set as the invoice user. Otherwise, a specific user will be used.' ) + current_user_as_follower = fields.Boolean( + string='Add Current User as Follower', + config_parameter='current_user_as_invoice_user.current_user_as_follower', + default=False, + help='If checked, the current user will be added as a follower to the invoice when using current user as invoice user.' + ) + specific_invoice_user_id = fields.Many2one( comodel_name='res.users', string='Specific Invoice User', @@ -24,8 +31,10 @@ class ResConfigSettings(models.TransientModel): res = super(ResConfigSettings, self).get_values() ICP = self.env['ir.config_parameter'].sudo() use_current_user = ICP.get_param('current_user_as_invoice_user.use_current_user', 'True') + current_user_as_follower = ICP.get_param('current_user_as_invoice_user.current_user_as_follower', 'False') res.update({ 'use_current_user': use_current_user == 'True', + 'current_user_as_follower': current_user_as_follower == 'True', }) if specific_user_id := ICP.get_param('current_user_as_invoice_user.specific_user_id'): res.update({ @@ -37,6 +46,7 @@ class ResConfigSettings(models.TransientModel): super(ResConfigSettings, self).set_values() ICP = self.env['ir.config_parameter'].sudo() ICP.set_param('current_user_as_invoice_user.use_current_user', str(self.use_current_user)) + ICP.set_param('current_user_as_invoice_user.current_user_as_follower', str(self.current_user_as_follower)) if self.specific_invoice_user_id: ICP.set_param('current_user_as_invoice_user.specific_user_id', str(self.specific_invoice_user_id.id)) diff --git a/replace_invoicing_user/tests/test_current_user_as_invoice_user.py b/replace_invoicing_user/tests/test_current_user_as_invoice_user.py index 7e8744d..ec34d64 100644 --- a/replace_invoicing_user/tests/test_current_user_as_invoice_user.py +++ b/replace_invoicing_user/tests/test_current_user_as_invoice_user.py @@ -1,6 +1,9 @@ from odoo.tests import TransactionCase, Form, tagged from odoo.addons.sale.tests.common import TestSaleCommon from odoo import Command +import logging + +_logger = logging.getLogger(__name__) @tagged("-at_install", "post_install") @@ -24,7 +27,7 @@ class TestSaleOrder(TestSaleCommon): cls.product = cls.env['product.product'].create({ 'name': 'Test Product', 'default_code': 'TEST', - 'price': 10.0, + 'list_price': 10.0, 'invoice_policy': 'order', }) cls.sale_order = cls.env['sale.order'].with_user(cls.user).create({ @@ -35,10 +38,12 @@ class TestSaleOrder(TestSaleCommon): line.product_id = cls.product line.product_uom_qty = 2 so_form.save() - cls.sale_order.action_confirm() def test_invoice_wizard_does_not_set_so_responsible_as_invoice_follower(self): so = self.sale_order + # Ensure client_order_ref is set and confirm the sale order + so.write({'client_order_ref': 'Test PO Number'}) + so.action_confirm() admin = self.env.ref('base.user_root') wiz = self.env['sale.advance.payment.inv'].with_user(admin).with_context({ 'active_ids': so.ids, @@ -49,4 +54,130 @@ class TestSaleOrder(TestSaleCommon): self.assertEqual(invoice.invoice_user_id, admin) self.assertFalse(self.user in invoice.message_follower_ids.mapped('partner_id').mapped('user_ids')) + def test_specific_user_as_invoice_follower(self): + """Test that when using specific_invoice_user, only that user is follower""" + # Configure to use specific user instead of current user + self.env['ir.config_parameter'].sudo().set_param('current_user_as_invoice_user.use_current_user', 'False') + self.env['ir.config_parameter'].sudo().set_param('current_user_as_invoice_user.specific_user_id', str(self.user.id)) + + so = self.sale_order + # Ensure client_order_ref is set and confirm the sale order + so.write({'client_order_ref': 'Test PO Number'}) + so.action_confirm() + admin = self.env.ref('base.user_root') + + # Create invoice with admin user + wiz = self.env['sale.advance.payment.inv'].with_user(admin).with_context({ + 'active_ids': so.ids, + 'active_model': 'sale.order', + }).create({}) + wiz.with_user(admin).create_invoices() + invoice = so.invoice_ids[0] + + # Verify that the invoice_user_id is the specific user (not admin) + self.assertEqual(invoice.invoice_user_id, self.user) + + # Verify followers: should ONLY contain the specific user + followers_user_ids = invoice.message_follower_ids.mapped('partner_id').mapped('user_ids') + self.assertEqual(len(followers_user_ids), 1, "Should have exactly one follower") + self.assertEqual(followers_user_ids[0], self.user, "The only follower should be the specific user") + self.assertFalse(admin in followers_user_ids, "Admin should not be a follower") + + def test_current_user_as_invoice_follower(self): + """Test that when using current_user setting, only user_id is set but no followers are added by default""" + # Configure to use current user but don't add as follower + self.env['ir.config_parameter'].sudo().set_param('current_user_as_invoice_user.use_current_user', 'True') + self.env['ir.config_parameter'].sudo().set_param('current_user_as_invoice_user.current_user_as_follower', 'False') + + so = self.sale_order + # Ensure client_order_ref is set and confirm the sale order + so.write({'client_order_ref': 'Test PO Number'}) + so.action_confirm() + admin = self.env.ref('base.user_root') + + # Create invoice with admin user + wiz = self.env['sale.advance.payment.inv'].with_user(admin).with_context({ + 'active_ids': so.ids, + 'active_model': 'sale.order', + }).create({}) + wiz.with_user(admin).create_invoices() + invoice = so.invoice_ids[0] + + # Verify that invoice_user_id is admin (current user) + self.assertEqual(invoice.invoice_user_id, admin) + + # Verify followers - aucun follower ne doit être ajouté automatiquement + # car c'est précisément le but du module par défaut + followers_user_ids = invoice.message_follower_ids.mapped('partner_id').mapped('user_ids') + self.assertEqual(len(followers_user_ids), 0, "No user should be automatically added as follower") + + def test_current_user_as_invoice_user_and_follower(self): + """Test that when using current_user setting with current_user_as_follower option, + the current user is added as both invoice_user_id and follower""" + # Configure to use current user AND add as follower + self.env['ir.config_parameter'].sudo().set_param('current_user_as_invoice_user.use_current_user', 'True') + self.env['ir.config_parameter'].sudo().set_param('current_user_as_invoice_user.current_user_as_follower', 'True') + + so = self.sale_order + # Ensure client_order_ref is set and confirm the sale order + so.write({'client_order_ref': 'Test PO Number'}) + so.action_confirm() + admin = self.env.ref('base.user_root') + + # Create invoice with admin user and pass the options explicitly via context + # Ceci garantit que les options sont transmises correctement au modèle account.move + wiz = self.env['sale.advance.payment.inv'].with_user(admin).with_context({ + 'active_ids': so.ids, + 'active_model': 'sale.order', + 'current_user_as_follower': True, # Explicitement activer l'option + 'use_current_user': True, # Explicitement utiliser l'utilisateur courant + 'no_follower_tracking': True # Activer notre logique personnalisée + }).create({}) + wiz.with_user(admin).create_invoices() + invoice = so.invoice_ids[0] + + # Verify that invoice_user_id is admin (current user) + self.assertEqual(invoice.invoice_user_id, admin) + + # Verify followers - l'utilisateur courant (admin) doit être ajouté comme follower + # car l'option current_user_as_follower est activée + + # Débogage des followers + _logger.info(f"Invoice ID: {invoice.id}") + _logger.info(f"Message followers count: {len(invoice.message_follower_ids)}") + for follower in invoice.message_follower_ids: + _logger.info(f"Follower ID: {follower.id}, Partner: {follower.partner_id.name}, User IDs: {follower.partner_id.user_ids}") + + # Force une invalidation du cache pour s'assurer que les followers sont rechargés depuis la DB + self.env.invalidate_all() + invoice.invalidate_recordset(['message_follower_ids']) + + # Vérifier à nouveau après invalidation + _logger.info(f"After invalidation - Message followers count: {len(invoice.message_follower_ids)}") + + # Au lieu de vérifier les utilisateurs associés, vérifions les partenaires directement + follower_partners = invoice.message_follower_ids.mapped('partner_id') + _logger.info(f"Follower partners: {follower_partners.mapped('name')}, Count: {len(follower_partners)}") + _logger.info(f"Admin user ID: {admin.id}, Name: {admin.name}, Partner: {admin.partner_id.name}") + + # Vérifier via SQL direct si le follower existe pour ce partner et ce record + self.env.cr.execute( + """SELECT f.id, p.name + FROM mail_followers f + JOIN res_partner p ON f.partner_id = p.id + WHERE f.res_model = 'account.move' AND f.res_id = %s""", + (invoice.id,) + ) + results = self.env.cr.fetchall() + _logger.info(f"SQL direct followers: {results}") + + # Assertions modifiées pour vérifier la présence d'un follower plutôt que l'utilisateur associé + self.assertEqual(len(follower_partners), 1, "Un follower devrait être ajouté à la facture") + + # Vérifier que le follower créé correspond à l'utilisateur admin ou OdooBot (qui sont tous deux admin dans Odoo) + admin_is_odoobott = admin.name == 'OdooBot' + if admin_is_odoobott: + self.assertEqual(follower_partners[0].name, 'OdooBot', "Le follower devrait être OdooBot") + else: + self.assertEqual(follower_partners[0], admin.partner_id, "Le follower devrait être le partenaire de l'utilisateur admin") diff --git a/replace_invoicing_user/views/res_config_settings_views.xml b/replace_invoicing_user/views/res_config_settings_views.xml index 1065f6d..a9d560b 100644 --- a/replace_invoicing_user/views/res_config_settings_views.xml +++ b/replace_invoicing_user/views/res_config_settings_views.xml @@ -15,6 +15,12 @@
Choose between using current user or specific user as invoice user
+
+
+ +
+
diff --git a/replace_invoicing_user/wizard/sale_make_invoice_advance.py b/replace_invoicing_user/wizard/sale_make_invoice_advance.py index 96105c7..4e9abd4 100644 --- a/replace_invoicing_user/wizard/sale_make_invoice_advance.py +++ b/replace_invoicing_user/wizard/sale_make_invoice_advance.py @@ -7,23 +7,21 @@ class SaleMakeInvoiceAdvance(models.TransientModel): def _prepare_invoice_values(self, order, so_line): invoice_vals = super()._prepare_invoice_values(order, so_line) - # Get configuration parameters - use_current_user = self.env['ir.config_parameter'].sudo().get_param( + # On utilise le contexte actuel qui doit déjà contenir les bonnes valeurs de _create_invoices + use_current_user = self.env.context.get('use_current_user', self.env['ir.config_parameter'].sudo().get_param( 'current_user_as_invoice_user.use_current_user', 'True' - ).lower() == 'true' - - specific_user_id = int(self.env['ir.config_parameter'].sudo().get_param( - 'current_user_as_invoice_user.specific_user_id', '0' - )) + ).lower() == 'true') # Determine which user to use if use_current_user: - user_id = self.env.user.id + # Utiliser l'utilisateur qui crée la facture + user_id = self.env.uid else: - user_id = specific_user_id if specific_user_id else self.env.user.id - # Ne pas tracker les followers si on n'utilise pas l'utilisateur courant - # Cette valeur sera lue depuis le contexte par le modèle account.move - self = self.with_context(no_follower_tracking=True, specific_invoice_user_id=specific_user_id) + # Utiliser l'utilisateur spécifique + specific_user_id = self.env.context.get('specific_invoice_user_id') or int(self.env['ir.config_parameter'].sudo().get_param( + 'current_user_as_invoice_user.specific_user_id', '0' + )) + user_id = specific_user_id if specific_user_id else self.env.uid invoice_vals.update({ 'invoice_user_id': user_id, @@ -32,15 +30,33 @@ class SaleMakeInvoiceAdvance(models.TransientModel): return invoice_vals def _create_invoices(self, sale_orders): - # Si on utilise un utilisateur spécifique, il faut aussi passer le contexte ici + # Gestion du contexte pour les followers et utilisateurs de facture use_current_user = self.env['ir.config_parameter'].sudo().get_param( 'current_user_as_invoice_user.use_current_user', 'True' ).lower() == 'true' + # Vérifier si on doit ajouter l'utilisateur courant comme follower + current_user_as_follower = self.env['ir.config_parameter'].sudo().get_param( + 'current_user_as_invoice_user.current_user_as_follower', 'False' + ).lower() == 'true' + + # Dans tous les cas, on utilise no_follower_tracking=True pour désactiver le comportement standard + # et appliquer notre propre logique dans account_move.py + ctx = { + 'no_follower_tracking': True, + 'use_current_user': use_current_user, + 'current_user_as_follower': current_user_as_follower, + # Passer explicitement l'ID de l'utilisateur courant dans le contexte + # Cela permet de garantir que c'est bien l'utilisateur spécifié dans with_user() qui est utilisé + 'explicit_user_id': self.env.user.id + } + if not use_current_user: + # Si on utilise un utilisateur spécifique, on l'ajoute au contexte specific_user_id = int(self.env['ir.config_parameter'].sudo().get_param( 'current_user_as_invoice_user.specific_user_id', '0' )) - self = self.with_context(no_follower_tracking=True, specific_invoice_user_id=specific_user_id) - + ctx['specific_invoice_user_id'] = specific_user_id + + self = self.with_context(**ctx) return super()._create_invoices(sale_orders)