Skip to content

Commit

Permalink
feat / ajout d'un handler pour exporter l'état d'une campagne (#259)
Browse files Browse the repository at this point in the history
* - ajout d'un handler pour exporter l'état d'une campagne

* - ajout d'un env nix pour les volontaires
  • Loading branch information
chrnin authored Jan 26, 2024
1 parent 1fa4db8 commit 6fae7d7
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 0 deletions.
3 changes: 3 additions & 0 deletions pkg/campaign/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ var sqlCheckSirets string

//go:embed sql/addSirets.sql
var sqlAddSirets string

//go:embed sql/selectExports.sql
var sqlSelectExports string
1 change: 1 addition & 0 deletions pkg/campaign/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ func ConfigureEndpoint(kanbanService core.KanbanService) func(campaignRoute *gin
campaignRoute.POST("/withdraw/:campaignID/:campaignEtablissementID", withdrawHandler)
campaignRoute.POST("/checksirets/:campaignID", checkSiretsHandler)
campaignRoute.POST("/addsirets/:campaignID", addSiretsHandler)
campaignRoute.GET("/export/:campaignID", core.CheckAnyRolesMiddleware("stats"), exportHandlerFunc(kanbanService))
}
}
170 changes: 170 additions & 0 deletions pkg/campaign/export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package campaign

import (
"bytes"
"context"
"datapi/pkg/core"
"datapi/pkg/db"
"datapi/pkg/utils"
"encoding/csv"
"fmt"
"github.com/gin-gonic/gin"
"github.com/gosimple/slug"
"github.com/signaux-faibles/libwekan"
"net/http"
"regexp"
"strconv"
"time"
)

type Exports struct {
Etablissements []*CampaignEtablissement `json:"etablissements"`
NbTotal int `json:"nbTotal"`
CampaignName string `json:"campaignName"`
WekanDomainRegexp string `json:"-"`
}

func (p *Exports) Tuple() []interface{} {
var ce CampaignEtablissement
p.Etablissements = append(p.Etablissements, &ce)
return []interface{}{
&p.NbTotal,
&p.WekanDomainRegexp,
&p.CampaignName,
&ce.Siret,
&ce.RaisonSociale,
&ce.RaisonSocialeGroupe,
&ce.Alert,
&ce.ID,
&ce.CampaignID,
&ce.Followed,
&ce.FirstAlert,
&ce.EtatAdministratif,
&ce.Action,
&ce.Rank,
&ce.CodeDepartement,
&ce.Detail,
&ce.Username,
}
}

func (c CampaignEtablissement) toFields() []string {
var fields []string
fields = append(fields, fmt.Sprintf("%d", c.ID))
fields = append(fields, fmt.Sprintf("%d", c.CampaignID))
fields = append(fields, fmt.Sprintf("%s", c.CodeDepartement))

fields = append(fields, string(c.Siret))
fields = append(fields, c.RaisonSociale)

if c.Action != nil {
fields = append(fields, *c.Action)
} else {
fields = append(fields, "pending")
}

if c.Detail != nil {
fields = append(fields, *c.Detail)
} else {
fields = append(fields, "")
}

if c.Username != nil {
fields = append(fields, *c.Username)
} else {
fields = append(fields, "non attribué")
}

if c.List != nil {
fields = append(fields, *c.List)
} else {
fields = append(fields, "Aucun partage d'information")
}
return fields
}

func (e Exports) toCSV() []byte {
csvBuffer := new(bytes.Buffer)
writer := csv.NewWriter(csvBuffer)
writer.Comma = ';'
headers := []string{
"id_campaign_etablissement",
"id_campaign",
"code_departement",
"siret",
"raison_sociale",
"statut",
"detail_statut",
"username",
"statut_accompagnement",
}
writer.Write(headers)
for _, etablissement := range e.Etablissements {
writer.Write(etablissement.toFields())
}
writer.Flush()
return csvBuffer.Bytes()
}

func exportHandlerFunc(kanbanService core.KanbanService) func(c *gin.Context) {
return func(c *gin.Context) {
var s core.Session
s.Bind(c)

campaignID, err := strconv.Atoi(c.Param("campaignID"))
if err != nil {
c.JSON(400, `/campaign/actions/taken/:campaignID: le parametre campaignID doit être un entier`)
return
}
boards := kanbanService.SelectBoardsForUsername(libwekan.Username(s.Username))
exports, err := selectExport(c, CampaignID(campaignID), boards, libwekan.Username(s.Username), kanbanService)
if err != nil {
c.JSON(http.StatusInternalServerError, err.Error())
}
csvBytes := exports.toCSV()
filename := fmt.Sprintf("export-campaign-%s-%s", exports.CampaignName, time.Now().Format("060102"))
c.Writer.Header().Set("Content-disposition", "attachment;filename="+slug.Make(filename)+".csv")
c.Data(http.StatusOK, "text/csv", csvBytes)
}
}

func selectExport(ctx context.Context, campaignID CampaignID, boards []libwekan.ConfigBoard,
username libwekan.Username, kanbanService core.KanbanService) (exports Exports, err error) {

zones := zonesFromBoards(boards)
err = db.Scan(ctx, &exports, sqlSelectExports, campaignID, zones, username)
if err != nil {
return Exports{}, err
}
if len(exports.Etablissements) == 0 {
return Exports{Etablissements: []*CampaignEtablissement{}}, nil
}

// limiter les boards scannées au périmètre de la campagne
re, err := regexp.CompilePOSIX(exports.WekanDomainRegexp)
if err != nil {
return exports, err
}

matchingBoards := utils.Filter(boards, boardMatchesRegexpFunc(re))
err = appendCardsToExport(ctx, &exports, matchingBoards, kanbanService, username)
return exports, err
}

func appendCardsToExport(ctx context.Context, exports *Exports,
boards []libwekan.ConfigBoard, kanbanService core.KanbanService, username libwekan.Username) error {
sirets := utils.Convert(exports.Etablissements, func(c *CampaignEtablissement) core.Siret { return c.Siret })
boardIDs := utils.Convert(boards, func(board libwekan.ConfigBoard) libwekan.BoardID { return board.Board.ID })
cards, err := kanbanService.SelectCardsFromSiretsAndBoardIDs(ctx, sirets, boardIDs, username)
if err != nil {
return err
}
for _, etablissement := range exports.Etablissements {
if card, ok := utils.First(cards, func(card core.KanbanCard) bool { return card.Siret == etablissement.Siret }); ok {
etablissement.CardID = &card.ID
etablissement.Description = &card.Description
etablissement.List = &card.ListTitle
}
}
return nil
}
36 changes: 36 additions & 0 deletions pkg/campaign/sql/selectExports.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
with actions as (select id_campaign_etablissement,
last(action order by id) as action,
last(detail order by id) as detail,
last(username order by id) as username
from campaign_etablissement_action
group by id_campaign_etablissement),
zones as (select key as slug, ARRAY(SELECT jsonb_array_elements_text(value)) as zone
from jsonb_each($2::jsonb)),
zone as (select flatmap(z.zone) as zone
from campaign c
inner join zones z on z.slug ~ c.wekan_domain_regexp
where id = $1)
select count(*) over () as nb_total,
c.wekan_domain_regexp,
c.libelle,
s.siret,
s.raison_sociale,
s.raison_sociale_groupe,
s.alert,
ce.id,
ce.id_campaign,
f.id is not null as followed,
s.first_alert,
s.etat_administratif,
a.action,
rank() over (order by ce.id) as rank,
s.code_departement,
a.detail,
a.username
from campaign_etablissement ce
inner join zone z on true
inner join campaign c on c.id = ce.id_campaign
inner join v_summaries s on s.siret = ce.siret and s.code_departement = any (z.zone)
left join etablissement_follow f on f.siret = ce.siret and f.username = $3 and f.active
left join actions a on a.id_campaign_etablissement = ce.id
where ce.id_campaign = $1
4 changes: 4 additions & 0 deletions shell.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
nativeBuildInputs = with pkgs.buildPackages; [ git go jetbrains.goland ];
}

0 comments on commit 6fae7d7

Please sign in to comment.