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, &notif)
   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, &notif)
   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, &notif)
   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  }