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:
Jaanu423
2026-07-02 12:50:04 +05:30
co-authored by Claude Sonnet 4.6
parent ba199d78e7
commit 7ce7353e7a
13 changed files with 597 additions and 1 deletions
@@ -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>
@@ -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>