feat(election): Board of Directors — director appointment management
- DirectorAppointment entity + Liquibase migration (0347) - CRUD API: POST/GET/PUT/DELETE /v1/election/directors with @Valid Bean Validation - GET /v1/election/directors/pending — WON candidates not yet appointed - Duplicate prevention: existsByElectionIdAndCandidateId check on appoint - Business validation: tenure end must be after start; tenure start cannot be before appointment date - @Scheduled daily midnight job to auto-expire tenure-ended directors via bulk JPQL UPDATE - Full audit logging for all director actions (success + failure) via ElectionAuditService - PendingDirectorData DTO; DirectorAppointmentStatus enum (ACTIVE/RESIGNED/REMOVED/TENURE_ENDED) - ElectionAuditAction: added APPOINT_DIRECTOR, UPDATE_DIRECTOR_APPOINTMENT, REMOVE_DIRECTOR_APPOINTMENT + FAILED variants - ElectionResultRepository: findByResult(String) for pending director lookup Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
ba199d78e7
commit
7ce7353e7a
+121
@@ -0,0 +1,121 @@
|
||||
package org.apache.fineract.portfolio.election.api;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.portfolio.election.data.DirectorAppointmentRequest;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionAuditAction;
|
||||
import org.apache.fineract.portfolio.election.service.DirectorAppointmentService;
|
||||
import org.apache.fineract.portfolio.election.service.ElectionAuditService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@Path("/v1/election/directors")
|
||||
@Tag(name = "Board of Directors", description = "Manage elected director appointments")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@RequiredArgsConstructor
|
||||
public class DirectorAppointmentApiResource {
|
||||
|
||||
private final DirectorAppointmentService service;
|
||||
private final ElectionAuditService auditService;
|
||||
|
||||
@GET
|
||||
@Path("/pending")
|
||||
@Operation(summary = "Won candidates not yet appointed as directors")
|
||||
public Response pending() {
|
||||
return Response.ok(service.retrievePending()).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Operation(summary = "List all director appointments (paginated)")
|
||||
public Response list(
|
||||
@QueryParam("status") String status,
|
||||
@QueryParam("electionId") Long electionId,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("size") @DefaultValue("20") int size) {
|
||||
return Response.ok(service.retrieveAll(status, electionId, page, size)).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Get director appointment detail")
|
||||
public Response get(@PathParam("id") Long id) {
|
||||
return Response.ok(service.retrieve(id)).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Operation(summary = "Appoint a director")
|
||||
public Response appoint(@Valid DirectorAppointmentRequest request,
|
||||
@Context HttpServletRequest httpRequest) {
|
||||
Long id;
|
||||
try {
|
||||
id = service.appoint(request);
|
||||
} catch (RuntimeException e) {
|
||||
auditService.logInNewTransaction(ElectionAuditAction.APPOINT_DIRECTOR_FAILED,
|
||||
"DIRECTOR", null, request.getElectionId(),
|
||||
"Director appointment failed: " + e.getMessage(), null, clientIp(httpRequest));
|
||||
throw e;
|
||||
}
|
||||
auditService.log(ElectionAuditAction.APPOINT_DIRECTOR,
|
||||
"DIRECTOR", id, request.getElectionId(),
|
||||
"Director appointed: " + request.getCandidateName() + " as " + request.getDesignation(),
|
||||
"candidateId=" + request.getCandidateId() + ", post=" + request.getPostName(),
|
||||
clientIp(httpRequest));
|
||||
return Response.ok(Map.of("resourceId", id)).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Update director appointment details")
|
||||
public Response update(@PathParam("id") Long id, @Valid DirectorAppointmentRequest request,
|
||||
@Context HttpServletRequest httpRequest) {
|
||||
try {
|
||||
service.update(id, request);
|
||||
} catch (RuntimeException e) {
|
||||
auditService.logInNewTransaction(ElectionAuditAction.UPDATE_DIRECTOR_APPOINTMENT_FAILED,
|
||||
"DIRECTOR", id, request.getElectionId(),
|
||||
"Director update failed: " + e.getMessage(), null, clientIp(httpRequest));
|
||||
throw e;
|
||||
}
|
||||
auditService.log(ElectionAuditAction.UPDATE_DIRECTOR_APPOINTMENT,
|
||||
"DIRECTOR", id, request.getElectionId(),
|
||||
"Director appointment updated: " + request.getCandidateName(),
|
||||
"designation=" + request.getDesignation() + ", status=" + request.getStatus(),
|
||||
clientIp(httpRequest));
|
||||
return Response.ok(Map.of("resourceId", id)).build();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Remove a director appointment")
|
||||
public Response delete(@PathParam("id") Long id,
|
||||
@Context HttpServletRequest httpRequest) {
|
||||
try {
|
||||
service.delete(id);
|
||||
} catch (RuntimeException e) {
|
||||
auditService.logInNewTransaction(ElectionAuditAction.REMOVE_DIRECTOR_APPOINTMENT_FAILED,
|
||||
"DIRECTOR", id, null,
|
||||
"Director removal failed: " + e.getMessage(), null, clientIp(httpRequest));
|
||||
throw e;
|
||||
}
|
||||
auditService.log(ElectionAuditAction.REMOVE_DIRECTOR_APPOINTMENT,
|
||||
"DIRECTOR", id, null,
|
||||
"Director appointment removed (id=" + id + ")", null,
|
||||
clientIp(httpRequest));
|
||||
return Response.ok(Map.of("resourceId", id)).build();
|
||||
}
|
||||
|
||||
private String clientIp(HttpServletRequest req) {
|
||||
String ip = req.getHeader("X-Forwarded-For");
|
||||
return (ip != null && !ip.isBlank()) ? ip.split(",")[0].trim() : req.getRemoteAddr();
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
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;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class DirectorAppointmentData {
|
||||
|
||||
private Long id;
|
||||
private Long electionId;
|
||||
private Long candidateId;
|
||||
private String candidateName;
|
||||
private String postName;
|
||||
private String designation;
|
||||
private LocalDate tenureStartDate;
|
||||
private LocalDate tenureEndDate;
|
||||
private String resolutionNo;
|
||||
private LocalDate appointmentDate;
|
||||
private String status;
|
||||
private String remarks;
|
||||
private LocalDateTime createdDate;
|
||||
private LocalDateTime lastModifiedDate;
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package org.apache.fineract.portfolio.election.data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class DirectorAppointmentRequest {
|
||||
|
||||
@NotNull(message = "Election ID is required")
|
||||
private Long electionId;
|
||||
|
||||
@NotNull(message = "Candidate ID is required")
|
||||
private Long candidateId;
|
||||
|
||||
@NotBlank(message = "Candidate name is required")
|
||||
private String candidateName;
|
||||
|
||||
@NotBlank(message = "Post name is required")
|
||||
private String postName;
|
||||
|
||||
@NotBlank(message = "Designation is required")
|
||||
private String designation;
|
||||
|
||||
@NotNull(message = "Appointment date is required")
|
||||
private LocalDate appointmentDate;
|
||||
|
||||
@NotNull(message = "Tenure start date is required")
|
||||
private LocalDate tenureStartDate;
|
||||
|
||||
@NotNull(message = "Tenure end date is required")
|
||||
private LocalDate tenureEndDate;
|
||||
|
||||
private String resolutionNo;
|
||||
private String status;
|
||||
private String remarks;
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
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 PendingDirectorData {
|
||||
private Long electionId;
|
||||
private String electionTitle;
|
||||
private Long candidateId;
|
||||
private String candidateName;
|
||||
private String postName;
|
||||
private Integer voteCount;
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
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.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_election_director_appointment")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class DirectorAppointment extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@Column(name = "election_id", nullable = false)
|
||||
private Long electionId;
|
||||
|
||||
@Column(name = "candidate_id", nullable = false)
|
||||
private Long candidateId;
|
||||
|
||||
@Column(name = "candidate_name", nullable = false, length = 200)
|
||||
private String candidateName;
|
||||
|
||||
@Column(name = "post_name", nullable = false, length = 200)
|
||||
private String postName;
|
||||
|
||||
@Column(name = "designation", length = 200)
|
||||
private String designation;
|
||||
|
||||
@Column(name = "tenure_start_date")
|
||||
private LocalDate tenureStartDate;
|
||||
|
||||
@Column(name = "tenure_end_date")
|
||||
private LocalDate tenureEndDate;
|
||||
|
||||
@Column(name = "resolution_no", length = 100)
|
||||
private String resolutionNo;
|
||||
|
||||
@Column(name = "appointment_date")
|
||||
private LocalDate appointmentDate;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "status", nullable = false, length = 30)
|
||||
private DirectorAppointmentStatus status = DirectorAppointmentStatus.ACTIVE;
|
||||
|
||||
@Column(name = "remarks", columnDefinition = "TEXT")
|
||||
private String remarks;
|
||||
|
||||
@Column(name = "created_date", nullable = false, updatable = false)
|
||||
private LocalDateTime createdDate;
|
||||
|
||||
@Column(name = "last_modified_date")
|
||||
private LocalDateTime lastModifiedDate;
|
||||
|
||||
@PrePersist
|
||||
void onPersist() {
|
||||
this.createdDate = LocalDateTime.now();
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
void onUpdate() {
|
||||
this.lastModifiedDate = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public static DirectorAppointment create(Long electionId, Long candidateId,
|
||||
String candidateName, String postName) {
|
||||
DirectorAppointment d = new DirectorAppointment();
|
||||
d.electionId = electionId;
|
||||
d.candidateId = candidateId;
|
||||
d.candidateName = candidateName;
|
||||
d.postName = postName;
|
||||
d.status = DirectorAppointmentStatus.ACTIVE;
|
||||
return d;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface DirectorAppointmentRepository extends JpaRepository<DirectorAppointment, Long> {
|
||||
|
||||
Page<DirectorAppointment> findAll(Pageable pageable);
|
||||
|
||||
Page<DirectorAppointment> findByStatus(DirectorAppointmentStatus status, Pageable pageable);
|
||||
|
||||
Page<DirectorAppointment> findByElectionId(Long electionId, Pageable pageable);
|
||||
|
||||
Page<DirectorAppointment> findByElectionIdAndStatus(Long electionId, DirectorAppointmentStatus status, Pageable pageable);
|
||||
|
||||
List<DirectorAppointment> findByElectionIdAndCandidateId(Long electionId, Long candidateId);
|
||||
|
||||
boolean existsByElectionIdAndCandidateId(Long electionId, Long candidateId);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE DirectorAppointment d SET d.status = 'TENURE_ENDED' WHERE d.status = 'ACTIVE' AND d.tenureEndDate < :today")
|
||||
int expireByTenureDate(LocalDate today);
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
public enum DirectorAppointmentStatus {
|
||||
ACTIVE,
|
||||
RESIGNED,
|
||||
REMOVED,
|
||||
TENURE_ENDED
|
||||
}
|
||||
+4
-1
@@ -22,5 +22,8 @@ public enum ElectionAuditAction {
|
||||
// Voting
|
||||
CAST_VOTE, MANUAL_VOTE_ENTRY, MANUAL_VOTE_ENTRY_FAILED,
|
||||
// Results
|
||||
DECLARE_RESULTS, DECLARE_RESULTS_FAILED
|
||||
DECLARE_RESULTS, DECLARE_RESULTS_FAILED,
|
||||
// Board of Directors
|
||||
APPOINT_DIRECTOR, UPDATE_DIRECTOR_APPOINTMENT, REMOVE_DIRECTOR_APPOINTMENT,
|
||||
APPOINT_DIRECTOR_FAILED, UPDATE_DIRECTOR_APPOINTMENT_FAILED, REMOVE_DIRECTOR_APPOINTMENT_FAILED
|
||||
}
|
||||
|
||||
+2
@@ -13,4 +13,6 @@ public interface ElectionResultRepository extends JpaRepository<ElectionResult,
|
||||
List<ElectionResult> findByElectionConfigIdAndElectionPostId(Long electionId, Long postId);
|
||||
|
||||
boolean existsByElectionConfigId(Long electionId);
|
||||
|
||||
List<ElectionResult> findByResult(String result);
|
||||
}
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package org.apache.fineract.portfolio.election.service;
|
||||
|
||||
import org.apache.fineract.portfolio.election.data.DirectorAppointmentData;
|
||||
import org.apache.fineract.portfolio.election.data.DirectorAppointmentRequest;
|
||||
import org.apache.fineract.portfolio.election.data.PagedResponse;
|
||||
import org.apache.fineract.portfolio.election.data.PendingDirectorData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface DirectorAppointmentService {
|
||||
|
||||
Long appoint(DirectorAppointmentRequest request);
|
||||
|
||||
List<PendingDirectorData> retrievePending();
|
||||
|
||||
void autoExpireByTenure();
|
||||
|
||||
DirectorAppointmentData update(Long id, DirectorAppointmentRequest request);
|
||||
|
||||
void delete(Long id);
|
||||
|
||||
DirectorAppointmentData retrieve(Long id);
|
||||
|
||||
PagedResponse<DirectorAppointmentData> retrieveAll(String status, Long electionId, int page, int size);
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
package org.apache.fineract.portfolio.election.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
||||
import org.apache.fineract.portfolio.election.data.DirectorAppointmentData;
|
||||
import org.apache.fineract.portfolio.election.data.DirectorAppointmentRequest;
|
||||
import org.apache.fineract.portfolio.election.data.PagedResponse;
|
||||
import org.apache.fineract.portfolio.election.data.PendingDirectorData;
|
||||
import org.apache.fineract.portfolio.election.domain.DirectorAppointment;
|
||||
import org.apache.fineract.portfolio.election.domain.DirectorAppointmentRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.DirectorAppointmentStatus;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionResultRepository;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DirectorAppointmentServiceImpl implements DirectorAppointmentService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DirectorAppointmentServiceImpl.class);
|
||||
|
||||
private final DirectorAppointmentRepository repository;
|
||||
private final ElectionResultRepository resultRepository;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Long appoint(DirectorAppointmentRequest req) {
|
||||
validateTenureDates(req);
|
||||
if (repository.existsByElectionIdAndCandidateId(req.getElectionId(), req.getCandidateId())) {
|
||||
throw new IllegalStateException(
|
||||
"Candidate " + req.getCandidateId() + " is already appointed for election " + req.getElectionId());
|
||||
}
|
||||
DirectorAppointment d = DirectorAppointment.create(
|
||||
req.getElectionId(), req.getCandidateId(),
|
||||
req.getCandidateName(), req.getPostName());
|
||||
applyFields(d, req);
|
||||
return repository.saveAndFlush(d).getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<PendingDirectorData> retrievePending() {
|
||||
return resultRepository.findByResult("WON").stream()
|
||||
.filter(r -> !repository.existsByElectionIdAndCandidateId(
|
||||
r.getElectionConfig().getId(), r.getCandidate().getId()))
|
||||
.map(r -> PendingDirectorData.builder()
|
||||
.electionId(r.getElectionConfig().getId())
|
||||
.electionTitle(r.getElectionConfig().getTitle())
|
||||
.candidateId(r.getCandidate().getId())
|
||||
.candidateName(r.getCandidate().getCandidateName())
|
||||
.postName(r.getElectionPost().getPostName())
|
||||
.voteCount(r.getVoteCount())
|
||||
.build())
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public DirectorAppointmentData update(Long id, DirectorAppointmentRequest req) {
|
||||
validateTenureDates(req);
|
||||
DirectorAppointment d = findOrThrow(id);
|
||||
applyFields(d, req);
|
||||
return toData(repository.save(d));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void delete(Long id) {
|
||||
repository.delete(findOrThrow(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public DirectorAppointmentData retrieve(Long id) {
|
||||
return toData(findOrThrow(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Scheduled(cron = "0 0 0 * * *")
|
||||
public void autoExpireByTenure() {
|
||||
int updated = repository.expireByTenureDate(LocalDate.now());
|
||||
if (updated > 0) {
|
||||
log.info("Auto-expired {} director appointment(s) whose tenure ended.", updated);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public PagedResponse<DirectorAppointmentData> retrieveAll(String status, Long electionId, int page, int size) {
|
||||
Pageable pageable = PageRequest.of(page, size, Sort.by("createdDate").descending());
|
||||
Page<DirectorAppointment> result;
|
||||
|
||||
DirectorAppointmentStatus statusEnum = null;
|
||||
if (status != null && !status.isBlank()) {
|
||||
try { statusEnum = DirectorAppointmentStatus.valueOf(status.toUpperCase()); } catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
if (electionId != null && statusEnum != null) {
|
||||
result = repository.findByElectionIdAndStatus(electionId, statusEnum, pageable);
|
||||
} else if (electionId != null) {
|
||||
result = repository.findByElectionId(electionId, pageable);
|
||||
} else if (statusEnum != null) {
|
||||
result = repository.findByStatus(statusEnum, pageable);
|
||||
} else {
|
||||
result = repository.findAll(pageable);
|
||||
}
|
||||
|
||||
return PagedResponse.<DirectorAppointmentData>builder()
|
||||
.content(result.getContent().stream().map(this::toData).toList())
|
||||
.pageNumber(result.getNumber())
|
||||
.pageSize(result.getSize())
|
||||
.totalElements(result.getTotalElements())
|
||||
.totalPages(result.getTotalPages())
|
||||
.first(result.isFirst())
|
||||
.last(result.isLast())
|
||||
.build();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void applyFields(DirectorAppointment d, DirectorAppointmentRequest req) {
|
||||
if (req.getDesignation() != null) d.setDesignation(req.getDesignation());
|
||||
if (req.getTenureStartDate() != null) d.setTenureStartDate(req.getTenureStartDate());
|
||||
if (req.getTenureEndDate() != null) d.setTenureEndDate(req.getTenureEndDate());
|
||||
if (req.getResolutionNo() != null) d.setResolutionNo(req.getResolutionNo());
|
||||
if (req.getAppointmentDate() != null) d.setAppointmentDate(req.getAppointmentDate());
|
||||
if (req.getRemarks() != null) d.setRemarks(req.getRemarks());
|
||||
if (req.getStatus() != null) {
|
||||
try { d.setStatus(DirectorAppointmentStatus.valueOf(req.getStatus().toUpperCase())); } catch (Exception ignored) {}
|
||||
}
|
||||
if (req.getCandidateName() != null) d.setCandidateName(req.getCandidateName());
|
||||
if (req.getPostName() != null) d.setPostName(req.getPostName());
|
||||
}
|
||||
|
||||
private void validateTenureDates(DirectorAppointmentRequest req) {
|
||||
if (req.getTenureStartDate() != null && req.getTenureEndDate() != null
|
||||
&& !req.getTenureEndDate().isAfter(req.getTenureStartDate())) {
|
||||
throw new IllegalArgumentException("Tenure end date must be after tenure start date");
|
||||
}
|
||||
if (req.getAppointmentDate() != null && req.getTenureStartDate() != null
|
||||
&& req.getTenureStartDate().isBefore(req.getAppointmentDate())) {
|
||||
throw new IllegalArgumentException("Tenure start date cannot be before appointment date");
|
||||
}
|
||||
}
|
||||
|
||||
private DirectorAppointment findOrThrow(Long id) {
|
||||
return repository.findById(id).orElseThrow(
|
||||
() -> new AbstractPlatformResourceNotFoundException("error.msg.director.appointment.not.found",
|
||||
"Director appointment not found with id: " + id, id) {});
|
||||
}
|
||||
|
||||
private DirectorAppointmentData toData(DirectorAppointment d) {
|
||||
return DirectorAppointmentData.builder()
|
||||
.id(d.getId())
|
||||
.electionId(d.getElectionId())
|
||||
.candidateId(d.getCandidateId())
|
||||
.candidateName(d.getCandidateName())
|
||||
.postName(d.getPostName())
|
||||
.designation(d.getDesignation())
|
||||
.tenureStartDate(d.getTenureStartDate())
|
||||
.tenureEndDate(d.getTenureEndDate())
|
||||
.resolutionNo(d.getResolutionNo())
|
||||
.appointmentDate(d.getAppointmentDate())
|
||||
.status(d.getStatus() != null ? d.getStatus().name() : null)
|
||||
.remarks(d.getRemarks())
|
||||
.createdDate(d.getCreatedDate())
|
||||
.lastModifiedDate(d.getLastModifiedDate())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -374,4 +374,5 @@
|
||||
<include file="parts/0344_election_candidate_manual_vote_count.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0345_election_candidate_manual_vote_count_drop_default.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0346_election_audit_log_description_to_text.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0347_election_director_appointment.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
<?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">
|
||||
|
||||
<changeSet id="0347-election-director-appointment" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_election_director_appointment"/></not>
|
||||
</preConditions>
|
||||
|
||||
<createTable tableName="m_election_director_appointment">
|
||||
<column name="id" type="BIGSERIAL" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="election_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="candidate_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="candidate_name" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="post_name" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="designation" type="VARCHAR(200)"/>
|
||||
<column name="tenure_start_date" type="DATE"/>
|
||||
<column name="tenure_end_date" type="DATE"/>
|
||||
<column name="resolution_no" type="VARCHAR(100)"/>
|
||||
<column name="appointment_date" type="DATE"/>
|
||||
<column name="status" type="VARCHAR(30)" defaultValue="ACTIVE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="remarks" type="TEXT"/>
|
||||
<column name="created_date" type="TIMESTAMP" defaultValueComputed="NOW()">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="last_modified_date" type="TIMESTAMP"/>
|
||||
</createTable>
|
||||
|
||||
<createIndex indexName="idx_director_appt_election"
|
||||
tableName="m_election_director_appointment">
|
||||
<column name="election_id"/>
|
||||
</createIndex>
|
||||
|
||||
<createIndex indexName="idx_director_appt_status"
|
||||
tableName="m_election_director_appointment">
|
||||
<column name="status"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user