github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/server/core/rawdata/properties.go (about) 1 package rawdata 2 3 import ( 4 "fmt" 5 "path" 6 "time" 7 ) 8 9 const CampDeltaTerminated = 45 // jours 10 11 const ( 12 mcMail Field = iota 13 mcNom 14 mcPrenom 15 ) 16 17 var HeadersMails = []Header{ 18 {Field: mcMail, Label: "Mail"}, 19 {Field: mcNom, Label: "Nom"}, 20 {Field: mcPrenom, Label: "Prénom"}, 21 } 22 23 // Avant renvoie true si le jour et le mois de date1 sont avant (au sens large) le jour et le mois de date2. 24 // Ignore les années. Prend en compte le cas du 29 fév. 25 func Avant(d1 Date, d2 Date) bool { 26 t1, t2 := time.Time(d1), time.Time(d2) 27 if t1.Month() < t2.Month() { 28 return true 29 } 30 if t1.Month() > t2.Month() { 31 return false 32 } 33 return t1.Day() <= t2.Day() 34 } 35 36 // Pour trier par âge manière naturelle, l'âge ne suffit pour distinguer les participants 37 // de même âge. 38 type AgeDate struct { 39 age int 40 date Date 41 } 42 43 func (a AgeDate) String() string { 44 return Int(a.age).String() 45 } 46 47 func (a AgeDate) Sortable() string { 48 // en fait, il suffit de retourner la date de naissance ici 49 return a.date.Sortable() 50 } 51 52 func (a AgeDate) Age() int { 53 return a.age 54 } 55 56 // CalculeAge renvoie l'age qu'aura une personne née le `dateNaissance` 57 // au jour `now` 58 func CalculeAge(dateNaissance Date, now time.Time) AgeDate { 59 if dateNaissance.Time().IsZero() { 60 return AgeDate{} 61 } 62 63 if now.IsZero() { 64 now = time.Now() 65 } 66 67 years := now.Year() - dateNaissance.Time().Year() 68 if Avant(dateNaissance, Date(now)) { 69 return AgeDate{age: years, date: dateNaissance} 70 } 71 return AgeDate{age: years - 1, date: dateNaissance} 72 } 73 74 // ----------------------------------- Personne ----------------------------------- // 75 // BasePersonne stocke les champs comparés lors du rapprochement 76 // des inscriptions, des équipiers ou des donateurs 77 type BasePersonne struct { 78 Nom String `json:"nom"` 79 NomJeuneFille String `json:"nom_jeune_fille"` 80 Prenom String `json:"prenom"` 81 DateNaissance Date `json:"date_naissance"` 82 VilleNaissance String `json:"ville_naissance"` 83 DepartementNaissance Departement `json:"departement_naissance"` 84 Sexe Sexe `json:"sexe"` 85 Tels Tels `json:"tels"` 86 Mail String `json:"mail"` 87 Adresse String `json:"adresse"` 88 CodePostal String `json:"code_postal"` 89 Ville String `json:"ville"` 90 Pays String `json:"pays"` 91 SecuriteSociale String `json:"securite_sociale"` 92 Profession String `json:"profession"` 93 Etudiant Bool `json:"etudiant"` 94 Fonctionnaire Bool `json:"fonctionnaire"` 95 } 96 97 func (r BasePersonne) FPrenom() string { 98 return FormatPrenom(r.Prenom) 99 } 100 101 func (r BasePersonne) FNom() string { 102 return r.Nom.ToUpper() 103 } 104 105 // NomPrenom renvoie NOM Prenom 106 func (r BasePersonne) NomPrenom() String { 107 return String(r.FNom() + " " + r.FPrenom()) 108 } 109 110 func (r BasePersonne) MailItem() Item { 111 return Item{Fields: F{ 112 mcNom: String(r.FNom()), 113 mcPrenom: String(r.FPrenom()), 114 mcMail: r.Mail, 115 }} 116 } 117 118 func (r BasePersonne) AgeDate() AgeDate { 119 dn := r.DateNaissance 120 if dn.Time().IsZero() { 121 return AgeDate{} 122 } 123 return CalculeAge(dn, time.Time{}) 124 } 125 126 func (r BasePersonne) Age() int { 127 return r.AgeDate().Age() 128 } 129 130 func (r BasePersonne) ToDestinataire() Destinataire { 131 return Destinataire{ 132 NomPrenom: r.NomPrenom(), 133 Adresse: r.Adresse, 134 CodePostal: r.CodePostal, 135 Ville: r.Ville, 136 Sexe: r.Sexe, 137 } 138 } 139 140 // Coordonnees selectionne les champs des coordonnées. 141 func (r BasePersonne) Coordonnees() Coordonnees { 142 return Coordonnees{ 143 Tels: r.Tels, 144 Mail: r.Mail, 145 Adresse: r.Adresse, 146 CodePostal: r.CodePostal, 147 Ville: r.Ville, 148 Pays: r.Pays, 149 } 150 } 151 152 // ----------------------------------- Camp ----------------------------------- // 153 154 // Label renvoie une description courte 155 func (c Camp) Label() String { 156 return String(fmt.Sprintf("%s %d", c.Nom, time.Time(c.DateDebut).Year())) 157 } 158 159 // Description renvoi une description détaillée, 160 // au format HTML 161 func (c Camp) Description() string { 162 return fmt.Sprintf(`Lieu : <b>%s</b> ; Age: de <b>%d à %d</b> ans`, c.Lieu, c.AgeMin, c.AgeMax) 163 } 164 165 // Periode renvoie la période du camp (cf `PERIODES`) 166 func (c Camp) Periode() String { 167 month := time.Time(c.DateDebut).Month() 168 switch month { 169 case 7, 8: 170 return PERIODES.ETE 171 case 9, 10, 11: 172 return PERIODES.AUTOMNE 173 case 12, 1, 2, 3: 174 return PERIODES.HIVER 175 case 4, 5, 6: 176 return PERIODES.PRINTEMPS 177 default: 178 return "" 179 } 180 } 181 182 func (c Camp) ColorPeriode() Color { 183 switch c.Periode() { 184 case PERIODES.ETE: 185 return RGBA{R: 45, G: 185, B: 187, A: 200} 186 case PERIODES.PRINTEMPS: 187 return RGBA{R: 170, G: 228, B: 62, A: 200} 188 case PERIODES.AUTOMNE: 189 return RGBA{R: 173, G: 116, B: 30, A: 200} 190 case PERIODES.HIVER: 191 return RGBA{R: 203, G: 199, B: 193, A: 200} 192 default: 193 return nil 194 } 195 } 196 197 // Duree renvoi le nombre de jours du camp, ou 0 si les dates sont manquantes 198 func (c Camp) Duree() int { 199 if c.DateDebut.Time().IsZero() || c.DateFin.Time().IsZero() { 200 return 0 201 } 202 return Plage{From: c.DateDebut, To: c.DateFin}.NbJours() 203 } 204 205 func (c Camp) Annee() Int { 206 return Int(c.DateDebut.Time().Year()) 207 } 208 209 // IsTerminated renvoie `true` si le camp est 210 // passé d'au moins `CampDeltaTerminated`. 211 // Un camp sans date de fin est considéré comme terminé. 212 func (c Camp) IsTerminated() bool { 213 dateFin := time.Time(c.DateFin) 214 if dateFin.IsZero() { 215 return true 216 } 217 return time.Now().After(dateFin.AddDate(0, 0, CampDeltaTerminated)) 218 } 219 220 // CheckEnvoisLock renvoie une erreur si l'envoi des documents est encore verouillé 221 func (c Camp) CheckEnvoisLock() error { 222 if c.Envois.Locked { 223 return fmt.Errorf("L'envoi des documents du séjour %s est encore verrouillé.", c.Label()) 224 } 225 return nil 226 } 227 228 // AgeDebutCamp renvoie l'âge qu'aura `personne` au premier jour 229 // du séjour. 230 func (c Camp) AgeDebutCamp(personne BasePersonne) AgeDate { 231 dateArrivee := c.DateDebut 232 if dateArrivee.Time().IsZero() { 233 return personne.AgeDate() 234 } 235 return CalculeAge(personne.DateNaissance, dateArrivee.Time()) 236 } 237 238 // AgeFinCamp renvoie l'âge qu'aura `personne` au dernier jour 239 // du séjour. 240 func (c Camp) AgeFinCamp(personne BasePersonne) AgeDate { 241 dateDepart := c.DateFin 242 if dateDepart.Time().IsZero() { 243 return personne.AgeDate() 244 } 245 return CalculeAge(personne.DateNaissance, dateDepart.Time()) 246 } 247 248 // HasAgeValide renvoie le statut correspondant aux âges min et max du séjour 249 // Seul le camp DateNaissance de `personne` est utilisé. 250 func (c Camp) HasAgeValide(personne BasePersonne) (min StatutAttente, max StatutAttente) { 251 ageDebut, ageFin := Int(c.AgeDebutCamp(personne).Age()), Int(c.AgeFinCamp(personne).Age()) 252 invalideDataDebut := personne.DateNaissance.Time().IsZero() || c.DateDebut.Time().IsZero() 253 invalideDataFin := personne.DateNaissance.Time().IsZero() || c.DateFin.Time().IsZero() 254 255 isMaxValide := func() StatutAttente { 256 if c.AgeMax == 0 { // Pas de critère > pas de dates. 257 return Inscrit 258 } 259 if invalideDataDebut { // impossible de calculer l'age 260 return Attente 261 } 262 if ageDebut > c.AgeMax { 263 return Attente 264 } 265 return Inscrit 266 } 267 268 isMinValide := func() StatutAttente { 269 if c.AgeMin == 0 { // Pas de critère > pas de dates. 270 return Inscrit 271 } 272 if invalideDataFin { // impossible de calculer l'age 273 return Attente 274 } 275 if ageFin < c.AgeMin { 276 return Attente 277 } 278 return Inscrit 279 } 280 min, max = isMinValide(), isMaxValide() 281 // cas particulier pour l'âge de 6 ans : 282 // pour respecter la loi on est obligé de refuser 283 if c.AgeMin == 6 && ageFin < c.AgeMin { 284 min = Refuse 285 } 286 return min, max 287 } 288 289 func (r Aide) Montant() Montant { 290 return Montant{ 291 parJour: bool(r.ParJour), 292 valeur: r.Valeur, 293 } 294 } 295 296 func (r Don) Label() string { 297 return fmt.Sprintf("montant <b>%s</b>, reçu le <i>%s</i>", r.Valeur.String(), r.DateReception.String()) 298 } 299 300 func (f Facture) UrlEspacePerso(host string) string { 301 if f.Key == "" { 302 return "" 303 } 304 return path.Join(host, string(f.Key)) 305 } 306 307 // Description renvoie une description et le montant, au format HTML 308 func (r Paiement) Description() (string, string) { 309 var payeur string 310 if r.IsAcompte { 311 payeur = fmt.Sprintf("Acompte de <i>%s</i> au %s", r.LabelPayeur, r.DateReglement.String()) 312 } else if r.IsRemboursement { 313 payeur = fmt.Sprintf("Remboursement au %s", r.DateReglement.String()) 314 } else { 315 payeur = fmt.Sprintf("Paiement de <i>%s</i> au %s", r.LabelPayeur, r.DateReglement.String()) 316 } 317 montant := fmt.Sprintf("<i>%s</i>", r.Valeur.String()) 318 if r.IsRemboursement { 319 montant = "<b>-</b> " + montant 320 } 321 prefix := "" 322 if r.IsInvalide { 323 prefix = "<b>Invalide</b> " 324 montant = fmt.Sprintf("(%s)", montant) 325 } 326 return prefix + payeur, montant 327 } 328 329 // Permissions représente les droites sur chaque module, 330 // qui interprète l'entier fourni. 0 signifie non utilisation. 331 type Modules struct { 332 Personnes int `json:"personnes,omitempty"` 333 Camps int `json:"camps,omitempty"` 334 Inscriptions int `json:"inscriptions,omitempty"` 335 SuiviCamps int `json:"suivi_camps,omitempty"` 336 SuiviDossiers int `json:"suivi_dossiers,omitempty"` 337 Paiements int `json:"paiements,omitempty"` 338 Aides int `json:"aides,omitempty"` 339 Equipiers int `json:"equipiers,omitempty"` 340 Dons int `json:"dons,omitempty"` 341 } 342 343 func (m Modules) ToReadOnly() Modules { 344 var out Modules 345 if m.Personnes > 0 { 346 out.Personnes = 1 347 } 348 if m.Camps > 0 { 349 out.Camps = 1 350 } 351 if m.Inscriptions > 0 { 352 out.Inscriptions = 1 353 } 354 if m.SuiviCamps > 0 { 355 out.SuiviCamps = 1 356 } 357 if m.SuiviDossiers > 0 { 358 out.SuiviDossiers = 1 359 } 360 if m.Paiements > 0 { 361 out.Paiements = 1 362 } 363 if m.Aides > 0 { 364 out.Aides = 1 365 } 366 if m.Equipiers > 0 { 367 out.Equipiers = 1 368 } 369 if m.Dons > 0 { 370 out.Dons = 1 371 } 372 return out 373 } 374 375 func (c Contraintes) FindBuiltin(categorie BuiltinContrainte) Contrainte { 376 for _, ct := range c { 377 if ct.Builtin == categorie { 378 return ct 379 } 380 } 381 return Contrainte{} 382 } 383 384 type Links struct { 385 EquipierContraintes map[int64]EquipierContraintes // id equipier -> 386 ParticipantGroupe map[int64]GroupeParticipant // idParticipant -> groupe (optionnel) 387 ParticipantEquipier map[int64]ParticipantEquipier // idParticipant -> idAnimateur (optionnel) 388 GroupeContraintes map[int64]GroupeContraintes // idGroupe -> ids contraintes 389 CampContraintes map[int64]CampContraintes // idCamp -> ids contraintes 390 DocumentPersonnes map[int64]DocumentPersonne // idDocument-> 391 DocumentAides map[int64]DocumentAide // idDocument -> 392 DocumentCamps map[int64]DocumentCamp // idDocument -> 393 MessageDocuments map[int64]MessageDocument // idMessage -> 394 MessageSondages map[int64]MessageSondage // idMessage -> 395 MessagePlaceliberes map[int64]MessagePlacelibere // idMessage -> 396 MessageAttestations map[int64]MessageAttestation // idMessage -> 397 MessageMessages map[int64]MessageMessage // idMessage -> 398 DonDonateurs map[int64]DonDonateur // idDon -> 399 } 400 401 type TargetDocument interface { 402 UpdateLinks(links *Links) 403 } 404 405 func (dp DocumentPersonne) UpdateLinks(links *Links) { 406 links.DocumentPersonnes[dp.IdDocument] = dp 407 } 408 func (da DocumentAide) UpdateLinks(links *Links) { 409 links.DocumentAides[da.IdDocument] = da 410 } 411 412 // ParProprietaire attribue chaque document à son propriétaire. 413 // Seuls les documents associés à des personnes sont considérés. 414 func (d Documents) ParProprietaire(targets DocumentPersonnes) map[int64][]Document { 415 mapDocs := map[int64][]Document{} 416 for _, target := range targets { 417 mapDocs[target.IdPersonne] = append(mapDocs[target.IdPersonne], d[target.IdDocument]) 418 } 419 return mapDocs 420 } 421 422 // AsIds renvoie les ids des contraintes 423 func (eqcts EquipierContraintes) AsIds() Ids { 424 var out Ids 425 for _, eqc := range eqcts { 426 out = append(out, eqc.IdContrainte) 427 } 428 return out 429 } 430 431 type LiensCampParticipants map[int64][]int64 432 type LiensFactureParticipants map[int64][]int64 433 434 // Resoud parcourt la table des participants et renvois 435 // les associations facture -> participants, camp -> inscrits; 436 // `groupes` n'influe que sur les liens camp -> inscrits : il est possible de 437 // passer `nil` si on a besoin uniquement des liens factures -> participants 438 func (pts Participants) Resoud() (LiensFactureParticipants, LiensCampParticipants) { 439 factureToParticipants := make(LiensFactureParticipants) 440 campsToParticipants := make(LiensCampParticipants) 441 for id, participant := range pts { 442 idFac := participant.IdFacture 443 if idFac.Valid { 444 factureToParticipants[idFac.Int64] = append(factureToParticipants[idFac.Int64], id) 445 } 446 idCamp := participant.IdCamp 447 campsToParticipants[idCamp] = append(campsToParticipants[idCamp], id) 448 } 449 return factureToParticipants, campsToParticipants 450 } 451 452 func (dest DestinatairesOptionnels) Index(i int) (Destinataire, error) { 453 if i >= len(dest) { 454 return Destinataire{}, fmt.Errorf("Index de destinataire %d invalide", i) 455 } 456 return dest[i], nil 457 } 458 459 func (eqs Equipiers) FindDirecteur() (Equipier, bool) { 460 for _, part := range eqs { 461 if part.Roles.Is(RDirecteur) { 462 return part, true 463 } 464 } 465 return Equipier{}, false 466 } 467 468 // FindLettre renvoie le premier lien vers une lettre, s'il existe 469 func (docs DocumentCamps) FindLettre() (DocumentCamp, bool) { 470 for _, doc := range docs { 471 if doc.IsLettre { 472 return doc, true 473 } 474 } 475 return DocumentCamp{}, false 476 } 477 478 // IId renvois un IdPersonne ou un IdOrganisme ou nil 479 func (d DonDonateur) IId() IId { 480 if d.IdPersonne.Valid { 481 return IdPersonne(d.IdPersonne.Int64) 482 } else if d.IdOrganisme.Valid { 483 return IdOrganisme(d.IdOrganisme.Int64) 484 } else { 485 return nil 486 } 487 }