import json from datetime import date, timedelta from django.test import TestCase from django.contrib.auth import get_user_model from django.urls import reverse from django.utils import timezone from contracts.models import Company, CompanyTeam, CompanyMember, CompanyMemberHistory, CompanyMemberAbsence from contracts.services_teams import ( get_member_team_for_date, set_member_team_schedule, get_teams_composition_for_period, get_member_absences_for_period, toggle_member_absence, duplicate_day_schedule, duplicate_week_schedule, sync_member_current_teams, ) User = get_user_model() class TeamPlanningServicesTests(TestCase): def setUp(self): self.user = User.objects.create_user(username="alice", password="password123") self.company = Company.objects.create(name="Acme Corp") self.team_a = CompanyTeam.objects.create(name="Team A", company=self.company) self.team_b = CompanyTeam.objects.create(name="Team B", company=self.company) self.member_alice = CompanyMember.objects.create(name="Alice", user=self.user) def test_schedule_future_team_change_preserves_today(self): """ Scénario clé : - Alice est dans l'équipe A aujourd'hui (02/09). - Le coordinateur planifie Alice dans l'équipe B pour demain (03/09). - Alice doit rester dans l'équipe A aujourd'hui et être dans l'équipe B demain. """ today = date(2026, 9, 2) tomorrow = date(2026, 9, 3) # Alice est affectée à l'équipe A depuis le 01/09 set_member_team_schedule(self.member_alice, self.team_a.id, date(2026, 9, 1)) # Vérifier qu'elle est bien dans l'équipe A aujourd'hui self.assertEqual(get_member_team_for_date(self.member_alice, today), self.team_a) # Planifier l'équipe B pour demain set_member_team_schedule(self.member_alice, self.team_b.id, tomorrow) # Vérifier : aujourd'hui elle est TOUJOURS dans l'équipe A self.assertEqual(get_member_team_for_date(self.member_alice, today), self.team_a) # Vérifier : demain elle sera dans l'équipe B self.assertEqual(get_member_team_for_date(self.member_alice, tomorrow), self.team_b) # Vérifier : après-demain elle reste dans l'équipe B (persistance) self.assertEqual(get_member_team_for_date(self.member_alice, date(2026, 9, 4)), self.team_b) def test_schedule_unassign_for_future_date(self): """Tester la désassignation à une date future.""" d1 = date(2026, 9, 1) d2 = date(2026, 9, 5) set_member_team_schedule(self.member_alice, self.team_a.id, d1) self.assertEqual(get_member_team_for_date(self.member_alice, date(2026, 9, 3)), self.team_a) # Désassigner à partir du 5 septembre set_member_team_schedule(self.member_alice, None, d2) self.assertEqual(get_member_team_for_date(self.member_alice, date(2026, 9, 4)), self.team_a) self.assertIsNone(get_member_team_for_date(self.member_alice, d2)) self.assertIsNone(get_member_team_for_date(self.member_alice, date(2026, 9, 10))) def test_toggle_absence(self): """Tester l'activation et désactivation d'une absence.""" target_date = date(2026, 9, 3) # Activer l'absence absence = toggle_member_absence(self.member_alice, target_date, is_absent=True, reason='maladie', comment='Grippe') self.assertIsNotNone(absence) self.assertEqual(CompanyMemberAbsence.objects.filter(member=self.member_alice, date=target_date).count(), 1) absences = get_member_absences_for_period({self.member_alice.id}, target_date, target_date) self.assertIn((self.member_alice.id, target_date), absences) self.assertEqual(absences[(self.member_alice.id, target_date)]['reason'], 'maladie') # Désactiver l'absence toggle_member_absence(self.member_alice, target_date, is_absent=False) self.assertEqual(CompanyMemberAbsence.objects.filter(member=self.member_alice, date=target_date).count(), 0) def test_duplicate_day_schedule(self): """Tester la duplication de configuration d'un jour vers un autre.""" monday = date(2026, 9, 7) tuesday = date(2026, 9, 8) # Lundi : Alice en Team A et absente set_member_team_schedule(self.member_alice, self.team_a.id, monday) toggle_member_absence(self.member_alice, monday, is_absent=True, reason='conge') # Dupliquer vers mardi avec copie des absences res = duplicate_day_schedule(monday, tuesday, copy_absences=True) self.assertGreaterEqual(res['assigned_count'], 1) self.assertEqual(res['absences_count'], 1) # Vérifier l'affectation du mardi self.assertEqual(get_member_team_for_date(self.member_alice, tuesday), self.team_a) self.assertTrue(CompanyMemberAbsence.objects.filter(member=self.member_alice, date=tuesday).exists()) def test_duplicate_week_schedule(self): """Tester la duplication d'une semaine complète vers la semaine suivante.""" w1_monday = date(2026, 9, 7) w2_monday = date(2026, 9, 14) set_member_team_schedule(self.member_alice, self.team_a.id, w1_monday) res = duplicate_week_schedule(w1_monday, w2_monday, copy_absences=False) self.assertGreaterEqual(res['assigned_count'], 1) # Vérifier que le mercredi de la semaine 2, Alice est bien en Team A self.assertEqual(get_member_team_for_date(self.member_alice, date(2026, 9, 16)), self.team_a) class TeamPlanningAjaxViewsTests(TestCase): def setUp(self): from common.models import UserConfig, Role import json self.user = User.objects.create_user(username="admin_user", password="password123") self.user_config = UserConfig.objects.create(user=self.user, is_intern=True) admin_role = Role.objects.create(name="admin") self.user_config.roles.add(admin_role) self.company = Company.objects.create(name="Acme Corp") self.team_a = CompanyTeam.objects.create(name="Team A", company=self.company) self.team_b = CompanyTeam.objects.create(name="Team B", company=self.company) self.tech_user = User.objects.create_user(username="alice", password="password123") self.member_alice = CompanyMember.objects.create(name="Alice", user=self.tech_user) self.client.login(username="admin_user", password="password123") def test_planning_data_ajax(self): url = reverse('contracts:team_planning_data_ajax') response = self.client.get(url, {'date': '2026-09-02', 'view_mode': 'week'}) self.assertEqual(response.status_code, 200) data = response.json() self.assertIn('days', data) self.assertEqual(len(data['days']), 7) self.assertIn('teams', data) self.assertIn('members', data) def test_planning_assign_member_ajax(self): url = reverse('contracts:team_planning_assign_member_ajax') payload = { 'member_id': self.member_alice.id, 'team_id': self.team_b.id, 'date': '2026-09-03', } response = self.client.post(url, data=json.dumps(payload), content_type='application/json') self.assertEqual(response.status_code, 200) self.assertTrue(response.json()['success']) # Vérifier l'affectation self.assertEqual(get_member_team_for_date(self.member_alice, date(2026, 9, 3)), self.team_b) def test_planning_toggle_absence_ajax(self): url = reverse('contracts:team_planning_toggle_absence_ajax') payload = { 'member_id': self.member_alice.id, 'date': '2026-09-04', 'is_absent': True, 'reason': 'maladie', 'comment': 'Rhume', } response = self.client.post(url, data=json.dumps(payload), content_type='application/json') self.assertEqual(response.status_code, 200) self.assertTrue(response.json()['is_absent']) self.assertEqual(CompanyMemberAbsence.objects.filter(member=self.member_alice, date='2026-09-04').count(), 1) def test_planning_duplicate_ajax(self): # Assigner Alice le 07/09 set_member_team_schedule(self.member_alice, self.team_a.id, date(2026, 9, 7)) url = reverse('contracts:team_planning_duplicate_ajax') payload = { 'mode': 'day', 'source_date': '2026-09-07', 'target_date': '2026-09-08', 'copy_absences': False, } response = self.client.post(url, data=json.dumps(payload), content_type='application/json') self.assertEqual(response.status_code, 200) self.assertTrue(response.json()['success']) self.assertEqual(get_member_team_for_date(self.member_alice, date(2026, 9, 8)), self.team_a) def test_teams_move_member_syncs_history(self): url = reverse('contracts:teams_move_member', args=[self.member_alice.id]) payload = { 'team_id': self.team_a.id, 'old_team_id': None, } response = self.client.post(url, data=json.dumps(payload), content_type='application/json') self.assertEqual(response.status_code, 200) self.assertTrue(response.json()['success']) today = timezone.now().date() self.assertEqual(get_member_team_for_date(self.member_alice, today), self.team_a)