feat: implement calendar functionality and add localization support for intervention models

This commit is contained in:
Karl Determe 2026-08-20 13:36:42 +02:00
parent 56281b4821
commit 76158a3be0
9 changed files with 151 additions and 26 deletions

View file

@ -5444,31 +5444,31 @@ msgstr "View intervention"
#: interventions/templates/interventions/interventions_calendar.html:660 #: interventions/templates/interventions/interventions_calendar.html:660
msgid "Historique du membre" msgid "Historique du membre"
msgstr "Sending history" msgstr "Member history"
#: interventions/templates/interventions/interventions_calendar.html:675 #: interventions/templates/interventions/interventions_calendar.html:675
msgid "Technicien / Membre" msgid "Technicien / Membre"
msgstr "Technician" msgstr "Technician / Member"
#: interventions/templates/interventions/interventions_calendar.html:681 #: interventions/templates/interventions/interventions_calendar.html:681
msgid "Entreprise" msgid "Entreprise"
msgstr "Maintenance" msgstr "Company / Organization"
#: interventions/templates/interventions/interventions_calendar.html:694 #: interventions/templates/interventions/interventions_calendar.html:694
msgid "Historique des équipes" msgid "Historique des équipes"
msgstr "Sending history" msgstr "Team history"
#: interventions/templates/interventions/interventions_calendar.html:702 #: interventions/templates/interventions/interventions_calendar.html:702
msgid "Entrée" msgid "Entrée"
msgstr "Maintenance" msgstr "Joined"
#: interventions/templates/interventions/interventions_calendar.html:703 #: interventions/templates/interventions/interventions_calendar.html:703
msgid "Sortie" msgid "Sortie"
msgstr "Sortie" msgstr "Left"
#: interventions/templates/interventions/interventions_calendar.html:718 #: interventions/templates/interventions/interventions_calendar.html:718
msgid "Dernières interventions réalisées / assignées" msgid "Dernières interventions réalisées / assignées"
msgstr "Export selected interventions" msgstr "Recent realized / assigned interventions"
#: interventions/templates/interventions/interventions_index.html:420 #: interventions/templates/interventions/interventions_index.html:420
msgid "Choisir le type d’intervention" msgid "Choisir le type d’intervention"

View file

@ -5502,15 +5502,15 @@ msgstr "Technicien"
#: interventions/templates/interventions/interventions_calendar.html:681 #: interventions/templates/interventions/interventions_calendar.html:681
msgid "Entreprise" msgid "Entreprise"
msgstr "Entretien" msgstr "Entreprise / Organisme"
#: interventions/templates/interventions/interventions_calendar.html:694 #: interventions/templates/interventions/interventions_calendar.html:694
msgid "Historique des équipes" msgid "Historique des équipes"
msgstr "Historique des envois" msgstr "Historique des équipes"
#: interventions/templates/interventions/interventions_calendar.html:702 #: interventions/templates/interventions/interventions_calendar.html:702
msgid "Entrée" msgid "Entrée"
msgstr "Entretien" msgstr "Entrée"
#: interventions/templates/interventions/interventions_calendar.html:703 #: interventions/templates/interventions/interventions_calendar.html:703
msgid "Sortie" msgid "Sortie"
@ -5518,7 +5518,7 @@ msgstr "Sortie"
#: interventions/templates/interventions/interventions_calendar.html:718 #: interventions/templates/interventions/interventions_calendar.html:718
msgid "Dernières interventions réalisées / assignées" msgid "Dernières interventions réalisées / assignées"
msgstr "Exporter les interventions sélectionnées" msgstr "Dernières interventions réalisées / assignées"
#: interventions/templates/interventions/interventions_index.html:420 #: interventions/templates/interventions/interventions_index.html:420
msgid "Choisir le type d’intervention" msgid "Choisir le type d’intervention"

View file

@ -5458,31 +5458,31 @@ msgstr "Interventie bekijken"
#: interventions/templates/interventions/interventions_calendar.html:660 #: interventions/templates/interventions/interventions_calendar.html:660
msgid "Historique du membre" msgid "Historique du membre"
msgstr "Verzendgeschiedenis" msgstr "Geschiedenis van het lid"
#: interventions/templates/interventions/interventions_calendar.html:675 #: interventions/templates/interventions/interventions_calendar.html:675
msgid "Technicien / Membre" msgid "Technicien / Membre"
msgstr "Technicus" msgstr "Technicus / Lid"
#: interventions/templates/interventions/interventions_calendar.html:681 #: interventions/templates/interventions/interventions_calendar.html:681
msgid "Entreprise" msgid "Entreprise"
msgstr "Onderhoud" msgstr "Bedrijf / Organisatie"
#: interventions/templates/interventions/interventions_calendar.html:694 #: interventions/templates/interventions/interventions_calendar.html:694
msgid "Historique des équipes" msgid "Historique des équipes"
msgstr "Verzendgeschiedenis" msgstr "Ploegengeschiedenis"
#: interventions/templates/interventions/interventions_calendar.html:702 #: interventions/templates/interventions/interventions_calendar.html:702
msgid "Entrée" msgid "Entrée"
msgstr "Onderhoud" msgstr "Instroom"
#: interventions/templates/interventions/interventions_calendar.html:703 #: interventions/templates/interventions/interventions_calendar.html:703
msgid "Sortie" msgid "Sortie"
msgstr "" msgstr "Uitstroom"
#: interventions/templates/interventions/interventions_calendar.html:718 #: interventions/templates/interventions/interventions_calendar.html:718
msgid "Dernières interventions réalisées / assignées" msgid "Dernières interventions réalisées / assignées"
msgstr "Geselecteerde interventies exporteren" msgstr "Laatste uitgevoerde / toegewezen interventies"
#: interventions/templates/interventions/interventions_index.html:420 #: interventions/templates/interventions/interventions_index.html:420
msgid "Choisir le type d’intervention" msgid "Choisir le type d’intervention"

View file

@ -639,6 +639,10 @@ class Intervention(models.Model):
while curr <= end_date: while curr <= end_date:
active_dates.add(curr) active_dates.add(curr)
curr += timedelta(days=1) curr += timedelta(days=1)
if self.begin_time:
active_dates.add(self.begin_time.date())
if self.end_time:
active_dates.add(self.end_time.date())
today = timezone.now().date() today = timezone.now().date()

View file

@ -272,13 +272,45 @@ class CalendarEventsTests(TestCase):
def test_member_history_ajax_permissions_and_data(self): def test_member_history_ajax_permissions_and_data(self):
from contracts.models import CompanyMember, CompanyMemberHistory from contracts.models import CompanyMember, CompanyMemberHistory
from interventions.models import Intervention
member = CompanyMember.objects.create(name="Jean Dupont", user=self.user) member = CompanyMember.objects.create(name="Jean Dupont", user=self.user)
member.teams.add(self.team_a) member.teams.add(self.team_a)
ten_days_ago = timezone.now().date() - timedelta(days=10)
two_days_ago = timezone.now().date() - timedelta(days=2)
CompanyMemberHistory.objects.create( CompanyMemberHistory.objects.create(
member=member, member=member,
team=self.team_a, team=self.team_a,
join_date=timezone.now().date() - timedelta(days=10) join_date=ten_days_ago,
leave_date=two_days_ago,
) )
# 1. Intervention assigned to team_a 5 days ago (during membership), finished by someone else
itv1 = Intervention.objects.create(
title='Team Task Finished by Manager',
status='finished',
maintain_type='corrective',
assigned_provider=self.company,
assigned_team=self.team_a,
begin_time=timezone.now() - timedelta(days=5),
end_time=timezone.now() - timedelta(days=5),
)
# 2. Intervention directly processed today
itv2 = Intervention.objects.create(
title='Directly Processed Task',
status='processed',
maintain_type='corrective',
assigned_provider=self.company,
assigned_member=member,
begin_time=timezone.now(),
end_time=timezone.now(),
)
# Clear active teams to simulate removed member
member.teams.clear()
url = reverse('interventions:member_history_ajax', args=[member.id]) url = reverse('interventions:member_history_ajax', args=[member.id])
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@ -286,10 +318,26 @@ class CalendarEventsTests(TestCase):
self.assertIn('member', data) self.assertIn('member', data)
self.assertEqual(data['member']['name'], "Jean Dupont") self.assertEqual(data['member']['name'], "Jean Dupont")
# Company name should still be resolved from history
self.assertEqual(data['member']['company'], self.company.name)
self.assertIn('team_history', data) self.assertIn('team_history', data)
self.assertTrue(len(data['team_history']) >= 1) self.assertTrue(len(data['team_history']) >= 1)
self.assertEqual(data['team_history'][0]['team_name'], self.team_a.name) self.assertEqual(data['team_history'][0]['team_name'], self.team_a.name)
# Interventions list should include both team task and directly processed task
self.assertIn('interventions', data)
itv_ids = [itv['id'] for itv in data['interventions']]
self.assertIn(itv1.id, itv_ids)
self.assertIn(itv2.id, itv_ids)
# Check status display and codes
itv1_entry = next(item for item in data['interventions'] if item['id'] == itv1.id)
self.assertEqual(itv1_entry['status_code'], 'finished')
itv2_entry = next(item for item in data['interventions'] if item['id'] == itv2.id)
self.assertEqual(itv2_entry['status_code'], 'processed')
def test_unassign_sans_equipe_reverts_status_to_backlog(self): def test_unassign_sans_equipe_reverts_status_to_backlog(self):
itv = Intervention.objects.create( itv = Intervention.objects.create(
title='Unassigned Test', title='Unassigned Test',

View file

@ -1371,13 +1371,11 @@ def member_history_ajax(request, member_id):
return JsonResponse({'error': _("Permission refusée.")}, status=403) return JsonResponse({'error': _("Permission refusée.")}, status=403)
member = get_object_or_404(CompanyMember.objects.prefetch_related('teams__company'), id=member_id) member = get_object_or_404(CompanyMember.objects.prefetch_related('teams__company'), id=member_id)
company_names = set(t.company.name for t in member.teams.all() if getattr(t, 'company', None))
company_str = ", ".join(sorted(company_names)) if company_names else ''
team_history_qs = ( team_history_qs = (
CompanyMemberHistory.objects CompanyMemberHistory.objects
.filter(member=member) .filter(member=member)
.select_related('team') .select_related('team__company')
.order_by('-join_date') .order_by('-join_date')
) )
team_history = [ team_history = [
@ -1390,12 +1388,51 @@ def member_history_ajax(request, member_id):
for h in team_history_qs for h in team_history_qs
] ]
company_names = set(t.company.name for t in member.teams.all() if getattr(t, 'company', None))
if not company_names:
company_names = set(h.team.company.name for h in team_history_qs if h.team and getattr(h.team, 'company', None))
if not company_names and member.user_id:
company_names = set(
uca.contract.company.name for uca in UserContractAccess.objects.filter(user_config__user=member.user)
if uca.contract and getattr(uca.contract, 'company', None)
)
company_str = ", ".join(sorted(company_names)) if company_names else '—'
# Filter assignments:
# 1. Directly assigned to this member in InterventionAssignment
assignment_q = Q(member=member)
# 2. Directly assigned / acted upon by member user on Intervention
if member.user_id:
assignment_q |= (
Q(intervention__assigned_member=member) |
Q(intervention__realized_events__event_user=member.user) |
Q(intervention__occupations__member=member)
)
# 3. Interventions assigned to teams during the periods member was in that team
team_periods_q = Q()
for h in team_history_qs:
if h.team_id and h.join_date:
tq = Q(team_id=h.team_id, date__gte=h.join_date)
if h.leave_date:
tq &= Q(date__lte=h.leave_date)
team_periods_q |= tq
for t in member.teams.all():
if not team_history_qs.filter(team=t).exists():
team_periods_q |= Q(team=t)
if team_periods_q:
assignment_q |= team_periods_q
assignments_qs = ( assignments_qs = (
InterventionAssignment.objects InterventionAssignment.objects
.filter(member=member) .filter(assignment_q)
.select_related('intervention', 'team') .select_related('intervention', 'team')
.order_by('-date', '-intervention__code', '-intervention__id')[:100] .order_by('-date', '-intervention__code', '-intervention__id')[:100]
) )
interventions_list = [ interventions_list = [
{ {
'id': a.intervention.id, 'id': a.intervention.id,
@ -1403,14 +1440,50 @@ def member_history_ajax(request, member_id):
'title': a.intervention.title, 'title': a.intervention.title,
'date': a.date.strftime('%d/%m/%Y') if a.date else '', 'date': a.date.strftime('%d/%m/%Y') if a.date else '',
'raw_date': a.date.isoformat() if a.date else '', 'raw_date': a.date.isoformat() if a.date else '',
'team_name': a.team.name if a.team else '—', 'team_name': a.team.name if a.team else (a.intervention.assigned_team.name if a.intervention.assigned_team else '—'),
'status_code': a.status or a.intervention.status, 'status_code': a.intervention.status,
'status_display': dict(STATUS_CHOICES).get(a.status or a.intervention.status, a.status or a.intervention.status), 'status_display': a.intervention.get_status_display(),
'detail_url': reverse('interventions:intervention_detail', args=[a.intervention.id]), 'detail_url': reverse('interventions:intervention_detail', args=[a.intervention.id]),
} }
for a in assignments_qs for a in assignments_qs
] ]
# Include any extra interventions directly assigned/realized by member without an InterventionAssignment
assigned_itv_ids = set(a.intervention.id for a in assignments_qs)
direct_itvs_q = Q(assigned_member=member)
if member.user_id:
direct_itvs_q |= Q(realized_events__event_user=member.user) | Q(occupations__member=member)
extra_itvs = (
Intervention.objects
.filter(direct_itvs_q)
.exclude(id__in=assigned_itv_ids)
.select_related('assigned_team')[:50]
)
for itv in extra_itvs:
itv_date = (itv.end_time or itv.begin_time or itv.planned_begin_time or itv.creation_time)
if itv_date:
itv_date_local = timezone.localtime(itv_date) if timezone.is_aware(itv_date) else itv_date
date_str = itv_date_local.strftime('%d/%m/%Y')
raw_date_str = itv_date_local.date().isoformat()
else:
date_str = ''
raw_date_str = ''
interventions_list.append({
'id': itv.id,
'code': itv.code,
'title': itv.title,
'date': date_str,
'raw_date': raw_date_str,
'team_name': itv.assigned_team.name if itv.assigned_team else '—',
'status_code': itv.status,
'status_display': itv.get_status_display(),
'detail_url': reverse('interventions:intervention_detail', args=[itv.id]),
})
interventions_list.sort(key=lambda x: (x['raw_date'], x['code'] or '', x['id']), reverse=True) interventions_list.sort(key=lambda x: (x['raw_date'], x['code'] or '', x['id']), reverse=True)
interventions_list = interventions_list[:100]
return JsonResponse({ return JsonResponse({
'member': { 'member': {