github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/server/vote/admin.go (about) 1 package vote 2 3 import ( 4 "bytes" 5 "fmt" 6 "path" 7 "sort" 8 "strings" 9 "time" 10 11 "github.com/benoitkugler/goACVE/server/core/rawdata" 12 "github.com/benoitkugler/goACVE/server/core/utils/mails" 13 "github.com/benoitkugler/goACVE/logs" 14 "github.com/benoitkugler/goACVE/server/shared" 15 ) 16 17 // --------------------- page administrateur ---------------------------- 18 19 const mailSubject = "[ACVE] Vote de l'association" 20 21 func lienVote(host string, id int64) string { 22 lien := logs.LienVote.Offusc(id) 23 return shared.BuildUrl(host, path.Join(EndpointVote, lien), nil) 24 } 25 26 type idVoteTime struct { 27 idVote int64 28 time time.Time 29 } 30 31 type timedVotes map[idVoteTime]bool 32 33 func (m timedVotes) last() time.Time { 34 var out time.Time 35 for k := range m { 36 if k.time.After(out) { 37 out = k.time 38 } 39 } 40 return out 41 } 42 43 var critereMembre = fmt.Sprintf(`rang_membre_asso IN ('%s', '%s', '%s') AND (is_temporaire IS NULL OR is_temporaire = false)`, 44 rawdata.RMAMembre, rawdata.RMACA, rawdata.RMABureau) 45 46 func (ct Controller) getMembres(host string) ([]Membre, error) { 47 // a synchroniser avec bilans 48 rows, err := ct.db.Query("SELECT * FROM personnes WHERE " + critereMembre) 49 if err != nil { 50 return nil, err 51 } 52 personnes, err := rawdata.ScanPersonnes(rows) 53 if err != nil { 54 return nil, err 55 } 56 row := ct.db.QueryRow("SELECT count(*) FROM votes") 57 var nbVotes int 58 if err := row.Scan(&nbVotes); err != nil { 59 return nil, err 60 } 61 vps, err := SelectAllVotePersonnes(ct.db) 62 if err != nil { 63 return nil, err 64 } 65 66 // pour chaque membre, combien de votes faits 67 tmp := make(map[int64]timedVotes) 68 for _, vp := range vps { 69 l := tmp[vp.IdPersonne] 70 if l == nil { 71 l = make(timedVotes) 72 } 73 l[idVoteTime{idVote: vp.IdVote, time: vp.Time}] = true 74 tmp[vp.IdPersonne] = l 75 } 76 77 var out []Membre 78 for _, pers := range personnes { 79 votePers := tmp[pers.Id] 80 out = append(out, Membre{ 81 NomPrenom: pers.FNom() + " " + pers.FPrenom(), 82 Mail: pers.Mail, 83 Lien: lienVote(host, pers.Id), 84 Participation: Participation{ 85 Actuels: len(votePers), 86 Total: nbVotes, 87 Last: votePers.last(), 88 }, 89 }) 90 } 91 sort.Slice(out, func(i, j int) bool { 92 return out[i].NomPrenom < out[j].NomPrenom 93 }) 94 return out, nil 95 } 96 97 type nomLien struct { 98 NomPrenom string 99 Lien string 100 } 101 102 type mailArgs struct { 103 Title string 104 FooterTitle string 105 FooterInfos string 106 SignatureMail string 107 MessagePersoLines []string 108 Liens []nomLien 109 } 110 111 func (ct Controller) genereMail(liens []nomLien) (string, error) { 112 args := mailArgs{ 113 Title: "Vote en ligne", 114 FooterTitle: rawdata.Asso.Title, 115 FooterInfos: rawdata.Asso.Infos, 116 SignatureMail: "<i>Ceci est un mail automatique, merci de ne pas y répondre.</i>", 117 Liens: liens, 118 } 119 var out bytes.Buffer 120 err := ct.templateMail.ExecuteTemplate(&out, "base.html", args) 121 return out.String(), err 122 } 123 124 func (ct Controller) inviteOne(membre Membre) error { 125 mailHtml, err := ct.genereMail([]nomLien{{NomPrenom: membre.NomPrenom, Lien: membre.Lien}}) 126 if err != nil { 127 return err 128 } 129 return mails.NewMailer(ct.smtp).SendMail(membre.Mail.String(), mailSubject, mailHtml, nil, nil) 130 } 131 132 func (ct Controller) inviteAll(host string) error { 133 membres, err := ct.getMembres(host) 134 if err != nil { 135 return err 136 } 137 // on regroupe les personnes par adresse mail 138 parMail := make(map[string][]nomLien) 139 for _, membre := range membres { 140 mail := membre.Mail.TrimSpace().ToLower() 141 parMail[mail] = append(parMail[mail], nomLien{NomPrenom: membre.NomPrenom, Lien: membre.Lien}) 142 } 143 // on ignore les mails vides 144 delete(parMail, "") 145 146 pool, err := mails.NewPool(ct.smtp, nil) 147 if err != nil { 148 return err 149 } 150 defer pool.Close() 151 var errs []string 152 for to, personnes := range parMail { 153 mailHtml, err := ct.genereMail(personnes) 154 if err != nil { 155 errs = append(errs, err.Error()) 156 continue 157 } 158 err = pool.SendMail(to, mailSubject, mailHtml, nil, nil) 159 if err != nil { 160 errs = append(errs, err.Error()) 161 continue 162 } 163 } 164 if len(errs) > 0 { 165 return fmt.Errorf("Certains mails n'ont pas été envoyés : <br/> %s", strings.Join(errs, "<br/>")) 166 } 167 return nil 168 } 169 170 func (ct Controller) addDecompte(votes []VoteCandidats) ([]VoteAdmin, error) { 171 actions, err := SelectAllVotePersonnes(ct.db) 172 if err != nil { 173 return nil, err 174 } 175 // on compte les actions par votes 176 allActions := make(map[int64]int) 177 for _, ac := range actions { 178 allActions[ac.IdVote]++ 179 } 180 181 voix, err := SelectAllVotePersonneCandidats(ct.db) 182 if err != nil { 183 return nil, err 184 } 185 // on compte les voix par candidats 186 allVoix := make(map[int64]int) 187 for _, v := range voix { 188 allVoix[v.IdCandidat]++ 189 } 190 191 out := make([]VoteAdmin, len(votes)) 192 // on récupère les voix 193 for i, v := range votes { 194 va := VoteAdmin{VoteCandidats: v, Voix: make(map[int64]int), Participation: allActions[v.Id]} 195 for _, candidat := range va.Candidats { 196 va.Voix[candidat.Id] = allVoix[candidat.Id] 197 } 198 out[i] = va 199 } 200 return out, nil 201 } 202 203 func (ct Controller) getVotes() ([]VoteCandidats, error) { 204 votes, err := SelectAllVotes(ct.db) 205 if err != nil { 206 return nil, err 207 } 208 candidats, err := SelectAllCandidats(ct.db) 209 if err != nil { 210 return nil, err 211 } 212 213 // on regroupe les candidats par vote 214 tmp := make(map[int64][]Candidat) // id vote -> candidats 215 for _, candidat := range candidats { 216 tmp[candidat.IdVote] = append(tmp[candidat.IdVote], candidat) 217 } 218 var out []VoteCandidats 219 for _, vote := range votes { 220 cds := tmp[vote.Id] 221 sort.Slice(cds, func(i, j int) bool { 222 return cds[i].Id < cds[j].Id 223 }) 224 out = append(out, VoteCandidats{ 225 Vote: vote, 226 Candidats: cds, 227 }) 228 } 229 sort.Slice(out, func(i, j int) bool { 230 return out[i].Id < out[j].Id 231 }) 232 return out, nil 233 } 234 235 func (ct Controller) getVotesWithDecompte() ([]VoteAdmin, error) { 236 votes, err := ct.getVotes() 237 if err != nil { 238 return nil, err 239 } 240 return ct.addDecompte(votes) 241 } 242 243 // vote est complété 244 func (ct Controller) createVote(vote VoteCandidats) error { 245 tx, err := ct.db.Begin() 246 if err != nil { 247 return err 248 } 249 vote.Vote, err = vote.Vote.Insert(tx) 250 if err != nil { 251 return shared.Rollback(tx, err) 252 } 253 for _, c := range vote.Candidats { 254 c.IdVote = vote.Id // on utilise l'id nouvellement attribué 255 _, err := c.Insert(tx) 256 if err != nil { 257 return shared.Rollback(tx, err) 258 } 259 } 260 return tx.Commit() 261 } 262 263 // modification seulement si aucune voix 264 func (ct Controller) updateVote(vote VoteCandidats) error { 265 voix, err := SelectVotePersonneByIdVote(ct.db, vote.Id) 266 if err != nil { 267 return err 268 } 269 if len(voix) > 0 { 270 return fmt.Errorf("Ce vote a déjà reçu des suffrages, les modifications sont donc désactivées.") 271 } 272 273 tx, err := ct.db.Begin() 274 if err != nil { 275 return err 276 } 277 vote.Vote, err = vote.Vote.Update(tx) 278 if err != nil { 279 return shared.Rollback(tx, err) 280 } 281 // on remet à zéro les choix 282 _, err = tx.Exec("DELETE FROM candidats WHERE id_vote = $1", vote.Id) 283 if err != nil { 284 return shared.Rollback(tx, err) 285 } 286 // puis on insère les nouveaux 287 for _, c := range vote.Candidats { 288 c.IdVote = vote.Id 289 _, err := c.Insert(tx) 290 if err != nil { 291 return shared.Rollback(tx, err) 292 } 293 } 294 return tx.Commit() 295 } 296 297 func (ct Controller) lockVote(params LockVote) (Vote, error) { 298 row := ct.db.QueryRow("UPDATE votes SET is_locked = $1 WHERE id = $2 RETURNING *", params.IsLocked, params.IdVote) 299 return ScanVote(row) 300 } 301 302 func (ct Controller) clearVote(id int64) error { 303 tx, err := ct.db.Begin() 304 if err != nil { 305 return err 306 } 307 _, err = tx.Exec("DELETE FROM vote_personne_candidats WHERE id_vote = $1", id) 308 if err != nil { 309 return shared.Rollback(tx, err) 310 } 311 _, err = tx.Exec("DELETE FROM vote_personnes WHERE id_vote = $1", id) 312 if err != nil { 313 return shared.Rollback(tx, err) 314 } 315 err = tx.Commit() 316 return err 317 } 318 319 func (ct Controller) deleteVote(id int64) error { 320 tx, err := ct.db.Begin() 321 if err != nil { 322 return err 323 } 324 // liens 325 _, err = tx.Exec("DELETE FROM vote_personne_candidats WHERE id_vote = $1", id) 326 if err != nil { 327 return shared.Rollback(tx, err) 328 } 329 _, err = tx.Exec("DELETE FROM vote_personnes WHERE id_vote = $1", id) 330 if err != nil { 331 return shared.Rollback(tx, err) 332 } 333 _, err = tx.Exec("DELETE FROM candidats WHERE id_vote = $1", id) 334 if err != nil { 335 return shared.Rollback(tx, err) 336 } 337 _, err = Vote{Id: id}.Delete(tx) 338 if err != nil { 339 return shared.Rollback(tx, err) 340 } 341 return tx.Commit() 342 } 343 344 func (ct Controller) exportBilanVotes() ([]byte, error) { 345 votes, err := ct.getVotesWithDecompte() 346 if err != nil { 347 return nil, err 348 } 349 350 // on compte les voix exprimées 351 votePersonnes, err := SelectAllVotePersonnes(ct.db) 352 if err != nil { 353 return nil, err 354 } 355 tmp := make(map[int64]int) 356 for _, vp := range votePersonnes { 357 tmp[vp.IdVote]++ 358 } 359 360 row := ct.db.QueryRow("SELECT count(*) FROM personnes WHERE " + critereMembre) 361 var nbTotal int 362 if err := row.Scan(&nbTotal); err != nil { 363 return nil, err 364 } 365 366 var vps []voteParticipants 367 for _, vote := range votes { 368 if !vote.IsLocked { 369 continue // on ignore les votes non verouillés 370 } 371 vps = append(vps, voteParticipants{ 372 VoteAdmin: vote, 373 nbParticipants: tmp[vote.Id], 374 nbTotal: nbTotal, 375 }) 376 } 377 if len(vps) == 0 { 378 return nil, fmt.Errorf("Aucun vote à exporter. Merci de <b>clôturer</b> les votes que vous voulez exporter.") 379 } 380 return genereBilanVotes(vps) 381 }