from django.conf import settings from django.db import models class PlanningState(models.Model): user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) revision = models.PositiveIntegerField(default=0) class WorkSchedule(models.Model): class Mode(models.TextChoices): PRECISE = 'precise', 'Horaires précis' DAILY_HOURS = 'daily_hours', 'Nombre d’heures par jour' WEEKLY_HOURS = 'weekly_hours', 'Nombre d’heures par semaine' user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='work_schedules') valid_from = models.DateField() cycle_weeks = models.PositiveSmallIntegerField(default=1) mode = models.CharField(max_length=16, choices=Mode.choices, default=Mode.PRECISE) day_targets = models.JSONField(default=list, blank=True) week_targets = models.JSONField(default=list, blank=True) valid_to = models.DateField(null=True, blank=True) created_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL, related_name='+') updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['-valid_from', '-pk'] constraints = [models.UniqueConstraint(fields=['user', 'valid_from'], name='schedule_user_start_unique')] class WorkScheduleSlot(models.Model): schedule = models.ForeignKey(WorkSchedule, on_delete=models.CASCADE, related_name='slots') weekday = models.PositiveSmallIntegerField() # lundi = 0 week_index = models.PositiveSmallIntegerField(default=0) start_time = models.TimeField() end_time = models.TimeField() end_day_offset = models.PositiveSmallIntegerField(default=0) class Meta: ordering = ['week_index', 'weekday', 'start_time'] class ScheduleException(models.Model): class Mode(models.TextChoices): PRECISE = 'precise', 'Horaires précis' DAILY_HOURS = 'daily_hours', 'Nombre d’heures à effectuer' class Kind(models.TextChoices): WORK = 'work', 'Horaires exceptionnels' REST = 'rest', 'Repos' ABSENCE = 'absence', 'Congé / absence' user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='schedule_exceptions') label = models.CharField(max_length=160, blank=True) kind = models.CharField(max_length=8, choices=Kind.choices, default=Kind.WORK) mode = models.CharField(max_length=16, choices=Mode.choices, default=Mode.PRECISE) target_minutes = models.PositiveSmallIntegerField(null=True, blank=True) start_date = models.DateField() end_date = models.DateField(null=True, blank=True) recurring = models.BooleanField(default=False) weekdays = models.JSONField(default=list) every_weeks = models.PositiveSmallIntegerField(default=1) created_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL, related_name='+') request = models.ForeignKey('DayChangeRequest', null=True, blank=True, on_delete=models.SET_NULL, related_name='applied_exceptions') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['-pk'] indexes = [models.Index(fields=['user', 'start_date'], name='exception_user_date_idx')] class ExceptionSlot(models.Model): exception = models.ForeignKey(ScheduleException, on_delete=models.CASCADE, related_name='slots') start_time = models.TimeField() end_time = models.TimeField() end_day_offset = models.PositiveSmallIntegerField(default=0) class Meta: ordering = ['start_time'] class DayChangeRequest(models.Model): class Kind(models.TextChoices): LEAVE = 'leave', 'Congé / absence' HOURS = 'hours', 'Modification d’horaires' MOVE = 'move', 'Déplacement de journée' SWAP = 'swap', 'Échange de journée' class Status(models.TextChoices): PENDING = 'pending', 'En attente' APPROVED = 'approved', 'Acceptée' REJECTED = 'rejected', 'Refusée' CANCELLED = 'cancelled', 'Annulée' user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='day_requests') kind = models.CharField(max_length=8, choices=Kind.choices) start_date = models.DateField() end_date = models.DateField() target_date = models.DateField(null=True, blank=True) target_user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name='received_swaps') reason = models.TextField(blank=True, max_length=2000) proposed_slots = models.JSONField(default=list) before = models.JSONField(default=list) after = models.JSONField(default=list) status = models.CharField(max_length=10, choices=Status.choices, default=Status.PENDING) reviewed_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name='+') reviewed_at = models.DateTimeField(null=True, blank=True) response = models.TextField(blank=True, max_length=2000) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['-created_at', '-pk'] indexes = [models.Index(fields=['status', 'user'], name='request_status_user_idx')]