feat: add team synchronization service, management command, and improve calendar header UI
This commit is contained in:
parent
da7de1ac7d
commit
fad90d91c9
4 changed files with 68 additions and 9 deletions
30
loko/contracts/management/commands/sync_teams_schedule.py
Normal file
30
loko/contracts/management/commands/sync_teams_schedule.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
from datetime import datetime
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone
|
||||
from contracts.services_teams import sync_all_members_current_teams
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Synchronise les équipes actuelles (member.teams) avec l'historique planifié pour aujourd'hui (ou une date donnée)."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--date',
|
||||
type=str,
|
||||
help="Date de synchronisation au format YYYY-MM-DD (défaut: aujourd'hui)",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
date_str = options.get('date')
|
||||
if date_str:
|
||||
try:
|
||||
target_date = datetime.strptime(date_str, '%Y-%m-%d').date()
|
||||
except ValueError:
|
||||
self.stderr.write(self.style.ERROR(f"Format de date invalide : {date_str} (attendu YYYY-MM-DD)"))
|
||||
return
|
||||
else:
|
||||
target_date = timezone.now().date()
|
||||
|
||||
self.stdout.write(f"Synchronisation des équipes pour la date : {target_date}...")
|
||||
count = sync_all_members_current_teams(target_date)
|
||||
self.stdout.write(self.style.SUCCESS(f"Synchronisation terminée avec succès pour {count} membre(s)."))
|
||||
|
|
@ -134,6 +134,23 @@ def sync_member_current_teams(member: CompanyMember, for_date: Optional[date] =
|
|||
member.teams.set(list(target_team_ids))
|
||||
|
||||
|
||||
def sync_all_members_current_teams(for_date: Optional[date] = None) -> int:
|
||||
"""
|
||||
Synchronise le champ ManyToMany member.teams pour tous les membres
|
||||
selon l'équipe active dans CompanyMemberHistory à la date donnée (aujourd'hui par défaut).
|
||||
Retourne le nombre de membres synchronisés.
|
||||
"""
|
||||
if for_date is None:
|
||||
for_date = timezone.now().date()
|
||||
|
||||
members = CompanyMember.objects.all().prefetch_related('teams')
|
||||
count = 0
|
||||
for m in members:
|
||||
sync_member_current_teams(m, for_date)
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def get_teams_composition_for_period(
|
||||
team_ids: Set[int],
|
||||
start_date: date,
|
||||
|
|
|
|||
|
|
@ -282,10 +282,10 @@
|
|||
/* Bouton de duplication rapide au bas à droite des en-têtes de jour */
|
||||
.btn-quick-duplicate {
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
right: 4px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
bottom: 2px;
|
||||
right: 2px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
|
@ -1558,8 +1558,15 @@ function renderPlanningMatrix(data) {
|
|||
const optionsTitle = "{% trans 'Options de la journée'|escapejs %}";
|
||||
const dupCustomTitle = "{% trans 'Dupliquer cette journée…'|escapejs %}";
|
||||
|
||||
let badgeHtml = '';
|
||||
if (day.is_today) {
|
||||
badgeHtml = `<span class="badge bg-primary text-white" style="font-size:0.65rem; padding: 2px 6px;">{% trans "Aujourd'hui"|escapejs %}</span>`;
|
||||
} else if (day.is_past) {
|
||||
badgeHtml = `<span class="badge bg-light text-muted border" style="font-size:0.62rem; padding: 2px 5px;"><i class="bi bi-clock-history me-1"></i>{% trans "Passé"|escapejs %}</span>`;
|
||||
}
|
||||
|
||||
th.innerHTML = `
|
||||
<div class="d-flex flex-column align-items-center justify-content-center position-relative py-1" style="min-height: 56px;">
|
||||
<div class="d-flex flex-column align-items-center justify-content-between position-relative py-1" style="min-height: 64px; height: 100%;">
|
||||
${CAN_EDIT_TEAMS ? `
|
||||
<div class="dropdown position-absolute" style="top: 0px; right: 0px;">
|
||||
<button class="btn btn-sm btn-link text-muted p-0 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" title="${optionsTitle}"></button>
|
||||
|
|
@ -1572,10 +1579,11 @@ function renderPlanningMatrix(data) {
|
|||
</ul>
|
||||
</div>` : ''}
|
||||
|
||||
<div class="small text-uppercase ${day.is_past ? 'text-muted opacity-75' : 'text-muted'}" style="font-size:0.75rem; letter-spacing: 0.5px;">${escapeHtml(day.day_short)}</div>
|
||||
<div class="fw-bold fs-6 ${day.is_past ? 'text-muted' : ''}">${escapeHtml(day.day_num)}</div>
|
||||
${day.is_today ? `<span class="badge bg-primary text-white mt-1" style="font-size:0.65rem;">{% trans "Aujourd'hui"|escapejs %}</span>` : ''}
|
||||
${day.is_past ? `<span class="badge bg-light text-muted border mt-1" style="font-size:0.62rem;"><i class="bi bi-clock-history me-1"></i>{% trans "Passé"|escapejs %}</span>` : ''}
|
||||
<div class="small text-uppercase ${day.is_past ? 'text-muted opacity-75' : 'text-muted'}" style="font-size:0.75rem; letter-spacing: 0.5px; line-height: 1.2;">${escapeHtml(day.day_short)}</div>
|
||||
<div class="fw-bold fs-6 ${day.is_past ? 'text-muted' : ''}" style="line-height: 1.2;">${escapeHtml(day.day_num)}</div>
|
||||
<div class="header-badge-slot d-flex align-items-center justify-content-center" style="min-height: 20px; width: 100%;">
|
||||
${badgeHtml}
|
||||
</div>
|
||||
|
||||
${CAN_EDIT_TEAMS ? `
|
||||
<button type="button"
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ from contracts.services_teams import (
|
|||
duplicate_day_schedule,
|
||||
duplicate_week_schedule,
|
||||
sync_member_current_teams,
|
||||
sync_all_members_current_teams,
|
||||
)
|
||||
|
||||
User = get_user_model()
|
||||
|
|
@ -54,6 +55,9 @@ def teams_index(request):
|
|||
if not user_can_view_teams(request.user):
|
||||
raise PermissionDenied("Vous n'avez pas accès à la gestion des équipes.")
|
||||
|
||||
# Synchroniser les équipes courantes pour aujourd'hui selon l'historique planifié
|
||||
sync_all_members_current_teams()
|
||||
|
||||
teams = (
|
||||
get_viewable_teams_for_user(request.user)
|
||||
.select_related("company")
|
||||
|
|
|
|||
Loading…
Reference in a new issue