diff --git a/data/test.db b/data/test.db index 9bc1a6ad1c39b57807326f33a2b4e09dd863fb6f..e845823db7d6ac62c9be7b05180e57ab3cf69e6f 100644 Binary files a/data/test.db and b/data/test.db differ diff --git a/internal/models/models.go b/internal/models/models.go index f389279f00a904ac87e0585892d87fdc005e5eb0..3da7356cd0db8b973c426b8965036aaf229a0ede 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 0000000000000000000000000000000000000000..1198536800d6bf2ce01ac200dbd20a83cc35e65e --- /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(¶meter) + 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 d542d0378be8a96664610a9d56f189bbbd50fb78..2e953f45c133d436b61702f15bb0f39febf3b6ad 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(¶meter) + d.db.Last(¶meter) + 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 72d07f5ffc6183f8f7a77c3546c256b9bde82c08..7cb688bde5cc0590551cee77d41ad3de6031515f 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 554015f750b32357a71f877aa1c95cb1205810c7..89082be8852771fb9caa944a5cc4b8086a22b27e 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 8e0e864196f2f90e33b99d1b48ee1b51c553dcef..2628938a3386d3637718f4cba0a4033a3050c681 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 6a8fb30934c91a5c6f78b736ffe456509d3ba3a8..574aa7fb8cbfb48360a9b7dacaac64540cc8cfb0 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 77c1c57050974e4fce1f1519d97668575fa47623..a956e75401a8453628bea4de3da97361f7c8f515 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 c9349fa9d5be8001289a50347fa7c89f45b417c9..c59a4dc91e2016832901ff9709fc55cfcbe8f4a7 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 d23545f0ffa10497aba9561ce4edc846cbdfa137..03708238ba7d5e56aaba6d75be589ad7b75295f4 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 3061f4dac640f38568b9a37dbdb9c1110480015a..74a7d86b16daacdd7625acbdd24cd9fce503c8e4 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 1279afa9e4961b60c529449e8d260a742c8eaf76..f8a41a18fe7db54ea2402b11214a1695063828c3 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 26bcfa98a3989f6c22273687cbd19a3d42a4a6fb..b81c0deee53c3e2e1b4e63bc7a2275582a8c2ced 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 6aa38a11a557f8a0f97ce15ac7624ae61ea31dd3..8fbace6fea18e7c371e1a7a11db023a7499337f0 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 295b1bca6dff30efc50e4ec0ff61b27c2112c450..04731b130cc6dde7c01dd1cede5b2c51c749289b 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; + } + } }