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 }