github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/server/directeurs/groupes.go (about) 1 package directeurs 2 3 import ( 4 "database/sql" 5 "errors" 6 "fmt" 7 "time" 8 9 rd "github.com/benoitkugler/goACVE/server/core/rawdata" 10 "github.com/benoitkugler/goACVE/server/core/rawdata/composites" 11 "github.com/benoitkugler/goACVE/server/shared" 12 ) 13 14 var defaultGroupeFrom = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) 15 16 const defaultPlageLength = 2 * 365 * 24 * time.Hour // 2ans 17 18 // charge et renvoie les groupes du séjour, ainsi que les assocation de contraintes 19 func loadGroupes(db rd.DB, idCamp int64) (rd.Groupes, rd.CampContraintes, rd.GroupeContraintes, error) { 20 groupes, err := rd.SelectGroupesByIdCamps(db, idCamp) 21 if err != nil { 22 return nil, nil, nil, err 23 } 24 groupeContraintes, err := rd.SelectGroupeContraintesByIdGroupes(db, groupes.Ids()...) 25 if err != nil { 26 return nil, nil, nil, err 27 } 28 campContraintes, err := rd.SelectCampContraintesByIdCamps(db, idCamp) 29 if err != nil { 30 return nil, nil, nil, err 31 } 32 return groupes, campContraintes, groupeContraintes, nil 33 } 34 35 func (rc DriverCampComplet) createGroupe(newGroupe rd.Groupe) (rd.Groupe, error) { 36 newGroupe.Nom = newGroupe.Nom.TrimSpace() 37 if newGroupe.Nom == "" { 38 return rd.Groupe{}, errors.New("Merci de nommer le groupe à créer.") 39 } 40 41 groupes, _, _, err := loadGroupes(rc.DB, rc.camp.Id) 42 if err != nil { 43 return rd.Groupe{}, err 44 } 45 46 // on crée vraiment un nouveau groupe, en allongant la plage de date 47 var lastDate time.Time 48 for _, g := range groupes { 49 if g.Plage.To.Time().After(lastDate) { 50 lastDate = g.Plage.To.Time() 51 } 52 } 53 if lastDate.IsZero() { 54 lastDate = defaultGroupeFrom 55 } 56 lastDate = lastDate.Add(24 * time.Hour) 57 newGroupe.IdCamp = rc.camp.Id 58 newGroupe.Plage = rd.Plage{From: rd.Date(lastDate), To: rd.Date(lastDate.Add(defaultPlageLength))} // 2 ans 59 60 return newGroupe.Insert(rc.DB) 61 } 62 63 func (rc DriverCampComplet) updateGroupe(groupe rd.Groupe) (rd.Groupe, error) { 64 if groupe.IdCamp != rc.camp.Id { 65 return rd.Groupe{}, fmt.Errorf("Le camp associé à ce groupe n'est pas valide (%d)", groupe.IdCamp) 66 } 67 68 // un groupe modifié par l'utilisateur a forcément un nom 69 groupe.Nom = groupe.Nom.TrimSpace() 70 if groupe.Nom == "" { 71 return rd.Groupe{}, errors.New("Merci de nommer ce groupe.") 72 } 73 74 return groupe.Update(rc.DB) 75 } 76 77 func (rc DriverCampComplet) checkGroupe(idGroupe int64) error { 78 groupes, _, _, err := loadGroupes(rc.DB, rc.camp.Id) 79 if err != nil { 80 return shared.FormatErr("Impossible de vérifier l'origine du groupe.", err) 81 } 82 if _, inSejour := groupes[idGroupe]; !inSejour { 83 return fmt.Errorf("Ce groupe (id %d) n'est pas lié au séjour !", idGroupe) 84 } 85 return nil 86 } 87 88 func (rc DriverCampComplet) deleteGroupe(idGroupe int64) error { 89 if err := rc.checkGroupe(idGroupe); err != nil { 90 return err 91 } 92 93 tx, err := rc.DB.Begin() 94 if err != nil { 95 return err 96 } 97 // on supprime les liens 98 _, err = rd.DeleteGroupeContraintesByIdGroupes(tx, idGroupe) 99 if err != nil { 100 return shared.Rollback(tx, err) 101 } 102 _, err = rd.DeleteGroupeParticipantsByIdGroupes(tx, idGroupe) 103 if err != nil { 104 return shared.Rollback(tx, err) 105 } 106 107 _, err = rd.DeleteGroupeById(tx, idGroupe) 108 if err != nil { 109 return shared.Rollback(tx, err) 110 } 111 err = tx.Commit() 112 return err 113 } 114 115 // met à jour les appartenances de chaque groupe , en une seule requête 116 // ne commit pas , ne rollback pas; 117 // si la liste est vide, ne fait rien 118 func updateIdGroupes(tx *sql.Tx, participants []groupeUpdate, idCamp int64) error { 119 if len(participants) == 0 { 120 return nil 121 } 122 123 var ( 124 idsParticipants rd.Ids 125 gps rd.GroupeParticipants 126 ) 127 for _, part := range participants { 128 idsParticipants = append(idsParticipants, part.idParticipant) 129 if part.idGroupe.Valid { 130 gps = append(gps, rd.GroupeParticipant{ 131 IdCamp: idCamp, 132 Manuel: false, 133 IdParticipant: part.idParticipant, 134 IdGroupe: part.idGroupe.Int64, 135 }) 136 } 137 } 138 139 // on commencer par supprimer les affectations courantes ... 140 _, err := rd.DeleteGroupeParticipantsByIdParticipants(tx, idsParticipants...) 141 if err != nil { 142 return err 143 } 144 145 // ... puis on ajoute les nouvelles 146 err = rd.InsertManyGroupeParticipants(tx, gps...) 147 return err 148 } 149 150 type groupeUpdate struct { 151 idParticipant int64 152 idGroupe rd.OptionnalId 153 } 154 155 // updatePlages modifie les plages des groupes donnés et met à jour les participants 156 // Si `erase` vaut `false`, les participants en mode manuel ne sont pas modifiés 157 // Renvoie le nombre de participants modifiés. 158 func (rc DriverCampComplet) updatePlages(groupes rd.Groupes, erase bool) (int, error) { 159 tx, err := rc.DB.Begin() 160 if err != nil { 161 return 0, err 162 } 163 // on commence par modifier les plages 164 for _, groupe := range groupes { 165 _, err := tx.Exec("UPDATE groupes SET plage = $1 WHERE id = $2", groupe.Plage, groupe.Id) 166 if err != nil { 167 return 0, shared.Rollback(tx, err) 168 } 169 } 170 171 // puis on s'assure d'avoir tous les groupes (mis à jour) 172 groupes, _, _, err = loadGroupes(tx, rc.camp.Id) 173 if err != nil { 174 return 0, shared.Rollback(tx, err) 175 } 176 // rc.camp.Base.Groupes = groupes 177 camp := composites.NewCampGroupes(rc.camp.RawData(), groupes) 178 179 // on calcule les nouvelles affectations 180 181 // on commence par charger les participants propres du camp ... 182 participants, err := rd.SelectParticipantsByIdCamps(tx, rc.camp.Id) 183 if err != nil { 184 return 0, shared.Rollback(tx, err) 185 } 186 // ... les affectations courantes ... 187 tmp, err := rd.SelectGroupeParticipantsByIdGroupes(tx, groupes.Ids()...) 188 if err != nil { 189 return 0, shared.Rollback(tx, err) 190 } 191 192 participantGroupe := tmp.ByIdParticipant() 193 // ... et les personnes pour les dates de naissance 194 rows, err := tx.Query(`SELECT personnes.* FROM personnes 195 JOIN participants ON participants.id_personne = personnes.id 196 WHERE participants.id = ANY($1)`, participants.Ids().AsSQL()) 197 if err != nil { 198 return 0, shared.Rollback(tx, err) 199 } 200 personnes, err := rd.ScanPersonnes(rows) 201 if err != nil { 202 return 0, shared.Rollback(tx, err) 203 } 204 205 var modifications []groupeUpdate 206 for _, part := range participants { 207 currentGroupe, has := participantGroupe[part.Id] 208 if !erase && has && currentGroupe.Manuel { 209 // le participant a déjà un groupe attribué manuellement : 210 // on n'y touche pas 211 continue 212 } 213 var currentIdGroupe, targetIdGroupe rd.OptionnalId 214 if has { 215 currentIdGroupe = rd.NewOptionnalId(currentGroupe.IdGroupe) 216 } 217 218 groupe, found := camp.TrouveGroupe(personnes[part.IdPersonne].DateNaissance) 219 if found { 220 targetIdGroupe = rd.NewOptionnalId(groupe.Id) 221 } 222 if currentIdGroupe == targetIdGroupe { 223 // le participant est déjà dans le bon groupe (au sens large): 224 // on ne fait rien. C'est plus logique pour l'utilisateur 225 continue 226 } 227 modifications = append(modifications, groupeUpdate{idParticipant: part.Id, idGroupe: targetIdGroupe}) 228 } 229 230 err = updateIdGroupes(tx, modifications, camp.Id) 231 if err != nil { 232 return 0, shared.Rollback(tx, err) 233 } 234 err = tx.Commit() 235 return len(modifications), err 236 }