- Add durationYears to ElectionConfig with Liquibase migration (0348)
- Block new election creation when previous election duration is still active (PostgreSQL-compatible native query)
- Auto-override director tenureEndDate from election durationEndDate on appoint and update
- Add ElectionSchedulerConfiguration to enable @Scheduled support
- Fix JPQL EclipseLink enum compatibility in expireByTenureDate query
- Add full audit logging to DirectorAppointmentServiceImpl (appoint, update, remove, auto-expire)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- 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>
- ElectionCandidateData: replace String symbol with Boolean hasSymbol + add memberId
- ElectionCandidateApiResource: symbol upload/download via ContentStoreService
(mimeType|storagePath format, backward-compatible with legacy base64 records)
- ElectionVotingApiResource: add GET /vote-summary endpoint (totalVotes, totalVoters,
per-candidate breakdown) using ElectionVoteRepository queries
- ElectionVoteRepository: add countByElectionConfigId and voteSummaryByCandidate queries
- ElectionReadPlatformServiceImpl: map hasSymbol and memberId in candidate list response
- ElectionWritePlatformServiceImpl: set photoUrl from memberId on approveCandidate,
delete old symbol file from ContentStore on removeCandidate
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PagedResponse<T>: generic paginated wrapper with statusCounts and summary fields
- ElectionConfigRepository: pageable query + countGroupByStatus
- ElectionNominationRepository: pageable queries + count-by-status helpers
- ElectionCandidateRepository: pageable queries by election and post
- ElectionReadPlatformService: all list methods return PagedResponse with page/size params
- ElectionReadPlatformServiceImpl: add missing Pageable import; paginated impls with summary data
- API resources: @QueryParam page/size with @DefaultValue on all 3 list endpoints
- ElectionCeoPrecheck: add familyLoanCheckResult + familyLoanCheckRemarks (7th auto-check)
- ElectionCeoPreCheckServiceImpl: fix directorLoan check (overdue only); add family loan check covering manual members (unverifiable→FAIL) and linked CBS clients (overdue→FAIL)
- Liquibase 0338/0339: extended CEO pre-check columns + family loan check columns
- changelog-tenant.xml: include both new changesets
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The 5 missing permissions were added to the already-executed changeset
causing checksum mismatch on restart. Move them to a new changeset
0337_seed_election_permissions_v2 with a preCondition guard.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add 5 permissions that the write service enforces but were absent from
the DB seed: UPDATE_ELECTION, ELIGIBILITY_CHECK_ELECTION,
CEO_PRECHECK_ELECTION, APPROVE_ELECTION_NOMINATION,
APPROVE_ELECTION_CANDIDATE — without these, role assignments for those
operations would fail at runtime.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace Fineract cash deposit API with direct INSERT for both savings and current accounts
→ Zero CashVault entries created anywhere in DB for disbursements
- Savings: directCreditSavingsAccount now sets running_balance_derived and updates
total_deposits_derived alongside account_balance_derived (parity with current account)
- Current: directCreditCurrentAccount — direct INSERT into m_current_account_transaction
with running_balance_derived, total_deposits_derived update, and remarks audit field
- getCurrentControlGlId: looks up SAVINGS_CONTROL GL for current account products
via acc_product_mapping (product_type=2, financial_account_type=2)
- directReverseCurrentCredit: compensation restores both account_balance_derived
and total_deposits_derived on rollback
- GL: combined DR sourceGL / CR SavingsControl entry posted for both savings and
current account credits; linkGlEntriesToSavingsAccounts sets entity_type_enum=2
so ACCOUNT NO column shows in journal entries view
- Hide reversed=true GL entries from all GL views in JournalEntryReadPlatformServiceImpl
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use parseTxnIdToLong() for voucherService.linkSavingsTransaction() to
handle hex GL txnId (e.g. "a2070e542bab") without NumberFormatException
- Add GL deposit maker-checker: submit stores gl_account_id, contra_gl_id,
office_id, currency_code; approve routes to executeGlJournalEntry()
- Save denomination at submit time (cash physically received); update
txn_id to real Fineract ID on approve, delete on reject
- Add Liquibase migration 0331 to add GL columns to m_cashier_deposit_request
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- VoucherService: central voucher register with globally unique voucher numbers
- CashierDepositRequestApiResource: full submit/approve/reject/list flow
- Cashier submits deposit request; balance credited only on manager approval
- Self-approval blocked (maker cannot be checker)
- Role guard: only manager/head-cashier/ALL_FUNCTIONS can approve/reject
- Race condition guard: WHERE status='PENDING' on approve/reject UPDATE
- Clean JSON error responses (no 500 stack traces) via apiError() helper
- Denomination total validation at submit and approve
- ClerkWithdrawalRequestApiResource: refactored to use m_voucher
- Voucher created at submit, linked to savings txn at approve
- Same role guard and self-approval block as deposit flow
- Disburse: denomination validation, null denoms for non-cash payments
- Migrations 0327-0330:
- 0327: add voucher_no to m_clerk_withdrawal_request
- 0328: add voucher_no to m_savings_account_transaction
- 0329: create m_voucher central table (globally unique voucher_no)
- 0330: create m_cashier_deposit_request table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add CashierGlDenominationApiResource: new POST /v1/cashier/gl-denomination endpoint
to record denomination breakdown for GL journal entries in m_savings_txn_denomination
- Fix GL account real-time balance in GLAccountReadPlatformServiceImpl using live SUM query
- Add Liquibase migrations 0318/0319/0320: make savings_account_id and txn_id nullable,
add payment_type column to m_savings_txn_denomination
- Add validCheckSum to 0317b changeset to resolve checksum mismatch on existing installs
- Enhance GL entries closing report SQL with date, time, teller, payment type, txn source
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix searchParam SQL syntax: parameterized LIKE with %value% instead of literal %?%
- Fix organizationRunningBalance: correlated subquery for latest journal entry (ORDER BY id DESC LIMIT 1)
- Migration 0317: grant READ_GLACCOUNT, READ_LOAN, READ_SHAREACCOUNT to CASHIER role
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CurrentAccountTransactionsApiResource:
- Fixed: search endpoint now queries m_current_account_transaction
instead of m_savings_account_transaction (wrong table)
- Added remarks column to search query (column exists in CA table)
- Charge Deduction (type=7) mapped in transactionType value
- Removed sat.remarks that caused 500 (column doesn't exist in savings table)
CurrentAccountTransactionWritePlatformServiceImpl:
- Added min required balance validation in withdrawal()
- Throws GeneralPlatformDomainRuleException when balance after withdrawal
would fall below minRequiredBalance (if enforceMinRequiredBalance=true)
StandingInstructionReadPlatformServiceImpl:
- Fixed SQL WHERE clause: fromAccountType + fromAccount condition
now correctly adds WHERE when fromAccountId is set
Liquibase 0315:
- Fix standing instruction permission code (trim trailing space on id=449)
- Grant READ permissions to CASHIER role for transaction entry screen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add CashierRoleHelper service: detects CASHIER role via DB permission
check, resolves cashier ID from staff/user linkage
- Role-based filtering on GET list endpoints (cashtransfer, cashsettlement,
cashintercashier, cashexchange, cashreports): CASHIER sees only own data,
ADMIN/HEAD sees all
- Pagination (offset/limit) added to all cashier list APIs returning
standard Fineract Page format {totalFilteredRecords, pageItems}
- ShareCollectionApiResource: collect cash before account activation,
post GL entries (Dr Cash Vault / Cr Share Sundry / Cr Income from Fees),
mark charges paid to prevent duplicate GL on activation,
GET status endpoint for collection check
- Migration 0315: grant READ_CLIENTKYC, READ_CLIENTNOMINEE, READ_ADDRESS,
READ_FAMILYMEMBERS, READ_SAVINGSACCOUNT to CASHIER role
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DenominationBreakdownWriter: add ORDER BY mc.id DESC LIMIT 1 to all cashier
lookup queries so multiple cashier records per staff do not crash queryForObject
- CashReportApiResource: same LIMIT 1 fix for cashier-day-status and position endpoints
- 0311: fix m_client and m_savings_account NOT NULL columns (created_by, last_modified_by,
created_on_utc, last_modified_on_utc, cif_number, is_lien_allowed, etc.)
- 0312: create CASHIER role if missing + grant all 34 cashier permissions (incl FD/RD)
- 0313: add missing m_cash_exchange columns (cashier_id, is_reversed, reversed_by,
reversed_at, reverse_reason, customer_id_proof)
- 0314: unique constraint on m_cashiers.staff_id to prevent duplicate allocations
- application.properties: restore local postgres password after merge
- ExecuteOverdueAndCurrentStandingInstructionsTest: fix constructor call (5 args)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(fundtransfer+imps+mfa): fund transfer module, IMPS switch client, MFA log API
- Fund transfer: NEFT/RTGS/IMPS/internal strategies, limit resolution, bulk
transfer, verification tokens, suspense GL, recall/approval flow, jobs
- IMPS CBS switch client with crypto (JWT/envelope) and transaction domain
- MFA log read API (audit of MFA events)
- Liquibase migrations 0300-0311 for fund transfer / IMPS tables, permissions,
roles, and suspense account
- JobName, SecurityConfig, dependencies.gradle wiring
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
- DenominationBreakdownWriter helper: validates breakdown total == amount and
persists per-denomination rows; wired into current, FD and RD deposit flows
(savings already had inline save) so all 4 deposit types now record the
cash denomination breakdown.
- CIF on denomination rows now sourced from the client (getCifNumber) instead
of the account external id.
- Allow denominationBreakdown param in the FD/RD transaction validator.
- Cash Exchange backend: m_cash_exchange + m_cash_exchange_detail tables,
entities, repositories and JAX-RS API with received==given validation;
registered the 0013 migration in changelog-tenant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- m_denomination_master CRUD API with duplicate (denomination+kind)
validation, soft delete + restore, and a full audit trail
(m_denomination_master_audit logs CREATE/UPDATE/ACTIVATE/DEACTIVATE/
DELETE/RESTORE with snapshot, changes, user and timestamp)
- m_savings_txn_denomination: persist per-transaction cash denomination
breakdown on savings deposit; allow denominationBreakdown request param
- Liquibase 0012_denomination_master registered in tenant changelog
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- StandingInstructionData: rename conflicting Lombok getters (getInstructionType,
getRecurrenceType, getRecurrenceFrequency, getFromAccountType, getToAccountType,
getTransferType) to domain enum methods so Jackson serializes EnumOptionData fields
as {id, code, value} objects instead of plain strings
- AccountTransfersWritePlatformServiceImpl: add current account transfer support for
all 4 missing cases (Current->Savings, Savings->Current, Current->Current,
Current->Loan) with performCurrentWithdrawal/performCurrentDeposit helpers
- AccountConfiguration: wire CurrentAccountRepository and CurrentAccountTransactionRepository
into AccountTransfersWritePlatformService bean
- StandingInstructionApiResource: fix getValue() type mismatch after getter rename
- ExecuteStandingInstructionsTasklet: update to use renamed enum accessor methods
- PortfolioAccountType: add CURRENT(3) type support
- DB migration: 0301_add_current_account_to_transfer_details.xml adds
from_current_account_id and to_current_account_id columns to transfer details table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ClientFamilyMembersWritePlatformServiceImpl#updateFamilyMember: guard
maritalStatusId/genderId/professionId with != null instead of != 0 so a
payload that omits these keys doesn't NPE when the boxed Long is unboxed.
- application.properties: fix the broken property name
`fineract.security.cors.allowed-metho pas` -> `allowed-methods`. The typo
left CORS allowed-methods empty, which made Spring reject every browser
POST with "Invalid CORS request" 403 once the SPA sent an Origin header.
- application.properties: change the Hikari default password from 313863
to root so the local devRun matches the tenant-DB default at line 49.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ClientFamilyMembersWritePlatformServiceImpl#updateFamilyMember: guard
maritalStatusId/genderId/professionId with != null instead of != 0 so a
payload that omits these keys doesn't NPE when the boxed Long is unboxed.
- application.properties: fix the broken property name
`fineract.security.cors.allowed-metho pas` -> `allowed-methods`. The typo
left CORS allowed-methods empty, which made Spring reject every browser
POST with "Invalid CORS request" 403 once the SPA sent an Origin header.
- application.properties: change the Hikari default password from 313863
to root so the local devRun matches the tenant-DB default at line 49.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Apply senior citizen rate override in FixedDepositAccount and RecurringDepositAccount
getEffectiveInterestRateAsFraction() so maturity is calculated at correct rate (e.g. 7.5%)
instead of chart slab rate on every event: submit, activate, posting, pre-closure, maturity
- Override getEffectiveInterestRateAsFractionAccrual() in both FD/RD to keep accrual
consistent with maturity calculation — independent of DB nominalAnnualInterestRate value
- Add isSeniorCitizen and seniorCitizenInterestRate fields to SavingsAccount entity with
JPA mapping and accessor methods; updateNominalAnnualInterestRate() helper added
- Extend DepositsApiConstants whitelist and SavingsAccountAssembler.applyCbsFields()
to read and persist senior citizen fields from API request as final override step
- Add DepositAccountAssembler early-stage rate override for senior citizen accounts
- Liquibase migration 2015: add is_senior_citizen and senior_citizen_interest_rate columns
to m_savings_account table
- Liquibase migration 0300: rename fd_base_interest_rate tables to CBS naming convention
- Update CBS copyright header in FdBaseInterestRateEffective and FdBaseInterestRateSlabHistory
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pre-save validator (ClientWritePlatformServiceJpaRepositoryImpl.validateClientUniqueness)
rejects duplicate mobile / email / PAN / Aadhaar (masked) / GSTIN before saveAndFlush
and before the LincPay KYC API call. Avoids burning vendor credits on identity-duplicate
records and gives the UI a clean field-level 403 instead of waiting for the DB constraint.
Splits the new repository finders into "create" and "excluding-self" variants so a
nullable Long parameter never reaches PostgreSQL's parser (same bigint <> varchar trap
that broke the family-member POST earlier).
Also seeds the full set of 28 Indian States + 8 UTs as STATE code_values (migration
0297). Previously only MAHARASHTRA / MADHYA_PRADESH / DELHI existed and the names were
UPPER_SNAKE_CASE; the postal PIN-lookup service returns "Madhya Pradesh" so any state
other than Maharashtra/Delhi silently failed to resolve to m_address.state_province_id.
Renames the 3 existing rows in place (preserves their ids/FKs) and inserts the missing
33 with idempotent preConditions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- DSC-as-2FA login: UI's claimAndValidate() now calls a new
AuthenticationService.applyTwoFactorToken(token, liveSeconds) instead of
validateOTP(). The /v1/login/dsc/{rn}/claim response is already a minted
TFAccessToken — POSTing it to /twofactor/validate fails because that path
expects an in-memory OTPRequest. applyTwoFactorToken runs only the
post-success bookkeeping (interceptor.setTwoFactorAccessToken, setCredentials,
Authentication Success alert that drives router.navigate(['/']), and
sessionStorage persist of {token, validFrom, validTo}). Fixes the
OTPTokenInvalidException users hit after a successful DSC verify+claim.
- Individual-only filter for link-client pickers: ClientTextSearch gains a
legalFormType field; SearchingClientRepository accepts an Integer legalForm
value and adds cb.equal(r.get("legalForm"), ...) to the spec.
ClientSearchService.parseLegalForm tolerates INDIVIDUAL/PERSON/1 and
NON_INDIVIDUAL/ENTITY/2; unknown = no filter so a typo doesn't blank results.
UI: clients.service.ts searchByText gains a 7th optional arg; six link
pickers (guardian, nominee, family-member across create flow and post-create
tabs) now pass 'INDIVIDUAL'. General client list and signatory picker are
intentionally unchanged.
- Title-case client names on persistence: ClientWritePlatformServiceJpa
RepositoryImpl now runs first/middle/last/full name fields through
toTitleCase() on both create and update paths. "aditya kumar vyas",
"ADITYA KUMAR VYAS", "Aditya Kumar Vyas" all persist as "Aditya Kumar Vyas".
Whitespace-split title case; preserves hyphens/apostrophes inside words
without re-capitalising after them so clerks verifying against ID documents
see predictable output.
- Migration 0296: unique constraint to prevent duplicate nominee per account
type (the underlying repository + service changes also persisted in this
commit complete the enforcement path).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend:
- m_bank_user_dsc + m_dsc_challenge tables (Liquibase 0292/0293/0294)
- DSC enrollment flow: start → JNLP → JWS helper → callback → upsert
- DSC login factor: challenge → sign → verify → claim minted TFA token
- 2FA gate now also accepts an active DSC mapping (AuthenticationApiResource
+ TwoFactorAuthenticationFilter), defensively wrapped so a missing DSC table
doesn't 500 the auth path
- TenantAwareBasicAuthenticationFilter exempts /api/v*/dsc/helper.jar and
strips Authorization header for that path so JWS's raw GET works without
the user seeing a browser Basic-auth prompt
- DSC helper JAR served at /api/v*/dsc/helper.jar from a configurable
filesystem path (default ~/.cbsnexora/dsc-helper.jar)
Java helper JAR (dsc-helper/):
- Standalone Gradle subproject, Java 8 target, fat jar with Gson shaded in
- Cross-platform token picker (Windows Cert Store / ePass2003 / eToken /
ProxKey / Other) — matches the JNLP arg contract emitted by the backend
- Sets Fineract-Platform-TenantId on its callback POST from the JNLP args
- JNLP age check now reads cbsnexora-dsc.jnlp (current name) AND
SignTender.jnlp (legacy) — picks the youngest, never blocks if none exist
Inline KYC upsert in updateClient (mirror of the create-flow path):
- ClientKycWritePlatformService.upsertKycInline() — creates or merges KYC
fields, bumps status to IN_PROGRESS on real changes; resets PAN verification
on PAN edit of a verified record
- ClientWritePlatformServiceJpaRepositoryImpl.updateClient() handles the
kyc:{} sub-object on PUT /clients/{id}, matching createClient
User uniqueness:
- m_appuser.email + m_appuser.mobile_no UNIQUE (Liquibase 0295)
- Pre-flight assertEmailAndMobileUnique() in create/update with self-exclusion
- handleDataIntegrityIssues maps the two new constraints to clean
field-scoped error messages (race-condition safety net)
Email OTP subject line now: "Your OTP for Login - <AppName> (dd-MM-yy-HH:mm)"
where AppName comes from fineract.email.from-name (default "CBSnexora").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Login 2FA via @Primary MfaFactorBackedTwoFactorServiceImpl (replaces legacy)
- LincPay SMS OTP and SMTP email OTP with auto-promote to m_user_mfa_factor
- Reusable EmailSenderService + EmailTemplateService (CBSnexora branding)
- Admin OTP audit viewer (GET /v1/otp-logs) with paging, search, masking
- Self-service MFA factor API at /v1/mfa/factors
- READ_OTP_LOG permission + OTP Log Auditor role + bankadmin direct grant
- JsonAuthenticationEntryPoint suppresses Basic-auth popup for SPA paths
- TwoFactorAuthenticationFilter grants TWOFACTOR_AUTHENTICATED when no challenge
- Soft-delete + audit columns + deletion audit + version on client tables
- CIF number generator (TTYYYYNNNNNN format)
- DocumentNotFoundExceptionMapper to silence noisy WARN logs on missing images
- Liquibase migrations 0278-0291 (MFA, OTP log, audit/soft-delete, role wiring)
Note: application.properties intentionally not committed — contains local DB
and Gmail App Password fallbacks. Set SPRING_MAIL_PASSWORD,
FINERACT_DEFAULT_TENANTDB_PWD, FINERACT_HIKARI_PASSWORD as env vars in prod.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>