Skip to content
Snippets Groups Projects
Commit 6f416fbf authored by Alexis POYEN's avatar Alexis POYEN
Browse files

Merge branch '47-capture-votes' into 'master'

Resolve "Capture votes"

Closes #47

See merge request apoyen/elections!42
parents bb626aca 408fc0df
No related branches found
No related tags found
1 merge request!42Resolve "Capture votes"
Pipeline #6009 passed
......@@ -35,7 +35,7 @@ func (d *DataHandler) handleVote(w http.ResponseWriter, r *http.Request) {
case "PUT":
switch auth.GetLoggedUserTechnical(w, r).Role {
case "ADMIN", "CAPTURER":
d.putVote(w, r, id)
d.putVote(w, r)
case "VISUALIZER":
http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed)
default:
......@@ -112,23 +112,32 @@ func (d *DataHandler) postVote(w http.ResponseWriter, r *http.Request) {
}
func (d *DataHandler) putVote(w http.ResponseWriter, r *http.Request, id int) {
var o Vote
if err := d.db.First(&o, id).Error; err != nil {
http.Error(w, ErrorIDIsMissing, http.StatusNotFound)
return
}
func (d *DataHandler) putVote(w http.ResponseWriter, r *http.Request) {
var vote Vote
err := json.NewDecoder(r.Body).Decode(&vote)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// check that objects are the same
if o.CandidateListID != vote.CandidateListID || o.DeskRoundID != vote.DeskRoundID {
http.Error(w, "Les objets ne correspondent pas", http.StatusInternalServerError)
return
var o Vote
if vote.Blank {
if err := d.db.Where("blank = true and desk_round_id = ?", vote.DeskRoundID).Find(&o).Error; err != nil {
http.Error(w, ErrorIDIsMissing, http.StatusNotFound)
return
}
} else if vote.NullVote {
if err := d.db.Where("null_vote = true and desk_round_id = ?", vote.DeskRoundID).Find(&o).Error; err != nil {
http.Error(w, ErrorIDIsMissing, http.StatusNotFound)
return
}
} else {
if err := d.db.Where("candidate_list_id = ? and desk_round_id = ?", vote.CandidateListID, vote.DeskRoundID).Find(&o).Error; err != nil {
http.Error(w, ErrorIDIsMissing, http.StatusNotFound)
return
}
}
o.VoiceNumber = vote.VoiceNumber
d.db.Save(&o)
json.NewEncoder(w).Encode(o)
......@@ -143,6 +152,16 @@ func (d *DataHandler) deleteVote(w http.ResponseWriter, r *http.Request, id int)
return
}
d.db.Delete(&o)
// Set completed to false for deskRound
var deskRound DeskRound
if err := d.db.First(&deskRound, o.DeskRoundID).Error; err != nil {
http.Error(w, ErrorParentNotFound, http.StatusNotFound)
return
}
deskRound.Completed = false
d.db.Save(&deskRound)
} else {
http.Error(w, ErrorIDIsMissing, http.StatusNotFound)
}
......
......@@ -124,7 +124,7 @@ func AdminTests(t *testing.T) {
// Get Votes
do("GET", "/api/Vote/", xsrfHeader, ``, 200, `[{"ID":1,"DeskRoundID":1,"CandidateListID":1,"VoiceNumber":158,"Blank":false,"NullVote":false}]`)
// Update a Vote
do("PUT", "/api/Vote/1", xsrfHeader, `{"ID":1,"DeskRoundID":1,"CandidateListID":1,"VoiceNumber":258,"Blank":false,"NullVote":true}`, 200, `{"ID":1,"DeskRoundID":1,"CandidateListID":1,"VoiceNumber":258,"Blank":false,"NullVote":false}`)
do("PUT", "/api/Vote/1", xsrfHeader, `{"DeskRoundID":1,"CandidateListID":1,"VoiceNumber":258,"Blank":false,"NullVote":false}`, 200, `{"ID":1,"DeskRoundID":1,"CandidateListID":1,"VoiceNumber":258,"Blank":false,"NullVote":false}`)
// TODO Update a DeskRound to Validated=true can only be done when votes are captured
......
......@@ -106,23 +106,27 @@ func appTests(t *testing.T) {
// Verify that a DeskRound can't be validated witout being completed
do("PUT", "/api/DeskRound/1", xsrfHeader, `{"ID":1,"Validated":true}`, 500, `Le bureau doit être complété avant de le valider`)
// Verify that you can't update a Vote if it's not the same
do("PUT", "/api/Vote/1", xsrfHeader, `{"ID":1,"DeskRoundID":2,"CandidateListID":1,"VoiceNumber":258,"Blank":false,"NullVote":true}`, 500, `Les objets ne correspondent pas`)
do("PUT", "/api/Vote/1", xsrfHeader, `{"ID":1,"DeskRoundID":2,"CandidateListID":2,"VoiceNumber":258,"Blank":false,"NullVote":true}`, 500, `Les objets ne correspondent pas`)
do("PUT", "/api/Vote/1", xsrfHeader, `{"ID":1,"DeskRoundID":1,"CandidateListID":2,"VoiceNumber":258,"Blank":false,"NullVote":true}`, 500, `Les objets ne correspondent pas`)
// Create Votes to complete a Desk
do("POST", "/api/Vote", xsrfHeader, `{"DeskRoundID":1,"CandidateListID":null,"VoiceNumber":3,"Blank":true}`, 200, `{"ID":2,"DeskRoundID":1,"CandidateListID":0,"VoiceNumber":3,"Blank":true,"NullVote":false}`)
do("POST", "/api/Vote", xsrfHeader, `{"DeskRoundID":1,"CandidateListID":null,"VoiceNumber":5,"NullVote":true}`, 200, `{"ID":3,"DeskRoundID":1,"CandidateListID":0,"VoiceNumber":5,"Blank":false,"NullVote":true}`)
do("GET", "/api/DeskRound/1", xsrfHeader, ``, 200, `{"ID":1,"RoundID":1,"DeskID":1,"Capturers":[],"Completed":true,"DateCompletion":"20`)
// Check to update the good vote
do("PUT", "/api/Vote/1", xsrfHeader, `{"DeskRoundID":1,"CandidateListID":1,"VoiceNumber":258,"Blank":false,"NullVote":false}`, 200, `{"ID":1,"DeskRoundID":1,"CandidateListID":1,"VoiceNumber":258,"Blank":false,"NullVote":false}`)
do("PUT", "/api/Vote/1", xsrfHeader, `{"DeskRoundID":1,"CandidateListID":null,"VoiceNumber":158,"Blank":true,"NullVote":false}`, 200, `{"ID":2,"DeskRoundID":1,"CandidateListID":0,"VoiceNumber":158,"Blank":true,"NullVote":false}`)
do("PUT", "/api/Vote/1", xsrfHeader, `{"DeskRoundID":1,"CandidateListID":null,"VoiceNumber":158,"Blank":false,"NullVote":true}`, 200, `{"ID":3,"DeskRoundID":1,"CandidateListID":0,"VoiceNumber":158,"Blank":false,"NullVote":true}`)
// Can't add the same vote several time
do("POST", "/api/Vote", xsrfHeader, `{"DeskRoundID":1,"CandidateListID":1,"VoiceNumber":158}`, 500, `Error the vote have already been captured`)
do("POST", "/api/Vote", xsrfHeader, `{"DeskRoundID":1,"CandidateListID":null,"VoiceNumber":3,"Blank":true}`, 500, `Error the vote have already been captured`)
do("POST", "/api/Vote", xsrfHeader, `{"DeskRoundID":1,"CandidateListID":null,"VoiceNumber":5,"NullVote":true}`, 500, `Error the vote have already been captured`)
//Check that on Vote deletion, deskRound is updated
do("DELETE", "/api/Vote/1", xsrfHeader, ``, 200, ``)
do("GET", "/api/DeskRound/1", xsrfHeader, ``, 200, `{"ID":1,"RoundID":1,"DeskID":1,"Capturers":[],"Completed":false,"DateCompletion":"20`)
// Verify that on Desk deletion deskRounds are deleted
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":true,"DateCompletion":"20`)
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":"20`)
do("DELETE", "/api/Desk/1", xsrfHeader, ``, 200, ``)
do("GET", "/api/DeskRound/1", xsrfHeader, ``, 404, `id is missing`)
......
......@@ -217,11 +217,10 @@ class DeskRoundSelector {
}
async openVotes(deskRound) {
this.parent.VoteHandler.displayVotes(
this.parent.voteHandler.displayVotes(
this.RoundID,
this.AreaID,
deskRound.ID
);
console.log("Ouverture des votes");
}
}
// Imports
import * as Auth from "/services/auth/auth.js";
import * as ElectionModel from "/services/model/election-model.js";
import * as RoundModel from "/services/model/round-model.js";
import * as AreaModel from "/services/model/area-model.js";
import * as SectionModel from "/services/model/section-model.js";
import * as DeskModel from "/services/model/desk-model.js";
import * as DeskRoundModel from "/services/model/deskRound-model.js";
import * as VoteModel from "/services/model/vote-model.js";
import * as CandidateListModel from "/services/model/candidateList-model.js";
export async function mount(parent) {
const voteComponent = new Vote(parent);
......@@ -10,7 +18,258 @@ class Vote {
constructor(parent) {
this.method = null;
this.parent = parent;
this.ElectionModel = ElectionModel.getElectionModel();
this.RoundModel = RoundModel.getRoundModel();
this.AreaModel = AreaModel.getAreaModel();
this.SectionModel = SectionModel.getSectionModel();
this.DeskModel = DeskModel.getDeskModel();
this.DeskRoundModel = DeskRoundModel.getDeskRoundModel();
this.VoteModel = VoteModel.getVoteModel();
this.CandidateListModel = CandidateListModel.getCandidateListModel();
}
async displayVotes(RoundID, AreaID, DeskRoundID) {}
async displayVotes(RoundID, AreaID, DeskRoundID) {
this.ElectionModel.current_user = await Auth.GetUser();
this.RoundModel.current_user = await Auth.GetUser();
this.AreaModel.current_user = await Auth.GetUser();
this.SectionModel.current_user = await Auth.GetUser();
this.DeskModel.current_user = await Auth.GetUser();
this.DeskRoundModel.current_user = await Auth.GetUser();
this.VoteModel.current_user = await Auth.GetUser();
this.CandidateListModel.current_user = await Auth.GetUser();
this.RoundID = RoundID;
this.AreaID = AreaID;
this.DeskRoundID = DeskRoundID;
document.getElementById("vote-section").innerHTML = /* HTML */ `
<header class="card-header">
<p >
<nav class="breadcrumb card-header-title" aria-label="breadcrumbs">
<ul id="vote-breadcrumb"></ul>
</nav>
</p>
</header>
<div id="votes-table" class="card-content"></div>
<nav class="level">
<div class="level-left"></div>
<div class="level-right">
<button id="votes-return" class="button level-item">
Retour
</button>
<button id="votes-cancel" class="button level-item">
Annuler
</button>
<button id="votes-delete" class="button is-danger level-item">
Supprimer
</button>
<button id="votes-save" class="button is-success level-item">
Sauvegarder
</button>
</div>
</nav>
`;
this.handleDom();
await this.refreshBreadCrumb();
await this.loadVotes();
}
voteTemplate(candidateList) {
return /* HTML */ `
<tr id="votes-vote-${candidateList.ID}">
<td>
${candidateList.Name}
</td>
<td>
<input
class="input"
type="number"
id="${candidateList.ID}-vote-voice"
/>
</td>
</tr>
`;
}
handleDom() {
let voteHandler = this;
document
.getElementById(`votes-return`)
.addEventListener("click", function () {
voteHandler.parent.deskRoundHandler.mount("vote-section");
});
document
.getElementById(`votes-cancel`)
.addEventListener("click", function () {
voteHandler.loadVotes();
});
document
.getElementById(`votes-save`)
.addEventListener("click", function () {
voteHandler.saveVotes();
});
document
.getElementById(`votes-delete`)
.addEventListener("click", function () {
voteHandler.deleteVotes();
});
}
async refreshBreadCrumb() {
let round = await this.RoundModel.getRound(this.RoundID);
let election = await this.ElectionModel.getElection(round.ElectionID);
let area = await this.AreaModel.getArea(this.AreaID);
let deskRound = await this.DeskRoundModel.getDeskRound(this.DeskRoundID);
let desk = await this.DeskModel.getDesk(deskRound.DeskID);
let section = await this.SectionModel.getSection(desk.SectionID);
let breadcrumb = document.getElementById("vote-breadcrumb");
let el = document.createElement("li");
el.innerHTML = "<a>" + election.Name + "</a>";
breadcrumb.appendChild(el);
el = document.createElement("li");
el.innerHTML =
"<a>tour : " +
round.Round +
", date : " +
new Date(round.Date).toLocaleDateString() +
"</a>";
breadcrumb.appendChild(el);
el = document.createElement("li");
el.innerHTML = "<a>" + area.Name + "</a>";
breadcrumb.appendChild(el);
el = document.createElement("li");
el.innerHTML = "<a>" + section.Name + "</a>";
breadcrumb.appendChild(el);
el = document.createElement("li");
el.innerHTML = "<a>" + desk.Name + "</a>";
breadcrumb.appendChild(el);
}
async loadVotes() {
document.getElementById("votes-table").innerHTML = /* HTML */ `<div
class="table-container"
>
<table class="table is-bordered is-narrow is-hoverable is-fullwidth">
<thead>
<tr class="is-selected">
<th>Liste</th>
<th>Nombre de voix</th>
</tr>
</thead>
<tbody id="votes-list"></tbody>
</table>
</div> `;
let votes = await this.updatesVotes();
let candidateLists = await this.updateCandidateLists();
const markup = candidateLists
.map((vote) => this.voteTemplate(vote))
.join("");
document.getElementById("votes-list").innerHTML = markup;
document.getElementById("votes-list").innerHTML += /* HTML */ `
<tr">
<td>Votes blanc</td>
<td>
<input
class="input"
type="number"
id="blank-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>
`;
votes.forEach((vote) => {
if (vote.Blank) {
document.getElementById("blank-vote-voice").value = vote.VoiceNumber;
} else if (vote.NullVote) {
document.getElementById("null-vote-voice").value = vote.VoiceNumber;
} else {
document.getElementById(vote.CandidateListID + "-vote-voice").value =
vote.VoiceNumber;
}
});
}
async saveVotes() {
let voteHandler = this;
let candidateLists = await this.updateCandidateLists();
let votes = await this.updatesVotes();
let method;
if (votes.length == 0) method = "POST";
else method = "PUT";
candidateLists.forEach(async (candidateList) => {
await voteHandler.VoteModel.saveVote(
method,
voteHandler.DeskRoundID,
candidateList.ID,
parseInt(
document.getElementById(candidateList.ID + "-vote-voice").value
),
false,
false
);
});
await this.VoteModel.saveVote(
method,
this.DeskRoundID,
null,
parseInt(document.getElementById("blank-vote-voice").value),
true,
false
);
await this.VoteModel.saveVote(
method,
this.DeskRoundID,
null,
parseInt(document.getElementById("null-vote-voice").value),
false,
true
);
await this.VoteModel.refreshVotes();
await this.loadVotes();
}
async updatesVotes() {
let voteHandler = this;
let votes = await this.VoteModel.getVotes();
return votes.filter((vote) => {
return vote.DeskRoundID == voteHandler.DeskRoundID;
});
}
async updateCandidateLists() {
let voteHandler = this;
let candidateLists = await this.CandidateListModel.getCandidateLists();
return candidateLists.filter((candidateList) => {
return (
candidateList.AreaID == voteHandler.AreaID &&
candidateList.RoundID == voteHandler.RoundID
);
});
}
async deleteVotes() {
let voteHandler = this;
let votes = await this.updatesVotes();
votes.forEach(async (vote) => {
await voteHandler.VoteModel.deleteVote(vote.ID);
await voteHandler.VoteModel.refreshVotes();
await voteHandler.loadVotes();
});
}
}
import * as Messages from "/services/messages/messages.js";
let voteModel;
export function getVoteModel() {
if (voteModel == null) {
voteModel = new VoteModel();
}
return voteModel;
}
class VoteModel {
constructor() {}
async getVote(id) {
if (this.votes == null) await this.refreshVotes();
let voteToGet;
this.votes.forEach((vote) => {
if (vote.ID == id) voteToGet = vote;
});
return voteToGet;
}
async getVotes() {
if (this.votes == null) {
try {
const response = await fetch("/api/Vote/", {
method: "GET",
headers: new Headers({
"XSRF-Token": this.current_user.xsrftoken,
}),
});
if (response.status !== 200) {
throw new Error(
`Votes could not be fetched (status ${response.status})`
);
}
this.votes = await response.json();
} catch (e) {
Messages.Show("is-warning", e.message);
console.error(e);
}
}
return this.votes;
}
async saveVote(
method,
DeskRoundID,
CandidateListID,
VoiceNumber,
Blank,
NullVote
) {
try {
const response = await fetch("/api/Vote/", {
method: method,
headers: new Headers({
"XSRF-Token": this.current_user.xsrftoken,
}),
body: JSON.stringify({
DeskRoundID: DeskRoundID,
CandidateListID: CandidateListID,
VoiceNumber: VoiceNumber,
Blank: Blank,
NullVote: NullVote,
}),
});
if (response.status !== 200) {
throw new Error(
`Vote could not be updated or created (status ${response.status})`
);
}
return await response.json();
} catch (e) {
Messages.Show("is-warning", e.message);
console.error(e);
return;
}
}
async deleteVote(ID) {
try {
const response = await fetch("/api/Vote/" + ID, {
method: "delete",
headers: new Headers({
"XSRF-Token": this.current_user.xsrftoken,
}),
});
if (response.status !== 200) {
throw new Error(
`Vote could not be deleted (status ${response.status})`
);
}
} catch (e) {
Messages.Show("is-warning", e.message);
console.error(e);
}
}
async refreshVotes() {
this.votes = null;
await this.getVotes();
}
}
......@@ -135,8 +135,13 @@ select {
.upper-text {
writing-mode: sideways-lr;
background-color: rgba(55,122,195,.95);
background-color: rgba(55, 122, 195, 0.95);
text-orientation: sideways-right;
text-align: center;
cursor: pointer;
}
#vote-breadcrumb a {
color: #000;
cursor: default;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment