feat: implement member absence tracking and visual styling in calendar interventions

This commit is contained in:
kdeterme 2026-09-02 14:28:12 +02:00
parent 61a099eb4f
commit 9702a19db8
2 changed files with 22 additions and 4 deletions

View file

@ -532,10 +532,17 @@ document.addEventListener('DOMContentLoaded', function () {
const formattedMembers = dayData.members.map(m => { const formattedMembers = dayData.members.map(m => {
const name = typeof m === 'object' && m ? m.name : m; const name = typeof m === 'object' && m ? m.name : m;
const id = typeof m === 'object' && m ? m.id : null; const id = typeof m === 'object' && m ? m.id : null;
const isAbsent = typeof m === 'object' && m ? Boolean(m.is_absent) : false;
const absenceReason = typeof m === 'object' && m ? (m.absence_reason || '') : '';
let memberNameHtml = escapeHtml(name);
if (isMemberHistoryAllowed && id) { if (isMemberHistoryAllowed && id) {
return `<a href="#" class="member-history-link text-decoration-none text-muted fw-semibold" data-member-id="${id}">${escapeHtml(name)}</a>`; memberNameHtml = `<a href="#" class="member-history-link text-decoration-none ${isAbsent ? 'text-danger' : 'text-muted fw-semibold'}" data-member-id="${id}">${escapeHtml(name)}</a>`;
} }
return escapeHtml(name); if (isAbsent) {
return `<span class="text-decoration-line-through text-danger opacity-75" title="${escapeHtml(absenceReason || gettext('Absent'))}">${memberNameHtml}</span> <span class="badge text-bg-danger p-0 px-1 font-monospace" style="font-size:0.6rem;">${escapeHtml(absenceReason || gettext('Absent'))}</span>`;
}
return memberNameHtml;
}).join(', '); }).join(', ');
membersHTML = `<div class="team-members-list text-muted small fst-italic mb-2" style="border-left: 3px solid ${teamColor};" title="${gettext('Composition de l’équipe')}"> membersHTML = `<div class="team-members-list text-muted small fst-italic mb-2" style="border-left: 3px solid ${teamColor};" title="${gettext('Composition de l’équipe')}">
<i class="bi bi-people-fill me-1" style="color: ${teamColor};"></i>${formattedMembers} <i class="bi bi-people-fill me-1" style="color: ${teamColor};"></i>${formattedMembers}

View file

@ -1506,6 +1506,11 @@ def member_history_ajax(request, member_id):
) )
company_str = ", ".join(sorted(company_names)) if company_names else '—' company_str = ", ".join(sorted(company_names)) if company_names else '—'
# 0. Récupérer les dates d'absences du membre
absent_dates = set(
CompanyMemberAbsence.objects.filter(member=member).values_list('date', flat=True)
)
# 1. Équipes et périodes valides du membre selon CompanyMemberHistory # 1. Équipes et périodes valides du membre selon CompanyMemberHistory
team_periods_q = Q() team_periods_q = Q()
for h in team_history_qs: for h in team_history_qs:
@ -1527,7 +1532,7 @@ def member_history_ajax(request, member_id):
.order_by('-date', '-intervention__id') .order_by('-date', '-intervention__id')
) )
# 3. Dédoublonnage strict par ID d'intervention # 3. Dédoublonnage strict par ID d'intervention et exclusion des jours d'absence
seen_itv_ids = set() seen_itv_ids = set()
interventions_dict = {} interventions_dict = {}
@ -1535,6 +1540,9 @@ def member_history_ajax(request, member_id):
itv = a.intervention itv = a.intervention
if not itv or itv.id in seen_itv_ids: if not itv or itv.id in seen_itv_ids:
continue continue
# Si le membre était absent ce jour-là et n'était pas nommément assigné
if a.date and a.date in absent_dates and a.member_id != member.id:
continue
seen_itv_ids.add(itv.id) seen_itv_ids.add(itv.id)
interventions_dict[itv.id] = { interventions_dict[itv.id] = {
'id': itv.id, 'id': itv.id,
@ -1564,17 +1572,20 @@ def member_history_ajax(request, member_id):
for itv in extra_itvs: for itv in extra_itvs:
if itv.id in seen_itv_ids: if itv.id in seen_itv_ids:
continue continue
seen_itv_ids.add(itv.id)
itv_date = (itv.end_time or itv.begin_time or itv.planned_begin_time or itv.creation_time) itv_date = (itv.end_time or itv.begin_time or itv.planned_begin_time or itv.creation_time)
if itv_date: if itv_date:
itv_date_local = timezone.localtime(itv_date) if timezone.is_aware(itv_date) else itv_date itv_date_local = timezone.localtime(itv_date) if timezone.is_aware(itv_date) else itv_date
itv_date_only = itv_date_local.date()
if itv_date_only in absent_dates and itv.assigned_member_id != member.id and not itv.occupations.filter(member=member).exists():
continue
date_str = itv_date_local.strftime('%d/%m/%Y') date_str = itv_date_local.strftime('%d/%m/%Y')
raw_date_str = itv_date_local.date().isoformat() raw_date_str = itv_date_local.date().isoformat()
else: else:
date_str = '' date_str = ''
raw_date_str = '' raw_date_str = ''
seen_itv_ids.add(itv.id)
interventions_dict[itv.id] = { interventions_dict[itv.id] = {
'id': itv.id, 'id': itv.id,
'code': itv.code, 'code': itv.code,