feat(election): committee meeting module — schedule, online/offline mode, soft delete, audit logging
- CommitteeMeeting entity, enums (ONLINE/OFFLINE mode, SCHEDULED/COMPLETED/CANCELLED status), repository with soft delete filter - CommitteeMeetingService + Impl: schedule, update, patchStatus, soft delete, retrieveByCommittee - CommitteeMeetingApiResource: REST endpoints at /v1/election/committee-meetings with full audit logging - ElectionAuditAction: add SCHEDULE_MEETING, UPDATE_MEETING, CANCEL_MEETING, COMPLETE_MEETING, DELETE_MEETING + FAILED variants - CommitteeService: expose getElectionIdForCommittee() for audit context - Liquibase 0351: create m_election_committee_meeting table with is_deleted soft delete column - Liquibase 0352: rename created_date/last_modified_date → created_on_utc/last_modified_on_utc to match EclipseLink base class mapping Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
a1a31aff25
commit
e72b9359c1
+143
@@ -0,0 +1,143 @@
|
||||
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.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.CommitteeMeetingRequest;
|
||||
import org.apache.fineract.portfolio.election.data.CommitteeMeetingData;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionAuditAction;
|
||||
import org.apache.fineract.portfolio.election.service.CommitteeMeetingService;
|
||||
import org.apache.fineract.portfolio.election.service.CommitteeService;
|
||||
import org.apache.fineract.portfolio.election.service.ElectionAuditService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@Path("/v1/election/committee-meetings")
|
||||
@Tag(name = "Committee Meetings", description = "Schedule and manage committee meetings")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@RequiredArgsConstructor
|
||||
public class CommitteeMeetingApiResource {
|
||||
|
||||
private final CommitteeMeetingService meetingService;
|
||||
private final CommitteeService committeeService;
|
||||
private final ElectionAuditService auditService;
|
||||
|
||||
@GET
|
||||
@Path("/committee/{committeeId}")
|
||||
@Operation(summary = "List all meetings for a committee")
|
||||
public Response listByCommittee(@PathParam("committeeId") Long committeeId) {
|
||||
return Response.ok(meetingService.retrieveByCommittee(committeeId)).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Get meeting detail")
|
||||
public Response get(@PathParam("id") Long id) {
|
||||
return Response.ok(meetingService.retrieve(id)).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/committee/{committeeId}")
|
||||
@Operation(summary = "Schedule a new meeting for a committee")
|
||||
public Response schedule(@PathParam("committeeId") Long committeeId,
|
||||
CommitteeMeetingRequest request,
|
||||
@Context HttpServletRequest httpRequest) {
|
||||
Long electionId = committeeService.getElectionIdForCommittee(committeeId);
|
||||
CommitteeMeetingData result;
|
||||
try {
|
||||
result = meetingService.schedule(committeeId, request);
|
||||
} catch (RuntimeException e) {
|
||||
auditService.logInNewTransaction(ElectionAuditAction.SCHEDULE_MEETING_FAILED,
|
||||
"COMMITTEE", committeeId, electionId,
|
||||
"Schedule meeting failed: " + e.getMessage(),
|
||||
"committeeId=" + committeeId + ", title=" + request.getTitle(),
|
||||
clientIp(httpRequest));
|
||||
throw e;
|
||||
}
|
||||
auditService.log(ElectionAuditAction.SCHEDULE_MEETING,
|
||||
"COMMITTEE", result.getId(), electionId,
|
||||
"Meeting \"" + request.getTitle() + "\" scheduled for " + request.getMeetingDate(),
|
||||
"mode=" + request.getMode() + ", date=" + request.getMeetingDate()
|
||||
+ ", committeeId=" + committeeId,
|
||||
clientIp(httpRequest));
|
||||
return Response.ok(result).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Update a scheduled meeting")
|
||||
public Response update(@PathParam("id") Long id,
|
||||
CommitteeMeetingRequest request,
|
||||
@Context HttpServletRequest httpRequest) {
|
||||
CommitteeMeetingData existing = meetingService.retrieve(id);
|
||||
Long electionId = committeeService.getElectionIdForCommittee(existing.getCommitteeId());
|
||||
CommitteeMeetingData result;
|
||||
try {
|
||||
result = meetingService.update(id, request);
|
||||
} catch (RuntimeException e) {
|
||||
auditService.logInNewTransaction(ElectionAuditAction.UPDATE_MEETING_FAILED,
|
||||
"COMMITTEE", id, electionId,
|
||||
"Update meeting failed: " + e.getMessage(), null, clientIp(httpRequest));
|
||||
throw e;
|
||||
}
|
||||
auditService.log(ElectionAuditAction.UPDATE_MEETING,
|
||||
"COMMITTEE", id, electionId,
|
||||
"Meeting \"" + result.getTitle() + "\" updated",
|
||||
"mode=" + result.getMode() + ", date=" + result.getMeetingDate(),
|
||||
clientIp(httpRequest));
|
||||
return Response.ok(result).build();
|
||||
}
|
||||
|
||||
@PATCH
|
||||
@Path("/{id}/status")
|
||||
@Operation(summary = "Change meeting status (COMPLETED or CANCELLED)")
|
||||
public Response patchStatus(@PathParam("id") Long id,
|
||||
Map<String, String> body,
|
||||
@Context HttpServletRequest httpRequest) {
|
||||
String status = body.get("status");
|
||||
if (status == null || status.isBlank()) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "status field is required")).build();
|
||||
}
|
||||
CommitteeMeetingData existing = meetingService.retrieve(id);
|
||||
Long electionId = committeeService.getElectionIdForCommittee(existing.getCommitteeId());
|
||||
CommitteeMeetingData result = meetingService.patchStatus(id, status);
|
||||
ElectionAuditAction action = "COMPLETED".equalsIgnoreCase(status)
|
||||
? ElectionAuditAction.COMPLETE_MEETING
|
||||
: ElectionAuditAction.CANCEL_MEETING;
|
||||
auditService.log(action, "COMMITTEE", id, electionId,
|
||||
"Meeting \"" + existing.getTitle() + "\" marked as " + status.toUpperCase(),
|
||||
"meetingId=" + id + ", newStatus=" + status.toUpperCase(),
|
||||
clientIp(httpRequest));
|
||||
return Response.ok(result).build();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(summary = "Delete a scheduled meeting")
|
||||
public Response delete(@PathParam("id") Long id,
|
||||
@Context HttpServletRequest httpRequest) {
|
||||
CommitteeMeetingData existing = meetingService.retrieve(id);
|
||||
Long electionId = committeeService.getElectionIdForCommittee(existing.getCommitteeId());
|
||||
meetingService.delete(id);
|
||||
auditService.log(ElectionAuditAction.DELETE_MEETING,
|
||||
"COMMITTEE", id, electionId,
|
||||
"Meeting \"" + existing.getTitle() + "\" deleted",
|
||||
"meetingId=" + id + ", date=" + existing.getMeetingDate(),
|
||||
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();
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package org.apache.fineract.portfolio.election.data;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class CommitteeMeetingData {
|
||||
private Long id;
|
||||
private Long committeeId;
|
||||
private String title;
|
||||
private LocalDate meetingDate;
|
||||
private String meetingTime;
|
||||
private String mode;
|
||||
private String status;
|
||||
private String agenda;
|
||||
// Online
|
||||
private String platform;
|
||||
private String meetingLink;
|
||||
private String meetingIdRef;
|
||||
private String meetingPassword;
|
||||
// Offline
|
||||
private String venue;
|
||||
private String address;
|
||||
private String city;
|
||||
// Audit
|
||||
private OffsetDateTime createdDate;
|
||||
private OffsetDateTime lastModifiedDate;
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package org.apache.fineract.portfolio.election.data;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Data
|
||||
public class CommitteeMeetingRequest {
|
||||
private String title;
|
||||
private LocalDate meetingDate;
|
||||
private String meetingTime;
|
||||
private String mode; // ONLINE | OFFLINE
|
||||
private String status; // SCHEDULED | COMPLETED | CANCELLED
|
||||
private String agenda;
|
||||
// Online
|
||||
private String platform;
|
||||
private String meetingLink;
|
||||
private String meetingIdRef;
|
||||
private String meetingPassword;
|
||||
// Offline
|
||||
private String venue;
|
||||
private String address;
|
||||
private String city;
|
||||
}
|
||||
+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.AbstractAuditableWithUTCDateTimeCustom;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_election_committee_meeting")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class CommitteeMeeting extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
@Column(name = "committee_id", nullable = false)
|
||||
private Long committeeId;
|
||||
|
||||
@Column(name = "title", nullable = false, length = 200)
|
||||
private String title;
|
||||
|
||||
@Column(name = "meeting_date", nullable = false)
|
||||
private LocalDate meetingDate;
|
||||
|
||||
@Column(name = "meeting_time", length = 10)
|
||||
private String meetingTime;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "mode", nullable = false, length = 20)
|
||||
private CommitteeMeetingMode mode;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "status", nullable = false, length = 20)
|
||||
private CommitteeMeetingStatus status = CommitteeMeetingStatus.SCHEDULED;
|
||||
|
||||
@Column(name = "agenda", columnDefinition = "TEXT")
|
||||
private String agenda;
|
||||
|
||||
// Online fields
|
||||
@Column(name = "platform", length = 50)
|
||||
private String platform;
|
||||
|
||||
@Column(name = "meeting_link", length = 500)
|
||||
private String meetingLink;
|
||||
|
||||
@Column(name = "meeting_id_ref", length = 100)
|
||||
private String meetingIdRef;
|
||||
|
||||
@Column(name = "meeting_password", length = 100)
|
||||
private String meetingPassword;
|
||||
|
||||
// Offline fields
|
||||
@Column(name = "venue", length = 200)
|
||||
private String venue;
|
||||
|
||||
@Column(name = "address", columnDefinition = "TEXT")
|
||||
private String address;
|
||||
|
||||
@Column(name = "city", length = 100)
|
||||
private String city;
|
||||
|
||||
@Column(name = "is_deleted", nullable = false)
|
||||
private boolean isDeleted = false;
|
||||
|
||||
public static CommitteeMeeting create(Long committeeId, String title, LocalDate meetingDate,
|
||||
String meetingTime, CommitteeMeetingMode mode, String agenda) {
|
||||
CommitteeMeeting m = new CommitteeMeeting();
|
||||
m.committeeId = committeeId;
|
||||
m.title = title;
|
||||
m.meetingDate = meetingDate;
|
||||
m.meetingTime = meetingTime;
|
||||
m.mode = mode;
|
||||
m.agenda = agenda;
|
||||
m.status = CommitteeMeetingStatus.SCHEDULED;
|
||||
return m;
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
public enum CommitteeMeetingMode {
|
||||
ONLINE, OFFLINE
|
||||
}
|
||||
+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 CommitteeMeetingRepository extends JpaRepository<CommitteeMeeting, Long> {
|
||||
|
||||
List<CommitteeMeeting> findByCommitteeIdAndIsDeletedFalseOrderByMeetingDateDesc(Long committeeId);
|
||||
|
||||
java.util.Optional<CommitteeMeeting> findByIdAndIsDeletedFalse(Long id);
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package org.apache.fineract.portfolio.election.domain;
|
||||
|
||||
public enum CommitteeMeetingStatus {
|
||||
SCHEDULED, COMPLETED, CANCELLED
|
||||
}
|
||||
+4
-1
@@ -28,5 +28,8 @@ public enum ElectionAuditAction {
|
||||
APPOINT_DIRECTOR_FAILED, UPDATE_DIRECTOR_APPOINTMENT_FAILED, REMOVE_DIRECTOR_APPOINTMENT_FAILED,
|
||||
// Committee
|
||||
CREATE_COMMITTEE, UPDATE_COMMITTEE, DISSOLVE_COMMITTEE,
|
||||
ADD_COMMITTEE_MEMBER, REMOVE_COMMITTEE_MEMBER
|
||||
ADD_COMMITTEE_MEMBER, REMOVE_COMMITTEE_MEMBER,
|
||||
// Committee Meeting
|
||||
SCHEDULE_MEETING, UPDATE_MEETING, CANCEL_MEETING, COMPLETE_MEETING, DELETE_MEETING,
|
||||
SCHEDULE_MEETING_FAILED, UPDATE_MEETING_FAILED
|
||||
}
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package org.apache.fineract.portfolio.election.service;
|
||||
|
||||
import org.apache.fineract.portfolio.election.data.CommitteeMeetingData;
|
||||
import org.apache.fineract.portfolio.election.data.CommitteeMeetingRequest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface CommitteeMeetingService {
|
||||
|
||||
CommitteeMeetingData schedule(Long committeeId, CommitteeMeetingRequest request);
|
||||
|
||||
CommitteeMeetingData update(Long meetingId, CommitteeMeetingRequest request);
|
||||
|
||||
CommitteeMeetingData patchStatus(Long meetingId, String status);
|
||||
|
||||
void delete(Long meetingId);
|
||||
|
||||
CommitteeMeetingData retrieve(Long meetingId);
|
||||
|
||||
List<CommitteeMeetingData> retrieveByCommittee(Long committeeId);
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
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.CommitteeMeetingData;
|
||||
import org.apache.fineract.portfolio.election.data.CommitteeMeetingRequest;
|
||||
import org.apache.fineract.portfolio.election.domain.CommitteeMeeting;
|
||||
import org.apache.fineract.portfolio.election.domain.CommitteeMeetingMode;
|
||||
import org.apache.fineract.portfolio.election.domain.CommitteeMeetingRepository;
|
||||
import org.apache.fineract.portfolio.election.domain.CommitteeMeetingStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CommitteeMeetingServiceImpl implements CommitteeMeetingService {
|
||||
|
||||
private final CommitteeMeetingRepository repository;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommitteeMeetingData schedule(Long committeeId, CommitteeMeetingRequest req) {
|
||||
validateRequest(req);
|
||||
CommitteeMeeting meeting = CommitteeMeeting.create(
|
||||
committeeId,
|
||||
req.getTitle(),
|
||||
req.getMeetingDate(),
|
||||
req.getMeetingTime(),
|
||||
CommitteeMeetingMode.valueOf(req.getMode().toUpperCase()),
|
||||
req.getAgenda());
|
||||
applyModeFields(meeting, req);
|
||||
return toData(repository.saveAndFlush(meeting));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommitteeMeetingData update(Long meetingId, CommitteeMeetingRequest req) {
|
||||
CommitteeMeeting meeting = findOrThrow(meetingId);
|
||||
if (req.getTitle() != null) meeting.setTitle(req.getTitle());
|
||||
if (req.getMeetingDate() != null) meeting.setMeetingDate(req.getMeetingDate());
|
||||
if (req.getMeetingTime() != null) meeting.setMeetingTime(req.getMeetingTime());
|
||||
if (req.getMode() != null) meeting.setMode(CommitteeMeetingMode.valueOf(req.getMode().toUpperCase()));
|
||||
if (req.getAgenda() != null) meeting.setAgenda(req.getAgenda());
|
||||
applyModeFields(meeting, req);
|
||||
return toData(repository.save(meeting));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommitteeMeetingData patchStatus(Long meetingId, String status) {
|
||||
CommitteeMeeting meeting = findOrThrow(meetingId);
|
||||
meeting.setStatus(CommitteeMeetingStatus.valueOf(status.toUpperCase()));
|
||||
return toData(repository.save(meeting));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void delete(Long meetingId) {
|
||||
CommitteeMeeting meeting = findOrThrow(meetingId);
|
||||
meeting.setDeleted(true);
|
||||
repository.save(meeting);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public CommitteeMeetingData retrieve(Long meetingId) {
|
||||
return toData(findOrThrow(meetingId));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<CommitteeMeetingData> retrieveByCommittee(Long committeeId) {
|
||||
return repository.findByCommitteeIdAndIsDeletedFalseOrderByMeetingDateDesc(committeeId)
|
||||
.stream().map(this::toData).toList();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void applyModeFields(CommitteeMeeting m, CommitteeMeetingRequest req) {
|
||||
CommitteeMeetingMode mode = m.getMode();
|
||||
if (mode == CommitteeMeetingMode.ONLINE) {
|
||||
m.setPlatform(req.getPlatform());
|
||||
m.setMeetingLink(req.getMeetingLink());
|
||||
m.setMeetingIdRef(req.getMeetingIdRef());
|
||||
m.setMeetingPassword(req.getMeetingPassword());
|
||||
// clear offline fields
|
||||
m.setVenue(null);
|
||||
m.setAddress(null);
|
||||
m.setCity(null);
|
||||
} else {
|
||||
m.setVenue(req.getVenue());
|
||||
m.setAddress(req.getAddress());
|
||||
m.setCity(req.getCity());
|
||||
// clear online fields
|
||||
m.setPlatform(null);
|
||||
m.setMeetingLink(null);
|
||||
m.setMeetingIdRef(null);
|
||||
m.setMeetingPassword(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateRequest(CommitteeMeetingRequest req) {
|
||||
if (req.getTitle() == null || req.getTitle().isBlank()) {
|
||||
throw new IllegalArgumentException("Meeting title is required");
|
||||
}
|
||||
if (req.getMeetingDate() == null) {
|
||||
throw new IllegalArgumentException("Meeting date is required");
|
||||
}
|
||||
if (req.getMode() == null || req.getMode().isBlank()) {
|
||||
throw new IllegalArgumentException("Meeting mode (ONLINE/OFFLINE) is required");
|
||||
}
|
||||
try {
|
||||
CommitteeMeetingMode.valueOf(req.getMode().toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Invalid meeting mode. Use ONLINE or OFFLINE");
|
||||
}
|
||||
}
|
||||
|
||||
private CommitteeMeeting findOrThrow(Long id) {
|
||||
return repository.findByIdAndIsDeletedFalse(id).orElseThrow(
|
||||
() -> new AbstractPlatformResourceNotFoundException("error.msg.committee.meeting.not.found",
|
||||
"Committee meeting not found with id: " + id, id) {});
|
||||
}
|
||||
|
||||
private CommitteeMeetingData toData(CommitteeMeeting m) {
|
||||
return CommitteeMeetingData.builder()
|
||||
.id(m.getId())
|
||||
.committeeId(m.getCommitteeId())
|
||||
.title(m.getTitle())
|
||||
.meetingDate(m.getMeetingDate())
|
||||
.meetingTime(m.getMeetingTime())
|
||||
.mode(m.getMode() != null ? m.getMode().name() : null)
|
||||
.status(m.getStatus() != null ? m.getStatus().name() : null)
|
||||
.agenda(m.getAgenda())
|
||||
.platform(m.getPlatform())
|
||||
.meetingLink(m.getMeetingLink())
|
||||
.meetingIdRef(m.getMeetingIdRef())
|
||||
.meetingPassword(m.getMeetingPassword())
|
||||
.venue(m.getVenue())
|
||||
.address(m.getAddress())
|
||||
.city(m.getCity())
|
||||
.createdDate(m.getCreatedDate().orElse(null))
|
||||
.lastModifiedDate(m.getLastModifiedDate().orElse(null))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+2
@@ -23,4 +23,6 @@ public interface CommitteeService {
|
||||
CommitteeData removeMember(Long committeeId, Long directorAppointmentId);
|
||||
|
||||
List<DirectorAppointmentData> getCurrentElectionDirectors();
|
||||
|
||||
Long getElectionIdForCommittee(Long committeeId);
|
||||
}
|
||||
|
||||
+7
@@ -161,6 +161,13 @@ public class CommitteeServiceImpl implements CommitteeService {
|
||||
.build()).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getElectionIdForCommittee(Long committeeId) {
|
||||
return committeeRepository.findById(committeeId)
|
||||
.map(this::electionIdForCommittee)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private Long electionIdFromDirectors(List<Long> directorAppointmentIds) {
|
||||
if (directorAppointmentIds == null || directorAppointmentIds.isEmpty()) return null;
|
||||
return directorRepository.findById(directorAppointmentIds.get(0))
|
||||
|
||||
@@ -378,4 +378,6 @@
|
||||
<include file="parts/0348_election_config_duration_years.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0349_election_committee.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0350_committee_image.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0351_committee_meeting.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0352_committee_meeting_rename_audit_cols.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<?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="0351-committee-meeting" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_election_committee_meeting"/></not>
|
||||
</preConditions>
|
||||
|
||||
<createTable tableName="m_election_committee_meeting">
|
||||
<column name="id" type="BIGSERIAL">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="committee_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="title" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="meeting_date" type="DATE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="meeting_time" type="VARCHAR(10)"/>
|
||||
<column name="mode" type="VARCHAR(20)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="status" type="VARCHAR(20)" defaultValue="SCHEDULED">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="agenda" type="TEXT"/>
|
||||
<!-- Online fields -->
|
||||
<column name="platform" type="VARCHAR(50)"/>
|
||||
<column name="meeting_link" type="VARCHAR(500)"/>
|
||||
<column name="meeting_id_ref" type="VARCHAR(100)"/>
|
||||
<column name="meeting_password" type="VARCHAR(100)"/>
|
||||
<!-- Offline fields -->
|
||||
<column name="venue" type="VARCHAR(200)"/>
|
||||
<column name="address" type="TEXT"/>
|
||||
<column name="city" type="VARCHAR(100)"/>
|
||||
<!-- Soft delete -->
|
||||
<column name="is_deleted" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<!-- Audit -->
|
||||
<column name="created_by" type="BIGINT"/>
|
||||
<column name="created_date" type="TIMESTAMP"/>
|
||||
<column name="last_modified_by" type="BIGINT"/>
|
||||
<column name="last_modified_date" type="TIMESTAMP"/>
|
||||
</createTable>
|
||||
|
||||
<addForeignKeyConstraint
|
||||
baseTableName="m_election_committee_meeting"
|
||||
baseColumnNames="committee_id"
|
||||
referencedTableName="m_election_committee"
|
||||
referencedColumnNames="id"
|
||||
constraintName="fk_committee_meeting_committee"/>
|
||||
|
||||
<createIndex tableName="m_election_committee_meeting" indexName="idx_committee_meeting_committee_date">
|
||||
<column name="committee_id"/>
|
||||
<column name="meeting_date" descending="true"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?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="0352-committee-meeting-rename-created-date" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<columnExists tableName="m_election_committee_meeting" columnName="created_date"/>
|
||||
</preConditions>
|
||||
<renameColumn tableName="m_election_committee_meeting"
|
||||
oldColumnName="created_date"
|
||||
newColumnName="created_on_utc"
|
||||
columnDataType="TIMESTAMP"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0352-committee-meeting-rename-modified-date" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<columnExists tableName="m_election_committee_meeting" columnName="last_modified_date"/>
|
||||
</preConditions>
|
||||
<renameColumn tableName="m_election_committee_meeting"
|
||||
oldColumnName="last_modified_date"
|
||||
newColumnName="last_modified_on_utc"
|
||||
columnDataType="TIMESTAMP"/>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user