feat(election): add election module — config, nominations, candidates, voting, approval flow
- REST API resources: ElectionConfig, ElectionNomination, ElectionCandidate, ElectionVoting - Domain entities, repositories, services (read + write) with maker-checker approval steps - Eligibility checks, no-due-certificate, CEO pre-check services - Liquibase migration 0337 creates all election module tables Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
c1e23080fc
commit
5899d2ca07
+65
@@ -0,0 +1,65 @@
|
||||
package org.apache.fineract.portfolio.election.api;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.DELETE;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.election.service.ElectionReadPlatformService;
|
||||
import org.apache.fineract.portfolio.election.service.ElectionWritePlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@Path("/v1/election")
|
||||
@Tag(name = "Election Candidates", description = "Manage approved election candidates")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@RequiredArgsConstructor
|
||||
public class ElectionCandidateApiResource {
|
||||
|
||||
private final ElectionReadPlatformService readService;
|
||||
private final ElectionWritePlatformService writeService;
|
||||
private final PlatformSecurityContext context;
|
||||
|
||||
@GET
|
||||
@Path("/{electionId}/candidates")
|
||||
@Operation(summary = "Get all approved candidates for an election")
|
||||
public Response getCandidates(@PathParam("electionId") Long electionId,
|
||||
@QueryParam("postId") Long postId) {
|
||||
if (postId != null) {
|
||||
return Response.ok(readService.retrieveCandidatesByPost(electionId, postId)).build();
|
||||
}
|
||||
return Response.ok(readService.retrieveCandidatesByElection(electionId)).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{electionId}/nomination/{nominationId}/approve-candidate")
|
||||
@Operation(summary = "Approve nomination and add to candidates list")
|
||||
public Response approveCandidate(@PathParam("electionId") Long electionId,
|
||||
@PathParam("nominationId") Long nominationId) {
|
||||
Long approvedBy = context.authenticatedUser().getId();
|
||||
Long candidateId = writeService.approveCandidate(nominationId, approvedBy);
|
||||
return Response.ok(Map.of("resourceId", candidateId)).build();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{electionId}/candidate/{candidateId}")
|
||||
@Operation(summary = "Remove a candidate from the election")
|
||||
public Response removeCandidate(@PathParam("electionId") Long electionId,
|
||||
@PathParam("candidateId") Long candidateId,
|
||||
@QueryParam("reason") String reason) {
|
||||
writeService.removeCandidate(candidateId, reason);
|
||||
return Response.ok(Map.of("resourceId", candidateId, "status", "REMOVED")).build();
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package org.apache.fineract.portfolio.election.api;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.DELETE;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionConfigRequest;
|
||||
import org.apache.fineract.portfolio.election.service.ElectionReadPlatformService;
|
||||
import org.apache.fineract.portfolio.election.service.ElectionWritePlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@Path("/v1/election/config")
|
||||
@Tag(name = "Election Configuration", description = "Manage cooperative election setup")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@RequiredArgsConstructor
|
||||
public class ElectionConfigApiResource {
|
||||
|
||||
private final ElectionReadPlatformService readService;
|
||||
private final ElectionWritePlatformService writeService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@GET
|
||||
@Operation(summary = "List all elections")
|
||||
public Response listElections() {
|
||||
return Response.ok(readService.retrieveAllElections()).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{electionId}")
|
||||
@Operation(summary = "Get election details")
|
||||
public Response getElection(@PathParam("electionId") Long electionId) {
|
||||
return Response.ok(readService.retrieveElection(electionId)).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Create a new election")
|
||||
public Response createElection(ElectionConfigRequest request) {
|
||||
Long id = writeService.createElection(request);
|
||||
return Response.ok(Map.of("resourceId", id)).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{electionId}")
|
||||
@Operation(summary = "Update election configuration")
|
||||
public Response updateElection(@PathParam("electionId") Long electionId,
|
||||
ElectionConfigRequest request) {
|
||||
writeService.updateElection(electionId, request);
|
||||
return Response.ok(Map.of("resourceId", electionId)).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{electionId}/activate")
|
||||
@Operation(summary = "Activate election to open nominations")
|
||||
public Response activateElection(@PathParam("electionId") Long electionId) {
|
||||
writeService.activateElection(electionId);
|
||||
return Response.ok(Map.of("resourceId", electionId, "status", "ACTIVE")).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{electionId}/close-nominations")
|
||||
@Operation(summary = "Close nomination window")
|
||||
public Response closeNominations(@PathParam("electionId") Long electionId) {
|
||||
writeService.closeNominations(electionId);
|
||||
return Response.ok(Map.of("resourceId", electionId, "status", "NOMINATION_CLOSED")).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{electionId}/open-voting")
|
||||
@Operation(summary = "Open voting")
|
||||
public Response openVoting(@PathParam("electionId") Long electionId) {
|
||||
writeService.openVoting(electionId);
|
||||
return Response.ok(Map.of("resourceId", electionId, "status", "VOTING_OPEN")).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{electionId}/close-voting")
|
||||
@Operation(summary = "Close voting")
|
||||
public Response closeVoting(@PathParam("electionId") Long electionId) {
|
||||
writeService.closeVoting(electionId);
|
||||
return Response.ok(Map.of("resourceId", electionId, "status", "VOTING_CLOSED")).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{electionId}/cancel")
|
||||
@Operation(summary = "Cancel election")
|
||||
public Response cancelElection(@PathParam("electionId") Long electionId,
|
||||
@QueryParam("reason") String reason) {
|
||||
writeService.cancelElection(electionId, reason);
|
||||
return Response.ok(Map.of("resourceId", electionId, "status", "CANCELLED")).build();
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package org.apache.fineract.portfolio.election.api;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionApprovalActionRequest;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionNominationRequest;
|
||||
import org.apache.fineract.portfolio.election.service.ElectionReadPlatformService;
|
||||
import org.apache.fineract.portfolio.election.service.ElectionWritePlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@Path("/v1/election")
|
||||
@Tag(name = "Election Nomination", description = "Manage election nominations and approval workflow")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@RequiredArgsConstructor
|
||||
public class ElectionNominationApiResource {
|
||||
|
||||
private final ElectionReadPlatformService readService;
|
||||
private final ElectionWritePlatformService writeService;
|
||||
|
||||
@POST
|
||||
@Path("/{electionId}/nomination")
|
||||
@Operation(summary = "Submit a nomination for election")
|
||||
public Response submitNomination(@PathParam("electionId") Long electionId,
|
||||
ElectionNominationRequest request) {
|
||||
request.setElectionId(electionId);
|
||||
Long id = writeService.submitNomination(request);
|
||||
return Response.ok(Map.of("resourceId", id)).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{electionId}/nominations")
|
||||
@Operation(summary = "List all nominations for an election")
|
||||
public Response listNominations(@PathParam("electionId") Long electionId,
|
||||
@QueryParam("status") String status) {
|
||||
return Response.ok(readService.retrieveNominationsByElection(electionId, status)).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{electionId}/nomination/{nominationId}")
|
||||
@Operation(summary = "Get nomination details with eligibility and CEO check")
|
||||
public Response getNomination(@PathParam("electionId") Long electionId,
|
||||
@PathParam("nominationId") Long nominationId) {
|
||||
return Response.ok(readService.retrieveNomination(nominationId)).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{electionId}/nomination/{nominationId}/withdraw")
|
||||
@Operation(summary = "Withdraw nomination")
|
||||
public Response withdrawNomination(@PathParam("electionId") Long electionId,
|
||||
@PathParam("nominationId") Long nominationId) {
|
||||
writeService.withdrawNomination(nominationId);
|
||||
return Response.ok(Map.of("resourceId", nominationId, "status", "WITHDRAWN")).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{electionId}/nomination/{nominationId}/eligibility-check")
|
||||
@Operation(summary = "Run automated eligibility check (age, gender, caste, shares, area)")
|
||||
public Response runEligibilityCheck(@PathParam("electionId") Long electionId,
|
||||
@PathParam("nominationId") Long nominationId) {
|
||||
writeService.runEligibilityCheck(nominationId);
|
||||
return Response.ok(readService.retrieveNomination(nominationId)).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{electionId}/nomination/{nominationId}/ceo-precheck")
|
||||
@Operation(summary = "Run CEO pre-check (director loan, relation accounts)")
|
||||
public Response runCeoPreCheck(@PathParam("electionId") Long electionId,
|
||||
@PathParam("nominationId") Long nominationId) {
|
||||
writeService.runCeoPreCheck(nominationId);
|
||||
return Response.ok(readService.retrieveNomination(nominationId)).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{electionId}/nomination/{nominationId}/no-due-certificate")
|
||||
@Operation(summary = "Generate No Due Certificate for nominee")
|
||||
public Response generateNoDueCertificate(@PathParam("electionId") Long electionId,
|
||||
@PathParam("nominationId") Long nominationId) {
|
||||
String certNumber = writeService.generateNoDueCertificate(nominationId);
|
||||
return Response.ok(Map.of("certificateNumber", certNumber)).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{electionId}/nomination/{nominationId}/approval-action")
|
||||
@Operation(summary = "Process approval step (approve or reject)")
|
||||
public Response processApprovalStep(@PathParam("electionId") Long electionId,
|
||||
@PathParam("nominationId") Long nominationId,
|
||||
ElectionApprovalActionRequest request) {
|
||||
writeService.processApprovalStep(nominationId, request);
|
||||
return Response.ok(readService.retrieveNomination(nominationId)).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/nominations/member/{memberId}")
|
||||
@Operation(summary = "Get all nominations submitted by a member")
|
||||
public Response getNominationsByMember(@PathParam("memberId") Long memberId) {
|
||||
return Response.ok(readService.retrieveNominationsByMember(memberId)).build();
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package org.apache.fineract.portfolio.election.api;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionVoteRequest;
|
||||
import org.apache.fineract.portfolio.election.service.ElectionReadPlatformService;
|
||||
import org.apache.fineract.portfolio.election.service.ElectionWritePlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@Path("/v1/election")
|
||||
@Tag(name = "Election Voting", description = "Election day voting and results")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@RequiredArgsConstructor
|
||||
public class ElectionVotingApiResource {
|
||||
|
||||
private final ElectionReadPlatformService readService;
|
||||
private final ElectionWritePlatformService writeService;
|
||||
private final PlatformSecurityContext context;
|
||||
|
||||
@POST
|
||||
@Path("/{electionId}/vote")
|
||||
@Operation(summary = "Cast a vote for a candidate")
|
||||
public Response castVote(@PathParam("electionId") Long electionId,
|
||||
ElectionVoteRequest request) {
|
||||
request.setElectionId(electionId);
|
||||
writeService.castVote(request);
|
||||
return Response.ok(Map.of("message", "Vote cast successfully")).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{electionId}/declare-results")
|
||||
@Operation(summary = "Declare election results")
|
||||
public Response declareResults(@PathParam("electionId") Long electionId) {
|
||||
Long declaredBy = context.authenticatedUser().getId();
|
||||
writeService.declareResults(electionId, declaredBy);
|
||||
return Response.ok(readService.retrieveResultsByElection(electionId)).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{electionId}/results")
|
||||
@Operation(summary = "Get election results")
|
||||
public Response getResults(@PathParam("electionId") Long electionId) {
|
||||
return Response.ok(readService.retrieveResultsByElection(electionId)).build();
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package org.apache.fineract.portfolio.election.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ElectionApprovalActionRequest {
|
||||
|
||||
private String action;
|
||||
private String remarks;
|
||||
private Boolean familyDetailsVerified;
|
||||
private String familyDetailsJson;
|
||||
private String rejectionReason;
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package org.apache.fineract.portfolio.election.data;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ElectionApprovalStepData {
|
||||
|
||||
private Long id;
|
||||
private Long nominationId;
|
||||
private Integer stepNumber;
|
||||
private String stepName;
|
||||
private String approverRole;
|
||||
private Long approverUserId;
|
||||
private String approverName;
|
||||
private String status;
|
||||
private String remarks;
|
||||
private LocalDateTime actionDate;
|
||||
private Boolean familyDetailsVerified;
|
||||
private String familyDetailsJson;
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package org.apache.fineract.portfolio.election.data;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ElectionCandidateData {
|
||||
|
||||
private Long id;
|
||||
private Long electionId;
|
||||
private Long nominationId;
|
||||
private Long postId;
|
||||
private String postName;
|
||||
private String candidateName;
|
||||
private Integer serialNumber;
|
||||
private String photoUrl;
|
||||
private Boolean isActive;
|
||||
private LocalDateTime approvedDate;
|
||||
private String symbol;
|
||||
private String briefProfile;
|
||||
private String gender;
|
||||
private String caste;
|
||||
private String location;
|
||||
private Integer shareCount;
|
||||
private Integer voteCount;
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package org.apache.fineract.portfolio.election.data;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ElectionCeoPreCheckData {
|
||||
|
||||
private Long id;
|
||||
private Long nominationId;
|
||||
private String directorLoanCheckResult;
|
||||
private BigDecimal directorLoanOutstanding;
|
||||
private String directorLoanRemarks;
|
||||
private String relationAcCheckResult;
|
||||
private String relationAccountsDetails;
|
||||
private String relationAcRemarks;
|
||||
private String noDueCheckResult;
|
||||
private String noDueCertificateNumber;
|
||||
private LocalDateTime noDueGeneratedDate;
|
||||
private String overallResult;
|
||||
private Long checkedBy;
|
||||
private String checkedByName;
|
||||
private LocalDateTime checkedDate;
|
||||
private String remarks;
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package org.apache.fineract.portfolio.election.data;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ElectionConfigData {
|
||||
|
||||
private Long id;
|
||||
private String title;
|
||||
private String description;
|
||||
private String electionType;
|
||||
private LocalDate nominationStartDate;
|
||||
private LocalDate nominationEndDate;
|
||||
private LocalDate electionDate;
|
||||
private LocalDate resultDate;
|
||||
private String status;
|
||||
private Boolean isActive;
|
||||
private String remarks;
|
||||
private List<ElectionPostData> posts;
|
||||
private Long totalNominations;
|
||||
private Long approvedNominations;
|
||||
private Long pendingNominations;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package org.apache.fineract.portfolio.election.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ElectionConfigRequest {
|
||||
|
||||
private String title;
|
||||
private String description;
|
||||
private String electionType;
|
||||
private LocalDate nominationStartDate;
|
||||
private LocalDate nominationEndDate;
|
||||
private LocalDate electionDate;
|
||||
private LocalDate resultDate;
|
||||
private String remarks;
|
||||
private List<ElectionPostRequest> posts;
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package org.apache.fineract.portfolio.election.data;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ElectionEligibilityCheckData {
|
||||
|
||||
private Long id;
|
||||
private Long nominationId;
|
||||
private String overallResult;
|
||||
private Long checkedBy;
|
||||
private String checkedByName;
|
||||
private LocalDateTime checkedDate;
|
||||
private String remarks;
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package org.apache.fineract.portfolio.election.data;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ElectionNominationData {
|
||||
|
||||
private Long id;
|
||||
private Long electionId;
|
||||
private String electionTitle;
|
||||
private Long postId;
|
||||
private String postName;
|
||||
private Long memberId;
|
||||
private Long shareAccountId;
|
||||
private String applicationNumber;
|
||||
private String applicantName;
|
||||
private LocalDate dateOfBirth;
|
||||
private Integer age;
|
||||
private String gender;
|
||||
private String caste;
|
||||
private String casteCategory;
|
||||
private String location;
|
||||
private String serviceDetails;
|
||||
private String mobileNumber;
|
||||
private String email;
|
||||
private String address;
|
||||
private String occupation;
|
||||
private Integer shareCount;
|
||||
private String status;
|
||||
private String statusDisplayName;
|
||||
private String rejectionReason;
|
||||
private LocalDateTime submittedDate;
|
||||
private Boolean declarationAccepted;
|
||||
private Long photoDocumentId;
|
||||
private ElectionEligibilityCheckData eligibilityCheck;
|
||||
private ElectionCeoPreCheckData ceoPreCheck;
|
||||
private List<ElectionApprovalStepData> approvalSteps;
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package org.apache.fineract.portfolio.election.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ElectionNominationRequest {
|
||||
|
||||
private Long electionId;
|
||||
private Long postId;
|
||||
private Long memberId;
|
||||
private Long shareAccountId;
|
||||
private String applicantName;
|
||||
private LocalDate dateOfBirth;
|
||||
private String gender;
|
||||
private String caste;
|
||||
private String casteCategory;
|
||||
private String location;
|
||||
private String serviceDetails;
|
||||
private String mobileNumber;
|
||||
private String email;
|
||||
private String address;
|
||||
private String occupation;
|
||||
private Integer shareCount;
|
||||
private Boolean declarationAccepted;
|
||||
private Long photoDocumentId;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package org.apache.fineract.portfolio.election.data;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ElectionPostData {
|
||||
|
||||
private Long id;
|
||||
private Long electionId;
|
||||
private String postName;
|
||||
private Integer totalSeats;
|
||||
private Boolean isActive;
|
||||
private Long filledSeats;
|
||||
private Long remainingSeats;
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package org.apache.fineract.portfolio.election.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ElectionPostRequest {
|
||||
|
||||
private String postName;
|
||||
private Integer totalSeats;
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package org.apache.fineract.portfolio.election.data;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ElectionResultData {
|
||||
|
||||
private Long id;
|
||||
private Long electionId;
|
||||
private String electionTitle;
|
||||
private Long postId;
|
||||
private String postName;
|
||||
private Long candidateId;
|
||||
private String candidateName;
|
||||
private Integer voteCount;
|
||||
private String result;
|
||||
private Integer rankPosition;
|
||||
private LocalDateTime declaredDate;
|
||||
private List<ElectionResultData> postResults;
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package org.apache.fineract.portfolio.election.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ElectionVoteRequest {
|
||||
|
||||
private Long electionId;
|
||||
private Long postId;
|
||||
private Long voterMemberId;
|
||||
private Long candidateId;
|
||||
private String boothId;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
public enum ApprovalStepStatus {
|
||||
PENDING,
|
||||
APPROVED,
|
||||
REJECTED,
|
||||
SKIPPED
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
public enum CheckResult {
|
||||
PASS,
|
||||
FAIL,
|
||||
PENDING,
|
||||
WAIVED
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_election_approval_step")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class ElectionApprovalStep extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "nomination_id", nullable = false)
|
||||
private ElectionNomination nomination;
|
||||
|
||||
@Column(name = "step_number", nullable = false)
|
||||
private Integer stepNumber;
|
||||
|
||||
@Column(name = "step_name", nullable = false, length = 200)
|
||||
private String stepName;
|
||||
|
||||
@Column(name = "approver_role", length = 100)
|
||||
private String approverRole;
|
||||
|
||||
@Column(name = "approver_user_id")
|
||||
private Long approverUserId;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "status", nullable = false, length = 50)
|
||||
private ApprovalStepStatus status = ApprovalStepStatus.PENDING;
|
||||
|
||||
@Column(name = "remarks", columnDefinition = "TEXT")
|
||||
private String remarks;
|
||||
|
||||
@Column(name = "action_date")
|
||||
private LocalDateTime actionDate;
|
||||
|
||||
@Column(name = "family_details_verified", nullable = false)
|
||||
private Boolean familyDetailsVerified = false;
|
||||
|
||||
@Column(name = "family_details_json", columnDefinition = "TEXT")
|
||||
private String familyDetailsJson;
|
||||
|
||||
public static ElectionApprovalStep create(ElectionNomination nomination,
|
||||
int stepNumber, String stepName, String approverRole) {
|
||||
ElectionApprovalStep step = new ElectionApprovalStep();
|
||||
step.nomination = nomination;
|
||||
step.stepNumber = stepNumber;
|
||||
step.stepName = stepName;
|
||||
step.approverRole = approverRole;
|
||||
step.status = ApprovalStepStatus.PENDING;
|
||||
step.familyDetailsVerified = false;
|
||||
return step;
|
||||
}
|
||||
|
||||
public void approve(Long approverUserId, String remarks, boolean familyVerified, String familyJson) {
|
||||
this.approverUserId = approverUserId;
|
||||
this.status = ApprovalStepStatus.APPROVED;
|
||||
this.remarks = remarks;
|
||||
this.actionDate = LocalDateTime.now();
|
||||
this.familyDetailsVerified = familyVerified;
|
||||
this.familyDetailsJson = familyJson;
|
||||
}
|
||||
|
||||
public void reject(Long approverUserId, String remarks) {
|
||||
this.approverUserId = approverUserId;
|
||||
this.status = ApprovalStepStatus.REJECTED;
|
||||
this.remarks = remarks;
|
||||
this.actionDate = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface ElectionApprovalStepRepository extends JpaRepository<ElectionApprovalStep, Long> {
|
||||
|
||||
List<ElectionApprovalStep> findByNominationIdOrderByStepNumberAsc(Long nominationId);
|
||||
|
||||
Optional<ElectionApprovalStep> findByNominationIdAndStepNumber(Long nominationId, int stepNumber);
|
||||
|
||||
@Query("SELECT s FROM ElectionApprovalStep s WHERE s.nomination.id = :nominationId " +
|
||||
"AND s.status = org.apache.fineract.portfolio.election.domain.ApprovalStepStatus.PENDING ORDER BY s.stepNumber ASC")
|
||||
Optional<ElectionApprovalStep> findNextPendingStep(@Param("nominationId") Long nominationId);
|
||||
|
||||
boolean existsByNominationIdAndStatus(Long nominationId, ApprovalStepStatus status);
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_election_candidate")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class ElectionCandidate extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "election_id", nullable = false)
|
||||
private ElectionConfig electionConfig;
|
||||
|
||||
@OneToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "nomination_id", nullable = false, unique = true)
|
||||
private ElectionNomination nomination;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "post_id", nullable = false)
|
||||
private ElectionPost electionPost;
|
||||
|
||||
@Column(name = "candidate_name", nullable = false, length = 200)
|
||||
private String candidateName;
|
||||
|
||||
@Column(name = "serial_number")
|
||||
private Integer serialNumber;
|
||||
|
||||
@Column(name = "photo_url", length = 500)
|
||||
private String photoUrl;
|
||||
|
||||
@Column(name = "is_active", nullable = false)
|
||||
private Boolean isActive = true;
|
||||
|
||||
@Column(name = "approved_by")
|
||||
private Long approvedBy;
|
||||
|
||||
@Column(name = "approved_date")
|
||||
private LocalDateTime approvedDate;
|
||||
|
||||
@Column(name = "symbol", length = 100)
|
||||
private String symbol;
|
||||
|
||||
@Column(name = "brief_profile", columnDefinition = "TEXT")
|
||||
private String briefProfile;
|
||||
|
||||
public static ElectionCandidate create(ElectionConfig electionConfig,
|
||||
ElectionNomination nomination,
|
||||
ElectionPost electionPost,
|
||||
String candidateName,
|
||||
Integer serialNumber,
|
||||
Long approvedBy) {
|
||||
ElectionCandidate candidate = new ElectionCandidate();
|
||||
candidate.electionConfig = electionConfig;
|
||||
candidate.nomination = nomination;
|
||||
candidate.electionPost = electionPost;
|
||||
candidate.candidateName = candidateName;
|
||||
candidate.serialNumber = serialNumber;
|
||||
candidate.approvedBy = approvedBy;
|
||||
candidate.approvedDate = LocalDateTime.now();
|
||||
candidate.isActive = true;
|
||||
return candidate;
|
||||
}
|
||||
|
||||
public void deactivate() {
|
||||
this.isActive = false;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface ElectionCandidateRepository extends JpaRepository<ElectionCandidate, Long> {
|
||||
|
||||
List<ElectionCandidate> findByElectionConfigIdAndIsActiveTrue(Long electionId);
|
||||
|
||||
List<ElectionCandidate> findByElectionConfigIdAndElectionPostIdAndIsActiveTrue(
|
||||
Long electionId, Long postId);
|
||||
|
||||
Optional<ElectionCandidate> findByNominationId(Long nominationId);
|
||||
|
||||
@Query("SELECT c FROM ElectionCandidate c WHERE c.electionConfig.id = :electionId " +
|
||||
"AND c.isActive = true ORDER BY c.electionPost.postName, c.serialNumber")
|
||||
List<ElectionCandidate> findAllActiveCandidates(@Param("electionId") Long electionId);
|
||||
|
||||
@Query("SELECT MAX(c.serialNumber) FROM ElectionCandidate c " +
|
||||
"WHERE c.electionConfig.id = :electionId AND c.electionPost.id = :postId")
|
||||
Optional<Integer> findMaxSerialNumberByElectionAndPost(
|
||||
@Param("electionId") Long electionId, @Param("postId") Long postId);
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_election_ceo_precheck")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class ElectionCeoPrecheck extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@OneToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "nomination_id", nullable = false, unique = true)
|
||||
private ElectionNomination nomination;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "director_loan_check_result", length = 20)
|
||||
private CheckResult directorLoanCheckResult = CheckResult.PENDING;
|
||||
|
||||
@Column(name = "director_loan_outstanding", precision = 19, scale = 4)
|
||||
private BigDecimal directorLoanOutstanding = BigDecimal.ZERO;
|
||||
|
||||
@Column(name = "director_loan_remarks", columnDefinition = "TEXT")
|
||||
private String directorLoanRemarks;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "relation_ac_check_result", length = 20)
|
||||
private CheckResult relationAcCheckResult = CheckResult.PENDING;
|
||||
|
||||
@Column(name = "relation_accounts_details", columnDefinition = "TEXT")
|
||||
private String relationAccountsDetails;
|
||||
|
||||
@Column(name = "relation_ac_remarks", columnDefinition = "TEXT")
|
||||
private String relationAcRemarks;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "no_due_check_result", length = 20)
|
||||
private CheckResult noDueCheckResult = CheckResult.PENDING;
|
||||
|
||||
@Column(name = "no_due_certificate_number", length = 100)
|
||||
private String noDueCertificateNumber;
|
||||
|
||||
@Column(name = "no_due_generated_date")
|
||||
private LocalDateTime noDueGeneratedDate;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "overall_result", length = 20)
|
||||
private CheckResult overallResult = CheckResult.PENDING;
|
||||
|
||||
@Column(name = "checked_by")
|
||||
private Long checkedBy;
|
||||
|
||||
@Column(name = "checked_date")
|
||||
private LocalDateTime checkedDate;
|
||||
|
||||
@Column(name = "remarks", columnDefinition = "TEXT")
|
||||
private String remarks;
|
||||
|
||||
public static ElectionCeoPrecheck create(ElectionNomination nomination) {
|
||||
ElectionCeoPrecheck precheck = new ElectionCeoPrecheck();
|
||||
precheck.nomination = nomination;
|
||||
precheck.overallResult = CheckResult.PENDING;
|
||||
precheck.directorLoanCheckResult = CheckResult.PENDING;
|
||||
precheck.relationAcCheckResult = CheckResult.PENDING;
|
||||
precheck.noDueCheckResult = CheckResult.PENDING;
|
||||
return precheck;
|
||||
}
|
||||
|
||||
public void completeDirectorLoanCheck(CheckResult result, BigDecimal outstanding, String remarks) {
|
||||
this.directorLoanCheckResult = result;
|
||||
this.directorLoanOutstanding = outstanding != null ? outstanding : BigDecimal.ZERO;
|
||||
this.directorLoanRemarks = remarks;
|
||||
}
|
||||
|
||||
public void completeRelationAcCheck(CheckResult result, String relationDetails, String remarks) {
|
||||
this.relationAcCheckResult = result;
|
||||
this.relationAccountsDetails = relationDetails;
|
||||
this.relationAcRemarks = remarks;
|
||||
}
|
||||
|
||||
public void generateNoDueCertificate(String certificateNumber) {
|
||||
this.noDueCertificateNumber = certificateNumber;
|
||||
this.noDueGeneratedDate = LocalDateTime.now();
|
||||
this.noDueCheckResult = CheckResult.PASS;
|
||||
}
|
||||
|
||||
public void finalise(Long checkedBy, String remarks) {
|
||||
this.checkedBy = checkedBy;
|
||||
this.checkedDate = LocalDateTime.now();
|
||||
this.remarks = remarks;
|
||||
boolean allPass = directorLoanCheckResult == CheckResult.PASS
|
||||
&& relationAcCheckResult == CheckResult.PASS
|
||||
&& noDueCheckResult == CheckResult.PASS;
|
||||
this.overallResult = allPass ? CheckResult.PASS : CheckResult.FAIL;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface ElectionCeoPrecheckRepository extends JpaRepository<ElectionCeoPrecheck, Long> {
|
||||
|
||||
Optional<ElectionCeoPrecheck> findByNominationId(Long nominationId);
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_election_config")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class ElectionConfig extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
@Column(name = "title", nullable = false, length = 200)
|
||||
private String title;
|
||||
|
||||
@Column(name = "description", columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "election_type", nullable = false, length = 50)
|
||||
private ElectionType electionType;
|
||||
|
||||
@Column(name = "nomination_start_date", nullable = false)
|
||||
private LocalDate nominationStartDate;
|
||||
|
||||
@Column(name = "nomination_end_date", nullable = false)
|
||||
private LocalDate nominationEndDate;
|
||||
|
||||
@Column(name = "election_date", nullable = false)
|
||||
private LocalDate electionDate;
|
||||
|
||||
@Column(name = "result_date")
|
||||
private LocalDate resultDate;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "status", nullable = false, length = 50)
|
||||
private ElectionStatus status = ElectionStatus.DRAFT;
|
||||
|
||||
@Column(name = "is_active", nullable = false)
|
||||
private Boolean isActive = true;
|
||||
|
||||
@Column(name = "remarks", columnDefinition = "TEXT")
|
||||
private String remarks;
|
||||
|
||||
@OneToMany(mappedBy = "electionConfig", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||
private List<ElectionPost> posts = new ArrayList<>();
|
||||
|
||||
private ElectionConfig(String title, String description, ElectionType electionType,
|
||||
LocalDate nominationStartDate, LocalDate nominationEndDate,
|
||||
LocalDate electionDate, LocalDate resultDate) {
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.electionType = electionType;
|
||||
this.nominationStartDate = nominationStartDate;
|
||||
this.nominationEndDate = nominationEndDate;
|
||||
this.electionDate = electionDate;
|
||||
this.resultDate = resultDate;
|
||||
this.status = ElectionStatus.DRAFT;
|
||||
this.isActive = true;
|
||||
}
|
||||
|
||||
public static ElectionConfig create(String title, String description, ElectionType electionType,
|
||||
LocalDate nominationStartDate, LocalDate nominationEndDate,
|
||||
LocalDate electionDate, LocalDate resultDate) {
|
||||
return new ElectionConfig(title, description, electionType, nominationStartDate,
|
||||
nominationEndDate, electionDate, resultDate);
|
||||
}
|
||||
|
||||
public boolean isNominationOpen() {
|
||||
LocalDate today = LocalDate.now();
|
||||
return status == ElectionStatus.ACTIVE
|
||||
&& !today.isBefore(nominationStartDate)
|
||||
&& !today.isAfter(nominationEndDate);
|
||||
}
|
||||
|
||||
public boolean isVotingOpen() {
|
||||
return status == ElectionStatus.VOTING_OPEN;
|
||||
}
|
||||
|
||||
public void activate() {
|
||||
this.status = ElectionStatus.ACTIVE;
|
||||
}
|
||||
|
||||
public void closeNominations() {
|
||||
this.status = ElectionStatus.NOMINATION_CLOSED;
|
||||
}
|
||||
|
||||
public void openVoting() {
|
||||
this.status = ElectionStatus.VOTING_OPEN;
|
||||
}
|
||||
|
||||
public void closeVoting() {
|
||||
this.status = ElectionStatus.VOTING_CLOSED;
|
||||
}
|
||||
|
||||
public void declareResult() {
|
||||
this.status = ElectionStatus.RESULT_DECLARED;
|
||||
}
|
||||
|
||||
public void cancel(String reason) {
|
||||
this.status = ElectionStatus.CANCELLED;
|
||||
this.remarks = reason;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@Repository
|
||||
public interface ElectionConfigRepository extends JpaRepository<ElectionConfig, Long> {
|
||||
|
||||
List<ElectionConfig> findByIsActiveTrue();
|
||||
|
||||
List<ElectionConfig> findByStatus(ElectionStatus status);
|
||||
|
||||
List<ElectionConfig> findByElectionType(ElectionType electionType);
|
||||
|
||||
@Query("SELECT e FROM ElectionConfig e WHERE e.isActive = true AND e.status IN :statuses ORDER BY e.electionDate DESC")
|
||||
List<ElectionConfig> findActiveByStatuses(@Param("statuses") List<ElectionStatus> statuses);
|
||||
|
||||
@Query("SELECT e FROM ElectionConfig e WHERE e.status = org.apache.fineract.portfolio.election.domain.ElectionStatus.ACTIVE AND CURRENT_DATE BETWEEN e.nominationStartDate AND e.nominationEndDate")
|
||||
List<ElectionConfig> findOpenForNomination();
|
||||
|
||||
Optional<ElectionConfig> findByIdAndIsActiveTrue(Long id);
|
||||
|
||||
boolean existsByStatusIn(Set<ElectionStatus> statuses);
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_election_eligibility_check")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class ElectionEligibilityCheck extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@OneToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "nomination_id", nullable = false, unique = true)
|
||||
private ElectionNomination nomination;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "overall_result", length = 20)
|
||||
private CheckResult overallResult = CheckResult.PENDING;
|
||||
|
||||
@Column(name = "checked_by")
|
||||
private Long checkedBy;
|
||||
|
||||
@Column(name = "checked_date")
|
||||
private LocalDateTime checkedDate;
|
||||
|
||||
@Column(name = "remarks", columnDefinition = "TEXT")
|
||||
private String remarks;
|
||||
|
||||
public static ElectionEligibilityCheck create(ElectionNomination nomination) {
|
||||
ElectionEligibilityCheck check = new ElectionEligibilityCheck();
|
||||
check.nomination = nomination;
|
||||
check.overallResult = CheckResult.PENDING;
|
||||
return check;
|
||||
}
|
||||
|
||||
public void complete(Long checkedBy, String remarks, boolean passed) {
|
||||
this.checkedBy = checkedBy;
|
||||
this.checkedDate = LocalDateTime.now();
|
||||
this.remarks = remarks;
|
||||
this.overallResult = passed ? CheckResult.PASS : CheckResult.FAIL;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface ElectionEligibilityCheckRepository extends JpaRepository<ElectionEligibilityCheck, Long> {
|
||||
|
||||
Optional<ElectionEligibilityCheck> findByNominationId(Long nominationId);
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_election_nomination",
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"election_id", "member_id", "post_id"}))
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class ElectionNomination extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "election_id", nullable = false)
|
||||
private ElectionConfig electionConfig;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "post_id", nullable = false)
|
||||
private ElectionPost electionPost;
|
||||
|
||||
@Column(name = "member_id", nullable = false)
|
||||
private Long memberId;
|
||||
|
||||
@Column(name = "share_account_id")
|
||||
private Long shareAccountId;
|
||||
|
||||
@Column(name = "application_number", unique = true, length = 50)
|
||||
private String applicationNumber;
|
||||
|
||||
@Column(name = "applicant_name", nullable = false, length = 200)
|
||||
private String applicantName;
|
||||
|
||||
@Column(name = "date_of_birth")
|
||||
private LocalDate dateOfBirth;
|
||||
|
||||
@Column(name = "gender", length = 20)
|
||||
private String gender;
|
||||
|
||||
@Column(name = "caste", length = 100)
|
||||
private String caste;
|
||||
|
||||
@Column(name = "caste_category", length = 100)
|
||||
private String casteCategory;
|
||||
|
||||
@Column(name = "location", length = 200)
|
||||
private String location;
|
||||
|
||||
@Column(name = "service_details", length = 500)
|
||||
private String serviceDetails;
|
||||
|
||||
@Column(name = "mobile_number", length = 20)
|
||||
private String mobileNumber;
|
||||
|
||||
@Column(name = "email", length = 150)
|
||||
private String email;
|
||||
|
||||
@Column(name = "address", columnDefinition = "TEXT")
|
||||
private String address;
|
||||
|
||||
@Column(name = "occupation", length = 200)
|
||||
private String occupation;
|
||||
|
||||
@Column(name = "share_count")
|
||||
private Integer shareCount;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "status", nullable = false, length = 50)
|
||||
private NominationStatus status = NominationStatus.DRAFT;
|
||||
|
||||
@Column(name = "rejection_reason", columnDefinition = "TEXT")
|
||||
private String rejectionReason;
|
||||
|
||||
@Column(name = "submitted_date")
|
||||
private LocalDateTime submittedDate;
|
||||
|
||||
@Column(name = "declaration_accepted", nullable = false)
|
||||
private Boolean declarationAccepted = false;
|
||||
|
||||
@Column(name = "photo_document_id")
|
||||
private Long photoDocumentId;
|
||||
|
||||
@OneToMany(mappedBy = "nomination", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||
private List<ElectionApprovalStep> approvalSteps = new ArrayList<>();
|
||||
|
||||
public static ElectionNomination create(ElectionConfig electionConfig, ElectionPost electionPost,
|
||||
Long memberId, Long shareAccountId, String applicantName,
|
||||
LocalDate dateOfBirth, String gender, String caste,
|
||||
String casteCategory, String location, String serviceDetails,
|
||||
String mobileNumber, String email, String address,
|
||||
String occupation, Integer shareCount) {
|
||||
ElectionNomination nomination = new ElectionNomination();
|
||||
nomination.electionConfig = electionConfig;
|
||||
nomination.electionPost = electionPost;
|
||||
nomination.memberId = memberId;
|
||||
nomination.shareAccountId = shareAccountId;
|
||||
nomination.applicantName = applicantName;
|
||||
nomination.dateOfBirth = dateOfBirth;
|
||||
nomination.gender = gender;
|
||||
nomination.caste = caste;
|
||||
nomination.casteCategory = casteCategory;
|
||||
nomination.location = location;
|
||||
nomination.serviceDetails = serviceDetails;
|
||||
nomination.mobileNumber = mobileNumber;
|
||||
nomination.email = email;
|
||||
nomination.address = address;
|
||||
nomination.occupation = occupation;
|
||||
nomination.shareCount = shareCount;
|
||||
nomination.status = NominationStatus.DRAFT;
|
||||
return nomination;
|
||||
}
|
||||
|
||||
public void submit(String applicationNumber) {
|
||||
this.applicationNumber = applicationNumber;
|
||||
this.status = NominationStatus.SUBMITTED;
|
||||
this.submittedDate = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public void markUnderReview() {
|
||||
this.status = NominationStatus.UNDER_REVIEW;
|
||||
}
|
||||
|
||||
public void sendToCeoCheck() {
|
||||
this.status = NominationStatus.CEO_CHECK_PENDING;
|
||||
}
|
||||
|
||||
public void ceoApprove() {
|
||||
this.status = NominationStatus.CEO_APPROVED;
|
||||
}
|
||||
|
||||
public void ceoReject(String reason) {
|
||||
this.status = NominationStatus.CEO_REJECTED;
|
||||
this.rejectionReason = reason;
|
||||
}
|
||||
|
||||
public void startApprovalWorkflow() {
|
||||
this.status = NominationStatus.APPROVAL_PENDING;
|
||||
}
|
||||
|
||||
public void approve() {
|
||||
this.status = NominationStatus.APPROVED;
|
||||
}
|
||||
|
||||
public void reject(String reason) {
|
||||
this.status = NominationStatus.REJECTED;
|
||||
this.rejectionReason = reason;
|
||||
}
|
||||
|
||||
public void withdraw() {
|
||||
this.status = NominationStatus.WITHDRAWN;
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface ElectionNominationRepository extends JpaRepository<ElectionNomination, Long> {
|
||||
|
||||
List<ElectionNomination> findByElectionConfigId(Long electionId);
|
||||
|
||||
List<ElectionNomination> findByElectionConfigIdAndStatus(Long electionId, NominationStatus status);
|
||||
|
||||
List<ElectionNomination> findByMemberId(Long memberId);
|
||||
|
||||
Optional<ElectionNomination> findByElectionConfigIdAndMemberIdAndElectionPostId(
|
||||
Long electionId, Long memberId, Long postId);
|
||||
|
||||
boolean existsByElectionConfigIdAndMemberIdAndElectionPostId(
|
||||
Long electionId, Long memberId, Long postId);
|
||||
|
||||
Optional<ElectionNomination> findByApplicationNumber(String applicationNumber);
|
||||
|
||||
@Query("SELECT n FROM ElectionNomination n WHERE n.electionConfig.id = :electionId " +
|
||||
"AND n.status NOT IN (org.apache.fineract.portfolio.election.domain.NominationStatus.REJECTED, org.apache.fineract.portfolio.election.domain.NominationStatus.WITHDRAWN)")
|
||||
List<ElectionNomination> findActiveNominationsByElection(@Param("electionId") Long electionId);
|
||||
|
||||
@Query("SELECT COUNT(n) FROM ElectionNomination n WHERE n.electionPost.id = :postId " +
|
||||
"AND n.status = org.apache.fineract.portfolio.election.domain.NominationStatus.APPROVED")
|
||||
long countApprovedByPost(@Param("postId") Long postId);
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_election_post")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class ElectionPost extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "election_id", nullable = false)
|
||||
private ElectionConfig electionConfig;
|
||||
|
||||
@Column(name = "post_name", nullable = false, length = 200)
|
||||
private String postName;
|
||||
|
||||
@Column(name = "total_seats", nullable = false)
|
||||
private Integer totalSeats = 1;
|
||||
|
||||
@Column(name = "is_active", nullable = false)
|
||||
private Boolean isActive = true;
|
||||
|
||||
public static ElectionPost create(ElectionConfig electionConfig, String postName, Integer totalSeats) {
|
||||
ElectionPost post = new ElectionPost();
|
||||
post.electionConfig = electionConfig;
|
||||
post.postName = postName;
|
||||
post.totalSeats = totalSeats != null ? totalSeats : 1;
|
||||
post.isActive = true;
|
||||
return post;
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface ElectionPostRepository extends JpaRepository<ElectionPost, Long> {
|
||||
|
||||
List<ElectionPost> findByElectionConfigIdAndIsActiveTrue(Long electionId);
|
||||
|
||||
List<ElectionPost> findByElectionConfigId(Long electionId);
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_election_result")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class ElectionResult extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "election_id", nullable = false)
|
||||
private ElectionConfig electionConfig;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "post_id", nullable = false)
|
||||
private ElectionPost electionPost;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "candidate_id", nullable = false)
|
||||
private ElectionCandidate candidate;
|
||||
|
||||
@Column(name = "vote_count", nullable = false)
|
||||
private Integer voteCount = 0;
|
||||
|
||||
@Column(name = "result", length = 20)
|
||||
private String result;
|
||||
|
||||
@Column(name = "rank_position")
|
||||
private Integer rankPosition;
|
||||
|
||||
@Column(name = "declared_by")
|
||||
private Long declaredBy;
|
||||
|
||||
@Column(name = "declared_date")
|
||||
private LocalDateTime declaredDate;
|
||||
|
||||
public static ElectionResult create(ElectionConfig electionConfig, ElectionPost post,
|
||||
ElectionCandidate candidate, int voteCount,
|
||||
String result, int rankPosition, Long declaredBy) {
|
||||
ElectionResult r = new ElectionResult();
|
||||
r.electionConfig = electionConfig;
|
||||
r.electionPost = post;
|
||||
r.candidate = candidate;
|
||||
r.voteCount = voteCount;
|
||||
r.result = result;
|
||||
r.rankPosition = rankPosition;
|
||||
r.declaredBy = declaredBy;
|
||||
r.declaredDate = LocalDateTime.now();
|
||||
return r;
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface ElectionResultRepository extends JpaRepository<ElectionResult, Long> {
|
||||
|
||||
List<ElectionResult> findByElectionConfigIdOrderByElectionPostIdAscRankPositionAsc(Long electionId);
|
||||
|
||||
List<ElectionResult> findByElectionConfigIdAndElectionPostId(Long electionId, Long postId);
|
||||
|
||||
boolean existsByElectionConfigId(Long electionId);
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
public enum ElectionStatus {
|
||||
DRAFT,
|
||||
ACTIVE,
|
||||
NOMINATION_CLOSED,
|
||||
VOTING_OPEN,
|
||||
VOTING_CLOSED,
|
||||
RESULT_DECLARED,
|
||||
CANCELLED
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
public enum ElectionType {
|
||||
DIRECTOR,
|
||||
GENERAL,
|
||||
BY_ELECTION
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_election_vote",
|
||||
uniqueConstraints = @UniqueConstraint(
|
||||
name = "uq_election_vote_member_post",
|
||||
columnNames = {"election_id", "post_id", "voter_member_id"}))
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class ElectionVote extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "election_id", nullable = false)
|
||||
private ElectionConfig electionConfig;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "post_id", nullable = false)
|
||||
private ElectionPost electionPost;
|
||||
|
||||
@Column(name = "voter_member_id", nullable = false)
|
||||
private Long voterMemberId;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "candidate_id", nullable = false)
|
||||
private ElectionCandidate candidate;
|
||||
|
||||
@Column(name = "vote_timestamp", nullable = false)
|
||||
private LocalDateTime voteTimestamp;
|
||||
|
||||
@Column(name = "booth_id", length = 100)
|
||||
private String boothId;
|
||||
|
||||
@Column(name = "ip_address", length = 50)
|
||||
private String ipAddress;
|
||||
|
||||
public static ElectionVote cast(ElectionConfig electionConfig, ElectionPost post,
|
||||
Long voterMemberId, ElectionCandidate candidate,
|
||||
String boothId, String ipAddress) {
|
||||
ElectionVote vote = new ElectionVote();
|
||||
vote.electionConfig = electionConfig;
|
||||
vote.electionPost = post;
|
||||
vote.voterMemberId = voterMemberId;
|
||||
vote.candidate = candidate;
|
||||
vote.voteTimestamp = LocalDateTime.now();
|
||||
vote.boothId = boothId;
|
||||
vote.ipAddress = ipAddress;
|
||||
return vote;
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface ElectionVoteRepository extends JpaRepository<ElectionVote, Long> {
|
||||
|
||||
boolean existsByElectionConfigIdAndElectionPostIdAndVoterMemberId(
|
||||
Long electionId, Long postId, Long voterMemberId);
|
||||
|
||||
@Query("SELECT v.candidate.id, COUNT(v) FROM ElectionVote v " +
|
||||
"WHERE v.electionConfig.id = :electionId AND v.electionPost.id = :postId " +
|
||||
"GROUP BY v.candidate.id ORDER BY COUNT(v) DESC")
|
||||
List<Object[]> countVotesByCandidate(@Param("electionId") Long electionId,
|
||||
@Param("postId") Long postId);
|
||||
|
||||
@Query("SELECT COUNT(DISTINCT v.voterMemberId) FROM ElectionVote v " +
|
||||
"WHERE v.electionConfig.id = :electionId")
|
||||
long countTotalVotersByElection(@Param("electionId") Long electionId);
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
public enum NominationStatus {
|
||||
DRAFT,
|
||||
SUBMITTED,
|
||||
UNDER_REVIEW,
|
||||
CEO_CHECK_PENDING,
|
||||
CEO_APPROVED,
|
||||
CEO_REJECTED,
|
||||
APPROVAL_PENDING,
|
||||
APPROVED,
|
||||
REJECTED,
|
||||
WITHDRAWN
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package org.apache.fineract.portfolio.election.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class ElectionAlreadyOngoingException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public ElectionAlreadyOngoingException() {
|
||||
super("error.msg.election.already.ongoing",
|
||||
"An election is already in progress. Complete or cancel the existing election before creating a new one.");
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package org.apache.fineract.portfolio.election.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
||||
|
||||
public class ElectionNominationNotFoundException extends AbstractPlatformResourceNotFoundException {
|
||||
|
||||
public ElectionNominationNotFoundException(Long id) {
|
||||
super("error.msg.election.nomination.not.found",
|
||||
"Election nomination with id " + id + " does not exist", id);
|
||||
}
|
||||
|
||||
public ElectionNominationNotFoundException(String applicationNumber) {
|
||||
super("error.msg.election.nomination.not.found",
|
||||
"Election nomination with application number " + applicationNumber + " does not exist",
|
||||
applicationNumber);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package org.apache.fineract.portfolio.election.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class ElectionNotActiveException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public ElectionNotActiveException(Long electionId, String currentStatus) {
|
||||
super("error.msg.election.not.active",
|
||||
"Election " + electionId + " is not open for nominations. Current status: " + currentStatus);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package org.apache.fineract.portfolio.election.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
||||
|
||||
public class ElectionNotFoundException extends AbstractPlatformResourceNotFoundException {
|
||||
|
||||
public ElectionNotFoundException(Long id) {
|
||||
super("error.msg.election.not.found", "Election with id " + id + " does not exist", id);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package org.apache.fineract.portfolio.election.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class IneligibleNominationException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public IneligibleNominationException(String reason) {
|
||||
super("error.msg.election.nomination.ineligible",
|
||||
"Nomination is ineligible: " + reason);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package org.apache.fineract.portfolio.election.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class InvalidNominationStateException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public InvalidNominationStateException(String action, String currentStatus) {
|
||||
super("error.msg.election.nomination.invalid.state",
|
||||
"Cannot perform action '" + action + "' on nomination in status: " + currentStatus);
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package org.apache.fineract.portfolio.election.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class NominationAlreadyExistsException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public NominationAlreadyExistsException(Long memberId, Long electionId, Long postId) {
|
||||
super("error.msg.election.nomination.already.exists",
|
||||
"Member " + memberId + " has already submitted a nomination for election "
|
||||
+ electionId + " post " + postId);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package org.apache.fineract.portfolio.election.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class VoteAlreadyCastException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public VoteAlreadyCastException(Long memberId, Long postId) {
|
||||
super("error.msg.election.vote.already.cast",
|
||||
"Member " + memberId + " has already cast their vote for post " + postId);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package org.apache.fineract.portfolio.election.service;
|
||||
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionCeoPrecheck;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionNomination;
|
||||
|
||||
public interface ElectionCeoPreCheckService {
|
||||
|
||||
ElectionCeoPrecheck performDirectorLoanCheck(ElectionNomination nomination);
|
||||
|
||||
ElectionCeoPrecheck performRelationAccountCheck(ElectionNomination nomination);
|
||||
|
||||
String generateNoDueCertificateNumber(ElectionNomination nomination);
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package org.apache.fineract.portfolio.election.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.portfolio.election.domain.CheckResult;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionCeoPrecheck;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionCeoPrecheckRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionNomination;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ElectionCeoPreCheckServiceImpl implements ElectionCeoPreCheckService {
|
||||
|
||||
private final ElectionCeoPrecheckRepository ceoPrecheckRepository;
|
||||
private final ElectionNoDueCertificateService noDueCertificateService;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ElectionCeoPrecheck performDirectorLoanCheck(ElectionNomination nomination) {
|
||||
ElectionCeoPrecheck precheck = ceoPrecheckRepository
|
||||
.findByNominationId(nomination.getId())
|
||||
.orElse(ElectionCeoPrecheck.create(nomination));
|
||||
|
||||
try {
|
||||
String query = """
|
||||
SELECT COALESCE(SUM(la.total_outstanding_derived), 0) AS outstanding
|
||||
FROM m_loan la
|
||||
INNER JOIN m_client c ON c.id = la.client_id
|
||||
INNER JOIN m_share_account sa ON sa.client_id = c.id
|
||||
WHERE sa.id = ?
|
||||
AND la.loan_status_id = 300
|
||||
""";
|
||||
|
||||
BigDecimal outstanding = jdbcTemplate.queryForObject(
|
||||
query, BigDecimal.class, nomination.getShareAccountId());
|
||||
|
||||
if (outstanding == null) outstanding = BigDecimal.ZERO;
|
||||
|
||||
boolean pass = outstanding.compareTo(BigDecimal.ZERO) == 0;
|
||||
String remarks = pass
|
||||
? "No outstanding director loans found"
|
||||
: "Outstanding loan amount: " + outstanding;
|
||||
|
||||
precheck.completeDirectorLoanCheck(
|
||||
pass ? CheckResult.PASS : CheckResult.FAIL, outstanding, remarks);
|
||||
} catch (Exception e) {
|
||||
log.error("Director loan check failed for nomination {}: {}", nomination.getId(), e.getMessage());
|
||||
precheck.completeDirectorLoanCheck(CheckResult.FAIL, BigDecimal.ZERO,
|
||||
"Check failed: " + e.getMessage());
|
||||
}
|
||||
|
||||
return ceoPrecheckRepository.save(precheck);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ElectionCeoPrecheck performRelationAccountCheck(ElectionNomination nomination) {
|
||||
ElectionCeoPrecheck precheck = ceoPrecheckRepository
|
||||
.findByNominationId(nomination.getId())
|
||||
.orElse(ElectionCeoPrecheck.create(nomination));
|
||||
|
||||
try {
|
||||
String query = """
|
||||
SELECT c.display_name, sa.account_no, sa.status_enum
|
||||
FROM m_share_account sa
|
||||
INNER JOIN m_client c ON c.id = sa.client_id
|
||||
WHERE c.id IN (
|
||||
SELECT DISTINCT sf.family_member_client_id
|
||||
FROM m_family_member sf
|
||||
WHERE sf.client_id = (
|
||||
SELECT client_id FROM m_share_account WHERE id = ?
|
||||
)
|
||||
AND sf.is_dependent = 0
|
||||
)
|
||||
AND sa.status_enum = 300
|
||||
""";
|
||||
|
||||
List<Map<String, Object>> rows = jdbcTemplate.queryForList(
|
||||
query, nomination.getShareAccountId());
|
||||
|
||||
List<String> relatedAccounts = new ArrayList<>();
|
||||
for (Map<String, Object> row : rows) {
|
||||
relatedAccounts.add(row.get("display_name") + " - " + row.get("account_no"));
|
||||
}
|
||||
|
||||
boolean pass = relatedAccounts.isEmpty();
|
||||
String details = pass ? "[]" : "[\"" + String.join("\",\"", relatedAccounts) + "\"]";
|
||||
String remarks = pass
|
||||
? "No related director accounts found"
|
||||
: relatedAccounts.size() + " related account(s) found: " + String.join(", ", relatedAccounts);
|
||||
|
||||
precheck.completeRelationAcCheck(
|
||||
pass ? CheckResult.PASS : CheckResult.FAIL, details, remarks);
|
||||
} catch (Exception e) {
|
||||
log.error("Relation account check failed for nomination {}: {}", nomination.getId(), e.getMessage());
|
||||
precheck.completeRelationAcCheck(CheckResult.FAIL, "[]", "Check failed: " + e.getMessage());
|
||||
}
|
||||
|
||||
return ceoPrecheckRepository.save(precheck);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public String generateNoDueCertificateNumber(ElectionNomination nomination) {
|
||||
ElectionCeoPrecheck precheck = ceoPrecheckRepository
|
||||
.findByNominationId(nomination.getId())
|
||||
.orElse(ElectionCeoPrecheck.create(nomination));
|
||||
|
||||
boolean hasNoDues = noDueCertificateService.hasNoDues(
|
||||
nomination.getMemberId(), nomination.getShareAccountId());
|
||||
|
||||
if (hasNoDues) {
|
||||
String certNumber = noDueCertificateService.generateCertificateNumber(nomination);
|
||||
precheck.generateNoDueCertificate(certNumber);
|
||||
ceoPrecheckRepository.save(precheck);
|
||||
return certNumber;
|
||||
} else {
|
||||
precheck.completeDirectorLoanCheck(CheckResult.FAIL, BigDecimal.ZERO,
|
||||
"Member has outstanding dues — NDC cannot be issued");
|
||||
ceoPrecheckRepository.save(precheck);
|
||||
throw new IllegalStateException("Member has outstanding dues. No Due Certificate cannot be issued.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package org.apache.fineract.portfolio.election.service;
|
||||
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionEligibilityCheck;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionNomination;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionPost;
|
||||
|
||||
public interface ElectionEligibilityService {
|
||||
|
||||
ElectionEligibilityCheck performCheck(ElectionNomination nomination, ElectionPost post, Long checkedBy);
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package org.apache.fineract.portfolio.election.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionEligibilityCheck;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionEligibilityCheckRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionNomination;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionPost;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ElectionEligibilityServiceImpl implements ElectionEligibilityService {
|
||||
|
||||
private final ElectionEligibilityCheckRepository eligibilityCheckRepository;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ElectionEligibilityCheck performCheck(ElectionNomination nomination,
|
||||
ElectionPost post, Long checkedBy) {
|
||||
ElectionEligibilityCheck check = eligibilityCheckRepository
|
||||
.findByNominationId(nomination.getId())
|
||||
.orElse(ElectionEligibilityCheck.create(nomination));
|
||||
|
||||
check.complete(checkedBy, "Eligibility check passed", true);
|
||||
return eligibilityCheckRepository.save(check);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package org.apache.fineract.portfolio.election.service;
|
||||
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionNomination;
|
||||
|
||||
public interface ElectionNoDueCertificateService {
|
||||
|
||||
String generateCertificateNumber(ElectionNomination nomination);
|
||||
|
||||
boolean hasNoDues(Long memberId, Long shareAccountId);
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package org.apache.fineract.portfolio.election.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionNomination;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ElectionNoDueCertificateServiceImpl implements ElectionNoDueCertificateService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Override
|
||||
public String generateCertificateNumber(ElectionNomination nomination) {
|
||||
String datePart = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
return "NDC-" + datePart + "-" + String.format("%06d", nomination.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNoDues(Long memberId, Long shareAccountId) {
|
||||
try {
|
||||
String loanDueQuery = """
|
||||
SELECT COUNT(la.id)
|
||||
FROM m_loan la
|
||||
WHERE la.client_id = (
|
||||
SELECT sa.client_id FROM m_share_account sa WHERE sa.id = ?
|
||||
)
|
||||
AND la.loan_status_id = 300
|
||||
AND la.total_outstanding_derived > 0
|
||||
""";
|
||||
Integer loanDueCount = jdbcTemplate.queryForObject(loanDueQuery, Integer.class, shareAccountId);
|
||||
return loanDueCount == null || loanDueCount == 0;
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not perform no-due check for member {}: {}", memberId, e.getMessage());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package org.apache.fineract.portfolio.election.service;
|
||||
|
||||
import org.apache.fineract.portfolio.election.data.ElectionCandidateData;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionConfigData;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionNominationData;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionResultData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ElectionReadPlatformService {
|
||||
|
||||
List<ElectionConfigData> retrieveAllElections();
|
||||
|
||||
ElectionConfigData retrieveElection(Long electionId);
|
||||
|
||||
List<ElectionNominationData> retrieveNominationsByElection(Long electionId, String status);
|
||||
|
||||
ElectionNominationData retrieveNomination(Long nominationId);
|
||||
|
||||
List<ElectionNominationData> retrieveNominationsByMember(Long memberId);
|
||||
|
||||
List<ElectionCandidateData> retrieveCandidatesByElection(Long electionId);
|
||||
|
||||
List<ElectionCandidateData> retrieveCandidatesByPost(Long electionId, Long postId);
|
||||
|
||||
List<ElectionResultData> retrieveResultsByElection(Long electionId);
|
||||
}
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
package org.apache.fineract.portfolio.election.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionApprovalStepData;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionCandidateData;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionCeoPreCheckData;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionConfigData;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionEligibilityCheckData;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionNominationData;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionPostData;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionResultData;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionApprovalStepRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionCandidateRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionCeoPrecheckRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionConfig;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionConfigRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionEligibilityCheckRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionNomination;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionNominationRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionResultRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.NominationStatus;
|
||||
import org.apache.fineract.portfolio.election.exception.ElectionNominationNotFoundException;
|
||||
import org.apache.fineract.portfolio.election.exception.ElectionNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Period;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ElectionReadPlatformServiceImpl implements ElectionReadPlatformService {
|
||||
|
||||
private final ElectionConfigRepository electionConfigRepository;
|
||||
private final ElectionNominationRepository nominationRepository;
|
||||
private final ElectionCandidateRepository candidateRepository;
|
||||
private final ElectionApprovalStepRepository approvalStepRepository;
|
||||
private final ElectionEligibilityCheckRepository eligibilityCheckRepository;
|
||||
private final ElectionCeoPrecheckRepository ceoPrecheckRepository;
|
||||
private final ElectionResultRepository resultRepository;
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<ElectionConfigData> retrieveAllElections() {
|
||||
return electionConfigRepository.findByIsActiveTrue().stream()
|
||||
.map(this::toConfigData)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public ElectionConfigData retrieveElection(Long electionId) {
|
||||
ElectionConfig election = electionConfigRepository.findById(electionId)
|
||||
.orElseThrow(() -> new ElectionNotFoundException(electionId));
|
||||
ElectionConfigData data = toConfigData(election);
|
||||
data.setPosts(election.getPosts().stream()
|
||||
.filter(p -> p.getIsActive())
|
||||
.map(p -> ElectionPostData.builder()
|
||||
.id(p.getId())
|
||||
.electionId(electionId)
|
||||
.postName(p.getPostName())
|
||||
.totalSeats(p.getTotalSeats())
|
||||
.isActive(p.getIsActive())
|
||||
.filledSeats(nominationRepository.countApprovedByPost(p.getId()))
|
||||
.remainingSeats((long) Math.max(0, p.getTotalSeats()
|
||||
- nominationRepository.countApprovedByPost(p.getId())))
|
||||
.build())
|
||||
.collect(Collectors.toList()));
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<ElectionNominationData> retrieveNominationsByElection(Long electionId, String status) {
|
||||
List<ElectionNomination> nominations = (status != null && !status.isBlank())
|
||||
? nominationRepository.findByElectionConfigIdAndStatus(
|
||||
electionId, NominationStatus.valueOf(status))
|
||||
: nominationRepository.findByElectionConfigId(electionId);
|
||||
return nominations.stream().map(this::toNominationData).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public ElectionNominationData retrieveNomination(Long nominationId) {
|
||||
ElectionNomination nomination = nominationRepository.findById(nominationId)
|
||||
.orElseThrow(() -> new ElectionNominationNotFoundException(nominationId));
|
||||
ElectionNominationData data = toNominationData(nomination);
|
||||
|
||||
eligibilityCheckRepository.findByNominationId(nominationId).ifPresent(ec ->
|
||||
data.setEligibilityCheck(ElectionEligibilityCheckData.builder()
|
||||
.id(ec.getId())
|
||||
.nominationId(nominationId)
|
||||
.overallResult(ec.getOverallResult() != null ? ec.getOverallResult().name() : null)
|
||||
.checkedBy(ec.getCheckedBy())
|
||||
.checkedDate(ec.getCheckedDate())
|
||||
.remarks(ec.getRemarks())
|
||||
.build()));
|
||||
|
||||
ceoPrecheckRepository.findByNominationId(nominationId).ifPresent(cp ->
|
||||
data.setCeoPreCheck(ElectionCeoPreCheckData.builder()
|
||||
.id(cp.getId())
|
||||
.nominationId(nominationId)
|
||||
.directorLoanCheckResult(cp.getDirectorLoanCheckResult() != null
|
||||
? cp.getDirectorLoanCheckResult().name() : null)
|
||||
.directorLoanOutstanding(cp.getDirectorLoanOutstanding())
|
||||
.directorLoanRemarks(cp.getDirectorLoanRemarks())
|
||||
.relationAcCheckResult(cp.getRelationAcCheckResult() != null
|
||||
? cp.getRelationAcCheckResult().name() : null)
|
||||
.relationAccountsDetails(cp.getRelationAccountsDetails())
|
||||
.relationAcRemarks(cp.getRelationAcRemarks())
|
||||
.noDueCheckResult(cp.getNoDueCheckResult() != null
|
||||
? cp.getNoDueCheckResult().name() : null)
|
||||
.noDueCertificateNumber(cp.getNoDueCertificateNumber())
|
||||
.noDueGeneratedDate(cp.getNoDueGeneratedDate())
|
||||
.overallResult(cp.getOverallResult() != null ? cp.getOverallResult().name() : null)
|
||||
.checkedBy(cp.getCheckedBy())
|
||||
.checkedDate(cp.getCheckedDate())
|
||||
.remarks(cp.getRemarks())
|
||||
.build()));
|
||||
|
||||
data.setApprovalSteps(
|
||||
approvalStepRepository.findByNominationIdOrderByStepNumberAsc(nominationId).stream()
|
||||
.map(s -> ElectionApprovalStepData.builder()
|
||||
.id(s.getId())
|
||||
.nominationId(nominationId)
|
||||
.stepNumber(s.getStepNumber())
|
||||
.stepName(s.getStepName())
|
||||
.approverRole(s.getApproverRole())
|
||||
.approverUserId(s.getApproverUserId())
|
||||
.status(s.getStatus() != null ? s.getStatus().name() : null)
|
||||
.remarks(s.getRemarks())
|
||||
.actionDate(s.getActionDate())
|
||||
.familyDetailsVerified(s.getFamilyDetailsVerified())
|
||||
.familyDetailsJson(s.getFamilyDetailsJson())
|
||||
.build())
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<ElectionNominationData> retrieveNominationsByMember(Long memberId) {
|
||||
return nominationRepository.findByMemberId(memberId).stream()
|
||||
.map(this::toNominationData)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<ElectionCandidateData> retrieveCandidatesByElection(Long electionId) {
|
||||
return candidateRepository.findAllActiveCandidates(electionId).stream()
|
||||
.map(c -> ElectionCandidateData.builder()
|
||||
.id(c.getId())
|
||||
.electionId(electionId)
|
||||
.nominationId(c.getNomination().getId())
|
||||
.postId(c.getElectionPost().getId())
|
||||
.postName(c.getElectionPost().getPostName())
|
||||
.candidateName(c.getCandidateName())
|
||||
.serialNumber(c.getSerialNumber())
|
||||
.photoUrl(c.getPhotoUrl())
|
||||
.isActive(c.getIsActive())
|
||||
.approvedDate(c.getApprovedDate())
|
||||
.symbol(c.getSymbol())
|
||||
.briefProfile(c.getBriefProfile())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<ElectionCandidateData> retrieveCandidatesByPost(Long electionId, Long postId) {
|
||||
return candidateRepository.findByElectionConfigIdAndElectionPostIdAndIsActiveTrue(electionId, postId)
|
||||
.stream()
|
||||
.map(c -> ElectionCandidateData.builder()
|
||||
.id(c.getId())
|
||||
.electionId(electionId)
|
||||
.nominationId(c.getNomination().getId())
|
||||
.postId(c.getElectionPost().getId())
|
||||
.postName(c.getElectionPost().getPostName())
|
||||
.candidateName(c.getCandidateName())
|
||||
.serialNumber(c.getSerialNumber())
|
||||
.photoUrl(c.getPhotoUrl())
|
||||
.symbol(c.getSymbol())
|
||||
.briefProfile(c.getBriefProfile())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<ElectionResultData> retrieveResultsByElection(Long electionId) {
|
||||
return resultRepository.findByElectionConfigIdOrderByElectionPostIdAscRankPositionAsc(electionId)
|
||||
.stream()
|
||||
.map(r -> ElectionResultData.builder()
|
||||
.id(r.getId())
|
||||
.electionId(electionId)
|
||||
.postId(r.getElectionPost().getId())
|
||||
.postName(r.getElectionPost().getPostName())
|
||||
.candidateId(r.getCandidate().getId())
|
||||
.candidateName(r.getCandidate().getCandidateName())
|
||||
.voteCount(r.getVoteCount())
|
||||
.result(r.getResult())
|
||||
.rankPosition(r.getRankPosition())
|
||||
.declaredDate(r.getDeclaredDate())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private ElectionConfigData toConfigData(ElectionConfig election) {
|
||||
return ElectionConfigData.builder()
|
||||
.id(election.getId())
|
||||
.title(election.getTitle())
|
||||
.description(election.getDescription())
|
||||
.electionType(election.getElectionType() != null ? election.getElectionType().name() : null)
|
||||
.nominationStartDate(election.getNominationStartDate())
|
||||
.nominationEndDate(election.getNominationEndDate())
|
||||
.electionDate(election.getElectionDate())
|
||||
.resultDate(election.getResultDate())
|
||||
.status(election.getStatus() != null ? election.getStatus().name() : null)
|
||||
.isActive(election.getIsActive())
|
||||
.remarks(election.getRemarks())
|
||||
.build();
|
||||
}
|
||||
|
||||
private ElectionNominationData toNominationData(ElectionNomination n) {
|
||||
Integer age = null;
|
||||
if (n.getDateOfBirth() != null) {
|
||||
age = Period.between(n.getDateOfBirth(), LocalDate.now()).getYears();
|
||||
}
|
||||
return ElectionNominationData.builder()
|
||||
.id(n.getId())
|
||||
.electionId(n.getElectionConfig().getId())
|
||||
.electionTitle(n.getElectionConfig().getTitle())
|
||||
.postId(n.getElectionPost().getId())
|
||||
.postName(n.getElectionPost().getPostName())
|
||||
.memberId(n.getMemberId())
|
||||
.shareAccountId(n.getShareAccountId())
|
||||
.applicationNumber(n.getApplicationNumber())
|
||||
.applicantName(n.getApplicantName())
|
||||
.dateOfBirth(n.getDateOfBirth())
|
||||
.age(age)
|
||||
.gender(n.getGender())
|
||||
.caste(n.getCaste())
|
||||
.casteCategory(n.getCasteCategory())
|
||||
.location(n.getLocation())
|
||||
.serviceDetails(n.getServiceDetails())
|
||||
.mobileNumber(n.getMobileNumber())
|
||||
.email(n.getEmail())
|
||||
.address(n.getAddress())
|
||||
.occupation(n.getOccupation())
|
||||
.shareCount(n.getShareCount())
|
||||
.status(n.getStatus() != null ? n.getStatus().name() : null)
|
||||
.statusDisplayName(statusDisplayName(n.getStatus()))
|
||||
.rejectionReason(n.getRejectionReason())
|
||||
.submittedDate(n.getSubmittedDate())
|
||||
.declarationAccepted(n.getDeclarationAccepted())
|
||||
.photoDocumentId(n.getPhotoDocumentId())
|
||||
.build();
|
||||
}
|
||||
|
||||
private String statusDisplayName(NominationStatus status) {
|
||||
if (status == null) return null;
|
||||
return switch (status) {
|
||||
case DRAFT -> "Draft";
|
||||
case SUBMITTED -> "Submitted";
|
||||
case UNDER_REVIEW -> "Under Review";
|
||||
case CEO_CHECK_PENDING -> "CEO Check Pending";
|
||||
case CEO_APPROVED -> "CEO Approved";
|
||||
case CEO_REJECTED -> "CEO Rejected";
|
||||
case APPROVAL_PENDING -> "Approval Pending";
|
||||
case APPROVED -> "Approved";
|
||||
case REJECTED -> "Rejected";
|
||||
case WITHDRAWN -> "Withdrawn";
|
||||
};
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package org.apache.fineract.portfolio.election.service;
|
||||
|
||||
import org.apache.fineract.portfolio.election.data.ElectionApprovalActionRequest;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionConfigRequest;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionNominationRequest;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionVoteRequest;
|
||||
|
||||
public interface ElectionWritePlatformService {
|
||||
|
||||
Long createElection(ElectionConfigRequest request);
|
||||
|
||||
void updateElection(Long electionId, ElectionConfigRequest request);
|
||||
|
||||
void activateElection(Long electionId);
|
||||
|
||||
void closeNominations(Long electionId);
|
||||
|
||||
void openVoting(Long electionId);
|
||||
|
||||
void closeVoting(Long electionId);
|
||||
|
||||
void cancelElection(Long electionId, String reason);
|
||||
|
||||
Long submitNomination(ElectionNominationRequest request);
|
||||
|
||||
void withdrawNomination(Long nominationId);
|
||||
|
||||
void runEligibilityCheck(Long nominationId);
|
||||
|
||||
void runCeoPreCheck(Long nominationId);
|
||||
|
||||
String generateNoDueCertificate(Long nominationId);
|
||||
|
||||
void processApprovalStep(Long nominationId, ElectionApprovalActionRequest request);
|
||||
|
||||
Long approveCandidate(Long nominationId, Long approvedBy);
|
||||
|
||||
void removeCandidate(Long candidateId, String reason);
|
||||
|
||||
void castVote(ElectionVoteRequest request);
|
||||
|
||||
void declareResults(Long electionId, Long declaredBy);
|
||||
}
|
||||
+411
@@ -0,0 +1,411 @@
|
||||
package org.apache.fineract.portfolio.election.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionApprovalActionRequest;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionConfigRequest;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionNominationRequest;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionPostRequest;
|
||||
import org.apache.fineract.portfolio.election.data.ElectionVoteRequest;
|
||||
import org.apache.fineract.portfolio.election.domain.ApprovalStepStatus;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionApprovalStep;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionApprovalStepRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionCandidate;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionCandidateRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionCeoPrecheckRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionConfig;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionConfigRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionNomination;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionNominationRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionPost;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionPostRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionResult;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionResultRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionStatus;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionType;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionVote;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionVoteRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.NominationStatus;
|
||||
import org.apache.fineract.portfolio.election.exception.ElectionAlreadyOngoingException;
|
||||
import org.apache.fineract.portfolio.election.exception.ElectionNotFoundException;
|
||||
import org.apache.fineract.portfolio.election.exception.ElectionNominationNotFoundException;
|
||||
import org.apache.fineract.portfolio.election.exception.ElectionNotActiveException;
|
||||
import org.apache.fineract.portfolio.election.exception.InvalidNominationStateException;
|
||||
import org.apache.fineract.portfolio.election.exception.NominationAlreadyExistsException;
|
||||
import org.apache.fineract.portfolio.election.exception.VoteAlreadyCastException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ElectionWritePlatformServiceImpl implements ElectionWritePlatformService {
|
||||
|
||||
private static final List<String> APPROVAL_STEPS = List.of(
|
||||
"Director Review",
|
||||
"Family Details Verification",
|
||||
"Final Approval",
|
||||
"CEO Final Sign-off"
|
||||
);
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final ElectionConfigRepository electionConfigRepository;
|
||||
private final ElectionPostRepository electionPostRepository;
|
||||
private final ElectionNominationRepository nominationRepository;
|
||||
private final ElectionCandidateRepository candidateRepository;
|
||||
private final ElectionApprovalStepRepository approvalStepRepository;
|
||||
private final ElectionCeoPrecheckRepository ceoPrecheckRepository;
|
||||
private final ElectionVoteRepository voteRepository;
|
||||
private final ElectionResultRepository resultRepository;
|
||||
private final ElectionEligibilityService eligibilityService;
|
||||
private final ElectionCeoPreCheckService ceoPreCheckService;
|
||||
private final ElectionNoDueCertificateService noDueCertificateService;
|
||||
|
||||
private static final Set<ElectionStatus> ONGOING_STATUSES = Set.of(
|
||||
ElectionStatus.ACTIVE,
|
||||
ElectionStatus.NOMINATION_CLOSED,
|
||||
ElectionStatus.VOTING_OPEN,
|
||||
ElectionStatus.VOTING_CLOSED
|
||||
);
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Long createElection(ElectionConfigRequest request) {
|
||||
context.authenticatedUser().validateHasPermissionTo("CREATE_ELECTION_CONFIG");
|
||||
|
||||
if (electionConfigRepository.existsByStatusIn(ONGOING_STATUSES)) {
|
||||
throw new ElectionAlreadyOngoingException();
|
||||
}
|
||||
|
||||
ElectionConfig election = ElectionConfig.create(
|
||||
request.getTitle(),
|
||||
request.getDescription(),
|
||||
ElectionType.valueOf(request.getElectionType()),
|
||||
request.getNominationStartDate(),
|
||||
request.getNominationEndDate(),
|
||||
request.getElectionDate(),
|
||||
request.getResultDate()
|
||||
);
|
||||
ElectionConfig saved = electionConfigRepository.saveAndFlush(election);
|
||||
|
||||
if (request.getPosts() != null) {
|
||||
for (ElectionPostRequest postReq : request.getPosts()) {
|
||||
ElectionPost post = ElectionPost.create(saved, postReq.getPostName(), postReq.getTotalSeats());
|
||||
electionPostRepository.saveAndFlush(post);
|
||||
}
|
||||
}
|
||||
|
||||
return saved.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void updateElection(Long electionId, ElectionConfigRequest request) {
|
||||
context.authenticatedUser().validateHasPermissionTo("UPDATE_ELECTION");
|
||||
ElectionConfig election = electionConfigRepository.findById(electionId)
|
||||
.orElseThrow(() -> new ElectionNotFoundException(electionId));
|
||||
if (request.getTitle() != null) election.setTitle(request.getTitle());
|
||||
if (request.getDescription() != null) election.setDescription(request.getDescription());
|
||||
if (request.getNominationStartDate() != null) election.setNominationStartDate(request.getNominationStartDate());
|
||||
if (request.getNominationEndDate() != null) election.setNominationEndDate(request.getNominationEndDate());
|
||||
if (request.getElectionDate() != null) election.setElectionDate(request.getElectionDate());
|
||||
if (request.getResultDate() != null) election.setResultDate(request.getResultDate());
|
||||
electionConfigRepository.save(election);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void activateElection(Long electionId) {
|
||||
context.authenticatedUser().validateHasPermissionTo("UPDATE_ELECTION");
|
||||
ElectionConfig election = electionConfigRepository.findById(electionId)
|
||||
.orElseThrow(() -> new ElectionNotFoundException(electionId));
|
||||
election.activate();
|
||||
electionConfigRepository.save(election);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void closeNominations(Long electionId) {
|
||||
context.authenticatedUser().validateHasPermissionTo("UPDATE_ELECTION");
|
||||
ElectionConfig election = electionConfigRepository.findById(electionId)
|
||||
.orElseThrow(() -> new ElectionNotFoundException(electionId));
|
||||
election.closeNominations();
|
||||
electionConfigRepository.save(election);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void openVoting(Long electionId) {
|
||||
context.authenticatedUser().validateHasPermissionTo("UPDATE_ELECTION");
|
||||
ElectionConfig election = electionConfigRepository.findById(electionId)
|
||||
.orElseThrow(() -> new ElectionNotFoundException(electionId));
|
||||
election.openVoting();
|
||||
electionConfigRepository.save(election);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void closeVoting(Long electionId) {
|
||||
context.authenticatedUser().validateHasPermissionTo("UPDATE_ELECTION");
|
||||
ElectionConfig election = electionConfigRepository.findById(electionId)
|
||||
.orElseThrow(() -> new ElectionNotFoundException(electionId));
|
||||
election.closeVoting();
|
||||
electionConfigRepository.save(election);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void cancelElection(Long electionId, String reason) {
|
||||
context.authenticatedUser().validateHasPermissionTo("UPDATE_ELECTION");
|
||||
ElectionConfig election = electionConfigRepository.findById(electionId)
|
||||
.orElseThrow(() -> new ElectionNotFoundException(electionId));
|
||||
election.cancel(reason);
|
||||
electionConfigRepository.save(election);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Long submitNomination(ElectionNominationRequest request) {
|
||||
ElectionConfig election = electionConfigRepository.findById(request.getElectionId())
|
||||
.orElseThrow(() -> new ElectionNotFoundException(request.getElectionId()));
|
||||
|
||||
if (!election.isNominationOpen()) {
|
||||
throw new ElectionNotActiveException(election.getId(), election.getStatus().name());
|
||||
}
|
||||
|
||||
ElectionPost post = electionPostRepository.findById(request.getPostId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Post not found: " + request.getPostId()));
|
||||
|
||||
if (nominationRepository.existsByElectionConfigIdAndMemberIdAndElectionPostId(
|
||||
request.getElectionId(), request.getMemberId(), request.getPostId())) {
|
||||
throw new NominationAlreadyExistsException(
|
||||
request.getMemberId(), request.getElectionId(), request.getPostId());
|
||||
}
|
||||
|
||||
ElectionNomination nomination = ElectionNomination.create(
|
||||
election, post, request.getMemberId(), request.getShareAccountId(),
|
||||
request.getApplicantName(), request.getDateOfBirth(), request.getGender(),
|
||||
request.getCaste(), request.getCasteCategory(), request.getLocation(),
|
||||
request.getServiceDetails(), request.getMobileNumber(), request.getEmail(),
|
||||
request.getAddress(), request.getOccupation(), request.getShareCount()
|
||||
);
|
||||
nomination.setDeclarationAccepted(Boolean.TRUE.equals(request.getDeclarationAccepted()));
|
||||
nomination.setPhotoDocumentId(request.getPhotoDocumentId());
|
||||
ElectionNomination saved = nominationRepository.saveAndFlush(nomination);
|
||||
|
||||
String appNumber = generateApplicationNumber(saved.getId(), election.getId());
|
||||
saved.submit(appNumber);
|
||||
nominationRepository.saveAndFlush(saved);
|
||||
|
||||
return saved.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void withdrawNomination(Long nominationId) {
|
||||
ElectionNomination nomination = nominationRepository.findById(nominationId)
|
||||
.orElseThrow(() -> new ElectionNominationNotFoundException(nominationId));
|
||||
if (nomination.getStatus() == NominationStatus.APPROVED) {
|
||||
throw new InvalidNominationStateException("withdraw", nomination.getStatus().name());
|
||||
}
|
||||
nomination.withdraw();
|
||||
nominationRepository.save(nomination);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void runEligibilityCheck(Long nominationId) {
|
||||
context.authenticatedUser().validateHasPermissionTo("ELIGIBILITY_CHECK_ELECTION");
|
||||
ElectionNomination nomination = nominationRepository.findById(nominationId)
|
||||
.orElseThrow(() -> new ElectionNominationNotFoundException(nominationId));
|
||||
|
||||
Long checkedBy = context.authenticatedUser().getId();
|
||||
eligibilityService.performCheck(nomination, nomination.getElectionPost(), checkedBy);
|
||||
nomination.markUnderReview();
|
||||
nominationRepository.save(nomination);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void runCeoPreCheck(Long nominationId) {
|
||||
context.authenticatedUser().validateHasPermissionTo("CEO_PRECHECK_ELECTION");
|
||||
ElectionNomination nomination = nominationRepository.findById(nominationId)
|
||||
.orElseThrow(() -> new ElectionNominationNotFoundException(nominationId));
|
||||
|
||||
nomination.sendToCeoCheck();
|
||||
nominationRepository.save(nomination);
|
||||
ceoPreCheckService.performDirectorLoanCheck(nomination);
|
||||
ceoPreCheckService.performRelationAccountCheck(nomination);
|
||||
|
||||
ceoPrecheckRepository.findByNominationId(nominationId).ifPresent(precheck -> {
|
||||
precheck.finalise(context.authenticatedUser().getId(), "CEO pre-check completed");
|
||||
ceoPrecheckRepository.save(precheck);
|
||||
if (org.apache.fineract.portfolio.election.domain.CheckResult.PASS == precheck.getOverallResult()) {
|
||||
nomination.ceoApprove();
|
||||
} else {
|
||||
nomination.ceoReject("Pre-check failed: " + precheck.getRemarks());
|
||||
}
|
||||
nominationRepository.save(nomination);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public String generateNoDueCertificate(Long nominationId) {
|
||||
context.authenticatedUser().validateHasPermissionTo("CEO_PRECHECK_ELECTION");
|
||||
ElectionNomination nomination = nominationRepository.findById(nominationId)
|
||||
.orElseThrow(() -> new ElectionNominationNotFoundException(nominationId));
|
||||
return ceoPreCheckService.generateNoDueCertificateNumber(nomination);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void processApprovalStep(Long nominationId, ElectionApprovalActionRequest request) {
|
||||
context.authenticatedUser().validateHasPermissionTo("APPROVE_ELECTION_NOMINATION");
|
||||
ElectionNomination nomination = nominationRepository.findById(nominationId)
|
||||
.orElseThrow(() -> new ElectionNominationNotFoundException(nominationId));
|
||||
|
||||
if (nomination.getStatus() == NominationStatus.CEO_APPROVED
|
||||
&& nomination.getApprovalSteps().isEmpty()) {
|
||||
initApprovalSteps(nomination);
|
||||
}
|
||||
|
||||
ElectionApprovalStep currentStep = approvalStepRepository
|
||||
.findNextPendingStep(nominationId)
|
||||
.orElseThrow(() -> new InvalidNominationStateException("approve", "No pending steps"));
|
||||
|
||||
Long approverId = context.authenticatedUser().getId();
|
||||
|
||||
if ("APPROVE".equalsIgnoreCase(request.getAction())) {
|
||||
currentStep.approve(approverId, request.getRemarks(),
|
||||
Boolean.TRUE.equals(request.getFamilyDetailsVerified()),
|
||||
request.getFamilyDetailsJson());
|
||||
approvalStepRepository.save(currentStep);
|
||||
|
||||
boolean allApproved = !approvalStepRepository
|
||||
.existsByNominationIdAndStatus(nominationId, ApprovalStepStatus.PENDING);
|
||||
if (allApproved) {
|
||||
nomination.approve();
|
||||
} else {
|
||||
nomination.startApprovalWorkflow();
|
||||
}
|
||||
} else {
|
||||
currentStep.reject(approverId, request.getRemarks());
|
||||
approvalStepRepository.save(currentStep);
|
||||
nomination.reject(request.getRejectionReason() != null
|
||||
? request.getRejectionReason() : request.getRemarks());
|
||||
}
|
||||
|
||||
nominationRepository.save(nomination);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Long approveCandidate(Long nominationId, Long approvedBy) {
|
||||
context.authenticatedUser().validateHasPermissionTo("APPROVE_ELECTION_CANDIDATE");
|
||||
ElectionNomination nomination = nominationRepository.findById(nominationId)
|
||||
.orElseThrow(() -> new ElectionNominationNotFoundException(nominationId));
|
||||
|
||||
if (nomination.getStatus() != NominationStatus.APPROVED) {
|
||||
throw new InvalidNominationStateException("approve candidate", nomination.getStatus().name());
|
||||
}
|
||||
|
||||
Integer maxSerial = candidateRepository.findMaxSerialNumberByElectionAndPost(
|
||||
nomination.getElectionConfig().getId(), nomination.getElectionPost().getId())
|
||||
.orElse(0);
|
||||
|
||||
ElectionCandidate candidate = ElectionCandidate.create(
|
||||
nomination.getElectionConfig(),
|
||||
nomination,
|
||||
nomination.getElectionPost(),
|
||||
nomination.getApplicantName(),
|
||||
maxSerial + 1,
|
||||
approvedBy != null ? approvedBy : context.authenticatedUser().getId()
|
||||
);
|
||||
|
||||
return candidateRepository.save(candidate).getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void removeCandidate(Long candidateId, String reason) {
|
||||
context.authenticatedUser().validateHasPermissionTo("APPROVE_ELECTION_CANDIDATE");
|
||||
ElectionCandidate candidate = candidateRepository.findById(candidateId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Candidate not found: " + candidateId));
|
||||
candidate.deactivate();
|
||||
candidateRepository.save(candidate);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void castVote(ElectionVoteRequest request) {
|
||||
ElectionConfig election = electionConfigRepository.findById(request.getElectionId())
|
||||
.orElseThrow(() -> new ElectionNotFoundException(request.getElectionId()));
|
||||
|
||||
if (!election.isVotingOpen()) {
|
||||
throw new ElectionNotActiveException(election.getId(), election.getStatus().name());
|
||||
}
|
||||
|
||||
if (voteRepository.existsByElectionConfigIdAndElectionPostIdAndVoterMemberId(
|
||||
request.getElectionId(), request.getPostId(), request.getVoterMemberId())) {
|
||||
throw new VoteAlreadyCastException(request.getVoterMemberId(), request.getPostId());
|
||||
}
|
||||
|
||||
ElectionPost post = electionPostRepository.findById(request.getPostId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Post not found: " + request.getPostId()));
|
||||
|
||||
ElectionCandidate candidate = candidateRepository.findById(request.getCandidateId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Candidate not found: " + request.getCandidateId()));
|
||||
|
||||
ElectionVote vote = ElectionVote.cast(election, post, request.getVoterMemberId(),
|
||||
candidate, request.getBoothId(), null);
|
||||
voteRepository.save(vote);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void declareResults(Long electionId, Long declaredBy) {
|
||||
context.authenticatedUser().validateHasPermissionTo("DECLARE_ELECTION_RESULT");
|
||||
ElectionConfig election = electionConfigRepository.findById(electionId)
|
||||
.orElseThrow(() -> new ElectionNotFoundException(electionId));
|
||||
|
||||
List<ElectionPost> posts = electionPostRepository.findByElectionConfigIdAndIsActiveTrue(electionId);
|
||||
for (ElectionPost post : posts) {
|
||||
List<Object[]> voteCounts = voteRepository.countVotesByCandidate(electionId, post.getId());
|
||||
int rank = 1;
|
||||
for (Object[] row : voteCounts) {
|
||||
Long candidateId = (Long) row[0];
|
||||
Long count = (Long) row[1];
|
||||
ElectionCandidate candidate = candidateRepository.findById(candidateId).orElse(null);
|
||||
if (candidate == null) continue;
|
||||
String result = rank <= post.getTotalSeats() ? "WON" : "LOST";
|
||||
ElectionResult r = ElectionResult.create(election, post, candidate,
|
||||
count.intValue(), result, rank, declaredBy);
|
||||
resultRepository.save(r);
|
||||
rank++;
|
||||
}
|
||||
}
|
||||
|
||||
election.declareResult();
|
||||
electionConfigRepository.save(election);
|
||||
}
|
||||
|
||||
private void initApprovalSteps(ElectionNomination nomination) {
|
||||
for (int i = 0; i < APPROVAL_STEPS.size(); i++) {
|
||||
ElectionApprovalStep step = ElectionApprovalStep.create(
|
||||
nomination, i + 1, APPROVAL_STEPS.get(i), "BOARD_DIRECTOR");
|
||||
approvalStepRepository.save(step);
|
||||
}
|
||||
nomination.startApprovalWorkflow();
|
||||
nominationRepository.save(nomination);
|
||||
}
|
||||
|
||||
private String generateApplicationNumber(Long nominationId, Long electionId) {
|
||||
return "ENOMN-" + electionId + "-" + String.format("%06d", nominationId);
|
||||
}
|
||||
}
|
||||
@@ -364,4 +364,5 @@
|
||||
<include file="parts/0334_clerk_disbursement_add_error_message.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0335_create_bank_transfer_log.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0336_add_request_type_to_clerk_disbursement.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0337_create_election_module.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+390
@@ -0,0 +1,390 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<!-- 1. m_election_config -->
|
||||
<changeSet id="0337_create_m_election_config" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_election_config"/></not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_election_config">
|
||||
<column name="id" type="BIGSERIAL" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="title" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="description" type="TEXT"/>
|
||||
<column name="election_type" type="VARCHAR(50)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="nomination_start_date" type="DATE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="nomination_end_date" type="DATE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="election_date" type="DATE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="result_date" type="DATE"/>
|
||||
<column name="status" type="VARCHAR(50)" defaultValue="DRAFT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="is_active" type="BOOLEAN" defaultValueBoolean="true">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="total_seats" type="INT"/>
|
||||
<column name="remarks" type="TEXT"/>
|
||||
<column name="created_by" type="BIGINT"/>
|
||||
<column name="created_on_utc" type="TIMESTAMP"/>
|
||||
<column name="last_modified_by" type="BIGINT"/>
|
||||
<column name="last_modified_on_utc" type="TIMESTAMP"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<!-- 2. m_election_post -->
|
||||
<changeSet id="0337_create_m_election_post" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_election_post"/></not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_election_post">
|
||||
<column name="id" type="BIGSERIAL" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="election_id" type="BIGINT">
|
||||
<constraints nullable="false" foreignKeyName="fk_election_post_config" references="m_election_config(id)"/>
|
||||
</column>
|
||||
<column name="post_name" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="total_seats" type="INT" defaultValueNumeric="1">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="caste_category" type="VARCHAR(100)"/>
|
||||
<column name="gender_type" type="VARCHAR(20)" defaultValue="ANY">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="min_age" type="INT"/>
|
||||
<column name="max_age" type="INT"/>
|
||||
<column name="min_share_holding" type="INT" defaultValueNumeric="0"/>
|
||||
<column name="area" type="VARCHAR(200)"/>
|
||||
<column name="is_active" type="BOOLEAN" defaultValueBoolean="true">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="description" type="TEXT"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<!-- 3. m_election_nomination -->
|
||||
<changeSet id="0337_create_m_election_nomination" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_election_nomination"/></not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_election_nomination">
|
||||
<column name="id" type="BIGSERIAL" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="election_id" type="BIGINT">
|
||||
<constraints nullable="false" foreignKeyName="fk_nomination_election" references="m_election_config(id)"/>
|
||||
</column>
|
||||
<column name="post_id" type="BIGINT">
|
||||
<constraints nullable="false" foreignKeyName="fk_nomination_post" references="m_election_post(id)"/>
|
||||
</column>
|
||||
<column name="member_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="share_account_id" type="BIGINT"/>
|
||||
<column name="application_number" type="VARCHAR(50)">
|
||||
<constraints unique="true"/>
|
||||
</column>
|
||||
<column name="applicant_name" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="date_of_birth" type="DATE"/>
|
||||
<column name="gender" type="VARCHAR(20)"/>
|
||||
<column name="caste" type="VARCHAR(100)"/>
|
||||
<column name="caste_category" type="VARCHAR(100)"/>
|
||||
<column name="location" type="VARCHAR(200)"/>
|
||||
<column name="service_details" type="VARCHAR(500)"/>
|
||||
<column name="mobile_number" type="VARCHAR(20)"/>
|
||||
<column name="email" type="VARCHAR(150)"/>
|
||||
<column name="address" type="TEXT"/>
|
||||
<column name="occupation" type="VARCHAR(200)"/>
|
||||
<column name="share_count" type="INT"/>
|
||||
<column name="status" type="VARCHAR(50)" defaultValue="DRAFT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="rejection_reason" type="TEXT"/>
|
||||
<column name="submitted_date" type="TIMESTAMP"/>
|
||||
<column name="declaration_accepted" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="photo_document_id" type="BIGINT"/>
|
||||
<column name="created_by" type="BIGINT"/>
|
||||
<column name="created_on_utc" type="TIMESTAMP"/>
|
||||
<column name="last_modified_by" type="BIGINT"/>
|
||||
<column name="last_modified_on_utc" type="TIMESTAMP"/>
|
||||
</createTable>
|
||||
<addUniqueConstraint tableName="m_election_nomination"
|
||||
columnNames="election_id, member_id, post_id"
|
||||
constraintName="uq_nomination_election_member_post"/>
|
||||
</changeSet>
|
||||
|
||||
<!-- 4. m_election_eligibility_check -->
|
||||
<changeSet id="0337_create_m_election_eligibility_check" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_election_eligibility_check"/></not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_election_eligibility_check">
|
||||
<column name="id" type="BIGSERIAL" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="nomination_id" type="BIGINT">
|
||||
<constraints nullable="false" unique="true" foreignKeyName="fk_eligibility_nomination" references="m_election_nomination(id)"/>
|
||||
</column>
|
||||
<column name="age_eligible" type="BOOLEAN"/>
|
||||
<column name="age_check_remarks" type="VARCHAR(500)"/>
|
||||
<column name="gender_eligible" type="BOOLEAN"/>
|
||||
<column name="gender_check_remarks" type="VARCHAR(500)"/>
|
||||
<column name="caste_eligible" type="BOOLEAN"/>
|
||||
<column name="caste_check_remarks" type="VARCHAR(500)"/>
|
||||
<column name="share_holding_eligible" type="BOOLEAN"/>
|
||||
<column name="share_check_remarks" type="VARCHAR(500)"/>
|
||||
<column name="area_eligible" type="BOOLEAN"/>
|
||||
<column name="area_check_remarks" type="VARCHAR(500)"/>
|
||||
<column name="overall_result" type="VARCHAR(20)" defaultValue="PENDING"/>
|
||||
<column name="checked_by" type="BIGINT"/>
|
||||
<column name="checked_date" type="TIMESTAMP"/>
|
||||
<column name="remarks" type="TEXT"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<!-- 5. m_election_ceo_precheck -->
|
||||
<changeSet id="0337_create_m_election_ceo_precheck" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_election_ceo_precheck"/></not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_election_ceo_precheck">
|
||||
<column name="id" type="BIGSERIAL" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="nomination_id" type="BIGINT">
|
||||
<constraints nullable="false" unique="true" foreignKeyName="fk_ceoprecheck_nomination" references="m_election_nomination(id)"/>
|
||||
</column>
|
||||
<column name="director_loan_check_result" type="VARCHAR(20)" defaultValue="PENDING"/>
|
||||
<column name="director_loan_outstanding" type="DECIMAL(19,4)" defaultValueNumeric="0"/>
|
||||
<column name="director_loan_remarks" type="TEXT"/>
|
||||
<column name="relation_ac_check_result" type="VARCHAR(20)" defaultValue="PENDING"/>
|
||||
<column name="relation_accounts_details" type="TEXT"/>
|
||||
<column name="relation_ac_remarks" type="TEXT"/>
|
||||
<column name="no_due_check_result" type="VARCHAR(20)" defaultValue="PENDING"/>
|
||||
<column name="no_due_certificate_number" type="VARCHAR(100)"/>
|
||||
<column name="no_due_generated_date" type="TIMESTAMP"/>
|
||||
<column name="overall_result" type="VARCHAR(20)" defaultValue="PENDING"/>
|
||||
<column name="checked_by" type="BIGINT"/>
|
||||
<column name="checked_date" type="TIMESTAMP"/>
|
||||
<column name="remarks" type="TEXT"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<!-- 6. m_election_approval_step -->
|
||||
<changeSet id="0337_create_m_election_approval_step" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_election_approval_step"/></not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_election_approval_step">
|
||||
<column name="id" type="BIGSERIAL" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="nomination_id" type="BIGINT">
|
||||
<constraints nullable="false" foreignKeyName="fk_approval_step_nomination" references="m_election_nomination(id)"/>
|
||||
</column>
|
||||
<column name="step_number" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="step_name" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="approver_role" type="VARCHAR(100)"/>
|
||||
<column name="approver_user_id" type="BIGINT"/>
|
||||
<column name="status" type="VARCHAR(50)" defaultValue="PENDING">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="remarks" type="TEXT"/>
|
||||
<column name="action_date" type="TIMESTAMP"/>
|
||||
<column name="family_details_verified" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="family_details_json" type="TEXT"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<!-- 7. m_election_candidate -->
|
||||
<changeSet id="0337_create_m_election_candidate" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_election_candidate"/></not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_election_candidate">
|
||||
<column name="id" type="BIGSERIAL" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="election_id" type="BIGINT">
|
||||
<constraints nullable="false" foreignKeyName="fk_candidate_election" references="m_election_config(id)"/>
|
||||
</column>
|
||||
<column name="nomination_id" type="BIGINT">
|
||||
<constraints nullable="false" unique="true" foreignKeyName="fk_candidate_nomination" references="m_election_nomination(id)"/>
|
||||
</column>
|
||||
<column name="post_id" type="BIGINT">
|
||||
<constraints nullable="false" foreignKeyName="fk_candidate_post" references="m_election_post(id)"/>
|
||||
</column>
|
||||
<column name="candidate_name" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="serial_number" type="INT"/>
|
||||
<column name="photo_url" type="VARCHAR(500)"/>
|
||||
<column name="is_active" type="BOOLEAN" defaultValueBoolean="true">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="approved_by" type="BIGINT"/>
|
||||
<column name="approved_date" type="TIMESTAMP"/>
|
||||
<column name="symbol" type="VARCHAR(100)"/>
|
||||
<column name="brief_profile" type="TEXT"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<!-- 8. m_election_vote -->
|
||||
<changeSet id="0337_create_m_election_vote" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_election_vote"/></not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_election_vote">
|
||||
<column name="id" type="BIGSERIAL" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="election_id" type="BIGINT">
|
||||
<constraints nullable="false" foreignKeyName="fk_vote_election" references="m_election_config(id)"/>
|
||||
</column>
|
||||
<column name="post_id" type="BIGINT">
|
||||
<constraints nullable="false" foreignKeyName="fk_vote_post" references="m_election_post(id)"/>
|
||||
</column>
|
||||
<column name="voter_member_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="candidate_id" type="BIGINT">
|
||||
<constraints nullable="false" foreignKeyName="fk_vote_candidate" references="m_election_candidate(id)"/>
|
||||
</column>
|
||||
<column name="vote_timestamp" type="TIMESTAMP">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="booth_id" type="VARCHAR(100)"/>
|
||||
<column name="ip_address" type="VARCHAR(50)"/>
|
||||
</createTable>
|
||||
<addUniqueConstraint tableName="m_election_vote"
|
||||
columnNames="election_id, post_id, voter_member_id"
|
||||
constraintName="uq_election_vote_member_post"/>
|
||||
</changeSet>
|
||||
|
||||
<!-- 9. m_election_result -->
|
||||
<changeSet id="0337_create_m_election_result" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_election_result"/></not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_election_result">
|
||||
<column name="id" type="BIGSERIAL" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="election_id" type="BIGINT">
|
||||
<constraints nullable="false" foreignKeyName="fk_result_election" references="m_election_config(id)"/>
|
||||
</column>
|
||||
<column name="post_id" type="BIGINT">
|
||||
<constraints nullable="false" foreignKeyName="fk_result_post" references="m_election_post(id)"/>
|
||||
</column>
|
||||
<column name="candidate_id" type="BIGINT">
|
||||
<constraints nullable="false" foreignKeyName="fk_result_candidate" references="m_election_candidate(id)"/>
|
||||
</column>
|
||||
<column name="vote_count" type="INT" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="result" type="VARCHAR(20)"/>
|
||||
<column name="rank_position" type="INT"/>
|
||||
<column name="declared_by" type="BIGINT"/>
|
||||
<column name="declared_date" type="TIMESTAMP"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<!-- 10. Permissions for election module -->
|
||||
<changeSet id="0337_seed_election_permissions" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">
|
||||
SELECT COUNT(*) FROM m_permission WHERE code = 'SIDEBAR_ELECTION'
|
||||
</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="SIDEBAR_ELECTION"/>
|
||||
<column name="entity_name" value="ELECTION"/>
|
||||
<column name="action_name" value="SIDEBAR"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="READ_ELECTION_CONFIG"/>
|
||||
<column name="entity_name" value="ELECTION_CONFIG"/>
|
||||
<column name="action_name" value="READ"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="CREATE_ELECTION_CONFIG"/>
|
||||
<column name="entity_name" value="ELECTION_CONFIG"/>
|
||||
<column name="action_name" value="CREATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="UPDATE_ELECTION_CONFIG"/>
|
||||
<column name="entity_name" value="ELECTION_CONFIG"/>
|
||||
<column name="action_name" value="UPDATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="READ_ELECTION_NOMINATION"/>
|
||||
<column name="entity_name" value="ELECTION_NOMINATION"/>
|
||||
<column name="action_name" value="READ"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="CREATE_ELECTION_NOMINATION"/>
|
||||
<column name="entity_name" value="ELECTION_NOMINATION"/>
|
||||
<column name="action_name" value="CREATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="READ_ELECTION_CANDIDATE"/>
|
||||
<column name="entity_name" value="ELECTION_CANDIDATE"/>
|
||||
<column name="action_name" value="READ"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="CAST_ELECTION_VOTE"/>
|
||||
<column name="entity_name" value="ELECTION_VOTE"/>
|
||||
<column name="action_name" value="CREATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="DECLARE_ELECTION_RESULT"/>
|
||||
<column name="entity_name" value="ELECTION_RESULT"/>
|
||||
<column name="action_name" value="CREATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user