From e72b9359c17ff40393e4d503084c49e34fca56fc Mon Sep 17 00:00:00 2001 From: Jaanu423 Date: Mon, 6 Jul 2026 10:16:20 +0530 Subject: [PATCH] =?UTF-8?q?feat(election):=20committee=20meeting=20module?= =?UTF-8?q?=20=E2=80=94=20schedule,=20online/offline=20mode,=20soft=20dele?= =?UTF-8?q?te,=20audit=20logging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../api/CommitteeMeetingApiResource.java | 143 +++++++++++++++++ .../election/data/CommitteeMeetingData.java | 32 ++++ .../data/CommitteeMeetingRequest.java | 24 +++ .../election/domain/CommitteeMeeting.java | 79 ++++++++++ .../election/domain/CommitteeMeetingMode.java | 5 + .../domain/CommitteeMeetingRepository.java | 14 ++ .../domain/CommitteeMeetingStatus.java | 5 + .../election/domain/ElectionAuditAction.java | 5 +- .../service/CommitteeMeetingService.java | 21 +++ .../service/CommitteeMeetingServiceImpl.java | 148 ++++++++++++++++++ .../election/service/CommitteeService.java | 2 + .../service/CommitteeServiceImpl.java | 7 + .../db/changelog/tenant/changelog-tenant.xml | 2 + .../tenant/parts/0351_committee_meeting.xml | 66 ++++++++ ...52_committee_meeting_rename_audit_cols.xml | 27 ++++ 15 files changed, 579 insertions(+), 1 deletion(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/election/api/CommitteeMeetingApiResource.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/election/data/CommitteeMeetingData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/election/data/CommitteeMeetingRequest.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/CommitteeMeeting.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/CommitteeMeetingMode.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/CommitteeMeetingRepository.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/CommitteeMeetingStatus.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/election/service/CommitteeMeetingService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/election/service/CommitteeMeetingServiceImpl.java create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0351_committee_meeting.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0352_committee_meeting_rename_audit_cols.xml diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/api/CommitteeMeetingApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/api/CommitteeMeetingApiResource.java new file mode 100644 index 000000000..0fbf934bb --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/api/CommitteeMeetingApiResource.java @@ -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 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(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/data/CommitteeMeetingData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/data/CommitteeMeetingData.java new file mode 100644 index 000000000..21e5c4903 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/data/CommitteeMeetingData.java @@ -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; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/data/CommitteeMeetingRequest.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/data/CommitteeMeetingRequest.java new file mode 100644 index 000000000..2db87b1a2 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/data/CommitteeMeetingRequest.java @@ -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; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/CommitteeMeeting.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/CommitteeMeeting.java new file mode 100644 index 000000000..b99d2567b --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/CommitteeMeeting.java @@ -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 { + + @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; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/CommitteeMeetingMode.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/CommitteeMeetingMode.java new file mode 100644 index 000000000..9b4a17561 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/CommitteeMeetingMode.java @@ -0,0 +1,5 @@ +package org.apache.fineract.portfolio.election.domain; + +public enum CommitteeMeetingMode { + ONLINE, OFFLINE +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/CommitteeMeetingRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/CommitteeMeetingRepository.java new file mode 100644 index 000000000..0a3a7c5ec --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/CommitteeMeetingRepository.java @@ -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 { + + List findByCommitteeIdAndIsDeletedFalseOrderByMeetingDateDesc(Long committeeId); + + java.util.Optional findByIdAndIsDeletedFalse(Long id); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/CommitteeMeetingStatus.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/CommitteeMeetingStatus.java new file mode 100644 index 000000000..e2edde596 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/CommitteeMeetingStatus.java @@ -0,0 +1,5 @@ +package org.apache.fineract.portfolio.election.domain; + +public enum CommitteeMeetingStatus { + SCHEDULED, COMPLETED, CANCELLED +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/ElectionAuditAction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/ElectionAuditAction.java index 4197d2f00..88a0cc752 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/ElectionAuditAction.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/domain/ElectionAuditAction.java @@ -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 } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/service/CommitteeMeetingService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/service/CommitteeMeetingService.java new file mode 100644 index 000000000..2accae560 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/service/CommitteeMeetingService.java @@ -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 retrieveByCommittee(Long committeeId); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/service/CommitteeMeetingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/service/CommitteeMeetingServiceImpl.java new file mode 100644 index 000000000..46236a0db --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/service/CommitteeMeetingServiceImpl.java @@ -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 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(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/service/CommitteeService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/service/CommitteeService.java index 8c19fbfa6..823dd6043 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/service/CommitteeService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/service/CommitteeService.java @@ -23,4 +23,6 @@ public interface CommitteeService { CommitteeData removeMember(Long committeeId, Long directorAppointmentId); List getCurrentElectionDirectors(); + + Long getElectionIdForCommittee(Long committeeId); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/service/CommitteeServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/service/CommitteeServiceImpl.java index 890f83b7a..7a370b0e0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/service/CommitteeServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/election/service/CommitteeServiceImpl.java @@ -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 directorAppointmentIds) { if (directorAppointmentIds == null || directorAppointmentIds.isEmpty()) return null; return directorRepository.findById(directorAppointmentIds.get(0)) diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index 71eff5283..504fc9738 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -378,4 +378,6 @@ + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0351_committee_meeting.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0351_committee_meeting.xml new file mode 100644 index 000000000..80abb384c --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0351_committee_meeting.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0352_committee_meeting_rename_audit_cols.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0352_committee_meeting_rename_audit_cols.xml new file mode 100644 index 000000000..72bb0f0d8 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0352_committee_meeting_rename_audit_cols.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + +