github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/server/espaceperso/views.go (about)

     1  package espaceperso
     2  
     3  import (
     4  	"errors"
     5  	"strconv"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/benoitkugler/goACVE/server/core/apiserver"
    10  	dm "github.com/benoitkugler/goACVE/server/core/datamodel"
    11  
    12  	"github.com/benoitkugler/goACVE/server/documents"
    13  	"github.com/benoitkugler/goACVE/server/shared"
    14  
    15  	rd "github.com/benoitkugler/goACVE/server/core/rawdata"
    16  
    17  	"github.com/labstack/echo"
    18  )
    19  
    20  type etatFinancier struct {
    21  	TotalSejours   rd.Euros `json:"total_sejours"`
    22  	TotalAides     rd.Euros `json:"total_aides"`
    23  	TotalPaiements rd.Euros `json:"total_paiements"`
    24  	TotalRestant   rd.Euros `json:"total_restant"`
    25  }
    26  
    27  // Accueil renvoie l'appli statique
    28  func Accueil(c echo.Context) error {
    29  	return c.File("server/static/bv/espace_perso.html")
    30  }
    31  
    32  // GetMeta charge les données statiques à l'espace perso.
    33  func (ct Controller) GetMetas(c echo.Context) error {
    34  	_, err := ct.decrypteKey(c.Param("key"))
    35  	if err != nil {
    36  		return err
    37  	}
    38  	p, err := ct.getMeta(c.Request().Host)
    39  	if err != nil {
    40  		return err
    41  	}
    42  	return c.JSON(200, p)
    43  }
    44  
    45  // GetData charge les données nécessaire à l'espace perso.
    46  func (ct Controller) GetData(c echo.Context) error {
    47  	idFacture, err := ct.decrypteKey(c.Param("key"))
    48  	if err != nil {
    49  		return err
    50  	}
    51  	base, err := ct.loadData(idFacture)
    52  	if err != nil {
    53  		return shared.FormatErr("Le chargement des données (depuis la base) a échoué. Veuillez nous excuser pour ce désagrément.", err)
    54  	}
    55  	p, err := ct.compileData(c.Request().Host, idFacture, base)
    56  	if err != nil {
    57  		return err
    58  	}
    59  	return c.JSON(200, p)
    60  }
    61  
    62  // GetFinances charge les données financières à l'espace perso.
    63  func (ct Controller) GetFinances(c echo.Context) error {
    64  	idFacture, err := ct.decrypteKey(c.Param("key"))
    65  	if err != nil {
    66  		return err
    67  	}
    68  	base, err := ct.loadData(idFacture)
    69  	if err != nil {
    70  		return shared.FormatErr("Le chargement des données (depuis la base) a échoué. Veuillez nous excuser pour ce désagrément.", err)
    71  	}
    72  	p, err := ct.loadFinances(c.Request().Host, idFacture, base)
    73  	if err != nil {
    74  		return err
    75  	}
    76  	return c.JSON(200, p)
    77  }
    78  
    79  func isFicheSanitaireLocked(mailRespo string, mails []string) bool {
    80  	out := len(mails) > 0
    81  	for _, mail := range mails {
    82  		if strings.TrimSpace(strings.ToLower(mail)) == strings.TrimSpace(strings.ToLower(mailRespo)) {
    83  			out = false
    84  			break
    85  		}
    86  	}
    87  	return out
    88  }
    89  
    90  // LoadJoomeo renvoie les données permettant l'accès à Joomeo
    91  func (ct Controller) LoadJoomeo(c echo.Context) error {
    92  	idFacture, err := ct.decrypteKey(c.Param("key"))
    93  	if err != nil {
    94  		return err
    95  	}
    96  	out, err := ct.loadJoomeo(idFacture)
    97  	if err != nil {
    98  		return err
    99  	}
   100  	return c.JSON(200, out)
   101  }
   102  
   103  // CreateDocument crée les meta données d'un document
   104  // répondant à la contrainte donnée pour la personne donnée,
   105  // et upload le contenu du fichier
   106  func (ct Controller) CreateDocument(c echo.Context) error {
   107  	if _, err := ct.decrypteKey(c.Param("key")); err != nil {
   108  		return err
   109  	}
   110  
   111  	var args InCreateDocument
   112  	fileName, fileContent, err := documents.ReceiveUploadWithMeta(c, apiserver.DOCUMENT_MAX_SIZE, &args)
   113  	if err != nil {
   114  		return shared.FormatErr("Téléversement impossible.", err)
   115  	}
   116  	idPersonne, err := shared.DecodeID(ct.Signing, args.IdPersonne, shared.OrPersonne)
   117  	if err != nil {
   118  		return shared.FormatErr("L'identifiant du participant est invalide.", err)
   119  	}
   120  
   121  	idContrainte, err := shared.DecodeID(ct.Signing, args.IdContrainte, shared.OrContrainte)
   122  	if err != nil {
   123  		return shared.FormatErr("L'identifiant de la catégorie de document est invalide.", err)
   124  	}
   125  
   126  	doc, err := documents.CreeDocumentPersonne(ct.Signing, ct.DB, c.Request().Host, idPersonne, documents.ParamsNewDocument{
   127  		IdContrainte: idContrainte,
   128  		Description:  rd.String("Ajouté via l'espace personnel"),
   129  		FileContent:  fileContent,
   130  		FileName:     fileName,
   131  	})
   132  	if err != nil {
   133  		return err
   134  	}
   135  	return c.JSON(200, doc)
   136  }
   137  
   138  // CreateAide ajoute une aide (non validée) au participant donné,
   139  // et upload un document associé.
   140  func (ct Controller) CreateAide(c echo.Context) error {
   141  	if _, err := ct.decrypteKey(c.Param("key")); err != nil {
   142  		return err
   143  	}
   144  	var inAide ChampsAideEditables
   145  	fileName, fileContent, err := documents.ReceiveUploadWithMeta(c, apiserver.DOCUMENT_MAX_SIZE, &inAide)
   146  	if err != nil {
   147  		return shared.FormatErr("L'aide à ajouter ne semble pas valide.", err)
   148  	}
   149  
   150  	idPart, err := shared.DecodeID(ct.Signing, inAide.IdParticipantCrypted, shared.OrParticipant)
   151  	if err != nil {
   152  		return shared.FormatErr("L'identifiant du participant est corrompu.", err)
   153  	}
   154  	idStruct, err := shared.DecodeID(ct.Signing, inAide.IdStructureCrypted, shared.OrStructureaide)
   155  	if err != nil {
   156  		return shared.FormatErr("L'identifiant de la structure est corrompu.", err)
   157  	}
   158  	newAide := rd.Aide{
   159  		IdParticipant:   idPart,
   160  		IdStructureaide: idStruct,
   161  		Valide:          false,
   162  		NbJoursMax:      inAide.NbJoursMax,
   163  		ParJour:         inAide.ParJour,
   164  		Valeur:          inAide.Valeur,
   165  	}
   166  	tx, err := ct.DB.Begin()
   167  	if err != nil {
   168  		return err
   169  	}
   170  
   171  	part, err := rd.SelectParticipant(tx, newAide.IdParticipant)
   172  	if err != nil {
   173  		return shared.Rollback(tx, err)
   174  	}
   175  
   176  	camp, err := rd.SelectCamp(tx, part.IdCamp)
   177  	if err != nil {
   178  		return shared.Rollback(tx, err)
   179  	}
   180  
   181  	newAide, err = newAide.Insert(tx)
   182  	if err != nil {
   183  		return shared.Rollback(tx, err)
   184  	}
   185  
   186  	newDoc := rd.Document{
   187  		Description:    "Créé via l'espace personnel",
   188  		DateHeureModif: rd.Time(time.Now()),
   189  	}
   190  	newDoc, err = newDoc.Insert(tx)
   191  	if err != nil {
   192  		return shared.Rollback(tx, err)
   193  	}
   194  
   195  	link := rd.DocumentAide{
   196  		IdAide:     newAide.Id,
   197  		IdDocument: newDoc.Id,
   198  	}
   199  	err = rd.InsertManyDocumentAides(tx, link)
   200  	if err != nil {
   201  		return shared.Rollback(tx, err)
   202  	}
   203  
   204  	newDoc, err = documents.SaveDocument(tx, newDoc.Id, fileContent, fileName, true)
   205  	if err != nil {
   206  		return shared.Rollback(tx, err)
   207  	}
   208  
   209  	base := dm.BaseLocale{
   210  		Camps:        rd.Camps{camp.Id: camp},
   211  		Participants: rd.Participants{part.Id: part},
   212  		Aides:        rd.Aides{newAide.Id: newAide},
   213  		Documents:    rd.Documents{newDoc.Id: newDoc},
   214  	}
   215  	base.DocumentAides = map[int64]rd.DocumentAide{newDoc.Id: link}
   216  
   217  	outAide, err := ct.shareAide(c.Request().Host, base.NewAide(newAide.Id))
   218  	if err != nil {
   219  		return shared.Rollback(tx, err)
   220  	}
   221  
   222  	// le participant et la structure ont été choisit par le client
   223  	outAide.IdParticipantCrypted = inAide.IdParticipantCrypted
   224  	outAide.IdStructureCrypted = inAide.IdStructureCrypted
   225  
   226  	if err = tx.Commit(); err != nil {
   227  		return shared.FormatErr("La validation de l'ajout du document a échoué.", err)
   228  	}
   229  	return c.JSON(200, outAide)
   230  }
   231  
   232  // UpdateAide met à jour une aide encore non validée.
   233  func (ct Controller) UpdateAide(c echo.Context) error {
   234  	if _, err := ct.decrypteKey(c.Param("key")); err != nil {
   235  		return err
   236  	}
   237  	var inAide Aide
   238  	if err := c.Bind(&inAide); err != nil {
   239  		return shared.FormatErr("L'aide à ajouter ne semble pas valide.", err)
   240  	}
   241  	idAide, err := shared.DecodeID(ct.Signing, inAide.IdCrypted, shared.OrAide)
   242  	if err != nil {
   243  		return shared.FormatErr("L'identifiant de l'aide est corrompu.", err)
   244  	}
   245  	tx, err := ct.DB.Begin()
   246  	if err != nil {
   247  		return err
   248  	}
   249  
   250  	aide, err := rd.SelectAide(tx, idAide)
   251  	if err != nil {
   252  		return shared.Rollback(tx, err)
   253  	}
   254  	if aide.Valide {
   255  		_ = tx.Rollback()
   256  		return errors.New("Cette aide a déjà été validée: les modifications sont désactivées.")
   257  	}
   258  
   259  	idPart, err := shared.DecodeID(ct.Signing, inAide.IdParticipantCrypted, shared.OrParticipant)
   260  	if err != nil {
   261  		return shared.Rollback(tx, err)
   262  	}
   263  	idStruct, err := shared.DecodeID(ct.Signing, inAide.IdStructureCrypted, shared.OrStructureaide)
   264  	if err != nil {
   265  		return shared.Rollback(tx, err)
   266  	}
   267  
   268  	aide.NbJoursMax = inAide.NbJoursMax
   269  	aide.Valeur = inAide.Valeur
   270  	aide.ParJour = inAide.ParJour
   271  	aide.IdParticipant = idPart
   272  	aide.IdStructureaide = idStruct
   273  	aide, err = aide.Update(tx)
   274  	if err != nil {
   275  		return shared.Rollback(tx, err)
   276  	}
   277  
   278  	part, err := rd.SelectParticipant(tx, aide.IdParticipant)
   279  	if err != nil {
   280  		return shared.Rollback(tx, err)
   281  	}
   282  
   283  	camp, err := rd.SelectCamp(tx, part.IdCamp)
   284  	if err != nil {
   285  		return shared.Rollback(tx, err)
   286  	}
   287  
   288  	base := dm.BaseLocale{
   289  		Camps:        rd.Camps{camp.Id: camp},
   290  		Participants: rd.Participants{part.Id: part},
   291  		Aides:        rd.Aides{aide.Id: aide},
   292  	}
   293  
   294  	outAide, err := ct.shareAide(c.Request().Host, base.NewAide(aide.Id))
   295  	if err != nil {
   296  		return shared.Rollback(tx, err)
   297  	}
   298  	// on ne modifie pas les champs suivants
   299  	outAide.IdCrypted = inAide.IdCrypted
   300  	outAide.IdParticipantCrypted = inAide.IdParticipantCrypted
   301  	outAide.IdStructureCrypted = inAide.IdStructureCrypted
   302  	outAide.Document = inAide.Document
   303  	if err = tx.Commit(); err != nil {
   304  		return shared.FormatErr("La modification de l'aide a échoué.", err)
   305  	}
   306  	return c.JSON(200, outAide)
   307  }
   308  
   309  // DeleteAide supprime l'aide non validée et le document associé
   310  func (ct Controller) DeleteAide(c echo.Context) error {
   311  	if _, err := ct.decrypteKey(c.Param("key")); err != nil {
   312  		return err
   313  	}
   314  	id, err := shared.DecodeID(ct.Signing, c.QueryParam("id-crypted"), shared.OrAide)
   315  	if err != nil {
   316  		return shared.FormatErr("L'aide n'a pu être identifiée.", err)
   317  	}
   318  
   319  	tx, err := ct.DB.Begin()
   320  	if err != nil {
   321  		return err
   322  	}
   323  
   324  	aide, err := rd.SelectAide(tx, id)
   325  	if err != nil {
   326  		return shared.Rollback(tx, err)
   327  	}
   328  	if aide.Valide {
   329  		_ = tx.Rollback()
   330  		return errors.New("Cette aide a déjà été validée: les modifications sont désactivées.")
   331  	}
   332  
   333  	// suppression du lien
   334  	row := tx.QueryRow("DELETE FROM document_aides WHERE id_aide = $1 RETURNING *", id)
   335  	documentAide, err := rd.ScanDocumentAide(row)
   336  	if err != nil {
   337  		return shared.Rollback(tx, err)
   338  	}
   339  
   340  	// suppression du contenu par cascade
   341  
   342  	// suppression du document
   343  	_, err = rd.DeleteDocumentById(tx, documentAide.IdDocument)
   344  	if err != nil {
   345  		return shared.Rollback(tx, err)
   346  	}
   347  
   348  	// finalement, suppression de l'aide
   349  	if _, err = rd.DeleteAideById(tx, aide.Id); err != nil {
   350  		return shared.Rollback(tx, err)
   351  	}
   352  	if err = tx.Commit(); err != nil {
   353  		return shared.FormatErr("La suppression de l'aide a échoué.", err)
   354  	}
   355  	return c.NoContent(200)
   356  }
   357  
   358  // UpdateFicheSanitaire met à jour une fiche sanitaire
   359  func (ct Controller) UpdateFicheSanitaire(c echo.Context) error {
   360  	idFacture, err := ct.decrypteKey(c.Param("key"))
   361  	if err != nil {
   362  		return err
   363  	}
   364  	var params InFicheSanitaire
   365  	if err = c.Bind(&params); err != nil {
   366  		return shared.FormatErr("La fiche sanitaire est corrompue", err)
   367  	}
   368  
   369  	out, err := ct.updateFicheSanitaire(idFacture, params)
   370  	if err != nil {
   371  		return err
   372  	}
   373  	return c.JSON(200, out)
   374  }
   375  
   376  // TransfertFicheSanitaire envoie un mail de demande de transfert
   377  func (ct Controller) TransfertFicheSanitaire(c echo.Context) error {
   378  	idFacture, err := ct.decrypteKey(c.Param("key"))
   379  	if err != nil {
   380  		return err
   381  	}
   382  	idCrypted := c.QueryParam("id-crypted")
   383  	respo, pers, err := ct.loadDataFicheSanitaire(idFacture, idCrypted)
   384  	if err != nil {
   385  		return err
   386  	}
   387  	mailErrors, err := ct.SendMailPartageFicheSanitaire(c.Request().Host, respo.Mail.String(), pers)
   388  	if err != nil {
   389  		return err
   390  	}
   391  	return c.JSON(200, mailErrors)
   392  }
   393  
   394  // ValideTransfertFicheSanitaire accepte un transfert.
   395  func (ct Controller) ValideTransfertFicheSanitaire(c echo.Context) error {
   396  	target := c.QueryParam("target")
   397  	if err := ct.validePartageFicheSanitaire(target); err != nil {
   398  		return shared.RedirectError(c, err)
   399  	}
   400  	return shared.RedirectSuccess(c, "Partage de fiche sanitaire", "Fiche sanitaire partagée",
   401  		"L'accès à la fiche a bien été mis à jour. <br/>"+
   402  			"<small class='text-muted'>Vous pouvez actualiser votre espace de suivi pour y accéder.</small>")
   403  }
   404  
   405  // UpdateOptionsParticipants met à jour les participants
   406  func (ct Controller) UpdateOptionsParticipants(c echo.Context) error {
   407  	if _, err := ct.decrypteKey(c.Param("key")); err != nil {
   408  		return err
   409  	}
   410  	var args struct {
   411  		Participants []Participant `json:"participants"`
   412  	}
   413  	if err := c.Bind(&args); err != nil {
   414  		return shared.FormatErr("Les options n'ont pas pu être lues.", err)
   415  	}
   416  	if err := ct.updateOptionsParticipants(args.Participants); err != nil {
   417  		return err
   418  	}
   419  	return c.NoContent(200)
   420  }
   421  
   422  // DownloadFacture gènère le document et met à jour les messages
   423  // (les données devraient être raffraichies)
   424  func (ct Controller) DownloadFacture(c echo.Context) error {
   425  	idFacture, err := ct.decrypteKey(c.Param("key"))
   426  	if err != nil {
   427  		return err
   428  	}
   429  	s := c.QueryParam("index-destinataire")
   430  	indexDestinataire, _ := strconv.Atoi(s) // fallback to responsable
   431  	out, name, err := ct.downloadFacture(idFacture, indexDestinataire)
   432  	if err != nil {
   433  		return shared.RedirectError(c, err)
   434  	}
   435  	return documents.Attachment(c, name, out, true)
   436  }
   437  
   438  // DownloadAttestationPresence gènère le document et met à jour les messages
   439  // (les données devraient être raffraichies)
   440  func (ct Controller) DownloadAttestationPresence(c echo.Context) error {
   441  	idFacture, err := ct.decrypteKey(c.Param("key"))
   442  	if err != nil {
   443  		return err
   444  	}
   445  	s := c.QueryParam("index-destinataire")
   446  	indexDestinataire, _ := strconv.Atoi(s) // fallback to responsable
   447  	out, err := ct.downloadAttestationPresence(idFacture, indexDestinataire)
   448  	if err != nil {
   449  		return shared.RedirectError(c, err)
   450  	}
   451  	return documents.Attachment(c, "Attestation.pdf", out, true)
   452  }
   453  
   454  // MarkConnection enregistre la connection sur l'espace de suivi,
   455  // pour permettre la mise à jour de l'état des notifications.
   456  func (ct Controller) MarkConnection(c echo.Context) error {
   457  	idFacture, err := ct.decrypteKey(c.Param("key"))
   458  	if err != nil {
   459  		return err
   460  	}
   461  	err = ct.markConnection(idFacture)
   462  	if err != nil {
   463  		return err
   464  	}
   465  	return c.NoContent(200)
   466  }
   467  
   468  // SaveSondage enregiste le retour sur un séjour.
   469  // Un idCrypted vide crée un nouvel item
   470  func (ct Controller) SaveSondage(c echo.Context) error {
   471  	idFacture, err := ct.decrypteKey(c.Param("key"))
   472  	if err != nil {
   473  		return err
   474  	}
   475  	var params PublicSondage
   476  	if err = c.Bind(&params); err != nil {
   477  		return err
   478  	}
   479  	out, err := ct.saveSondage(c.Request().Host, idFacture, params)
   480  	if err != nil {
   481  		return err
   482  	}
   483  	return c.JSON(200, out)
   484  }
   485  
   486  // CreeMessage envoie un nouveau message.
   487  func (ct Controller) CreeMessage(c echo.Context) error {
   488  	idFacture, err := ct.decrypteKey(c.Param("key"))
   489  	if err != nil {
   490  		return err
   491  	}
   492  	var params EditMessage
   493  	if err = c.Bind(&params); err != nil {
   494  		return err
   495  	}
   496  	out, err := ct.creeMessage(idFacture, params.Contenu)
   497  	if err != nil {
   498  		return err
   499  	}
   500  	return c.JSON(200, out)
   501  }
   502  
   503  // EditMessage modifie le contenu d'un message existant.
   504  func (ct Controller) EditMessage(c echo.Context) error {
   505  	idFacture, err := ct.decrypteKey(c.Param("key"))
   506  	if err != nil {
   507  		return err
   508  	}
   509  	var params EditMessage
   510  	if err = c.Bind(&params); err != nil {
   511  		return err
   512  	}
   513  	out, err := ct.editMessage(idFacture, params)
   514  	if err != nil {
   515  		return err
   516  	}
   517  	return c.JSON(200, out)
   518  }
   519  
   520  // DeleteMessage supprime un message libre.
   521  // Le message est marqué comme supprimé, et son contenu est réellement supprimé.
   522  func (ct Controller) DeleteMessage(c echo.Context) error {
   523  	idFacture, err := ct.decrypteKey(c.Param("key"))
   524  	if err != nil {
   525  		return err
   526  	}
   527  	idMessage, err := shared.ParseId(c, "id-message")
   528  	if err != nil {
   529  		return err
   530  	}
   531  	out, err := ct.deleteMessage(idFacture, idMessage)
   532  	if err != nil {
   533  		return err
   534  	}
   535  	return c.JSON(200, out)
   536  }
   537  
   538  // ConfirmePlaceliberee passe le participant en liste principale
   539  // et recharge les données.
   540  func (ct Controller) ConfirmePlaceliberee(c echo.Context) error {
   541  	idFacture, err := ct.decrypteKey(c.Param("key"))
   542  	if err != nil {
   543  		return err
   544  	}
   545  	idMessage, err := shared.ParseId(c, "id-message")
   546  	if err != nil {
   547  		return err
   548  	}
   549  	err = ct.confirmePlaceliberee(idFacture, idMessage)
   550  	if err != nil {
   551  		return err
   552  	}
   553  	// les participants ont changés
   554  	base, err := ct.loadData(idFacture)
   555  	if err != nil {
   556  		return shared.FormatErr("Le chargement des données (depuis la base) a échoué. Veuillez nous excuser pour ce désagrément.", err)
   557  	}
   558  	p, err := ct.compileData(c.Request().Host, idFacture, base)
   559  	if err != nil {
   560  		return err
   561  	}
   562  	return c.JSON(200, p)
   563  }