github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/client/controllers/cont_dons.go (about)

     1  package controllers
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/benoitkugler/goACVE/server/core/apiserver"
    12  	dm "github.com/benoitkugler/goACVE/server/core/datamodel"
    13  	rd "github.com/benoitkugler/goACVE/server/core/rawdata"
    14  	"github.com/benoitkugler/goACVE/server/core/rawdata/matching"
    15  	"github.com/benoitkugler/goACVE/server/core/utils/table"
    16  )
    17  
    18  var headerDonateursCoords = []rd.Header{
    19  	{Field: dm.DonDonateurAdresse, Label: "Adresse"},
    20  	{Field: dm.DonDonateurCodePostal, Label: "Code postal"},
    21  	{Field: dm.DonDonateurVille, Label: "Ville"},
    22  	{Field: dm.DonDonateurPays, Label: "Pays"},
    23  	{Field: dm.DonDonateurMail, Label: "Mail"},
    24  	{Field: dm.DonDonateurTels, Label: "Tels."},
    25  }
    26  
    27  const (
    28  	DonsCourants = "courants"
    29  	DonsTous     = "tous"
    30  	DonsAnnee    = "annee"
    31  )
    32  
    33  type ButtonsDons struct {
    34  	Creer, Supprimer, EditerRecus, ExporterListe, ImportHelloasso EtatSideButton
    35  }
    36  
    37  type OngletDons interface {
    38  	baseOnglet
    39  
    40  	ConfirmeForceRecuFiscal(don dm.AccesDon) bool
    41  }
    42  
    43  type EtatDons struct {
    44  	DonCurrent   rd.IId
    45  	Recherche    string
    46  	CritereEmis  rd.OptionnalBool
    47  	CritereAnnee int
    48  	// Sélection du donateur
    49  	// Oui: uniquement organismes, Non: uniquement personne, Indiff: tous
    50  	CritereOrganismes rd.OptionnalBool
    51  }
    52  
    53  type Dons struct {
    54  	Onglet    OngletDons
    55  	main      *MainController
    56  	Liste     rd.Table
    57  	Header    []rd.Header
    58  	Base      *dm.BaseLocale
    59  	Etat      EtatDons
    60  	EditRight bool // Droit d'ajout/edition/suppression
    61  }
    62  
    63  func NewDons(main *MainController, permission int) *Dons {
    64  	return &Dons{main: main, Base: main.Base, EditRight: permission >= 2,
    65  		Header: []rd.Header{
    66  			{Field: dm.DonDonateur, Label: "Donateur"},
    67  			{Field: dm.DonValeur, Label: "Valeur"},
    68  			{Field: dm.DonModePaiement, Label: "Mode de paiement"},
    69  			{Field: dm.DonDateReception, Label: "Date de réception"},
    70  			{Field: dm.DonRemercie, Label: "Remercié ?"},
    71  			{Field: dm.DonRecuEmis, Label: "Reçu fiscal émis le"},
    72  			{Field: dm.DonDetails, Label: "Détails"},
    73  			{Field: dm.DonAffectation, Label: "Affectation"},
    74  		}}
    75  }
    76  
    77  func (c *Dons) resetData() {
    78  	c.Liste = make(rd.Table, 0, len(c.Base.Dons))
    79  	cEmis, cDonateur, cAnnee := c.Etat.CritereEmis, c.Etat.CritereOrganismes, c.Etat.CritereAnnee
    80  	for id, don := range c.Base.Dons {
    81  		match := cEmis == 0 || don.RecuEmis.Time().IsZero() != cEmis.Bool()
    82  		match = match && cAnnee == 0 || don.DateReception.Time().Year() == cAnnee
    83  		_, isDonateurOrga := c.Base.DonDonateurs[id].IId().(rd.IdOrganisme)
    84  		match = match && (cDonateur == rd.OBPeutEtre || cDonateur.Bool() == isDonateurOrga)
    85  		if match {
    86  			c.Liste = append(c.Liste, c.Base.NewDon(id).AsItem())
    87  		}
    88  	}
    89  
    90  	if search := strings.TrimSpace(c.Etat.Recherche); search != "" {
    91  		c.Liste = dm.RechercheDetaillee(c.Liste, search, c.Header)
    92  	}
    93  
    94  	sort.Slice(c.Liste, func(i, j int) bool {
    95  		return c.Liste[i].Id.Int64() < c.Liste[j].Id.Int64()
    96  	})
    97  	dm.SortStableBy(c.Liste, dm.DonDateReception, true)
    98  
    99  	// besoin de déselectionner si absent des critères :
   100  	if c.Etat.DonCurrent != nil && !HasId(c.Liste, c.Etat.DonCurrent) {
   101  		c.Etat.DonCurrent = nil
   102  	}
   103  }
   104  
   105  // GetStats renvoi le total des dons, pour les personnes et pour les organismes
   106  // pour le critère courant.
   107  func (c *Dons) GetStats() []Stat {
   108  	var totalOrga, totalPers rd.Euros
   109  	for _, p := range c.Liste {
   110  		idDon := p.Id.Int64()
   111  		don := c.Base.Dons[idDon]
   112  		switch c.Base.DonDonateurs[idDon].IId().(type) {
   113  		case rd.IdOrganisme:
   114  			totalOrga += don.Valeur
   115  		case rd.IdPersonne:
   116  			totalPers += don.Valeur
   117  		}
   118  	}
   119  	return []Stat{
   120  		{Label: "Montant total pour les personnes", Value: totalPers.String()},
   121  		{Label: "Montant total pour les organismes", Value: totalOrga.String()},
   122  	}
   123  }
   124  
   125  func (c *Dons) PossibleYears() []int {
   126  	tmp := map[int]bool{}
   127  	for _, don := range c.Base.Dons {
   128  		tmp[don.DateReception.Time().Year()] = true
   129  	}
   130  	delete(tmp, 0) // si une date est vide
   131  	var out []int
   132  	for y := range tmp {
   133  		out = append(out, y)
   134  	}
   135  	sort.Ints(out)
   136  	return out
   137  }
   138  
   139  func (c *Dons) SideButtons() ButtonsDons {
   140  	bs := ButtonsDons{}
   141  	if c.EditRight {
   142  		bs.Creer = ButtonActivated
   143  		EtatButton := ButtonPresent
   144  		if c.Etat.DonCurrent != nil {
   145  			EtatButton = ButtonActivated
   146  		}
   147  		bs.Supprimer = EtatButton
   148  		bs.EditerRecus = ButtonActivated
   149  		bs.ImportHelloasso = ButtonActivated
   150  	}
   151  	bs.ExporterListe = ButtonActivated
   152  	return bs
   153  }
   154  
   155  // CreeDon enregistre le nouveau don sur le serveur
   156  func (c *Dons) CreeDon(don apiserver.CreateDonIn) {
   157  	job := func() (interface{}, error) {
   158  		var out apiserver.CreateDonIn
   159  		err := requete(apiserver.UrlDons, http.MethodPut, don, &out)
   160  		return out, err
   161  	}
   162  	onSuccess := func(_out interface{}) {
   163  		out := _out.(apiserver.CreateDonIn)
   164  		c.Base.ApplyCreateDon(out)
   165  		c.main.ShowStandard("Don enregistré avec succès.", false)
   166  		c.Reset()
   167  	}
   168  
   169  	c.main.ShowStandard("Création du don...", true)
   170  	c.main.Background.Run(job, onSuccess)
   171  }
   172  
   173  // UpdateDon met à jour les profils transmis sur le serveur
   174  func (c *Dons) UpdateDon(don apiserver.CreateDonIn) {
   175  	job := func() (interface{}, error) {
   176  		var out apiserver.CreateDonIn
   177  		err := requete(apiserver.UrlDons, http.MethodPost, don, &out)
   178  		return out, err
   179  	}
   180  	onSuccess := func(_out interface{}) {
   181  		out := _out.(apiserver.CreateDonIn)
   182  		c.Base.ApplyCreateDon(out)
   183  		c.main.ShowStandard("Don mis à jour avec succès.", false)
   184  		c.Reset()
   185  	}
   186  
   187  	c.main.ShowStandard("Mise à jour du don...", true)
   188  	c.main.Background.Run(job, onSuccess)
   189  }
   190  
   191  // SupprimeDon supprime le don courant, sans demander de confirmation
   192  func (c *Dons) SupprimeDon() {
   193  	if c.Etat.DonCurrent == nil {
   194  		c.main.ShowError(errors.New("Aucun don courant !"))
   195  		return
   196  	}
   197  	idDon := c.Etat.DonCurrent.Int64()
   198  	job := func() (interface{}, error) {
   199  		var out rd.Don
   200  		err := requete(apiserver.UrlDons, http.MethodDelete, IdAsParams(idDon), &out)
   201  		return out, err
   202  	}
   203  	onSuccess := func(_out interface{}) {
   204  		out := _out.(rd.Don)
   205  		c.Etat.DonCurrent = nil
   206  		delete(c.Base.Dons, idDon)
   207  		delete(c.Base.DonDonateurs, idDon)
   208  		c.main.ShowStandard(fmt.Sprintf("Don (id %d) supprimé avec succès.", out.Id), false)
   209  		c.Reset()
   210  	}
   211  
   212  	c.main.ShowStandard("Suppression du don...", true)
   213  	c.main.Background.Run(job, onSuccess)
   214  }
   215  
   216  // ExporteListe enregistre la liste courante des dons au format Excel,
   217  // et renvoie le chemin du document.
   218  func (c *Dons) ExporteListe(avecCoordonnees bool) string {
   219  	if len(c.Liste) == 0 {
   220  		c.main.ShowError(errors.New("Aucun don à exporter."))
   221  		return ""
   222  	}
   223  	var total rd.Euros
   224  	for _, item := range c.Liste { // filtre annonyme + total
   225  		d := c.Base.Dons[item.Id.Int64()]
   226  		total += d.Valeur
   227  	}
   228  	headers := c.Header
   229  	if avecCoordonnees {
   230  		headers = append(headers, headerDonateursCoords...)
   231  	}
   232  	path, err := table.EnregistreDons(headers, c.Liste, total, LocalFolder)
   233  	if err != nil {
   234  		c.main.ShowError(err)
   235  		return ""
   236  	}
   237  	c.main.ShowStandard(fmt.Sprintf("Liste des dons enregistré dans %s", path), false)
   238  	return path
   239  }
   240  
   241  // EditeRecusFiscaux lance l'édition des reçus fiscaux et des étiquettes, de manière SYNCHRONE.
   242  func (c *Dons) EditeRecusFiscaux(params dm.OptionsRecuFiscal) string {
   243  	c.main.ShowStandard("Edition du reçu fiscal...", true)
   244  	resp, err := requeteResponseMultipart(apiserver.UrlRecuFiscal, http.MethodPost, params)
   245  	if err != nil {
   246  		c.main.ShowError(err)
   247  		return ""
   248  	}
   249  	jsonData, archive := resp[apiserver.RecusFiscauxJSONKey], resp[apiserver.RecusFiscauxZIPKey]
   250  	var dons rd.Dons
   251  	if err = json.Unmarshal(jsonData, &dons); err != nil {
   252  		c.main.ShowError(err)
   253  		return ""
   254  	}
   255  	for id, don := range dons {
   256  		c.Base.Dons[id] = don
   257  	}
   258  	path := c.main.SaveFile(archive, "recus_fiscaux_ACVE.zip")
   259  	if path != "" {
   260  		c.main.ShowStandard(fmt.Sprintf("Reçus fiscaux enregistrés : %s", path), false)
   261  	}
   262  	return path
   263  }
   264  
   265  type DonsImported []apiserver.DonDonateur
   266  
   267  var HeaderDonImported = []rd.Header{
   268  	{Field: dm.DonDonateur, Label: "Donateur"},
   269  	{Field: dm.DonDateReception, Label: "Effectué le"},
   270  	{Field: dm.DonValeur, Label: "Montant"},
   271  	{Field: dm.LastField + 1, Label: "ID HelloAsso"},
   272  }
   273  
   274  // on identifie les dons par leur index dans une slice
   275  func donDonateurToItem(d apiserver.DonDonateur, i int) rd.Item {
   276  	fields := rd.F{
   277  		dm.DonDonateur:      d.Donateur.NomPrenom(),
   278  		dm.DonValeur:        d.Don.Valeur,
   279  		dm.DonDateReception: d.Don.DateReception,
   280  		dm.LastField + 1:    rd.String(d.Don.Infos.IdPaiementHelloAsso),
   281  	}
   282  	return rd.Item{Id: rd.Id(i), Fields: fields}
   283  }
   284  
   285  func (ds DonsImported) Table() rd.Table {
   286  	table := make(rd.Table, len(ds))
   287  	for i, d := range ds {
   288  		table[i] = donDonateurToItem(d, i)
   289  	}
   290  	return table
   291  }
   292  
   293  // ChargeDonsHelloasso charge et renvoie les dons
   294  // de l'API HelloAsso.
   295  func (c *Dons) ChargeDonsHelloasso() DonsImported {
   296  	var out DonsImported
   297  	err := requete(apiserver.UrlDonsHelloasso, http.MethodGet, nil, &out)
   298  	if err != nil {
   299  		c.main.ShowError(err)
   300  	} else if len(out) == 0 {
   301  		c.main.ShowError(errors.New("Aucun nouveau don à importer."))
   302  	}
   303  	return out
   304  }
   305  
   306  // IdentifieDonHelloasso crée un nouveau don et identifie son donateur,
   307  // en créant si demandé un nouveau profil, de manière asynchrone.
   308  func (c *Dons) IdentifieDonHelloasso(don apiserver.DonDonateur, target matching.IdentifieTarget) {
   309  	params := apiserver.IdentifieDonIn{Don: don.Don}
   310  	switch target := target.(type) {
   311  	case matching.NouveauProfil:
   312  		params.Rattache = false
   313  		params.Modifications = don.Donateur
   314  	case matching.Rattache:
   315  		params.Rattache = true
   316  		params.Modifications = target.Modifications
   317  		params.IdPersonneTarget = target.IdTarget
   318  	}
   319  	job := func() (interface{}, error) {
   320  		var out apiserver.IdentifieDonOut
   321  		err := requete(apiserver.UrlDonsHelloasso, http.MethodPost, params, &out)
   322  		return out, err
   323  	}
   324  	onSuccess := func(_out interface{}) {
   325  		out := _out.(apiserver.IdentifieDonOut)
   326  		c.Base.ApplyIdentifieDon(out)
   327  		c.Etat.DonCurrent = rd.Id(out.Don.Id)
   328  		c.main.ShowStandard("Don importé avec succès.", false)
   329  		c.Reset()
   330  	}
   331  
   332  	c.main.ShowStandard("Import du don...", true)
   333  	c.main.Background.Run(job, onSuccess)
   334  }