github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/server/core/datamodel/baselocale.go (about)

     1  package datamodel
     2  
     3  import (
     4  	"sort"
     5  	"strings"
     6  	"time"
     7  
     8  	"github.com/benoitkugler/goACVE/server/core/apiserver"
     9  	rd "github.com/benoitkugler/goACVE/server/core/rawdata"
    10  )
    11  
    12  // enregistre les liens indirectes de la base de données
    13  type liens = map[int64][]int64
    14  
    15  type LiensPersonneDocuments liens
    16  type LiensAideDocuments liens
    17  type LiensCampParticipants = rd.LiensCampParticipants
    18  type LiensFactureParticipants = rd.LiensFactureParticipants
    19  type LiensCampParticipantsimples liens
    20  type LiensCampEquipiers liens
    21  type LiensParticipantAides liens
    22  type LiensFacturePaiements liens
    23  type LiensFactureMessages liens
    24  
    25  // NewBaseLocale initialise les maps.
    26  func NewBaseLocale() BaseLocale {
    27  	// à garder synchronisé
    28  	return BaseLocale{
    29  		Personnes:          make(rd.Personnes),
    30  		Camps:              make(rd.Camps),
    31  		Groupes:            make(rd.Groupes),
    32  		Sondages:           make(rd.Sondages),
    33  		Participants:       make(rd.Participants),
    34  		Equipiers:          make(rd.Equipiers),
    35  		Participantsimples: make(rd.Participantsimples),
    36  		Structureaides:     make(rd.Structureaides),
    37  		Aides:              make(rd.Aides),
    38  		Organismes:         make(rd.Organismes),
    39  		Dons:               make(rd.Dons),
    40  		Contraintes:        make(rd.Contraintes),
    41  		Documents:          make(rd.Documents),
    42  		Paiements:          make(rd.Paiements),
    43  		Factures:           make(rd.Factures),
    44  		Messages:           make(rd.Messages),
    45  		Links: rd.Links{
    46  			EquipierContraintes: make(map[int64]rd.EquipierContraintes),
    47  			ParticipantGroupe:   make(map[int64]rd.GroupeParticipant),
    48  			ParticipantEquipier: make(map[int64]rd.ParticipantEquipier),
    49  			GroupeContraintes:   make(map[int64]rd.GroupeContraintes),
    50  			CampContraintes:     make(map[int64]rd.CampContraintes),
    51  			DocumentPersonnes:   make(map[int64]rd.DocumentPersonne),
    52  			DocumentAides:       make(map[int64]rd.DocumentAide),
    53  			DocumentCamps:       make(map[int64]rd.DocumentCamp),
    54  			MessageDocuments:    make(map[int64]rd.MessageDocument),
    55  			MessageSondages:     make(map[int64]rd.MessageSondage),
    56  			MessagePlaceliberes: make(map[int64]rd.MessagePlacelibere),
    57  			MessageAttestations: make(map[int64]rd.MessageAttestation),
    58  			MessageMessages:     make(map[int64]rd.MessageMessage),
    59  			DonDonateurs:        make(map[int64]rd.DonDonateur),
    60  		},
    61  	}
    62  }
    63  
    64  // GetFacture renvoie la facture associé au participant ou au dossier
    65  // Renvoie `false` sinon
    66  func (b *BaseLocale) IdAsFacture(id rd.IId) (AccesFacture, bool) {
    67  	switch id := id.(type) {
    68  	case rd.IdFacture:
    69  		return b.NewFacture(id.Int64()), true
    70  	case rd.IdParticipant:
    71  		return b.NewParticipant(id.Int64()).GetFacture()
    72  	default:
    73  		return AccesFacture{}, false
    74  	}
    75  }
    76  
    77  func (b *BaseLocale) GetRawPersonnes() []rd.Personne {
    78  	out := make([]rd.Personne, 0, len(b.Personnes))
    79  	for _, p := range b.Personnes {
    80  		out = append(out, p)
    81  	}
    82  	return out
    83  }
    84  
    85  // getDocuments renvoie tous les documents.
    86  func (b *BaseLocale) getDocumentPersonnes() []AccesDocumentPersonne {
    87  	out := make([]AccesDocumentPersonne, 0, len(b.DocumentPersonnes))
    88  	for idDocument := range b.DocumentPersonnes {
    89  		out = append(out, AccesDocumentPersonne{b.NewDocument(idDocument)})
    90  	}
    91  	return out
    92  }
    93  
    94  // getDocuments renvoie tous les documents.
    95  func (b *BaseLocale) getDocumentAides() rd.Table {
    96  	out := make(rd.Table, 0, len(b.DocumentAides))
    97  	for idDocument := range b.DocumentAides {
    98  		out = append(out, AccesDocumentAide{b.NewDocument(idDocument)}.AsItem())
    99  	}
   100  	return out
   101  }
   102  
   103  func (b *BaseLocale) GetAllDocuments(forAide bool) rd.Table {
   104  	if forAide {
   105  		return b.getDocumentAides()
   106  	}
   107  	return DocumentsPersonnesToAcces(b.getDocumentPersonnes())
   108  }
   109  
   110  func DocumentsPersonnesToAcces(in []AccesDocumentPersonne) rd.Table {
   111  	out := make(rd.Table, len(in))
   112  	for index, doc := range in {
   113  		out[index] = doc.AsItem()
   114  	}
   115  	return out
   116  }
   117  
   118  // ResoudDocuments renvois les documents pour les personnes
   119  func (b *BaseLocale) ResoudDocumentsPersonnes() LiensPersonneDocuments {
   120  	forPersonnes := LiensPersonneDocuments{}
   121  	for _, lien := range b.DocumentPersonnes {
   122  		forPersonnes[lien.IdPersonne] = append(forPersonnes[lien.IdPersonne], lien.IdDocument)
   123  	}
   124  	return forPersonnes
   125  }
   126  
   127  // ResoudDocuments renvois les documents pour les personnes
   128  func (b *BaseLocale) ResoudDocumentsAides() LiensAideDocuments {
   129  	forAides := LiensAideDocuments{}
   130  	for _, lien := range b.DocumentAides {
   131  		forAides[lien.IdAide] = append(forAides[lien.IdAide], lien.IdDocument)
   132  	}
   133  	return forAides
   134  }
   135  
   136  // -------------------------------------- Camps --------------------------------------
   137  
   138  // GetCamps renvoie une liste des camps. `is_terminated` vaut -1,0 ou 1
   139  // pour Non, Ignoré, Oui respectivement.
   140  // Si `only_open` est true, les camps non ouverts aux inscriptions
   141  // ne sont pas sélectionnés.
   142  // Les camps sont triés par date de début.
   143  func (b *BaseLocale) GetCamps(onlyOpen bool, isTerminated rd.OptionnalBool) (out []AccesCamp) {
   144  	for id, camp := range b.Camps {
   145  		keep := true
   146  		if onlyOpen {
   147  			keep = keep && bool(camp.Ouvert)
   148  		}
   149  		switch isTerminated {
   150  		case rd.OBNon:
   151  			keep = keep && !camp.IsTerminated()
   152  		case rd.OBOui:
   153  			keep = keep && camp.IsTerminated()
   154  		}
   155  		if keep {
   156  			out = append(out, b.NewCamp(id))
   157  		}
   158  	}
   159  	sort.Slice(out, func(i, j int) bool {
   160  		return out[i].RawData().DateDebut.Time().After(out[j].RawData().DateDebut.Time())
   161  	})
   162  	return
   163  }
   164  
   165  // IsParticipantAlreadyHere vérifie si le potentiel participant n'est pas déjà présent
   166  // dans le camp.
   167  func (b *BaseLocale) IsParticipantAlreadyHere(idPersonne, idCamp int64) bool {
   168  	for _, part := range b.Participants {
   169  		if part.IdPersonne == idPersonne && part.IdCamp == idCamp {
   170  			return true
   171  		}
   172  	}
   173  	return false
   174  }
   175  
   176  // IsParticipantsimpleAlreadyHere vérifie si le potentiel participant n'est pas déjà présent
   177  // dans le camp.
   178  func (b *BaseLocale) IsParticipantsimpleAlreadyHere(idPersonne, idCamp int64) bool {
   179  	for _, part := range b.Participantsimples {
   180  		if part.IdPersonne == idPersonne && part.IdCamp == idCamp {
   181  			return true
   182  		}
   183  	}
   184  	return false
   185  }
   186  
   187  // IsEquipierAlreadyHere vérifie si le potentiel équipier n'est pas déjà présent
   188  // dans le camp.
   189  func (b *BaseLocale) IsEquipierAlreadyHere(equipier rd.Equipier) bool {
   190  	for _, part := range b.Equipiers {
   191  		if part.IdPersonne == equipier.IdPersonne && part.IdCamp == equipier.IdCamp {
   192  			return true
   193  		}
   194  	}
   195  	return false
   196  }
   197  
   198  // ---------------------- Paiements et factures ----------------------
   199  
   200  func (b BaseLocale) GetPayeursBanques() (payeurs []string, banques []string) {
   201  	mapPayeurs, mapBanques := rd.StringSet{}, rd.StringSet{}
   202  	for _, paiement := range b.Paiements {
   203  		mapPayeurs[strings.TrimSpace(string(paiement.LabelPayeur))] = true
   204  		mapBanques[strings.TrimSpace(string(paiement.NomBanque))] = true
   205  	}
   206  	for _, pers := range b.Personnes {
   207  		if pers.Age() >= 18 && !pers.IsTemporaire {
   208  			mapPayeurs[strings.TrimSpace(string(pers.Nom+" "+pers.Prenom))] = true
   209  		}
   210  	}
   211  	delete(mapPayeurs, "")
   212  	delete(mapBanques, "")
   213  	return mapPayeurs.ToList(), mapBanques.ToList()
   214  }
   215  
   216  // ResoudInscritsPaiementsAides parcourt une fois les tables
   217  // et renvoie les associations (comme ids):
   218  // 	- facture -> paiements
   219  //	- facture -> participants
   220  //	- camp -> participants
   221  // 	- participant -> aides
   222  
   223  // ResoudParticipants parcourt la table des participants et renvois
   224  // les associations facture -> participants, camp -> inscrits
   225  func (b *BaseLocale) ResoudParticipants() (LiensFactureParticipants, LiensCampParticipants) {
   226  	return b.Participants.Resoud()
   227  }
   228  
   229  // ResoudParticipantsimples parcourt la table des participants simples et renvois
   230  // les associations camp -> inscrits
   231  func (b *BaseLocale) ResoudParticipantsimples() LiensCampParticipantsimples {
   232  	out := make(LiensCampParticipantsimples, len(b.Camps))
   233  	for id, equipier := range b.Participantsimples {
   234  		out[equipier.IdCamp] = append(out[equipier.IdCamp], id)
   235  	}
   236  	return out
   237  }
   238  
   239  // ResoudEquipiers parcourt la table des equipiers et renvois
   240  // les associations camp -> inscrits
   241  func (b *BaseLocale) ResoudEquipiers() LiensCampEquipiers {
   242  	out := make(LiensCampEquipiers, len(b.Camps))
   243  	for id, equipier := range b.Equipiers {
   244  		out[equipier.IdCamp] = append(out[equipier.IdCamp], id)
   245  	}
   246  	return out
   247  }
   248  
   249  // ResoudAides parcourt la table des aides et renvois
   250  // les associations participant -> aides
   251  func (b *BaseLocale) ResoudAides() LiensParticipantAides {
   252  	participantsToA := make(LiensParticipantAides, len(b.Participants))
   253  	for id, aides := range b.Aides {
   254  		participantsToA[aides.IdParticipant] = append(participantsToA[aides.IdParticipant], id)
   255  	}
   256  	return participantsToA
   257  }
   258  
   259  // ResoudAides parcourt la table des paiements et renvois
   260  // les associations facture -> paiements
   261  func (b *BaseLocale) ResoudPaiements() LiensFacturePaiements {
   262  	facturesToPaie := make(LiensFacturePaiements, len(b.Factures))
   263  	for id, paiement := range b.Paiements {
   264  		facturesToPaie[paiement.IdFacture] = append(facturesToPaie[paiement.IdFacture], id)
   265  	}
   266  	return facturesToPaie
   267  }
   268  
   269  func (b *BaseLocale) ResoudMessages() LiensFactureMessages {
   270  	out := make(LiensFactureMessages)
   271  	for _, message := range b.Messages {
   272  		out[message.IdFacture] = append(out[message.IdFacture], message.Id)
   273  	}
   274  	return out
   275  }
   276  
   277  type financesParticipant struct {
   278  	participant   rd.Data
   279  	bilanPrixCamp BilanPrixCamp
   280  	bilanFacture  BilanFacture
   281  }
   282  
   283  const (
   284  	FinancesPNomPrenom rd.Field = iota
   285  	FinancesPPrixBase
   286  	FinancesPPrixNet
   287  	FinancesPTotalAides
   288  	FinancesPEtatPaiement
   289  )
   290  
   291  func (p financesParticipant) AsItem() rd.Item {
   292  	fields := rd.F{
   293  		FinancesPNomPrenom:    p.participant,
   294  		FinancesPPrixBase:     p.bilanPrixCamp.PrixBase,
   295  		FinancesPPrixNet:      p.bilanPrixCamp.PrixNet(),
   296  		FinancesPTotalAides:   p.bilanPrixCamp.TotalAides(),
   297  		FinancesPEtatPaiement: p.bilanFacture.StatutPaiement(),
   298  	}
   299  	bgColors := rd.MapColors{FinancesPEtatPaiement: p.bilanFacture.StatutPaiement().Color()}
   300  	return rd.Item{Fields: fields, BackgroundColors: bgColors}
   301  }
   302  
   303  // SuiviFinancier renvoie une liste de participants avec les infos financières.
   304  // Renvoie aussi le total demandé et le total des aides extérieures
   305  // Les aides et paiements invalides sont ignorés.
   306  func (b *BaseLocale) SuiviFinancier(camp AccesCamp) (rd.Table, rd.Euros, rd.Euros) {
   307  	var (
   308  		out                      rd.Table
   309  		totalAides, totalDemande rd.Euros
   310  	)
   311  	// Mise en cache pour éviter un cout quadratique
   312  	cache := b.NewCacheEtatFinancier()
   313  	_, cToParticipants := b.ResoudParticipants()
   314  	for _, acPart := range camp.GetInscrits(cToParticipants) {
   315  		bilanPrixCamp := acPart.EtatFinancier(cache.PAides, false)
   316  		totalDemande += bilanPrixCamp.PrixNet()
   317  		totalAides += bilanPrixCamp.TotalAides()
   318  
   319  		fac, hasFacture := acPart.GetFacture()
   320  		var bilanFacture BilanFacture
   321  		if hasFacture {
   322  			bilanFacture = fac.EtatFinancier(cache, false)
   323  		}
   324  
   325  		out = append(out, financesParticipant{
   326  			participant:   acPart.GetPersonne().RawData().NomPrenom(),
   327  			bilanPrixCamp: bilanPrixCamp,
   328  			bilanFacture:  bilanFacture,
   329  		}.AsItem())
   330  	}
   331  	return out, totalDemande, totalAides
   332  }
   333  
   334  // ------------------------------- Dons -------------------------------
   335  
   336  // OptionsRecuFiscal implémente l'émission de reçus fiscaux,
   337  // partagée entre le client et le serveur.
   338  type OptionsRecuFiscal struct {
   339  	Annee  int
   340  	ReEmet bool // accepte les dons déjà émis en reçu fiscaux
   341  }
   342  
   343  type RecuFiscal struct {
   344  	// Montant total
   345  	Montant rd.Euros
   346  	// Dans le cas de plusieurs dons, le mode est un des modes utilisés
   347  	Mode rd.ModePaiment
   348  	// Dans le cas de plusieurs dons, la date la plus récente est utilisée
   349  	Date time.Time
   350  }
   351  
   352  type RecusFiscaux struct {
   353  	Recus      map[int64]RecuFiscal
   354  	IdsDons    rd.Ids
   355  	NbAnnonyme int
   356  }
   357  
   358  // Select aggrège les dons et renvoie aussi le nombre de dons annonymes,
   359  // et de dons annonymes de type HelloAsso
   360  // Les dons collectifs sont ignorés.
   361  func (o OptionsRecuFiscal) Select(dons rd.Dons, liens map[int64]rd.DonDonateur) (r RecusFiscaux) {
   362  	r.Recus = map[int64]RecuFiscal{} // idDonateur -> recu
   363  	for _, don := range dons {
   364  		switch donateur := liens[don.Id].IId().(type) {
   365  		case rd.IdOrganisme:
   366  			// les dons collectifs ne sont pas concernés par les reçus fiscaux
   367  		case nil:
   368  			// don anonyme
   369  			r.NbAnnonyme++
   370  		case rd.IdPersonne:
   371  			annneOk := don.DateReception.Time().Year() == o.Annee
   372  			reEmetOk := o.ReEmet || don.RecuEmis.Time().IsZero()
   373  			if !annneOk || !reEmetOk {
   374  				continue
   375  			}
   376  			r.IdsDons = append(r.IdsDons, don.Id)
   377  			rf := r.Recus[donateur.Int64()]
   378  			rf.Montant += don.Valeur
   379  			rf.Mode = don.ModePaiement
   380  			if rf.Date.Before(don.DateReception.Time()) {
   381  				rf.Date = don.DateReception.Time()
   382  			}
   383  			r.Recus[donateur.Int64()] = rf
   384  		}
   385  	}
   386  	return r
   387  }
   388  
   389  // GetFacturesSendDocuments renvoie les dossiers ayant déjà reçu un message 'Document'
   390  // pour le séjour donné.
   391  func (b *BaseLocale) GetFacturesSendDocuments(idCamp int64) rd.Set {
   392  	var ids rd.Ids
   393  	for idMessage, complement := range b.MessageDocuments {
   394  		if complement.IdCamp == idCamp {
   395  			message := b.Messages[idMessage]
   396  			ids = append(ids, message.IdFacture)
   397  		}
   398  	}
   399  	// unicité :
   400  	return ids.AsSet()
   401  }
   402  
   403  // ------------------------------- Cleanup -------------------------------
   404  
   405  func (b *BaseLocale) ApplyIdentifie(out apiserver.IdentifiePersonneOut) {
   406  	b.Personnes[out.Personne.Id] = out.Personne
   407  	for _, doc := range out.Documents {
   408  		b.DocumentPersonnes[doc.IdDocument] = doc
   409  	}
   410  	b.CleanupDocuments(out.DeletedDocs)
   411  	delete(b.Personnes, out.DeletedPersonne)
   412  }
   413  
   414  func (b *BaseLocale) ApplyIdentifieDon(out apiserver.IdentifieDonOut) {
   415  	b.Personnes[out.Personne.Id] = out.Personne
   416  	b.Dons[out.Don.Id] = out.Don
   417  	b.DonDonateurs[out.Donateur.IdDon] = out.Donateur
   418  }
   419  
   420  func (b *BaseLocale) ApplyEditMessage(out apiserver.EditMessageOut) {
   421  	for _, m := range out.Messages {
   422  		b.Messages[m.Id] = m
   423  	}
   424  	for _, m := range out.MessageMessages {
   425  		b.MessageMessages[m.IdMessage] = m
   426  	}
   427  }
   428  
   429  func (b *BaseLocale) ApplyCreateParticipant(out apiserver.CreateParticipantOut) {
   430  	b.Participants[out.Participant.Id] = out.Participant
   431  	if out.FoundGroupe {
   432  		b.ParticipantGroupe[out.Participant.Id] = out.GroupeParticipant
   433  	}
   434  }
   435  
   436  func (b *BaseLocale) ApplyFusionneFactures(out apiserver.FusionneFacturesOut) {
   437  	for id, v := range out.Paiements {
   438  		b.Paiements[id] = v
   439  	}
   440  	for id, v := range out.Participants {
   441  		b.Participants[id] = v
   442  	}
   443  	for id, v := range out.Messages {
   444  		b.Messages[id] = v
   445  	}
   446  	for id, v := range out.Sondages {
   447  		b.Sondages[id] = v
   448  	}
   449  	delete(b.Factures, out.OldId)
   450  }
   451  
   452  func (b *BaseLocale) ApplyCreateDon(out apiserver.CreateDonIn) {
   453  	b.Dons[out.Don.Id] = out.Don
   454  	b.DonDonateurs[out.Don.Id] = out.Donateur
   455  }
   456  
   457  func (b *BaseLocale) CleanupParticipant(out apiserver.DeleteParticipantOut) {
   458  	delete(b.Participants, out.IdParticipant)
   459  	for _, aide := range out.IdsAides {
   460  		delete(b.Aides, aide)
   461  	}
   462  	for _, doc := range out.IdsDocuments {
   463  		delete(b.Documents, doc)
   464  	}
   465  	delete(b.Personnes, out.IdPersonne)
   466  	delete(b.ParticipantGroupe, out.IdParticipant)
   467  	for _, message := range out.IdsMessages {
   468  		delete(b.MessagePlaceliberes, message)
   469  		delete(b.Messages, message)
   470  	}
   471  }
   472  
   473  // CleanupDocuments enlève les documents et leurs liens
   474  func (b *BaseLocale) CleanupDocuments(ids rd.Ids) {
   475  	for _, idDocument := range ids {
   476  		delete(b.Documents, idDocument)
   477  		delete(b.DocumentPersonnes, idDocument)
   478  		delete(b.DocumentCamps, idDocument)
   479  		delete(b.DocumentAides, idDocument)
   480  	}
   481  }
   482  
   483  // DeleteFacture supprime aussi les messages associés
   484  func (b *BaseLocale) DeleteFacture(id int64) {
   485  	delete(b.Factures, id)
   486  	for _, message := range b.Messages {
   487  		if message.IdFacture == id {
   488  			delete(b.Messages, message.Id)
   489  			delete(b.MessageAttestations, message.Id)
   490  			delete(b.MessageDocuments, message.Id)
   491  			delete(b.MessageMessages, message.Id)
   492  			delete(b.MessagePlaceliberes, message.Id)
   493  			delete(b.MessageSondages, message.Id)
   494  		}
   495  	}
   496  }