# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from datetime import datetime, timedelta from odoo.tests.common import TransactionCase, tagged from odoo.exceptions import AccessError, ValidationError from odoo.tests import Form @tagged('post_install', '-at_install') class TestPortalPlanningIntegration(TransactionCase): """Test d'intégration pour les workflows complets du module portal_planning.""" @classmethod def setUpClass(cls): super().setUpClass() # Créer un utilisateur portail cls.portal_user = cls.env['res.users'].create({ 'name': 'Portal User', 'login': 'portal_test_user', 'email': 'portal_test@example.com', 'groups_id': [(6, 0, [cls.env.ref('base.group_portal').id])], }) # Créer un utilisateur manager cls.manager_user = cls.env['res.users'].create({ 'name': 'Manager User', 'login': 'manager_test_user', 'email': 'manager_test@example.com', 'groups_id': [(6, 0, [cls.env.ref('planning.group_planning_manager').id])], }) # Créer un employé lié à l'utilisateur portail cls.employee = cls.env['hr.employee'].create({ 'name': 'Test Employee', 'user_id': cls.portal_user.id, }) # Créer un rôle de planning cls.role = cls.env['planning.role'].create({ 'name': 'Test Role', }) # Dates pour les tests cls.start_datetime = datetime.now().replace(hour=8, minute=0, second=0, microsecond=0) + timedelta(days=1) cls.end_datetime = cls.start_datetime + timedelta(hours=4) def test_01_complete_planning_request_workflow(self): """Test du workflow complet de demande de planning: création, soumission, approbation, confirmation.""" # 1. Créer une demande de planning en tant qu'utilisateur portail request_vals = { 'name': 'Test Planning Request', 'start_datetime': self.start_datetime, 'end_datetime': self.end_datetime, 'role_id': self.role.id, 'employee_id': self.employee.id, 'user_id': self.portal_user.id, 'state': 'draft', } request = self.env['portal.planning.request'].sudo(self.portal_user).create(request_vals) # Vérifier que la demande est bien créée self.assertEqual(request.state, 'draft') # 2. Soumettre la demande request.sudo(self.portal_user).action_submit() self.assertEqual(request.state, 'submitted') # 3. Approuver la demande en tant que manager request.sudo(self.manager_user).action_approve() self.assertEqual(request.state, 'approved') # 4. Vérifier qu'un créneau de planning a été créé slot = self.env['planning.slot'].sudo().search([ ('employee_id', '=', self.employee.id), ('start_datetime', '=', self.start_datetime), ('end_datetime', '=', self.end_datetime), ], limit=1) self.assertTrue(slot, "Un créneau de planning aurait dû être créé") self.assertEqual(slot.role_id, self.role) # 5. Confirmer le créneau en tant qu'utilisateur portail slot.sudo(self.portal_user).action_confirm_portal() # Vérifier que le créneau est confirmé self.assertEqual(slot.portal_status, 'confirmed') self.assertTrue(slot.portal_confirmed) def test_02_complete_planning_modification_workflow(self): """Test du workflow complet de modification de planning: création, modification, approbation.""" # 1. Créer un créneau de planning slot = self.env['planning.slot'].sudo().create({ 'name': 'Test Slot', 'start_datetime': self.start_datetime, 'end_datetime': self.end_datetime, 'role_id': self.role.id, 'employee_id': self.employee.id, 'allocated_hours': 4.0, 'portal_status': 'draft', }) # 2. Modifier le créneau en tant qu'utilisateur portail new_start = self.start_datetime + timedelta(hours=1) new_end = self.end_datetime + timedelta(hours=1) # Simuler une modification via le portail slot.sudo(self.portal_user).write({ 'portal_start_datetime': new_start, 'portal_end_datetime': new_end, 'portal_modified': True, 'portal_status': 'modified', }) # Vérifier que le statut a changé self.assertEqual(slot.portal_status, 'modified') self.assertTrue(slot.portal_modified) # 3. Approuver la modification en tant que manager slot.sudo(self.manager_user).action_approve_modification() # Vérifier que les modifications ont été appliquées self.assertEqual(slot.start_datetime, new_start) self.assertEqual(slot.end_datetime, new_end) self.assertEqual(slot.portal_status, 'confirmed') self.assertTrue(slot.portal_confirmed) self.assertFalse(slot.portal_modified) def test_03_complete_planning_exchange_workflow(self): """Test du workflow complet d'échange de planning: création, soumission, approbation.""" # 1. Créer un deuxième utilisateur portail et employé portal_user2 = self.env['res.users'].create({ 'name': 'Portal User 2', 'login': 'portal_test_user2', 'email': 'portal_test2@example.com', 'groups_id': [(6, 0, [self.env.ref('base.group_portal').id])], }) employee2 = self.env['hr.employee'].create({ 'name': 'Test Employee 2', 'user_id': portal_user2.id, }) # 2. Créer deux créneaux de planning slot1 = self.env['planning.slot'].sudo().create({ 'name': 'Slot 1', 'start_datetime': self.start_datetime, 'end_datetime': self.end_datetime, 'role_id': self.role.id, 'employee_id': self.employee.id, 'allocated_hours': 4.0, 'portal_status': 'confirmed', 'portal_confirmed': True, }) slot2 = self.env['planning.slot'].sudo().create({ 'name': 'Slot 2', 'start_datetime': self.start_datetime + timedelta(days=1), 'end_datetime': self.end_datetime + timedelta(days=1), 'role_id': self.role.id, 'employee_id': employee2.id, 'allocated_hours': 4.0, 'portal_status': 'confirmed', 'portal_confirmed': True, }) # 3. Créer une demande d'échange en tant qu'utilisateur portail exchange = self.env['portal.planning.exchange'].sudo(self.portal_user).create({ 'name': 'Test Exchange', 'source_slot_id': slot1.id, 'target_slot_id': slot2.id, 'source_user_id': self.portal_user.id, 'target_user_id': portal_user2.id, 'state': 'draft', 'description': 'Test exchange reason', }) # Vérifier que la demande est bien créée self.assertEqual(exchange.state, 'draft') # 4. Soumettre la demande exchange.sudo(self.portal_user).action_submit() self.assertEqual(exchange.state, 'pending') # 5. Approuver la demande en tant que manager exchange.sudo(self.manager_user).action_approve() self.assertEqual(exchange.state, 'approved') # 6. Vérifier que les employés ont été échangés slot1.refresh() slot2.refresh() self.assertEqual(slot1.employee_id, employee2) self.assertEqual(slot2.employee_id, self.employee) def test_04_integration_with_timesheet(self): """Test de l'intégration avec les feuilles de temps.""" # Vérifier si les modules nécessaires sont installés module_obj = self.env['ir.module.module'] hr_timesheet_module = module_obj.search([('name', '=', 'hr_timesheet'), ('state', '=', 'installed')]) project_module = module_obj.search([('name', '=', 'project'), ('state', '=', 'installed')]) if not (hr_timesheet_module and project_module): self.skipTest("Ce test nécessite les modules 'hr_timesheet' et 'project' installés") try: # 1. Créer un projet et une tâche project = self.env['project.project'].create({ 'name': 'Test Project', }) task = self.env['project.task'].create({ 'name': 'Test Task', 'project_id': project.id, }) # 2. Créer un créneau de planning avec génération de feuille de temps activée slot_vals = { 'name': 'Test Slot', 'start_datetime': self.start_datetime, 'end_datetime': self.end_datetime, 'role_id': self.role.id, 'employee_id': self.employee.id, 'allocated_hours': 4.0, 'portal_status': 'confirmed', 'portal_confirmed': True, } # Vérifier si les champs liés au timesheet existent slot_fields = self.env['planning.slot']._fields if 'generate_timesheet' in slot_fields: slot_vals['generate_timesheet'] = True if 'project_id' in slot_fields: slot_vals['project_id'] = project.id if 'task_id' in slot_fields: slot_vals['task_id'] = task.id slot = self.env['planning.slot'].sudo().create(slot_vals) # 3. Générer la feuille de temps si la méthode existe if hasattr(slot, 'action_generate_timesheet'): slot.sudo().action_generate_timesheet() # 4. Vérifier qu'une feuille de temps a été créée timesheet = self.env['account.analytic.line'].sudo().search([ ('planning_slot_id', '=', slot.id), ('employee_id', '=', self.employee.id), ('project_id', '=', project.id), ('task_id', '=', task.id), ], limit=1) self.assertTrue(timesheet, "Une feuille de temps aurait dû être créée") if hasattr(timesheet, 'unit_amount'): self.assertEqual(timesheet.unit_amount, slot.allocated_hours) except Exception as e: self.skipTest(f"Test ignoré en raison d'une erreur: {e}") def test_05_portal_access_rights(self): """Test des droits d'accès des utilisateurs du portail.""" # 1. Créer un créneau de planning pour l'employé slot = self.env['planning.slot'].sudo().create({ 'name': 'Test Slot', 'start_datetime': self.start_datetime, 'end_datetime': self.end_datetime, 'role_id': self.role.id, 'employee_id': self.employee.id, 'allocated_hours': 4.0, 'portal_status': 'draft', }) # 2. Créer un autre employé et un créneau associé other_employee = self.env['hr.employee'].create({ 'name': 'Other Employee', }) other_slot = self.env['planning.slot'].sudo().create({ 'name': 'Other Slot', 'start_datetime': self.start_datetime, 'end_datetime': self.end_datetime, 'role_id': self.role.id, 'employee_id': other_employee.id, 'allocated_hours': 4.0, 'portal_status': 'draft', }) # 3. Vérifier que l'utilisateur portail peut accéder à son propre créneau # mais pas à celui d'un autre employé # Note: Nous utilisons search_count car read pourrait lever une exception own_slots_count = self.env['planning.slot'].sudo(self.portal_user).search_count([ ('id', '=', slot.id) ]) other_slots_count = self.env['planning.slot'].sudo(self.portal_user).search_count([ ('id', '=', other_slot.id) ]) self.assertEqual(own_slots_count, 1, "L'utilisateur portail devrait pouvoir voir son propre créneau") self.assertEqual(other_slots_count, 0, "L'utilisateur portail ne devrait pas pouvoir voir le créneau d'un autre employé") # 4. Vérifier que le manager peut accéder à tous les créneaux manager_own_slots_count = self.env['planning.slot'].sudo(self.manager_user).search_count([ ('id', '=', slot.id) ]) manager_other_slots_count = self.env['planning.slot'].sudo(self.manager_user).search_count([ ('id', '=', other_slot.id) ]) self.assertEqual(manager_own_slots_count, 1, "Le manager devrait pouvoir voir tous les créneaux") self.assertEqual(manager_other_slots_count, 1, "Le manager devrait pouvoir voir tous les créneaux")