From 8fc22fa4c5bcc0fa88658dc2087d93edb532ce8f Mon Sep 17 00:00:00 2001
From: Alexis POYEN <apoyen@grandlyon.com>
Date: Tue, 18 Aug 2020 09:16:42 +0200
Subject: [PATCH] Feat : create default parameter on round creation

preload Parameter on API call
---
 data/test.db                                  | Bin 77824 -> 77824 bytes
 internal/models/models.go                     |   3 +
 internal/models/parameters.go                 |  96 +++++
 internal/models/round.go                      |  16 +-
 internal/models/vote.go                       |  10 +-
 internal/rootmux/admin_test.go                |  11 +-
 internal/rootmux/capturer_test.go             |   4 +-
 internal/rootmux/rootmux_test.go              |   5 +-
 internal/rootmux/visualizer_test.go           |   4 +-
 web/components/help/management-help.js        | 373 ++++++++++--------
 web/components/management/rounds-card.js      |  52 ++-
 .../visualization/results-general.js          |  12 +-
 web/components/visualization/results-zone.js  |  33 +-
 .../visualization/visualization-section.js    |  30 +-
 web/components/vote/votes.js                  |  92 ++---
 web/services/model/round-model.js             |  60 ++-
 16 files changed, 516 insertions(+), 285 deletions(-)
 create mode 100644 internal/models/parameters.go

diff --git a/data/test.db b/data/test.db
index 9bc1a6ad1c39b57807326f33a2b4e09dd863fb6f..e845823db7d6ac62c9be7b05180e57ab3cf69e6f 100644
GIT binary patch
delta 1169
zcmZ`&UuYCZ9G=<xyLY!ccP5vVq}4rZQL#B^cV>2WW9zATsgX8bPK0V{d(~ABY?R9-
zDn2xApMpsK3>d*A_Y$FRLH9urlF;_W7X_abUwR0F4}v`qg}$`A$u?@DJN))xe*68t
z`M&vfq!AuzgvUEG4FEukR~x*NyxKbPDcRWo`JO+N=qr$x`XSn)Hr2Y+ugs@zC+{U5
z$amuZMxMue<Dt-r@Nf*k!o<XlaP%c?xiNvD+b_+9TfIR*_Fv!^kd3wHa`4v!ld-y}
zC9<)hf!cRJoGy#sVxPNGT-x?w3gHlHqJ8Kga?$VT3c_d<710GWfactc_KzAyCqSFn
zSlAEmic^KpkdhAN<g?{!`A4VbR0mzR@YS9qx(+1u8C0L4FWl5fLrool2~a9|$H)O$
z<my@RbgXsiuM~`KW!;!{>sH=iIwpo?nLDu2jj`daE^Kxg`VlDsiY3uEmS<L%&Nbab
z;BJcZoEwZ|o6$80%eM|3EfwEEm-fKar=!HOY-(BJ@3Ho52wg&1wXSYg7L|jkC#iF(
zWO7z$<Da;D$0w8RYiX%L88)fdIirchG+Wmvwqaw=m;1xhX4_gkF?CM4$j{{BAyA2V
z@`@re-6<k7E$hD}gj3rab$A_(VRJ@_-_sHRR3e_ko@DXUbXGi?>F5Zo3e2V0=C;1#
zlxSY-5Zh;WWnOzvOm68qo4TKXABfAdU-~`;NMaC+S>pS8+hqOV8~X;g4Qz?xY}?dX
z1xCchdhZ7RZUUG=3_~~R)XxZZxXpSxomECbRM7ysit_58%4J1UzgDQ5Nhg%#$%yR7
z!-g^xxy6q^TDkpI--3}Qp<Y0Av3qf2Sa|`$;+sX2zb%v5gz2WeqO)}7SfzZrGFWp4
zwc|rtk26rKI(|5uQlA)G$m^>FE0Lg#a}wmQ=gh#MQynhXoT2J6`k0RvqYHHJD(e4Z
zag%Z$r10H1CnQxu%MuGvmPPZu5*am3Tc<)@I`ldU0T06j>$W(*bh!0x(UdWaiTikI
xb2<vqJV5i_pL!i7``|LA>^<Vuw>vuDL=kFvDb$TX?#%|mu;o@J4WDm<;6GC`Bh>%^

delta 721
zcmY*WT}TvB6rP#=pP#+6x|6X6j#iMQ<DEPITVdrx7-X(eAQWPMq=XTa(TiF=N!0Fo
zpbc7`2;YU~K`7Rxr06N=K@#d=i5_|oVhIW&sOuI)7ruw{oqN7>zH?@4;n`Za(3)Cj
z7^az`iGri(D0Ug$>x|a-LZXq(4KZj}vYzz0A!%8BD!k#}C0@sWMZQG8#gE2Ap$p-u
zASJ(IgCTAy%-`arSWA05dwUf4n4dU%`P$^w(KBP0;AQLp*QLU<SZlz=xjh*HVHVZV
zVYGn)^c>ws7|o#^x`8Hxl-h&<_d4ujxY)ZNF)2F6zZV3(0|x~fbEuy|OQ=5(XKTsg
zqtnq5j%RW?7|3hzy^LTy-yE^KiOa#}JquRyJK<+uP*`*aDM>%sDOIFF@soI66of@k
z3%i3ug?Ry{X9Ur(G~4qG$Fgl3dKOgR%NC4J3nFnfVpzoSOp7!UQ)Row=_Zb<>sj5*
znnZJmV;P=pHVQ+bi5N)bHi#}XHw3V!IgYKHx=CCraFVtY1yjSsaEWK@Tb!C;CL%mD
zJj^6urmVpGg-of3Wn*bCt6Q3GS=b{cHuhqY#Te=jGtjxXuT)F0@dUAT$04TWmEQ8~
zcDTEEtdtknsGw^)!NjqtB^dLSd4I4}km!F0+tdlxx7c&=*_R@&OANhq6S0|~?#r@5
zw;Yw`81w*X$+hGu>8ms+ne^K25__Up%)!X23F2}bq*Vp_R=0)Dhd59xe>ktAz?BLe
zqhc%AR2%EbTGWDJ5*@pi1sjzeu=ZEs164>@2O4TestU|hkEj2yGF;tpaqzL)A#;qp
Vl9JgU=t&LXm9)%mwiO2>zX2(V%aQ;9

diff --git a/internal/models/models.go b/internal/models/models.go
index f389279..3da7356 100644
--- a/internal/models/models.go
+++ b/internal/models/models.go
@@ -127,6 +127,7 @@ type Parameter struct {
 	CreatedAt         time.Time  `json:"-"`
 	UpdatedAt         time.Time  `json:"-"`
 	DeletedAt         *time.Time `json:"-"`
+	RoundID           uint
 	CountBlankAndNull bool
 	ShowOnlyCompleted bool
 	ShowMap           bool
@@ -258,6 +259,8 @@ func (d *DataHandler) ProcessAPI(w http.ResponseWriter, r *http.Request) {
 		d.handleVote(w, r)
 	case "Maps":
 		d.handleMaps(w, r)
+	case "Parameter":
+		d.handleParameters(w, r)
 	}
 
 }
diff --git a/internal/models/parameters.go b/internal/models/parameters.go
new file mode 100644
index 0000000..1198536
--- /dev/null
+++ b/internal/models/parameters.go
@@ -0,0 +1,96 @@
+package models
+
+import (
+	"encoding/json"
+	"net/http"
+	"strconv"
+	"strings"
+
+	"forge.grandlyon.com/gestion-des-assemblees/elections/internal/auth"
+)
+
+func (d *DataHandler) handleParameters(w http.ResponseWriter, r *http.Request) {
+	id, _ := strconv.Atoi(strings.TrimPrefix(r.URL.Path, "/api/Parameter/"))
+	switch method := r.Method; method {
+	case "GET":
+		switch auth.GetLoggedUserTechnical(w, r).Role {
+		case "ADMIN", "CAPTURER", "VISUALIZER":
+			d.getParameter(w, r, id)
+		default:
+			http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError)
+		}
+	case "POST":
+		switch auth.GetLoggedUserTechnical(w, r).Role {
+		case "ADMIN", "CAPTURER", "VISUALIZER":
+			http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed)
+		default:
+			http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError)
+		}
+
+	case "PUT":
+		switch auth.GetLoggedUserTechnical(w, r).Role {
+		case "ADMIN":
+			d.putParameter(w, r, id)
+		case "CAPTURER", "VISUALIZER":
+			http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed)
+		default:
+			http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError)
+		}
+	case "DELETE":
+		switch auth.GetLoggedUserTechnical(w, r).Role {
+		case "ADMIN":
+			d.deleteParameter(w, r, id)
+		case "CAPTURER", "VISUALIZER":
+			http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed)
+		default:
+			http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError)
+		}
+	default:
+		http.Error(w, "method not allowed", 400)
+	}
+}
+
+func (d *DataHandler) getParameter(w http.ResponseWriter, r *http.Request, id int) {
+	if id != 0 {
+		var o Parameter
+		if err := d.db.First(&o, id).Error; err != nil {
+			http.Error(w, ErrorIDIsMissing, http.StatusNotFound)
+			return
+		}
+		json.NewEncoder(w).Encode(o)
+	}
+}
+
+func (d *DataHandler) putParameter(w http.ResponseWriter, r *http.Request, id int) {
+	var o Parameter
+	if err := d.db.First(&o, id).Error; err != nil {
+		http.Error(w, ErrorIDIsMissing, http.StatusNotFound)
+		return
+	}
+	var parameter Parameter
+	err := json.NewDecoder(r.Body).Decode(&parameter)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	o.CountBlankAndNull = parameter.CountBlankAndNull
+	o.ShowMap = parameter.ShowMap
+	o.ShowOnlyCompleted = parameter.ShowOnlyCompleted
+	d.db.Save(&o)
+	json.NewEncoder(w).Encode(o)
+
+}
+
+func (d *DataHandler) deleteParameter(w http.ResponseWriter, r *http.Request, id int) {
+	if id != 0 {
+		var o Parameter
+		if err := d.db.First(&o, id).Error; err != nil {
+			http.Error(w, ErrorIDIsMissing, http.StatusNotFound)
+			return
+		}
+
+		d.db.Delete(&o)
+	} else {
+		http.Error(w, ErrorIDIsMissing, http.StatusNotFound)
+	}
+}
diff --git a/internal/models/round.go b/internal/models/round.go
index d542d03..2e953f4 100644
--- a/internal/models/round.go
+++ b/internal/models/round.go
@@ -55,14 +55,14 @@ func (d *DataHandler) handleRound(w http.ResponseWriter, r *http.Request) {
 func (d *DataHandler) getRound(w http.ResponseWriter, r *http.Request, id int) {
 	if id != 0 {
 		var o Round
-		if err := d.db.Preload("DeskRounds").Preload("CandidateLists").First(&o, id).Error; err != nil {
+		if err := d.db.Preload("DeskRounds").Preload("CandidateLists").Preload("Parameter").First(&o, id).Error; err != nil {
 			http.Error(w, ErrorIDIsMissing, http.StatusNotFound)
 			return
 		}
 		json.NewEncoder(w).Encode(o)
 	} else {
 		var o []Round
-		d.db.Preload("DeskRounds").Preload("CandidateLists").Find(&o)
+		d.db.Preload("DeskRounds").Preload("CandidateLists").Preload("Parameter").Find(&o)
 		json.NewEncoder(w).Encode(o)
 	}
 }
@@ -78,6 +78,16 @@ func (d *DataHandler) postRound(w http.ResponseWriter, r *http.Request) {
 	d.db.Create(&o)
 	d.db.Last(&o)
 
+	parameter := Parameter{
+		RoundID:           o.ID,
+		ShowMap:           false,
+		ShowOnlyCompleted: false,
+		CountBlankAndNull: false,
+	}
+	d.db.Create(&parameter)
+	d.db.Last(&parameter)
+	o.Parameter = parameter
+
 	var election Election
 	d.db.Preload("Areas").First(&election, o.ElectionID)
 	for _, area := range election.Areas {
@@ -95,7 +105,7 @@ func (d *DataHandler) postRound(w http.ResponseWriter, r *http.Request) {
 
 func (d *DataHandler) putRound(w http.ResponseWriter, r *http.Request, id int) {
 	var o Round
-	if err := d.db.Preload("DeskRounds").Preload("CandidateLists").First(&o, id).Error; err != nil {
+	if err := d.db.Preload("DeskRounds").Preload("CandidateLists").Preload("Parameter").First(&o, id).Error; err != nil {
 		http.Error(w, ErrorIDIsMissing, http.StatusNotFound)
 		return
 	}
diff --git a/internal/models/vote.go b/internal/models/vote.go
index 72d07f5..7cb688b 100644
--- a/internal/models/vote.go
+++ b/internal/models/vote.go
@@ -229,7 +229,7 @@ func (vote *Vote) AfterSave(scope *gorm.Scope) error {
 		return errors.New(ErrorValidateVote)
 	}
 	var round Round
-	if err := scope.DB().First(&round, deskRound.RoundID).Error; err != nil {
+	if err := scope.DB().Preload("Parameter").First(&round, deskRound.RoundID).Error; err != nil {
 		return errors.New(ErrorValidateVote)
 	}
 
@@ -245,7 +245,13 @@ func (vote *Vote) AfterSave(scope *gorm.Scope) error {
 	}
 	var votesNumberPerDesk = len(votes)
 
-	if votesNumberPerDesk == (listNumberPerArea + 2) {
+	var expectedNumberOfVote = 0
+	if round.Parameter.CountBlankAndNull {
+		expectedNumberOfVote = listNumberPerArea + 2
+	} else {
+		expectedNumberOfVote = listNumberPerArea
+	}
+	if votesNumberPerDesk == expectedNumberOfVote {
 		deskRound.Completed = true
 		deskRound.DateCompletion = time.Now()
 		scope.DB().Save(&deskRound)
diff --git a/internal/rootmux/admin_test.go b/internal/rootmux/admin_test.go
index 554015f..89082be 100644
--- a/internal/rootmux/admin_test.go
+++ b/internal/rootmux/admin_test.go
@@ -70,13 +70,16 @@ func AdminTests(t *testing.T) {
 		do("PUT", "/api/Desk/1", xsrfHeader, `{"ID":1,"SectionID":1,"Name":"Desk 1","WitnessDesk":false,"Subscribed":3587}`, 200, `{"ID":1,"SectionID":1,"Name":"Desk 1","WitnessDesk":false,"Subscribed":3587,"DeskRounds":[]}`)
 
 		// Create a Round
-		do("POST", "/api/Round", xsrfHeader, `{"ElectionID":1,"Date":"2020-06-28","Round":1}`, 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":null,"CandidateLists":null}`)
+		do("POST", "/api/Round", xsrfHeader, `{"ElectionID":1,"Date":"2020-06-28","Round":1}`, 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":1,"RoundID":1,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":null,"CandidateLists":null}`)
 		// Get a Round
-		do("GET", "/api/Round/1", xsrfHeader, ``, 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[]}`)
+		do("GET", "/api/Round/1", xsrfHeader, ``, 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":1,"RoundID":1,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[]}`)
 		// Get Rounds
-		do("GET", "/api/Round/", xsrfHeader, ``, 200, `[{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[]}]`)
+		do("GET", "/api/Round/", xsrfHeader, ``, 200, `[{"ID":1,"ElectionID":1,"Parameter":{"ID":1,"RoundID":1,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[]}]`)
 		// Update a Round
-		do("PUT", "/api/Round/1", xsrfHeader, `{"ID":1,"ElectionID":1,"Date":"2020-07-28","Round":2}`, 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-07-28","Round":2,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[]}`)
+		do("PUT", "/api/Round/1", xsrfHeader, `{"ID":1,"ElectionID":1,"Date":"2020-07-28","Round":2}`, 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":1,"RoundID":1,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-07-28","Round":2,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[]}`)
+
+		// Update Parameters
+		do("PUT", "/api/Parameter/1", xsrfHeader, `{"ID":1,"RoundID":1,"CountBlankAndNull":true,"ShowOnlyCompleted":true,"ShowMap":true}`, 200, `{"ID":1,"RoundID":1,"CountBlankAndNull":true,"ShowOnlyCompleted":true,"ShowMap":true`)
 
 		// Create a DeskRound should fail with 400
 		do("POST", "/api/DeskRound", xsrfHeader, `{"Test":1,"Date":"2020-06-28","Round":1}`, 400, `method not allowed`)
diff --git a/internal/rootmux/capturer_test.go b/internal/rootmux/capturer_test.go
index 8e0e864..2628938 100644
--- a/internal/rootmux/capturer_test.go
+++ b/internal/rootmux/capturer_test.go
@@ -82,9 +82,9 @@ func CapturerTests(t *testing.T) {
 		// Create a round should fail with 405
 		do("POST", "/api/Round", xsrfHeader, `{"ElectionID":1,"Date":"2020-06-28","Round":1}`, 405, `You're not authorize to execute this method on this ressource.`)
 		// Get a desk
-		do("GET", "/api/Round/1", xsrfHeader, "", 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null},{"ID":2,"RoundID":1,"DeskID":2,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}`)
+		do("GET", "/api/Round/1", xsrfHeader, "", 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":1,"RoundID":1,"CountBlankAndNull":true,"ShowOnlyCompleted":true,"ShowMap":true},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null},{"ID":2,"RoundID":1,"DeskID":2,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}`)
 		// Get all the desks
-		do("GET", "/api/Round/", xsrfHeader, "", 200, `[{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null},{"ID":2,"RoundID":1,"DeskID":2,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}]`)
+		do("GET", "/api/Round/", xsrfHeader, "", 200, `[{"ID":1,"ElectionID":1,"Parameter":{"ID":1,"RoundID":1,"CountBlankAndNull":true,"ShowOnlyCompleted":true,"ShowMap":true},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null},{"ID":2,"RoundID":1,"DeskID":2,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}]`)
 		// Update a desk should fail with 405
 		do("PUT", "/api/Round/1", xsrfHeader, `{"ID":1,"ElectionID":1,"Date":"2020-07-28","Round":2}`, 405, `You're not authorize to execute this method on this ressource.`)
 		// Delete a desk should fail with 405
diff --git a/internal/rootmux/rootmux_test.go b/internal/rootmux/rootmux_test.go
index 6a8fb30..574aa7f 100644
--- a/internal/rootmux/rootmux_test.go
+++ b/internal/rootmux/rootmux_test.go
@@ -100,7 +100,7 @@ func appTests(t *testing.T) {
 		do("POST", "/api/Capturer", xsrfHeader, `{"UserID":2,"Name":"Capturer"}`, 500, `UserID is already bind to a Capturer`)
 
 		// Verify that RoundDesks have been created on Round Creation
-		do("GET", "/api/Round/1", xsrfHeader, ``, 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null},{"ID":2,"RoundID":1,"DeskID":2,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}`)
+		do("GET", "/api/Round/1", xsrfHeader, ``, 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":1,"RoundID":1,"CountBlankAndNull":true,"ShowOnlyCompleted":true,"ShowMap":true},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null},{"ID":2,"RoundID":1,"DeskID":2,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}`)
 		do("GET", "/api/Desk/1", xsrfHeader, ``, 200, `{"ID":1,"SectionID":1,"Name":"Desk 1","WitnessDesk":true,"Subscribed":9587,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}]}`)
 
 		// Verify that a DeskRound can't be validated witout being completed
@@ -287,7 +287,8 @@ func resetDataWithData(t *testing.T) {
 		do("POST", "/api/Section", xsrfHeader, `{"AreaID":1,"Name":"Section 1","MapID":"1"}`, 200, `{"ID":1,"AreaID":1,"Name":"Section 1","MapID":"1","Desks":null}`)
 		do("POST", "/api/Desk", xsrfHeader, `{"SectionID":1,"Name":"Desk 1","WitnessDesk":true,"Subscribed":9587}`, 200, `{"ID":1,"SectionID":1,"Name":"Desk 1","WitnessDesk":true,"Subscribed":9587,"DeskRounds":null}`)
 		do("POST", "/api/Desk", xsrfHeader, `{"SectionID":1,"Name":"Desk 2","WitnessDesk":false,"Subscribed":3784}`, 200, `{"ID":2,"SectionID":1,"Name":"Desk 2","WitnessDesk":false,"Subscribed":3784,"DeskRounds":null}`)
-		do("POST", "/api/Round", xsrfHeader, `{"ElectionID":1,"Date":"2020-06-28","Round":1}`, 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":null,"CandidateLists":null}`)
+		do("POST", "/api/Round", xsrfHeader, `{"ElectionID":1,"Date":"2020-06-28","Round":1}`, 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":1,"RoundID":1,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":null,"CandidateLists":null}`)
+		do("PUT", "/api/Parameter/1", xsrfHeader, `{"ID":1,"RoundID":1,"CountBlankAndNull":true,"ShowOnlyCompleted":true,"ShowMap":true}`, 200, `{"ID":1,"RoundID":1,"CountBlankAndNull":true,"ShowOnlyCompleted":true,"ShowMap":true`)
 		do("POST", "/api/Party", xsrfHeader, `{"Name":"MyGreatParty","Color":"#FFFFFF"}`, 200, `{"ID":1,"Name":"MyGreatParty","Color":"#FFFFFF","CandidateLists":null}`)
 		do("POST", "/api/CandidateList", xsrfHeader, `{"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1}`, 200, `{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}`)
 		do("POST", "/api/Candidate", xsrfHeader, `{"CandidateListID":1,"FullName":"Candidate","Rank":1,"CommunityCounseller":true,"Birthdate":"2020-06-28","PotentialIncompatibility":false,"Refused":false,"Removed":false}`, 200, `{"ID":1,"CandidateListID":1,"FullName":"Candidate","Rank":1,"CommunityCounseller":true,"Birthdate":"2020-06-28","PotentialIncompatibility":false,"Refused":false,"Removed":false}`)
diff --git a/internal/rootmux/visualizer_test.go b/internal/rootmux/visualizer_test.go
index 77c1c57..a956e75 100644
--- a/internal/rootmux/visualizer_test.go
+++ b/internal/rootmux/visualizer_test.go
@@ -69,9 +69,9 @@ func VisualizerTests(t *testing.T) {
 		// Create a round should fail with 405
 		do("POST", "/api/Round", xsrfHeader, `{"ElectionID":1,"Date":"2020-06-28","Round":1}`, 405, `You're not authorize to execute this method on this ressource.`)
 		// Get a desk
-		do("GET", "/api/Round/1", xsrfHeader, "", 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null},{"ID":2,"RoundID":1,"DeskID":2,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}`)
+		do("GET", "/api/Round/1", xsrfHeader, "", 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":1,"RoundID":1,"CountBlankAndNull":true,"ShowOnlyCompleted":true,"ShowMap":true},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null},{"ID":2,"RoundID":1,"DeskID":2,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}`)
 		// Get all the desks
-		do("GET", "/api/Round/", xsrfHeader, "", 200, `[{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null},{"ID":2,"RoundID":1,"DeskID":2,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}]`)
+		do("GET", "/api/Round/", xsrfHeader, "", 200, `[{"ID":1,"ElectionID":1,"Parameter":{"ID":1,"RoundID":1,"CountBlankAndNull":true,"ShowOnlyCompleted":true,"ShowMap":true},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null},{"ID":2,"RoundID":1,"DeskID":2,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}]`)
 		// Update a desk should fail with 405
 		do("PUT", "/api/Round/1", xsrfHeader, `{"ID":1,"ElectionID":1,"Date":"2020-07-28","Round":2}`, 405, `You're not authorize to execute this method on this ressource.`)
 		// Delete a desk should fail with 405
diff --git a/web/components/help/management-help.js b/web/components/help/management-help.js
index c9349fa..c59a4dc 100644
--- a/web/components/help/management-help.js
+++ b/web/components/help/management-help.js
@@ -10,178 +10,207 @@ class ManagementHelper {
     const mountpoint = where;
     document.getElementById(mountpoint).innerHTML = /* HTML */ `
       <div class="card-no-hover helper">
-        <h2 class="title is-2">Aide pour la gestion d'une élection</h2>
-
-        <h5 class="title is-5">Onglet Partis politiques</h5>
-
-        L'onglet "Partis Politiques" permet de créer des tendances poltiques qui
-        se retrouvent sur plusieurs circonscriptions.<br />
-        Pour ajuter un parti il suffit de cliquer sur
-        <span class="icon is-small">
-          <i class="fas fa-plus"></i>
-        </span>
-        Un fenêtre s'ouvre et permet de saisir le nom du parti (ou de la
-        tendance politique) et de choisir la couleur qui sera associé à ce parti
-        dans l'affichage des résultats. Pour modifier un parti, il suffit de
-        cliquer sur
-        <span class="icon is-small">
-          <i class="fas fa-pen"></i>
-        </span>
-        et pour en supprimer un sur
-        <span class="icon is-small"> <i class="fas fa-trash"></i> </span>. La
-        suppression affichera l'ensemble des listes qui sont rattachées à ce
-        parti sur toutes les élections et demandera de confirmer l'action de
-        suppression car toutes ces listes seront alors supprimées.<br /><br />
-
-        <h5 class="title is-5">Onglet Élection</h5>
-        L'onglet élection permet de créer le découpage territorial d'une
-        élection. Il est possible de naviguer dans l'abrorescence de l'élection
-        en cliquant sur les différentes zones la composant<br /><br />
-
-        <article class="message is-warning">
-          <div class="message-header">
-            <p>Attention</p>
-          </div>
-          <div class="message-body">
-            Il est necessaire pour que l'application fonctionne corrctement que
-            chaque élection ai au moins une circonscription composée elle même
-            d'au moins une ville avec au minimum un bureau.
-          </div>
-        </article>
-
-        <strong>Élection</strong>
-        Pour créer une nouvelle élection cliquer sur
-        <span class="icon is-small"> <i class="fas fa-plus"></i> </span>, une
-        fenêtre s'ouvre alors, il faut saisir le nom de cette élection, choisir
-        le système de vote qui sera appliqué (pour calculer les résultats),
-        selectionner la carte pour les cirocnscriptions et celle pour les
-        villes. En cliquant sur "Sauvegarder" l'élection sera ajoutée et en
-        cliquant sur "Sauvegarder (ajouter circonscription)" elle sera
-        sauvegardée et la fenêtre pour y attacher une circonscription sera
-        ouverte.<br /><br />
-
-        <strong>Circonscription</strong>
-        Une circonscription peut être ajoutée à l'élection selectionnée en
-        cliquant sur
-        <span class="icon is-small"><i class="fas fa-plus"></i></span> . Il faut
-        alors saisir son nom, le nombre de siège à pourvoir et l'identifiant qui
-        lui correspond sur la carte des circonscriptions. <br />En cliquant sur
-        "Sauvegarder" la circonscription sera ajoutée et en cliquant sur
-        "Sauvegarder (ajouter circonscription)" elle sera ajoutée et la fenêtre
-        pour ajouter une nouvelle circonscription à l'élection sera ouverte. En
-        cliquant sur "Sauvegarder (ajouter ville)" elle sera ajoutée et la
-        fenêtre pour ajouter une ville à la circonscription sera ouverte.<br /><br />
-
-        <strong>Ville</strong>
-        Une ville peut être ajoutée à une circonscription selectionnée en
-        cliquant sur
-        <span class="icon is-small"><i class="fas fa-plus"></i></span> . Il faut
-        alors saisir son nom et l'identifiant qui lui correspond sur la carte
-        des villes. <br />En cliquant sur "Sauvegarder" la ville sera ajoutée et
-        en cliquant sur "Sauvegarder (ajouter ville)" elle sera ajoutée et la
-        fenêtre pour ajouter une nouvelle ville à la circonscription sera
-        ouverte. En cliquant sur "Sauvegarder (ajouter bureau)" elle sera
-        ajoutée et la fenêtre pour ajouter un bureau à la ville sera ouverte.<br /><br />
-
-        <strong>Bureau</strong>
-        Un bureau peut être ajoutée à une ville selectionnée en cliquant sur
-        <span class="icon is-small"><i class="fas fa-plus"></i></span> . Il faut
-        alors saisir son nom et le nombre d'inscrit dans ce bureau.
-        <article class="message is-warning">
-          <div class="message-header">
-            <p>Attention</p>
-            <button class="delete" aria-label="delete"></button>
-          </div>
-          <div class="message-body">
-            Le nombre d'inscrit est obligatoire car il permet de calculer le
-            taux d'abstention et le pourcentage de saisie en cours qui est
-            pondéré par le nombre d'inscrits par bureau.
-          </div>
-        </article>
-        En cliquant sur "Sauvegarder" le bureau sera ajouté et en cliquant sur
-        "Sauvegarder (ajouter bureau)" il sera ajouté et la fenêtre pour ajouter
-        un nouveau bureau à la ville sera ouverte.<br /><br />
-
-        <strong>Cloner une élection</strong>
-        Il est possible de cloner une élection et son arborescence en cliquant
-        sur
-        <span class="icon is-small">
-          <i class="fas fa-clone"></i>
-        </span>
-        dans la liste des élections.<br /><br />
-
-        <h5 class="title is-5">Onglet Tour</h5>
-        Une élection est composée de un ou plusieurs tours, il faut donc
-        configuré ces tours pour utiliser l'application.<br /><br />
-
-        Dans l'onglet "Tour" il est possible de créer un tour pour une élection
-        en cliquant sur
-        <span class="icon is-small"><i class="fas fa-plus"></i></span>. Il faut
-        alors sélectionner l'élection à laquelle on crée un tour, la date du
-        tour et le numéro du tour. Il est possible de modifier les informations
-        en cliquant sur
-        <span class="icon is-small"><i class="fas fa-pen"></i></span> et de le
-        supprimer en cliquant sur
-        <span class="icon is-small"><i class="fas fa-trash"></i></span
-        ><br /><br />
-
-        En cliquant sur un tour, on rentre dans la configuration du tour. La
-        bouton sur la gauche permet de revenir à l'écran précédent. Il y a deux
-        sections qui peuvent être ouvertes l'une après l'autre, il faut cliquer
-        sur la barre de titre de l'une des deux sections pour en changer.<br /><br />
-
-        <strong>Section "Bureaux de Votes"</strong><br />
-        La liste de tous les bureaux de votes de l'éelection s'affiche ici. En
-        cliquant sur un bureau les détails de ce bureau s'affiche. Il est
-        possible de consulter les votes et de les modifier comme dans l'onglet
-        "Votes". Il est possible de "Valider" un bureau, les votes de ce bureau
-        ne pourront alors plus être modifié.<br /><br />
-
-        Pour ajouter un utilisateur à la liste des saisseurs, cliquer sur
-        l'icône
-        <span class="icon is-small">
-          <i class="fas fa-user"></i>
-        </span>
-        pour ouvrir la liste des saisisseurs de ce burea, il suffit ensuite de
-        cliquer sur l'utilisateur dans la colonne de droite "Saisisseurs
-        disponibles", il va alors basculer dans la colonne de gauche
-        "Saisisseurs actifs". Pour le supprimer il suffit de faire de même dans
-        la colonne de gauche "Saisisseurs actifs", il va alors basculer dans la
-        colonne de droite "Saisisseurs disponibles".<br /><br />
-
-        <strong>Section "Liste des candidats par circonscription"</strong>
-        La liste des circonscription du tour est affichée, en cliquant sur une
-        circonscription, la liste des listes de candidats de la circonscription
-        s'affiche. Il est possible d'en créer une en cliquant sur
-        <span class="icon is-small"> <i class="fas fa-plus"></i>. </span>Une
-        fenêtre s'ouvre alors pour saisir le nom de la liste et le parti auquel
-        elle est attachée. En cliquant sur
-        <span class="icon is-small">
-          <i class="fas fa-download"></i>
-        </span>
-        Une fenêtre s'ouvre et permet d'importer une liste directement depuis un
-        tour précédent pour éviter de resaisir toutes les informations de la
-        listes.<br /><br />
-
-        En cliquant sur une liste, les canddiats s'affichent, il est possible
-        d'ajouter et de modifier des candidats en éditant les champs dans le
-        tableau et en cliquant sur
-        <span class="icon is-small">
-          <i class="fas fa-check"></i>
-        </span>
-        pour chaque ligne éditée afin qu'elles soient prises en compte.<br />
-        Le rang ordonne les candidats lors du calcul des élus, la date de
-        naissance permet de départager des listes et des candidats lors du
-        calcul des élus en cas d'égalité, la case incompatibilité potentielle
-        permet d'identifier les élus qui pourraient poser problème s'ils sont
-        élus (autre mandat en cours, fonctionnaire...). La case refusé permet
-        d'éliminer du calcul des élus des candidats qui se sont porter sur une
-        liste mais qui finalement ne souhaient pas siéger. La case supprimer
-        permet d'éliminer du calcul des élus les candidats qui seraient décédé
-        entre la date de cloture de dépôt des listes et l'élection. (La case
-        "Conseiller communautaire" n'est pas fonctionelle mais permettrai
-        d'identifier les élus qui vont siéger au conseil communautaire lors
-        d'élection municipale)
+        <div class="content">
+          <h2 class="title is-2">Aide pour la gestion d'une élection</h2>
+
+          <h5 class="title is-5">Onglet Partis politiques</h5>
+
+          L'onglet "Partis Politiques" permet de créer des tendances poltiques
+          qui se retrouvent sur plusieurs circonscriptions.<br />
+          Pour ajuter un parti il suffit de cliquer sur
+          <span class="icon is-small">
+            <i class="fas fa-plus"></i>
+          </span>
+          Un fenêtre s'ouvre et permet de saisir le nom du parti (ou de la
+          tendance politique) et de choisir la couleur qui sera associé à ce
+          parti dans l'affichage des résultats. Pour modifier un parti, il
+          suffit de cliquer sur
+          <span class="icon is-small">
+            <i class="fas fa-pen"></i>
+          </span>
+          et pour en supprimer un sur
+          <span class="icon is-small"> <i class="fas fa-trash"></i> </span>. La
+          suppression affichera l'ensemble des listes qui sont rattachées à ce
+          parti sur toutes les élections et demandera de confirmer l'action de
+          suppression car toutes ces listes seront alors supprimées.<br /><br />
+
+          <h5 class="title is-5">Onglet Élection</h5>
+          L'onglet élection permet de créer le découpage territorial d'une
+          élection. Il est possible de naviguer dans l'abrorescence de
+          l'élection en cliquant sur les différentes zones la composant<br /><br />
+
+          <article class="message is-warning">
+            <div class="message-header">
+              <p>Attention</p>
+            </div>
+            <div class="message-body">
+              Il est necessaire pour que l'application fonctionne corrctement
+              que chaque élection ai au moins une circonscription composée elle
+              même d'au moins une ville avec au minimum un bureau.
+            </div>
+          </article>
+
+          <strong>Élection</strong>
+          Pour créer une nouvelle élection cliquer sur
+          <span class="icon is-small"> <i class="fas fa-plus"></i> </span>, une
+          fenêtre s'ouvre alors, il faut saisir le nom de cette élection,
+          choisir le système de vote qui sera appliqué (pour calculer les
+          résultats), selectionner la carte pour les cirocnscriptions et celle
+          pour les villes. En cliquant sur "Sauvegarder" l'élection sera ajoutée
+          et en cliquant sur "Sauvegarder (ajouter circonscription)" elle sera
+          sauvegardée et la fenêtre pour y attacher une circonscription sera
+          ouverte.<br /><br />
+
+          <strong>Circonscription</strong>
+          Une circonscription peut être ajoutée à l'élection selectionnée en
+          cliquant sur
+          <span class="icon is-small"><i class="fas fa-plus"></i></span> . Il
+          faut alors saisir son nom, le nombre de siège à pourvoir et
+          l'identifiant qui lui correspond sur la carte des circonscriptions.
+          <br />En cliquant sur "Sauvegarder" la circonscription sera ajoutée et
+          en cliquant sur "Sauvegarder (ajouter circonscription)" elle sera
+          ajoutée et la fenêtre pour ajouter une nouvelle circonscription à
+          l'élection sera ouverte. En cliquant sur "Sauvegarder (ajouter ville)"
+          elle sera ajoutée et la fenêtre pour ajouter une ville à la
+          circonscription sera ouverte.<br /><br />
+
+          <strong>Ville</strong>
+          Une ville peut être ajoutée à une circonscription selectionnée en
+          cliquant sur
+          <span class="icon is-small"><i class="fas fa-plus"></i></span> . Il
+          faut alors saisir son nom et l'identifiant qui lui correspond sur la
+          carte des villes. <br />En cliquant sur "Sauvegarder" la ville sera
+          ajoutée et en cliquant sur "Sauvegarder (ajouter ville)" elle sera
+          ajoutée et la fenêtre pour ajouter une nouvelle ville à la
+          circonscription sera ouverte. En cliquant sur "Sauvegarder (ajouter
+          bureau)" elle sera ajoutée et la fenêtre pour ajouter un bureau à la
+          ville sera ouverte.<br /><br />
+
+          <strong>Bureau</strong>
+          Un bureau peut être ajoutée à une ville selectionnée en cliquant sur
+          <span class="icon is-small"><i class="fas fa-plus"></i></span> . Il
+          faut alors saisir son nom et le nombre d'inscrit dans ce bureau.
+          <article class="message is-warning">
+            <div class="message-header">
+              <p>Attention</p>
+              <button class="delete" aria-label="delete"></button>
+            </div>
+            <div class="message-body">
+              Le nombre d'inscrit est obligatoire car il permet de calculer le
+              taux d'abstention et le pourcentage de saisie en cours qui est
+              pondéré par le nombre d'inscrits par bureau.
+            </div>
+          </article>
+          En cliquant sur "Sauvegarder" le bureau sera ajouté et en cliquant sur
+          "Sauvegarder (ajouter bureau)" il sera ajouté et la fenêtre pour
+          ajouter un nouveau bureau à la ville sera ouverte.<br /><br />
+
+          <strong>Cloner une élection</strong>
+          Il est possible de cloner une élection et son arborescence en cliquant
+          sur
+          <span class="icon is-small">
+            <i class="fas fa-clone"></i>
+          </span>
+          dans la liste des élections.<br /><br />
+
+          <h5 class="title is-5">Onglet Tour</h5>
+          Une élection est composée de un ou plusieurs tours, il faut donc
+          configuré ces tours pour utiliser l'application.<br /><br />
+
+          Dans l'onglet "Tour" il est possible de créer un tour pour une
+          élection en cliquant sur
+          <span class="icon is-small"><i class="fas fa-plus"></i></span>. Il
+          faut alors sélectionner l'élection à laquelle on crée un tour, la date
+          du tour et le numéro du tour. Il est possible de modifier les
+          informations en cliquant sur
+          <span class="icon is-small"><i class="fas fa-pen"></i></span> et de le
+          supprimer en cliquant sur
+          <span class="icon is-small"><i class="fas fa-trash"></i></span
+          ><br /><br />
+
+          En cliquant sur un tour, on rentre dans la configuration du tour. La
+          bouton sur la gauche permet de revenir à l'écran précédent. Il y a
+          deux sections qui peuvent être ouvertes l'une après l'autre, il faut
+          cliquer sur la barre de titre de l'une des deux sections pour en
+          changer.<br /><br />
+
+          <em>Options d'un tour</em><br /><br />
+
+          Trois options sont disponibles :<br />
+
+          <ul>
+            <li>
+              <em>Afficher une cartographie des résultats : </em>Permet
+              d'afficher une carte des résultats. Si cette option est cochée
+              il est nécessaire de saisir les identifiants de carte pour les
+              circonscriptions et les villes.
+            </li>
+            <li>
+              <em>Comptabiliser les votes blancs et nuls : </em>Oblige à saisir
+              les votes blancs et nuls pour compléter un bureau de vote.
+            </li>
+            <li>
+              <em>Ne pas afficher de résultats partiel : </em>Empêche la
+              possibilité d'afficher les résultats partiels dans l'onglet
+              "Résultats"
+            </li>
+          </ul>
+          <br />
+
+          <strong>Section "Bureaux de Votes"</strong><br />
+          La liste de tous les bureaux de votes de l'éelection s'affiche ici. En
+          cliquant sur un bureau les détails de ce bureau s'affiche. Il est
+          possible de consulter les votes et de les modifier comme dans l'onglet
+          "Votes". Il est possible de "Valider" un bureau, les votes de ce
+          bureau ne pourront alors plus être modifié.<br /><br />
+
+          Pour ajouter un utilisateur à la liste des saisseurs, cliquer sur
+          l'icône
+          <span class="icon is-small">
+            <i class="fas fa-user"></i>
+          </span>
+          pour ouvrir la liste des saisisseurs de ce burea, il suffit ensuite de
+          cliquer sur l'utilisateur dans la colonne de droite "Saisisseurs
+          disponibles", il va alors basculer dans la colonne de gauche
+          "Saisisseurs actifs". Pour le supprimer il suffit de faire de même
+          dans la colonne de gauche "Saisisseurs actifs", il va alors basculer
+          dans la colonne de droite "Saisisseurs disponibles".<br /><br />
+
+          <strong>Section "Liste des candidats par circonscription"</strong>
+          La liste des circonscription du tour est affichée, en cliquant sur une
+          circonscription, la liste des listes de candidats de la
+          circonscription s'affiche. Il est possible d'en créer une en cliquant
+          sur
+          <span class="icon is-small"> <i class="fas fa-plus"></i>. </span>Une
+          fenêtre s'ouvre alors pour saisir le nom de la liste et le parti
+          auquel elle est attachée. En cliquant sur
+          <span class="icon is-small">
+            <i class="fas fa-download"></i>
+          </span>
+          Une fenêtre s'ouvre et permet d'importer une liste directement depuis
+          un tour précédent pour éviter de resaisir toutes les informations de
+          la listes.<br /><br />
+
+          En cliquant sur une liste, les canddiats s'affichent, il est possible
+          d'ajouter et de modifier des candidats en éditant les champs dans le
+          tableau et en cliquant sur
+          <span class="icon is-small">
+            <i class="fas fa-check"></i>
+          </span>
+          pour chaque ligne éditée afin qu'elles soient prises en compte.<br />
+          Le rang ordonne les candidats lors du calcul des élus, la date de
+          naissance permet de départager des listes et des candidats lors du
+          calcul des élus en cas d'égalité, la case incompatibilité potentielle
+          permet d'identifier les élus qui pourraient poser problème s'ils sont
+          élus (autre mandat en cours, fonctionnaire...). La case refusé permet
+          d'éliminer du calcul des élus des candidats qui se sont porter sur une
+          liste mais qui finalement ne souhaient pas siéger. La case supprimer
+          permet d'éliminer du calcul des élus les candidats qui seraient décédé
+          entre la date de cloture de dépôt des listes et l'élection. (La case
+          "Conseiller communautaire" n'est pas fonctionelle mais permettrai
+          d'identifier les élus qui vont siéger au conseil communautaire lors
+          d'élection municipale)
+        </div>
       </div>
     `;
   }
diff --git a/web/components/management/rounds-card.js b/web/components/management/rounds-card.js
index d23545f..0370823 100644
--- a/web/components/management/rounds-card.js
+++ b/web/components/management/rounds-card.js
@@ -139,6 +139,36 @@ class Round {
               <input class="input" type="number" id="round-modal-round" />
             </div>
           </div>
+          <div class="field">
+            <label class="checkbox">
+              <input
+                class="input"
+                type="checkbox"
+                id="parameter-modal-showmap"
+              />
+              Afficher une cartographie des résultats
+            </label>
+          </div>
+          <div class="field">
+            <label class="checkbox">
+              <input
+                class="input"
+                type="checkbox"
+                id="parameter-modal-countblankandnull"
+              />
+              Comptabiliser les votes blancs et nuls
+            </label>
+          </div>
+          <div class="field">
+            <label class="checkbox">
+              <input
+                class="input"
+                type="checkbox"
+                id="parameter-modal-onlycompletedresults"
+              />
+              Ne pas afficher de résultats partiel
+            </label>
+          </div>
         </section>
         <footer class="modal-card-foot">
           <button id="round-modal-save" class="button is-success">
@@ -183,7 +213,7 @@ class Round {
           });
         });
       document
-        .getElementById(`rounds-round-${round.ID}`)
+        .getElementById(`rounds-round-desc-${round.ID}`)
         .addEventListener("click", async function () {
           roundHandler.activateRound(round);
         });
@@ -262,6 +292,13 @@ class Round {
     document.getElementById("round-modal-id").value = null;
     document.getElementById("round-modal-date").value = null;
     document.getElementById("round-modal-round").value = null;
+    document.getElementById(
+      "parameter-modal-countblankandnull"
+    ).checked = false;
+    document.getElementById("parameter-modal-showmap").checked = false;
+    document.getElementById(
+      "parameter-modal-onlycompletedresults"
+    ).checked = false;
     Common.toggleModal("round-modal", "round-modal-card");
   }
 
@@ -274,6 +311,12 @@ class Round {
       .toISOString()
       .split("T")[0];
     document.getElementById("round-modal-round").value = round.Round;
+    document.getElementById("parameter-modal-countblankandnull").checked =
+      round.Parameter.CountBlankAndNull;
+    document.getElementById("parameter-modal-showmap").checked =
+      round.Parameter.ShowMap;
+    document.getElementById("parameter-modal-onlycompletedresults").checked =
+      round.Parameter.ShowOnlyCompleted;
     Common.toggleModal("round-modal", "round-modal-card");
   }
 
@@ -288,6 +331,13 @@ class Round {
       document.getElementById("round-modal-date").value,
       parseInt(document.getElementById("round-modal-round").value)
     );
+
+    await this.RoundModel.saveParameter(
+      round.Parameter.ID,
+      document.getElementById("parameter-modal-countblankandnull").checked,
+      document.getElementById("parameter-modal-showmap").checked,
+      document.getElementById("parameter-modal-onlycompletedresults").checked
+    );
     await this.displayRounds();
     Common.toggleModal("round-modal", "round-modal-card");
     this.activateRound(round);
diff --git a/web/components/visualization/results-general.js b/web/components/visualization/results-general.js
index 3061f4d..74a7d86 100644
--- a/web/components/visualization/results-general.js
+++ b/web/components/visualization/results-general.js
@@ -15,7 +15,7 @@ class ResultGeneralComponent {
   async mount(where) {
     const mountpoint = where;
     document.getElementById(mountpoint).innerHTML = /* HTML */ `
-      <div class="column is-half">
+      <div class="column">
         <div class="card-no-hover">
           <header class="card-header">
             <p class="card-header-title">
@@ -26,16 +26,6 @@ class ResultGeneralComponent {
           <div id="round-detaileds-results" class="content"></div>
         </div>
       </div>
-      <div class="column is-half">
-        <div class="card-no-hover">
-          <header class="card-header">
-            <p class="card-header-title">
-              Statistiques
-            </p>
-          </header>
-          <div id="stats-results" class="content"></div>
-        </div>
-      </div>
     `;
     this.handleDom();
     this.displayRoundResults();
diff --git a/web/components/visualization/results-zone.js b/web/components/visualization/results-zone.js
index 1279afa..f8a41a1 100644
--- a/web/components/visualization/results-zone.js
+++ b/web/components/visualization/results-zone.js
@@ -40,7 +40,7 @@ class ResultZoneComponent {
               </span>
             </button>
           </header>
-          <div id="map-component" class="card-content">Cartes</div>
+          <div id="map-component" class="card-content">Les cartes ne sont pas activées pour ce tour</div>
         </div>
       </div>
       <div class="column">
@@ -92,7 +92,8 @@ class ResultZoneComponent {
     `;
     this.ResultsFlow = await ResultsFlow.mount(this);
     this.ResultsDetaileds = await ResultsDetaileds.mount(this);
-    this.ResultsMap = await ResultsMap.mount(this);
+    if (this.parent.round.Parameter.ShowMap)
+      this.ResultsMap = await ResultsMap.mount(this);
     this.scroller = Scroller.scrollInit(
       "news-flow",
       document.getElementById("auto-scroll")
@@ -232,22 +233,26 @@ class ResultZoneComponent {
     if (this.parent.zone === "areas") {
       await this.ResultsFlow.displayFlowAreas();
       this.ResultsDetaileds.displayAreasResults();
-      if (this.ResultsMap.map.getSource("data-source") !== undefined) {
-        let dataSource = await this.ResultsMap.colorAreas(
-          this.ResultsMap,
-          this.ResultsMap.dataSource
-        );
-        this.ResultsMap.map.getSource("data-source").setData(dataSource);
+      if (this.parent.round.Parameter.ShowMap) {
+        if (this.ResultsMap.map.getSource("data-source") !== undefined) {
+          let dataSource = await this.ResultsMap.colorAreas(
+            this.ResultsMap,
+            this.ResultsMap.dataSource
+          );
+          this.ResultsMap.map.getSource("data-source").setData(dataSource);
+        }
       }
     } else if (this.parent.zone === "sections") {
       await this.ResultsFlow.displayFlowSections();
       this.ResultsDetaileds.displaySectionsResults();
-      if (this.ResultsMap.map.getSource("data-source") !== undefined) {
-        let dataSource = await this.ResultsMap.colorSections(
-          this.ResultsMap,
-          this.ResultsMap.dataSource
-        );
-        this.ResultsMap.map.getSource("data-source").setData(dataSource);
+      if (this.parent.round.Parameter.ShowMap) {
+        if (this.ResultsMap.map.getSource("data-source") !== undefined) {
+          let dataSource = await this.ResultsMap.colorSections(
+            this.ResultsMap,
+            this.ResultsMap.dataSource
+          );
+          this.ResultsMap.map.getSource("data-source").setData(dataSource);
+        }
       }
     }
   }
diff --git a/web/components/visualization/visualization-section.js b/web/components/visualization/visualization-section.js
index 26bcfa9..b81c0de 100644
--- a/web/components/visualization/visualization-section.js
+++ b/web/components/visualization/visualization-section.js
@@ -46,12 +46,19 @@ class ResultComponent {
         </ul>
       </div>
       <div class="control filter">
+        ${this.round.Parameter.ShowOnlyCompleted
+          ? ``
+          : /* HTML */ `<label class="radio">
+              <input type="radio" name="filter" value="partial" checked />
+              Partiel
+            </label>`}
         <label class="radio">
-          <input type="radio" name="filter" value="partial" checked />
-          Partiel
-        </label>
-        <label class="radio">
-          <input type="radio" name="filter" value="completed" />
+          <input
+            type="radio"
+            name="filter"
+            value="completed"
+            ${this.round.Parameter.ShowOnlyCompleted ? `checked` : ``}
+          />
           Complété
         </label>
         <label class="radio">
@@ -120,15 +127,16 @@ class ResultComponent {
   }
 
   async hideGeneralSection() {
-    let resultHandler = this;
     document.getElementById("general").setAttribute("class", "");
     document.getElementById("results-zone").style.display = "flex";
     document.getElementById("results-general").style.display = "none";
-    await resultHandler.calculateResults();
-    if (resultHandler.zone == "areas")
-      await this.resultsZone.ResultsMap.displayMapAreas();
-    else await this.resultsZone.ResultsMap.displayMapSections();
-    await resultHandler.resultsZone.displayResults();
+    await this.calculateResults();
+    if (this.round.Parameter.ShowMap) {
+      if (this.zone == "areas")
+        await this.resultsZone.ResultsMap.displayMapAreas();
+      else await this.resultsZone.ResultsMap.displayMapSections();
+    }
+    await this.resultsZone.displayResults();
   }
 
   async calculateResults() {
diff --git a/web/components/vote/votes.js b/web/components/vote/votes.js
index 6aa38a1..8fbace6 100644
--- a/web/components/vote/votes.js
+++ b/web/components/vote/votes.js
@@ -185,7 +185,9 @@ class Vote {
       .map((vote) => this.voteTemplate(vote))
       .join("");
     document.getElementById("votes-list").innerHTML = markup;
-    document.getElementById("votes-list").innerHTML += /* HTML */ `
+    let round = await this.RoundModel.getRound(this.RoundID);
+    if (round.Parameter.CountBlankAndNull) {
+      document.getElementById("votes-list").innerHTML += /* HTML */ `
     <tr">
       <td>Votes blanc</td>
       <td>
@@ -197,24 +199,27 @@ class Vote {
       </td>
     </tr>
   `;
-    document.getElementById("votes-list").innerHTML += /* HTML */ `
-      <tr>
-        <td>Votes nul</td>
-        <td>
-          <input class="input" type="number" id="null-vote-voice" />
-        </td>
-      </tr>
-    `;
+      document.getElementById("votes-list").innerHTML += /* HTML */ `
+        <tr>
+          <td>Votes nul</td>
+          <td>
+            <input class="input" type="number" id="null-vote-voice" />
+          </td>
+        </tr>
+      `;
+    }
 
     let totalVotes = 0;
     let votesExpressed = 0;
     votes.forEach((vote) => {
-      totalVotes += vote.VoiceNumber;
-      if (vote.Blank) {
+      if (vote.Blank && round.Parameter.CountBlankAndNull) {
+        totalVotes += vote.VoiceNumber;
         document.getElementById("blank-vote-voice").value = vote.VoiceNumber;
-      } else if (vote.NullVote) {
+      } else if (vote.NullVote && round.Parameter.CountBlankAndNull) {
+        totalVotes += vote.VoiceNumber;
         document.getElementById("null-vote-voice").value = vote.VoiceNumber;
-      } else {
+      } else if (!vote.Blank && !vote.NullVote) {
+        totalVotes += vote.VoiceNumber;
         votesExpressed += vote.VoiceNumber;
         document.getElementById(vote.CandidateListID + "-vote-voice").value =
           vote.VoiceNumber;
@@ -268,40 +273,35 @@ class Vote {
       );
     }
 
-    if (
-      parseInt(
-        document.getElementById("blank-vote-voice").value
-      ) < 0
-    ) {
-      Messages.Show("is-warning", "Erreur sur les valeurs saisies");
-      return;
-    } else {
-      await this.VoteModel.saveVote(
-        method,
-        this.DeskRoundID,
-        null,
-        parseInt(document.getElementById("blank-vote-voice").value),
-        true,
-        false
-      );
-    }
+    let round = await this.RoundModel.getRound(this.RoundID);
+    if (round.Parameter.CountBlankAndNull) {
+      if (parseInt(document.getElementById("blank-vote-voice").value) < 0) {
+        Messages.Show("is-warning", "Erreur sur les valeurs saisies");
+        return;
+      } else {
+        await this.VoteModel.saveVote(
+          method,
+          this.DeskRoundID,
+          null,
+          parseInt(document.getElementById("blank-vote-voice").value),
+          true,
+          false
+        );
+      }
 
-    if (
-      parseInt(
-        document.getElementById("null-vote-voice").value
-      ) < 0
-    ) {
-      Messages.Show("is-warning", "Erreur sur les valeurs saisies");
-      return;
-    } else {
-      await this.VoteModel.saveVote(
-        method,
-        this.DeskRoundID,
-        null,
-        parseInt(document.getElementById("null-vote-voice").value),
-        false,
-        true
-      );
+      if (parseInt(document.getElementById("null-vote-voice").value) < 0) {
+        Messages.Show("is-warning", "Erreur sur les valeurs saisies");
+        return;
+      } else {
+        await this.VoteModel.saveVote(
+          method,
+          this.DeskRoundID,
+          null,
+          parseInt(document.getElementById("null-vote-voice").value),
+          false,
+          true
+        );
+      }
     }
     this.refreshParent();
   }
diff --git a/web/services/model/round-model.js b/web/services/model/round-model.js
index 295b1bc..04731b1 100644
--- a/web/services/model/round-model.js
+++ b/web/services/model/round-model.js
@@ -46,21 +46,18 @@ class RoundModel {
 
   async saveRound(method, ID, ElectionID, Date, Round) {
     try {
-        const response = await fetch(
-            "/api/Round/" + ID,
-            {
-              method: method,
-              headers: new Headers({
-                "XSRF-Token": this.current_user.xsrftoken,
-              }),
-              body: JSON.stringify({
-                ID: ID,
-                ElectionID: ElectionID,
-                Date: Date,
-                Round: Round,
-              }),
-            }
-          );
+      const response = await fetch("/api/Round/" + ID, {
+        method: method,
+        headers: new Headers({
+          "XSRF-Token": this.current_user.xsrftoken,
+        }),
+        body: JSON.stringify({
+          ID: ID,
+          ElectionID: ElectionID,
+          Date: Date,
+          Round: Round,
+        }),
+      });
       if (response.status !== 200) {
         throw new Error(
           `Round could not be updated or created (status ${response.status})`
@@ -99,4 +96,37 @@ class RoundModel {
     this.rounds = null;
     await this.getRounds();
   }
+
+  async saveParameter(
+    ID,
+    countBlankAndNull,
+    showMap,
+    showOnlyCompleted
+  ) {
+    try {
+      const response = await fetch("/api/Parameter/" + ID, {
+        method: "PUT",
+        headers: new Headers({
+          "XSRF-Token": this.current_user.xsrftoken,
+        }),
+        body: JSON.stringify({
+          ID: ID,
+          CountBlankAndNull: countBlankAndNull,
+          ShowMap: showMap,
+          ShowOnlyCompleted: showOnlyCompleted,
+        }),
+      });
+      if (response.status !== 200) {
+        throw new Error(
+          `Parameter could not be updated (status ${response.status})`
+        );
+      }
+      this.refreshRounds();
+      return await response.json();
+    } catch (e) {
+      Messages.Show("is-warning", e.message);
+      console.error(e);
+      return;
+    }
+  }
 }
-- 
GitLab