feat(election): add election module — config, nominations, candidates, voting, approval flow

- REST API resources: ElectionConfig, ElectionNomination, ElectionCandidate, ElectionVoting
- Domain entities, repositories, services (read + write) with maker-checker approval steps
- Eligibility checks, no-due-certificate, CEO pre-check services
- Liquibase migration 0337 creates all election module tables

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-06-19 17:39:43 +05:30
co-authored by Claude Sonnet 4.6
parent c1e23080fc
commit 5899d2ca07
60 changed files with 3162 additions and 0 deletions
@@ -364,4 +364,5 @@
<include file="parts/0334_clerk_disbursement_add_error_message.xml" relativeToChangelogFile="true"/>
<include file="parts/0335_create_bank_transfer_log.xml" relativeToChangelogFile="true"/>
<include file="parts/0336_add_request_type_to_clerk_disbursement.xml" relativeToChangelogFile="true"/>
<include file="parts/0337_create_election_module.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,390 @@
<?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">
<!-- 1. m_election_config -->
<changeSet id="0337_create_m_election_config" author="CBS">
<preConditions onFail="MARK_RAN">
<not><tableExists tableName="m_election_config"/></not>
</preConditions>
<createTable tableName="m_election_config">
<column name="id" type="BIGSERIAL" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="title" type="VARCHAR(200)">
<constraints nullable="false"/>
</column>
<column name="description" type="TEXT"/>
<column name="election_type" type="VARCHAR(50)">
<constraints nullable="false"/>
</column>
<column name="nomination_start_date" type="DATE">
<constraints nullable="false"/>
</column>
<column name="nomination_end_date" type="DATE">
<constraints nullable="false"/>
</column>
<column name="election_date" type="DATE">
<constraints nullable="false"/>
</column>
<column name="result_date" type="DATE"/>
<column name="status" type="VARCHAR(50)" defaultValue="DRAFT">
<constraints nullable="false"/>
</column>
<column name="is_active" type="BOOLEAN" defaultValueBoolean="true">
<constraints nullable="false"/>
</column>
<column name="total_seats" type="INT"/>
<column name="remarks" type="TEXT"/>
<column name="created_by" type="BIGINT"/>
<column name="created_on_utc" type="TIMESTAMP"/>
<column name="last_modified_by" type="BIGINT"/>
<column name="last_modified_on_utc" type="TIMESTAMP"/>
</createTable>
</changeSet>
<!-- 2. m_election_post -->
<changeSet id="0337_create_m_election_post" author="CBS">
<preConditions onFail="MARK_RAN">
<not><tableExists tableName="m_election_post"/></not>
</preConditions>
<createTable tableName="m_election_post">
<column name="id" type="BIGSERIAL" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="election_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_election_post_config" references="m_election_config(id)"/>
</column>
<column name="post_name" type="VARCHAR(200)">
<constraints nullable="false"/>
</column>
<column name="total_seats" type="INT" defaultValueNumeric="1">
<constraints nullable="false"/>
</column>
<column name="caste_category" type="VARCHAR(100)"/>
<column name="gender_type" type="VARCHAR(20)" defaultValue="ANY">
<constraints nullable="false"/>
</column>
<column name="min_age" type="INT"/>
<column name="max_age" type="INT"/>
<column name="min_share_holding" type="INT" defaultValueNumeric="0"/>
<column name="area" type="VARCHAR(200)"/>
<column name="is_active" type="BOOLEAN" defaultValueBoolean="true">
<constraints nullable="false"/>
</column>
<column name="description" type="TEXT"/>
</createTable>
</changeSet>
<!-- 3. m_election_nomination -->
<changeSet id="0337_create_m_election_nomination" author="CBS">
<preConditions onFail="MARK_RAN">
<not><tableExists tableName="m_election_nomination"/></not>
</preConditions>
<createTable tableName="m_election_nomination">
<column name="id" type="BIGSERIAL" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="election_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_nomination_election" references="m_election_config(id)"/>
</column>
<column name="post_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_nomination_post" references="m_election_post(id)"/>
</column>
<column name="member_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="share_account_id" type="BIGINT"/>
<column name="application_number" type="VARCHAR(50)">
<constraints unique="true"/>
</column>
<column name="applicant_name" type="VARCHAR(200)">
<constraints nullable="false"/>
</column>
<column name="date_of_birth" type="DATE"/>
<column name="gender" type="VARCHAR(20)"/>
<column name="caste" type="VARCHAR(100)"/>
<column name="caste_category" type="VARCHAR(100)"/>
<column name="location" type="VARCHAR(200)"/>
<column name="service_details" type="VARCHAR(500)"/>
<column name="mobile_number" type="VARCHAR(20)"/>
<column name="email" type="VARCHAR(150)"/>
<column name="address" type="TEXT"/>
<column name="occupation" type="VARCHAR(200)"/>
<column name="share_count" type="INT"/>
<column name="status" type="VARCHAR(50)" defaultValue="DRAFT">
<constraints nullable="false"/>
</column>
<column name="rejection_reason" type="TEXT"/>
<column name="submitted_date" type="TIMESTAMP"/>
<column name="declaration_accepted" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="photo_document_id" type="BIGINT"/>
<column name="created_by" type="BIGINT"/>
<column name="created_on_utc" type="TIMESTAMP"/>
<column name="last_modified_by" type="BIGINT"/>
<column name="last_modified_on_utc" type="TIMESTAMP"/>
</createTable>
<addUniqueConstraint tableName="m_election_nomination"
columnNames="election_id, member_id, post_id"
constraintName="uq_nomination_election_member_post"/>
</changeSet>
<!-- 4. m_election_eligibility_check -->
<changeSet id="0337_create_m_election_eligibility_check" author="CBS">
<preConditions onFail="MARK_RAN">
<not><tableExists tableName="m_election_eligibility_check"/></not>
</preConditions>
<createTable tableName="m_election_eligibility_check">
<column name="id" type="BIGSERIAL" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="nomination_id" type="BIGINT">
<constraints nullable="false" unique="true" foreignKeyName="fk_eligibility_nomination" references="m_election_nomination(id)"/>
</column>
<column name="age_eligible" type="BOOLEAN"/>
<column name="age_check_remarks" type="VARCHAR(500)"/>
<column name="gender_eligible" type="BOOLEAN"/>
<column name="gender_check_remarks" type="VARCHAR(500)"/>
<column name="caste_eligible" type="BOOLEAN"/>
<column name="caste_check_remarks" type="VARCHAR(500)"/>
<column name="share_holding_eligible" type="BOOLEAN"/>
<column name="share_check_remarks" type="VARCHAR(500)"/>
<column name="area_eligible" type="BOOLEAN"/>
<column name="area_check_remarks" type="VARCHAR(500)"/>
<column name="overall_result" type="VARCHAR(20)" defaultValue="PENDING"/>
<column name="checked_by" type="BIGINT"/>
<column name="checked_date" type="TIMESTAMP"/>
<column name="remarks" type="TEXT"/>
</createTable>
</changeSet>
<!-- 5. m_election_ceo_precheck -->
<changeSet id="0337_create_m_election_ceo_precheck" author="CBS">
<preConditions onFail="MARK_RAN">
<not><tableExists tableName="m_election_ceo_precheck"/></not>
</preConditions>
<createTable tableName="m_election_ceo_precheck">
<column name="id" type="BIGSERIAL" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="nomination_id" type="BIGINT">
<constraints nullable="false" unique="true" foreignKeyName="fk_ceoprecheck_nomination" references="m_election_nomination(id)"/>
</column>
<column name="director_loan_check_result" type="VARCHAR(20)" defaultValue="PENDING"/>
<column name="director_loan_outstanding" type="DECIMAL(19,4)" defaultValueNumeric="0"/>
<column name="director_loan_remarks" type="TEXT"/>
<column name="relation_ac_check_result" type="VARCHAR(20)" defaultValue="PENDING"/>
<column name="relation_accounts_details" type="TEXT"/>
<column name="relation_ac_remarks" type="TEXT"/>
<column name="no_due_check_result" type="VARCHAR(20)" defaultValue="PENDING"/>
<column name="no_due_certificate_number" type="VARCHAR(100)"/>
<column name="no_due_generated_date" type="TIMESTAMP"/>
<column name="overall_result" type="VARCHAR(20)" defaultValue="PENDING"/>
<column name="checked_by" type="BIGINT"/>
<column name="checked_date" type="TIMESTAMP"/>
<column name="remarks" type="TEXT"/>
</createTable>
</changeSet>
<!-- 6. m_election_approval_step -->
<changeSet id="0337_create_m_election_approval_step" author="CBS">
<preConditions onFail="MARK_RAN">
<not><tableExists tableName="m_election_approval_step"/></not>
</preConditions>
<createTable tableName="m_election_approval_step">
<column name="id" type="BIGSERIAL" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="nomination_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_approval_step_nomination" references="m_election_nomination(id)"/>
</column>
<column name="step_number" type="INT">
<constraints nullable="false"/>
</column>
<column name="step_name" type="VARCHAR(200)">
<constraints nullable="false"/>
</column>
<column name="approver_role" type="VARCHAR(100)"/>
<column name="approver_user_id" type="BIGINT"/>
<column name="status" type="VARCHAR(50)" defaultValue="PENDING">
<constraints nullable="false"/>
</column>
<column name="remarks" type="TEXT"/>
<column name="action_date" type="TIMESTAMP"/>
<column name="family_details_verified" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="family_details_json" type="TEXT"/>
</createTable>
</changeSet>
<!-- 7. m_election_candidate -->
<changeSet id="0337_create_m_election_candidate" author="CBS">
<preConditions onFail="MARK_RAN">
<not><tableExists tableName="m_election_candidate"/></not>
</preConditions>
<createTable tableName="m_election_candidate">
<column name="id" type="BIGSERIAL" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="election_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_candidate_election" references="m_election_config(id)"/>
</column>
<column name="nomination_id" type="BIGINT">
<constraints nullable="false" unique="true" foreignKeyName="fk_candidate_nomination" references="m_election_nomination(id)"/>
</column>
<column name="post_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_candidate_post" references="m_election_post(id)"/>
</column>
<column name="candidate_name" type="VARCHAR(200)">
<constraints nullable="false"/>
</column>
<column name="serial_number" type="INT"/>
<column name="photo_url" type="VARCHAR(500)"/>
<column name="is_active" type="BOOLEAN" defaultValueBoolean="true">
<constraints nullable="false"/>
</column>
<column name="approved_by" type="BIGINT"/>
<column name="approved_date" type="TIMESTAMP"/>
<column name="symbol" type="VARCHAR(100)"/>
<column name="brief_profile" type="TEXT"/>
</createTable>
</changeSet>
<!-- 8. m_election_vote -->
<changeSet id="0337_create_m_election_vote" author="CBS">
<preConditions onFail="MARK_RAN">
<not><tableExists tableName="m_election_vote"/></not>
</preConditions>
<createTable tableName="m_election_vote">
<column name="id" type="BIGSERIAL" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="election_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_vote_election" references="m_election_config(id)"/>
</column>
<column name="post_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_vote_post" references="m_election_post(id)"/>
</column>
<column name="voter_member_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="candidate_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_vote_candidate" references="m_election_candidate(id)"/>
</column>
<column name="vote_timestamp" type="TIMESTAMP">
<constraints nullable="false"/>
</column>
<column name="booth_id" type="VARCHAR(100)"/>
<column name="ip_address" type="VARCHAR(50)"/>
</createTable>
<addUniqueConstraint tableName="m_election_vote"
columnNames="election_id, post_id, voter_member_id"
constraintName="uq_election_vote_member_post"/>
</changeSet>
<!-- 9. m_election_result -->
<changeSet id="0337_create_m_election_result" author="CBS">
<preConditions onFail="MARK_RAN">
<not><tableExists tableName="m_election_result"/></not>
</preConditions>
<createTable tableName="m_election_result">
<column name="id" type="BIGSERIAL" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="election_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_result_election" references="m_election_config(id)"/>
</column>
<column name="post_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_result_post" references="m_election_post(id)"/>
</column>
<column name="candidate_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_result_candidate" references="m_election_candidate(id)"/>
</column>
<column name="vote_count" type="INT" defaultValueNumeric="0">
<constraints nullable="false"/>
</column>
<column name="result" type="VARCHAR(20)"/>
<column name="rank_position" type="INT"/>
<column name="declared_by" type="BIGINT"/>
<column name="declared_date" type="TIMESTAMP"/>
</createTable>
</changeSet>
<!-- 10. Permissions for election module -->
<changeSet id="0337_seed_election_permissions" author="CBS">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">
SELECT COUNT(*) FROM m_permission WHERE code = 'SIDEBAR_ELECTION'
</sqlCheck>
</preConditions>
<insert tableName="m_permission">
<column name="grouping" value="portfolio"/>
<column name="code" value="SIDEBAR_ELECTION"/>
<column name="entity_name" value="ELECTION"/>
<column name="action_name" value="SIDEBAR"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
<insert tableName="m_permission">
<column name="grouping" value="portfolio"/>
<column name="code" value="READ_ELECTION_CONFIG"/>
<column name="entity_name" value="ELECTION_CONFIG"/>
<column name="action_name" value="READ"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
<insert tableName="m_permission">
<column name="grouping" value="portfolio"/>
<column name="code" value="CREATE_ELECTION_CONFIG"/>
<column name="entity_name" value="ELECTION_CONFIG"/>
<column name="action_name" value="CREATE"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
<insert tableName="m_permission">
<column name="grouping" value="portfolio"/>
<column name="code" value="UPDATE_ELECTION_CONFIG"/>
<column name="entity_name" value="ELECTION_CONFIG"/>
<column name="action_name" value="UPDATE"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
<insert tableName="m_permission">
<column name="grouping" value="portfolio"/>
<column name="code" value="READ_ELECTION_NOMINATION"/>
<column name="entity_name" value="ELECTION_NOMINATION"/>
<column name="action_name" value="READ"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
<insert tableName="m_permission">
<column name="grouping" value="portfolio"/>
<column name="code" value="CREATE_ELECTION_NOMINATION"/>
<column name="entity_name" value="ELECTION_NOMINATION"/>
<column name="action_name" value="CREATE"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
<insert tableName="m_permission">
<column name="grouping" value="portfolio"/>
<column name="code" value="READ_ELECTION_CANDIDATE"/>
<column name="entity_name" value="ELECTION_CANDIDATE"/>
<column name="action_name" value="READ"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
<insert tableName="m_permission">
<column name="grouping" value="portfolio"/>
<column name="code" value="CAST_ELECTION_VOTE"/>
<column name="entity_name" value="ELECTION_VOTE"/>
<column name="action_name" value="CREATE"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
<insert tableName="m_permission">
<column name="grouping" value="portfolio"/>
<column name="code" value="DECLARE_ELECTION_RESULT"/>
<column name="entity_name" value="ELECTION_RESULT"/>
<column name="action_name" value="CREATE"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
</changeSet>
</databaseChangeLog>