feat(election): candidate voter analysis API — multi-dimension breakdown + sub-caste migration
- New ElectionAnalyticsServiceImpl with 14 SQL breakdown queries: gender, age, area, social category (caste), sub-caste, religion, occupation, share-holding, tenure, loan, education, marital status, monthly income, savings account depth - Cross-dimension match: caste + area simultaneously (core voter base) - Zero-count row injection for caste/gender/sub-caste when candidate's category has no shareholder matches in GROUP BY result - Liquibase 0343: sub_caste_cv_id column on m_client with 33 pre-seeded sub-caste values across General / OBC / SC / ST categories - Liquibase 0342: election post eligibility columns - CandidateVoterAnalysisData DTO with education, marital, income, savings, activeSavingsHolders, crossMatchCount fields Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
34f84e1fc6
commit
388af0a6a3
+10
@@ -19,6 +19,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.contentstore.service.ContentStoreService;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.election.domain.ElectionAuditAction;
|
||||
import org.apache.fineract.portfolio.election.service.ElectionAnalyticsService;
|
||||
import org.apache.fineract.portfolio.election.service.ElectionAuditService;
|
||||
import org.apache.fineract.portfolio.election.service.ElectionReadPlatformService;
|
||||
import org.apache.fineract.portfolio.election.service.ElectionWritePlatformService;
|
||||
@@ -45,6 +46,7 @@ public class ElectionCandidateApiResource {
|
||||
private final PlatformSecurityContext context;
|
||||
private final ContentStoreService contentStoreService;
|
||||
private final ElectionAuditService electionAuditService;
|
||||
private final ElectionAnalyticsService analyticsService;
|
||||
|
||||
@GET
|
||||
@Path("/{electionId}/candidates")
|
||||
@@ -168,6 +170,14 @@ public class ElectionCandidateApiResource {
|
||||
return Response.ok(Map.of("resourceId", candidateId, "status", "REMOVED")).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{electionId}/candidates/{candidateId}/voter-analysis")
|
||||
@Operation(summary = "Get demographic voter analysis for a candidate")
|
||||
public Response getCandidateVoterAnalysis(@PathParam("electionId") Long electionId,
|
||||
@PathParam("candidateId") Long candidateId) {
|
||||
return Response.ok(analyticsService.getCandidateVoterAnalysis(candidateId)).build();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package org.apache.fineract.portfolio.election.data;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CandidateVoterAnalysisData {
|
||||
|
||||
// ── Candidate / Post info ─────────────────────────────────────────────────
|
||||
private Long candidateId;
|
||||
private Long electionId;
|
||||
private String candidateName;
|
||||
private String postName;
|
||||
private String candidateGender;
|
||||
private String candidateCaste;
|
||||
private String candidateLocation; // free-text from nomination form (informational only)
|
||||
private String candidateArea; // city from m_client_address (used for area match)
|
||||
private Integer candidateAge;
|
||||
|
||||
// ── Post eligibility criteria ─────────────────────────────────────────────
|
||||
private String postGenderType;
|
||||
private Integer postMinAge;
|
||||
private Integer postMaxAge;
|
||||
private Integer postMinShareHolding;
|
||||
|
||||
// ── Voter pool summary ────────────────────────────────────────────────────
|
||||
private long totalActiveShareholders;
|
||||
|
||||
/** Shareholders who satisfy ALL post criteria simultaneously */
|
||||
private long actualEligibleVoters;
|
||||
|
||||
// ── Gender breakdown ──────────────────────────────────────────────────────
|
||||
private List<BreakdownItem> genderBreakdown;
|
||||
|
||||
// ── Age breakdown ─────────────────────────────────────────────────────────
|
||||
private List<BreakdownItem> ageBreakdown;
|
||||
private long eligibleByAge;
|
||||
|
||||
// ── Area / Office breakdown ───────────────────────────────────────────────
|
||||
private List<BreakdownItem> areaBreakdown;
|
||||
private long areaMatchCount;
|
||||
|
||||
// ── Caste / Social Category breakdown ────────────────────────────────────
|
||||
private List<BreakdownItem> casteBreakdown;
|
||||
private long casteMatchCount;
|
||||
|
||||
// ── Sub-caste breakdown ───────────────────────────────────────────────────
|
||||
private String candidateSubCaste;
|
||||
private List<BreakdownItem> subCasteBreakdown;
|
||||
private long subCasteMatchCount;
|
||||
|
||||
// ── Religion breakdown ────────────────────────────────────────────────────
|
||||
private List<BreakdownItem> religionBreakdown;
|
||||
|
||||
// ── Occupation breakdown ──────────────────────────────────────────────────
|
||||
private List<BreakdownItem> occupationBreakdown;
|
||||
|
||||
// ── Share holding distribution ────────────────────────────────────────────
|
||||
private List<BreakdownItem> shareHoldingBreakdown;
|
||||
|
||||
// ── Member tenure breakdown ────────────────────────────────────────────────
|
||||
private List<BreakdownItem> tenureBreakdown;
|
||||
|
||||
// ── Loan engagement ───────────────────────────────────────────────────────
|
||||
private List<BreakdownItem> loanHolderBreakdown;
|
||||
private long activeLoanHolders;
|
||||
|
||||
// ── Family members ────────────────────────────────────────────────────────
|
||||
private long shareholdersWithFamily;
|
||||
private long totalFamilyMembers;
|
||||
|
||||
// ── Education breakdown ───────────────────────────────────────────────────
|
||||
private List<BreakdownItem> educationBreakdown;
|
||||
|
||||
// ── Marital status breakdown ──────────────────────────────────────────────
|
||||
private List<BreakdownItem> maritalBreakdown;
|
||||
|
||||
// ── Monthly income distribution ───────────────────────────────────────────
|
||||
private List<BreakdownItem> monthlyIncomeBreakdown;
|
||||
|
||||
// ── Savings account engagement ────────────────────────────────────────────
|
||||
private List<BreakdownItem> savingsBreakdown;
|
||||
private long activeSavingsHolders;
|
||||
|
||||
// ── Cross-dimension match (caste + area) ──────────────────────────────────
|
||||
private long crossMatchCount;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class BreakdownItem {
|
||||
private String label;
|
||||
private long count;
|
||||
private double percent;
|
||||
private boolean match;
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package org.apache.fineract.portfolio.election.service;
|
||||
|
||||
import org.apache.fineract.portfolio.election.data.CandidateVoterAnalysisData;
|
||||
|
||||
public interface ElectionAnalyticsService {
|
||||
|
||||
/**
|
||||
* Returns a voter-base demographic analysis for a given candidate.
|
||||
* Compares all active shareholders against the candidate's own
|
||||
* gender, caste, location and age — highlighting which segments
|
||||
* are naturally aligned with the candidate's profile.
|
||||
*/
|
||||
CandidateVoterAnalysisData getCandidateVoterAnalysis(Long candidateId);
|
||||
}
|
||||
+614
@@ -0,0 +1,614 @@
|
||||
package org.apache.fineract.portfolio.election.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.portfolio.election.data.CandidateVoterAnalysisData;
|
||||
import org.apache.fineract.portfolio.election.data.CandidateVoterAnalysisData.BreakdownItem;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Period;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ElectionAnalyticsServiceImpl implements ElectionAnalyticsService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
// ── Candidate + post info ──────────────────────────────────────────────────
|
||||
private static final String SQL_CANDIDATE_INFO = """
|
||||
SELECT ec.id, ec.election_id,
|
||||
n.applicant_name, n.gender, n.caste_category, n.caste AS sub_caste, n.location, n.date_of_birth,
|
||||
p.post_name, p.min_age, p.max_age, p.gender_type, p.area, p.min_share_holding,
|
||||
COALESCE(NULLIF(ca.city,''), NULLIF(ca.town_village,''), NULLIF(ca.county_district,'')) AS candidate_city
|
||||
FROM m_election_candidate ec
|
||||
JOIN m_election_nomination n ON ec.nomination_id = n.id
|
||||
JOIN m_election_post p ON ec.post_id = p.id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT ma.city, ma.town_village, ma.county_district
|
||||
FROM m_client_address mca
|
||||
JOIN m_address ma ON ma.id = mca.address_id
|
||||
WHERE mca.client_id = n.member_id
|
||||
AND mca.is_active = true AND mca.is_deleted = false
|
||||
ORDER BY mca.id DESC LIMIT 1
|
||||
) ca ON true
|
||||
WHERE ec.id = ?
|
||||
""";
|
||||
|
||||
// ── Active shareholder totals ──────────────────────────────────────────────
|
||||
private static final String SQL_TOTAL = """
|
||||
SELECT COUNT(DISTINCT sa.client_id)
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id
|
||||
WHERE sa.status_enum = 300 AND c.status_enum = 300
|
||||
""";
|
||||
|
||||
// ── Gender ────────────────────────────────────────────────────────────────
|
||||
private static final String SQL_GENDER = """
|
||||
SELECT COALESCE(cv.code_value, 'Not Specified') AS label,
|
||||
COUNT(DISTINCT sa.client_id) AS cnt
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id AND c.status_enum = 300
|
||||
LEFT JOIN m_code_value cv ON c.gender_cv_id = cv.id
|
||||
WHERE sa.status_enum = 300
|
||||
GROUP BY cv.code_value
|
||||
ORDER BY cnt DESC
|
||||
""";
|
||||
|
||||
// ── Age bands ─────────────────────────────────────────────────────────────
|
||||
private static final String SQL_AGE = """
|
||||
SELECT
|
||||
CASE
|
||||
WHEN c.date_of_birth IS NULL THEN 'Not Specified'
|
||||
WHEN EXTRACT(YEAR FROM AGE(CURRENT_DATE, c.date_of_birth)) < 25 THEN 'Under 25'
|
||||
WHEN EXTRACT(YEAR FROM AGE(CURRENT_DATE, c.date_of_birth)) < 35 THEN '25–34'
|
||||
WHEN EXTRACT(YEAR FROM AGE(CURRENT_DATE, c.date_of_birth)) < 45 THEN '35–44'
|
||||
WHEN EXTRACT(YEAR FROM AGE(CURRENT_DATE, c.date_of_birth)) < 55 THEN '45–54'
|
||||
WHEN EXTRACT(YEAR FROM AGE(CURRENT_DATE, c.date_of_birth)) < 65 THEN '55–64'
|
||||
ELSE '65+'
|
||||
END AS label,
|
||||
COUNT(DISTINCT sa.client_id) AS cnt
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id AND c.status_enum = 300
|
||||
WHERE sa.status_enum = 300
|
||||
GROUP BY label
|
||||
ORDER BY cnt DESC
|
||||
""";
|
||||
|
||||
// ── Area (city/locality from client address) ──────────────────────────────
|
||||
private static final String SQL_AREA = """
|
||||
SELECT COALESCE(NULLIF(a.city,''), NULLIF(a.town_village,''), NULLIF(a.county_district,''), 'Not Specified') AS label,
|
||||
COUNT(DISTINCT sa.client_id) AS cnt
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id AND c.status_enum = 300
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT ma.city, ma.town_village, ma.county_district
|
||||
FROM m_client_address mca
|
||||
JOIN m_address ma ON ma.id = mca.address_id
|
||||
WHERE mca.client_id = c.id
|
||||
AND mca.is_active = true AND mca.is_deleted = false
|
||||
ORDER BY mca.id DESC LIMIT 1
|
||||
) a ON true
|
||||
WHERE sa.status_enum = 300
|
||||
GROUP BY label
|
||||
ORDER BY cnt DESC
|
||||
LIMIT 20
|
||||
""";
|
||||
|
||||
// ── Caste / Social Category ───────────────────────────────────────────────
|
||||
private static final String SQL_CASTE = """
|
||||
SELECT COALESCE(cv.code_value, 'Not Specified') AS label,
|
||||
COUNT(DISTINCT sa.client_id) AS cnt
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id AND c.status_enum = 300
|
||||
LEFT JOIN m_code_value cv ON c.social_category_cv_id = cv.id
|
||||
WHERE sa.status_enum = 300
|
||||
GROUP BY cv.code_value
|
||||
ORDER BY cnt DESC
|
||||
""";
|
||||
|
||||
// ── Share holding distribution ────────────────────────────────────────────
|
||||
private static final String SQL_SHARE_HOLDING = """
|
||||
SELECT
|
||||
CASE
|
||||
WHEN sa.total_approved_shares IS NULL OR sa.total_approved_shares = 0 THEN 'Not Specified'
|
||||
WHEN sa.total_approved_shares >= 500 THEN '500+ shares'
|
||||
WHEN sa.total_approved_shares >= 100 THEN '100–499 shares'
|
||||
WHEN sa.total_approved_shares >= 50 THEN '50–99 shares'
|
||||
WHEN sa.total_approved_shares >= 10 THEN '10–49 shares'
|
||||
ELSE '1–9 shares'
|
||||
END AS label,
|
||||
COUNT(DISTINCT sa.client_id) AS cnt,
|
||||
MIN(COALESCE(sa.total_approved_shares, 0)) AS sort_key
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id AND c.status_enum = 300
|
||||
WHERE sa.status_enum = 300
|
||||
GROUP BY label
|
||||
ORDER BY sort_key DESC
|
||||
""";
|
||||
|
||||
// ── Member tenure ─────────────────────────────────────────────────────────
|
||||
private static final String SQL_TENURE = """
|
||||
SELECT
|
||||
CASE
|
||||
WHEN c.activation_date IS NULL THEN 'Not Specified'
|
||||
WHEN c.activation_date >= CURRENT_DATE - INTERVAL '2 years' THEN 'Under 2 years'
|
||||
WHEN c.activation_date >= CURRENT_DATE - INTERVAL '5 years' THEN '2–5 years'
|
||||
WHEN c.activation_date >= CURRENT_DATE - INTERVAL '10 years' THEN '5–10 years'
|
||||
ELSE '10+ years'
|
||||
END AS label,
|
||||
COUNT(DISTINCT sa.client_id) AS cnt
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id AND c.status_enum = 300
|
||||
WHERE sa.status_enum = 300
|
||||
GROUP BY label
|
||||
ORDER BY cnt DESC
|
||||
""";
|
||||
|
||||
// ── Loan engagement ───────────────────────────────────────────────────────
|
||||
private static final String SQL_LOAN = """
|
||||
SELECT
|
||||
CASE WHEN l.id IS NOT NULL THEN 'Active Loan Holders' ELSE 'No Active Loan' END AS label,
|
||||
COUNT(DISTINCT sa.client_id) AS cnt
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id AND c.status_enum = 300
|
||||
LEFT JOIN m_loan l ON l.client_id = c.id AND l.loan_status_id = 300
|
||||
WHERE sa.status_enum = 300
|
||||
GROUP BY CASE WHEN l.id IS NOT NULL THEN 'Active Loan Holders' ELSE 'No Active Loan' END
|
||||
ORDER BY cnt DESC
|
||||
""";
|
||||
|
||||
// base for age-range eligibility — clauses added dynamically
|
||||
private static final String SQL_ELIGIBLE_AGE_BASE = """
|
||||
SELECT COUNT(DISTINCT sa.client_id)
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id AND c.status_enum = 300
|
||||
WHERE sa.status_enum = 300
|
||||
AND c.date_of_birth IS NOT NULL
|
||||
""";
|
||||
|
||||
// ── Sub-caste breakdown ───────────────────────────────────────────────────
|
||||
private static final String SQL_SUB_CASTE = """
|
||||
SELECT COALESCE(cv.code_value, 'Not Specified') AS label,
|
||||
COUNT(DISTINCT sa.client_id) AS cnt
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id AND c.status_enum = 300
|
||||
LEFT JOIN m_code_value cv ON c.sub_caste_cv_id = cv.id
|
||||
WHERE sa.status_enum = 300
|
||||
GROUP BY cv.code_value
|
||||
ORDER BY cnt DESC
|
||||
""";
|
||||
|
||||
// ── Religion breakdown ────────────────────────────────────────────────────
|
||||
private static final String SQL_RELIGION = """
|
||||
SELECT COALESCE(cv.code_value, 'Not Specified') AS label,
|
||||
COUNT(DISTINCT sa.client_id) AS cnt
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id AND c.status_enum = 300
|
||||
LEFT JOIN m_code_value cv ON c.religion_cv_id = cv.id
|
||||
WHERE sa.status_enum = 300
|
||||
GROUP BY cv.code_value
|
||||
ORDER BY cnt DESC
|
||||
""";
|
||||
|
||||
// ── Occupation category breakdown ─────────────────────────────────────────
|
||||
private static final String SQL_OCCUPATION = """
|
||||
SELECT COALESCE(cv.code_value, 'Not Specified') AS label,
|
||||
COUNT(DISTINCT sa.client_id) AS cnt
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id AND c.status_enum = 300
|
||||
LEFT JOIN m_code_value cv ON c.occupation_category_cv_id = cv.id
|
||||
WHERE sa.status_enum = 300
|
||||
GROUP BY cv.code_value
|
||||
ORDER BY cnt DESC
|
||||
""";
|
||||
|
||||
// ── Education Level ───────────────────────────────────────────────────────
|
||||
private static final String SQL_EDUCATION = """
|
||||
SELECT COALESCE(cv.code_value, 'Not Specified') AS label,
|
||||
COUNT(DISTINCT sa.client_id) AS cnt
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id AND c.status_enum = 300
|
||||
LEFT JOIN m_code_value cv ON c.education_level_cv_id = cv.id
|
||||
WHERE sa.status_enum = 300
|
||||
GROUP BY cv.code_value
|
||||
ORDER BY cnt DESC
|
||||
""";
|
||||
|
||||
// ── Marital Status ────────────────────────────────────────────────────────
|
||||
private static final String SQL_MARITAL = """
|
||||
SELECT COALESCE(cv.code_value, 'Not Specified') AS label,
|
||||
COUNT(DISTINCT sa.client_id) AS cnt
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id AND c.status_enum = 300
|
||||
LEFT JOIN m_code_value cv ON c.marital_status_cv_id = cv.id
|
||||
WHERE sa.status_enum = 300
|
||||
GROUP BY cv.code_value
|
||||
ORDER BY cnt DESC
|
||||
""";
|
||||
|
||||
// ── Monthly Income tiers ──────────────────────────────────────────────────
|
||||
private static final String SQL_MONTHLY_INCOME = """
|
||||
SELECT
|
||||
CASE
|
||||
WHEN c.monthly_income IS NULL OR c.monthly_income = 0 THEN 'Not Specified'
|
||||
WHEN c.monthly_income >= 100000 THEN '₹1L+'
|
||||
WHEN c.monthly_income >= 50000 THEN '₹50k–1L'
|
||||
WHEN c.monthly_income >= 25000 THEN '₹25k–50k'
|
||||
WHEN c.monthly_income >= 10000 THEN '₹10k–25k'
|
||||
WHEN c.monthly_income >= 5000 THEN '₹5k–10k'
|
||||
ELSE 'Under ₹5k'
|
||||
END AS label,
|
||||
COUNT(DISTINCT sa.client_id) AS cnt,
|
||||
MIN(COALESCE(c.monthly_income, 0)) AS sort_key
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id AND c.status_enum = 300
|
||||
WHERE sa.status_enum = 300
|
||||
GROUP BY label
|
||||
ORDER BY sort_key DESC
|
||||
""";
|
||||
|
||||
// ── Savings account engagement ────────────────────────────────────────────
|
||||
private static final String SQL_SAVINGS = """
|
||||
SELECT
|
||||
CASE WHEN sv.id IS NOT NULL THEN 'Active Savings' ELSE 'No Active Savings' END AS label,
|
||||
COUNT(DISTINCT sa.client_id) AS cnt
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id AND c.status_enum = 300
|
||||
LEFT JOIN m_savings_account sv ON sv.client_id = c.id AND sv.status_enum = 300
|
||||
WHERE sa.status_enum = 300
|
||||
GROUP BY CASE WHEN sv.id IS NOT NULL THEN 'Active Savings' ELSE 'No Active Savings' END
|
||||
ORDER BY cnt DESC
|
||||
""";
|
||||
|
||||
// ── Family members ────────────────────────────────────────────────────────
|
||||
private static final String SQL_FAMILY = """
|
||||
SELECT
|
||||
COUNT(DISTINCT CASE WHEN fm.id IS NOT NULL THEN sa.client_id END) AS with_family,
|
||||
COUNT(fm.id) AS total_family
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id AND c.status_enum = 300
|
||||
LEFT JOIN m_family_members fm ON fm.client_id = c.id
|
||||
WHERE sa.status_enum = 300
|
||||
""";
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public CandidateVoterAnalysisData getCandidateVoterAnalysis(Long candidateId) {
|
||||
// ── Candidate info ────────────────────────────────────────────────────
|
||||
Map<String, Object> info;
|
||||
try {
|
||||
info = jdbcTemplate.queryForMap(SQL_CANDIDATE_INFO, candidateId);
|
||||
} catch (EmptyResultDataAccessException ex) {
|
||||
throw new NotFoundException("Candidate not found with id: " + candidateId);
|
||||
}
|
||||
|
||||
String candidateName = str(info, "applicant_name");
|
||||
String candidateGender = str(info, "gender");
|
||||
String candidateCaste = str(info, "caste_category");
|
||||
String candidateSubCaste = str(info, "sub_caste");
|
||||
String candidateLocation = str(info, "location");
|
||||
Long electionId = toLong(info, "election_id");
|
||||
String postName = str(info, "post_name");
|
||||
Integer postMinAge = toInt(info, "min_age");
|
||||
Integer postMaxAge = toInt(info, "max_age");
|
||||
String postGenderType = str(info, "gender_type");
|
||||
Integer postMinShareHolding = toInt(info, "min_share_holding");
|
||||
// Candidate's city from m_client_address, used for area match
|
||||
String candidateCity = str(info, "candidate_city");
|
||||
|
||||
// Candidate age from DOB
|
||||
Integer candidateAge = null;
|
||||
Object dob = info.get("date_of_birth");
|
||||
if (dob instanceof java.sql.Date) {
|
||||
LocalDate dobDate = ((java.sql.Date) dob).toLocalDate();
|
||||
candidateAge = Period.between(dobDate, LocalDate.now()).getYears();
|
||||
}
|
||||
|
||||
// ── Voter pool ─────────────────────────────────────────────────────────
|
||||
long total = jdbcTemplate.queryForObject(SQL_TOTAL, Long.class);
|
||||
|
||||
// ── Gender ─────────────────────────────────────────────────────────────
|
||||
List<BreakdownItem> genderBreakdown = buildBreakdown(SQL_GENDER, total, candidateGender, true);
|
||||
if (candidateGender != null && !candidateGender.isBlank()) {
|
||||
boolean hasGenderMatch = genderBreakdown.stream().anyMatch(BreakdownItem::isMatch);
|
||||
if (!hasGenderMatch) {
|
||||
genderBreakdown.add(new BreakdownItem(candidateGender, 0, 0.0, true));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Age ────────────────────────────────────────────────────────────────
|
||||
List<BreakdownItem> ageBreakdown = buildAgeBreakdown(total, candidateAge);
|
||||
|
||||
long eligibleByAge = buildEligibleByAgeCount(postMinAge, postMaxAge, total);
|
||||
|
||||
// ── Area ───────────────────────────────────────────────────────────────
|
||||
// Match uses candidate's city from m_client_address (not free-text nomination location)
|
||||
List<BreakdownItem> areaBreakdown = buildBreakdown(SQL_AREA, total, candidateCity, false);
|
||||
long areaMatchCount = areaBreakdown.stream()
|
||||
.filter(BreakdownItem::isMatch).mapToLong(BreakdownItem::getCount).sum();
|
||||
|
||||
// ── Caste ──────────────────────────────────────────────────────────────
|
||||
List<BreakdownItem> casteBreakdown = buildBreakdown(SQL_CASTE, total, candidateCaste, true);
|
||||
// If candidate's own category has 0 shareholders it won't appear in the GROUP BY result.
|
||||
// Add an explicit zero row so the UI can show "Candidate: OBC — 0 shareholders".
|
||||
if (candidateCaste != null && !candidateCaste.isBlank()) {
|
||||
boolean hasMatch = casteBreakdown.stream().anyMatch(BreakdownItem::isMatch);
|
||||
if (!hasMatch) {
|
||||
casteBreakdown.add(new BreakdownItem(candidateCaste, 0, 0.0, true));
|
||||
}
|
||||
}
|
||||
long casteMatchCount = casteBreakdown.stream()
|
||||
.filter(BreakdownItem::isMatch).mapToLong(BreakdownItem::getCount).sum();
|
||||
|
||||
// ── Sub-caste ──────────────────────────────────────────────────────────
|
||||
List<BreakdownItem> subCasteBreakdown = buildBreakdown(SQL_SUB_CASTE, total, candidateSubCaste, true);
|
||||
if (candidateSubCaste != null && !candidateSubCaste.isBlank()) {
|
||||
boolean hasSubCasteMatch = subCasteBreakdown.stream().anyMatch(BreakdownItem::isMatch);
|
||||
if (!hasSubCasteMatch) {
|
||||
subCasteBreakdown.add(new BreakdownItem(candidateSubCaste, 0, 0.0, true));
|
||||
}
|
||||
}
|
||||
long subCasteMatchCount = subCasteBreakdown.stream()
|
||||
.filter(BreakdownItem::isMatch).mapToLong(BreakdownItem::getCount).sum();
|
||||
|
||||
// ── Religion ───────────────────────────────────────────────────────────
|
||||
List<BreakdownItem> religionBreakdown = buildBreakdown(SQL_RELIGION, total, null, false);
|
||||
|
||||
// ── Occupation ─────────────────────────────────────────────────────────
|
||||
List<BreakdownItem> occupationBreakdown = buildBreakdown(SQL_OCCUPATION, total, null, false);
|
||||
|
||||
// ── Share holding distribution ─────────────────────────────────────────
|
||||
List<BreakdownItem> shareHoldingBreakdown = buildBreakdown(SQL_SHARE_HOLDING, total, null, false);
|
||||
|
||||
// ── Member tenure ──────────────────────────────────────────────────────
|
||||
List<BreakdownItem> tenureBreakdown = buildBreakdown(SQL_TENURE, total, null, false);
|
||||
|
||||
// ── Loan engagement ────────────────────────────────────────────────────
|
||||
List<BreakdownItem> loanHolderBreakdown = buildBreakdown(SQL_LOAN, total, null, false);
|
||||
long activeLoanHolders = loanHolderBreakdown.stream()
|
||||
.filter(i -> "Active Loan Holders".equals(i.getLabel()))
|
||||
.mapToLong(BreakdownItem::getCount).sum();
|
||||
|
||||
// ── Family members ─────────────────────────────────────────────────────
|
||||
Map<String, Object> familyRow = jdbcTemplate.queryForMap(SQL_FAMILY);
|
||||
long shareholdersWithFamily = toLong(familyRow, "with_family");
|
||||
long totalFamilyMembers = toLong(familyRow, "total_family");
|
||||
|
||||
// ── Education ──────────────────────────────────────────────────────────
|
||||
List<BreakdownItem> educationBreakdown = buildBreakdown(SQL_EDUCATION, total, null, false);
|
||||
|
||||
// ── Marital Status ─────────────────────────────────────────────────────
|
||||
List<BreakdownItem> maritalBreakdown = buildBreakdown(SQL_MARITAL, total, null, false);
|
||||
|
||||
// ── Monthly Income ─────────────────────────────────────────────────────
|
||||
List<BreakdownItem> monthlyIncomeBreakdown = buildBreakdown(SQL_MONTHLY_INCOME, total, null, false);
|
||||
|
||||
// ── Savings ────────────────────────────────────────────────────────────
|
||||
List<BreakdownItem> savingsBreakdown = buildBreakdown(SQL_SAVINGS, total, "Active Savings", false);
|
||||
long activeSavingsHolders = savingsBreakdown.stream()
|
||||
.filter(i -> "Active Savings".equals(i.getLabel()))
|
||||
.mapToLong(BreakdownItem::getCount).sum();
|
||||
|
||||
// ── Cross-dimension match (caste + area) ───────────────────────────────
|
||||
long crossMatchCount = buildCrossMatchCount(candidateCaste, candidateCity);
|
||||
|
||||
// ── Actual eligible voters (all post criteria combined) ────────────────
|
||||
long actualEligibleVoters = buildActualEligibleCount(postGenderType, postMinAge, postMaxAge, postMinShareHolding);
|
||||
|
||||
return CandidateVoterAnalysisData.builder()
|
||||
.candidateId(candidateId)
|
||||
.electionId(electionId)
|
||||
.candidateName(candidateName)
|
||||
.postName(postName)
|
||||
.candidateGender(candidateGender)
|
||||
.candidateCaste(candidateCaste)
|
||||
.candidateLocation(candidateLocation)
|
||||
.candidateArea(candidateCity)
|
||||
.candidateAge(candidateAge)
|
||||
// Post criteria
|
||||
.postGenderType(postGenderType)
|
||||
.postMinAge(postMinAge)
|
||||
.postMaxAge(postMaxAge)
|
||||
.postMinShareHolding(postMinShareHolding)
|
||||
// Voter pool
|
||||
.totalActiveShareholders(total)
|
||||
.actualEligibleVoters(actualEligibleVoters)
|
||||
// Gender
|
||||
.genderBreakdown(genderBreakdown)
|
||||
// Age
|
||||
.ageBreakdown(ageBreakdown)
|
||||
.eligibleByAge(eligibleByAge)
|
||||
// Area
|
||||
.areaBreakdown(areaBreakdown)
|
||||
.areaMatchCount(areaMatchCount)
|
||||
// Caste
|
||||
.casteBreakdown(casteBreakdown)
|
||||
.casteMatchCount(casteMatchCount)
|
||||
// Sub-caste
|
||||
.candidateSubCaste(candidateSubCaste)
|
||||
.subCasteBreakdown(subCasteBreakdown)
|
||||
.subCasteMatchCount(subCasteMatchCount)
|
||||
// Religion
|
||||
.religionBreakdown(religionBreakdown)
|
||||
// Occupation
|
||||
.occupationBreakdown(occupationBreakdown)
|
||||
// Share holdings
|
||||
.shareHoldingBreakdown(shareHoldingBreakdown)
|
||||
// Tenure
|
||||
.tenureBreakdown(tenureBreakdown)
|
||||
// Loan
|
||||
.loanHolderBreakdown(loanHolderBreakdown)
|
||||
.activeLoanHolders(activeLoanHolders)
|
||||
// Family
|
||||
.shareholdersWithFamily(shareholdersWithFamily)
|
||||
.totalFamilyMembers(totalFamilyMembers)
|
||||
// Education
|
||||
.educationBreakdown(educationBreakdown)
|
||||
// Marital
|
||||
.maritalBreakdown(maritalBreakdown)
|
||||
// Monthly Income
|
||||
.monthlyIncomeBreakdown(monthlyIncomeBreakdown)
|
||||
// Savings
|
||||
.savingsBreakdown(savingsBreakdown)
|
||||
.activeSavingsHolders(activeSavingsHolders)
|
||||
// Cross-match
|
||||
.crossMatchCount(crossMatchCount)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts shareholders satisfying ALL provided post criteria simultaneously.
|
||||
* Uses dynamic SQL so absent criteria are simply skipped.
|
||||
*/
|
||||
/**
|
||||
* Returns count of shareholders within the post's age window.
|
||||
* If no age criteria → returns total (no restriction = everyone eligible).
|
||||
*/
|
||||
private long buildEligibleByAgeCount(Integer minAge, Integer maxAge, long total) {
|
||||
if (minAge == null && maxAge == null) {
|
||||
return total; // open post — no age restriction
|
||||
}
|
||||
StringBuilder sql = new StringBuilder(SQL_ELIGIBLE_AGE_BASE);
|
||||
List<Object> params = new ArrayList<>();
|
||||
if (minAge != null) {
|
||||
sql.append("AND EXTRACT(YEAR FROM AGE(CURRENT_DATE, c.date_of_birth)) >= ? ");
|
||||
params.add(minAge);
|
||||
}
|
||||
if (maxAge != null) {
|
||||
sql.append("AND EXTRACT(YEAR FROM AGE(CURRENT_DATE, c.date_of_birth)) <= ? ");
|
||||
params.add(maxAge);
|
||||
}
|
||||
Long count = jdbcTemplate.queryForObject(sql.toString(), Long.class, params.toArray());
|
||||
return count != null ? count : 0;
|
||||
}
|
||||
|
||||
private long buildActualEligibleCount(String genderType, Integer minAge, Integer maxAge, Integer minShares) {
|
||||
boolean hasGender = genderType != null && !genderType.isBlank() && !"ANY".equalsIgnoreCase(genderType);
|
||||
boolean hasMinAge = minAge != null;
|
||||
boolean hasMaxAge = maxAge != null;
|
||||
boolean hasShares = minShares != null && minShares > 0;
|
||||
|
||||
// If no criteria are defined the post is open to all shareholders
|
||||
if (!hasGender && !hasMinAge && !hasMaxAge && !hasShares) {
|
||||
Long count = jdbcTemplate.queryForObject(SQL_TOTAL, Long.class);
|
||||
return count != null ? count : 0;
|
||||
}
|
||||
|
||||
StringBuilder sql = new StringBuilder("""
|
||||
SELECT COUNT(DISTINCT sa.client_id)
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id AND c.status_enum = 300
|
||||
LEFT JOIN m_code_value gender_cv ON c.gender_cv_id = gender_cv.id
|
||||
WHERE sa.status_enum = 300
|
||||
""");
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
if (hasGender) {
|
||||
sql.append("AND UPPER(COALESCE(gender_cv.code_value, '')) = ? ");
|
||||
params.add(genderType.toUpperCase());
|
||||
}
|
||||
if (hasMinAge) {
|
||||
sql.append("AND c.date_of_birth IS NOT NULL ");
|
||||
sql.append("AND EXTRACT(YEAR FROM AGE(CURRENT_DATE, c.date_of_birth)) >= ? ");
|
||||
params.add(minAge);
|
||||
}
|
||||
if (hasMaxAge) {
|
||||
sql.append("AND c.date_of_birth IS NOT NULL ");
|
||||
sql.append("AND EXTRACT(YEAR FROM AGE(CURRENT_DATE, c.date_of_birth)) <= ? ");
|
||||
params.add(maxAge);
|
||||
}
|
||||
if (hasShares) {
|
||||
sql.append("AND COALESCE(sa.total_approved_shares, 0) >= ? ");
|
||||
params.add(minShares);
|
||||
}
|
||||
|
||||
Long count = jdbcTemplate.queryForObject(sql.toString(), Long.class, params.toArray());
|
||||
return count != null ? count : 0;
|
||||
}
|
||||
|
||||
private long buildCrossMatchCount(String caste, String city) {
|
||||
if (caste == null || caste.isBlank() || city == null || city.isBlank()) return 0;
|
||||
String sql = """
|
||||
SELECT COUNT(DISTINCT sa.client_id)
|
||||
FROM m_share_account sa
|
||||
JOIN m_client c ON sa.client_id = c.id AND c.status_enum = 300
|
||||
LEFT JOIN m_code_value caste_cv ON c.social_category_cv_id = caste_cv.id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT COALESCE(NULLIF(ma.city,''), NULLIF(ma.town_village,''), NULLIF(ma.county_district,'')) AS city
|
||||
FROM m_client_address mca
|
||||
JOIN m_address ma ON ma.id = mca.address_id
|
||||
WHERE mca.client_id = c.id AND mca.is_active = true AND mca.is_deleted = false
|
||||
ORDER BY mca.id DESC LIMIT 1
|
||||
) addr ON true
|
||||
WHERE sa.status_enum = 300
|
||||
AND UPPER(COALESCE(caste_cv.code_value, '')) = ?
|
||||
AND addr.city = ?
|
||||
""";
|
||||
Long count = jdbcTemplate.queryForObject(sql, Long.class, caste.toUpperCase(), city);
|
||||
return count != null ? count : 0;
|
||||
}
|
||||
|
||||
private List<BreakdownItem> buildBreakdown(String sql, long total, String matchLabel, boolean caseInsensitive) {
|
||||
List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql);
|
||||
List<BreakdownItem> items = new ArrayList<>();
|
||||
for (Map<String, Object> row : rows) {
|
||||
String label = str(row, "label");
|
||||
long cnt = toLong(row, "cnt");
|
||||
double pct = total > 0 ? Math.round((cnt * 1000.0 / total)) / 10.0 : 0;
|
||||
boolean match = matchLabel != null && !matchLabel.isBlank()
|
||||
&& (caseInsensitive ? matchLabel.equalsIgnoreCase(label) : matchLabel.equals(label));
|
||||
items.add(new BreakdownItem(label, cnt, pct, match));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
private List<BreakdownItem> buildAgeBreakdown(long total, Integer candidateAge) {
|
||||
List<Map<String, Object>> rows = jdbcTemplate.queryForList(SQL_AGE);
|
||||
String candidateRange = null;
|
||||
if (candidateAge != null) {
|
||||
candidateRange = candidateAge < 25 ? "Under 25"
|
||||
: candidateAge < 35 ? "25–34"
|
||||
: candidateAge < 45 ? "35–44"
|
||||
: candidateAge < 55 ? "45–54"
|
||||
: candidateAge < 65 ? "55–64"
|
||||
: "65+";
|
||||
}
|
||||
List<BreakdownItem> items = new ArrayList<>();
|
||||
for (Map<String, Object> row : rows) {
|
||||
String label = str(row, "label");
|
||||
long cnt = toLong(row, "cnt");
|
||||
double pct = total > 0 ? Math.round((cnt * 1000.0 / total)) / 10.0 : 0;
|
||||
boolean match = label.equals(candidateRange);
|
||||
items.add(new BreakdownItem(label, cnt, pct, match));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
private static String str(Map<String, Object> row, String key) {
|
||||
Object v = row.get(key);
|
||||
return v == null ? null : v.toString().trim();
|
||||
}
|
||||
|
||||
private static long toLong(Map<String, Object> row, String key) {
|
||||
Object v = row.get(key);
|
||||
if (v == null) return 0;
|
||||
if (v instanceof Number) return ((Number) v).longValue();
|
||||
try { return Long.parseLong(v.toString()); } catch (Exception e) { return 0; }
|
||||
}
|
||||
|
||||
private static Integer toInt(Map<String, Object> row, String key) {
|
||||
Object v = row.get(key);
|
||||
if (v == null) return null;
|
||||
if (v instanceof Number) return ((Number) v).intValue();
|
||||
try { return Integer.parseInt(v.toString()); } catch (Exception e) { return null; }
|
||||
}
|
||||
}
|
||||
@@ -369,4 +369,6 @@
|
||||
<include file="parts/0339_election_ceo_precheck_family_loan_check.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0340_election_candidate_symbol_text.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0341_election_audit_log.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0342_election_post_add_eligibility_columns.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0343_client_sub_caste_column.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
<?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">
|
||||
|
||||
<!-- Add gender_type if not present -->
|
||||
<changeSet id="0342_election_post_add_gender_type" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_election_post" columnName="gender_type"/></not>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_election_post">
|
||||
<column name="gender_type" type="VARCHAR(20)" defaultValue="ANY">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
<!-- Add min_age if not present -->
|
||||
<changeSet id="0342_election_post_add_min_age" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_election_post" columnName="min_age"/></not>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_election_post">
|
||||
<column name="min_age" type="INT"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
<!-- Add max_age if not present -->
|
||||
<changeSet id="0342_election_post_add_max_age" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_election_post" columnName="max_age"/></not>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_election_post">
|
||||
<column name="max_age" type="INT"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
<!-- Add min_share_holding if not present -->
|
||||
<changeSet id="0342_election_post_add_min_share_holding" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_election_post" columnName="min_share_holding"/></not>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_election_post">
|
||||
<column name="min_share_holding" type="INT" defaultValueNumeric="0"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
<!-- Add area if not present -->
|
||||
<changeSet id="0342_election_post_add_area" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_election_post" columnName="area"/></not>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_election_post">
|
||||
<column name="area" type="VARCHAR(200)"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
<?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">
|
||||
|
||||
<!-- Add SubCaste master code -->
|
||||
<changeSet id="0343-1" author="election-analytics">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_code WHERE code_name = 'SubCaste'</sqlCheck>
|
||||
</preConditions>
|
||||
<sql>INSERT INTO m_code (code_name, is_system_defined) VALUES ('SubCaste', false)</sql>
|
||||
</changeSet>
|
||||
|
||||
<!-- Add sub-caste values: General -->
|
||||
<changeSet id="0343-2" author="election-analytics">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">
|
||||
SELECT COUNT(*) FROM m_code_value cv
|
||||
JOIN m_code mc ON cv.code_id = mc.id
|
||||
WHERE mc.code_name = 'SubCaste' AND cv.code_value = 'Brahmin'
|
||||
</sqlCheck>
|
||||
</preConditions>
|
||||
<sql>
|
||||
INSERT INTO m_code_value (code_id, code_value, order_position, is_active, is_mandatory)
|
||||
SELECT mc.id, vals.code_value, vals.pos, true, false
|
||||
FROM m_code mc,
|
||||
(VALUES
|
||||
('Brahmin', 1),
|
||||
('Kshatriya / Rajput', 2),
|
||||
('Vaishya / Baniya', 3),
|
||||
('Kayastha', 4),
|
||||
('Jat', 5),
|
||||
('Sindhi', 6),
|
||||
('Other General', 7)
|
||||
) AS vals(code_value, pos)
|
||||
WHERE mc.code_name = 'SubCaste';
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
<!-- Add sub-caste values: OBC -->
|
||||
<changeSet id="0343-3" author="election-analytics">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">
|
||||
SELECT COUNT(*) FROM m_code_value cv
|
||||
JOIN m_code mc ON cv.code_id = mc.id
|
||||
WHERE mc.code_name = 'SubCaste' AND cv.code_value = 'Yadav'
|
||||
</sqlCheck>
|
||||
</preConditions>
|
||||
<sql>
|
||||
INSERT INTO m_code_value (code_id, code_value, order_position, is_active, is_mandatory)
|
||||
SELECT mc.id, vals.code_value, vals.pos, true, false
|
||||
FROM m_code mc,
|
||||
(VALUES
|
||||
('Yadav', 10),
|
||||
('Kurmi', 11),
|
||||
('Teli', 12),
|
||||
('Nai / Napit', 13),
|
||||
('Kewat / Dhimar', 14),
|
||||
('Kumhar', 15),
|
||||
('Kushwaha / Patel',16),
|
||||
('Mali', 17),
|
||||
('Lohar', 18),
|
||||
('Prajapati', 19),
|
||||
('Banjara', 20),
|
||||
('Other OBC', 21)
|
||||
) AS vals(code_value, pos)
|
||||
WHERE mc.code_name = 'SubCaste';
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
<!-- Add sub-caste values: SC -->
|
||||
<changeSet id="0343-4" author="election-analytics">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">
|
||||
SELECT COUNT(*) FROM m_code_value cv
|
||||
JOIN m_code mc ON cv.code_id = mc.id
|
||||
WHERE mc.code_name = 'SubCaste' AND cv.code_value = 'Chamar / Jatav'
|
||||
</sqlCheck>
|
||||
</preConditions>
|
||||
<sql>
|
||||
INSERT INTO m_code_value (code_id, code_value, order_position, is_active, is_mandatory)
|
||||
SELECT mc.id, vals.code_value, vals.pos, true, false
|
||||
FROM m_code mc,
|
||||
(VALUES
|
||||
('Chamar / Jatav', 30),
|
||||
('Balmiki', 31),
|
||||
('Dhobi', 32),
|
||||
('Pasi', 33),
|
||||
('Meghwal', 34),
|
||||
('Kori', 35),
|
||||
('Other SC', 36)
|
||||
) AS vals(code_value, pos)
|
||||
WHERE mc.code_name = 'SubCaste';
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
<!-- Add sub-caste values: ST -->
|
||||
<changeSet id="0343-5" author="election-analytics">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">
|
||||
SELECT COUNT(*) FROM m_code_value cv
|
||||
JOIN m_code mc ON cv.code_id = mc.id
|
||||
WHERE mc.code_name = 'SubCaste' AND cv.code_value = 'Gond'
|
||||
</sqlCheck>
|
||||
</preConditions>
|
||||
<sql>
|
||||
INSERT INTO m_code_value (code_id, code_value, order_position, is_active, is_mandatory)
|
||||
SELECT mc.id, vals.code_value, vals.pos, true, false
|
||||
FROM m_code mc,
|
||||
(VALUES
|
||||
('Gond', 40),
|
||||
('Bhil', 41),
|
||||
('Korku', 42),
|
||||
('Baiga', 43),
|
||||
('Bharia', 44),
|
||||
('Sahariya',45),
|
||||
('Other ST',46)
|
||||
) AS vals(code_value, pos)
|
||||
WHERE mc.code_name = 'SubCaste';
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
<!-- Add sub_caste_cv_id column to m_client -->
|
||||
<changeSet id="0343-6" author="election-analytics">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_client" columnName="sub_caste_cv_id"/></not>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client">
|
||||
<column name="sub_caste_cv_id" type="BIGINT">
|
||||
<constraints nullable="true"
|
||||
foreignKeyName="fk_client_sub_caste"
|
||||
references="m_code_value(id)"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user