github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/client/controllers/cont_suivi_dossiers_taches.go (about) 1 package controllers 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "net/http" 8 "time" 9 10 "github.com/benoitkugler/goACVE/server/core/apiserver" 11 12 dm "github.com/benoitkugler/goACVE/server/core/datamodel" 13 rd "github.com/benoitkugler/goACVE/server/core/rawdata" 14 ) 15 16 // Ce fichier définit les différents actions 17 // liées au suivi d'un dossier. 18 19 // AjoutePaiement ajoute le paiement au dossier courant. 20 func (c *SuiviDossiers) AjoutePaiement(paiement rd.Paiement) { 21 if c.Etat.FactureCurrent == nil { 22 c.main.ShowError(errors.New("Aucun dossier courant !")) 23 return 24 } 25 c.main.Controllers.Paiements.CreePaiement(paiement) 26 } 27 28 // ------------------- Communication ------------------------------------------ 29 30 func (c *SuiviDossiers) EnvoiMessageCentre(fac dm.AccesFacture, contenu rd.String) { 31 if !c.Onglet.ConfirmeMail(fac, rd.MCentre) { 32 c.main.ShowStandard("Envoi annulé", false) 33 return 34 } 35 36 params := apiserver.NotifieMessageIn{IdFacture: fac.Id, Contenu: contenu} 37 38 job := func() (interface{}, error) { 39 var out apiserver.NotifieMessageOut 40 err := requete(apiserver.UrlNotifieMessagePerso, http.MethodPut, params, &out) 41 return out, err 42 } 43 onSuccess := func(_out interface{}) { 44 out := _out.(apiserver.NotifieMessageOut) 45 c.Base.Messages[out.Message.Id] = out.Message 46 c.Base.MessageMessages[out.MessageMessage.IdMessage] = out.MessageMessage 47 c.main.ShowStandard("Message envoyé avec succès.", false) 48 c.Reset() 49 } 50 51 c.main.ShowStandard("Envoi du message...", true) 52 c.main.Background.Run(job, onSuccess) 53 } 54 55 func (c *SuiviDossiers) EditMessageCentre(idMessages rd.Ids, contenu rd.String) { 56 params := apiserver.EditMessageIn{IdMessages: idMessages, Contenu: contenu} 57 job := func() (interface{}, error) { 58 var out apiserver.EditMessageOut 59 err := requete(apiserver.UrlNotifieMessagePerso, http.MethodPost, params, &out) 60 return out, err 61 } 62 onSuccess := func(_out interface{}) { 63 out := _out.(apiserver.EditMessageOut) 64 c.Base.ApplyEditMessage(out) 65 c.main.ShowStandard("Message(s) modifié(s) avec succès.", false) 66 c.Reset() 67 } 68 69 c.main.ShowStandard("Modifications du message...", true) 70 c.main.Background.Run(job, onSuccess) 71 } 72 73 func (c *SuiviDossiers) ToggleMessageVu(idMessage int64, vu bool) { 74 params := c.Base.Messages[idMessage] 75 params.Vu = vu 76 job := func() (interface{}, error) { 77 var out rd.Message 78 err := requete(apiserver.UrlMessages, http.MethodPost, params, &out) 79 return out, err 80 } 81 onSuccess := func(_out interface{}) { 82 out := _out.(rd.Message) 83 c.Base.Messages[out.Id] = out 84 c.main.ShowStandard("Message modifié avec succès.", false) 85 c.Reset() 86 } 87 88 c.main.ShowStandard("Modifications du message...", true) 89 c.main.Background.Run(job, onSuccess) 90 } 91 92 func (c *SuiviDossiers) SupprimeMessages(idMessages rd.Ids) { 93 if !c.Onglet.ConfirmeSupprimeMessages(len(idMessages)) { 94 c.main.ShowStandard("Suppression annulée", false) 95 return 96 } 97 98 job := func() (interface{}, error) { 99 var out rd.Messages 100 err := requete(apiserver.UrlDeleteMessages, http.MethodPost, idMessages, &out) 101 return out, err 102 } 103 onSuccess := func(_out interface{}) { 104 out := _out.(rd.Messages) 105 for _, message := range out { 106 c.Base.Messages[message.Id] = message 107 delete(c.Base.MessageAttestations, message.Id) 108 delete(c.Base.MessageDocuments, message.Id) 109 delete(c.Base.MessageSondages, message.Id) 110 delete(c.Base.MessagePlaceliberes, message.Id) 111 delete(c.Base.MessageMessages, message.Id) 112 } 113 c.main.ShowStandard("Message(s) supprimé(s) avec succès.", false) 114 c.Reset() 115 } 116 117 c.main.ShowStandard("Suppression du message...", true) 118 c.main.Background.Run(job, onSuccess) 119 } 120 121 func (c *SuiviDossiers) envoiSimpleMessage(fac dm.AccesFacture, messageKind rd.MessageKind) { 122 if !c.Onglet.ConfirmeMail(fac, messageKind) { 123 c.main.ShowStandard("Envoi annulé", false) 124 return 125 } 126 127 params := apiserver.NotifieSimple{IdFacture: fac.Id, Kind: messageKind} 128 129 job := func() (interface{}, error) { 130 var out rd.Message 131 err := requete(apiserver.UrlNotifieMessage, http.MethodPost, params, &out) 132 return out, err 133 } 134 onSuccess := func(_out interface{}) { 135 out := _out.(rd.Message) 136 c.Base.Messages[out.Id] = out 137 c.main.ShowStandard("Message envoyé avec succès.", false) 138 c.Reset() 139 } 140 141 c.main.ShowStandard("Envoi du message...", true) 142 c.main.Background.Run(job, onSuccess) 143 } 144 145 // EnvoiAccuseReception envoie un mail de notification de manière ASYNCHRONE 146 func (c *SuiviDossiers) EnvoiAccuseReception(fac dm.AccesFacture) { 147 c.envoiSimpleMessage(fac, rd.MAccuseReception) 148 } 149 150 func (c *SuiviDossiers) EnvoiFacture(fac dm.AccesFacture) { 151 c.envoiSimpleMessage(fac, rd.MFacture) 152 } 153 154 // EnvoiPlaceLiberee envoie un mail et modifie le rôle 155 func (c *SuiviDossiers) EnvoiPlaceLiberee(fac dm.AccesFacture, idParticipant int64, newStatut rd.StatutAttente) { 156 if !c.Onglet.ConfirmeMail(fac, rd.MPlaceLiberee) { 157 c.main.ShowStandard("Envoi annulé", false) 158 return 159 } 160 161 msg := "Envoi d'un mail de notification..." 162 if newStatut == rd.Inscrit { 163 msg = "Passage en liste principale..." 164 } 165 params := apiserver.NotifiePlaceLibereeIn{ 166 IdParticipant: idParticipant, 167 NewStatut: newStatut, 168 } 169 job := func() (interface{}, error) { 170 var out apiserver.NotifiePlaceLibereeOut 171 err := requete(apiserver.UrlNotifiePlaceLiberee, http.MethodPost, params, &out) 172 return out, err 173 } 174 onSuccess := func(_out interface{}) { 175 out := _out.(apiserver.NotifiePlaceLibereeOut) 176 c.Base.Participants[out.Participant.Id] = out.Participant 177 c.Base.Messages[out.Message.Id] = out.Message 178 c.Base.MessagePlaceliberes[out.Details.IdMessage] = out.Details 179 c.main.ShowStandard("Mail envoyé avec succès.", false) 180 c.main.ResetAllControllers() 181 } 182 183 c.main.ShowStandard(msg, true) 184 c.main.Background.Run(job, onSuccess) 185 } 186 187 func (c *SuiviDossiers) envoiAttestation(fac dm.AccesFacture, kind rd.MessageKind) { 188 if !c.Onglet.ConfirmeMail(fac, kind) { 189 c.main.ShowStandard("Envoi annulé", false) 190 return 191 } 192 193 params := apiserver.NotifieAttestationIn{IdFacture: fac.Id, Kind: kind} 194 195 job := func() (interface{}, error) { 196 var out apiserver.NotifieAttestationOut 197 err := requete(apiserver.UrlNotifieAttestation, http.MethodPost, params, &out) 198 return out, err 199 } 200 onSuccess := func(_out interface{}) { 201 out := _out.(apiserver.NotifieAttestationOut) 202 c.Base.Messages[out.Message.Id] = out.Message 203 c.Base.MessageAttestations[out.MessageAttestation.IdMessage] = out.MessageAttestation 204 c.main.ShowStandard("Attestation envoyée avec succès.", false) 205 c.Reset() 206 } 207 208 c.main.ShowStandard("Envoi du message...", true) 209 c.main.Background.Run(job, onSuccess) 210 } 211 212 func (c *SuiviDossiers) EnvoiAttestationPresence(fac dm.AccesFacture) { 213 for _, part := range fac.GetDossiers(nil) { 214 if time.Now().Before(part.GetPresence().To.Time()) { 215 msg := fmt.Sprintf("%s n'a pas encore terminé son séjour.", part.GetPersonne().RawData().NomPrenom()) 216 c.main.ShowError(errors.New(msg)) // on continue quand même 217 break 218 } 219 } 220 c.envoiAttestation(fac, rd.MAttestationPresence) 221 } 222 223 func (c *SuiviDossiers) EnvoiFactureAcquittee(fac dm.AccesFacture) { 224 bilan := fac.EtatFinancier(dm.CacheEtatFinancier{}, false) 225 if !bilan.IsAcquitte() { 226 msg := "Une facture acquittée est demandée, mais les paiements sont insuffisants." 227 c.main.ShowError(errors.New(msg)) // on continue quand même 228 } 229 c.envoiAttestation(fac, rd.MFactureAcquittee) 230 } 231 232 type streamHandler struct { 233 idsDossiers rd.Ids // dossiers concernés 234 base *dm.BaseLocale 235 counter int // index du dossier courant 236 errors []DossierError // enregistre les erreurs 237 238 onError func(int, string) 239 } 240 241 // permet de suivre l'envoi de plusieurs messages 242 type envoiMessagesHandler struct { 243 streamHandler 244 onSuccess func(int, apiserver.NotifieManyOut) 245 } 246 247 // permet de suivre l'envoi des documents 248 type envoiDocsHandler struct { 249 streamHandler 250 onSuccess func(int, apiserver.NotifieDocumentsOut) 251 } 252 253 // permet de suivre l'envoi des sondages 254 type envoiSondagesHandler struct { 255 streamHandler 256 onSuccess func(int, apiserver.NotifieSondagesOut) 257 } 258 259 // out est la cible (spécialisée), qui doit être un pointer 260 func (e *streamHandler) onWrite(p []byte, out interface{}) (succeed bool, nb int, err error) { 261 tmp := apiserver.StreamOut{Data: out} 262 err = json.Unmarshal(p, &tmp) 263 if err != nil { 264 // erreur inatendue (et non erreur sur l'envoi) 265 return false, 0, err 266 } 267 268 if tmp.Error != "" { 269 // erreur attendue 270 fac := e.base.NewFacture(e.idsDossiers[e.counter]) 271 e.errors = append(e.errors, DossierError{ 272 Fac: fac.GetPersonne().RawData().NomPrenom().String(), 273 Err: tmp.Error, 274 }) 275 e.onError(e.counter, tmp.Error) 276 } 277 e.counter += 1 // on met a jour la progression 278 return tmp.Error == "", len(p), nil 279 } 280 281 func (e *envoiMessagesHandler) Write(p []byte) (int, error) { 282 var notif apiserver.NotifieManyOut 283 ok, nb, err := e.onWrite(p, ¬if) 284 if err != nil || !ok { 285 // erreur inatendue ou erreur déjà gérée 286 return nb, err 287 } 288 e.onSuccess(e.counter, notif) 289 return nb, err 290 } 291 292 func (e *envoiDocsHandler) Write(p []byte) (int, error) { 293 var notif apiserver.NotifieDocumentsOut 294 ok, nb, err := e.onWrite(p, ¬if) 295 if err != nil || !ok { 296 // erreur inatendue ou erreur déjà gérée 297 return nb, err 298 } 299 e.onSuccess(e.counter, notif) 300 return nb, err 301 } 302 303 func (e *envoiSondagesHandler) Write(p []byte) (int, error) { 304 var notif apiserver.NotifieSondagesOut 305 ok, nb, err := e.onWrite(p, ¬if) 306 if err != nil || !ok { 307 // erreur inatendue ou erreur déjà gérée 308 return nb, err 309 } 310 e.onSuccess(e.counter, notif) 311 return nb, err 312 } 313 314 // func (c *SuiviDossiers) FilterFactures(idCamp int64, factures rd.Table) rd.Ids { 315 // var filtredIds rd.Ids 316 // cache, _ := c.Base.ResoudParticipants() 317 // for _, ac := range factures { 318 // for _, part := range c.Base.NewFacture(ac.Id.Int64()).GetDossiers(cache) { 319 // if part.GetCamp().Id == idCamp && part.RawData().ListeAttente.IsInscrit() { 320 // filtredIds = append(filtredIds, ac.Id.Int64()) 321 // break 322 // } 323 // } 324 // } 325 // return filtredIds 326 // } 327 328 // DossierError résume l'erreur rencontrée 329 // pendant l'envoi de messages 330 type DossierError struct { 331 Fac, Err string 332 } 333 334 type GUIMonitor interface { 335 OnSuccess(index int) 336 OnError(index int, err string) 337 } 338 339 // SendMessagesSignals groupe deux fonctions qui doivent 340 // émetter un signal, depuis le thread secondaire (worker) vers 341 // le thread principal. 342 type SendMessagesSignals struct { 343 OnError func(index int, err string) 344 OnSuccess func(index int, out apiserver.NotifieManyOut) 345 } 346 347 func (c *SuiviDossiers) OnSuccesNotifieMessage(out apiserver.NotifieManyOut) { 348 c.Base.Messages[out.Message.Id] = out.Message 349 c.Base.MessageMessages[out.MessageMessage.IdMessage] = out.MessageMessage 350 c.Reset() 351 } 352 353 // EnvoiManyMessages (monitor est créé dans le thread principal) 354 func (c *SuiviDossiers) EnvoiManyMessages(contenu rd.String, idsDossiers rd.Ids, 355 monitor SendMessagesSignals) ([]DossierError, error) { 356 357 if len(idsDossiers) == 0 { 358 return nil, errors.New("Aucun dossier !") 359 } 360 params := apiserver.NotifieManyIn{ 361 Contenu: contenu, 362 IdsFactures: idsDossiers, 363 } 364 365 writer := envoiMessagesHandler{ 366 streamHandler: streamHandler{ 367 idsDossiers: idsDossiers, 368 base: c.Base, 369 onError: monitor.OnError, 370 }, 371 onSuccess: monitor.OnSuccess, 372 } 373 374 if err := requestMonitor(apiserver.UrlNotifieMessagePersos, http.MethodPost, params, &writer); err != nil { 375 return nil, fmt.Errorf("Erreur pendant l'envoi des messages : %s", err) 376 } 377 return writer.errors, nil 378 } 379 380 type SendDocumentsSignals struct { 381 OnError func(index int, err string) 382 OnSuccess func(index int, out apiserver.NotifieDocumentsOut) 383 } 384 385 // SelectionneFacturesDocuments sélectionne automatiquement les dossiers concernés. 386 func (c *SuiviDossiers) SelectionneFacturesDocuments(idCamp int64, resend bool) rd.Ids { 387 var filtredIds rd.Ids 388 cache, _ := c.Base.ResoudParticipants() 389 alreadySent := c.Base.GetFacturesSendDocuments(idCamp) 390 for _, fac := range c.Base.Factures { 391 resendOk := (resend || !alreadySent[fac.Id]) 392 for _, part := range c.Base.NewFacture(fac.Id).GetDossiers(cache) { 393 if resendOk && part.GetCamp().Id == idCamp && part.RawData().ListeAttente.IsInscrit() { 394 filtredIds = append(filtredIds, fac.Id) 395 break 396 } 397 } 398 } 399 return filtredIds 400 } 401 402 func (c *SuiviDossiers) OnSuccesNotifieDocuments(out apiserver.NotifieDocumentsOut) { 403 c.Base.Messages[out.Message.Id] = out.Message 404 c.Base.MessageDocuments[out.MessageCamp.IdMessage] = out.MessageCamp 405 c.Reset() 406 } 407 408 // EnvoiDocumentsCamp envois les documents du camp donné aux factures données. 409 // `onError` et `onSuccess` sont exécutées dans le même thread 410 // à la fin de chaque envoi effectué sur le serveur. 411 func (c *SuiviDossiers) EnvoiDocumentsCamp(campId int64, idsDossiers rd.Ids, 412 monitor SendDocumentsSignals) (facErrors []DossierError, err error) { 413 camp := c.Base.Camps[campId] 414 if len(idsDossiers) == 0 { 415 return nil, fmt.Errorf("Aucun dossier n'est concerné par le <i>%s</i> !", camp.Label()) 416 } 417 params := apiserver.NotifieDocumentsIn{ 418 IdCamp: campId, 419 IdsFactures: idsDossiers, 420 } 421 422 writer := envoiDocsHandler{ 423 streamHandler: streamHandler{ 424 idsDossiers: idsDossiers, 425 base: c.Base, 426 onError: monitor.OnError, 427 }, 428 onSuccess: monitor.OnSuccess, 429 } 430 if err := requestMonitor(apiserver.UrlNotifieDocuments, http.MethodPost, params, &writer); err != nil { 431 return nil, fmt.Errorf("Erreur pendant l'envoi des messages : %s", err) 432 } 433 return writer.errors, nil 434 } 435 436 type SendSondagesSignals struct { 437 OnError func(index int, err string) 438 OnSuccess func(index int, out apiserver.NotifieSondagesOut) 439 } 440 441 func (c *SuiviDossiers) OnSuccesNotifieSondages(out apiserver.NotifieSondagesOut) { 442 c.Base.Messages[out.Message.Id] = out.Message 443 c.Base.MessageSondages[out.MessageSondage.IdMessage] = out.MessageSondage 444 c.Reset() 445 } 446 447 // EnvoiSondages envois les sondages du camp donné aux factures données. 448 // `onError` et `onSuccess` sont exécutées dans le même thread 449 // à la fin de chaque envoi effectué sur le serveur. 450 func (c *SuiviDossiers) EnvoiSondages(campId int64, idsDossiers rd.Ids, 451 monitor SendSondagesSignals) (facErrors []DossierError, err error) { 452 camp := c.Base.Camps[campId] 453 if len(idsDossiers) == 0 { 454 return nil, fmt.Errorf("Aucun dossier n'est concerné par le <i>%s</i> !", camp.Label()) 455 } 456 params := apiserver.NotifieDocumentsIn{ 457 IdCamp: campId, 458 IdsFactures: idsDossiers, 459 } 460 461 writer := envoiSondagesHandler{ 462 streamHandler: streamHandler{ 463 idsDossiers: idsDossiers, 464 base: c.Base, 465 onError: monitor.OnError, 466 }, 467 onSuccess: monitor.OnSuccess, 468 } 469 if err := requestMonitor(apiserver.UrlNotifieSondages, http.MethodPost, params, &writer); err != nil { 470 return nil, fmt.Errorf("Erreur pendant l'envoi des messages : %s", err) 471 } 472 return writer.errors, nil 473 } 474 475 type PseudoMessage struct { 476 dm.PseudoMessage 477 478 // Ids des messages envoyé en même temps 479 // (ex: documents, message groupés, sondages) 480 Groupe rd.Ids 481 } 482 483 type key struct { 484 kind rd.MessageKind 485 time rd.Time 486 } 487 488 type CacheGroupes map[int64]rd.Ids 489 490 func (ct *SuiviDossiers) NewCacheGroupes() CacheGroupes { 491 return resoudGroupeMessages(ct.Base.Messages) 492 } 493 494 func (c CacheGroupes) PromoteMessages(messages []dm.PseudoMessage) []PseudoMessage { 495 out := make([]PseudoMessage, len(messages)) 496 for i, m := range messages { 497 var groupe rd.Ids 498 if m.Kind != rd.MPaiement { 499 groupe = c[m.Id] 500 } 501 out[i] = PseudoMessage{PseudoMessage: m, Groupe: groupe} 502 } 503 return out 504 } 505 506 // on évite un cout quadratique; pour éviter des faux positifs 507 // on se restreint à certains catégories 508 func resoudGroupeMessages(messages rd.Messages) CacheGroupes { 509 groupes := map[key]rd.Ids{} 510 for _, message := range messages { 511 switch message.Kind { 512 case rd.MSondage, rd.MDocuments, rd.MCentre: 513 default: 514 continue 515 } 516 key := key{kind: message.Kind, time: message.Created} 517 groupes[key] = append(groupes[key], message.Id) 518 } 519 out := map[int64]rd.Ids{} // id message -> id messages 520 for _, groupe := range groupes { 521 // on enlève les groupes de longeur 1, qui ne sont donc pas vraiment des groupes 522 if len(groupe) <= 1 { 523 continue 524 } 525 for _, idMessage := range groupe { 526 out[idMessage] = groupe 527 } 528 } 529 return out 530 }