feat(election): CBS ContentStore symbol upload, hasSymbol DTO, vote summary API
- ElectionCandidateData: replace String symbol with Boolean hasSymbol + add memberId - ElectionCandidateApiResource: symbol upload/download via ContentStoreService (mimeType|storagePath format, backward-compatible with legacy base64 records) - ElectionVotingApiResource: add GET /vote-summary endpoint (totalVotes, totalVoters, per-candidate breakdown) using ElectionVoteRepository queries - ElectionVoteRepository: add countByElectionConfigId and voteSummaryByCandidate queries - ElectionReadPlatformServiceImpl: map hasSymbol and memberId in candidate list response - ElectionWritePlatformServiceImpl: set photoUrl from memberId on approveCandidate, delete old symbol file from ContentStore on removeCandidate Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
ed139d6e68
commit
3d30bdb788
+86
@@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
import jakarta.ws.rs.Consumes;
|
import jakarta.ws.rs.Consumes;
|
||||||
import jakarta.ws.rs.DELETE;
|
import jakarta.ws.rs.DELETE;
|
||||||
import jakarta.ws.rs.GET;
|
import jakarta.ws.rs.GET;
|
||||||
|
import jakarta.ws.rs.PATCH;
|
||||||
import jakarta.ws.rs.POST;
|
import jakarta.ws.rs.POST;
|
||||||
import jakarta.ws.rs.Path;
|
import jakarta.ws.rs.Path;
|
||||||
import jakarta.ws.rs.PathParam;
|
import jakarta.ws.rs.PathParam;
|
||||||
@@ -16,8 +17,15 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.portfolio.election.service.ElectionReadPlatformService;
|
import org.apache.fineract.portfolio.election.service.ElectionReadPlatformService;
|
||||||
import org.apache.fineract.portfolio.election.service.ElectionWritePlatformService;
|
import org.apache.fineract.portfolio.election.service.ElectionWritePlatformService;
|
||||||
|
import org.apache.fineract.infrastructure.contentstore.service.ContentStoreService;
|
||||||
|
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
|
||||||
|
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
|
||||||
|
import org.glassfish.jersey.media.multipart.FormDataParam;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@@ -31,6 +39,7 @@ public class ElectionCandidateApiResource {
|
|||||||
private final ElectionReadPlatformService readService;
|
private final ElectionReadPlatformService readService;
|
||||||
private final ElectionWritePlatformService writeService;
|
private final ElectionWritePlatformService writeService;
|
||||||
private final PlatformSecurityContext context;
|
private final PlatformSecurityContext context;
|
||||||
|
private final ContentStoreService contentStoreService;
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/{electionId}/candidates")
|
@Path("/{electionId}/candidates")
|
||||||
@@ -55,6 +64,83 @@ public class ElectionCandidateApiResource {
|
|||||||
return Response.ok(Map.of("resourceId", candidateId)).build();
|
return Response.ok(Map.of("resourceId", candidateId)).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PATCH
|
||||||
|
@Path("/{electionId}/candidate/{candidateId}")
|
||||||
|
@Operation(summary = "Update candidate symbol or brief profile")
|
||||||
|
public Response updateCandidate(@PathParam("electionId") Long electionId,
|
||||||
|
@PathParam("candidateId") Long candidateId,
|
||||||
|
Map<String, String> body) {
|
||||||
|
writeService.updateCandidate(candidateId, body.get("symbol"), body.get("briefProfile"));
|
||||||
|
return Response.ok(Map.of("resourceId", candidateId)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/{electionId}/candidate/{candidateId}/symbol")
|
||||||
|
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||||
|
@Operation(summary = "Upload election symbol image for a candidate")
|
||||||
|
public Response uploadCandidateSymbol(@PathParam("electionId") Long electionId,
|
||||||
|
@PathParam("candidateId") Long candidateId,
|
||||||
|
@FormDataParam("file") InputStream fileStream,
|
||||||
|
@FormDataParam("file") FormDataContentDisposition fileDetail,
|
||||||
|
@FormDataParam("file") FormDataBodyPart filePart) {
|
||||||
|
String mimeType = (filePart != null && filePart.getMediaType() != null)
|
||||||
|
? filePart.getMediaType().toString() : "image/png";
|
||||||
|
|
||||||
|
// delete old file if it exists in new path format (safe — ignore failures)
|
||||||
|
String existing = readService.retrieveCandidateSymbol(candidateId);
|
||||||
|
if (existing != null && !existing.startsWith("data:") && existing.contains("|")) {
|
||||||
|
String[] ep = existing.split("\\|", 2);
|
||||||
|
if (ep.length == 2) {
|
||||||
|
try { contentStoreService.delete(ep[1]); } catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String ext = extractExt(fileDetail != null ? fileDetail.getFileName() : null);
|
||||||
|
String storagePath = "images" + contentStoreService.getDelimiter()
|
||||||
|
+ "election_candidates" + contentStoreService.getDelimiter()
|
||||||
|
+ candidateId + contentStoreService.getDelimiter()
|
||||||
|
+ "symbol" + ext;
|
||||||
|
|
||||||
|
// use the returned (sanitized/normalized) path, not the pre-computed one
|
||||||
|
String savedPath = contentStoreService.upload(storagePath, fileStream, mimeType);
|
||||||
|
writeService.uploadCandidateSymbol(candidateId, mimeType + "|" + savedPath);
|
||||||
|
return Response.ok(Map.of("resourceId", candidateId)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/{electionId}/candidate/{candidateId}/symbol")
|
||||||
|
@Produces(MediaType.TEXT_PLAIN)
|
||||||
|
@Operation(summary = "Get election symbol for a candidate (returns base64 data URL)")
|
||||||
|
public Response getCandidateSymbol(@PathParam("electionId") Long electionId,
|
||||||
|
@PathParam("candidateId") Long candidateId) throws IOException {
|
||||||
|
String symbol = readService.retrieveCandidateSymbol(candidateId);
|
||||||
|
if (symbol == null) return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
|
|
||||||
|
// legacy records: already a base64 data URL — return as-is
|
||||||
|
if (symbol.startsWith("data:")) {
|
||||||
|
return Response.ok(symbol).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// new format: "mimeType|storagePath"
|
||||||
|
String[] parts = symbol.split("\\|", 2);
|
||||||
|
if (parts.length < 2) return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
|
String mimeType = parts[0];
|
||||||
|
String storagePath = parts[1];
|
||||||
|
try {
|
||||||
|
byte[] bytes = contentStoreService.download(storagePath).readAllBytes();
|
||||||
|
String base64 = "data:" + mimeType + ";base64," + Base64.getEncoder().encodeToString(bytes);
|
||||||
|
return Response.ok(base64).build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractExt(String filename) {
|
||||||
|
if (filename == null) return ".png";
|
||||||
|
int dot = filename.lastIndexOf('.');
|
||||||
|
return dot >= 0 ? filename.substring(dot).toLowerCase() : ".png";
|
||||||
|
}
|
||||||
|
|
||||||
@DELETE
|
@DELETE
|
||||||
@Path("/{electionId}/candidate/{candidateId}")
|
@Path("/{electionId}/candidate/{candidateId}")
|
||||||
@Operation(summary = "Remove a candidate from the election")
|
@Operation(summary = "Remove a candidate from the election")
|
||||||
|
|||||||
+28
-1
@@ -8,16 +8,18 @@ import jakarta.ws.rs.POST;
|
|||||||
import jakarta.ws.rs.Path;
|
import jakarta.ws.rs.Path;
|
||||||
import jakarta.ws.rs.PathParam;
|
import jakarta.ws.rs.PathParam;
|
||||||
import jakarta.ws.rs.Produces;
|
import jakarta.ws.rs.Produces;
|
||||||
import jakarta.ws.rs.QueryParam;
|
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.portfolio.election.data.ElectionVoteRequest;
|
import org.apache.fineract.portfolio.election.data.ElectionVoteRequest;
|
||||||
|
import org.apache.fineract.portfolio.election.domain.ElectionVoteRepository;
|
||||||
import org.apache.fineract.portfolio.election.service.ElectionReadPlatformService;
|
import org.apache.fineract.portfolio.election.service.ElectionReadPlatformService;
|
||||||
import org.apache.fineract.portfolio.election.service.ElectionWritePlatformService;
|
import org.apache.fineract.portfolio.election.service.ElectionWritePlatformService;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@@ -31,6 +33,7 @@ public class ElectionVotingApiResource {
|
|||||||
private final ElectionReadPlatformService readService;
|
private final ElectionReadPlatformService readService;
|
||||||
private final ElectionWritePlatformService writeService;
|
private final ElectionWritePlatformService writeService;
|
||||||
private final PlatformSecurityContext context;
|
private final PlatformSecurityContext context;
|
||||||
|
private final ElectionVoteRepository voteRepository;
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/{electionId}/vote")
|
@Path("/{electionId}/vote")
|
||||||
@@ -57,4 +60,28 @@ public class ElectionVotingApiResource {
|
|||||||
public Response getResults(@PathParam("electionId") Long electionId) {
|
public Response getResults(@PathParam("electionId") Long electionId) {
|
||||||
return Response.ok(readService.retrieveResultsByElection(electionId)).build();
|
return Response.ok(readService.retrieveResultsByElection(electionId)).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/{electionId}/vote-summary")
|
||||||
|
@Operation(summary = "Get live vote counts per candidate per post")
|
||||||
|
public Response getVoteSummary(@PathParam("electionId") Long electionId) {
|
||||||
|
long totalVotes = voteRepository.countByElectionConfigId(electionId);
|
||||||
|
long totalVoters = voteRepository.countTotalVotersByElection(electionId);
|
||||||
|
List<Object[]> rows = voteRepository.voteSummaryByCandidate(electionId);
|
||||||
|
|
||||||
|
List<Map<String, Object>> breakdown = rows.stream().map(r -> {
|
||||||
|
Map<String, Object> entry = new HashMap<>();
|
||||||
|
entry.put("postId", r[0]);
|
||||||
|
entry.put("candidateId", r[1]);
|
||||||
|
entry.put("voteCount", r[2]);
|
||||||
|
return entry;
|
||||||
|
}).collect(java.util.stream.Collectors.toList());
|
||||||
|
|
||||||
|
Map<String, Object> summary = new HashMap<>();
|
||||||
|
summary.put("electionId", electionId);
|
||||||
|
summary.put("totalVotes", totalVotes);
|
||||||
|
summary.put("totalVoters", totalVoters);
|
||||||
|
summary.put("breakdown", breakdown);
|
||||||
|
return Response.ok(summary).build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -16,6 +16,7 @@ public class ElectionCandidateData {
|
|||||||
private Long id;
|
private Long id;
|
||||||
private Long electionId;
|
private Long electionId;
|
||||||
private Long nominationId;
|
private Long nominationId;
|
||||||
|
private Long memberId;
|
||||||
private Long postId;
|
private Long postId;
|
||||||
private String postName;
|
private String postName;
|
||||||
private String candidateName;
|
private String candidateName;
|
||||||
@@ -23,7 +24,7 @@ public class ElectionCandidateData {
|
|||||||
private String photoUrl;
|
private String photoUrl;
|
||||||
private Boolean isActive;
|
private Boolean isActive;
|
||||||
private LocalDateTime approvedDate;
|
private LocalDateTime approvedDate;
|
||||||
private String symbol;
|
private Boolean hasSymbol;
|
||||||
private String briefProfile;
|
private String briefProfile;
|
||||||
private String gender;
|
private String gender;
|
||||||
private String caste;
|
private String caste;
|
||||||
|
|||||||
+1
-1
@@ -45,7 +45,7 @@ public class ElectionCandidate extends AbstractPersistableCustom<Long> {
|
|||||||
@Column(name = "approved_date")
|
@Column(name = "approved_date")
|
||||||
private LocalDateTime approvedDate;
|
private LocalDateTime approvedDate;
|
||||||
|
|
||||||
@Column(name = "symbol", length = 100)
|
@Column(name = "symbol", columnDefinition = "TEXT")
|
||||||
private String symbol;
|
private String symbol;
|
||||||
|
|
||||||
@Column(name = "brief_profile", columnDefinition = "TEXT")
|
@Column(name = "brief_profile", columnDefinition = "TEXT")
|
||||||
|
|||||||
+7
@@ -22,4 +22,11 @@ public interface ElectionVoteRepository extends JpaRepository<ElectionVote, Long
|
|||||||
@Query("SELECT COUNT(DISTINCT v.voterMemberId) FROM ElectionVote v " +
|
@Query("SELECT COUNT(DISTINCT v.voterMemberId) FROM ElectionVote v " +
|
||||||
"WHERE v.electionConfig.id = :electionId")
|
"WHERE v.electionConfig.id = :electionId")
|
||||||
long countTotalVotersByElection(@Param("electionId") Long electionId);
|
long countTotalVotersByElection(@Param("electionId") Long electionId);
|
||||||
|
|
||||||
|
long countByElectionConfigId(Long electionId);
|
||||||
|
|
||||||
|
@Query("SELECT v.electionPost.id, v.candidate.id, COUNT(v) " +
|
||||||
|
"FROM ElectionVote v WHERE v.electionConfig.id = :electionId " +
|
||||||
|
"GROUP BY v.electionPost.id, v.candidate.id ORDER BY v.electionPost.id, COUNT(v) DESC")
|
||||||
|
List<Object[]> voteSummaryByCandidate(@Param("electionId") Long electionId);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -25,4 +25,6 @@ public interface ElectionReadPlatformService {
|
|||||||
PagedResponse<ElectionCandidateData> retrieveCandidatesByPost(Long electionId, Long postId, int page, int size);
|
PagedResponse<ElectionCandidateData> retrieveCandidatesByPost(Long electionId, Long postId, int page, int size);
|
||||||
|
|
||||||
List<ElectionResultData> retrieveResultsByElection(Long electionId);
|
List<ElectionResultData> retrieveResultsByElection(Long electionId);
|
||||||
|
|
||||||
|
String retrieveCandidateSymbol(Long candidateId);
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -240,6 +240,7 @@ public class ElectionReadPlatformServiceImpl implements ElectionReadPlatformServ
|
|||||||
.id(c.getId())
|
.id(c.getId())
|
||||||
.electionId(electionId)
|
.electionId(electionId)
|
||||||
.nominationId(c.getNomination().getId())
|
.nominationId(c.getNomination().getId())
|
||||||
|
.memberId(c.getNomination().getMemberId())
|
||||||
.postId(c.getElectionPost().getId())
|
.postId(c.getElectionPost().getId())
|
||||||
.postName(c.getElectionPost().getPostName())
|
.postName(c.getElectionPost().getPostName())
|
||||||
.candidateName(c.getCandidateName())
|
.candidateName(c.getCandidateName())
|
||||||
@@ -247,7 +248,7 @@ public class ElectionReadPlatformServiceImpl implements ElectionReadPlatformServ
|
|||||||
.photoUrl(c.getPhotoUrl())
|
.photoUrl(c.getPhotoUrl())
|
||||||
.isActive(c.getIsActive())
|
.isActive(c.getIsActive())
|
||||||
.approvedDate(c.getApprovedDate())
|
.approvedDate(c.getApprovedDate())
|
||||||
.symbol(c.getSymbol())
|
.hasSymbol(c.getSymbol() != null)
|
||||||
.briefProfile(c.getBriefProfile())
|
.briefProfile(c.getBriefProfile())
|
||||||
.build())
|
.build())
|
||||||
.collect(Collectors.toList()))
|
.collect(Collectors.toList()))
|
||||||
@@ -260,6 +261,14 @@ public class ElectionReadPlatformServiceImpl implements ElectionReadPlatformServ
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public String retrieveCandidateSymbol(Long candidateId) {
|
||||||
|
return candidateRepository.findById(candidateId)
|
||||||
|
.map(ElectionCandidate::getSymbol)
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public List<ElectionResultData> retrieveResultsByElection(Long electionId) {
|
public List<ElectionResultData> retrieveResultsByElection(Long electionId) {
|
||||||
|
|||||||
+4
@@ -39,6 +39,10 @@ public interface ElectionWritePlatformService {
|
|||||||
|
|
||||||
void removeCandidate(Long candidateId, String reason);
|
void removeCandidate(Long candidateId, String reason);
|
||||||
|
|
||||||
|
void updateCandidate(Long candidateId, String symbol, String briefProfile);
|
||||||
|
|
||||||
|
void uploadCandidateSymbol(Long candidateId, String base64Symbol);
|
||||||
|
|
||||||
void castVote(ElectionVoteRequest request);
|
void castVote(ElectionVoteRequest request);
|
||||||
|
|
||||||
void declareResults(Long electionId, Long declaredBy);
|
void declareResults(Long electionId, Long declaredBy);
|
||||||
|
|||||||
+31
@@ -34,6 +34,7 @@ import org.apache.fineract.portfolio.election.exception.ElectionNotActiveExcepti
|
|||||||
import org.apache.fineract.portfolio.election.exception.InvalidNominationStateException;
|
import org.apache.fineract.portfolio.election.exception.InvalidNominationStateException;
|
||||||
import org.apache.fineract.portfolio.election.exception.NominationAlreadyExistsException;
|
import org.apache.fineract.portfolio.election.exception.NominationAlreadyExistsException;
|
||||||
import org.apache.fineract.portfolio.election.exception.VoteAlreadyCastException;
|
import org.apache.fineract.portfolio.election.exception.VoteAlreadyCastException;
|
||||||
|
import org.apache.fineract.infrastructure.contentstore.service.ContentStoreService;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -59,6 +60,7 @@ public class ElectionWritePlatformServiceImpl implements ElectionWritePlatformSe
|
|||||||
private final ElectionPostRepository electionPostRepository;
|
private final ElectionPostRepository electionPostRepository;
|
||||||
private final ElectionNominationRepository nominationRepository;
|
private final ElectionNominationRepository nominationRepository;
|
||||||
private final ElectionCandidateRepository candidateRepository;
|
private final ElectionCandidateRepository candidateRepository;
|
||||||
|
private final ContentStoreService contentStoreService;
|
||||||
private final ElectionApprovalStepRepository approvalStepRepository;
|
private final ElectionApprovalStepRepository approvalStepRepository;
|
||||||
private final ElectionCeoPrecheckRepository ceoPrecheckRepository;
|
private final ElectionCeoPrecheckRepository ceoPrecheckRepository;
|
||||||
private final ElectionVoteRepository voteRepository;
|
private final ElectionVoteRepository voteRepository;
|
||||||
@@ -384,6 +386,10 @@ public class ElectionWritePlatformServiceImpl implements ElectionWritePlatformSe
|
|||||||
approvedBy != null ? approvedBy : context.authenticatedUser().getId()
|
approvedBy != null ? approvedBy : context.authenticatedUser().getId()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (nomination.getMemberId() != null) {
|
||||||
|
candidate.setPhotoUrl("/clients/" + nomination.getMemberId() + "/images");
|
||||||
|
}
|
||||||
|
|
||||||
return candidateRepository.saveAndFlush(candidate).getId();
|
return candidateRepository.saveAndFlush(candidate).getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,10 +399,35 @@ public class ElectionWritePlatformServiceImpl implements ElectionWritePlatformSe
|
|||||||
context.authenticatedUser().validateHasPermissionTo("APPROVE_ELECTION_CANDIDATE");
|
context.authenticatedUser().validateHasPermissionTo("APPROVE_ELECTION_CANDIDATE");
|
||||||
ElectionCandidate candidate = candidateRepository.findById(candidateId)
|
ElectionCandidate candidate = candidateRepository.findById(candidateId)
|
||||||
.orElseThrow(() -> new IllegalArgumentException("Candidate not found: " + candidateId));
|
.orElseThrow(() -> new IllegalArgumentException("Candidate not found: " + candidateId));
|
||||||
|
|
||||||
|
String symbol = candidate.getSymbol();
|
||||||
|
if (symbol != null && !symbol.startsWith("data:") && symbol.contains("|")) {
|
||||||
|
try { contentStoreService.delete(symbol.split("\\|", 2)[1]); } catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
candidate.deactivate();
|
candidate.deactivate();
|
||||||
candidateRepository.save(candidate);
|
candidateRepository.save(candidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void updateCandidate(Long candidateId, String symbol, String briefProfile) {
|
||||||
|
ElectionCandidate candidate = candidateRepository.findById(candidateId)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("Candidate not found: " + candidateId));
|
||||||
|
if (symbol != null) candidate.setSymbol(symbol);
|
||||||
|
if (briefProfile != null) candidate.setBriefProfile(briefProfile);
|
||||||
|
candidateRepository.save(candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void uploadCandidateSymbol(Long candidateId, String base64Symbol) {
|
||||||
|
ElectionCandidate candidate = candidateRepository.findById(candidateId)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("Candidate not found: " + candidateId));
|
||||||
|
candidate.setSymbol(base64Symbol);
|
||||||
|
candidateRepository.save(candidate);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void castVote(ElectionVoteRequest request) {
|
public void castVote(ElectionVoteRequest request) {
|
||||||
|
|||||||
@@ -367,4 +367,5 @@
|
|||||||
<include file="parts/0337_create_election_module.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0337_create_election_module.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0338_election_ceo_precheck_extended_checks.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0338_election_ceo_precheck_extended_checks.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0339_election_ceo_precheck_family_loan_check.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0339_election_ceo_precheck_family_loan_check.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0340_election_candidate_symbol_text.xml" relativeToChangelogFile="true"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
Reference in New Issue
Block a user