feat: add weekly training schedule planning with Gantt chart and conflict detection
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
package com.astalange.core.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.time.LocalTime;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@Entity
|
||||
@Table(name = "creneau_entrainement")
|
||||
public class CreneauEntrainement {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "categorie_id", nullable = false)
|
||||
private Categorie categorie;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "terrain_id", nullable = false)
|
||||
private Terrain terrain;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
private ZoneTerrain zone;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "jour_semaine", nullable = false)
|
||||
private JourSemaine jourSemaine;
|
||||
|
||||
@Column(name = "heure_debut", nullable = false)
|
||||
private LocalTime heureDebut;
|
||||
|
||||
@Column(name = "heure_fin", nullable = false)
|
||||
private LocalTime heureFin;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "saison_id", nullable = false)
|
||||
private Saison saison;
|
||||
|
||||
@Transient
|
||||
private boolean enConflit = false;
|
||||
|
||||
@Transient
|
||||
private Set<String> categoriesEnConflit = new HashSet<>();
|
||||
|
||||
// Logic for Gantt diagram grid placement
|
||||
|
||||
public int getStartColumn() {
|
||||
if (heureDebut == null) return 3;
|
||||
// Clip to working hours 08:00 to 22:00
|
||||
LocalTime time = heureDebut;
|
||||
if (time.isBefore(LocalTime.of(8, 0))) {
|
||||
time = LocalTime.of(8, 0);
|
||||
}
|
||||
long minutes = Duration.between(LocalTime.of(8, 0), time).toMinutes();
|
||||
return (int) (minutes / 15) + 3; // +3 because col 1 is Field name, col 2 is Zone name
|
||||
}
|
||||
|
||||
public int getEndColumn() {
|
||||
if (heureFin == null) return 3;
|
||||
LocalTime time = heureFin;
|
||||
if (time.isAfter(LocalTime.of(22, 0))) {
|
||||
time = LocalTime.of(22, 0);
|
||||
}
|
||||
long minutes = Duration.between(LocalTime.of(8, 0), time).toMinutes();
|
||||
return (int) (minutes / 15) + 3;
|
||||
}
|
||||
|
||||
public int getStartRow(int fieldIndex) {
|
||||
int baseRow = 3 + (fieldIndex * 5); // Row index matching HTML layout (base starts at 3, 5 rows per field)
|
||||
if (zone == null) return baseRow;
|
||||
return switch (zone) {
|
||||
case COMPLET, DEMI_A, QUART_1 -> baseRow;
|
||||
case QUART_2 -> baseRow + 1;
|
||||
case DEMI_B, QUART_3 -> baseRow + 2;
|
||||
case QUART_4 -> baseRow + 3;
|
||||
};
|
||||
}
|
||||
|
||||
public int getEndRow(int fieldIndex) {
|
||||
int baseRow = 3 + (fieldIndex * 5);
|
||||
if (zone == null) return baseRow + 4;
|
||||
return switch (zone) {
|
||||
case QUART_1 -> baseRow + 1;
|
||||
case DEMI_A, QUART_2 -> baseRow + 2;
|
||||
case QUART_3 -> baseRow + 3;
|
||||
case DEMI_B, QUART_4, COMPLET -> baseRow + 4;
|
||||
};
|
||||
}
|
||||
|
||||
// Overlap and conflict checking logic
|
||||
|
||||
public boolean overlapsWith(CreneauEntrainement other) {
|
||||
if (this.id != null && this.id.equals(other.id)) {
|
||||
return false; // same slot
|
||||
}
|
||||
if (this.jourSemaine != other.jourSemaine) {
|
||||
return false;
|
||||
}
|
||||
if (this.terrain == null || other.terrain == null || !Objects.equals(this.terrain.getId(), other.terrain.getId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Time overlap check: start1 < end2 and start2 < end1
|
||||
boolean timeOverlap = this.heureDebut.isBefore(other.heureFin) && other.heureDebut.isBefore(this.heureFin);
|
||||
if (!timeOverlap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Spatial overlap check
|
||||
if (this.zone == null || other.zone == null) {
|
||||
return false;
|
||||
}
|
||||
Set<Integer> q1 = this.zone.getQuarters();
|
||||
Set<Integer> q2 = other.zone.getQuarters();
|
||||
return !Collections.disjoint(q1, q2);
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Categorie getCategorie() {
|
||||
return categorie;
|
||||
}
|
||||
|
||||
public void setCategorie(Categorie categorie) {
|
||||
this.categorie = categorie;
|
||||
}
|
||||
|
||||
public Terrain getTerrain() {
|
||||
return terrain;
|
||||
}
|
||||
|
||||
public void setTerrain(Terrain terrain) {
|
||||
this.terrain = terrain;
|
||||
}
|
||||
|
||||
public ZoneTerrain getZone() {
|
||||
return zone;
|
||||
}
|
||||
|
||||
public void setZone(ZoneTerrain zone) {
|
||||
this.zone = zone;
|
||||
}
|
||||
|
||||
public JourSemaine getJourSemaine() {
|
||||
return jourSemaine;
|
||||
}
|
||||
|
||||
public void setJourSemaine(JourSemaine jourSemaine) {
|
||||
this.jourSemaine = jourSemaine;
|
||||
}
|
||||
|
||||
public LocalTime getHeureDebut() {
|
||||
return heureDebut;
|
||||
}
|
||||
|
||||
public void setHeureDebut(LocalTime heureDebut) {
|
||||
this.heureDebut = heureDebut;
|
||||
}
|
||||
|
||||
public LocalTime getHeureFin() {
|
||||
return heureFin;
|
||||
}
|
||||
|
||||
public void setHeureFin(LocalTime heureFin) {
|
||||
this.heureFin = heureFin;
|
||||
}
|
||||
|
||||
public Saison getSaison() {
|
||||
return saison;
|
||||
}
|
||||
|
||||
public void setSaison(Saison saison) {
|
||||
this.saison = saison;
|
||||
}
|
||||
|
||||
public boolean isEnConflit() {
|
||||
return enConflit;
|
||||
}
|
||||
|
||||
public void setEnConflit(boolean enConflit) {
|
||||
this.enConflit = enConflit;
|
||||
}
|
||||
|
||||
public Set<String> getCategoriesEnConflit() {
|
||||
return categoriesEnConflit;
|
||||
}
|
||||
|
||||
public void setCategoriesEnConflit(Set<String> categoriesEnConflit) {
|
||||
this.categoriesEnConflit = categoriesEnConflit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.astalange.core.entity;
|
||||
|
||||
public enum JourSemaine {
|
||||
LUNDI("Lundi"),
|
||||
MARDI("Mardi"),
|
||||
MERCREDI("Mercredi"),
|
||||
JEUDI("Jeudi"),
|
||||
VENDREDI("Vendredi"),
|
||||
SAMEDI("Samedi"),
|
||||
DIMANCHE("Dimanche");
|
||||
|
||||
private final String libelle;
|
||||
|
||||
JourSemaine(String libelle) {
|
||||
this.libelle = libelle;
|
||||
}
|
||||
|
||||
public String getLibelle() {
|
||||
return libelle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.astalange.core.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "terrain")
|
||||
public class Terrain {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String nom;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getNom() {
|
||||
return nom;
|
||||
}
|
||||
|
||||
public void setNom(String nom) {
|
||||
this.nom = nom;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.astalange.core.entity;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public enum ZoneTerrain {
|
||||
COMPLET("Terrain Complet", Set.of(1, 2, 3, 4)),
|
||||
DEMI_A("Demi-terrain A", Set.of(1, 2)),
|
||||
DEMI_B("Demi-terrain B", Set.of(3, 4)),
|
||||
QUART_1("Quart 1", Set.of(1)),
|
||||
QUART_2("Quart 2", Set.of(2)),
|
||||
QUART_3("Quart 3", Set.of(3)),
|
||||
QUART_4("Quart 4", Set.of(4));
|
||||
|
||||
private final String libelle;
|
||||
private final Set<Integer> quarters;
|
||||
|
||||
ZoneTerrain(String libelle, Set<Integer> quarters) {
|
||||
this.libelle = libelle;
|
||||
this.quarters = quarters;
|
||||
}
|
||||
|
||||
public String getLibelle() {
|
||||
return libelle;
|
||||
}
|
||||
|
||||
public Set<Integer> getQuarters() {
|
||||
return quarters;
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.astalange.core.repository;
|
||||
|
||||
import com.astalange.core.entity.CreneauEntrainement;
|
||||
import com.astalange.core.entity.JourSemaine;
|
||||
import com.astalange.core.entity.Saison;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface CreneauEntrainementRepository extends JpaRepository<CreneauEntrainement, Long> {
|
||||
|
||||
@Query("SELECT c FROM CreneauEntrainement c JOIN FETCH c.categorie JOIN FETCH c.terrain WHERE c.saison = :saison")
|
||||
List<CreneauEntrainement> findBySaisonFetch(@Param("saison") Saison saison);
|
||||
|
||||
@Query("SELECT c FROM CreneauEntrainement c JOIN FETCH c.categorie JOIN FETCH c.terrain WHERE c.saison = :saison AND c.jourSemaine = :jourSemaine")
|
||||
List<CreneauEntrainement> findBySaisonAndJourSemaineFetch(@Param("saison") Saison saison, @Param("jourSemaine") JourSemaine jourSemaine);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.astalange.core.repository;
|
||||
|
||||
import com.astalange.core.entity.Terrain;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface TerrainRepository extends JpaRepository<Terrain, Long> {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
-- V10__add_terrains_and_schedules.sql
|
||||
|
||||
-- 1. Table des terrains
|
||||
CREATE TABLE terrain (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
nom VARCHAR(255) NOT NULL
|
||||
);
|
||||
|
||||
-- Insertion des deux terrains par défaut
|
||||
INSERT INTO terrain (nom) VALUES ('Terrain Vert');
|
||||
INSERT INTO terrain (nom) VALUES ('Terrain Synthétique');
|
||||
|
||||
-- 2. Table des créneaux d'entraînement
|
||||
CREATE TABLE creneau_entrainement (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
categorie_id BIGINT NOT NULL,
|
||||
terrain_id BIGINT NOT NULL,
|
||||
zone VARCHAR(50) NOT NULL,
|
||||
jour_semaine VARCHAR(50) NOT NULL,
|
||||
heure_debut TIME NOT NULL,
|
||||
heure_fin TIME NOT NULL,
|
||||
saison_id BIGINT NOT NULL,
|
||||
CONSTRAINT fk_creneau_categorie FOREIGN KEY (categorie_id) REFERENCES categorie(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_creneau_terrain FOREIGN KEY (terrain_id) REFERENCES terrain(id),
|
||||
CONSTRAINT fk_creneau_saison FOREIGN KEY (saison_id) REFERENCES saison(id) ON DELETE CASCADE
|
||||
);
|
||||
Reference in New Issue
Block a user