Имплементација на корисничките сценарија во техничкиот прототип
Во досега имплементираниот технички прототип на апликацијата имплементирани се следните сценарија:
ID | UseCase |
---|---|
1 | Пријавува учество на партија и кандидати |
2 | Гласа онлајн |
3 | Прегледува вкупна излезност |
4 | Прегледува вкупни резултати од избори со кандидат |
5 | Прегледува избирачки список |
6 | Прегледува изборни единици |
7 | Ажурира изборни единици |
8 | Прегледува гласачки места |
9 | Ажурира изборни единици |
10 | Прегледува гласачки места |
11 | Креира инстанца од избори |
12 | Прегледува типови на избори |
13 | Ажурира типови на избори |
Пријава учество на партија и кандидати
Администраторот пристапува на адреса /admin/participations каде му се појавуа формулар за внесување податоци за опис на кандидатура, кандидат, име на пратија, реализација на избори и општина во која се кандидира. Под истиот се листаат сите претходни кандидатури кои биле внесени. За ова е одговорен следниот контролер:
@GetMapping("/admin/participations") public String candidacies(Model m) { m.addAttribute("replaceTemplate", "elections_participation"); m.addAttribute("electionsRealizations", candidatesElectionRealizationService.findAll()); m.addAttribute("municipalities", municipalityService.findAll()); m.addAttribute("parties", partyService.findAll()); m.addAttribute("candidates", candidateService.findAll()); m.addAttribute("candidacy", new Candidacy()); m.addAttribute("candidacies", candidacyService.findAll()); return "admin"; }
По внесување на податоците и клик на копчето „Запиши“ се праќа POST барање до контролерот кој ги обработува.
@PostMapping("/admin/participations") public String addCandidacy(@RequestParam(required = false) Long id, @RequestParam String description, @RequestParam Long candidate, @RequestParam Long candidatesElectionRealization, @RequestParam(required = false) Long municipality, @RequestParam Long party, Model m) { candidacyService.update(id, description, candidate, party, candidatesElectionRealization, municipality); return "redirect:/admin/participations"; }
Контролерот повикува функција од candidacyService за додавање и ажурирање (ажурирањето сѐ уште не е имплементирано во view-то) на кандидатури каде се креира нова инстанца од објектот Candidacy, и за истиот се полнат добиентите податоци од формуларот. Оттаму се повикува функцијата save од candidacyRepository кој наследува од JpaRepository. Таа служи за зачувување на новиот објект во базата.
public Candidacy update(Long id, String description, Long candidateId, Long partyId, Long candidatesElectionRealizationId, Long municipalityId) { Candidacy candidacy = new Candidacy(); if (id != null){ candidacy = findById(id); } candidacy.setMunicipality(municipalityService.findById(municipalityId)); candidacy.setDescription(description); candidacy.setCandidate(candidateService.findById(candidateId)); candidacy.setParty(partyService.findById(partyId)); candidacy.setCandidatesElectionRealization(candidatesElectionRealizationService.findById(candidatesElectionRealizationId)); return candidacyRepository.save(candidacy); }
Гласа онлајн
За да ја користи оваа функционалност, корисникот треба да пристапи до страницата /vote каде што го добива следниот приказ:
За ова е одговорен следниот контролер
@GetMapping("/vote") public String showVote(@RequestParam(required = false) Boolean error, Model m) { m.addAttribute("replaceTemplate", "vote_form"); m.addAttribute("error", error); return "index"; }
По успешен внес на потребните податоци во формуларот (број на лична карта и ЕМБГ), истиот се поднесува со клик на копчето „Продолжи“, при што се испраќа POST барање кое го пречекува следниот контролер:
@PostMapping("/vote") public String vote( Model m, @RequestParam String numberLicence, @RequestParam String embg, HttpServletRequest httpServletRequest) { Citizen citizen = citizenService.validateCitizen(embg, numberLicence); if (citizen != null) { httpServletRequest.getSession(true).setAttribute("citizenId", citizen.getId()); m.addAttribute("candidacies", candidacyService.findAll()); m.addAttribute("replaceTemplate", "candidacy_list"); return "redirect:/vote/available"; } else { m.addAttribute("replaceTemplate", "vote_form"); return "redirect:/vote?error=true"; } }
Овде, најпрвин се прави валидација на корисникот преку повикување на соодветната функција од сервисот за граѓани
@Override public Citizen validateCitizen(String idNum, String documentNumber) { try { Citizen citizen = findByIdNum(idNum); return citizen.getDocument().getDocumentNumber().equals(documentNumber) ? citizen : null; } catch (Exception ex) { return null; } }
За спречување злопупотреба целиот процес на гласање се одвива преку чување на потребните атрибути во сесија на серверот, па така, по успешна валидација на граѓанинот, се креира нова сесија, како атрибут се запишува неговиот идентификатор и корисникот се пренасочува до страницата за избор на инстанца од избори на која ќе гласата (/vote/available). Во спротивно, корисникот се враќа на страницата за најава и се испишува соодветна порака за грешка. По пренасочувањето, корисникот го добива следниот приказ, а за ова барање е одговорен контролерот:
@GetMapping("/vote/available") public String getAvailableElections(Model m, HttpServletRequest httpServletRequest) { if (httpServletRequest.getSession(false) == null) { return "redirect:/vote"; } Long citizenId = (Long) httpServletRequest.getSession().getAttribute("citizenId"); List<CandidatesElectionRealization> availableElections = candidatesElectionRealizationService.findAvailable(citizenId); m.addAttribute("realizations", availableElections); m.addAttribute("replaceTemplate", "elections_choice"); return "index"; }
@Override public List<CandidatesElectionRealization> findAvailable(Long citizenId) { Citizen citizen = citizenService.findById(citizenId); return repository.availableElections(citizen, citizen.getAddress().getMunicipality()); }
@Query(value = "select distinct elections from CandidatesElectionRealization elections join Candidacy candidacy on candidacy.candidatesElectionRealization = elections where elections.date >= current_date and elections not in (select v.electionRealization from Vote v where v.citizen = :citizen) and (candidacy.municipality is null or candidacy.municipality = :municipality)") List<CandidatesElectionRealization> availableElections(@Param("citizen") Citizen citizen, @Param("municipality") Municipality municipality);
Од безбедносни причини споменати погоре, идентификаторот на корисникот се зема од сесијата и соодветно од сервисот се бара листа на избори на кои најавениот корисник има право да гласа. Преку соодветниот репозиториум и прашалникот наведен во претходните фази и напишан во JPQL погоре, од базата се земаат потребните податоци. Истите се прикажуваат во паѓачка листа. Доколку корисникот се обиде да пристапи до оваа страница без претходна успешна валидација, по проверката за постоење на сесијата и валиден идентификатор, истиот ќе биде пренасочен кон страницата за најава.
Прегледува вкупна излезност
Со пристап на јавната адреса /turnout каде се прикажува излезноста на граѓаните во одредена реализација. Каде има полиња за која реализација (имплементирано), која општина, кој пол, кое избирачко место и во кое време да се видат резултатите од излезноста, кое за сега не е имплементирано. Со клик на копчето „Пребарај“ се праќа барање до контролерот на адреса /turnout. Во контролерот се повикува функција од voteService со параметар id на самата реализација.
@GetMapping("/turnout") public String turnOutResults(Model m, @RequestParam(required = false) Long realizationId) { Double turnOut = voteService.turnOutByElectionRealization(realizationId); m.addAttribute("replaceTemplate", "turnout_circle"); m.addAttribute("turnoutPercentage", turnOut); m.addAttribute("realizationId", realizationId); m.addAttribute("municipalities", municipalityService.findAll()); m.addAttribute("realizations", candidatesElectionRealizationService.findAll()); m.addAttribute("pollingStations", addressService.findAllPollingStations()); m.addAttribute("realization",candidatesElectionRealizationService.findById(realizationId)); return "index"; }
Сервисот го обработува повикот од контролерот, со тоа што ја наоѓа реализацијата на избори со тоа id и повикува друга функција turnOutByRealization од voteRepository кој имплементира интерфејс од JpaRepository.
public Double turnOutByElectionRealization(Long id) { ElectionRealization electionRealization = electionRealizationService.findById(id); return voteRepository.turnOutByRealization(electionRealization, LocalDate.now().minusYears(18)); }
Покрај функциите кои ги содржи интерфејсот JpaRepository може да се креираат и прашалници. Во долунаведениот прашалник се зимаат сите гласања од одредена реализација во однос со сите граѓани помножено по 100 за да се добие бараниот процент.
@Query(value = "SELECT (SELECT COUNT(v) FROM Vote v WHERE v.electionRealization = :ri) * 100.0 / (SELECT COUNT(c) FROM Citizen c WHERE c.dateOfBirth <= :dateThreshold) FROM ElectionRealization ri WHERE ri = :ri") public Double turnOutByRealization(@Param("ri") ElectionRealization electionRealization, @Param("dateThreshold") LocalDate dateThreshold);
Прегледува вкупни резултати од избори со кандидат
Оваа функционалност е достапна за сите корисници, без разлика дали се автеницирани и може да се пристапи на страницата /results. Приказот кој го добива корисникот изгледа вака:
За приказ на оваа страница е одговорен следниот контролер:
@GetMapping("/results") public String totalResults(@RequestParam(required = false) Long realizationId, Model m) { List<TotalCandidacyResults> total = voteService.resultsByCandidateElectionsRealization(realizationId); m.addAttribute("candidacies", total); m.addAttribute("realizationId", realizationId); m.addAttribute("municipalities", municipalityService.findAll()); m.addAttribute("realizations", candidatesElectionRealizationService.findAll()); m.addAttribute("pollingStations", addressService.findAllPollingStations()); m.addAttribute("realization",candidatesElectionRealizationService.findById(realizationId)); m.addAttribute("replaceTemplate", "results"); return "index"; }
На страницата се прикажани филтри од кои во досегашната имплементација функционира само оној за избор на реализацијата. По избор на соодветните критериуми, со клик на копчето пребарај се испраќа GET барање кој го пречекува контролерот. Според избраната реализација се повикува функција од сервисот за реализации со кандидатури од каде што се добива листа од објекти од record-от TotalCandidacyResults.
public record TotalCandidacyResults (Candidacy candidacy, Long voteCount, Double percent) {}
@Override public List<TotalCandidacyResults> resultsByCandidateElectionsRealization(Long realizationId) { CandidatesElectionRealization candidatesElectionRealization = candidatesElectionRealizationService.findById(realizationId); List<TotalCandidacyResults> votes = voteRepository.countVotesByCitizenAndRealization(candidatesElectionRealization); Long totalVotes = votes.stream().mapToLong(TotalCandidacyResults::voteCount).sum(); return votes.stream() .map(x -> new TotalCandidacyResults(x.candidacy(), x.voteCount(), x.voteCount() * 100.0 / totalVotes)) .sorted(Comparator.comparing(TotalCandidacyResults::voteCount).reversed()) .toList(); }
За оваа цел, се зема избраната реалзација од репозиториумот за реалзиации со кандидат и потоа преку следниот прашалник со добиваат резултатите во облик кандидат - број на гласовите. За базата да не се оптеретува дополнително, процентот на гласовите се пресметува во сервисот.
@Query("SELECT NEW mk.ukim.finki.eglas.records.TotalCandidacyResults(kan, COUNT(distinct cv), null) " + "FROM Candidate k " + "LEFT JOIN Candidacy kan ON kan.candidate = k AND kan.candidatesElectionRealization = :realization " + "LEFT JOIN CandidacyVote cv ON cv.candidacy = kan " + "WHERE kan is not null " + "GROUP BY kan") List<TotalCandidacyResults> countVotesByCitizenAndRealization(@Param("realization") CandidatesElectionRealization realization);
Attachments (5)
- dostapni_izbori.png (20.0 KB ) - added by 9 months ago.
- glasanje_najava.png (16.9 KB ) - added by 9 months ago.
- kandidaturi.jpg (90.4 KB ) - added by 9 months ago.
- rezultati.png (205.2 KB ) - added by 9 months ago.
- izleznost.png (202.6 KB ) - added by 9 months ago.
Download all attachments as: .zip