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  }