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 }