Compare commits

...
503 Commits
Author SHA1 Message Date
Jaanu423andClaude Sonnet 4.6 ea2dfd789d feat: senior citizen interest rate fix for FD/RD accounts + CBS table renames
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
- 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>
2026-05-21 20:57:17 +05:30
Jaanu423andClaude Sonnet 4.6 9f627bde61 feat: accounts list — active-only filter, date range & as-on-date search (backend)
- SearchParameters: added fromDate, toDate, asOnDate (LocalDate) fields with hasX() helpers
- SavingsAccountsApiResource: added status, fromDate, toDate, asOnDate query params
- SavingsAccountReadPlatformServiceImpl: resolveStatusEnum(), date filters on activatedon_date, default ORDER BY id DESC
- CurrentAccountsApiResource + service: same filters on ca.status_enum and ca.activatedon_date
- DepositAccountReadPlatformService (interface): new retrieveAllPaged overload with date/status params
- DepositAccountReadPlatformServiceImpl: resolveStatusEnum(), date filters, default ORDER BY id DESC for FD/RD
- FixedDepositAccountsApiResource + RecurringDepositAccountsApiResource: pass new params to service

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 13:56:31 +05:30
Jaanu423andClaude Sonnet 4.6 ce0fb18eeb feat: implement full current account transaction write service
- New CurrentAccountTransactionWritePlatformService interface with deposit,
  withdrawal, holdAmount, releaseAmount, undoTransaction, reverseTransaction,
  and adjustTransaction operations
- New CurrentAccountTransactionWritePlatformServiceImpl using m_current_account
  tables directly (not m_savings_account), with GL journal entry posting
- Add holdAmount, releaseAmount, reversal factory methods to CurrentAccountTransaction
- Wire all 7 current account transaction command handlers to new write service
- Fix CurrentAccountTransactionsApiResource template SQL column: currency_multiplesof
- Fix account number search in SearchParameters

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:20:30 +05:30
Jaanu423andClaude Sonnet 4.6 b25d5a30d2 feat: FD/RD GL transaction prefixes + savingsAccountNo in journal entry API
- Use FD/RD transaction ID prefixes for Fixed/Recurring Deposit GL entries
  via ThreadLocal context set in Cash/AccrualBasedAccountingProcessorForSavings
- Add depositAccountType to SavingsDTO and pass from SavingsAccount bridge data
- Add savingsAccountNo field to JournalEntryData; populate via LEFT JOIN on
  m_savings_account (SB/FD/RD) and m_current_account (CA prefix) in read query
- Fix JournalEntryMapper MapStruct: ignore savingsAccountNo (SQL-populated)
- Fix JournalEntryRunningBalanceUpdateServiceImpl constructor call (27 params)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 18:26:15 +05:30
Aditya be26ba16c9 Merge remote-tracking branch 'self-hosted/feature/account-charges' into feature/Backend-DSC-MFA
# Conflicts:
#	fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
2026-05-20 13:26:58 +05:30
AdityaandClaude Opus 4.7 8b4351013e Client uniqueness pre-save check + seed all Indian states & UTs
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>
2026-05-20 12:31:54 +05:30
Nitesh YadavandClaude Sonnet 4.6 cc0ccbb461 feat: multi-nominee support, duplicate validation, and FD/RD required fields
Account Creation Enhancements:
- Added multi-nominee support for SB, CA, FD, RD accounts (nominees[] array)
- Backend: saveNomineesIfPresent() validates total share <= 100% before saving
- Added 'nominees' to parameter whitelists (SavingsAccountConstant, DepositsApiConstants)

Duplicate Validation Fixes:
- SB/FD/RD: Fixed duplicate externalId error handler to walk full exception cause
  chain (EclipseLink wraps PSQLException deep) — now matches constraint
  'm_savings_account_external_id_key' and returns proper 400 error
- CA: Added try-catch around both saveAndFlush calls for duplicate externalId
  (constraint: ca_unq_external_id) with throwIfDuplicateExternalId() helper
- CA: Added duplicate product check — blocks creating same product twice for client
- SB/FD/RD: Added JpaSystemException handling via getAllCauseMessages() helper

Current Account (CA) Improvements:
- CurrentAccount entity: added @UniqueConstraint for external_id (ca_unq_external_id)
- CurrentAccountRepository: added countActiveByClientAndProduct() JPQL query
- Liquibase 0298: unique constraint on m_current_account.external_id

FD/RD Required Field Validation (Backend):
- DepositAccountDataValidator: depositPeriod now required for RD (was FD-only)
- DepositAccountDataValidator: mandatoryRecommendedDepositAmount now notNull+positive for RD
- DepositAccountDataValidator: customerSegment now required (notBlank) for both FD and RD

Liquibase Migrations:
- 0297: fix unique nominee per account constraint
- 0298: unique external_id on m_current_account
- 2014: add is_mandatory column to m_savings_account_joint_holders

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 08:00:00 +05:30
Nitesh Yadav 9d67afec3e merege code feature/Backend-MFA-Product 2026-05-19 10:50:25 +05:30
Nitesh Yadav f93a08c6b3 updade code based on the accounting deatils search 2026-05-19 10:34:52 +05:30
AdityaandClaude Opus 4.7 026e5fb875 Fix family-member POST: null JPQL param caused bigint <> varchar SQL error
The relationship-uniqueness query used (:excludeId IS NULL OR fm.id <> :excludeId).
On create, excludeId is null and PostgreSQL parses the parameter as varchar before
the IS NULL branch can short-circuit, so adding a family member returned 403 with
"operator does not exist: bigint <> character varying". Split into two queries so
neither has a nullable Long parameter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:40:14 +05:30
Nitesh Yadav 390bded7ef rd and fd account active code update 2026-05-18 15:16:25 +05:30
Nitesh Yadav d0c8be4ee9 charges add module add in the saving account and current account 2026-05-16 21:20:30 +05:30
Aditya 28fa6d9615 Merge feature/Backend-DSC-MFA into feature/merge_update_code
Brings TOTP / Google Authenticator MFA factor (device mapping at
/v1/mfa/totp/setup + /verify, login via existing twofactor pipeline),
DSC-2FA login UI fix, Individual-only filter on guardian/nominee/family
link pickers, Title Case normalization on client names, and migration
0296 unique nominee per account type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

# Conflicts:
#	.claude/settings.local.json
#	fineract-provider/logs/fineract.log
2026-05-15 17:54:49 +05:30
AdityaandClaude Opus 4.7 45a041b023 Add Google Authenticator (TOTP) as MFA factor: device mapping + login
Backend:
- New dependency dev.samstevens.totp:totp:1.7.1 (bundles ZXing for QR PNG).
- TotpService: secret/QR/otpauth-URL/verify primitives. SHA1, 6 digits, 30s —
  the standard combo every consumer authenticator app accepts unconfigured.
  Issuer label is configurable via FINERACT_MFA_TOTP_ISSUER, defaults to
  "CBSnexora".
- Two-step device mapping driven by TotpEnrollmentService:
  * POST /v1/mfa/totp/setup  — generate secret, upsert m_user_mfa_factor row
    as factor_type=TOTP/verified=false, return {secret, qrDataUri, otpAuthUrl,
    issuer, factorId}. Refuses to overwrite an already-verified-and-active
    factor so an accidental click can't unbind the user's working device.
  * POST /v1/mfa/totp/verify — body {code}; verify against the stored secret
    and flip verified=true on success.
- Login wiring in MfaFactorBackedTwoFactorServiceImpl:
  * getDeliveryMethodsForUser surfaces verified TOTP factors as
    {name:"totp", target:"Google Authenticator"} so the picker offers it
    alongside SMS/EMAIL.
  * createNewOTPToken("totp") creates a no-op OTPRequest (no LincPay/SMTP
    round-trip) carrying the factor id in the token field so validate can
    look up the secret.
  * createAccessTokenFromOTP branches on channel — TOTP path verifies code
    vs factor.secret with totp library (sliding 1-step window) and mints
    a TFAccessToken with the same lifetime + extended-token logic as OTP.
  * Defence-in-depth: re-checks at validate time that the factor still
    belongs to the user, is verified, and not disabled.
- TwoFactorConstants.TOTP_DELIVERY_METHOD_NAME = "totp".
- SecurityConfig: /api/*/mfa/totp/** is fullyAuthenticated() (exempt from
  the TFA gate so users can enrol before they can satisfy 2FA).

UI changes pending (see chat): add a "Google Authenticator" tile on the
MFA Factors settings page that calls /setup → shows QR + secret → calls
/verify; extend two-factor-authentication.component.ts iconFor() and copy
for the new "totp" delivery method. Existing requestOTP / validateOTP
pipeline handles the login flow transparently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 17:41:55 +05:30
Nitesh Yadav 4eeee03f24 fixed error nominee add error 2026-05-15 16:09:13 +05:30
Nitesh Yadav 001a79e35e merge code feature/Backend-DSC-MFA this branch date 15-05-2026 2026-05-15 13:08:15 +05:30
Nitesh Yadav 7e0fe99ea6 nominee facility added code update 2026-05-15 12:59:57 +05:30
AdityaandClaude Opus 4.7 6558ea6998 DSC-2FA login fix + Individual-only link pickers + Title Case names
- 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>
2026-05-15 11:50:04 +05:30
AdityaandClaude Opus 4.7 e1d7db9b7c DSC as first-class MFA factor + branded helper UI
- m_user_mfa_factor is now the single 2FA gate. DSC enroll callback writes a
  factor row (factor_type=DSC), revoke flips disabled_on when the last cert
  goes. AuthenticationApiResource and TwoFactorAuthenticationFilter drop the
  direct m_bank_user_dsc check so disabling from the UI actually stops the
  challenge at login.
- SecurityConfig exempts /api/*/dsc/** and /api/*/mfa/factors from the
  TWOFACTOR_AUTHENTICATED gate. Without this a user with a verified factor
  can't manage their own factors after they break the device.
- MfaFactorType gains a DSC enum value; SelfUserMfaFactorServiceImpl error
  message lists it.
- dsc-helper: CBSnexora-branded header (logo, title, subtitle, accent palette),
  8-column cert table (Holder Name, Alias, Organisation, DSC Type, Serial,
  Issuer, Valid From, Expires), LdapName-based DN parsing, sign+verify proof-
  of-possession that picks SHA256withRSA / SHA256withECDSA from the key's own
  algorithm (works on RSA, RSA Signing-only, EC), pattern-matched JNLP age
  check, dscType JNLP-arg override with KeyUsage fallback.
- generate-test-dsc.ps1 produces a realistic self-signed DSC with strong key
  protection so dev testing sees real CN/O values and a PIN prompt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:05:12 +05:30
Nitesh Yadav 167079c2d7 merege code feature/Backend-DSC-MFA branch 2026-05-12 12:48:43 +05:30
Nitesh Yadav 30bbd6d0af fixed error db migration and rd product edit error fixed 2026-05-12 11:32:13 +05:30
LincPay ac9eb24e43 Update fineract-provider/src/main/resources/application.properties 2026-05-12 11:20:34 +05:30
AdityaandClaude Opus 4.7 8284851f67 Add DSC-based MFA: enrollment, login factor, JWS helper, tenant-scoped serving
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>
2026-05-12 11:13:19 +05:30
Nitesh Yadav 6544ad3cbc update application properties file 2026-05-11 12:47:02 +05:30
LincPay a8aa941ed5 Update fineract-provider/src/main/resources/application.properties 2026-05-11 12:30:53 +05:30
Nitesh Yadav 6b65bdc8ce merge feature/update_code and feature/Backend-MFA-Product branch code 2026-05-11 10:44:57 +05:30
AdityaandClaude Opus 4.7 5e3212dc54 Add MFA backend: SMS/email OTP, factor management, OTP audit log
- 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>
2026-05-10 13:19:10 +05:30
Nitesh Yadav fbc6fcdcc1 rd and fd product and account code update 2026-05-08 15:55:22 +05:30
Nitesh Yadav 44878377d8 rd account and rd product code update 2026-05-08 13:09:07 +05:30
Nitesh Yadav f1535a40c8 fd account complete mddule 2026-05-07 17:19:34 +05:30
Nitesh Yadav 407d567175 update fd account 2026-05-07 10:56:29 +05:30
Nitesh Yadav 4735111a4f fd product releated update code 2026-05-05 15:18:35 +05:30
Nitesh Yadav 0bbf2faa3e fixed some error releated product code 2026-05-01 11:33:07 +05:30
Nitesh Yadav a5fc407cd5 feat: merge feature/merge-updates into feature/reports-products 2026-05-01 10:51:55 +05:30
Nitesh Yadav be220d8778 rd account save api update 2026-04-30 16:12:17 +05:30
Nitesh YadavandClaude Sonnet 4.6 045437fcb8 rd account: add CBS fields to validator whitelist, make mandatoryRecommendedDepositAmount optional
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 13:51:19 +05:30
Aditya c51a36b4e9 Merge remote-tracking branch 'self-hosted/feature/merge-updates' into feature/reports-products
# Conflicts:
#	fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
2026-04-29 19:23:21 +05:30
Nitesh Yadav 766f838c32 current account save api update 2026-04-29 18:09:25 +05:30
Nitesh Yadav 15fa25f463 current account save api update 2026-04-29 18:07:37 +05:30
Nitesh Yadav 2a9bd85ef2 current account saving account ,rd account code udpate 2026-04-29 15:30:36 +05:30
Nitesh Yadav a7965f8792 current account save api update 2026-04-29 15:18:45 +05:30
AdityaandClaude Opus 4.6 43b1e1da33 Add CKYC Reports & Dashboard API module
- GET /ckyc/reports/summary — KPI cards (total, gender split, missing PAN/Aadhaar, dup mobile)
- GET /ckyc/reports/state-distribution — donut chart by state
- GET /ckyc/reports/branch-distribution — top-N branches bar chart
- GET /ckyc/reports/upload-trend — daily upload line chart
- GET /ckyc/customers — paginated, filterable customer list
- GET /ckyc/customers/search — search by name/mobile/pan/cif (capped 50)
- GET /ckyc/customers/{cif} — customer detail with addresses
- GET /ckyc/customers/export — CSV/TXT (pipe-delimited CERSAI format)
- POST /ckyc/files — parse inbound CKYC .txt file
- GET /ckyc/files — list historical uploads
- POST /ckyc/files/generate — generate outbound submission file
- GET /ckyc/branches, /states, /codes/id-types — master data
- Migration 0259: performance indexes on m_client_kyc and m_client
- Postman collection with all endpoints

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 16:25:41 +05:30
Nitesh Yadav cbdcee690d saving account save api update 2026-04-28 14:59:55 +05:30
Nitesh Yadav dc4a720ae2 feat: update savings product backend changes 2026-04-28 13:12:27 +05:30
AdityaandClaude Opus 4.6 9fb43ad971 Add Education Level, Degree, Religion, Social Category & Income fields to Client module
- Migration 0256: EducationLevel & DegreeQualification codes with m_client FK columns
- Migration 0257: Religion & SocialCategory codes with m_client FK columns
- Migration 0258: agriculture_income & non_agriculture_income decimal columns on m_client
- Full API support: create, update, template options, and GET response for all new fields
- Postman collection for testing all new dropdowns

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 16:31:55 +05:30
Nitesh YadavandClaude Sonnet 4.6 72adbf6305 add accountTypeOptions and customerSegmentOptions to savings/current/RD account templates
- Added accountTypeOptions and customerSegmentOptions fields to SavingsAccountData and RecurringDepositAccountData
- Added CodeValueReadPlatformService to SavingsAccountTemplateReadPlatformServiceImpl, CurrentAccountTemplateReadPlatformServiceImpl, and DepositAccountReadPlatformServiceImpl to fetch options from code values
- Added options to SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS and RD response parameters
- Updated SavingsConfiguration bean factory methods to pass new CodeValueReadPlatformService dependency
- Added validCheckSum for modified Liquibase changeset 0272
- Fixed AccountNumberGeneratorTest constructor to include GLAccountRepository and SavingsProductRepository

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 15:39:58 +05:30
Nitesh Yadav 53ba465160 update code current account ,saving account and rd account 2026-04-27 11:12:33 +05:30
Nitesh Yadav 6ffcfff48e some changes only 2026-04-27 10:57:00 +05:30
Nitesh Yadav 159633f59a add gl mapping in the saving product,current product,rd product and also add genrate account no all three 2026-04-26 02:02:56 +05:30
Nitesh Yadav efb0511725 update account code current,saving and RD 2026-04-24 16:14:13 +05:30
Nitesh Yadav 0303c07ac4 saving , current and RD account code update 2026-04-23 11:20:35 +05:30
Nitesh Yadav 691a3fc471 saving , current and RD account code update 2026-04-22 18:15:46 +05:30
Nitesh Yadav 05c5adb034 update code saving product add on client 2026-04-21 17:30:57 +05:30
Nitesh Yadav 98dd941712 some update logic update on showing option 2026-04-21 13:19:19 +05:30
Nitesh Yadav 47af139443 merging code 2026-04-20 15:26:56 +05:30
Nitesh Yadav 304f694387 merging code 2026-04-20 14:51:57 +05:30
Nitesh Yadav 800e114a4d update RD Prodcut code 2026-04-20 12:51:35 +05:30
AdityaandClaude Opus 4.6 b4429d04b0 Add Client CKYC backend module with all source, migrations, and static resources
Includes CKYC batch processing, KYC proof types, non-CKYC client APIs,
ReKYC handler, SFTP upload service, and DB migrations (0250-0255).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:42:21 +05:30
Nitesh Yadav 2facd3744d update current product code and RD code 2026-04-15 20:34:12 +05:30
Nitesh Yadav d7821c7824 update saving product code 2026-04-13 13:26:33 +05:30
AdityaandClaude Opus 4.6 011239f399 Fix KYC update on verified records and client address duplication
- Allow KYC updates on VERIFIED records with mandatory remarks for audit trail;
  reset PAN verification when PAN number changes, moving status to IN_PROGRESS
- Fix client address duplicates: deactivate old addresses of same type on
  create/update, persist isActive flag (missing save), filter reads by is_active

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:00:37 +05:30
AdityaandClaude Opus 4.6 87e132f93d Add extended fields to ClientNonPersonDetails: authorizedSignatory, additionalInfo, dateOfRegistration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 15:06:45 +05:30
AdityaandClaude Opus 4.6 90c21d40c1 Add CIF number, Occupation Category & Occupation fields to Client module
- Added mandatory unique CIF number (auto-generated as CIF+padded ID)
- Added Occupation Category dropdown (6 categories: Salaried, Business, Professional, Agriculture, Student, Others)
- Added Occupation dropdown (54 occupations mapped to categories)
- Both stored in m_code/m_code_value tables (same pattern as Gender)
- Visible in client create/update API and personal data view
- Includes Liquibase migrations 0227 & 0228
- Added Postman collection for User Management APIs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 18:51:26 +05:30
AdityaandClaude Opus 4.6 ba5365d4af Add all CBS custom modules: KYC, Sanctions, RBI Feed, OpenProject, Mobile Lookup, Charges, Avro schemas
Includes all prior work not yet committed on this branch:
- KYC module (LincPay eKYC: PAN, Aadhaar OTP, GSTIN verification)
- Sanctions screening (UN Security Council consolidated list)
- RBI Feed integration with scheduled jobs
- OpenProject API integration
- Mobile lookup service
- Charge module enhancements (current account support)
- Client family member nominee fields
- Avro schema updates
- Liquibase migrations 0216-0224
- Postman collections for OpenProject, RBI Feeds, Sanctions
- SQL seed scripts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:54:10 +05:30
AdityaandClaude Opus 4.6 78f37257a7 Add CFCFRMS (MHA/I4C) Bank API and Simulator module
CFCFRMS integration for cyber fraud complaint management:
- Bank-side: complaint CRUD, lien/freeze/resolve actions, escalation, audit trail, dashboard stats
- Simulator (I4C/MHA): push complaints, hold directives, bulk push, receive updates/escalations, account/transaction verification, bank performance metrics
- Domain: ComplaintTicket, ComplaintAuditLog, CfcfrmsSimulatorLog entities
- Liquibase 0226: 3 tables (MySQL+PostgreSQL), indexes, permissions
- Seed data: 13 realistic complaint tickets + 5 simulator log entries
- JAX-RS endpoints at /v1/cfcfrms/complaints/* and /v1/simulator/cfcfrms/*
- Full Swagger @Operation annotations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:49:54 +05:30
AdityaandClaude Opus 4.6 6a464291d5 Add Current Account module mirroring Savings Account infrastructure
- CurrentAccount entity (@DiscriminatorValue 400) extending SavingsAccount
- 4 JAX-RS API resources: /v1/currentaccounts, /v1/currentproducts, transactions, charges
- 38 command handlers delegating to existing savings services
- ~35 CommandWrapperBuilder methods for current account commands
- DB migration 0225 with 67 permissions (CURRENTACCOUNT, CURRENTPRODUCT, CURRENTACCOUNTCHARGE)
- DepositsApiConstants and DepositAccountType enum updated for CURRENT_DEPOSIT

Reuses existing m_savings_account table (deposit_type_enum=400), GL accounting,
interest calculation, and all savings write/read services — no code duplication.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 18:16:36 +05:30
Peter KovacsandAdam Saghy 5d4f57912d FINERACT-2326: Fix buydown fee amounts missing from Transaction Summary Reports - E2E tests 2026-02-24 12:49:34 +00:00
Oleksii NovikovandAdam Saghy 7b545beb16 FINERACT-2326: Fix buydown fee amounts missing from Transaction Summary Reports 2026-02-24 12:49:34 +00:00
Adam Saghy a656dbde18 FINERACT-2421: Upgrade tomcat version 2026-02-23 17:53:25 +00:00
Attila BudaiandAdam Saghy 2047553366 FINERACT-2081: add api verification workflow 2026-02-23 15:43:14 +00:00
Adam Saghy 4a02c26059 FINERACT-2421: Fix flaky guarantor test 2026-02-23 13:16:54 +00:00
Soma SörösandAdam Saghy 93f5292bc3 FINERACT-2418: [REPORTING] Update "Transaction Summary Report" and "Trial Balance Report" with originator details 2026-02-23 10:52:23 +00:00
MarianaDmytrivBinariksandAdam Saghy 47710e161b FINERACT-2413: added e2e tests for re-amortization with equal interest split accruals handling 2026-02-23 10:10:38 +00:00
Soma SörösandAdam Saghy fb43706e6c FINERACT-2413: Re-amortization:- Accrual and Accrual Activity handling - Equal outstanding interest split 2026-02-23 10:10:38 +00:00
Soma SörösandAdam Saghy c7b4fc5404 FINERACT-2413: Re-amortization:- Accrual and Accrual Activity handling - Equal outstanding interest split e2e tests 2026-02-23 10:10:38 +00:00
yuwatidoraandAleksandar Vidakovic 701cfae60b FINERACT 2289: New command processing - Working Days
command base test fix
2026-02-20 21:39:41 +01:00
Adam Saghy ce3c9cc297 FINERACT-2421: Disable wrong guarantor test case temporarily 2026-02-20 12:54:55 +00:00
Ralph HopmanandAdam Saghy 76d108953b FINERACT-2498: Add GROUP as a Guarantor Type to Support Group Savings Accounts as Loan Collateral 2026-02-20 11:44:46 +00:00
Mend RenovateandAdam Saghy 55c51b140e FINERACT-2181: Update actions/setup-node digest to 49933ea 2026-02-20 11:31:22 +00:00
Aleksandar VidakovicandAdam Saghy 84fbb9d688 FINERACT-2421: fix breaking commmand persistence test 2026-02-20 10:51:59 +00:00
DeathGun44andAdam Saghy 045e3fe6ab FINERACT-2449: Replace unbounded SimpleAsyncTaskExecutor with ThreadPoolTaskExecutor 2026-02-20 09:22:35 +00:00
Jose Alberto HernandezandAdam Saghy 9c9c90d2af FINERACT-2421: Loan repayment transaction template with overpaid loan 2026-02-19 16:25:22 +00:00
Ralph HopmanandAdam Saghy 4467f91382 FINERACT-2496: Add Batch API Support for Creating Savings Account Charges 2026-02-19 11:33:27 +00:00
DeathGun44andAdam Saghy 3c2c60dee5 FINERACT-2495: Fix signed commits check to use PR head SHA instead of merge ref
Signed-off-by: DeathGun44 <krishnamewara841@gmail.com>
2026-02-19 11:32:45 +00:00
dependabot[bot]andAdam Saghy 57d2eba6e7 Bump actions/stale from 10.1.1 to 10.2.0
Bumps [actions/stale](https://github.com/actions/stale) from 10.1.1 to 10.2.0.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/997185467fa4f803885201cee163a9f38240193d...b5d41d4e1d5dceea10e7104786b73624c18a190f)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-version: 10.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-19 11:30:43 +00:00
Ralph HopmanandAdam Saghy 17c2febcac FINERACT-2492: AprCalculator produces incorrect annual interest rate when DaysInYearType is ACTUAL with WHOLE_TERM frequency 2026-02-19 11:30:28 +00:00
airajenaandAdam Saghy 41da36b1f4 FINERACT-2028: Add audit logging for manual scheduler job execution 2026-02-19 11:25:49 +00:00
airajenaandAdam Saghy 296cf902b9 FINERACT-1878: Separate profile image paths for staff and clients 2026-02-19 11:25:22 +00:00
Satish11012007andAdam Saghy 89487a8ace FINERACT-1266: Fix potential NPE in determineInterestPostingPeriods 2026-02-19 11:23:42 +00:00
Wilfred KigenyiandAdam Saghy c9a41794e8 FINERACT-2457: Share transaction chronological validation incorrectly blocks new transactions based on rejected/reversed transactions 2026-02-19 11:15:28 +00:00
Mend RenovateandAdam Saghy fcc3da2515 FINERACT-2181: Update postgres Docker tag to v18 2026-02-19 11:15:03 +00:00
DeathGun44andAdam Saghy ca3e980543 FINERACT-2003: Enforce password reset on first login
:wq
2026-02-19 11:14:17 +00:00
Mend RenovateandAdam Saghy 8b92beb473 FINERACT-2181: Update actions/checkout digest to 93cb6ef 2026-02-19 11:08:01 +00:00
dependabot[bot]andArnold Gálovics 6c54b4bca6 Bump gradle/actions from 5.0.0 to 5.0.1
Bumps [gradle/actions](https://github.com/gradle/actions) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/v5...v5.0.1)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: 5.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-19 10:21:10 +01:00
Aleksandar VidakovicandAleksandar Vidakovic ae3e2a6343 FINERACT-2245: New command processing - mix taxonomy 2026-02-18 20:26:51 +01:00
Dhananjay BhagatandAdam Saghy fe98aa6a58 FINERACT-2489: Fix NPE for missing charts in Recurring Deposit Product 2026-02-18 16:18:51 +00:00
Rustam ZeinalovandAdam Saghy 85bd81b6ae FINERACT-2421: added e2e tests to validate issues with interest from overdue balance corrections on model 2026-02-18 16:15:19 +00:00
Oleksii NovikovandAdam Saghy e65257835b FINERACT-2421: Fix phantom interest from overdue balance corrections on model 2026-02-18 16:15:19 +00:00
Adam Monsen e2fa9e9762 FINERACT-2458: move DPG badge alongside others
It looked odd having the badge and text above the first H1 heading.

see also: https://github.com/apache/fineract/pull/5404
2026-02-18 07:41:00 -08:00
Ashhar Ahmad KhanandAdam Saghy a8ba79d19a FINERACT-2468: Fix datatable operations in PostgreSQL non-public schemas
Use current_schema instead of hardcoded 'public' in getTableIndexes()
and isTablePresent() methods to support multi-tenant PostgreSQL
deployments where datatables reside in tenant-specific schemas.

This fixes:
- GET /datatables returning 404 for new datatables
- Datatable save operations failing with 'Datatable not found'

Root cause: Index metadata queries were looking in 'public' schema
while table columns query correctly used current_schema, causing
inconsistent behavior in GenericDataServiceImpl.

Changes:
- PostgreSQLQueryService.getTableIndexes(): Use current_schema
- PostgreSQLQueryService.isTablePresent(): Use current_schema

Testing:
- All unit tests pass (93/93 in fineract-core)
- Manual PostgreSQL verification confirms fix works correctly
2026-02-18 15:38:50 +00:00
AmbikaandAdam Monsen 5cd6b718ed FINERACT-2458: Add DPG verified badge to main README 2026-02-18 07:28:56 -08:00
Adam Saghy 41cf451e13 FINERACT-2455: Add Working Capital Loan module 2026-02-18 15:19:56 +00:00
DeathGun44andAdam Saghy 2a2ce6e0a6 FINERACT-2116: Add validation integration tests for Credit Bureau Configuration APIs
Signed-off-by: DeathGun44 <krishnamewara841@gmail.com>
2026-02-18 15:19:39 +00:00
Aleksandar VidakovicandGitHub 3a842ecb9a FINERACT-2243: New command processing - payment type (#5506) 2026-02-18 08:19:29 -06:00
Oleksii NovikovandAdam Saghy 020754bad4 FINERACT-2421: Retry external asset owner creation in case of unique constraint violation 2026-02-18 12:11:02 +00:00
MarianaDmytrivBinariksandAdam Saghy da85c069b7 FINERACT-2421: e2e tests for pay-off with 2nd disbursement with downpayment 2026-02-18 11:01:35 +00:00
mariiaKraievskaandAdam Saghy 2634e54fdd FINERACT-2421: Pay-off amount incorrectly calculated if the loan has more than one downpayment 2026-02-18 11:01:35 +00:00
Attila BudaiandAdam Saghy 115063b89e FINERACT-2466: fix trial balance report
Signed-off-by: Attila Budai <budai.attila.istvan@gmail.com>
2026-02-18 10:17:17 +00:00
Víctor RomeroandAdam Saghy 9ebaf46b6a Restore mifos-fineract-client-publish.yml
Restore mifos-fineract-client-publish.yml it is used by another Mifos Community Projects
2026-02-18 10:17:17 +00:00
MarianaDmytrivBinariksandAdam Saghy 6f55ce1a4f FINERACT-2418: e2e test scenarios for disbursal and reage events with changed terms flag 2026-02-18 09:27:59 +00:00
Oleksii NovikovandAdam Saghy 1feb1d730f FINERACT-2418: Extend disbursal and reage events with changed terms flag 2026-02-18 09:27:59 +00:00
mariiaKraievskaandAdam Saghy 136a966fc3 FINERACT-2421: Fix undo repayment with linked chargeback error 2026-02-18 09:21:05 +00:00
Prakash M SandAdam Saghy 9ca717ffd2 Added empty Line after Class name to pass spotlessJavaCheck 2026-02-18 09:11:08 +00:00
Prakash M SandAdam Saghy b0ec0a667c Removed Trailing spaces in empty lines 2026-02-18 09:11:08 +00:00
Prakash M SandAdam Saghy 2c008e0e65 Aligned formatting with Spotless requirements 2026-02-18 09:11:08 +00:00
Prakash M SandAdam Saghy 8e376c097a Add unit test for SavingsHelper to handle null posting type 2026-02-18 09:11:08 +00:00
Prakash M SandAdam Saghy 2f17c20dfd Add null check for postingPeriodType in SavingsHelper 2026-02-18 09:11:08 +00:00
Abdelrahman MostafaandAdam Saghy 92f8b3d0d4 FINERACT-2491: Refactor SavingsEnumerations to use switch expression 2026-02-17 14:11:32 +00:00
airajenaandAleksandar Vidakovic e4e0082872 FINERACT-1956: Add Antora skeleton structure and consolidate contributor guides 2026-02-17 14:12:25 +01:00
Attila BudaiandArnold Gálovics 3e98cf791a FINERACT-2421: fix loan accrual posts after loan close 2026-02-17 13:31:09 +01:00
airajenaandAdam Saghy 2ee6f22713 FINERACT-2050: Fix client transfer loan status staying in TRANSFER_IN_PROGRESS 2026-02-17 12:06:12 +00:00
airajenaandAdam Saghy 7daf4b8a84 FINERACT-2005: Prohibit password re-use with configurable global setting 2026-02-17 11:57:28 +00:00
054d18b020 FINERACT-2461: Replace SQL string concatenation with parameterized queries
Fix ~30 SQL injection vulnerabilities across datatable services by
replacing string concatenation with JDBC parameterized queries (?).

Files changed:
- DatatableWriteServiceImpl: parameterized DELETE, INSERT, SELECT queries
- DatatableUtil: parameterized dataScopedSQL, office join conditions
- GenericDataService/Impl: added parameterized fillResultsetRowData overload
- DatatableReadServiceImpl: parameterized countDatatableEntries
- WriteSurveyServiceImpl: removed duplicate vulnerable getPermissionSql
- DepositAccountReadPlatformServiceImpl: added pagination validation

Tests added:
- DatatableUtilTest: 11 tests for parameterized dataScopedSQL and joins
- DatatableWriteServiceImplTest: 7 tests for parameterized CRUD queries
- DatatableReadServiceImplCountTest: 1 test for parameterized count query

Do good with AI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 11:51:55 +00:00
Rustam ZeinalovandAdam Saghy 87e328e9e7 FINERACT-2418: added e2e test covering loan origination feature 2026-02-17 11:12:52 +00:00
edk12564andAdam Saghy 588b831f8f FINERACT-176: fixed missing throw exception in Teller module
- Fixed missing throw exception for validation errors in Teller Module
- added unit tests to confirm changes to TellerCommandFromApiJsonDeserializer

ReportedBy: subramanyasn
2026-02-17 10:37:06 +00:00
Aleksandar VidakovicandAleksandar Vidakovic 2ff0c62f40 FINERACT-2246: New command processing - document management 2026-02-16 14:56:40 +01:00
Ralph HopmanandGitHub 8a61e30a45 FINERACT-2486: Fix guarantor endpoint returning savingsAccount.id=0 for group savings (#5482) 2026-02-14 16:16:06 -06:00
Ralph HopmanandGitHub 05662e64d5 FINERACT-2473: Add available balance to group accounts endpoint (#5468) 2026-02-14 16:15:34 -06:00
Dhananjay BhagatandGitHub 0ea65d820d FINERACT-2487: Add interestPostingPeriodType to mandatory fields in Recurring Deposit Product API docs (#5488) 2026-02-14 13:13:48 -06:00
Ralph HopmanandGitHub 8a795e83c7 FINERACT-2490: Add status field to StandingInstructionData serialization (#5493) 2026-02-14 13:05:54 -06:00
Ralph HopmanandGitHub 4bb5c02b19 FINERACT-2488: Fix duplicate results in /api/v2/clients/search (#5485) 2026-02-14 13:05:13 -06:00
Ralph HopmanandArnold Gálovics 176d510384 FINERACT-2483: Add Lombok usage guidelines to CONTRIBUTING.md 2026-02-14 13:37:33 +01:00
Ralph HopmanandArnold Gálovics 9a69cc4826 FINERACT-2482: On-hold transactions API returns empty results for group savings accounts 2026-02-14 13:36:24 +01:00
Adam SaghyandSoma Sörös 6269cbbb79 FINERACT-2421: Allow ProgressiveLoanInterestScheduleModel recalculation
Co-authored-by: Soma Sörös <soma.soros@dpc.hu>
2026-02-13 14:58:37 +01:00
saifulhuq01andAdam Saghy a8fb5e7826 FINERACT-2464: Refactor LoanScheduleAssembler logic extraction 2026-02-13 09:52:33 +01:00
Adam Saghy 01911c41c4 FINERACT-2421: Skip testcontainer tests if docker is not available 2026-02-12 23:03:59 +01:00
Jose Alberto HernandezandAleksandar Vidakovic 8d560a1f7c FINERACT-2332: SavingCOB infrastructure 2026-02-12 14:02:18 +01:00
Jose Alberto HernandezandAleksandar Vidakovic 356dd07ca0 FINERACT-1932: Fineract modularization - fineract-security 2026-02-12 14:01:38 +01:00
mariiaKraievskaandAdam Saghy f504281067 FINERACT-2465: Send a loan account business event during charge-off 2026-02-12 13:44:34 +01:00
Rustam ZeinalovandAdam Saghy badc2594dd FINERACT-2421: Updated e2e test covering External Asset Owner creation 2026-02-12 10:58:24 +01:00
Jose Alberto HernandezandAdam Saghy 955222ee82 FINERACT-2421: External Asset Owner create api 2026-02-12 10:58:24 +01:00
nidhiii128andAdam Saghy 6875645825 FINERACT-2474: Add Unit Tests for ClientReadPlatformServiceImpl 2026-02-11 12:26:11 +01:00
Aman MittalandAdam Saghy b009c4b184 FINERACT-2421: Refractor - remove ApiResponses annotations (Phase 8) 2026-02-11 12:03:36 +01:00
Aman MittalandAdam Saghy cfa87a0bb2 FINERACT-2421: Bump - org.assertj:assertj-core CVE-2026-24400 2026-02-11 12:02:35 +01:00
DeathGun44andAdam Saghy 76d4216f0a FINERACT-2472: Fix loanProductId NPE in Credit Bureau Mapping 2026-02-11 12:02:11 +01:00
Vladyslav SamchenkoandAdam Saghy 840f95c88f FINERACT-2015: Fix saving account with withdrawal fee 2026-02-11 12:01:03 +01:00
mariiaKraievskaandAdam Saghy 7faaf96246 FINERACT-2467: Add previous owner id to transaction summary report with asset owner 2026-02-11 11:59:31 +01:00
Aleksandar VidakovicandAleksandar Vidakovic 02fb166b5b FINERACT-2239: New command processing - resource notes 2026-02-11 00:15:27 +01:00
Jose Alberto HernandezandAdam Saghy 28f63cc20e FINERACT-2418: Loan Originator data with codevalues 2026-02-09 16:42:53 +01:00
Attila BudaiandAdam Saghy 99861ce93a FINERACT-2418: add originator during the loan application 2026-02-09 14:40:34 +01:00
SaifulhuqandGitHub f5951f1a7b FINERACT-2461: Refactor EmailReadPlatformServiceImpl to use Prepared Statements (#5417)
* FINERACT-2461: Refactor EmailReadPlatformServiceImpl to use Prepared Statements
* FINERACT-2461: Add unit tests for EmailReadPlatformServiceImpl
2026-02-06 10:39:40 +01:00
mariiaKraievskaandArnold Gálovics c57f44d3f0 FINERACT-2418: Add originators details to external business events 2026-02-06 09:37:36 +01:00
dependabot[bot]andArnold Gálovics 288038d7bb Bump actions/checkout from 4 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-06 09:34:58 +01:00
Adam SaghyandArnold Gálovics f6d013c68d FINERACT-2421: Run inline COB on newly created loans that are behind 2026-02-06 09:34:32 +01:00
Adam SaghyandArnold Gálovics 967e0ea306 FINERACT-2421: Generate better client names during testing 2026-02-06 09:34:00 +01:00
DeathGun44andAdam Monsen d61b5e65f9 FINERACT-2177: Add Git signed commits verification 2026-02-05 19:24:04 -08:00
MarianaDmytrivBinariksandAdam Saghy a98c1434ac FINERACT-2421: e2e test scenarios for adjust interest with MIR and CBR results in improper allocations 2026-02-05 16:01:05 +01:00
mariiaKraievskaandAdam Saghy 0904c4f530 FINERACT-2421: Add pastDueDate to loan summary and event - E2E test 2026-02-05 13:45:18 +01:00
Oleksii NovikovandAdam Saghy 41d859e68a FINERACT-2421: Add pastDueDate to loan summary and event 2026-02-05 13:45:18 +01:00
8d064ddf6c FINERACT-2452: New command processing - audit
Co-Authored-By: Adam Saghy <adam.saghy@dpc.hu>
Co-Authored-By: Yu Wati Nyi <nyi22y@mtholyoke.edu>
2026-02-05 12:55:05 +01:00
Jose Alberto HernandezandAdam Saghy 1d1876edfd FINERACT-2421: Disbursement charge amount incorrect calculation 2026-02-05 10:41:19 +01:00
Attila BudaiandAdam Saghy 2620c61afa FINERACT-2418: add originator global config 2026-02-05 10:39:41 +01:00
Attila BudaiandAdam Saghy ef12056e5d FINERACT-2418: add originatior association 2026-02-03 14:58:28 +01:00
Attila BudaiandAdam Saghy 3b5ea96f5e FINERACT-2418: add originatior loan mapping 2026-02-03 14:58:28 +01:00
Peter KovacsandAdam Saghy 3dbada34f7 FINERACT-2412: Full term tranche - charges and penalties handling - E2E tests 2026-02-03 14:58:13 +01:00
Attila BudaiandAdam Saghy 221f80699f FINERACT-2418: fetch loan originators by loan 2026-02-03 13:21:24 +01:00
MarianaDmytrivBinariksandAdam Saghy ca3508093a FINERACT-2412: e2e test scenarios for full term tranche with n+1 instalments 2026-02-03 13:12:22 +01:00
Soma SörösandAdam Saghy d2497a77fc FINERACT-2412: full term tranche - N+1 installment handling 2026-02-03 13:12:22 +01:00
MarianaDmytrivBinariksandAdam Saghy 7a7ed538e6 FINERACT-2412: e2e test scenarios get loan Api changes with active terms 2026-02-03 11:28:52 +01:00
Soma SörösandAdam Saghy 7bb4840d7a FINERACT-2412: full term tranche - get loan API changes & add to LoanAccountDataV1 (events) 2026-02-03 11:28:52 +01:00
MarianaDmytrivBinariksandAdam Saghy ff9d15dad8 FINERACT-2412: e2e test scenarios with accruals handling for re-amortization default behaviour 2026-02-03 10:56:53 +01:00
mariiaKraievskaandAdam Saghy 4a2f7de888 FINERACT-2413: Re-amortization:- Accrual and Accrual Activity handling - Default Handling 2026-02-03 10:56:53 +01:00
Aman MittalandAdam Saghy 1c90880b7a FINERACT-2421: Refractor - remove ApiResponses annotations (Phase 7) 2026-02-02 19:27:39 +01:00
Aman MittalandAdam Saghy 433959c280 FINERACT-2421: Bump - org.apache.tomcat.embed:tomcat-embed-core CVE-2025-61795 2026-02-02 19:26:03 +01:00
mariiaKraievskaandAdam Saghy 5f2f096878 FINERACT-2421: Re-aging at the same as 2nd disbursement date outcomes with incorrect repayment schedule 2026-02-02 19:21:26 +01:00
dependabot[bot]andAdam Saghy 58028951bd Bump actions/cache from 5.0.2 to 5.0.3
Bumps [actions/cache](https://github.com/actions/cache) from 5.0.2 to 5.0.3.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/8b402f58fbc84540c8b491a91e594a4576fec3d7...cdf6c1fa76f9f475f3d7449005a359c84ca0f306)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 5.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-02 19:17:57 +01:00
dependabot[bot]andAdam Saghy a0389fec61 Bump gradle/actions from 5.0.0 to 5.0.1
Bumps [gradle/actions](https://github.com/gradle/actions) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/v5...v5.0.1)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: 5.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-02 19:01:07 +01:00
saifulhuq01andAdam Saghy b107acb005 FINERACT-2459: Refactor SmsReadPlatformServiceImpl to use Prepared Statements 2026-02-02 18:59:51 +01:00
Rustam ZeinalovandAdam Saghy 007187111d FINERACT-2413: added e2e test for handling multiple re-amortization on the loan 2026-02-02 18:59:07 +01:00
Soma SörösandAdam Saghy 2ee19f9825 FINERACT-2413: Allow multiple Re-amortization on a loan account 2026-02-02 18:59:07 +01:00
Rustam ZeinalovandAdam Saghy 01d3049538 FINERACT-2456: added e2e test covering charge off calculation after repayment reversal with multiple merchant refunds and credit balance refund 2026-02-02 18:55:17 +01:00
Oleksii NovikovandAdam Saghy 3b0d053d52 FINERACT-2456: Fix charge off after repayment reversal with merchant refunds and credit balance refund 2026-02-02 18:55:17 +01:00
Peter KovacsandAdam Saghy 0a113fd42d FINERACT-2412: full term tranche- interest pause handling - E2E tests 2026-02-02 18:53:48 +01:00
Soma SörösandAdam Saghy 9884025bd9 FINERACT-2412: full term tranche- interest pause handling 2026-02-02 18:53:48 +01:00
Jose Alberto HernandezandAdam Saghy 7b322481b8 FINERACT-2421: Adjust interest with MIR and CBR results in improper allocations 2026-02-02 18:52:59 +01:00
Dhananjay BhagatandAdam Saghy cc372286fa FINERACT-1598: Remove unsupported recurringDepositFrequency fields from API docs 2026-02-02 18:44:05 +01:00
Adam Saghy 5408921091 FINERACT-2421: Fix flaky test case 2026-01-30 22:01:15 +01:00
Adam Saghy 0ed6b7f94c FINERACT-2421: Fix Trial Balance report 2026-01-30 11:39:03 +01:00
Adam Saghy 0fd0f977eb FINERACT-2421: Allow hyphens in query params of Batch API calls 2026-01-30 11:37:58 +01:00
mariiaKraievskaandArnold Gálovics 5831cb3afd FINERACT-2412: Delayed Schedule Captures Documentation 2026-01-28 17:02:17 +01:00
Attila BudaiandArnold Gálovics 82e6a237a6 FINERACT-2418: add origination api implementation 2026-01-28 17:01:57 +01:00
mariiaKraievskaandAdam Saghy ba978c591b FINERACT-2412: full term tranche - capitalized income handling 2026-01-28 14:31:06 +01:00
mariiaKraievskaandAdam Saghy 4c14715078 FINERACT-2412: full term tranche - payment holiday handling 2026-01-28 11:47:25 +01:00
MarianaDmytrivBinariksandAdam Saghy 1dfba8e30b FINERACT-2412: e2e test scenarios for full term tranche with charge-off transaction 2026-01-27 17:37:33 +01:00
mariiaKraievskaandAdam Saghy 0e7ab0d26f FINERACT-2412: full term tranche - contract termination handling 2026-01-27 16:28:21 +01:00
mariiaKraievskaandAdam Saghy 4ba9bdf4dd FINERACT-2412: full term tranche - re-amortization handling 2026-01-27 15:08:02 +01:00
Peter KovacsandAdam Saghy 6cc5323090 FINERACT-2421: Allow multiple interest rate schedule adjustments same date - E2E tests 2026-01-27 15:04:30 +01:00
Jose Alberto HernandezandAdam Saghy 532190a649 FINERACT-2421: Allow multiple interest rate schedule adjustments same date 2026-01-27 15:04:30 +01:00
MarianaDmytrivBinariksandAdam Saghy f40ce6d9f6 FINERACT-2412: e2e test scenarios for full term tranche with chargeback trn 2026-01-27 14:00:47 +01:00
mariiaKraievskaandAdam Saghy f3bda3aff5 FINERACT-2412: full term tranche - write-off handling 2026-01-27 12:10:02 +01:00
Rustam ZeinalovandAdam Saghy e31ee7a9f8 FINERACT-2412: Added e2e tests for validating handling of backdated monetary and reversal transactions for full term tranche loans 2026-01-27 11:00:54 +01:00
Adam Monsen 3b2a5bcb30 FINERACT-2448: standardize on release ready, improve releases section
I want us to standardize on being "release ready". That is, we agree to do our best to keep incomplete features off `develop` and to keep the build passing.

As part of this change I also want to update the [VERSIONS section in the README](https://github.com/apache/fineract/blob/d093a089c9c13051eb8922100e38e44a3933fd9c/README.md#versions) to more accurately reflect how we do releases today.
2026-01-26 10:13:55 -08:00
Aman MittalandAdam Saghy 6aafb308ea FINERACT-2421: Refractor - remove ApiResponses annotations (Phase 6) 2026-01-26 15:48:24 +01:00
DeathGun44andAdam Saghy c74e661281 FINERACT-2053: Fix Prometheus scraping config path and hostname 2026-01-26 15:44:41 +01:00
Aman MittalandAdam Saghy e2e36b105a FINERACT-2421: Bump mysql connector CVE-2025-30706 2026-01-26 15:29:43 +01:00
DeathGun44andAdam Saghy 9aea69fdf8 FINERACT-2450: Fix IndexOutOfBoundsException in GlobalConfigurationHelper 2026-01-26 15:28:11 +01:00
Shivd131andAdam Saghy 55f764d200 FINERACT-2438: Add Micrometer Observability to Reporting API 2026-01-26 15:08:44 +01:00
MarianaDmytrivBinariksandAdam Saghy 0729e41ff9 FINERACT-2412: e2e test scenario for full term tranche with buy down fee trn 2026-01-26 14:54:55 +01:00
oluexpert99andAdam Saghy e9dfc4a9b8 FINERACT-2446: Tax Group and Tax Component APIs return HTTP 500 when no record is found (empty result), instead of returning a proper HTTP 404 Not Found 2026-01-26 14:52:23 +01:00
oluexpert99andAdam Saghy 5ca75c94ec FINERACT-2446: Tax Group and Tax Component APIs return HTTP 500 when no record is found (empty result), instead of returning a proper HTTP 404 Not Found 2026-01-26 14:52:23 +01:00
ap200506andAdam Saghy f2630fb712 FINERACT-2447: Bulk Import processing result file not working on PostgreSQL 2026-01-26 14:50:21 +01:00
airajenaandAdam Saghy 52aaad9d25 FINERACT-2155: Allow annual charge payment after due date 2026-01-26 13:12:17 +01:00
Oleksii NovikovandAdam Saghy d093a089c9 FINERACT-2354: Fix charge-off after re-age 2026-01-23 18:20:43 +01:00
MarianaDmytrivBinariksandAdam Saghy c85f2ab4f4 FINERACT-2354: e2e tests for charge-off and contract termination trns after re-age 2026-01-23 18:20:43 +01:00
MarianaDmytrivBinariksandArnold Gálovics 23faa55326 FINERACT-2412: e2e test scenarios for full term tranche with re-aging trns 2026-01-23 17:17:42 +01:00
Adam SaghyandArnold Gálovics 4ebff33792 FINERACT-2421: No interest, no interest accrual calculation needed 2026-01-23 17:07:58 +01:00
mariiaKraievskaandAdam Saghy ddc82f1b68 FINERACT-2389: loan is not closed with outstanding amount during pay-off loan with reschedule trns 2026-01-23 16:11:11 +01:00
Attila BudaiandArnold Gálovics bf879064af FINERACT-2435: fix as of data api 2026-01-23 15:52:42 +01:00
dependabot[bot]andAdam Saghy 2b4de8e655 Bump actions/checkout from 6.0.1 to 6.0.2
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/8e8c483db84b4bee98b60c0593521ed34d9990e8...de0fac2e4500dabe0009e67214ff5f5447ce83dd)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-23 14:06:57 +01:00
Aman MittalandAdam Saghy 7eb1fdd97b FINERACT-2421: chore(deps): upgrade org.eclipse.jgit to 7.2.1 CVE-2025-4949 2026-01-23 14:01:13 +01:00
Rustam ZeinalovandAdam Saghy f7a90d8776 FINERACT-2421: added e2e test covering manual interest refund on closed loan generates exception 2026-01-23 14:00:49 +01:00
Jose Alberto HernandezandAdam Saghy fb96df64ab FINERACT-2421: Manual interest refund on closed loan generates exception 2026-01-23 14:00:49 +01:00
Aman-MittalandAdam Saghy 8b24325f84 FINERACT-2421: Refractor redundant and some deprecated code as per new Java 21 style (Phase 5) 2026-01-23 12:15:46 +01:00
KrishnaandGitHub 6f1d96fc2c FINERACT-2158: Add workflow to enforce PR title convention 2026-01-22 11:43:33 -08:00
Soma SörösandAdam Saghy 9ef9362acb FINERACT-2354: e2e tests for pay-off to close re-aged loan with charge outcomes with Active loan status 2026-01-22 16:03:58 +01:00
Soma SörösandAdam Saghy 641c6da1b9 FINERACT-2354: [BE] pay-off to close re-aged loan with charge outcomes with Active loan status 2026-01-22 16:03:58 +01:00
Adam Saghy 1d12c13c8e FINERACT-2418: add origination-api-skeleton 2026-01-21 13:13:04 +01:00
Attila BudaiandAdam Saghy 87cad91aa8 FINERACT-2418: add origination-api-skeleton 2026-01-21 13:13:04 +01:00
Rustam ZeinalovandAdam Saghy bffa04c3e6 FINERACT-2389: added e2e test validating incorrect repayment schedule amount calculations for 2nd disb, CI and other actions after re-amortization 2026-01-20 12:28:43 +01:00
mariiaKraievskaandAdam Saghy 5e6c88ee3a FINERACT-2389: incorrect repayment schedule amount calculations for 2nd disb, CI and other actions after re-amortization 2026-01-20 12:28:43 +01:00
Adam Saghy 0cb23ac5c5 FINERACT-2437: Decrease the number of compilation warnings 2026-01-20 11:35:33 +01:00
Rustam ZeinalovandAdam Saghy 70f76b84f3 FINERACT-2413: added e2e tests for validation of schedule handling after re-amortization default behavior 2026-01-20 09:42:23 +01:00
Oleksii NovikovandAdam Saghy 696013e650 FINERACT-2413: Schedule handling after re-amortization default behavior 2026-01-20 09:42:23 +01:00
MarianaDmytrivBinariksandAdam Saghy 61e84ff87e FINERACT-2354: e2e tests for re-aging with accruals for equal amortization 2026-01-19 19:29:21 +01:00
Soma SörösandAdam Saghy eecc54c224 FINERACT-2354: Re-aging - Accrual and Accrual Activity handling - Equal Amortization 2026-01-19 19:29:21 +01:00
airajenaandAdam Saghy 3a6f805d50 FINERACT-2164: Add Gradle SBOM (Software Bill of Materials) generator
- Add CycloneDX plugin for SBOM generation (version 3.1.0)
- Configure as optional task (not part of default build)
- Generate SBOM in CycloneDX format with license information
- Usage: ./gradlew :fineract-provider:cyclonedxDirectBom

Output: build/reports/cyclonedx-direct/bom.{json,xml}
2026-01-19 15:00:40 +01:00
MonicaandAdam Saghy 11d6eb9f2d FINERACT-2428: Fix 'acount' typo to 'account' in legacy docs 2026-01-19 14:51:14 +01:00
Attila BudaiandAdam Saghy f83ea7ea15 FINERACT-2418: create the loan origination module 2026-01-19 14:14:20 +01:00
mariiaKraievskaandAdam Saghy 2a064dcb09 FINERACT-2389: Loan reschedule with adjustedDueDate after re-age 2026-01-19 12:51:04 +01:00
Jose Alberto HernandezandAdam Saghy e9af3a8ac8 FINERACT-2421: Undo Last Disbursement failed after reaging 2026-01-19 12:38:31 +01:00
dependabot[bot]andArnold Gálovics 961682e80c Bump actions/cache from 5.0.1 to 5.0.2
Bumps [actions/cache](https://github.com/actions/cache) from 5.0.1 to 5.0.2.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/9255dc7a253b0ccc959486e2bca901246202afeb...8b402f58fbc84540c8b491a91e594a4576fec3d7)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 5.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 11:31:20 +01:00
yvana-kengneandAdam Saghy 61d361f998 FINERACT-2055 Internal Server Error Encountered When Accessing Individual Collection Sheet Option 2026-01-16 22:18:22 +01:00
Kengne-yvanaandAdam Saghy 44b6b29dc5 FINERACT-2086: Fix ClassCastException in AccountAssociations and add unit tests
- Simplified conversion using Number.intValue() to handle Short and Integer types.
- Removed redundant null checks as per feedback.
- Added unit tests for type-safety verification.
2026-01-16 22:18:05 +01:00
Adam Saghy 0891adb267 FINERACT-2421: Rework code value handling in E2E 2026-01-16 17:59:14 +01:00
Adam Saghy 0f73ae1eb7 FINERACT-2421: Move E2E init data into runner 2026-01-16 10:37:26 +01:00
MonicaandAdam Saghy ea1609120f FINERACT-2423: Add preconditions to all changesets 2026-01-15 15:17:41 +01:00
MonicaandAdam Saghy cccb7d7726 FINERACT-2423: Remove redundant 0071 fix and keep safe 0072 migration 2026-01-15 15:17:41 +01:00
MonicaandAdam Saghy 6324489b96 Fix Liquibase changelog formatting and failing elements 2026-01-15 15:17:41 +01:00
Monica-CodingWorldandAdam Saghy 2efedf59e8 FINERACT-2423: Fix invalid Liquibase changelog filename with space
Apply spotless formatting

FINERACT-2423: Add Liquibase changeset to fix invalid 0072 filename

FINERACT-2423: Remove unnecessary whitespace in changelog-tenant.xml

FINERACT-2423: Add Apache license header to Liquibase changelog

FINERACT-2423: Add Apache license header to Liquibase changeset

Fix for FINERACT-2423: [Your fix description]

- Applied code formatting (spotlessApply)
- Passed SpotBugs checks
- Passed Checkstyle test checks
2026-01-15 15:17:41 +01:00
mariiaKraievskaandAdam Saghy db066fbcff FINERACT-2412: Full term tranche - undo last disbursement support 2026-01-15 14:18:46 +01:00
dependabot[bot]andAdam Saghy fa0ceab30b Bump actions/setup-node from 6.1.0 to 6.2.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.1.0 to 6.2.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/395ad3262231945c25e8478fd5baf05154b1d79f...6044e13b5dc448c55e2357c09f80417699197238)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-15 13:36:15 +01:00
Adam Saghy 633acf204b FINERACT-2421: Allow reage preview API via Batch API 2026-01-15 13:35:54 +01:00
Oleksii NovikovandAdam Saghy 1a2faa9c62 FINERACT-2413: Add re-amortization documentation 2026-01-15 13:33:52 +01:00
mariiaKraievskaandAdam Saghy 09645e6afb FINERACT-2412: Full term tranche - downpayment handling 2026-01-15 13:33:27 +01:00
Adam Saghy d413e2b4af FINERACT-2421: Make "Business date configuration" reliable 2026-01-15 10:21:00 +01:00
Attila BudaiandAdam Saghy 442290ef92 FINERACT-2354: handle reamortization error code 500 issue on same disbursement day 2026-01-15 10:17:43 +01:00
yvana-kengneandAdam Saghy 53ff74425c FINERACT-2430: Fix typo: rename 'acount' to 'account' 2026-01-15 10:16:50 +01:00
Peter KovacsandAdam Saghy e028a8b976 FINERACT-2421: [doc] Asset Externalization documentation 2026-01-15 10:13:06 +01:00
Shiv DeshpandeandAdam Saghy 8f2dbe476c Fix(FINERACT-2320): Return false instead of 403 when Loan COB job is disabled 2026-01-15 10:12:33 +01:00
airajenaandAdam Saghy 07d67493df FINERACT-1266: Fix NullPointerException in SavingsHelper.determineInterestPostingPeriods 2026-01-15 10:10:29 +01:00
airajenaandAdam Saghy d94050d476 FINERACT-2421: Add unit tests for SQLBuilder 2026-01-15 10:08:15 +01:00
Aira JenaandGitHub 3ba476fdc4 FINERACT-2421: Add unit tests for ExternalIdFactory (#5276)
* FINERACT-2421: Add unit tests for ExternalIdFactory

* FINERACT-2421: Fix ambiguous method reference in ExternalIdFactoryTest

Cast null to String to resolve ambiguous method call between
produce(String) and produce(List<String>) overloads.
2026-01-15 10:07:17 +01:00
mariiaKraievskaandAdam Saghy 32a642255d FINERACT-2412: Full term tranche - monetary transaction allocation 2026-01-15 09:55:13 +01:00
MarianaDmytrivBinariksandAdam Saghy b59abb3fbc FINERACT-2354: e2e tests for re-aging with accruals for default behaviour 2026-01-15 09:48:08 +01:00
mariiaKraievskaandAdam Saghy fcf1fb6126 FINERACT-2354: Re-aging:- Accrual and Accrual Activity handling - Default Handling 2026-01-15 09:48:08 +01:00
mariiaKraievskaandAdam Saghy 09031aa5a7 FINERACT-2412: Full term tranche - Schedule handling and Calculations 2026-01-13 18:06:12 +01:00
Rustam ZeinalovandAdam Saghy 3e197e367b FINERACT-2421: added e2e test for verifying reprocess Interest Refund txn amount if no txn was changed 2026-01-13 10:05:19 +01:00
Jose Alberto HernandezandAdam Saghy 6df78f64c0 FINERACT-2389: Reprocess Interest Refund txn amount if no txn was changed 2026-01-13 10:05:19 +01:00
dependabot[bot]andArnold Gálovics 0026aaf1a6 Bump actions/cache from 4.3.0 to 5.0.1
Bumps [actions/cache](https://github.com/actions/cache) from 4.3.0 to 5.0.1.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/0057852bfaa89a56745cba8c7296529d2fc39830...9255dc7a253b0ccc959486e2bca901246202afeb)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 5.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-13 08:53:13 +01:00
dependabot[bot]andArnold Gálovics e284d55e19 Bump actions/setup-java from 4 to 5
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-13 08:51:56 +01:00
Adam Monsen 2032ceb9d9 Revert "exclude jquery-1.7.min.js from source release"
This reverts commit 7923d9e6cb.

See that commit's log message, and https://github.com/apache/tooling-trusted-releases/issues/427

TL;DR → ATR now ignores files ending in .min.js so we no longer need the explicit exclude for jquery-1.7.min.js
2026-01-12 08:00:46 -08:00
Adam Saghy a210834419 FINERACT-2421: Add Verify Liquibase Backward Compatibility check 2026-01-12 14:24:06 +01:00
mariiaKraievskaandAdam Saghy ae3a740070 FINERACT-2421: [doc] Pause delinquency user documentation 2026-01-12 14:09:56 +01:00
Wilfred KigenyiandAdam Saghy baaca2dd97 FINERACT-2432 2026-01-12 13:53:37 +01:00
airajenaandAdam Saghy eaf843a3ac FINERACT-2421: Fix GRETERTHAN typo in ConditionType enum 2026-01-12 13:50:40 +01:00
Víctor RomeroandAdam Saghy 646291339c Revert "FINERACT-2422: standardize use of aging vs. ageing (#5302)"
This reverts commit 174da7d862.
2026-01-09 16:12:31 +01:00
Víctor RomeroandAdam Saghy ed32c133cc Revert "[#FINERACT-2422] standardize use of aging vs. ageing (#5289)"
This reverts commit 61034e6041.
2026-01-09 16:12:22 +01:00
Adam Saghy f0692a8cf1 FINERACT-2421: Set back the XmX to 12g 2026-01-09 13:54:36 +01:00
MarianaDmytrivBinariksandAdam Saghy dff889be29 FINERACT-2790: e2e tests for re-amortization with equal interest split with diff trns afterwards 2026-01-08 22:47:03 +01:00
MonicaandAdam Saghy 591395f39c FINERACT-2429: Fix typo getCreditAcount to getCreditAccount in TaxComponent and SavingsAccountTransaction 2026-01-08 22:46:28 +01:00
Mend RenovateandAdam Saghy 0399007be0 FINERACT-2181: Update actions/cache digest to 0057852 2026-01-08 22:42:25 +01:00
Aman-MittalandAdam Saghy 9cc14e7fdb FINERACT-2421: fix(security): migrate request matchers to PathPatternRequestMatcher (no behavior change) 2026-01-08 22:38:40 +01:00
Aman-MittalandAdam Saghy d1e456ef43 FINERACT-2421: Refractor redundant and some deprecated code as per new Java 21 style (Phase 4) 2026-01-08 22:32:39 +01:00
Víctor RomeroandGitHub 174da7d862 FINERACT-2422: standardize use of aging vs. ageing (#5302) 2026-01-08 13:58:01 -06:00
Kengne-yvanaandGitHub 61034e6041 [#FINERACT-2422] standardize use of aging vs. ageing (#5289) 2026-01-08 10:43:21 -06:00
4c1016d239 FINERACT-2434: Add input validation to split-tests.sh script (#5296)
Co-authored-by: FarhanAlstonair <Farhan@github123>
2026-01-08 10:16:39 -06:00
Peter KovacsandAdam Saghy 9003034b34 FINERACT-2433/fix-interest-accrual-calculation - E2E test 2026-01-08 12:22:48 +01:00
Adam Saghy 8b3c8280de FINERACT-2433: Fix interest and accrual calculation in case of last installment payment 2026-01-08 12:22:48 +01:00
Rustam ZeinalovandAdam Saghy d42b4f7b6c FINERACT-2173: added e2e tests covering extended migration scenario with special transaction types and conditions 2026-01-08 12:21:40 +01:00
Rustam ZeinalovandAdam Saghy 97511ca2ff FINERACT-2174: added e2e tests covering extended migration scenario with fees/penalties 2026-01-08 11:36:16 +01:00
ap200506-sudoandGitHub e2842de23b FINERACT-2431: Fix typo in populateSharedAccountsWorkbook (#5293) 2026-01-07 09:48:31 -06:00
Adam Saghy 7ee0f7f4e4 FINERACT-2421: Improved Spring Batch context handling 2026-01-07 14:08:51 +01:00
Kengne-yvanaandGitHub 5c04216841 FINERACT-2426: Fix ChartOfAcountsConstants typo to ChartOfAccountsConstants (#5284) 2026-01-06 16:38:33 -06:00
MonicaandAdam Saghy e17303fcb9 FINERACT-2206: Fix date inconsistency in SavingsInterestPostingTest
Fixed startDate from dynamic current year to hardcoded 2025 in
testPostInterestNotZero() to maintain consistent 2025 timeline.
Resolves HTTP 400 error caused by date mismatches.
2026-01-03 19:49:44 +01:00
MonicaandAdam Saghy ab9f4fd40e FINERACT-2206: Fix typo in allowPartialPeriodInterestCalculation
Fixed misspelled 'allowPartialPeriodInterestCalcualtion' to
'allowPartialPeriodInterestCalculation' in REST API and code.
35 files updated as specified in JIRA.
2026-01-03 19:49:44 +01:00
Jose Alberto HernandezandAdam Saghy b7306f19d0 FINERACT-2425: Tax component and Tax Group refactoring 2026-01-03 01:56:47 +01:00
dependabot[bot]andArnold Gálovics cbf8a4f5ab Bump actions/checkout from 5.0.1 to 6.0.1
Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.1 to 6.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/93cb6efe18208431cddfb8368fd83d5badbf9bfd...8e8c483db84b4bee98b60c0593521ed34d9990e8)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-30 09:36:19 +01:00
dependabot[bot]andArnold Gálovics f6eb016c2d Bump actions/cache from 4.3.0 to 5.0.1
Bumps [actions/cache](https://github.com/actions/cache) from 4.3.0 to 5.0.1.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/0057852bfaa89a56745cba8c7296529d2fc39830...9255dc7a253b0ccc959486e2bca901246202afeb)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 5.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-30 09:36:06 +01:00
Mend RenovateandArnold Gálovics dfdb98e66b FINERACT-2181: Update actions/checkout digest to 93cb6ef 2025-12-29 22:24:00 +01:00
Mend RenovateandArnold Gálovics 88471bcd95 FINERACT-2181: Update actions/cache digest to 0057852 2025-12-29 22:23:46 +01:00
airajenaandArnold Gálovics 8134d7f6a0 FINERACT-2421: Simplify InputStream to String conversion using Java 11+ API 2025-12-29 10:36:05 +01:00
airajenaandArnold Gálovics b4fa48263b FINERACT-2421: Fix potential NullPointerException in CalendarCommandFromApiJsonDeserializer
The extractBooleanNamed method can return null when the JSON value is null
or missing. Using primitive boolean causes automatic unboxing which throws
NullPointerException.

Changes:
- CalendarCommandFromApiJsonDeserializer: Use Boolean wrapper instead of
  primitive boolean for 'repeating' field extraction
- CalendarCommandFromApiJsonDeserializer: Use Boolean.TRUE.equals() for
  null-safe boolean comparison
- CalendarCommand: Update field type and constructor parameter from
  boolean to Boolean to support null values

This addresses the FIXME comments on lines 156 and 298 in the original file.
2025-12-29 10:35:40 +01:00
dependabot[bot]andAdam Saghy e294ce88bf Bump actions/cache from 4.3.0 to 5.0.1
Bumps [actions/cache](https://github.com/actions/cache) from 4.3.0 to 5.0.1.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/0057852bfaa89a56745cba8c7296529d2fc39830...9255dc7a253b0ccc959486e2bca901246202afeb)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 5.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-26 19:29:02 +01:00
dependabot[bot]andAdam Saghy 2c712326bd Bump actions/checkout from 5.0.1 to 6.0.1
Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.1 to 6.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/93cb6efe18208431cddfb8368fd83d5badbf9bfd...8e8c483db84b4bee98b60c0593521ed34d9990e8)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-26 19:28:55 +01:00
MarianaDmytrivBinariksandAdam Saghy a738c268d7 FINERACT-2413: e2e tests for re-amortization forbidden for charged-ff or written-off loan 2025-12-26 19:28:23 +01:00
mariiaKraievskaandAdam Saghy d263fec31d FINERACT-2413: Re-amortization on charged-off, contract terminated & written-off loan 2025-12-26 19:28:23 +01:00
Aman-MittalandAdam Saghy 0ba1745443 FINERACT-2421: Refractor redundant and some deprecated code as per new Java 21 style (Phase 3) 2025-12-26 19:27:38 +01:00
Mend RenovateandAdam Saghy 7dac82a4fc FINERACT-2181: Update actions/checkout digest to 93cb6ef 2025-12-23 12:40:50 +01:00
Peter KovacsandArnold Gálovics cffa2a906d FINERACT-2181: Improve validation for reschedule fromDate parameter - E2E tests 2025-12-23 11:13:09 +01:00
Oleksii NovikovandArnold Gálovics 8610368a18 FINERACT-2181: Improve validation for reschedule fromDate parameter 2025-12-23 11:13:09 +01:00
MarianaDmytrivBinariksandArnold Gálovics 46f218fdc2 FINERACT-2354: e2e tests for re-amortization undo 2025-12-23 11:12:41 +01:00
Oleksii NovikovandArnold Gálovics 2858316c71 FINERACT-2413: Improve re-amortization undo validation 2025-12-23 11:12:41 +01:00
Adam SaghyandArnold Gálovics b457b2aa93 FINERACT-2421: Fix lombok annotations 2025-12-23 11:12:04 +01:00
Mend RenovateandArnold Gálovics 3363ee4f10 FINERACT-2181: Update actions/cache digest to 0057852 2025-12-23 11:11:37 +01:00
Adam MonsenandArnold Gálovics 15c2facf99 FINERACT-2424: post 1.14.0 release docs cleanup
* Go analog for step 15, automation was broken.
* Fix two admonitions: I meant to use `NOTE`. `INFO` is not a valid admonition in asciidoc, so it just shows up as text.
2025-12-23 11:11:09 +01:00
Adam Monsen d8b6649b32 Merge branch 'release/1.14.0' into develop
* release/1.14.0:
  update manual release instructions
  port steps 10 & 11 to the ATR
  docs: adapt artifact checks to new ATR tool
  improve svn release staging/release instructions
  exclude jquery-1.7.min.js from source release
  ignore .swp files when building src release
  add license to .mailmap
  add RAT excludes for ATR
  add license to scripts/*.sh
  fix both NOTICE files for ATR
  doc: remove errant config
  improve start release notes wording in step 3
2025-12-22 12:54:17 -08:00
Adam Monsen 9977fb3fcb update manual release instructions
Not gonna lie, I'm getting tired of maintaining this second source of truth. Hopefully my laziness, impatience, and hubris will tip the scales far enough to delete it.
2025-12-22 12:46:52 -08:00
Adam Monsen e1b0329748 port steps 10 & 11 to the ATR 2025-12-22 11:43:31 -08:00
Adam Monsen 30b25cdaa1 docs: adapt artifact checks to new ATR tool
We're already reaping rewards for using ATR (checks+voting+email support), and I want us to do both the old way AND the new way for now. I'll consider the old release process in "soft deprecation" as the ATR matures.
2025-12-22 11:02:32 -08:00
Adam Monsen f4aa11468d improve svn release staging/release instructions
All good. I tested these just now.

ATR so far does artifact checks & voting/email management.
Working with artifacts on the svn server is still our responsibility, for now.
2025-12-22 09:57:47 -08:00
Adam Monsen 7923d9e6cb exclude jquery-1.7.min.js from source release
This is to workaround an issue, UI misunderstanding, or PEBKAC with the ATR (ASF Trusted Releases) tool wherein I am unable to exclude the license header check for this file. jQuery (and its dependencies) can be used entirely under the MIT, see <https://jquery.com/license/>.

Once https://github.com/apache/tooling-trusted-releases/issues/312 ... is resolved we should be able to revert this workaround.

While this workaround is in place, consumers of apiLive.htm must:

1. put a copy of jquery-1.7.min.js next to apiLive.htm
2. change apiLive.htm to refer to jquery-1.7.min.js on a CDN

If someone needing apiLive.htm prefers the first option, they can grab a copy at https://code.jquery.com/jquery-1.7.min.js .
If they prefer the second option, they can pick a minified or unminified link listed at https://blog.jquery.com/2011/11/03/jquery-1-7-released/ and modify line 6 of apiLive.htm to refer to that instead, for example:

- <script type="text/javascript" src="jquery-1.7.min.js"></script>
+ <script type="text/javascript" src="https://code.jquery.com/jquery-1.7.min.js"></script>

There are other ways to deal with the license header check as well. For example: We could use 2nd option above (always), or finally/actually port away from apiLive.htm (it is in legacy-docs, after all).
2025-12-19 08:44:34 -08:00
Monica-CodingWorldandAdam Saghy 5f40037d6b docs: updated Java version requirement to Java 21 2025-12-19 10:05:27 +01:00
Monica-CodingWorldandAdam Saghy 7a773387d2 docs: update Java version requirement to Java 21 2025-12-19 10:05:27 +01:00
Monica-CodingWorldandAdam Saghy a12a1445de FINERACT-792: Improve developer environment setup and IntelliJ IDEA guidance 2025-12-19 10:05:27 +01:00
MarianaDmytrivBinariksandAdam Saghy c55cc5ce11 FINERACT-2354: e2e tests for re-age with equal amortization forbidden after write-off 2025-12-19 10:04:07 +01:00
Rustam ZeinalovandAdam Saghy 56b506b3f6 FINERACT-2354:added e2e test for validation of re-aging with various interest handling is forbidden on written-off loan 2025-12-19 10:04:07 +01:00
Adam Monsen 927f8dc589 ignore .swp files when building src release
just in case. I accidentally did this while working on the 1.14.0 release.
2025-12-18 14:38:32 -08:00
Adam Monsen 19305c6c60 add license to .mailmap
ATR was complaining
2025-12-18 14:37:59 -08:00
Adam Monsen 47ba127f23 add RAT excludes for ATR 2025-12-18 14:37:09 -08:00
Adam Monsen 86f6a8c9b8 add license to scripts/*.sh
conform rather than bypass RAT license check
2025-12-18 10:45:32 -08:00
Adam Monsen 2c4ed2e984 fix both NOTICE files for ATR
ATR (the ASF Trusted Releases tool) expects these to follow a very specific template. In this case I had to s/by/at/ and add a newline.

See: https://github.com/apache/tooling-trusted-releases/blob/52e202f2deae74d38bc60849736339b2ab919de8/atr/tasks/checks/license.py#L299
2025-12-18 10:27:14 -08:00
Adam Monsen ad2be8ad1c doc: remove errant config
similar to 45404bafba

All asciidoctor config must be in `fineract-doc/src/docs/en/config.adoc`.

In this case `:sectnums:` re-starts ToC section numbering partway through the ToC.
2025-12-18 09:33:39 -08:00
Adam Monsen 0f6f182193 improve start release notes wording in step 3 2025-12-18 09:15:11 -08:00
Adam Saghy 3996637ca4 FINERACT-2421: Upgrade dependencies 2025-12-18 15:37:10 +01:00
Adam SaghyandGitHub f6b2daf54c FINERACT-2421: Upgrade dependencies (#5244) 2025-12-18 14:14:02 +01:00
Dömötör D. LugosiandGitHub f94c0e7329 FINERACT-2421: Fix kubernetes minikube example configuration 2025-12-18 13:50:25 +01:00
Oleksii NovikovandAdam Saghy 630f41f11f FINERACT-2413: Re-amortization reverse replay handling 2025-12-18 12:37:52 +01:00
MarianaDmytrivBinariksandAdam Saghy e8e9fb139d FINERACT-2413: e2e tests for re-amortization reverse-replay handling 2025-12-18 12:37:52 +01:00
MarianaDmytrivBinariksandAdam Saghy 9e396b26a4 FINERACT-2354: e2e tests for re-age with equal amortization with reschedule 2025-12-18 12:37:14 +01:00
mariiaKraievskaandAdam Saghy 97b8bfa26f FINERACT-2354: Loan reschedule with extra terms after re-aging 2025-12-18 12:37:14 +01:00
Aman-MittalandAdam Saghy 4bfae0400c FINERACT-2326: Refractor redundant and some deprecated code as per new Java 21 style (Phase 2) 2025-12-17 08:51:47 +01:00
dependabot[bot]andAdam Saghy 695816f990 Bump actions/cache from 4.3.0 to 5.0.1
Bumps [actions/cache](https://github.com/actions/cache) from 4.3.0 to 5.0.1.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/0057852bfaa89a56745cba8c7296529d2fc39830...9255dc7a253b0ccc959486e2bca901246202afeb)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 5.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-16 12:46:09 +01:00
MarianaDmytrivBinariksandAdam Saghy dee325ec7f FINERACT2354: added e2e tests for re-aging with equal amortization with backdated interest pause 2025-12-16 12:43:01 +01:00
mariiaKraievskaandAdam Saghy 1a6975c930 FINERACT-2354: Re-aging: Repayment schedule handling in case of overlapping Interest pause 2025-12-16 12:43:01 +01:00
Rustam ZeinalovandAdam Saghy f8c5122921 added e2e tests for backdated re-aging with default behaviour 2025-12-16 12:38:36 +01:00
MarianaDmytrivBinariksandAdam Saghy 215fc28ead FINERACT2354: e2e tests for re-aging with default behaviour with backdated re-aging 2025-12-16 12:38:36 +01:00
mariiaKraievskaandAdam Saghy f070ff4f5d FINERACT-2354: Chargeback handling with backdated re-age - with default re-aging behaviour 2025-12-16 12:38:36 +01:00
Ahmed AdelandAdam Saghy 352ef6317c FINERACT-2326: Refactor trial balance processing to reduce cognitive complexity 2025-12-16 12:35:06 +01:00
dependabot[bot]andArnold Gálovics e5be162117 Bump actions/setup-node from 4.4.0 to 6.1.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.4.0 to 6.1.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/49933ea5288caeca8642d1e84afbd3f7d6820020...395ad3262231945c25e8478fd5baf05154b1d79f)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-16 09:00:58 +01:00
Mend RenovateandArnold Gálovics 7efbce7ee1 FINERACT-2181: Update actions/cache digest to 0057852 2025-12-15 15:58:00 +01:00
Mend RenovateandAdam Saghy 8ebb2e10dc FINERACT-2181: Update actions/setup-node digest to 49933ea 2025-12-15 13:33:15 +01:00
dependabot[bot]andAdam Saghy 8e2593b3f2 Bump actions/checkout from 5.0.1 to 6.0.1
Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.1 to 6.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/93cb6efe18208431cddfb8368fd83d5badbf9bfd...8e8c483db84b4bee98b60c0593521ed34d9990e8)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 09:48:17 +01:00
dependabot[bot]andAdam Saghy 6980a03c64 Bump actions/upload-artifact from 5.0.0 to 6.0.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/330a01c490aca151604b8cf639adc76d48f6c5d4...b7c566a772e6b6bfb58ed0dc250532a479d7789f)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 09:48:06 +01:00
dependabot[bot]andAdam Saghy efad92798e Bump actions/cache from 5.0.0 to 5.0.1
Bumps [actions/cache](https://github.com/actions/cache) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/a7833574556fa59680c1b7cb190c1735db73ebf0...9255dc7a253b0ccc959486e2bca901246202afeb)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 5.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 09:47:54 +01:00
MarianaDmytrivBinariksandAdam Saghy 9bcf30abac FINERACT-2354: e2e tests with equal amortization moved into separate feature file 2025-12-15 09:46:24 +01:00
Victor Romero 78bd68a017 FINERACT-2312: fix missing allowFullTermForTranche parameter for the Calculate the Loan Schedule 2025-12-12 20:45:07 -06:00
Mend RenovateandArnold Gálovics 36b22f2b6b FINERACT-2181: Update actions/checkout digest to 93cb6ef 2025-12-12 20:06:08 +01:00
Satvik MishraandArnold Gálovics d043f4cb61 FINERACT-2415: Refactor InlineLoanCOBExecutorServiceImpl to reduce duplication 2025-12-12 20:03:09 +01:00
Oleksii NovikovandArnold Gálovics 36d779eabf FINERACT-2398: Add handling for equal overdue interest split re-amortization 2025-12-12 20:02:06 +01:00
Peter KovacsandArnold Gálovics a229a55c49 FINERACT-2790: Re-amortization- Interest handling-Equal Overdue Interest Split - E2E tests 2025-12-12 20:02:06 +01:00
MarianaDmytrivBinariksandAdam Saghy 8e34003be9 FINERACT-2354: e2e tests for re-age preview with equal amortization 2025-12-12 14:54:48 +01:00
Soma SörösandAdam Saghy 77c1f33f5a FINERACT-2354: [BE] re-aging preview page outcomes with incorrect calculation for diff scenarios with interest bearing loans 2025-12-12 14:54:48 +01:00
dependabot[bot]andArnold Gálovics 85cc3d7132 Bump actions/cache from 4.3.0 to 5.0.0
Bumps [actions/cache](https://github.com/actions/cache) from 4.3.0 to 5.0.0.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/0057852bfaa89a56745cba8c7296529d2fc39830...a7833574556fa59680c1b7cb190c1735db73ebf0)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-12 13:49:18 +01:00
Adam Monsen a085f8c60b link to new vulnerabilities list 2025-12-11 13:50:23 -08:00
Adam Monsen 5468f7999d FINERACT-2416: improve PR template
I started editing this just to fix the link to the "Pull Requests" doc (it moved from README.md to CONTRIBUTING.md), but then I saw a few other places in need of service.

* use Markdown links for readability
* add link to pull request size limit wiki page re: code dumps
* remove link to PR 1284, it doesn't add anything here and takes the reader out of context
* add link to mailing list
* add link to testing docs
2025-12-11 13:44:50 -08:00
MarianaDmytrivBinariksandAdam Saghy ef8621b080 FINERACT-2354: e2e test scenario for re-age with equal amortization with interest pause 2025-12-11 17:09:38 +01:00
Adam Saghy c3ed5caab2 FINERACT-2389: EMI rounding handling 2025-12-11 17:08:57 +01:00
MarianaDmytrivBinariksandAdam Saghy 6d051f5a2f FINERACT-2354: e2e test scenario for disbursement amount less than one 2025-12-11 17:08:57 +01:00
Adam Saghy 767c66f245 FINERACT-2326: Fix external id generation during reinvest 2025-12-11 13:04:59 +01:00
Soma SörösandAdam Saghy 1d0100bfcf FINERACT-2354: e2e test scenarios for re-age with chargeback for last instalment strategy 2025-12-10 13:10:33 +01:00
Soma SörösandAdam Saghy fa5be3f5a5 FINERACT-2354: [BE] chargeback handling with backdated re-age for last adjustment strategy with equal amortization re-aging behaviour 2025-12-10 13:10:33 +01:00
Adam Saghy 1ad45759fd FINERACT-2354: Validation of Re-age amount during submission 2025-12-10 13:09:16 +01:00
mariiaKraievskaandGitHub 8644d66fc0 FINERACT-2326: Fix configuration options for CORS (#5210) 2025-12-09 06:52:34 -06:00
Attila BudaiandAdam Saghy a3fdfb920d FINERACT-2312: full term configuration added 2025-12-08 19:08:29 +01:00
Peter KovacsandAdam Saghy 035b90211c FINERACT-2398: Re-amortization:- Preview Schedule API during re-amortization - E2E tests 2025-12-08 14:33:07 +01:00
mariiaKraievskaandAdam Saghy ad90d98aa0 FINERACT-2398: Re-amortization:- Preview Schedule API during re-amortization 2025-12-08 14:33:07 +01:00
Peter KovacsandAdam Saghy c70edf65f8 FINERACT-2354: Schedule handling after re-age - points 4-5-6 2025-12-08 13:57:54 +01:00
Aman-MittalandAdam Saghy dce3c66def FINERACT-2326: Refractor redundant and some deprecated code as per new Java 21 style 2025-12-08 13:49:44 +01:00
Rustam ZeinalovandAdam Saghy 399789ba42 FINERACT-2354: added e2e tests verifying re-aging which changes the due dates and frequencies - Default Behavior, interestRecalculation=true 2025-12-08 11:56:27 +01:00
Rustam ZeinalovandAdam Saghy 3d8d122bb3 FINERACT-2398: added e2e test cases for validation of re-amortization:- Interest handling-Default behavior 2025-12-08 11:54:16 +01:00
mariiaKraievskaandAdam Saghy 3c4d8da1c8 FINERACT-2398: Re-amortization:- Interest handling-Default behavior 2025-12-08 11:54:16 +01:00
Adam Saghy 8391614330 FINERACT-2326: Add configuration options for CORS handling 2025-12-08 10:23:17 +01:00
dependabot[bot]andAdam Saghy df4f43091b Bump actions/setup-java from 5.0.0 to 5.1.0
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5.0.0 to 5.1.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/dded0888837ed1f317902acf8a20df0ad188d165...f2beeb24e141e01a676f977032f5a29d81c9e27e)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: 5.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 10:22:14 +01:00
Soma SörösandAdam Saghy de0dabd548 FINERACT-2354: [doc] Re-aging functional description 2025-12-08 09:53:44 +01:00
Jose Alberto HernandezandAdam Saghy a58bdf49b6 FINERACT-2354: Validation of Re-age amount during submission 2025-12-08 09:53:03 +01:00
Adam Saghy 4c22e2a9fc FINERACT-2326: Force single-threaded execution of tenant database upgrades 2025-12-05 15:50:41 +01:00
Adam SaghyandArnold Gálovics 469dfa48be FINERACT-2326: Add extra valid checksum since Liquibase changed on how checksums calculated 2025-12-05 15:20:52 +01:00
dependabot[bot]andAdam Saghy 1162e15ae5 Bump actions/setup-node from 4.4.0 to 6.1.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.4.0 to 6.1.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/49933ea5288caeca8642d1e84afbd3f7d6820020...395ad3262231945c25e8478fd5baf05154b1d79f)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-03 12:41:22 +01:00
dependabot[bot]andAdam Saghy 73ac7011fd Bump actions/stale from 10.1.0 to 10.1.1
Bumps [actions/stale](https://github.com/actions/stale) from 10.1.0 to 10.1.1.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/5f858e3efba33a5ca4407a664cc011ad407f2008...997185467fa4f803885201cee163a9f38240193d)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-version: 10.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-03 12:41:14 +01:00
dependabot[bot]andAdam Saghy 2d17811a0a Bump actions/checkout from 5.0.1 to 6.0.1
Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.1 to 6.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/93cb6efe18208431cddfb8368fd83d5badbf9bfd...8e8c483db84b4bee98b60c0593521ed34d9990e8)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-03 12:41:06 +01:00
Adam Saghy f55b63432e FINERACT-2326: Not implemented exception handling 2025-12-03 12:40:55 +01:00
Oleksii NovikovandAdam Saghy c50ed19959 FINERACT-1981: Fix reschedule installment for progressive loans 2025-12-03 10:14:29 +01:00
MarianaDmytrivBinariksandAdam Saghy 9e32b3718d FINERACT-2354: e2e test scenarios with multidisburse and CI after re-aging 2025-12-03 09:48:15 +01:00
Siddharthan P SandAdam Saghy 3d3b2dad6f FINERACT-2316: Localize interest rate chart slab validation
- Add localized validation messages for overlap and gap errors in interest rate chart slabs
- Use DataValidatorBuilder with resource/parameter context for clean error codes
- Add unit tests for validation error codes and arguments
- Add German translations for validation messages
2025-12-03 09:44:09 +01:00
Adam SaghyandGitHub 270901b9a2 FINERACT-2326: Downgrade liquibase due to licensing concerns (#5190) 2025-12-02 13:34:48 -06:00
Mend RenovateandAdam Saghy 2a17485dbb FINERACT-2181: Update actions/setup-node digest to 49933ea 2025-12-02 10:15:49 +01:00
Mend RenovateandAdam Saghy c80b00b211 FINERACT-2181: Update actions/checkout digest to 93cb6ef 2025-12-02 10:15:30 +01:00
MarianaDmytrivBinariksandAdam Saghy 949fcc9e0e FINERACT-2389: e2e test scenarios with pay-off after contract termination 2025-12-01 19:12:22 +01:00
Jose Alberto HernandezandAdam Saghy a5a290debc FINERACT-2389: Pay-Off amount calculated after Contract Termination 2025-12-01 19:12:22 +01:00
Aman-MittalandAdam Saghy 00e9ac8320 FINERACT-2346: Refractor, Minor Sonar fixes and remove some deprecated code 2025-12-01 19:11:59 +01:00
Attila BudaiandAdam Saghy 73d8659390 FINERACT-2354: add reaging interest template data 2025-12-01 15:09:11 +01:00
Peter KovacsandAdam Saghy 25fd822b00 FINERACT-2354: Re-age - Excess payment handling - Default Behavior, interestRecalculation = true, without dueDate change - missing testRailIds added 2025-12-01 13:15:42 +01:00
Rustam ZeinalovandAdam Saghy f487e4f6c2 FINERACT-2354: Added e2e tests for partially paid installment - adjust to last edge case of re-aging 2025-12-01 13:15:42 +01:00
mariiaKraievskaandAdam Saghy b18912cc9d FINERACT-2354: Re-age - Excess payment handling - Default Behavior, interestRecalculation = true 2025-12-01 13:15:42 +01:00
MarianaDmytrivBinariksandAdam Saghy 2bde43ea83 FINERACT-2354: e2e test scenarios for reverse-replay re-aging with equal amortization 2025-11-26 12:28:33 +01:00
Ralph HopmanandArnold Gálovics a9d5307fe5 FINERACT-2411: Allow empty columns to be deleted from datatables 2025-11-26 11:45:53 +01:00
Mend RenovateandArnold Gálovics e5b6268123 FINERACT-2181: Update groovy monorepo to v5 2025-11-26 11:44:58 +01:00
Rustam ZeinalovandAdam Saghy 8f1589cb58 FINERACT-2354: Re-aging: EQUAL AMORTIZATION BEHAVIOUR - Reversal of Re-aging transaction - extra E2E tests 2025-11-26 11:30:55 +01:00
Adam Saghy f28382b70f FINERACT-2380: move retrofit client based test to feign 2025-11-26 10:47:29 +01:00
Attila BudaiandAdam Saghy 5d2a9c5390 FINERACT-2380: move retrofit client based test to feign 2025-11-26 10:47:29 +01:00
dependabot[bot]andAdam Saghy 6204cde27c Bump actions/checkout from 5.0.0 to 6.0.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/08c6903cd8c0fde910a37f88322edcfb5dd907a8...1af3b93b6815bc44a9784bd300feb67ff0d1eeb3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-25 13:04:35 +01:00
Mend RenovateandAdam Saghy 0d8ae9f4cf FINERACT-2181: Update dependency org.jruby:jruby-complete to v10 2025-11-25 13:04:03 +01:00
Peter KovacsandAdam Saghy 2595224ff1 FINERACT-2354: Re-aging: EQUAL AMORTIZATION BEHAVIOUR - Reversal of Re-aging transaction - E2E tests 2025-11-25 13:01:43 +01:00
Adam Saghy fdf3568081 FINERACT-2326: Update Transaction Summary report with txn classification details 2025-11-25 10:38:57 +01:00
MarianaDmytrivBinariksandAdam Saghy 5784cb7edb FINERACT-2354: e2e test scenarios for re-aging with payable and full interest handling options 2025-11-21 15:02:34 +01:00
Soma SörösandAdam Saghy 747121bbe2 FINERACT-2354: Re-aging: -Interest Handling Option: Equal amortization 2025-11-21 15:02:34 +01:00
Jose Alberto HernandezandAdam Saghy 47e8674fa0 FINERACT-2405: totalUnpaidPayableNotDueInterest resets to 0 on Charge-Off 2025-11-19 19:58:33 +01:00
Jose Alberto HernandezandAdam Saghy 2681ee3518 FINERACT-2389: Pay-Off amount incorrectly calculated when downpayment 2025-11-19 16:45:47 +01:00
Jose Alberto HernandezandAdam Saghy 60526f0ce9 FINERACT-2389: Negative amount is shown on repayment schedule 2025-11-18 14:28:20 +01:00
Akshat SoniandArnold Gálovics 9a16a26d5f FINERACT-2407: add precheck 2025-11-17 12:56:01 +01:00
Akshat SoniandArnold Gálovics ce813d877c FINERACT-2407: add permission READ_FAMILYMEMBERS id removed field=id 2025-11-17 12:56:01 +01:00
Akshat SoniandArnold Gálovics 7ed0f4d991 FINERACT-2407: add permission READ_FAMILYMEMBERS 2025-11-17 12:56:01 +01:00
Mend RenovateandArnold Gálovics 8711258875 FINERACT-2181: Update actions/stale action to v10 2025-11-17 12:54:48 +01:00
Mend RenovateandArnold Gálovics dfd00bf119 FINERACT-2181: Update ghcr.io/navikt/mock-oauth2-server Docker tag to v3 2025-11-17 12:54:11 +01:00
Mend RenovateandArnold Gálovics 0d1a542de1 FINERACT-2181: Update dependency org.yaml:snakeyaml to v2 2025-11-17 12:54:03 +01:00
Peter KovacsandArnold Gálovics 6f6a1d9ea2 FINERACT-1724: [doc] Downpayment user documentation 2025-11-17 08:36:53 +01:00
Adam Saghy da1f310d43 FINERACT-2326: Fix duplicate reverse replay of ‘Accrual Activity’ during reprocessing 2025-11-13 18:15:18 +01:00
Peter KovacsandAdam Saghy 1b3217be39 FINERACT-2326: Loan contract termination same disbursement date - E2E 2025-11-13 12:47:46 +01:00
Jose Alberto HernandezandAdam Saghy a0b31bbb25 FINERACT-2326: Loan contract termination same disbursement date 2025-11-13 12:47:46 +01:00
Rustam ZeinalovandAdam Saghy fdb9e5518c FINERACT-2354: Fourth step - Chargeback before re-age edge case for Interest bearing loans - updated e2e scenario tags 2025-11-13 12:46:26 +01:00
mariiaKraievskaandAdam Saghy 4c238801ac FINERACT-2354: Fourth step - Chargeback before re-age edge case for Interest bearing loans - Default Behavior, interestRecalculation = true, without dueDate change 2025-11-13 12:46:26 +01:00
MarianaDmytrivBinariksandAdam Saghy 0d737b21cd FINERACT-2354: e2e test scenarios for re-aging reverse-replay for default behaviour 2025-11-13 12:43:53 +01:00
mariiaKraievskaandAdam Saghy 5ca10eb1f9 FINERACT-2354: Re-aging: DEFAULT BEHAVIOUR - Reverse Replay handling, additional test case 2025-11-13 12:43:53 +01:00
Adam Saghy c79c076ee6 FINERACT-2354: In case full transaction reprocessing, we need to recalculate the schedule as well 2025-11-13 08:44:22 +01:00
Adam Saghy 051571003e FINERACT-2354: Work with DTOs instead of entities inside ProgressiveEMICalculator 2025-11-13 08:44:22 +01:00
Rustam ZeinalovandAdam Saghy 068e398f90 FINERACT-2354: Addding e2e tests covering - backdated re-age edge case for interest bearing loans 2025-11-13 08:44:22 +01:00
mariiaKraievskaandAdam Saghy 5a1c6edbef FINERACT-2354: Third step - backdated re-age edge case for Interest bearing loans - Default Behavior, interestRecalculation = true, without dueDate change 2025-11-13 08:44:22 +01:00
MarianaDmytrivBinariksandAdam Saghy 01ca515e8f FINERACT-2354: e2e test scenarios for re-aging preview 2025-11-11 12:04:51 +01:00
Oleksii NovikovandAdam Saghy 3efafa778e FINERACT-2354: Introduce reaging preview API 2025-11-11 12:04:51 +01:00
Mend RenovateandArnold Gálovics ed8e9d1376 FINERACT-2181: Update dependency org.liquibase.ext:liquibase-postgresql to v5 2025-11-11 10:03:34 +01:00
Mend RenovateandArnold Gálovics 4e71edda97 FINERACT-2181: Update dependency org.liquibase:liquibase-core to v5 2025-11-11 10:03:22 +01:00
Mend RenovateandArnold Gálovics d0e3dff93a FINERACT-2181: Update dependency io.github.bonigarcia:webdrivermanager to v6 2025-11-11 08:55:16 +01:00
Arnold GalovicsandArnold Gálovics fe4c970e05 FINERACT-2389: Fix for starting up in Liquibase only mode and pipeline to verify this to prevent breaking 2025-11-07 11:18:30 +01:00
Oleksii NovikovandAdam Saghy 9a83ca7787 FINERACT-2181: Add e2e test to verify repayment reversal after adding a fee charge with transaction reprocessing 2025-11-07 11:15:39 +01:00
Peter KovacsandAdam Saghy 0f4e68567f FINERACT-2354: Reversal of re-aging transaction - E2E tests 2025-11-07 10:23:26 +01:00
Peter KovacsandAdam Saghy 1b2801447b FINERACT-2354: Reversal of re-aging transaction - remove restriction of reversal if there was payment after or on that day 2025-11-07 10:23:26 +01:00
cucusenokandAdam Saghy 9320dd1bd7 FINEREACT-2404: Improved extensibility and cleanup 2025-11-07 10:19:40 +01:00
MarianaDmytrivBinariksandAdam Saghy d269cb983f FINERACT-2406: e2e test scenarios for flush mode while reprocessing 2025-11-06 21:12:58 +01:00
Adam Saghy ccf760c79d FINERACT-2406: Set COMMIT flush mode during transaction processing 2025-11-06 21:12:58 +01:00
Arnold GalovicsandArnold Gálovics 5079203d57 FINERACT-2389: Fix for rounding mode not being initialized whenever a new tenant is added to the system without a restart 2025-11-06 14:46:25 +01:00
MarianaDmytrivBinariksandAdam Saghy 63c220e5d2 FINERACT-2403: e2e test scenarios for last emi payment 2025-11-03 14:43:52 +01:00
Adam Saghy dc0162c09a FINERACT-2403: Fix incorrect EMI calculation of last installment 2025-11-03 14:43:52 +01:00
alburquerquerangelarturoandAdam Saghy 5c8d86cba5 FINERACT-2394:Fixed Deposits is creating double Interest Postings 2025-11-03 10:59:54 +01:00
Aman MittalandAdam Monsen 8c3c44b618 FINERACT-2402: Replace delimiter : with = in kafka-server.env 2025-10-31 13:52:18 -07:00
Adam SaghyandArnold Gálovics 774e164601 FINERACT-2389: Update Transaction Summary Report with Asset Owner report 2025-10-31 12:31:17 +01:00
MarianaDmytrivBinariksandAdam Saghy ba05687292 FINERACT-2401: e2e test scenarios interest pause biz events 2025-10-31 09:58:06 +01:00
Oleksii NovikovandAdam Saghy 1b4abd18a9 FINERACT-2401: Add business events for interest pause actions 2025-10-31 09:58:06 +01:00
5ea7c36804 FINERACT-2402: Replace the legacy Bitnami Kafka Docker image with the official Apache Kafka image. (#5131)
Co-authored-by: Aman Mittal <aman.mittal@cjdarcl.com>
2025-10-30 10:04:09 -06:00
mariiaKraievskaandAdam Saghy 67e823f926 FINERACT-2354: Second step - charge-off edge case of re-aging for Interest bearing loans - Default Behavior, interestRecalculation = true, without dueDate change 2025-10-29 13:30:38 +01:00
Andreas RøsdalandAdam Saghy 6386eb142b FINERACT-2389: Upgrade OpenPDF to v3 2025-10-29 12:38:18 +01:00
Jose Alberto HernandezandAdam Saghy a849e9bae3 FINERACT-2398: Re-amortization Interest Handling configuration 2025-10-29 12:13:05 +01:00
Peter KovacsandAdam Saghy 8a86759773 FINERACT-2389: Fix the handling of nullable field overrides - E2E tests 2025-10-29 11:26:16 +01:00
Oleksii NovikovandAdam Saghy adc4ee3545 FINERACT-2389: Fix the handling of nullable field overrides 2025-10-29 11:26:16 +01:00
Adam Saghy b32837e9b4 FINERACT-2390: Fix infinite loop issue 2025-10-28 09:25:47 +01:00
MarianaDmytrivBinariksandAdam Saghy 1596d09e54 FINERACT-2390: e2e test scenarios for infinite loop with MIR trn issue 2025-10-28 09:25:47 +01:00
dependabot[bot]andArnold Gálovics 331a762a38 Bump actions/upload-artifact from 4.6.2 to 5.0.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 5.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/ea165f8d65b6e75b540449e92b4886f43607fa02...330a01c490aca151604b8cf639adc76d48f6c5d4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-28 07:54:41 +01:00
mariiaKraievskaandAdam Saghy d21a1a98a2 FINERACT-2354: First step - basic implementation of re-aging for Interest bearing loans - Default Behavior, interestRecalculation = true, without dueDate change 2025-10-27 14:22:46 +01:00
Rustam ZeinalovandAdam Saghy 23c1b2b8af FINERACT-2354: added e2e tests for validation of re-aging for interest bearing products with interest calculation: default behavior 2025-10-27 14:22:46 +01:00
MarianaDmytrivBinariksandAdam Saghy e95e4da65e FINERACT-2385: e2e test scenarios for re-aging disallowed with zero amount 2025-10-27 10:45:14 +01:00
Oleksii NovikovandAdam Saghy 4ae6fd5694 FINERACT-2385: Zero amount reage transaction should not be allowed 2025-10-27 10:45:14 +01:00
MarianaDmytrivBinariksandAdam Saghy 54e85c748d FINERACT-2382: e2e test scenarios for flat cumulative multidisbursal loan 2025-10-27 10:14:46 +01:00
Jose Alberto HernandezandAdam Saghy 2269d616dc FINERACT-2382: Repayment schedule for Flat-Cumulative-Multi Disbursement 2025-10-27 10:14:46 +01:00
Adam SaghyandArnold Gálovics 567b12170b FINERACT-2389: Improve E2E test data initialization resilience 2025-10-24 08:52:24 +02:00
Adam Saghy 6532b8d522 FINERACT-2389: Automatically close loans at the end of test executions 2025-10-23 00:18:49 +02:00
Adam MonsenandGitHub 4ea19469cb FINERACT-2397: update release manager docs post-1.13.0 release (#5116)
* release step 6: emphasize "re-run the command above"
* release step 9: link to Database Setup in e2e or integration section (currently links to e2e since `cucumber.adoc` precedes `integration.adoc` in `fineract-doc/src/docs/en/chapters/testing/index.adoc`)
* release step 10: improve rc verify instructions in voting email, based on https://lists.apache.org/thread/jk2qzjxc89hd1c8xzvhtmlbbj1m8ktbl - thread: "release process improvement (was: 🗳️ 1.13.0 for release)"
* release step 15: use current mission statement from top-level readme in release announcement email
2025-10-22 08:44:22 -06:00
Peter KovacsandAdam Saghy d37b45251f FINERACT-2326: fix delinquency date calculation after pause - E2E test 2025-10-22 13:17:32 +02:00
Attila BudaiandAdam Saghy 13170dc551 FINERACT-2326: fix delinquent days & delinquency date after delinquency pause calculations 2025-10-22 13:17:32 +02:00
Attila BudaiandAdam Saghy 967dbed021 FINERACT-2380: feign related test fixes 2025-10-21 19:48:41 +02:00
Adam Saghy d6354ee408 FINERACT-2396: Fix retry issue during persisting CommandSource 2025-10-21 18:38:05 +02:00
Adam Monsen a561264f81 Merge branch 'release/1.13.0' into develop
* release/1.13.0:
  don't include actual email addresses
  save 1.13.0 vote result
  shorten/simplify gpg key sending commands
  update link to rc verification instructions
  fix typos in step 9 re: running the binary
  improve step 9 artifact verification gpg tips
  fix tarball explosion in release step 9 🧯
  cucumber.adoc: fix list continuations
  further improve release steps 6 and 9
  release step 6: fix sanity check example
  improve staging doc and commands in release step 8
  doc: remove errant config but keep :sectlinks:
  clean during artifact build release step 6
  FINERACT-2326: Do not remove external id if transaction got not replayed
  FINERACT-2326: Fix `Trial Balance Summary with asset owner` report
  FINERACT-2326: Loan point in time API now properly handles future dates
  improve release branch email template
  improve release process kickoff email template
2025-10-20 08:54:29 -07:00
Adam Monsen 826608f6cc don't include actual email addresses
might help voters avoid a bit of spam
2025-10-20 08:05:08 -07:00
Rustam ZeinalovandAdam Saghy 56b69d6fa0 FINERACT-2389: added e2e test to validate loan rescheduling on the first day of 1st repayment schedule 2025-10-20 15:46:37 +02:00
Adam Saghy d42ba45c9a FINERACT-2389: Fix related installment query 2025-10-20 15:46:37 +02:00
Adam Monsen ccfce92a3b save 1.13.0 vote result 2025-10-19 09:50:06 -07:00
Attila BudaiandAdam Saghy 553347b320 FINERACT-2380: create feign client 2025-10-17 14:25:39 +02:00
NickeshandAdam Saghy f193bbbdf1 FINERACT-2391: check for AppUser type before casting Authentication Principal 2025-10-15 15:02:25 +02:00
Jose Alberto HernandezandAdam Saghy b9e6f21e18 FINERACT-2354: Re-aging-for-interest-bearing-products 2025-10-15 11:38:10 +02:00
Aman-MittalandAdam Saghy c50113a868 FINERACT-2346: Minor SonarQube fixes 2025-10-15 10:05:25 +02:00
Peter KovacsandArnold Gálovics 3b62d562a2 FINERACT-2358: Advanced-accounting-rules-based-on-write-off-reason E2E tests 2025-10-15 09:52:16 +02:00
Jose Alberto HernandezandArnold Gálovics c518d8622a FINERACT-2358: Advanced accounting rules based on write-off reason 2025-10-14 20:26:24 +02:00
dependabot[bot]andArnold Gálovics bef931ca17 Bump actions/setup-node from 5.0.0 to 6.0.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/a0853c24544627f65ddf259abe73b1d18a591444...2028fbc5c25fe9cf00d9f06a71cc4710d4507903)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-14 20:18:49 +02:00
Felix van HoveandGitHub 1a2ab056e9 FINERACT-2383: Modernize and correct README page (#5076)
see

https://github.com/apache/fineract/pull/5076

https://issues.apache.org/jira/browse/FINERACT-2383

https://lists.apache.org/thread/6nctwb6g6krxxtcxr3q1blx2xb9w8h1s
2025-10-14 09:23:04 -07:00
dependabot[bot]andArnold Gálovics 54d7f8bae8 Bump gradle/actions from 4 to 5
Bumps [gradle/actions](https://github.com/gradle/actions) from 4 to 5.
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/v4...v5)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-14 16:01:59 +02:00
Arnold GalovicsandArnold Gálovics 1bf71fb561 FINERACT-2326: Minor fix to generate IDs for saved interest transaction for loans 2025-10-14 12:27:35 +02:00
Mend RenovateandArnold Gálovics 09e0b97b22 FINERACT-2181: Update actions/setup-java action to v5 2025-10-14 12:27:22 +02:00
Mend RenovateandArnold Gálovics f366f441b4 FINERACT-2181: Update actions/setup-node digest to 49933ea 2025-10-14 12:27:12 +02:00
Adam Monsen 95e6cea504 shorten/simplify gpg key sending commands
also: change <3> to match text above, omitting otherwise helpful 0x hex designation prefix
2025-10-13 09:40:15 -07:00
MarianaDmytrivBinariksandArnold Gálovics a58db39a74 FINERACT-2326: added e2e auto tests for accrual reversal logic issue 2025-10-13 15:24:21 +02:00
Adam SaghyandArnold Gálovics 469489d408 FINERACT-2389: Update Transaction Summary with Asset Owner report 2025-10-10 19:32:21 +02:00
Adam Saghy ebae20654a FINERACT-2326: Do not remove external id if transaction got not replayed 2025-10-08 21:33:04 +02:00
Adam Saghy b692248a8a FINERACT-2326: Fix Trial Balance Summary with asset owner report 2025-10-08 17:05:02 +02:00
Arnold GalovicsandArnold Gálovics c9c9e9f835 FINERACT-2326: Loan point in time API now properly handles future dates 2025-10-08 10:29:59 +02:00
Mend RenovateandArnold Gálovics ccfe062649 FINERACT-2181: Update actions/checkout action to v5 2025-10-08 08:52:25 +02:00
2073 changed files with 614740 additions and 35059 deletions
+27
View File
@@ -0,0 +1,27 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# See comment below license in .atr-rat-excludes-src.txt for an explanation of
# what this is.
# if you make changes here, sort this list before committing asciibetically
# with en_US.UTF-8 lang/locale
.rat-excludes
README.md
fineract-documentation.pdf
# vim:ft=conf
+54
View File
@@ -0,0 +1,54 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# Path patterns to exclude from RAT checks run by ATR.
#
# RAT = Release Audit Tool (https://creadur.apache.org/rat/)
# ATR = ASF Trusted Releases (https://release-test.apache.org , https://github.com/apache/tooling-trusted-releases/)
#
# This duplicates exclusions in build.gradle.
# This is probably only necessary until both RAT and org.nosphere.apache.rat plugin are using 0.17.
# See https://github.com/eskatos/creadur-rat-gradle/issues/28 .
# Once both are on 0.17, we should be able to move the RAT excludes used during
# the build from build.gradle into a .rat-excludes file shared by both our
# build and ATR.
# Right now ATR does something custom, see
# https://github.com/apache/tooling-trusted-releases/issues/312 .
# If we get tired of waiting for the RAT gradle plugin or the ATR to upgrade to
# 0.17, we could write our own exclusion lists generator.
# if you make changes here, sort this list before committing asciibetically
# with en_US.UTF-8 lang/locale
**/META-INF/fineract-test.config
**/legacy-docs/
*.adoc
*.avsc
*.feature
*.html
*.md
*.mustache
*.puml
*.txt
.openapi-generator-ignore
/.asf.yaml
/.rat-excludes
/config/fineractdev-eclipse-preferences.epf
/fineract-provider/src/test/resources/results/xbrl.xml
/gradle/wrapper/gradle-wrapper.properties
org.springframework.boot.autoconfigure.AutoConfiguration.imports
# vim:ft=conf
+93
View File
@@ -0,0 +1,93 @@
import hashlib
import json
import os
import sys
import tempfile
from datetime import datetime, timezone
from http.client import HTTPConnection, HTTPException
from pathlib import Path
import traceback
from contextlib import closing
from typing import Optional
WEBSERVER_HOST = "localhost"
WEBSERVER_ENDPOINT = "/api/provenance/call"
PORT_FILE_SUFFIX = "-provenance-port.txt"
class ProvenanceHookError(RuntimeError):
pass
def http_request(method, host, port, location, *, body: Optional[bytes] = None, headers={}, timeout=None) -> bytes:
with closing(HTTPConnection(host, port, timeout=timeout)) as connection:
connection.request(method, location, body=body, headers=headers)
def get_server_port():
claude_root = os.getenv("CLAUDE_PROJECT_DIR")
path_hash = hashlib.md5(claude_root.encode('utf-8')).hexdigest()
port_file = Path(tempfile.gettempdir()) / (path_hash + PORT_FILE_SUFFIX)
return int(port_file.read_text("utf-8").strip())
def send_diff_to_webserver(file_path, timestamp_ms):
try:
port = get_server_port()
except FileNotFoundError as e:
raise ProvenanceHookError(
f"Could not determine API port: {e.filename} does not exist") from e
except Exception as e:
raise ProvenanceHookError("Could not determine API port") from e
url = f"http://{WEBSERVER_HOST}:{port}{WEBSERVER_ENDPOINT}"
try:
payload = {"file_path": file_path, "timestamp": timestamp_ms}
return http_request(
"POST",
WEBSERVER_HOST,
port=port,
location=WEBSERVER_ENDPOINT,
body=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
headers={'Content-Type': 'application/json'},
timeout=0.5
)
except (HTTPException, OSError, ConnectionError) as e:
raise ProvenanceHookError(
f"Network error while sending diff to {url}") from e
except Exception as e:
raise ProvenanceHookError(
f"Unknown error while sending diff to {url}") from e
def extract_file_path(tool_name, tool_input):
if tool_name in ["Write", "Edit", "MultiEdit"]:
return tool_input.get('file_path', 'unknown')
if tool_name == "NotebookEdit":
return tool_input.get('notebook_path', 'unknown')
return 'unknown'
def excepthook(type, value, traceback_):
traceback.print_exception(type, value, traceback_, file=sys.stderr)
sys.exit(1)
def main():
data = json.load(sys.stdin)
tool_name = data.get('tool_name', 'unknown')
modification_tools = [
"Write", "Edit", "MultiEdit", "NotebookEdit"
]
if tool_name in modification_tools:
tool_input = data.get('tool_input', {})
file_path = extract_file_path(tool_name, tool_input)
if file_path:
timestamp_ms = int(datetime.now(timezone.utc).timestamp() * 1000)
send_diff_to_webserver(file_path, timestamp_ms)
if __name__ == "__main__":
sys.excepthook = excepthook
sys.exit(main())
+1
View File
@@ -0,0 +1 @@
{"sessionId":"0fda6e48-a79d-4980-a9ee-31a6d5f735e1","pid":15548,"acquiredAt":1776168814856}
+30
View File
@@ -0,0 +1,30 @@
{
"permissions": {
"allow": [
"Bash(git remote add self-hosted https://git.zeptic.in/CBSNexora/JAVA-Backend-CBS-MAIN.git)",
"Bash(git push self-hosted develop:main)",
"WebFetch(domain:rbi.org.in)",
"Bash(xargs grep -l \"@Scheduled\")",
"Bash(./gradlew compileJava -x spotlessCheck)",
"Bash(./gradlew bootJar -x test -x spotlessCheck)",
"Bash(./gradlew :fineract-provider:compileJava -x spotlessCheck)",
"Bash(./gradlew :fineract-core:compileJava :fineract-provider:compileJava)",
"Bash(curl -sk -o /dev/null -w \"%{http_code}\" https://localhost:8443/fineract-provider/api/v1/rbi-feeds)",
"Bash(./gradlew bootRun -x test)",
"Bash(ls *.gradle)",
"Bash(curl -sk -o /dev/null -w \"%{http_code}\" https://localhost:8443/fineract-provider/api)",
"Bash(ls c:/Users/adity/Music/CBS/fineract/fineract-provider/src/main/resources/db/changelog/tenant/parts/0232*)",
"Bash(taskkill //F //PID 36040)",
"Bash(findstr :8443)",
"Bash(findstr LISTENING)",
"Bash(taskkill //F //PID 14668)",
"Bash(./gradlew --stop)"
],
"additionalDirectories": [
"C:\\Users\\adity\\Music\\CBS\\UI\\web-app\\src\\app\\rbi-circulars",
"C:\\Users\\adity\\Music\\CBS\\UI\\web-app\\src\\app\\clients\\create-client",
"C:\\Users\\adity\\Music\\CBS\\UI\\web-app\\src\\app\\clients\\client-stepper\\client-general-step",
"C:\\Users\\adity\\Music\\CBS\\UI\\web-app\\src\\app"
]
}
}
+76
View File
@@ -0,0 +1,76 @@
{
"permissions": {
"allow": [
"Bash(taskkill //PID 24168 //F)",
"Bash(PGPASSWORD=postgres psql -h localhost -U postgres -d fineract_default -c \"SELECT id, display_name, date_of_birth FROM m_client ORDER BY id DESC LIMIT 5;\")",
"Bash(taskkill //PID 10112 //F)",
"Bash(taskkill //PID 39420 //F)",
"Bash(curl -sk -u \"mifos:password\" -H \"fineract-platform-tenantid: default\" \"https://localhost:8443/fineract-provider/api/v1/rbi-feeds\")",
"Bash(sort -t: -k2 -n)",
"Bash(curl -sk -u \"mifos:password\" -H \"fineract-platform-tenantid: default\" \"https://localhost:8443/fineract-provider/api/v1/rbi-feeds/144\")",
"Bash(curl -sk -u \"mifos:password\" -H \"fineract-platform-tenantid: default\" \"https://localhost:8443/fineract-provider/api/v1/openproject/work-packages?pageSize=10&offset=1\")",
"Bash(curl -sk -u \"mifos:password\" -H \"fineract-platform-tenantid: default\" -H \"Content-Type: application/json\" -X POST \"https://localhost:8443/fineract-provider/api/v1/rbi-feeds/sync-openproject\" -d '{}')",
"Bash(taskkill //PID 36832 //F)",
"Bash(mvn clean:*)",
"Bash(where mvn.cmd)",
"Bash(where mvn:*)",
"Read(//c/Program Files/**)",
"Read(//c/Users/adity/**)",
"Bash(echo \"$MAVEN_HOME\")",
"Bash(xargs basename:*)",
"Bash(./gradlew :fineract-tax:compileJava)",
"Bash(grep -n \"INSERT INTO \\\\`m_role\\\\`\\\\|INSERT INTO m_role\\\\|INSERT INTO \\\\`m_permission\\\\`\\\\|INSERT INTO m_permission\" /c/Users/adity/Music/CBS/fineract/fineract-provider/src/main/resources/sql/migrations/sample_data/load_sample_data.sql)",
"Bash(npm start:*)",
"Bash(grep -oP \"^\\\\\\\\\\(''''\\([^'''']+\\)''''\" \"c:\\\\Users\\\\adity\\\\Music\\\\CBS\\\\fineract\\\\fineract-db\\\\old-schema-files\\\\0003-mifosx-permissions-and-authorisation-utf8.sql\")",
"Bash(tasklist)",
"Bash(wmic process:*)",
"Bash(powershell -Command \"Get-CimInstance Win32_Process -Filter \"\"Name=''java.exe''\"\" | Select-Object ProcessId, @{N=''Cmd'';E={$_.CommandLine.Substring\\(0, [Math]::Min\\(200, $_.CommandLine.Length\\)\\)}} | Format-Table -AutoSize\")",
"Bash(psql -U postgres -d fineract_default -c \"UPDATE databasechangelog SET md5sum = NULL WHERE id = ''0231-1'' AND filename = ''db/changelog/tenant/parts/0231_add_menu_permissions.xml'';\" -c \"UPDATE m_permission SET grouping = ''menu'' WHERE code LIKE ''MENU_%'' AND entity_name = ''MENU'';\")",
"Bash(PGPASSWORD=root psql -U postgres -d fineract_default -c \"UPDATE databasechangelog SET md5sum = NULL WHERE id = ''0231-1'' AND filename = ''db/changelog/tenant/parts/0231_add_menu_permissions.xml'';\" -c \"UPDATE m_permission SET grouping = ''menu'' WHERE code LIKE ''MENU_%'' AND entity_name = ''MENU'';\")",
"Bash(PGPASSWORD=root psql -U postgres -d fineract_default -c \"UPDATE databasechangelog SET md5sum = NULL WHERE id = ''0231-1'' AND filename = ''db/changelog/tenant/parts/0231_add_menu_permissions.xml'';\" -c \"UPDATE m_permission SET grouping = ''heading_menu'' WHERE code LIKE ''MENU_%'' AND entity_name = ''MENU'';\")",
"Bash(grep -l \"pan_full_name\\\\|pan_category\\\\|pan_status\\\\|pan_aadhaar\" \"c:\\\\Users\\\\adity\\\\Music\\\\CBS\\\\fineract\\\\fineract-provider\\\\src\\\\main\\\\resources\\\\db\\\\changelog\\\\tenant\\\\parts\"/*.xml)",
"Bash(grep -l \"m_client_kyc\" \"c:\\\\Users\\\\adity\\\\Music\\\\CBS\\\\fineract\\\\fineract-provider\\\\src\\\\main\\\\resources\\\\db\\\\changelog\\\\tenant\\\\parts\"/*.xml)",
"Bash(./gradlew devRun -x test -x spotlessCheck)",
"Bash(taskkill //F //FI \"IMAGENAME eq java.exe\")",
"Bash(./gradlew bootRun)",
"Bash(curl -sk -X POST \"https://localhost:8443/fineract-provider/api/v1/authentication?username=mifos&password=password\" -H 'Fineract-Platform-TenantId: default' -H 'Content-Type: application/json')",
"Bash(curl -sk \"https://localhost:8443/fineract-provider/api/v1/clients?limit=5\" -H \"Fineract-Platform-TenantId: default\" -u \"mifos:password\")",
"Bash(curl -sk -X POST \"https://localhost:8443/fineract-provider/api/v1/authentication\" -H \"Fineract-Platform-TenantId: default\" -H \"Content-Type: application/json\" -d '{\"\"username\"\":\"\"mifos\"\",\"\"password\"\":\"\"password\"\"}')",
"Bash(curl -sk \"https://localhost:8443/fineract-provider/api/v1/clients?limit=5\" -H \"Fineract-Platform-TenantId: default\" -H \"Authorization: Basic bWlmb3M6cGFzc3dvcmQ=\")",
"Bash(./gradlew :fineract-provider:devRun --console=plain)",
"Bash(curl -k -o /tmp/test-helper.jar -w \"HTTP %{http_code} size=%{size_download} bytes\\\\n\" https://localhost:8443/fineract-provider/api/v1/dsc/helper.jar)",
"Bash(powershell -Command \"Get-Process java -ErrorAction SilentlyContinue | Select-Object Id, StartTime | Format-List\")",
"Bash(./gradlew :fineract-provider:compileJava --console=plain)",
"Bash(./gradlew :fineract-provider:compileJava :fineract-security:compileJava -q)",
"Bash(./gradlew :fineract-provider:compileJava -q)",
"PowerShell(Get-NetTCPConnection -LocalPort 8443 -State Listen -ErrorAction SilentlyContinue | Select-Object LocalPort, OwningProcess, @{Name='ProcessName';Expression={\\(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue\\).ProcessName}}, @{Name='StartTime';Expression={\\(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue\\).StartTime}})",
"PowerShell(Get-NetTCPConnection -LocalPort 8443 -ErrorAction SilentlyContinue | Format-Table LocalPort, State, OwningProcess, @{Name='ProcessName';Expression={\\(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue\\).ProcessName}} -AutoSize; netstat -ano | Select-String \":8443\")",
"PowerShell(Get-Process -Id 9340 -ErrorAction SilentlyContinue | Select-Object Id, ProcessName, StartTime, @{Name='MemMB';Expression={[math]::Round\\($_.WorkingSet64/1MB,0\\)}}, Path | Format-List)",
"Bash(git -c core.editor=true rebase --continue)",
"Bash(git rebase *)",
"Bash(rm -rf .git/rebase-merge; git status -sb | head -3)",
"Bash(git -c commit.gpgsign=false commit -m ' *)",
"Bash(git pull *)",
"Bash(./gradlew :fineract-security:compileJava :fineract-provider:compileJava -q)",
"Bash(git ls-remote *)"
],
"additionalDirectories": [
"c:\\Users\\adity\\Music\\CBS\\cfcfrms-bank-api",
"C:\\Users\\adity\\Music\\CBS\\UI\\web-app\\src\\assets\\translations",
"C:\\Users\\adity\\Music\\CBS\\fineract\\.git\\rebase-merge"
]
},
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit|NotebookEdit",
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/post-tool-call.py\""
}
]
}
]
}
}
+6 -8
View File
@@ -1,18 +1,16 @@
## Description
Describe the changes made and why they were made.
Ignore if these details are present on the associated [Apache Fineract JIRA ticket](https://github.com/apache/fineract/pull/1284).
Describe the changes made and why they were made. (Ignore if these details are present on the associated Apache Fineract JIRA ticket.)
## Checklist
Please make sure these boxes are checked before submitting your pull request - thanks!
- [ ] Write the commit message as per https://github.com/apache/fineract/#pull-requests
- [ ] Write the commit message as per [our guidelines](https://github.com/apache/fineract/blob/develop/CONTRIBUTING.md#pull-requests)
- [ ] Acknowledge that we will not review PRs that are not passing the build _("green")_ - it is your responsibility to get a proposed PR to pass the build, not primarily the project's maintainers.
- [ ] Create/update unit or integration tests for verifying the changes made.
- [ ] Follow coding conventions at https://cwiki.apache.org/confluence/display/FINERACT/Coding+Conventions.
- [ ] Create/update [unit or integration tests](https://fineract.apache.org/docs/current/#_testing) for verifying the changes made.
- [ ] Follow our [coding conventions](https://cwiki.apache.org/confluence/display/FINERACT/Coding+Conventions).
- [ ] Add required Swagger annotation and update API documentation at fineract-provider/src/main/resources/static/legacy-docs/apiLive.htm with details of any API changes
- [ ] Submission is not a "code dump". (Large changes can be made "in repository" via a branch. Ask on the developer mailing list for guidance, if required.)
- [ ] [This PR must not be a "code dump"](https://cwiki.apache.org/confluence/display/FINERACT/Pull+Request+Size+Limit). Large changes can be made in a branch, with assistance. Ask for help on the [developer mailing list](https://fineract.apache.org/#contribute).
FYI our guidelines for code reviews are at https://cwiki.apache.org/confluence/display/FINERACT/Code+Review+Guide.
Your assigned reviewer(s) will follow our [guidelines for code reviews](https://cwiki.apache.org/confluence/display/FINERACT/Code+Review+Guide).
+7 -7
View File
@@ -27,19 +27,19 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0
fetch-tags: true
- name: Set up JDK 21
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v4
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5
with:
java-version: '21'
distribution: 'zulu'
- name: Cache Gradle dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4
with:
path: |
~/.gradle/caches
@@ -47,7 +47,7 @@ jobs:
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- name: Setup Gradle and Validate Wrapper
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
with:
validate-wrappers: true
@@ -87,7 +87,7 @@ jobs:
- name: Archive test results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
with:
name: test-results-${{ matrix.task }}
path: |
@@ -98,7 +98,7 @@ jobs:
- name: Archive Progressive Loan JAR
if: matrix.job_type == 'progressive-loan' && always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
with:
name: progressive-loan-jar
path: ${{ env.EMBEDDABLE_JAR_FILE }}
@@ -107,7 +107,7 @@ jobs:
- name: Archive server logs
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
with:
name: server-logs-${{ matrix.task }}
path: '**/build/cargo/'
+3 -3
View File
@@ -26,18 +26,18 @@ jobs:
IMAGE_NAME: fineract
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v4
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5
with:
java-version: '21'
distribution: 'zulu'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
- name: Build the image
run: ./gradlew --no-daemon --console=plain :fineract-provider:jibDockerBuild -Djib.to.image=$IMAGE_NAME -x test -x cucumber
+5 -5
View File
@@ -10,23 +10,23 @@ jobs:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v4
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5
with:
java-version: '21'
distribution: 'zulu'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v4
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
- name: Congfigure vega-cli
run: npm i -g vega-cli --unsafe
- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@v4
uses: gradle/actions/wrapper-validation@v5.0.1
- name: Install additional software
run: |
sudo apt-get update
+6 -6
View File
@@ -33,18 +33,18 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v4
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5
with:
java-version: '21'
distribution: 'zulu'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
- name: Make scripts executable
run: chmod +x scripts/split-features.sh
@@ -146,7 +146,7 @@ jobs:
- name: Upload test results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
with:
name: allure-results-shard-${{ matrix.shard_index }}
path: |
@@ -159,7 +159,7 @@ jobs:
- name: Upload Allure Report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
with:
name: allure-report-shard-${{ matrix.shard_index }}
path: allure-report-shard-${{ matrix.shard_index }}
@@ -167,7 +167,7 @@ jobs:
- name: Upload logs
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
with:
name: logs-shard-${{ matrix.shard_index }}
path: |
+7 -7
View File
@@ -25,7 +25,7 @@ jobs:
options: --health-cmd="healthcheck.sh --su-mysql --connect --innodb_initialized" --health-interval=5s --health-timeout=2s --health-retries=3
mock-oauth2-server:
image: ghcr.io/navikt/mock-oauth2-server:2.1.10
image: ghcr.io/navikt/mock-oauth2-server:3.0.1
ports:
- 9000:9000
env:
@@ -44,19 +44,19 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0
fetch-tags: true
- name: Set up JDK 21
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v4
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5
with:
java-version: '21'
distribution: 'zulu'
- name: Cache Gradle dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4
with:
path: |
~/.gradle/caches
@@ -64,7 +64,7 @@ jobs:
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- name: Setup Gradle and Validate Wrapper
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
with:
validate-wrappers: true
@@ -135,7 +135,7 @@ jobs:
- name: Archive test results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
with:
name: test-results-${{ matrix.task }}
path: '**/build/reports/'
@@ -143,7 +143,7 @@ jobs:
- name: Archive server logs
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
with:
name: server-logs-${{ matrix.task }}
path: '**/build/cargo/'
+7 -7
View File
@@ -25,7 +25,7 @@ jobs:
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
mock-oauth2-server:
image: ghcr.io/navikt/mock-oauth2-server:2.1.10
image: ghcr.io/navikt/mock-oauth2-server:3.0.1
ports:
- 9000:9000
env:
@@ -44,19 +44,19 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0
fetch-tags: true
- name: Set up JDK 21
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v4
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5
with:
java-version: '21'
distribution: 'zulu'
- name: Cache Gradle dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4
with:
path: |
~/.gradle/caches
@@ -64,7 +64,7 @@ jobs:
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- name: Setup Gradle and Validate Wrapper
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
with:
validate-wrappers: true
@@ -135,7 +135,7 @@ jobs:
- name: Archive test results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
with:
name: test-results-${{ matrix.task }}
path: '**/build/reports/'
@@ -143,7 +143,7 @@ jobs:
- name: Archive server logs
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
with:
name: server-logs-${{ matrix.task }}
path: '**/build/cargo/'
+8 -8
View File
@@ -17,7 +17,7 @@ jobs:
services:
postgresql:
image: postgres:17.4
image: postgres:18.2
ports:
- 5432:5432
env:
@@ -26,7 +26,7 @@ jobs:
options: --health-cmd="pg_isready -q -d postgres -U root" --health-interval=5s --health-timeout=2s --health-retries=3
mock-oauth2-server:
image: ghcr.io/navikt/mock-oauth2-server:2.1.10
image: ghcr.io/navikt/mock-oauth2-server:3.0.1
ports:
- 9000:9000
env:
@@ -45,19 +45,19 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0
fetch-tags: true
- name: Set up JDK 21
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v4
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5
with:
java-version: '21'
distribution: 'zulu'
- name: Cache Gradle dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4
with:
path: |
~/.gradle/caches
@@ -65,7 +65,7 @@ jobs:
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- name: Setup Gradle and Validate Wrapper
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
with:
validate-wrappers: true
@@ -136,7 +136,7 @@ jobs:
- name: Archive test results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
with:
name: test-results-${{ matrix.task }}
path: '**/build/reports/'
@@ -144,7 +144,7 @@ jobs:
- name: Archive server logs
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
with:
name: server-logs-${{ matrix.task }}
path: '**/build/cargo/'
@@ -0,0 +1,81 @@
name: Fineract Liquibase Only mode - PostgreSQL
on: [push, pull_request]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-22.04
timeout-minutes: 60
services:
postgresql:
image: postgres:18.2
ports:
- 5432:5432
env:
POSTGRES_USER: root
POSTGRES_PASSWORD: postgres
options: --health-cmd="pg_isready -q -d postgres -U root" --health-interval=5s --health-timeout=2s --health-retries=3
env:
TZ: Asia/Kolkata
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
steps:
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0
fetch-tags: true
- name: Set up JDK 21
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5
with:
java-version: '21'
distribution: 'zulu'
- name: Cache Gradle dependencies
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- name: Setup Gradle and Validate Wrapper
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
with:
validate-wrappers: true
- name: Verify PostgreSQL connection
run: |
while ! pg_isready -d postgres -U root -h 127.0.0.1 -p 5432 ; do
sleep 1
done
- name: Initialise databases
run: |
./gradlew --no-daemon -q createPGDB -PdbName=fineract_tenants
./gradlew --no-daemon -q createPGDB -PdbName=fineract_default
- name: Run Fineract in Liquibase only mode
env:
FINERACT_DEFAULT_TENANTDB_CONN_PARAMS: ""
FINERACT_DEFAULT_TENANTDB_DESCRIPTION: "Default Demo Tenant"
FINERACT_DEFAULT_TENANTDB_HOSTNAME: "localhost"
FINERACT_DEFAULT_TENANTDB_IDENTIFIER: "default"
FINERACT_DEFAULT_TENANTDB_NAME: "fineract_default"
FINERACT_DEFAULT_TENANTDB_PORT: "5432"
FINERACT_DEFAULT_TENANTDB_PWD: "postgres"
FINERACT_DEFAULT_TENANTDB_TIMEZONE: "Asia/Kolkata"
FINERACT_DEFAULT_TENANTDB_UID: "root"
FINERACT_HIKARI_DRIVER_SOURCE_CLASS_NAME: "org.postgresql.Driver"
FINERACT_HIKARI_JDBC_URL: "jdbc:postgresql://localhost:5432/fineract_tenants"
FINERACT_HIKARI_PASSWORD: "postgres"
FINERACT_HIKARI_USERNAME: "root"
SPRING_PROFILES_ACTIVE: "liquibase-only"
run:
./gradlew fineract-provider:bootRun
@@ -0,0 +1,36 @@
name: Publish Fineract client to Mifos Artifactory
on:
push:
branches:
- develop-mifos
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-24.04
timeout-minutes: 60
env:
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
steps:
- name: Checkout Source Code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0
- name: Generate build number
uses: onyxmueller/build-tag-number@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up JDK 21
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5
with:
java-version: '21'
distribution: 'zulu'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
- name: Build the image
run: ./gradlew publish -Pfineract.config.username=$ARTIFACTORY_USERNAME -Pfineract.config.password=$ARTIFACTORY_PASSWORD -Pfineract.release.version=${BUILD_NUMBER}
+26
View File
@@ -0,0 +1,26 @@
name: Fineract PR Compliance
on:
pull_request:
types: [opened, edited, reopened, synchronize]
permissions:
pull-requests: read
jobs:
verify-title:
name: Validate Jira Ticket ID
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- name: Verify Title Format
run: |
title="${{ github.event.pull_request.title }}"
regex="^FINERACT-[0-9]+: "
if [[ ! "$title" =~ $regex ]]; then
echo "::error::PR title '$title' is invalid."
echo "::error::It must start with a Jira Ticket ID (e.g., 'FINERACT-123: Description...')."
exit 1
fi
echo "Success: PR title matches the required format."
+3 -3
View File
@@ -15,18 +15,18 @@ jobs:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
steps:
- name: Checkout Source Code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v4
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5
with:
java-version: '21'
distribution: 'zulu'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
- name: Get Git Hashes
run: |
@@ -14,7 +14,7 @@ jobs:
services:
postgresql:
image: postgres:17.4
image: postgres:18.2
ports:
- 5432:5432
env:
@@ -22,7 +22,7 @@ jobs:
POSTGRES_PASSWORD: postgres
options: --health-cmd="pg_isready -q -d postgres -U root" --health-interval=5s --health-timeout=2s --health-retries=3
mock-oauth2-server:
image: ghcr.io/navikt/mock-oauth2-server:2.1.10
image: ghcr.io/navikt/mock-oauth2-server:3.0.1
ports:
- 9000:9000
env:
@@ -33,18 +33,18 @@ jobs:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0
fetch-tags: true
- name: Set up JDK 21
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v4
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5
with:
java-version: '21'
distribution: 'zulu'
- name: Setup Gradle and Validate Wrapper
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
with:
validate-wrappers: true
- name: Verify PostgreSQL connection
@@ -84,7 +84,7 @@ jobs:
./gradlew --no-daemon --console=plain :oauth2-tests:test -PdbType=postgresql
- name: Archive test results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
with:
name: test-results
retention-days: 5
@@ -95,7 +95,7 @@ jobs:
oauth2-tests/build/reports/
- name: Archive server logs
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
with:
name: server-logs
retention-days: 5
+3 -3
View File
@@ -24,18 +24,18 @@ jobs:
IMAGE_NAME: fineract
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v4
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5
with:
java-version: '21'
distribution: 'zulu'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
- name: Build the image
run: ./gradlew --no-daemon --console=plain :fineract-provider:jibDockerBuild -Djib.to.image=$IMAGE_NAME -x test -x cucumber
+3 -3
View File
@@ -20,16 +20,16 @@ jobs:
JAVA_BINARIES: .
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v4
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5
with:
java-version: '21'
distribution: 'zulu'
- name: Setup Gradle and Validate Wrapper
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1
with:
validate-wrappers: true
- name: Build
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v9
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
# stale-issue-message: 'Stale issue message'
@@ -0,0 +1,225 @@
name: Verify API Backward Compatibility
on: [pull_request]
permissions:
contents: read
pull-requests: write
jobs:
api-compatibility-check:
runs-on: ubuntu-24.04
timeout-minutes: 15
env:
TZ: Asia/Kolkata
steps:
- name: Checkout base branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
repository: ${{ github.event.pull_request.base.repo.full_name }}
ref: ${{ github.event.pull_request.base.ref }}
fetch-depth: 0
path: baseline
- name: Set up JDK 21
uses: actions/setup-java@v5
with:
distribution: 'zulu'
java-version: '21'
- name: Generate baseline spec
working-directory: baseline
run: ./gradlew :fineract-provider:resolve --no-daemon
- name: Checkout PR branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
path: current
- name: Generate PR spec
working-directory: current
run: ./gradlew :fineract-provider:resolve --no-daemon
- name: Sanitize specs
run: |
python3 -c "
import json, sys
def sanitize(path):
with open(path) as f:
spec = json.load(f)
fixed = 0
for path_item in spec.get('paths', {}).values():
for op in path_item.values():
if not isinstance(op, dict) or 'requestBody' not in op:
continue
for media in op['requestBody'].get('content', {}).values():
if 'schema' not in media:
media['schema'] = {'type': 'object'}
fixed += 1
if fixed:
with open(path, 'w') as f:
json.dump(spec, f)
print(f'{path}: fixed {fixed} entries')
sanitize('${GITHUB_WORKSPACE}/baseline/fineract-provider/build/resources/main/static/fineract.json')
sanitize('${GITHUB_WORKSPACE}/current/fineract-provider/build/resources/main/static/fineract.json')
"
- name: Check breaking changes
id: breaking-check
continue-on-error: true
working-directory: current
run: |
set -o pipefail
./gradlew :fineract-provider:checkBreakingChanges \
-PapiBaseline="${GITHUB_WORKSPACE}/baseline/fineract-provider/build/resources/main/static/fineract.json" \
--no-daemon --quiet 2>&1 | tail -50
- name: Build report
if: steps.breaking-check.outcome == 'failure'
id: report
run: |
REPORT_DIR="current/fineract-provider/build/swagger-brake"
python3 -c "
import json, glob, os
from collections import defaultdict
RULE_DESC = {
'R001': 'Standard API changed to beta',
'R002': 'Path deleted',
'R003': 'Request media type deleted',
'R004': 'Request parameter deleted',
'R005': 'Request parameter enum value deleted',
'R006': 'Request parameter location changed',
'R007': 'Request parameter made required',
'R008': 'Request parameter type changed',
'R009': 'Request attribute removed',
'R010': 'Request type changed',
'R011': 'Request enum value deleted',
'R012': 'Response code deleted',
'R013': 'Response media type deleted',
'R014': 'Response attribute removed',
'R015': 'Response type changed',
'R016': 'Response enum value deleted',
'R017': 'Request parameter constraint changed',
}
report_dir = '${REPORT_DIR}'
files = sorted(glob.glob(os.path.join(report_dir, '*.json')))
if not files:
body = 'Breaking change detected but no report file found.'
else:
with open(files[0]) as f:
data = json.load(f)
all_changes = []
for items in data.get('breakingChanges', {}).values():
all_changes.extend(items)
if not all_changes:
body = 'Breaking change detected but no details available in report.'
else:
def detail(c):
for key in ('attributeName', 'attribute', 'name', 'mediaType', 'enumValue', 'code'):
v = c.get(key)
if v:
val = v.rsplit('.', 1)[-1]
if key in ('attributeName', 'attribute', 'name'):
return val
return f'{key}={val}'
return '-'
groups = defaultdict(list)
for c in all_changes:
groups[(c.get('ruleCode', '?'), detail(c))].append(c)
lines = []
lines.append('| Rule | Description | Detail | Affected endpoints | Count |')
lines.append('|------|-------------|--------|--------------------|-------|')
for (rule, det), items in sorted(groups.items()):
desc = RULE_DESC.get(rule, '')
eps = sorted(set(
f'{c.get(\"method\",\"\")} {c.get(\"path\",\"\")}'
for c in items if c.get('path')
))
ep_str = ', '.join(f'\`{e}\`' for e in eps[:5])
if len(eps) > 5:
ep_str += f' +{len(eps)-5} more'
lines.append(f'| {rule} | {desc} | \`{det}\` | {ep_str} | {len(items)} |')
lines.append('')
lines.append(f'**Total: {len(all_changes)} violations across {len(groups)} unique changes**')
body = '\n'.join(lines)
with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
f.write('has_report=true\n')
report_file = '${GITHUB_WORKSPACE}/breaking-changes-report.md'
with open(report_file, 'w') as f:
f.write('## Breaking API Changes Detected\n\n')
f.write(body)
f.write('\n\n> **Note:** This check is informational only and does not block the PR.\n')
# Also write to step summary
with open(os.environ['GITHUB_STEP_SUMMARY'], 'a') as f:
f.write('## Breaking API Changes Detected\n\n')
f.write(body)
f.write('\n\n> **Note:** This check is informational only and does not block the PR.\n')
"
- name: Comment on PR
if: always()
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
MARKER="<!-- swagger-brake-report -->"
# Find existing comment by marker
COMMENT_ID=$(gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
--jq ".[] | select(.body | contains(\"${MARKER}\")) | .id" | head -1)
if [ "${{ steps.breaking-check.outcome }}" == "failure" ] && [ -f "${GITHUB_WORKSPACE}/breaking-changes-report.md" ]; then
# Prepend marker to the report
BODY="${MARKER}
$(cat ${GITHUB_WORKSPACE}/breaking-changes-report.md)"
if [ -n "$COMMENT_ID" ]; then
gh api "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" \
-X PATCH -f body="${BODY}"
else
gh pr comment "${PR_NUMBER}" --repo ${{ github.repository }} --body "${BODY}"
fi
elif [ -n "$COMMENT_ID" ]; then
# No breaking changes anymore, delete the old comment
gh api "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" -X DELETE
fi
- name: Report no breaking changes
if: steps.breaking-check.outcome == 'success'
run: |
echo "## No Breaking API Changes Detected" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "The API contract is backward compatible." >> $GITHUB_STEP_SUMMARY
- name: Archive breaking change report
if: always()
uses: actions/upload-artifact@v4
with:
name: api-compatibility-report
path: current/fineract-provider/build/swagger-brake/
retention-days: 30
- name: Fail if breaking changes detected
if: steps.breaking-check.outcome == 'failure'
run: |
echo "::error::Breaking API changes detected. See the report above for details."
exit 1
+47
View File
@@ -0,0 +1,47 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
name: Fineract Signed Commits Check
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
jobs:
verify-signatures:
name: Verify Commit Signatures
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Fetch base branch
run: git fetch origin ${{ github.base_ref }}
- name: Verify signatures
run: |
chmod +x scripts/verify-signed-commits.sh
./scripts/verify-signed-commits.sh \
--base-ref origin/${{ github.base_ref }} \
--head-ref ${{ github.event.pull_request.head.sha }} \
--strict
@@ -0,0 +1,163 @@
name: Verify Liquibase Backward Compatibility
on: [pull_request]
permissions:
contents: read
jobs:
liquibase-backward-compatibility-check:
runs-on: ubuntu-24.04
timeout-minutes: 30
services:
postgresql:
image: postgres:18.2
ports:
- 5432:5432
env:
POSTGRES_USER: root
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd="pg_isready -q -d postgres -U root"
--health-interval=5s
--health-timeout=2s
--health-retries=5
env:
DB_USER: root
DB_PASSWORD: postgres
DB_NAME: fineract_default
TZ: Asia/Kolkata
steps:
- name: Checkout the base branch (`develop`)
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
repository: ${{ github.event.pull_request.base.repo.full_name }}
ref: ${{ github.event.pull_request.base.ref }}
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@v5
with:
distribution: 'zulu'
java-version: '21'
- name: Wait for PostgreSQL
run: |
until pg_isready -h localhost -U $DB_USER; do
echo "Waiting for postgres..."
sleep 2
done
- name: Init base schema
run: |
./gradlew --no-daemon createPGDB -PdbName=fineract_tenants
./gradlew --no-daemon createPGDB -PdbName=$DB_NAME
- name: Start backend on base branch
run: |
./gradlew :fineract-provider:devRun --args="\
--spring.datasource.hikari.driverClassName=org.postgresql.Driver \
--spring.datasource.hikari.jdbcUrl=jdbc:postgresql://localhost:5432/fineract_tenants \
--spring.datasource.hikari.username=root \
--spring.datasource.hikari.password=postgres \
--fineract.tenant.host=localhost \
--fineract.tenant.port=5432 \
--fineract.tenant.username=root \
--fineract.tenant.password=postgres" &
BACKEND_PID=$!
echo $BACKEND_PID > backend.pid
# Wait for Actuator to come up (adjust host/port/path if needed)
ACTUATOR_URL="https://localhost:8443/fineract-provider/actuator/health"
TIMEOUT_SECONDS=600
INTERVAL_SECONDS=2
echo "Waiting for backend Actuator: $ACTUATOR_URL (timeout ${TIMEOUT_SECONDS}s)..."
start_ts=$(date +%s)
while true; do
# If the process died, fail fast
if ! kill -0 "$BACKEND_PID" 2>/dev/null; then
echo "Backend process exited before Actuator became available."
exit 1
fi
# Check endpoint
if curl -kfsS "$ACTUATOR_URL" >/dev/null 2>&1; then
echo "Actuator is up."
break
fi
# Timeout
now_ts=$(date +%s)
if [ $((now_ts - start_ts)) -ge "$TIMEOUT_SECONDS" ]; then
echo "Timed out waiting for Actuator."
exit 1
fi
sleep "$INTERVAL_SECONDS"
done
- name: Stop backend
run: |
kill $(cat backend.pid)
sleep 10
- name: Checkout the PR branch
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Start backend on PR branch
run: |
./gradlew :fineract-provider:devRun --args="\
--spring.datasource.hikari.driverClassName=org.postgresql.Driver \
--spring.datasource.hikari.jdbcUrl=jdbc:postgresql://localhost:5432/fineract_tenants \
--spring.datasource.hikari.username=root \
--spring.datasource.hikari.password=postgres \
--fineract.tenant.host=localhost \
--fineract.tenant.port=5432 \
--fineract.tenant.username=root \
--fineract.tenant.password=postgres" &
BACKEND_PID=$!
echo $BACKEND_PID > backend.pid
# Wait for Actuator to come up (adjust host/port/path if needed)
ACTUATOR_URL="https://localhost:8443/fineract-provider/actuator/health"
TIMEOUT_SECONDS=600
INTERVAL_SECONDS=2
echo "Waiting for backend Actuator: $ACTUATOR_URL (timeout ${TIMEOUT_SECONDS}s)..."
start_ts=$(date +%s)
while true; do
# If the process died, fail fast
if ! kill -0 "$BACKEND_PID" 2>/dev/null; then
echo "Backend process exited before Actuator became available."
exit 1
fi
# Check endpoint
if curl -kfsS "$ACTUATOR_URL" >/dev/null 2>&1; then
echo "Actuator is up."
break
fi
# Timeout
now_ts=$(date +%s)
if [ $((now_ts - start_ts)) -ge "$TIMEOUT_SECONDS" ]; then
echo "Timed out waiting for Actuator."
exit 1
fi
sleep "$INTERVAL_SECONDS"
done
- name: Stop backend
run: |
kill $(cat backend.pid)
sleep 10
+17
View File
@@ -1,3 +1,20 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# .mailmap unifies differences in commit names and emails, and shortens committers' names.
# .mailmap format is a bit weird, e.g. https://github.com/sympy/sympy/wiki/Using-.mailmap explains it.
# .mailmap official doc is on https://git-scm.com/docs/git-shortlog#_mapping_authors
+33
View File
@@ -0,0 +1,33 @@
{
"permissions": {
"allow": [
"Bash(type *)",
"mcp__idea__get_symbol_info",
"Bash(gradlew *)",
"Bash(select-string *)",
"Bash(select-object *)",
"Bash(dir *)",
"Bash(findstr *)",
"Bash(./gradlew *)",
"Bash(gradlew.bat *)",
"Bash(taskkill *)",
"Bash(timeout *)",
"Bash(.\\gradlew.bat *)",
"Bash(get-content *)",
"Bash(tasklist)",
"Bash(curl *)",
"Bash(netstat *)",
"Bash(curl.exe *)",
"Bash(rmdir *)",
"mcp__idea__build_project",
"Bash(.\\gradlew *)",
"Bash(.\\clean_build.bat)",
"mcp__idea__find_files_by_name_keyword",
"mcp__idea__search_in_files_by_text",
"mcp__idea__get_file_text_by_path",
"mcp__idea__search_in_files_by_regex",
"Bash(powershell *)"
]
},
"$version": 3
}
+7
View File
@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(type *)"
]
}
}
@@ -0,0 +1,365 @@
{
"info": {
"name": "CBS — CIN & DIN Verification (LincPay eKYC)",
"description": "Postman collection for CIN (Company Identification Number) and DIN (Director Identification Number) verification via the CBS Fineract KYC module.\n\nFlow:\n1. Create/update a KYC record with cinNumber and/or dinNumber\n2. Trigger verify-cin or verify-din command\n3. The backend calls LincPay eKYC API (validate-company / validate-director)\n4. On success, cinVerified/dinVerified flags are set on the KYC record",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"variable": [
{
"key": "base_url",
"value": "https://localhost:8443/fineract-provider/api/v1",
"description": "Fineract API base URL"
},
{
"key": "tenant_id",
"value": "default",
"description": "Tenant identifier"
},
{
"key": "username",
"value": "mifos",
"description": "API username"
},
{
"key": "password",
"value": "password",
"description": "API password"
},
{
"key": "client_id",
"value": "1",
"description": "Client ID for KYC operations"
},
{
"key": "kyc_id",
"value": "1",
"description": "KYC record ID"
},
{
"key": "cin_number",
"value": "U12345MH2020PTC123456",
"description": "21-character CIN for company verification"
},
{
"key": "din_number",
"value": "12345678",
"description": "8-digit DIN for director verification"
}
],
"auth": {
"type": "basic",
"basic": [
{ "key": "username", "value": "{{username}}", "type": "string" },
{ "key": "password", "value": "{{password}}", "type": "string" }
]
},
"item": [
{
"name": "1. Initiate KYC with CIN & DIN",
"item": [
{
"name": "Initiate KYC (Non-Individual with CIN & DIN)",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test('Status code is 200', function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test('KYC record created', function () {",
" var json = pm.response.json();",
" pm.expect(json).to.have.property('resourceId');",
" pm.collectionVariables.set('kyc_id', json.resourceId);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{ "key": "Content-Type", "value": "application/json" },
{ "key": "Fineract-Platform-TenantId", "value": "{{tenant_id}}" }
],
"body": {
"mode": "raw",
"raw": "{\n \"kycType\": 2,\n \"riskCategory\": 2,\n \"panNumber\": \"ABCDE1234F\",\n \"gstinNumber\": \"27ABCDE1234F1Z5\",\n \"cinNumber\": \"{{cin_number}}\",\n \"dinNumber\": \"{{din_number}}\",\n \"remarks\": \"Non-individual KYC with CIN and DIN\"\n}"
},
"url": {
"raw": "{{base_url}}/clients/{{client_id}}/kyc",
"host": ["{{base_url}}"],
"path": ["clients", "{{client_id}}", "kyc"]
},
"description": "Initiate a KYC record for a non-individual client with CIN and DIN numbers.\n\n- kycType: 2 = NON_INDIVIDUAL\n- riskCategory: 1=LOW, 2=MEDIUM, 3=HIGH\n- cinNumber: 21-character Corporate Identification Number\n- dinNumber: 8-digit Director Identification Number"
},
"response": []
},
{
"name": "Update KYC — Add CIN & DIN",
"request": {
"method": "PUT",
"header": [
{ "key": "Content-Type", "value": "application/json" },
{ "key": "Fineract-Platform-TenantId", "value": "{{tenant_id}}" }
],
"body": {
"mode": "raw",
"raw": "{\n \"cinNumber\": \"{{cin_number}}\",\n \"dinNumber\": \"{{din_number}}\"\n}"
},
"url": {
"raw": "{{base_url}}/clients/{{client_id}}/kyc/{{kyc_id}}",
"host": ["{{base_url}}"],
"path": ["clients", "{{client_id}}", "kyc", "{{kyc_id}}"]
},
"description": "Update an existing KYC record to add CIN and/or DIN numbers before triggering verification."
},
"response": []
}
],
"description": "Create or update a KYC record with CIN and DIN numbers before triggering verification."
},
{
"name": "2. CIN Verification (Company)",
"item": [
{
"name": "Verify CIN — Trigger LincPay validate-company",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test('Status code is 200', function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test('CIN verified successfully', function () {",
" var json = pm.response.json();",
" pm.expect(json.changes).to.have.property('cinVerified');",
" pm.expect(json.changes.cinVerified).to.be.true;",
"});",
"",
"pm.test('cinVerifiedOn date is set', function () {",
" var json = pm.response.json();",
" pm.expect(json.changes).to.have.property('cinVerifiedOn');",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{ "key": "Content-Type", "value": "application/json" },
{ "key": "Fineract-Platform-TenantId", "value": "{{tenant_id}}" }
],
"body": {
"mode": "raw",
"raw": "{}"
},
"url": {
"raw": "{{base_url}}/clients/{{client_id}}/kyc/{{kyc_id}}?command=verify-cin",
"host": ["{{base_url}}"],
"path": ["clients", "{{client_id}}", "kyc", "{{kyc_id}}"],
"query": [
{ "key": "command", "value": "verify-cin" }
]
},
"description": "Trigger CIN verification via LincPay MCA API.\n\n**Backend flow:**\n1. Reads cinNumber from the KYC record\n2. Calls LincPay: POST https://ekyc.lincpay.in/EKYC/ekyc/validate-company\n Body: { \"apiKey\": \"...\", \"cin\": \"U12345MH2020PTC123456\", \"secretKey\": \"...\" }\n3. On success: marks cinVerified=true, cinVerifiedOn=today\n4. On failure: returns error with vendor message\n\n**Prerequisites:**\n- KYC record must exist with a cinNumber set\n- KYC must not be in REJECTED status"
},
"response": [
{
"name": "200 — CIN Verified Successfully",
"originalRequest": {
"method": "POST",
"header": [],
"body": { "mode": "raw", "raw": "{}" },
"url": { "raw": "{{base_url}}/clients/1/kyc/1?command=verify-cin" }
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"body": "{\n \"officeId\": 1,\n \"clientId\": 1,\n \"resourceId\": 1,\n \"changes\": {\n \"cinVerified\": true,\n \"cinVerifiedOn\": [2026, 4, 3],\n \"kycStatus\": 200\n }\n}"
},
{
"name": "400 — CIN Not Set",
"status": "Bad Request",
"code": 400,
"_postman_previewlanguage": "json",
"body": "{\n \"developerMessage\": \"CIN number is not set on this KYC record. Update the KYC record first.\",\n \"httpStatusCode\": \"503\",\n \"defaultUserMessage\": \"CIN number is not set on this KYC record. Update the KYC record first.\",\n \"userMessageGlobalisationCode\": \"error.msg.client.kyc.cin.not.found\"\n}"
}
]
},
{
"name": "Direct LincPay CIN API (for testing)",
"request": {
"method": "POST",
"header": [
{ "key": "Content-Type", "value": "application/json" },
{ "key": "accept", "value": "*/*" }
],
"body": {
"mode": "raw",
"raw": "{\n \"apiKey\": \"YOUR_API_KEY\",\n \"cin\": \"{{cin_number}}\",\n \"secretKey\": \"YOUR_SECRET_KEY\"\n}"
},
"url": {
"raw": "https://ekyc.lincpay.in/EKYC/ekyc/validate-company",
"protocol": "https",
"host": ["ekyc", "lincpay", "in"],
"path": ["EKYC", "ekyc", "validate-company"]
},
"description": "Direct call to LincPay validate-company API for testing.\nReplace apiKey and secretKey with actual credentials."
},
"response": []
}
],
"description": "CIN (Corporate Identification Number) verification flow. CIN is a 21-character alphanumeric code allotted by MCA to companies registered under the Companies Act."
},
{
"name": "3. DIN Verification (Director)",
"item": [
{
"name": "Verify DIN — Trigger LincPay validate-director",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test('Status code is 200', function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test('DIN verified successfully', function () {",
" var json = pm.response.json();",
" pm.expect(json.changes).to.have.property('dinVerified');",
" pm.expect(json.changes.dinVerified).to.be.true;",
"});",
"",
"pm.test('dinVerifiedOn date is set', function () {",
" var json = pm.response.json();",
" pm.expect(json.changes).to.have.property('dinVerifiedOn');",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{ "key": "Content-Type", "value": "application/json" },
{ "key": "Fineract-Platform-TenantId", "value": "{{tenant_id}}" }
],
"body": {
"mode": "raw",
"raw": "{}"
},
"url": {
"raw": "{{base_url}}/clients/{{client_id}}/kyc/{{kyc_id}}?command=verify-din",
"host": ["{{base_url}}"],
"path": ["clients", "{{client_id}}", "kyc", "{{kyc_id}}"],
"query": [
{ "key": "command", "value": "verify-din" }
]
},
"description": "Trigger DIN verification via LincPay MCA API.\n\n**Backend flow:**\n1. Reads dinNumber from the KYC record\n2. Calls LincPay: POST https://ekyc.lincpay.in/EKYC/ekyc/validate-director\n Body: { \"apiKey\": \"...\", \"din\": \"12345678\", \"secretKey\": \"...\" }\n3. On success: marks dinVerified=true, dinVerifiedOn=today\n4. On failure: returns error with vendor message\n\n**Prerequisites:**\n- KYC record must exist with a dinNumber set\n- KYC must not be in REJECTED status"
},
"response": [
{
"name": "200 — DIN Verified Successfully",
"originalRequest": {
"method": "POST",
"header": [],
"body": { "mode": "raw", "raw": "{}" },
"url": { "raw": "{{base_url}}/clients/1/kyc/1?command=verify-din" }
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"body": "{\n \"officeId\": 1,\n \"clientId\": 1,\n \"resourceId\": 1,\n \"changes\": {\n \"dinVerified\": true,\n \"dinVerifiedOn\": [2026, 4, 3],\n \"kycStatus\": 200\n }\n}"
},
{
"name": "400 — DIN Not Set",
"status": "Bad Request",
"code": 400,
"_postman_previewlanguage": "json",
"body": "{\n \"developerMessage\": \"DIN number is not set on this KYC record. Update the KYC record first.\",\n \"httpStatusCode\": \"503\",\n \"defaultUserMessage\": \"DIN number is not set on this KYC record. Update the KYC record first.\",\n \"userMessageGlobalisationCode\": \"error.msg.client.kyc.din.not.found\"\n}"
}
]
},
{
"name": "Direct LincPay DIN API (for testing)",
"request": {
"method": "POST",
"header": [
{ "key": "Content-Type", "value": "application/json" },
{ "key": "accept", "value": "*/*" }
],
"body": {
"mode": "raw",
"raw": "{\n \"apiKey\": \"YOUR_API_KEY\",\n \"din\": \"{{din_number}}\",\n \"secretKey\": \"YOUR_SECRET_KEY\"\n}"
},
"url": {
"raw": "https://ekyc.lincpay.in/EKYC/ekyc/validate-director",
"protocol": "https",
"host": ["ekyc", "lincpay", "in"],
"path": ["EKYC", "ekyc", "validate-director"]
},
"description": "Direct call to LincPay validate-director API for testing.\nReplace apiKey and secretKey with actual credentials."
},
"response": []
}
],
"description": "DIN (Director Identification Number) verification flow. DIN is an 8-digit unique number allotted by MCA to company directors."
},
{
"name": "4. Get KYC Record",
"item": [
{
"name": "Get KYC Record (check CIN/DIN verification status)",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test('Status code is 200', function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test('CIN fields present', function () {",
" var json = pm.response.json();",
" pm.expect(json).to.have.property('cinNumber');",
" pm.expect(json).to.have.property('cinVerified');",
"});",
"",
"pm.test('DIN fields present', function () {",
" var json = pm.response.json();",
" pm.expect(json).to.have.property('dinNumber');",
" pm.expect(json).to.have.property('dinVerified');",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [
{ "key": "Fineract-Platform-TenantId", "value": "{{tenant_id}}" }
],
"url": {
"raw": "{{base_url}}/clients/{{client_id}}/kyc/{{kyc_id}}",
"host": ["{{base_url}}"],
"path": ["clients", "{{client_id}}", "kyc", "{{kyc_id}}"]
},
"description": "Retrieve the KYC record to verify CIN/DIN verification status after running the verify commands."
},
"response": []
}
]
}
]
}
+289 -9
View File
@@ -1,16 +1,296 @@
# Contributing
Hello there!
First of all, **Thank You** for contributing to Apache Fineract, we're grateful for your interest in our project.
First of all, **Thank You** for contributing to Apache Fineract! We are grateful for your interest in this project.
Please do [join our developer mailing list](https://fineract.apache.org/#contribute), as it's where discussions about this project take place - say _Hi_ there!
Please join the [developer mailing list](https://fineract.apache.org/#contribute), if you have not already done so, as this is where discussions about this project take place - say _Hi_ there! Please also have a quick look at the [Code of Conduct](CODE_OF_CONDUCT.md).
Our [JIRA Dashboard](https://issues.apache.org/jira/secure/Dashboard.jspa?selectPageId=12335824) is another good way to see what's going on (do create a Login).
The [JIRA Dashboard](https://issues.apache.org/jira/secure/Dashboard.jspa?selectPageId=12335824) shows what's going on. Create a login - it can take a day or two. If you face difficulties, ask on the mailing list.
Here are a few additional useful pointers:
You don't need to be a committer to provide pull requests, but [Becoming a Committer](https://cwiki.apache.org/confluence/display/FINERACT/Becoming+a+Committer) explains the process of becoming one - just in case...
* https://github.com/apache/fineract/#pull-requests
* https://github.com/apache/fineract/#checkstyle-and-spotless
* https://github.com/apache/fineract/#logging-guidelines
* https://github.com/apache/fineract/#error-handling-guidelines
Note also [our Code of Conduct](CODE_OF_CONDUCT.md).
## Developer How To's
### How to run tests
#### Unit tests
Here's how to run the set of relatively fast and independent Fineract tests:
```bash
./gradlew test -x :twofactor-tests:test -x :oauth2-tests:test -x :integration-tests:test
```
This runs nearly 1,000 tests and completes in a few minutes on decent hardware.
They shouldn't need any special servers/services running.
#### Integration tests
Running tests with external dependencies is a multi-step process with many moving parts.
Sometimes there are arbitrary failures and the prerequisite setup can be daunting.
A full local integration test run (on a developer workstation) covering every possible test using every external service and every supported relational database engine could take an entire day - and that's assuming everything is properly configured and runs as expected.
Right now we depend on GitHub to know if "the build" is passing (it's actually multiple builds).
The authoritative source of truth for what commands/services/tests to run, how, and when are the files in `.github/workflows/`.
Output from runs based on those configuration files appears at <https://github.com/apache/fineract/actions>.
Incorrect default Java-related executables may cause test failures.
To fix this on Debian and Ubuntu systems, run the following:
```bash
export JAVA_HOME=/usr/lib/jvm/zulu21
sudo update-alternatives --set java $JAVA_HOME/bin/java
sudo update-alternatives --set javac $JAVA_HOME/bin/javac
sudo update-alternatives --set javadoc $JAVA_HOME/bin/javadoc
```
This would correct, for example, a [class file version error](https://en.wikipedia.org/wiki/Java_class_file#General_layout).
You might see something like this if a Java 11 executable (class file format version 56) was the system default, but the integration tests were using Java 21 (class file format version 65):
> UnsupportedClassVersionError: com.example.package/ClassName has been compiled by a more recent version of the Java Runtime (class file version 65.0), this version of the Java Runtime only recognizes class file versions up to 55.0
The GitHub builds are run in [short-lived virtual machines](https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners), so locally reproducing the same may require additional effort, such as these extra clean-up procedures:
```bash
# Might fix `error: cannot find symbol` or other intermittent failures.
# `doc` here is a placeholder for any task(s) you are trying to run.
# 💚 This is generally very safe to run between builds.
./gradlew --refresh-dependencies doc
# Destroy anything untracked by git.
# ⚠️ This may delete something important, e.g. a finely-tuned IDE configuration.
git clean --force -dx
# Destroy various caches and configs.
# ⚠️ This may delete gibibytes of cached data, making the next build very slow.
rm -rf ~/.gradle ~/.m2 /tmp/cargo*
# Destroy any Java containers left running.
# 💚 This is generally very safe to run between builds.
ps auxwww | grep [c]argo | awk '{ print $2 }' | xargs -r kill
```
Integration test runs such as
```bash
./gradlew --no-daemon --console=plain test -x :twofactor-tests:test \
-x :oauth2-tests:test :fineract-e2e-tests-runner:test -PdbType=postgresql
```
in `.github/workflows/build-postgresql.yml` often take an hour or longer to complete.
If you notice the `:integration-tests:test` task taking significantly less time, say, one minute, gradle may be skipping it.
Look for something like this in the test output:
> Task :integration-tests:test UP-TO-DATE 👀
Custom actions are attached to task ':integration-tests:test'.
Build cache key for task ':integration-tests:test' is 6aeeec3f58bf9703d4c100fbaa657f5c
Skipping task ':integration-tests:test' as it is up-to-date.
Resolve mutations for :integration-tests:cargoStopLocal (Thread[Execution worker Thread 11,5,main]) started.
:integration-tests:cargoStopLocal (Thread[Execution worker Thread 11,5,main]) started.
(This is with the `--info` gradle argument with eyeballs added for emphasis.)
The `--rerun-tasks` gradle argument may help, or you can try destroying `~/.gradle` and other clean-up procedures as indicated above, then re-running tests.
This is useful for repeated test runs (say, for timing) when gradle would otherwise assume a task is "up-to-date" and not re-run it.
See the next section for testing in Eclipse and [here](https://fineract-academy.com) for testing in IntelliJ.
## Recommended IDEs
Apache Fineract can be developed using multiple IDEs. The most commonly used and supported IDEs are:
- **Eclipse IDE** – Fully supported and commonly used within the community.
- **IntelliJ IDEA (Community or Ultimate)** – Recommended for most developers due to excellent Gradle support and debugging experience.
The sections below provide setup guidance for each IDE.
### How to run and debug in Eclipse IDE
It is possible to run Fineract in Eclipse IDE and also to debug Fineract using Eclipse's debugging facilities.
To do this, you need to create the Eclipse project files and import the project into an Eclipse workspace:
1. Create Eclipse project files into the Fineract project by running `./gradlew cleanEclipse eclipse`
2. Import the fineract-provider project into your Eclipse workspace (File->Import->General->Existing Projects into Workspace, choose root directory fineract/fineract-provider)
3. Do a clean build of the project in Eclipse (Project->Clean...)
3. Run / debug Fineract by right clicking on org.apache.fineract.ServerApplication class and choosing Run As / Debug As -> Java Application. All normal Eclipse debugging features (breakpoints, watchpoints etc) should work as expected.
If you change the project settings (dependencies etc) in Gradle, you should redo step 1 and refresh the project in Eclipse.
You can also use Eclipse JUnit support to run tests in Eclipse (Run As->JUnit Test)
Finally, modifying source code in Eclipse automatically triggers hot code replace to a running instance, allowing you to immediately test your changes
## How to run and debug in IntelliJ IDEA
IntelliJ IDEA provides strong Gradle integration and is recommended for most new contributors.
### Prerequisites
- IntelliJ IDEA (Community Edition is sufficient)
- Java 21 (as required by Fineract)
- Gradle (wrapper included in the repository)
### Steps
1. Open IntelliJ IDEA and select **Open**
2. Choose the root `fineract` directory
3. When prompted, import the project as a **Gradle project**
4. Use the Gradle wrapper (`gradlew`) when asked
5. Ensure the correct JDK is selected (**Java 21**)
6. After import completes, run:
- `org.apache.fineract.ServerApplication` as a **Java Application**
### Notes
- IntelliJ may take several minutes to index the project on first open
- If you encounter build issues, try running `./gradlew clean build` from the terminal
How to download Gradle wrapper
---
The file gradle/wrapper/gradle-wrapper.jar binary is checked into this projects Git source repository,
but won't exist in your copy of the Fineract codebase if you downloaded a released source archive from apache.org.
In that case, you need to download it using the commands below:
```bash
wget -P gradle/wrapper https://github.com/apache/fineract/raw/develop/gradle/wrapper/gradle-wrapper.jar
```
or
```bash
curl -L https://github.com/apache/fineract/raw/develop/gradle/wrapper/gradle-wrapper.jar > \
gradle/wrapper/gradle-wrapper.jar
```
### How to run Apache RAT (Release Audit Tool)
1. Extract the archive file to your local directory.
2. Run `./gradlew rat`. A report will be generated under build/reports/rat/rat-report.txt
### How to build documentation
Run the following command:
```bash
./gradlew doc
```
Some dependencies are required (e.g. Ghostscript, Graphviz), see [.github/workflows/build-documentation.yml](https://github.com/apache/fineract/tree/develop/.github/workflows/build-documentation.yml) for hints.
IDEs such as IntelliJ are useful for editing the AsciiDoc source files while providing a live rendered preview.
HTML rendered from the AsciiDoc source files is also available online at [https://fineract.apache.org/docs/current/](https://fineract.apache.org/docs/current/).
## How We Code
### Checkstyle and Spotless
This project enforces its code conventions using [checkstyle.xml](config/checkstyle/checkstyle.xml) through Checkstyle and [fineractdev-formatter.xml](config/fineractdev-formatter.xml) through Spotless. They are configured to run automatically during the normal Gradle build, and fail if there are any violations detected. You can run the following command to automatically fix spotless violations:
```bash
./gradlew spotlessApply
```
Since some checks are present in both Checkstyle and Spotless, the same command can help you fix some of the Checkstyle violations too.
You can also check solely for Spotless violations, but normally don't have to, because regular builds already include this:
```bash
./gradlew spotlessCheck
```
We recommend that you configure your favourite Java IDE to match those conventions. For Eclipse, you can go to
Window > Java > Code Style and import the aforementioned [config/fineractdev-formatter.xml](config/fineractdev-formatter.xml) under formatter section and [config/fineractdev-cleanup.xml](config/fineractdev-cleanup.xml) under cleanup section.
You could also use Checkstyle directly in your IDE, but you don't have to: it may just be more convenient for you. For Eclipse, use https://checkstyle.org/eclipse-cs/ and load our checkstyle.xml into it. For IntelliJ you can use [CheckStyle-IDEA](https://plugins.jetbrains.com/plugin/1065-checkstyle-idea).
### Code Coverage
Changed or added code should ideally have test coverage.
The project uses Jacoco to measure unit tests code coverage. To generate a report run the following command:
```bash
./gradlew clean build jacocoTestReport
```
Generated reports can be found in the build/code-coverage directory.
### Lombok
The project uses [Lombok](https://projectlombok.org/) to reduce boilerplate code. Configuration is in [lombok.config](lombok.config).
* Use `@Getter` / `@Setter` / `@NoArgsConstructor` on JPA entities. Never use `@Data` on entities (causes JPA lazy loading issues).
* Use `@RequiredArgsConstructor` on service classes for constructor-based dependency injection.
* Use `@Slf4j` for logging instead of manually declaring loggers.
* Use `@Data` for simple DTOs (includes getters, setters, toString, equals, hashCode).
* Use `@Builder` with `@Builder.Default` for configuration classes and complex object construction.
* Never use `@SneakyThrows` - handle exceptions explicitly.
### Error Handling
* When catching exceptions, either rethrow them, or log them. Either way, include the root cause by using `catch (SomeException e)` and then either `throw AnotherException("..details..", e)` or `LOG.error("...context...", e)`.
* Completely empty catch blocks are VERY suspicious. Are you sure that you want to just "swallow" an exception? Really, 100% totally absolutely sure?? ;-) Such "normal exceptions which just happen sometimes but are actually not really errors" are almost always a bad idea, can be a performance issue, and typically are an indication of another problem - e.g. the use of a wrong API which throws an Exception for an expected condition, when really you would want to use another API that instead returns something empty or optional.
* In tests, you'll typically never catch exceptions, but just propagate them, with `@Test void testXYZ() throws SomeException, AnotherException`..., so that the test fails if the exception happens. Unless you actually really want to test for the occurrence of a problem - in that case, use [JUnit's Assert.assertThrows()](https://github.com/junit-team/junit4/wiki/Exception-testing) (but not `@Test(expected = SomeException.class)`).
* Never catch `NullPointerException` & Co.
### Logging
* We use [SLF4J](http://www.slf4j.org) as our logging API.
* Never, ever, use `System.out` and `System.err` or `printStackTrace()` anywhere, but always `LOG.info()` or `LOG.error()` instead.
* Use placeholder (`LOG.error("Could not... details: {}", something, exception)`) and never String concatenation (`LOG.error("Could not... details: " + something, exception)`)
* Which Log Level is appropriate?
* `LOG.error()` should be used to inform an "operator" running Fineract who supervises error logs of an unexpected condition. This includes technical problems with an external "environment" (e.g. can't reach a database), and situations which are likely bugs which need to be fixed in the code. They do NOT include e.g. validation errors for incoming API requests - that is signaled through the API response - and does (should) not be logged as an error. (Note that there is no _FATAL_ level in SLF4J; a "FATAL" event should just be logged as an _ERROR_.)
* `LOG.warn()` should be using sparingly. Make up your mind if it's an error (above) - or not!
* `LOG.info()` can be used notably for one-time actions taken during start-up. It should typically NOT be used to print out "regular" application usage information. The default logging configuration always outputs the application INFO logs, and in production under load, there's really no point to constantly spew out lots of information from frequently traversed paths in the code about what's going on. (Metrics are a better way.) `LOG.info()` *can* be used freely in tests though.
* `LOG.debug()` can be used anywhere in the code to log things that may be useful during investigations of specific problems. They are not shown in the default logging configuration, but can be enabled for troubleshooting. Developers should typically "turn down" most `LOG.info()` which they used while writing a new feature to "follow along what happens during local testing" to `LOG.debug()` for production before we merge their PRs.
* `LOG.trace()` is not used in Fineract.
## Change Process
### Dependency Upgrades
This project uses a number of 3rd-party libraries. We have set up [Renovate's bot](https://github.com/renovatebot/github-action) to automatically raise Pull Requests for our review when new dependencies are available.
Our `ClasspathHellDuplicatesCheckRuleTest` detects classes that appear in more than 1 JAR. If a version bump in [build.gradle](https://github.com/apache/fineract/blob/develop/build.gradle) causes changes in transitives dependencies, then you may have to add related `exclude` to our [dependencies.gradle](https://github.com/apache/fineract/search?q=dependencies.gradle). Running `./gradlew dependencies` helps to understand what is required.
### Pull Requests
We request that your commit message includes a FINERACT JIRA issue and a one-liner that describes the changes.
Start with an upper case imperative verb (not past form), and a short but concise clear description. (E.g. "FINERACT-821: Add enforced HideUtilityClassConstructor checkstyle").
If your PR is failing to pass our CI build due to a test failure, then:
1. Understand if the failure is due to your PR or an unrelated unstable test.
1. If you suspect it is because of a "flaky" test, and not due to a change in your PR, then please do not simply wait for an active maintainer to come and help you, but instead be a proactive contributor to the project - see next steps. Do understand that we may not review PRs that are not green - it is the contributor's (that's you!) responsibility to get a proposed PR to pass the build, not primarily the maintainers.
1. Search for the name of the failed test on https://issues.apache.org/jira/, e.g. for `AccountingScenarioIntegrationTest` you would find [FINERACT-899](https://issues.apache.org/jira/browse/FINERACT-899).
1. If you happen to read in such bug reports that tests were just recently fixed, or ignored, then rebase your PR to pick up that change.
1. If you find previous comments "proving" that the same test has arbitrarily failed in at least 3 past PRs, then please do yourself raise a small separate new PR proposing to add an `@Disabled // TODO FINERACT-123` to the respective unstable test (e.g. [#774](https://github.com/apache/fineract/pull/774)) with the commit message mentioning said JIRA, as always. (Please do NOT just `@Disabled` any existing tests mixed in as part of your larger PR.)
1. If there is no existing JIRA for the test, then first please evaluate whether the failure couldn't be a (perhaps strange) impact of the change you are proposing after all. If it's not, then please raise a new JIRA to document the suspected Flaky Test, and link it to [FINERACT-850](https://issues.apache.org/jira/browse/FINERACT-850). This will allow the next person coming along hitting the same test failure to easily find it, and eventually propose to ignore the unstable test.
1. Then (only) Close and Reopen your PR, which will cause a new build, to see if it passes.
1. Of course, we very much appreciate you then jumping onto any such bugs and helping us figure out how to fix all ignored tests!
[Pull Request Size Limit](https://cwiki.apache.org/confluence/display/FINERACT/Pull+Request+Size+Limit)
documents that we cannot accept huge "code dump" Pull Requests, with some related suggestions.
Guideline for new Feature commits involving Refactoring: If you are submitting a PR for a new feature,
and it involves refactoring, try to differentiate "new feature code" from "refactored" by placing
them in different commits. This helps to review your code faster.
We have an automated bot which marks pull requests as "stale" after a while, and ultimately automatically closes them.
### Merge Strategy
This project's committers typically prefer to bring your pull requests in through _Rebase and Merge_ instead of _Create a Merge Commit_. (If you are unfamiliar with GitHub's UI regarding this, note the somewhat hidden little triangle drop-down at the bottom of the PR, visible only to committers, not contributors.) This avoids the "merge commits" which we consider to be somewhat "polluting" the project's commit log history view. We understand this doesn't give an easy automatic reference to the original PR (which GitHub automatically adds to the merge commit message it generates), but we consider this an only very minor inconvenience; it's typically relatively easy to find the original PR even just from the commit message, and JIRA.
We expect most proposed PRs to typically consist of a single commit. Committers may use _Squash and merge_ to combine your commits at merge time, and if they do so, will rewrite your commit message as they see fit.
Neither of these two are hard absolute rules, but mere conventions. Multiple commits in single PRs make sense in certain cases (e.g. branch backports).
### Signing Your Commits
We encourage contributors to sign commits with GPG keys. Signed commits show a "Verified" badge on GitHub.
For GPG setup instructions, see the [Fineract GPG Guide](https://fineract.apache.org/docs/current/#_gpg_2).
To verify your commits locally before pushing:
```bash
./scripts/verify-signed-commits.sh
```
+168
View File
@@ -0,0 +1,168 @@
# SavingsProduct Custom Fields - Implementation Summary
## ✅ Completed Changes
### 1. **Validator Updates**
**File:** `fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java`
**Changes:**
- Added `balanceFrequency` to `SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS` whitelist
- Added `accountClosedZeroBalanceInDayClosing` to `SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS` whitelist
### 2. **API Constants**
**File:** `fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java`
**Changes:**
```java
public static final String balanceFrequencyParamName = "balanceFrequency";
public static final String accountClosedZeroBalanceInDayClosingParamName = "accountClosedZeroBalanceInDayClosing";
```
### 3. **Entity (Domain Model)**
**File:** `fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java`
**Changes:**
- Added field: `@Column(name = "balance_frequency", length = 50) private String balanceFrequency;`
- Added field: `@Column(name = "account_closed_zero_balance_in_day_closing") private Boolean accountClosedZeroBalanceInDayClosing = false;`
- Added getters: `getBalanceFrequency()`, `getAccountClosedZeroBalanceInDayClosing()`
- Added setters: `setBalanceFrequency()`, `setAccountClosedZeroBalanceInDayClosing()`
- Added update logic in `update()` method to handle changes to these fields
### 4. **Assembler**
**File:** `fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductAssembler.java`
**Changes:**
```java
if (command.parameterExists("balanceFrequency")) {
product.setBalanceFrequency(command.stringValueOfParameterNamed("balanceFrequency"));
}
if (command.parameterExists("accountClosedZeroBalanceInDayClosing")) {
product.setAccountClosedZeroBalanceInDayClosing(command.booleanObjectValueOfParameterNamed("accountClosedZeroBalanceInDayClosing"));
}
```
### 5. **DTO (Data Transfer Object)**
**File:** `fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductData.java`
**Changes:**
```java
@Getter @Setter private String balanceFrequency;
@Getter @Setter private Boolean accountClosedZeroBalanceInDayClosing;
```
### 6. **Read Platform Service**
**File:** `fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java`
**Changes:**
- SQL Query: Added `sp.balance_frequency as balanceFrequency`
- SQL Query: Added `sp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing`
- ResultSet mapping: Added extraction of both fields
### 7. **Database Migration**
**File:** `database_migration_add_custom_fields.sql`
```sql
ALTER TABLE m_savings_product
ADD COLUMN balance_frequency VARCHAR(50) DEFAULT NULL,
ADD COLUMN account_closed_zero_balance_in_day_closing BOOLEAN DEFAULT FALSE;
```
---
## 📋 What These Fields Do
### **balanceFrequency** (String)
- **Purpose:** Defines how often balance is calculated/checked
- **Example Values:** "Daily", "Monthly", "Quarterly", "Yearly"
- **Database Column:** `m_savings_product.balance_frequency`
- **Type:** VARCHAR(50)
### **accountClosedZeroBalanceInDayClosing** (Boolean)
- **Purpose:** Flag to automatically close accounts when balance reaches zero at day end
- **Example Values:** `true` or `false`
- **Default:** `false`
- **Database Column:** `m_savings_product.account_closed_zero_balance_in_day_closing`
- **Type:** BOOLEAN
---
## 🎯 API Usage
### **POST /v1/savingsproducts** (Create)
```json
{
"name": "Saving Product",
"shortName": "SD22",
// ... other fields ...
"balanceFrequency": "Monthly",
"accountClosedZeroBalanceInDayClosing": true,
// ... rest of fields ...
}
```
### **GET /v1/savingsproducts/{productId}** (Read)
Response will include:
```json
{
"id": 1,
"name": "Saving Product",
// ... other fields ...
"balanceFrequency": "Monthly",
"accountClosedZeroBalanceInDayClosing": true,
// ... rest of fields ...
}
```
### **PUT /v1/savingsproducts/{productId}** (Update)
```json
{
"balanceFrequency": "Quarterly",
"accountClosedZeroBalanceInDayClosing": false
}
```
---
## 🗄️ Database Migration Steps
Run this SQL script to add the columns:
```bash
mysql -u root -p your_database < database_migration_add_custom_fields.sql
```
Or manually execute:
```sql
ALTER TABLE m_savings_product
ADD COLUMN balance_frequency VARCHAR(50) DEFAULT NULL,
ADD COLUMN account_closed_zero_balance_in_day_closing BOOLEAN DEFAULT FALSE;
```
---
## ✅ Validation
The fields are now:
- ✅ **Accepted** in CREATE request (POST)
- ✅ **Saved** to database
- ✅ **Returned** in GET response
- ✅ **Updatable** via PUT request
- ✅ **Validated** (no longer rejected as unsupported)
---
## 🚀 Next Steps
1. **Run Database Migration:** Execute the SQL script
2. **Rebuild Application:** `./gradlew clean build`
3. **Restart Server:** Apply the changes
4. **Test API:** Use your payload to create a savings product
---
## 📝 Notes
- Both fields are **optional** (nullable)
- `balanceFrequency` accepts any string value (you can add validation later if needed)
- `accountClosedZeroBalanceInDayClosing` defaults to `false` if not provided
- Both fields support **partial updates** (you can update just one of them)
@@ -0,0 +1,277 @@
i {
"info": {
"name": "Entity Classification - CBS Fineract",
"description": "Entity Classification module for non-individual client onboarding with 7 regulatory categories:\n1. Startup India (DPIIT)\n2. Mudra Borrower (PMMY)\n3. Export Oriented Unit (EOU)\n4. Import/Export (IEC/DGFT)\n5. Priority Sector Lending (PSL)\n6. Non-Priority Sector (NPS)\n7. MSME (Udyam Registration)",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"auth": {
"type": "basic",
"basic": [
{
"key": "username",
"value": "bankadmin",
"type": "string"
},
{
"key": "password",
"value": "LincPay@2026",
"type": "string"
}
]
},
"variable": [
{
"key": "baseUrl",
"value": "https://localhost:8443/fineract-provider/api/v1"
},
{
"key": "clientId",
"value": "49"
},
{
"key": "tenantId",
"value": "default"
}
],
"item": [
{
"name": "GET - Retrieve All Entity Classifications",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"url": {
"raw": "{{baseUrl}}/clients/{{clientId}}/entityclassification",
"host": ["{{baseUrl}}"],
"path": ["clients", "{{clientId}}", "entityclassification"]
},
"description": "Retrieves all 6 entity classification categories for the given client. Returns null for categories that have not been submitted yet."
}
},
{
"name": "POST - Save Startup India (DPIIT)",
"request": {
"method": "POST",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"url": {
"raw": "{{baseUrl}}/clients/{{clientId}}/entityclassification/startup",
"host": ["{{baseUrl}}"],
"path": ["clients", "{{clientId}}", "entityclassification", "startup"]
},
"body": {
"mode": "raw",
"raw": "{\n \"enabled\": true,\n \"dpiitRecognitionNumber\": \"DIPP12345\",\n \"recognitionDate\": \"15 January 2024\",\n \"certificateExpiryDate\": \"14 January 2034\",\n \"entityStage\": \"GROWTH\",\n \"sectorAsDpiit\": \"Information Technology\",\n \"nicIndustryCode\": \"62011\",\n \"taxExemption80iac\": true,\n \"sidbiFund\": false,\n \"selfCertification\": true,\n \"fastTrackPatent\": true,\n \"govtTenderExemption\": true,\n \"creditGuaranteeCgss\": false,\n \"pslLoanLimit\": 5000000.00,\n \"pslSubCategory\": \"StartupIndia-IT\",\n \"locale\": \"en\",\n \"dateFormat\": \"dd MMMM yyyy\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"description": "Creates or updates Startup India (DPIIT) entity classification. Captures DPIIT recognition number, tax exemptions under Section 80IAC, SIDBI fund eligibility, and PSL limits."
}
},
{
"name": "POST - Save Mudra Borrower (PMMY)",
"request": {
"method": "POST",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"url": {
"raw": "{{baseUrl}}/clients/{{clientId}}/entityclassification/mudra",
"host": ["{{baseUrl}}"],
"path": ["clients", "{{clientId}}", "entityclassification", "mudra"]
},
"body": {
"mode": "raw",
"raw": "{\n \"enabled\": true,\n \"mudraCategory\": \"KISHORE\",\n \"mudraLoanAccountNo\": \"MUDRA2024MH00567\",\n \"lendingInstitutionName\": \"State Bank of India\",\n \"sanctionedAmount\": 350000.00,\n \"disbursementDate\": \"01 March 2024\",\n \"loanPurpose\": \"Working Capital\",\n \"mudraCardNumber\": \"MC20240567\",\n \"currentOutstanding\": 280000.00,\n \"repaymentStatus\": \"REGULAR\",\n \"npaFlag\": false,\n \"locale\": \"en\",\n \"dateFormat\": \"dd MMMM yyyy\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"description": "Creates or updates Mudra Borrower (PMMY) entity classification. Supports SHISHU (up to Rs 50K), KISHORE (50K-5L), and TARUN (5L-10L) categories under Pradhan Mantri Mudra Yojana."
}
},
{
"name": "POST - Save Export Oriented Unit (EOU)",
"request": {
"method": "POST",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"url": {
"raw": "{{baseUrl}}/clients/{{clientId}}/entityclassification/eou",
"host": ["{{baseUrl}}"],
"path": ["clients", "{{clientId}}", "entityclassification", "eou"]
},
"body": {
"mode": "raw",
"raw": "{\n \"enabled\": true,\n \"eouRegistrationNumber\": \"EOU/MH/2023/00456\",\n \"registeredDcZone\": \"SEEPZ-SEZ Mumbai\",\n \"bondLutNumber\": \"LUT/2024/MH/0089\",\n \"validityFrom\": \"01 April 2023\",\n \"validityTo\": \"31 March 2028\",\n \"gstinEou\": \"27AABCU9603R1ZM\",\n \"annualExportObligation\": 25000000.00,\n \"nfeTarget\": 5000000.00,\n \"softexFilingStatus\": \"FILED\",\n \"brcSubmissionStatus\": \"SUBMITTED\",\n \"adCategoryBankCode\": \"AD-I\",\n \"femaDeclarationDate\": \"15 April 2023\",\n \"locale\": \"en\",\n \"dateFormat\": \"dd MMMM yyyy\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"description": "Creates or updates Export Oriented Unit (EOU) entity classification. Captures EOU registration, Bond/LUT details, GSTIN, export obligations, NFE targets, and FEMA compliance."
}
},
{
"name": "POST - Save Import/Export (IEC/DGFT)",
"request": {
"method": "POST",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"url": {
"raw": "{{baseUrl}}/clients/{{clientId}}/entityclassification/importexport",
"host": ["{{baseUrl}}"],
"path": ["clients", "{{clientId}}", "entityclassification", "importexport"]
},
"body": {
"mode": "raw",
"raw": "{\n \"enabled\": true,\n \"iecCode\": \"0305012345\",\n \"iecIssueDate\": \"10 June 2020\",\n \"iecStatus\": \"ACTIVE\",\n \"tradeType\": \"BOTH\",\n \"primaryHsCode\": \"8471\",\n \"keyTradingCountries\": \"USA, UAE, Singapore, Germany\",\n \"adCategoryBankCode\": \"AD-I\",\n \"ecgcPolicyNumber\": \"ECGC/WTP/2024/00123\",\n \"ecgcPolicyExpiry\": \"31 March 2025\",\n \"eefcAccountRequired\": true,\n \"pcfcFacilityRequired\": false,\n \"femaDeclarationObtained\": true,\n \"locale\": \"en\",\n \"dateFormat\": \"dd MMMM yyyy\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"description": "Creates or updates Import/Export (IEC/DGFT) entity classification. Captures Importer Exporter Code, HS codes, ECGC insurance, EEFC/PCFC requirements, and FEMA compliance."
}
},
{
"name": "POST - Save Priority Sector Lending (PSL)",
"request": {
"method": "POST",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"url": {
"raw": "{{baseUrl}}/clients/{{clientId}}/entityclassification/psl",
"host": ["{{baseUrl}}"],
"path": ["clients", "{{clientId}}", "entityclassification", "psl"]
},
"body": {
"mode": "raw",
"raw": "{\n \"enabled\": true,\n \"pslCategory\": \"MICRO_ENTERPRISE\",\n \"pslSubCategory\": \"Manufacturing\",\n \"pslLoanPurposeCode\": \"ME01\",\n \"pslEligibilityBasis\": \"Udyam Registration - Micro Enterprise with investment up to Rs 1 Cr\",\n \"fiddReportingCode\": \"FIDD-ME-MFG\",\n \"pslSanctionedLimit\": 10000000.00,\n \"msmeClassification\": \"MICRO\",\n \"udyamRegistrationNumber\": \"UDYAM-MH-01-0012345\",\n \"udyamRegistrationDate\": \"20 July 2023\",\n \"annualTurnover\": 45000000.00,\n \"investmentPlantMachinery\": 8500000.00,\n \"weakerSectionFlag\": true,\n \"scStBorrowerFlag\": false,\n \"womenEntrepreneurFlag\": true,\n \"minorityCommunityFlag\": false,\n \"locale\": \"en\",\n \"dateFormat\": \"dd MMMM yyyy\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"description": "Creates or updates Priority Sector Lending (PSL) entity classification as per RBI Master Direction. Captures PSL category, MSME classification, Udyam registration, FIDD reporting codes, and weaker section flags."
}
},
{
"name": "POST - Save Non-Priority Sector (NPS)",
"request": {
"method": "POST",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"url": {
"raw": "{{baseUrl}}/clients/{{clientId}}/entityclassification/nps",
"host": ["{{baseUrl}}"],
"path": ["clients", "{{clientId}}", "entityclassification", "nps"]
},
"body": {
"mode": "raw",
"raw": "{\n \"enabled\": true,\n \"industrySector\": \"Real Estate\",\n \"nicCode\": \"68100\",\n \"industryDescription\": \"Real estate activities with owned or leased property - Commercial\",\n \"annualRevenueBand\": \"50CR_TO_100CR\",\n \"riskCategory\": \"MEDIUM\",\n \"customerRiskScore\": 55,\n \"amlRiskClassification\": \"Medium Risk - Enhanced Due Diligence Required\",\n \"pepFlag\": false,\n \"amlWatchlistMatch\": false,\n \"adverseMediaFlag\": false\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"description": "Creates or updates Non-Priority Sector (NPS) entity classification. Captures industry details, NIC code, risk categorization, AML risk classification, PEP status, and adverse media flags."
}
},
{
"name": "POST - Save MSME (Udyam Registration)",
"request": {
"method": "POST",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"url": {
"raw": "{{baseUrl}}/clients/{{clientId}}/entityclassification/msme",
"host": ["{{baseUrl}}"],
"path": ["clients", "{{clientId}}", "entityclassification", "msme"]
},
"body": {
"mode": "raw",
"raw": "{\n \"enabled\": true,\n \"udyamRegistrationNumber\": \"UDYAM-MP-23-0012345\",\n \"registrationDate\": \"15 March 2023\",\n \"registrationStatus\": \"ACTIVE\",\n \"msmeClassification\": \"SMALL\",\n \"enterpriseType\": \"MANUFACTURING\",\n \"nicActivityCode\": \"13111\",\n \"annualTurnover\": 12.50,\n \"investmentPlantMachinery\": 3.75,\n \"financialYearAssessment\": \"2024-25\",\n \"socialCategoryOwner\": \"GENERAL\",\n \"genderPrimaryOwner\": \"MALE\",\n \"noOfEmployees\": 45,\n \"womenLedEnterprise\": false,\n \"divyangPwdOwned\": false,\n \"dateFormat\": \"dd MMMM yyyy\",\n \"locale\": \"en\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"description": "Creates or updates MSME (Udyam Registration) entity classification. Captures Udyam number, MSME classification (Micro/Small/Medium), enterprise type, financial thresholds, owner/promoter details, and special flags (women-led, PWD owned)."
}
}
]
}
@@ -0,0 +1,607 @@
{
"info": {
"_postman_id": "user-mgmt-collection",
"name": "Fineract - User Management",
"description": "Fineract User Management APIs - Login, Create, Update, Change Password, Delete Users",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"auth": {
"type": "basic",
"basic": [
{
"key": "username",
"value": "{{username}}",
"type": "string"
},
{
"key": "password",
"value": "{{password}}",
"type": "string"
}
]
},
"variable": [
{
"key": "baseUrl",
"value": "https://localhost:8443/fineract-provider/api"
},
{
"key": "username",
"value": "mifos"
},
{
"key": "password",
"value": "password"
},
{
"key": "tenantId",
"value": "default"
},
{
"key": "userId",
"value": "1"
}
],
"item": [
{
"name": "Authentication",
"item": [
{
"name": "Login (Self-Service)",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"username\": \"mifos\",\n \"password\": \"password\"\n}"
},
"url": {
"raw": "{{baseUrl}}/v1/self/authentication?returnClientList=true",
"host": ["{{baseUrl}}"],
"path": ["v1", "self", "authentication"],
"query": [
{
"key": "returnClientList",
"value": "true"
}
]
},
"description": "Authenticates a user and returns auth token. Use this for self-service users."
}
},
{
"name": "Login (Basic Auth Test)",
"request": {
"method": "GET",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"url": {
"raw": "{{baseUrl}}/v1/users/1",
"host": ["{{baseUrl}}"],
"path": ["v1", "users", "1"]
},
"description": "Test basic auth by fetching user details. If credentials are correct, you'll get a 200 response."
}
}
]
},
{
"name": "Users",
"item": [
{
"name": "Get All Users",
"request": {
"method": "GET",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"url": {
"raw": "{{baseUrl}}/v1/users",
"host": ["{{baseUrl}}"],
"path": ["v1", "users"]
}
}
},
{
"name": "Get User by ID",
"request": {
"method": "GET",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"url": {
"raw": "{{baseUrl}}/v1/users/{{userId}}",
"host": ["{{baseUrl}}"],
"path": ["v1", "users", "{{userId}}"]
}
}
},
{
"name": "Get User Template",
"request": {
"method": "GET",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"url": {
"raw": "{{baseUrl}}/v1/users/template",
"host": ["{{baseUrl}}"],
"path": ["v1", "users", "template"]
},
"description": "Returns template data (available offices, roles) needed to create a new user."
}
},
{
"name": "Create User",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"username\": \"newuser\",\n \"firstname\": \"New\",\n \"lastname\": \"User\",\n \"email\": \"newuser@example.com\",\n \"officeId\": 1,\n \"roles\": [1],\n \"sendPasswordToEmail\": false,\n \"password\": \"Password@123\",\n \"repeatPassword\": \"Password@123\",\n \"isSelfServiceUser\": false,\n \"passwordNeverExpires\": true\n}"
},
"url": {
"raw": "{{baseUrl}}/v1/users",
"host": ["{{baseUrl}}"],
"path": ["v1", "users"]
}
}
},
{
"name": "Update User (Change Username)",
"request": {
"method": "PUT",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"username\": \"updatedUsername\"\n}"
},
"url": {
"raw": "{{baseUrl}}/v1/users/{{userId}}",
"host": ["{{baseUrl}}"],
"path": ["v1", "users", "{{userId}}"]
}
}
},
{
"name": "Update User (Change Password via PUT)",
"request": {
"method": "PUT",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"password\": \"NewPassword@123\",\n \"repeatPassword\": \"NewPassword@123\"\n}"
},
"url": {
"raw": "{{baseUrl}}/v1/users/{{userId}}",
"host": ["{{baseUrl}}"],
"path": ["v1", "users", "{{userId}}"]
}
}
},
{
"name": "Change Password (Dedicated Endpoint)",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"password\": \"NewPassword@123\",\n \"repeatPassword\": \"NewPassword@123\"\n}"
},
"url": {
"raw": "{{baseUrl}}/v1/users/{{userId}}/pwd",
"host": ["{{baseUrl}}"],
"path": ["v1", "users", "{{userId}}", "pwd"]
},
"description": "Dedicated password change endpoint. Must provide both password and repeatPassword."
}
},
{
"name": "Update User (Full Update)",
"request": {
"method": "PUT",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"username\": \"updatedUser\",\n \"firstname\": \"Updated\",\n \"lastname\": \"User\",\n \"email\": \"updated@example.com\",\n \"officeId\": 1,\n \"roles\": [1],\n \"password\": \"NewPassword@123\",\n \"repeatPassword\": \"NewPassword@123\",\n \"passwordNeverExpires\": true\n}"
},
"url": {
"raw": "{{baseUrl}}/v1/users/{{userId}}",
"host": ["{{baseUrl}}"],
"path": ["v1", "users", "{{userId}}"]
}
}
},
{
"name": "Delete User",
"request": {
"method": "DELETE",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"url": {
"raw": "{{baseUrl}}/v1/users/{{userId}}",
"host": ["{{baseUrl}}"],
"path": ["v1", "users", "{{userId}}"]
}
}
}
]
},
{
"name": "Roles",
"item": [
{
"name": "Get All Roles",
"request": {
"method": "GET",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"url": {
"raw": "{{baseUrl}}/v1/roles",
"host": ["{{baseUrl}}"],
"path": ["v1", "roles"]
}
}
},
{
"name": "Get Role by ID",
"request": {
"method": "GET",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"url": {
"raw": "{{baseUrl}}/v1/roles/1",
"host": ["{{baseUrl}}"],
"path": ["v1", "roles", "1"]
}
}
},
{
"name": "Get Role Permissions",
"request": {
"method": "GET",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"url": {
"raw": "{{baseUrl}}/v1/roles/1/permissions",
"host": ["{{baseUrl}}"],
"path": ["v1", "roles", "1", "permissions"]
}
}
},
{
"name": "Create Role",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"New Role\",\n \"description\": \"A new custom role\"\n}"
},
"url": {
"raw": "{{baseUrl}}/v1/roles",
"host": ["{{baseUrl}}"],
"path": ["v1", "roles"]
}
}
},
{
"name": "Update Role",
"request": {
"method": "PUT",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"Updated Role\",\n \"description\": \"Updated description\"\n}"
},
"url": {
"raw": "{{baseUrl}}/v1/roles/1",
"host": ["{{baseUrl}}"],
"path": ["v1", "roles", "1"]
}
}
},
{
"name": "Update Role Permissions",
"request": {
"method": "PUT",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"permissions\": {\n \"READ_USER\": true,\n \"CREATE_USER\": true,\n \"UPDATE_USER\": true\n }\n}"
},
"url": {
"raw": "{{baseUrl}}/v1/roles/1/permissions",
"host": ["{{baseUrl}}"],
"path": ["v1", "roles", "1", "permissions"]
}
}
},
{
"name": "Enable Role",
"request": {
"method": "POST",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"url": {
"raw": "{{baseUrl}}/v1/roles/1?command=enable",
"host": ["{{baseUrl}}"],
"path": ["v1", "roles", "1"],
"query": [
{
"key": "command",
"value": "enable"
}
]
}
}
},
{
"name": "Disable Role",
"request": {
"method": "POST",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"url": {
"raw": "{{baseUrl}}/v1/roles/1?command=disable",
"host": ["{{baseUrl}}"],
"path": ["v1", "roles", "1"],
"query": [
{
"key": "command",
"value": "disable"
}
]
}
}
},
{
"name": "Delete Role",
"request": {
"method": "DELETE",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"url": {
"raw": "{{baseUrl}}/v1/roles/1",
"host": ["{{baseUrl}}"],
"path": ["v1", "roles", "1"]
}
}
}
]
},
{
"name": "Password Preferences",
"item": [
{
"name": "Get Password Preferences",
"request": {
"method": "GET",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"url": {
"raw": "{{baseUrl}}/v1/passwordpreferences",
"host": ["{{baseUrl}}"],
"path": ["v1", "passwordpreferences"]
}
}
},
{
"name": "Get Password Preferences Template",
"request": {
"method": "GET",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"url": {
"raw": "{{baseUrl}}/v1/passwordpreferences/template",
"host": ["{{baseUrl}}"],
"path": ["v1", "passwordpreferences", "template"]
}
}
},
{
"name": "Update Password Preferences",
"request": {
"method": "PUT",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"validationPolicyId\": 2\n}"
},
"url": {
"raw": "{{baseUrl}}/v1/passwordpreferences",
"host": ["{{baseUrl}}"],
"path": ["v1", "passwordpreferences"]
}
}
}
]
},
{
"name": "Permissions",
"item": [
{
"name": "Get All Permissions",
"request": {
"method": "GET",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"url": {
"raw": "{{baseUrl}}/v1/permissions",
"host": ["{{baseUrl}}"],
"path": ["v1", "permissions"]
}
}
},
{
"name": "Update Permissions (Maker-Checker)",
"request": {
"method": "PUT",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"permissions\": {\n \"CREATE_USER_MAKER\": true,\n \"CREATE_USER_CHECKER\": true\n }\n}"
},
"url": {
"raw": "{{baseUrl}}/v1/permissions",
"host": ["{{baseUrl}}"],
"path": ["v1", "permissions"]
}
}
}
]
}
]
}
+668
View File
@@ -0,0 +1,668 @@
# Savings Product Payload Implementation Guide
## Overview
Your UI has been updated to send a comprehensive new payload with additional fields for the `/savingsproducts` POST endpoint. This document outlines **all changes required** in the backend to support these new fields.
---
## 🔍 New Fields in Your Payload
### **Simple Fields (Direct Columns in m_savings_product table)**
| Field | Type | Example | Description |
|-------|------|---------|-------------|
| accountType | String | "Small Saving Account" | Type of savings account |
| customerSegment | String | "Individual" | Customer classification |
| productCode | String | "SB-IND-001" | Product identifier code |
| effectiveDate | Date | "10 April 2026" | When the product becomes effective |
| effectiveTo | Date | "10 April 2026" | Validity end date |
| endDate | Date | "10 April 2026" | Product end date |
| introducerRequired | Boolean | true | If introducer/referrer is mandatory |
| shareLinkingEnabled | Boolean | true | If share account linking allowed |
| nominationFacility | Boolean | true | If nomination facility available |
| dormancyAlert | Boolean | true | If dormancy alerts enabled |
| interestCalculateSelector | Integer | 2 | Interest calculation method selector |
| averagePeriod | String | "Monthly" | Period for averaging balance |
| autoPost | Boolean | true | Auto-post interest |
| frequencyFixDay | Integer | 30 | Fixed day for frequency |
| lastInterestDate | Date | "10 April 2026" | Last interest posting date |
| enableLockinPeriod | Boolean | true | If lock-in period enforced |
### **Complex Objects (Require Separate Tables)**
#### 1️⃣ **accountFacilityConfig** (Single object with facility booleans)
```json
{
"atmFacility": true,
"atmWithdrawalFacility": true,
"upiFacility": true,
"posTransactionsFacility": true,
"chequeBook": true,
"impsFacility": true,
"smsAlerts": true,
"bbpsFacility": true,
"insuranceCover": true,
"bepgFacility": true,
"neftFacility": true,
"rtgsFacility": true,
"interBankTotalCapFacility": true,
"withdrawalFacility": true,
"depositFacilityKyc": true,
"virtualDebitCard": true
}
```
→ **Table:** `m_savings_product_facility_config` (One-to-One relationship)
#### 2️⃣ **accountFacilityLimits** (Array of limit objects)
```json
[
{"limitType": "ATM Withdrawal", "generalLimit": 40000, "minorAccountLimit": 5000, "smallSavingLimit": 10000},
{"limitType": "POS Transactions", "generalLimit": 100000, "minorAccountLimit": 2000, "smallSavingLimit": 10000},
...
]
```
→ **Table:** `m_savings_product_facility_limits` (One-to-Many relationship)
#### 3️⃣ **depositLimitKyc** (Nested KYC limit rules)
```json
{
"general": {"kycType": "Full KYC", "limit": "No Limit"},
"minor": {"kycType": "Guardian KYC", "limit": 100000},
"smallSaving": {"kycType": "Min KYC", "limit": 50000}
}
```
→ **Table:** `m_savings_product_deposit_limit_kyc` (One-to-One relationship, or JSON column)
---
## 📋 Database Changes Required
### **1. Alter m_savings_product table**
```sql
ALTER TABLE m_savings_product ADD COLUMN (
account_type VARCHAR(50),
customer_segment VARCHAR(50),
product_code VARCHAR(20) UNIQUE,
effective_date DATE,
effective_to DATE,
end_date DATE,
introducer_required BOOLEAN DEFAULT FALSE,
share_linking_enabled BOOLEAN DEFAULT FALSE,
nomination_facility BOOLEAN DEFAULT FALSE,
dormancy_alert BOOLEAN DEFAULT FALSE,
interest_calculate_selector INT,
average_period VARCHAR(20),
auto_post BOOLEAN DEFAULT FALSE,
frequency_fix_day INT,
last_interest_date DATE,
enable_lockin_period BOOLEAN DEFAULT FALSE
);
```
### **2. Create m_savings_product_facility_config table** (One-to-One)
```sql
CREATE TABLE m_savings_product_facility_config (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
savings_product_id BIGINT NOT NULL UNIQUE,
atm_facility BOOLEAN DEFAULT FALSE,
atm_withdrawal_facility BOOLEAN DEFAULT FALSE,
upi_facility BOOLEAN DEFAULT FALSE,
pos_transactions_facility BOOLEAN DEFAULT FALSE,
cheque_book BOOLEAN DEFAULT FALSE,
imps_facility BOOLEAN DEFAULT FALSE,
sms_alerts BOOLEAN DEFAULT FALSE,
bbps_facility BOOLEAN DEFAULT FALSE,
insurance_cover BOOLEAN DEFAULT FALSE,
bepg_facility BOOLEAN DEFAULT FALSE,
neft_facility BOOLEAN DEFAULT FALSE,
rtgs_facility BOOLEAN DEFAULT FALSE,
inter_bank_total_cap_facility BOOLEAN DEFAULT FALSE,
withdrawal_facility BOOLEAN DEFAULT FALSE,
deposit_facility_kyc BOOLEAN DEFAULT FALSE,
virtual_debit_card BOOLEAN DEFAULT FALSE,
created_date DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (savings_product_id) REFERENCES m_savings_product(id) ON DELETE CASCADE
);
```
### **3. Create m_savings_product_facility_limits table** (One-to-Many)
```sql
CREATE TABLE m_savings_product_facility_limits (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
savings_product_id BIGINT NOT NULL,
limit_type VARCHAR(100) NOT NULL,
general_limit VARCHAR(50), -- Can be decimal or "N/A", "UNLIMITED"
minor_account_limit VARCHAR(50),
small_saving_limit VARCHAR(50),
created_date DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (savings_product_id) REFERENCES m_savings_product(id) ON DELETE CASCADE,
UNIQUE KEY unique_product_limit_type (savings_product_id, limit_type)
);
```
### **4. Create m_savings_product_deposit_limit_kyc table** (One-to-One)
```sql
CREATE TABLE m_savings_product_deposit_limit_kyc (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
savings_product_id BIGINT NOT NULL UNIQUE,
general_kyc_type VARCHAR(50),
general_limit VARCHAR(50), -- "No Limit" or numeric
minor_kyc_type VARCHAR(50),
minor_limit BIGINT,
small_saving_kyc_type VARCHAR(50),
small_saving_limit BIGINT,
created_date DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (savings_product_id) REFERENCES m_savings_product(id) ON DELETE CASCADE
);
```
---
## 🏗️ Java Entity Classes to Create/Update
### **1. Update SavingsProduct Entity**
Add these fields to `fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java`:
```java
@Column(name = "account_type", length = 50)
private String accountType;
@Column(name = "customer_segment", length = 50)
private String customerSegment;
@Column(name = "product_code", length = 20, unique = true)
private String productCode;
@Column(name = "effective_date")
private LocalDate effectiveDate;
@Column(name = "effective_to")
private LocalDate effectiveTo;
@Column(name = "end_date")
private LocalDate endDate;
@Column(name = "introducer_required")
private boolean introducerRequired;
@Column(name = "share_linking_enabled")
private boolean shareLinkingEnabled;
@Column(name = "nomination_facility")
private boolean nominationFacility;
@Column(name = "dormancy_alert")
private boolean dormancyAlert;
@Column(name = "interest_calculate_selector")
private Integer interestCalculateSelector;
@Column(name = "average_period", length = 20)
private String averagePeriod;
@Column(name = "auto_post")
private boolean autoPost;
@Column(name = "frequency_fix_day")
private Integer frequencyFixDay;
@Column(name = "last_interest_date")
private LocalDate lastInterestDate;
@Column(name = "enable_lockin_period")
private boolean enableLockinPeriod;
// One-to-One relationship
@OneToOne(cascade = CascadeType.ALL, mappedBy = "savingsProduct", orphanRemoval = true)
private SavingsProductFacilityConfig facilityConfig;
// One-to-Many relationship
@OneToMany(cascade = CascadeType.ALL, mappedBy = "savingsProduct", orphanRemoval = true)
private Set<SavingsProductFacilityLimit> facilityLimits = new HashSet<>();
// One-to-One relationship
@OneToOne(cascade = CascadeType.ALL, mappedBy = "savingsProduct", orphanRemoval = true)
private SavingsProductDepositLimitKyc depositLimitKyc;
```
### **2. Create SavingsProductFacilityConfig Entity**
`fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductFacilityConfig.java`:
```java
@Entity
@Table(name = "m_savings_product_facility_config")
public class SavingsProductFacilityConfig extends AbstractPersistableCustom<Long> {
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "savings_product_id", nullable = false)
private SavingsProduct savingsProduct;
@Column(name = "atm_facility")
private Boolean atmFacility = false;
@Column(name = "atm_withdrawal_facility")
private Boolean atmWithdrawalFacility = false;
@Column(name = "upi_facility")
private Boolean upiFacility = false;
@Column(name = "pos_transactions_facility")
private Boolean posTransactionsFacility = false;
@Column(name = "cheque_book")
private Boolean chequeBook = false;
@Column(name = "imps_facility")
private Boolean impsFacility = false;
@Column(name = "sms_alerts")
private Boolean smsAlerts = false;
@Column(name = "bbps_facility")
private Boolean bbpsFacility = false;
@Column(name = "insurance_cover")
private Boolean insuranceCover = false;
@Column(name = "bepg_facility")
private Boolean bepgFacility = false;
@Column(name = "neft_facility")
private Boolean neftFacility = false;
@Column(name = "rtgs_facility")
private Boolean rtgsFacility = false;
@Column(name = "inter_bank_total_cap_facility")
private Boolean interBankTotalCapFacility = false;
@Column(name = "withdrawal_facility")
private Boolean withdrawalFacility = false;
@Column(name = "deposit_facility_kyc")
private Boolean depositFacilityKyc = false;
@Column(name = "virtual_debit_card")
private Boolean virtualDebitCard = false;
// Getters and setters
}
```
### **3. Create SavingsProductFacilityLimit Entity**
`fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductFacilityLimit.java`:
```java
@Entity
@Table(name = "m_savings_product_facility_limits")
public class SavingsProductFacilityLimit extends AbstractPersistableCustom<Long> {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "savings_product_id", nullable = false)
private SavingsProduct savingsProduct;
@Column(name = "limit_type", length = 100, nullable = false)
private String limitType;
@Column(name = "general_limit", length = 50)
private String generalLimit; // Can be "UNLIMITED", "N/A", or numeric value
@Column(name = "minor_account_limit", length = 50)
private String minorAccountLimit;
@Column(name = "small_saving_limit", length = 50)
private String smallSavingLimit;
// Getters and setters
public static SavingsProductFacilityLimit create(
SavingsProduct savingsProduct,
String limitType,
String generalLimit,
String minorAccountLimit,
String smallSavingLimit) {
SavingsProductFacilityLimit limit = new SavingsProductFacilityLimit();
limit.savingsProduct = savingsProduct;
limit.limitType = limitType;
limit.generalLimit = generalLimit;
limit.minorAccountLimit = minorAccountLimit;
limit.smallSavingLimit = smallSavingLimit;
return limit;
}
}
```
### **4. Create SavingsProductDepositLimitKyc Entity**
`fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductDepositLimitKyc.java`:
```java
@Entity
@Table(name = "m_savings_product_deposit_limit_kyc")
public class SavingsProductDepositLimitKyc extends AbstractPersistableCustom<Long> {
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "savings_product_id", nullable = false)
private SavingsProduct savingsProduct;
@Column(name = "general_kyc_type", length = 50)
private String generalKycType;
@Column(name = "general_limit", length = 50)
private String generalLimit; // "No Limit" or numeric
@Column(name = "minor_kyc_type", length = 50)
private String minorKycType;
@Column(name = "minor_limit")
private Long minorLimit;
@Column(name = "small_saving_kyc_type", length = 50)
private String smallSavingKycType;
@Column(name = "small_saving_limit")
private Long smallSavingLimit;
// Getters and setters
}
```
---
## 🔑 API Constants to Add
Update `fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java`:
```java
// New Savings Product Fields
public static final String accountTypeParamName = "accountType";
public static final String customerSegmentParamName = "customerSegment";
public static final String productCodeParamName = "productCode";
public static final String effectiveDateParamName = "effectiveDate";
public static final String effectiveToParamName = "effectiveTo";
public static final String endDateParamName = "endDate";
public static final String introducerRequiredParamName = "introducerRequired";
public static final String shareLinkingEnabledParamName = "shareLinkingEnabled";
public static final String nominationFacilityParamName = "nominationFacility";
public static final String dormancyAlertParamName = "dormancyAlert";
public static final String interestCalculateSelectorParamName = "interestCalculateSelector";
public static final String averagePeriodParamName = "averagePeriod";
public static final String autoPostParamName = "autoPost";
public static final String frequencyFixDayParamName = "frequencyFixDay";
public static final String lastInterestDateParamName = "lastInterestDate";
public static final String enableLockinPeriodParamName = "enableLockinPeriod";
// Complex objects
public static final String accountFacilityConfigParamName = "accountFacilityConfig";
public static final String accountFacilityLimitsParamName = "accountFacilityLimits";
public static final String depositLimitKycParamName = "depositLimitKyc";
// Facility config fields
public static final String atmFacilityParamName = "atmFacility";
public static final String atmWithdrawalFacilityParamName = "atmWithdrawalFacility";
public static final String upiFacilityParamName = "upiFacility";
// ... etc for all facility fields
// Limit fields
public static final String limitTypeParamName = "limitType";
public static final String generalLimitParamName = "generalLimit";
public static final String minorAccountLimitParamName = "minorAccountLimit";
public static final String smallSavingLimitParamName = "smallSavingLimit";
```
---
## ✅ Validator Updates
Update `fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java`:
1. **Add to parameter whitelist:**
```java
private static final Set<String> SAVINGS_PRODUCT_REQUEST_DATA_PARAMETERS =
new HashSet<>(Arrays.asList(
// ... existing parameters ...
"accountType",
"customerSegment",
"productCode",
"effectiveDate",
"effectiveTo",
"endDate",
"introducerRequired",
"shareLinkingEnabled",
"nominationFacility",
"dormancyAlert",
"interestCalculateSelector",
"averagePeriod",
"autoPost",
"frequencyFixDay",
"lastInterestDate",
"enableLockinPeriod",
"accountFacilityConfig",
"accountFacilityLimits",
"depositLimitKyc"
));
```
2. **Add validation logic in validateForCreate() and validateForUpdate():**
```java
if (command.stringValueOfParameterNamedAllowingNull("productCode") != null) {
final String productCode = command.stringValueOfParameterNamed("productCode");
if (StringUtils.isBlank(productCode)) {
baseDataValidator.reset().parameter("productCode").value(productCode).notBlank();
}
}
if (command.hasParameter("effectiveDate")) {
final LocalDate effectiveDate = command.localDateValueOfParameterNamed("effectiveDate");
baseDataValidator.reset().parameter("effectiveDate").value(effectiveDate).notNull();
}
// Similar for other date fields
if (command.hasParameter("effectiveTo") && command.hasParameter("effectiveDate")) {
final LocalDate effectiveDate = command.localDateValueOfParameterNamed("effectiveDate");
final LocalDate effectiveTo = command.localDateValueOfParameterNamed("effectiveTo");
if (effectiveTo.isBefore(effectiveDate)) {
baseDataValidator.reset().parameter("effectiveTo").value(effectiveTo)
.failWithCode("error.msg.effectiveto.cannot.be.before.effectivedate");
}
}
```
---
## 🔧 Assembler Updates
Update `fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductAssembler.java`:
Add helper methods to extract and assemble nested objects:
```java
public SavingsProductFacilityConfig assembleAccountFacilityConfig(final JsonCommand command, final SavingsProduct product) {
final JsonObject jsonObject = command.parsedJson();
if (!jsonObject.has("accountFacilityConfig")) {
return null;
}
final JsonObject facilityConfigJson = jsonObject.getAsJsonObject("accountFacilityConfig");
SavingsProductFacilityConfig config = new SavingsProductFacilityConfig();
config.setSavingsProduct(product);
config.setAtmFacility(facilityConfigJson.has("atmFacility") ?
facilityConfigJson.getAsJsonPrimitive("atmFacility").getAsBoolean() : false);
config.setAtmWithdrawalFacility(facilityConfigJson.has("atmWithdrawalFacility") ?
facilityConfigJson.getAsJsonPrimitive("atmWithdrawalFacility").getAsBoolean() : false);
// ... set other facilities ...
return config;
}
public Set<SavingsProductFacilityLimit> assembleAccountFacilityLimits(final JsonCommand command, final SavingsProduct product) {
final JsonObject jsonObject = command.parsedJson();
final Set<SavingsProductFacilityLimit> limits = new HashSet<>();
if (!jsonObject.has("accountFacilityLimits")) {
return limits;
}
final JsonArray limitsArray = jsonObject.getAsJsonArray("accountFacilityLimits");
for (JsonElement element : limitsArray) {
final JsonObject limitJson = element.getAsJsonObject();
SavingsProductFacilityLimit limit = SavingsProductFacilityLimit.create(
product,
limitJson.has("limitType") ? limitJson.getAsJsonPrimitive("limitType").getAsString() : null,
limitJson.has("generalLimit") ? limitJson.getAsJsonPrimitive("generalLimit").getAsString() : null,
limitJson.has("minorAccountLimit") ? limitJson.getAsJsonPrimitive("minorAccountLimit").getAsString() : null,
limitJson.has("smallSavingLimit") ? limitJson.getAsJsonPrimitive("smallSavingLimit").getAsString() : null
);
limits.add(limit);
}
return limits;
}
public SavingsProductDepositLimitKyc assembleDepositLimitKyc(final JsonCommand command, final SavingsProduct product) {
final JsonObject jsonObject = command.parsedJson();
if (!jsonObject.has("depositLimitKyc")) {
return null;
}
final JsonObject kycJson = jsonObject.getAsJsonObject("depositLimitKyc");
SavingsProductDepositLimitKyc kyc = new SavingsProductDepositLimitKyc();
kyc.setSavingsProduct(product);
if (kycJson.has("general")) {
final JsonObject generalJson = kycJson.getAsJsonObject("general");
kyc.setGeneralKycType(generalJson.has("kycType") ?
generalJson.getAsJsonPrimitive("kycType").getAsString() : null);
kyc.setGeneralLimit(generalJson.has("limit") ?
generalJson.getAsJsonPrimitive("limit").getAsString() : null);
}
// ... handle minor and smallSaving similarly ...
return kyc;
}
```
---
## 💾 Repository Classes to Create
### **SavingsProductFacilityConfigRepository.java**
```java
@Repository
public interface SavingsProductFacilityConfigRepository extends
JpaRepository<SavingsProductFacilityConfig, Long> {
SavingsProductFacilityConfig findBySavingsProductId(Long savingsProductId);
}
```
### **SavingsProductFacilityLimitRepository.java**
```java
@Repository
public interface SavingsProductFacilityLimitRepository extends
JpaRepository<SavingsProductFacilityLimit, Long> {
List<SavingsProductFacilityLimit> findBySavingsProductId(Long savingsProductId);
}
```
### **SavingsProductDepositLimitKycRepository.java**
```java
@Repository
public interface SavingsProductDepositLimitKycRepository extends
JpaRepository<SavingsProductDepositLimitKyc, Long> {
SavingsProductDepositLimitKyc findBySavingsProductId(Long savingsProductId);
}
```
---
## 🚀 Service Updates
Update `SavingsProductWritePlatformServiceImpl.create()` method:
```java
public CommandProcessingResult create(final JsonCommand command) {
try {
this.savingsProductDataValidator.validateForCreate(command.json());
// ... existing code ...
// Assemble new objects
final SavingsProductFacilityConfig facilityConfig =
this.savingsProductAssembler.assembleAccountFacilityConfig(command, product);
final Set<SavingsProductFacilityLimit> facilityLimits =
this.savingsProductAssembler.assembleAccountFacilityLimits(command, product);
final SavingsProductDepositLimitKyc depositLimitKyc =
this.savingsProductAssembler.assembleDepositLimitKyc(command, product);
if (facilityConfig != null) {
product.setFacilityConfig(facilityConfig);
facilityConfigRepository.saveAndFlush(facilityConfig);
}
if (!facilityLimits.isEmpty()) {
product.setFacilityLimits(facilityLimits);
facilityLimitRepository.saveAll(facilityLimits);
}
if (depositLimitKyc != null) {
product.setDepositLimitKyc(depositLimitKyc);
depositLimitKycRepository.saveAndFlush(depositLimitKyc);
}
// Save main product
final SavingsProduct productSaved = this.savingProductRepository.saveAndFlush(product);
return new CommandProcessingResultBuilder()
.withCommandId(command.commandId())
.withEntityId(productSaved.getId())
.build();
} catch (final DataAccessException e) {
// error handling
}
}
```
---
## 📊 Read Service Updates
Update `SavingsProductReadPlatformService` to include new fields in queries and DTO.
---
## 🎯 Summary of Changes
| Component | Type | Files to Modify/Create |
|-----------|------|----------------------|
| Database | Migration | SQL scripts |
| Entities | Create/Modify | 4 entity classes |
| Constants | Modify | SavingsApiConstants.java |
| Validator | Modify | SavingsProductDataValidator.java |
| Assembler | Modify | SavingsProductAssembler.java |
| Repositories | Create | 3 repository interfaces |
| Services | Modify | SavingsProductWrite/ReadPlatformService |
| DAO | Modify | SQL queries in read service |
---
## 📌 Implementation Priority
1. **Phase 1:** Create database tables
2. **Phase 2:** Create entity classes
3. **Phase 3:** Add API constants and validator rules
4. **Phase 4:** Create repositories and update service layer
5. **Phase 5:** Update read service and DTOs
6. **Phase 6:** Test with your POST endpoint
---
## ⚠️ Important Notes
- ✅ Fields like `otherGlMappings`, `isDormancyTrackingActive`, etc. **already exist** in the system
- ✅ Ensure backward compatibility - make all new fields optional in the validator
- ✅ Use cascade = CascadeType.ALL and orphanRemoval = true for nested entities
- ✅ Add unique constraints where needed (e.g., productCode, limit combinations)
- ✅ Consider adding indexes on frequently queried fields
- ✅ Update DTO classes to include these new fields in API responses
+2 -2
View File
@@ -1,8 +1,8 @@
Apache Fineract
Copyright 2008-2025 The Apache Software Foundation
This product includes software developed by The Apache Software
Foundation (http://www.apache.org/).
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).
This product includes third party libraries and resource files from
different providers. The details of the different packages and their
+2 -2
View File
@@ -1,5 +1,5 @@
Apache Fineract
Copyright 2008-2025 The Apache Software Foundation
This product includes software developed by The Apache Software
Foundation (http://www.apache.org/).
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).
+273
View File
@@ -0,0 +1,273 @@
{
"info": {
"_postman_id": "op-fineract-collection",
"name": "Fineract - OpenProject Integration",
"description": "Postman collection for Fineract OpenProject integration API endpoints.\n\nBase URL: {{baseUrl}}/fineract-provider/api/v1/openproject\n\nAuthentication: Basic Auth (Fineract user credentials)",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"auth": {
"type": "basic",
"basic": [
{
"key": "username",
"value": "{{fineractUser}}",
"type": "string"
},
{
"key": "password",
"value": "{{fineractPassword}}",
"type": "string"
}
]
},
"variable": [
{
"key": "baseUrl",
"value": "https://localhost:8443",
"type": "string"
},
{
"key": "fineractUser",
"value": "mifos",
"type": "string"
},
{
"key": "fineractPassword",
"value": "password",
"type": "string"
},
{
"key": "tenantId",
"value": "default",
"type": "string"
}
],
"item": [
{
"name": "Get Project Details",
"request": {
"method": "GET",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/openproject/project",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "openproject", "project"]
},
"description": "Returns the configured OpenProject project details (CBSNexora)."
}
},
{
"name": "Get Work Packages (All)",
"request": {
"method": "GET",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/openproject/work-packages?pageSize=25&offset=1",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "openproject", "work-packages"],
"query": [
{
"key": "pageSize",
"value": "25"
},
{
"key": "offset",
"value": "1"
}
]
},
"description": "Returns all work packages from the OpenProject project."
}
},
{
"name": "Get Work Packages (Open)",
"request": {
"method": "GET",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/openproject/work-packages?status=open&pageSize=25&offset=1",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "openproject", "work-packages"],
"query": [
{
"key": "status",
"value": "open"
},
{
"key": "pageSize",
"value": "25"
},
{
"key": "offset",
"value": "1"
}
]
},
"description": "Returns only open work packages."
}
},
{
"name": "Get Work Packages (Closed)",
"request": {
"method": "GET",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/openproject/work-packages?status=closed&pageSize=25&offset=1",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "openproject", "work-packages"],
"query": [
{
"key": "status",
"value": "closed"
},
{
"key": "pageSize",
"value": "25"
},
{
"key": "offset",
"value": "1"
}
]
},
"description": "Returns only closed work packages."
}
},
{
"name": "Get Status Summary",
"request": {
"method": "GET",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/openproject/summary",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "openproject", "summary"]
},
"description": "Returns a summary of all work packages grouped by status, type, and priority."
}
},
{
"name": "Create Work Package - Feature",
"request": {
"method": "POST",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"subject\": \"Integrate OpenProject with CBS Dashboard\",\n \"description\": \"Add OpenProject work package tracking to the CBS admin dashboard so project managers can view task progress directly from the banking system.\",\n \"type\": \"Feature\"\n}"
},
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/openproject/work-packages",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "openproject", "work-packages"]
},
"description": "Creates a new Feature work package in OpenProject.\n\nValid types: Task, Milestone, Summary task, Feature, Epic, User story, Bug, Incident, Support Ticket, Problem, Change Request, Enhancement"
}
},
{
"name": "Create Work Package - Task",
"request": {
"method": "POST",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"subject\": \"Setup CI/CD pipeline for CBS deployment\",\n \"description\": \"Configure automated build and deployment pipeline for the Core Banking System.\",\n \"type\": \"Task\"\n}"
},
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/openproject/work-packages",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "openproject", "work-packages"]
},
"description": "Creates a new Task work package in OpenProject."
}
},
{
"name": "Create Work Package - Bug",
"request": {
"method": "POST",
"header": [
{
"key": "fineract-platform-tenantid",
"value": "{{tenantId}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"subject\": \"Fix loan repayment calculation rounding error\",\n \"description\": \"Loan repayment amounts show minor rounding differences when calculated with different interest methods.\",\n \"type\": \"Bug\"\n}"
},
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/openproject/work-packages",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "openproject", "work-packages"]
},
"description": "Creates a new Bug work package in OpenProject."
}
}
]
}
+276
View File
@@ -0,0 +1,276 @@
{
"info": {
"_postman_id": "rbi-feeds-collection",
"name": "RBI Feeds API - Fineract CBS",
"description": "RBI RSS Feed endpoints for fetching and retrieving RBI circulars, notifications, press releases etc.",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"auth": {
"type": "basic",
"basic": [
{
"key": "username",
"value": "mifos",
"type": "string"
},
{
"key": "password",
"value": "password",
"type": "string"
}
]
},
"variable": [
{
"key": "baseUrl",
"value": "https://localhost:8443/fineract-provider/api/v1",
"type": "string"
},
{
"key": "tenantId",
"value": "default",
"type": "string"
}
],
"item": [
{
"name": "1. Trigger Fetch (Run First)",
"request": {
"method": "POST",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{baseUrl}}/rbi-feeds/fetch",
"host": ["{{baseUrl}}"],
"path": ["rbi-feeds", "fetch"]
},
"description": "Fetches latest RSS feeds from rbi.org.in (5 categories: PRESS_RELEASE, NOTIFICATION, SPEECH, TENDER, PUBLICATION) and stores new items in the database. Run this FIRST to populate data."
},
"response": []
},
{
"name": "2. Get All Feeds",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"url": {
"raw": "{{baseUrl}}/rbi-feeds",
"host": ["{{baseUrl}}"],
"path": ["rbi-feeds"]
},
"description": "Retrieve all RBI feed items sorted by publication date (newest first)."
},
"response": []
},
{
"name": "3. Get Feeds by Category - PRESS_RELEASE",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"url": {
"raw": "{{baseUrl}}/rbi-feeds?category=PRESS_RELEASE",
"host": ["{{baseUrl}}"],
"path": ["rbi-feeds"],
"query": [
{
"key": "category",
"value": "PRESS_RELEASE"
}
]
},
"description": "Filter feeds by PRESS_RELEASE category."
},
"response": []
},
{
"name": "4. Get Feeds by Category - NOTIFICATION",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"url": {
"raw": "{{baseUrl}}/rbi-feeds?category=NOTIFICATION",
"host": ["{{baseUrl}}"],
"path": ["rbi-feeds"],
"query": [
{
"key": "category",
"value": "NOTIFICATION"
}
]
},
"description": "Filter feeds by NOTIFICATION category."
},
"response": []
},
{
"name": "5. Get Feeds by Category - SPEECH",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"url": {
"raw": "{{baseUrl}}/rbi-feeds?category=SPEECH",
"host": ["{{baseUrl}}"],
"path": ["rbi-feeds"],
"query": [
{
"key": "category",
"value": "SPEECH"
}
]
},
"description": "Filter feeds by SPEECH category."
},
"response": []
},
{
"name": "6. Get Feeds by Category - TENDER",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"url": {
"raw": "{{baseUrl}}/rbi-feeds?category=TENDER",
"host": ["{{baseUrl}}"],
"path": ["rbi-feeds"],
"query": [
{
"key": "category",
"value": "TENDER"
}
]
},
"description": "Filter feeds by TENDER category."
},
"response": []
},
{
"name": "7. Get Feeds by Category - PUBLICATION",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"url": {
"raw": "{{baseUrl}}/rbi-feeds?category=PUBLICATION",
"host": ["{{baseUrl}}"],
"path": ["rbi-feeds"],
"query": [
{
"key": "category",
"value": "PUBLICATION"
}
]
},
"description": "Filter feeds by PUBLICATION category."
},
"response": []
},
{
"name": "8. Get Single Feed by ID",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"url": {
"raw": "{{baseUrl}}/rbi-feeds/1",
"host": ["{{baseUrl}}"],
"path": ["rbi-feeds", "1"]
},
"description": "Retrieve a single RBI feed item by its ID. Change the ID in the URL as needed."
},
"response": []
}
]
}
+229 -453
View File
@@ -1,80 +1,71 @@
Apache Fineract: A Platform for Microfinance
============
# Apache Fineract
<!-- TODO Reactivate when there is a working CI-CD instance: [![Swagger Validation](https://validator.swagger.io/validator?url=https://sandbox.mifos.community/fineract-provider/swagger-ui/fineract.yaml)](https://validator.swagger.io/validator/debug?url=https://sandbox.mifos.community/fineract-provider/swagger-ui/fineract.yaml) -->
[![Build](https://github.com/apache/fineract/actions/workflows/build-mariadb.yml/badge.svg?branch=develop)](https://github.com/apache/fineract/actions/workflows/build-mariadb.yml)
[![Docker Hub](https://img.shields.io/docker/pulls/apache/fineract.svg?logo=Docker)](https://hub.docker.com/r/apache/fineract)
[![Docker Build](https://github.com/apache/fineract/actions/workflows/publish-dockerhub.yml/badge.svg)](https://github.com/apache/fineract/actions/workflows/publish-dockerhub.yml)
[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=apache_fineract&metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=apache_fineract)
[![Verified DPG](https://img.shields.io/badge/Verified-DPG-3333AB)](https://www.digitalpublicgoods.net/r/apache-fineract "Apache Fineract is a verified Digital Public Good under the Digital Public Goods Alliance.")
</b>
Apache Fineract is an open-source core banking platform providing a
flexible, extensible foundation for a wide range of financial services. By
making robust banking technology openly available, it lowers barriers for
institutions and innovators to reach underserved and unbanked populations.
Fineract is a mature platform with open APIs that provides a reliable, robust, and affordable core banking solution for financial institutions offering services to the world’s 3 billion underbanked and unbanked.
Have a look at the [documentation](https://fineract.apache.org/docs/current), the [wiki](https://cwiki.apache.org/confluence/display/FINERACT) or at the [FAQ](https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=91554327), if this README does not answer what you are looking for.
[Have a look at the FAQ on our Wiki at apache.org](https://cwiki.apache.org/confluence/display/FINERACT/FAQ) if this README does not answer what you are looking for. [Visit our JIRA Dashboard](https://issues.apache.org/jira/secure/Dashboard.jspa?selectPageId=12335824) to find issues to work on, see what others are working on, or open new issues.
COMMUNITY
=========
If you are interested in contributing to this project, but perhaps don't quite know how and where to get started, please [join our developer mailing list](http://fineract.apache.org/#contribute), listen into our conversations, chime into threads, and just send us a "Hello!" introduction email; we're a friendly bunch, and look forward to hearing from you.
If you are interested in contributing to this project, but perhaps don't quite know how and where to get started, please [join our developer mailing list](http://fineract.apache.org/#contribute), listen into our conversations, chime into threads, or just send us a "Hello!" introduction email; we're a friendly bunch, and look forward to hearing from you. A more informal alternative is the [Fineract Slack channel](https://app.slack.com/client/T0F5GHE8Y/C028634A61L) (thank you, Mifos, for supporting the Slack channel!).
For the developer wiki, see [Contributor's Zone](https://cwiki.apache.org/confluence/display/FINERACT/Contributor%27s+Zone). Maybe [these how-to articles](https://cwiki.apache.org/confluence/display/FINERACT/How-to+articles) help you to get started.
In any case visit [our JIRA Dashboard](https://issues.apache.org/jira/secure/Dashboard.jspa?selectPageId=12335824) to find issues to work on, see what others are doing, or open new issues.
In the moment you get started writing code, please consult our [CONTRIBUTING](CONTRIBUTING.md) guidelines, where you will find more information on subjects like coding style, testing and pull requests.
REQUIREMENTS
============
* min. 16GB RAM and 8 core CPU
* `MariaDB >= 11.5.2` or `PostgreSQL >= 17.0`
* `Java >= 21` (Azul Zulu JVM is tested by our CI on GitHub Actions)
* MariaDB `11.5.2`
You can run the required version of the database server in a container, instead of having to install it, like this:
Tomcat (min. v10) is only required, if you wish to deploy the Fineract WAR to a separate external servlet container. You do not need to install Tomcat to run Fineract. We recommend the use of the self-contained JAR, which transparently embeds a servlet container using Spring Boot.
docker run --name mariadb-11.5 -p 3306:3306 -e MARIADB_ROOT_PASSWORD=mysql -d mariadb:11.5.2
and stop and destroy it like this:
SECURITY
============
For a list of known vulnerabilities, see [Apache Fineract Security Reports](https://fineract.apache.org/security.html).
docker rm -f mariadb-11.5
If you believe you have found a new vulnerability, [let us know privately](https://fineract.apache.org/#contribute).
<br>Beware that this database container database keeps its state inside the container and not on the host filesystem. It is lost when you destroy (rm) this container. This is typically fine for development. See [Caveats: Where to Store Data on the database container documentation](https://hub.docker.com/_/mariadb) re. how to make it persistent instead of ephemeral.<br>
For details about security during development and deployment, see the documentation [here](https://fineract.apache.org/docs/current/#_security).
Tomcat v10 is only required if you wish to deploy the Fineract WAR to a separate external servlet container. Note that you do not require to install Tomcat to develop Fineract, or to run it in production if you use the self-contained JAR, which transparently embeds a servlet container using Spring Boot. (Until FINERACT-730, Tomcat 7/8 were also supported, but now Tomcat 10 is required.)
<br>IMPORTANT: If you use MySQL or MariaDB
INSTRUCTIONS
============
Recently (after release `1.7.0`), we introduced improved date time handling in Fineract. Date time is from now on stored in UTC and we are enforcing UTC timezone even on the JDBC driver, e. g. for MySQL:
The following how-to's assume you have Java installed, you cloned the repository (or downloaded and extracted a [specific version](https://github.com/apache/fineract/releases)) and you have a [database server](#database-and-tables) (MariaDB or PostgreSQL) running.
```
serverTimezone=UTC&useLegacyDatetimeCode=false&sessionVariables=time_zone=‘-00:00’
How to run for local development
---
Run the following commands in this order:
```bash
./gradlew createDB -PdbName=fineract_tenants
./gradlew createDB -PdbName=fineract_default
./gradlew devRun
```
__DO__: If you do use MySQL as your Fineract database then the following configuration is highly recommended:
* Run the application in UTC (the default command line in our Docker image has the necessary parameters already set)
* Run the MySQL database server in UTC (if you use managed services like AWS RDS then this should be the default anyway, but it would be good to double-check)
__DON'T__: In case the Fineract instance and the MySQL server are __not__ running in UTC then the following could happen:
* MySQL is saving date time values differently from PostgreSQL
* Example scenario: if the Fineract instance runs in timezone: GMT+2, and the local date time is 2022-08-11 17:15 ...
* ... then __PostgreSQL saves__ the LocalDateTime as is: __2022-08-11 17:15__
* ... and __MySQL saves__ the LocalDateTime in UTC: __2022-08-11 15:15__
* ... but when we __read__ the date time from PostgreSQL __or__ from MySQL, then both systems give us the same values: __2022-08-11 17:15 GMT+2__
If a previously used Fineract instance didn't run in UTC (backward compatibility), then all prior dates will be read wrongly by MySQL/MariaDB. This can cause issues when you run the database migration scripts.
__RECOMMENDATION__: you need to shift all dates in your database by the timezone offset that your Fineract instance used.
<br>INSTRUCTIONS: How to run for local development
============
Run the following commands:
1. `./gradlew createDB -PdbName=fineract_tenants`
1. `./gradlew createDB -PdbName=fineract_default`
1. `./gradlew devRun`
Fineract is now running, and will be listening for API requests on port 8443 by default.
This creates two databases and builds and runs Fineract, which will be listening for API requests on port 8443 (by default) now.
Confirm Fineract is ready with, for example:
```bash
curl --insecure https://localhost:8443/fineract-provider/actuator/health`
curl --insecure https://localhost:8443/fineract-provider/actuator/health
```
To test authenticated endpoints, include credentials in your request:
@@ -87,233 +78,102 @@ curl --location \
--header 'Authorization: Basic bWlmb3M6cGFzc3dvcmQ='
```
<br>INSTRUCTIONS: How to build the JAR file
============
1. Clone the repository or download and extract the archive file to your local directory.
2. Run `./gradlew clean bootJar` to build a modern cloud native fully self contained JAR file which will be created at `fineract-provider/build/libs` directory.
3. As we are not allowed to include a JDBC driver in the built JAR, download a JDBC driver of your choice. For example: `wget https://dlm.mariadb.com/4174416/Connectors/java/connector-java-3.5.2/mariadb-java-client-3.5.2.jar`
4. Start the jar and pass the directory where you have downloaded the JDBC driver as loader.path, for example: `java -Dloader.path=. -jar fineract-provider/build/libs/fineract-provider.jar` (does not require external Tomcat)
The tenants database connection details are configured [via environment variables (as with Docker container)](#instructions-to-run-using-docker-and-docker-compose), e.g. like this:
export FINERACT_HIKARI_PASSWORD=verysecret
...
java -jar fineract-provider.jar
How to run for production
---
Running Fineract to try it out is relatively easy. If you intend to use it in a production environment, be aware that a proper deployment can be complex, costly, and time-consuming. Considerations include: Security, privacy, compliance, performance, service availability, backups, and more. The Fineract project does not provide a comprehensive guide for deploying Fineract in production. You might need skills in enterprise Java applications and more. Alternatively, you could pay a vendor for Fineract deployment and maintenance. You will find tips and tricks for deploying and securing Fineract in our official documentation and in the community-maintained wiki.
<br>SECURITY
============
If you believe you have found a security vulnerability, [let us know privately](https://fineract.apache.org/#contribute).
How to build the JAR file
---
Build a modern, cloud native, fully self contained JAR file:
```bash
./gradlew clean bootJar
```
The JAR will be created in the `fineract-provider/build/libs` directory.
As we are not allowed to include a JDBC driver in the built JAR, download a JDBC driver of your choice. For example:
```bash
wget https://dlm.mariadb.com/4174416/Connectors/java/connector-java-3.5.2/mariadb-java-client-3.5.2.jar
```
Start the JAR and specify the directory containing the JDBC driver using the loader.path option, for example:
```bash
java -Dloader.path=. -jar fineract-provider/build/libs/fineract-provider.jar
```
This does not require an external Tomcat.
For details about security during development and deployment, see <https://fineract.apache.org/docs/current/#_security>.
The tenants database connection details are configured [via environment variables (as with Docker container)](#instructions-to-run-using-docker-or-podman), e.g. like this:
```bash
export FINERACT_HIKARI_PASSWORD=verysecret
...
java -jar fineract-provider.jar
```
<br>INSTRUCTIONS: How to build a WAR file
============
1. Clone the repository or download and extract the archive file to your local directory.
2. Run `./gradlew :fineract-war:clean :fineract-war:war` to build a traditional WAR file which will be created at `fineract-war/build/libs` directory.
3. Deploy this WAR to your Tomcat v10 Servlet Container.
How to build the WAR file
---
Build a traditional WAR file:
```bash
./gradlew :fineract-war:clean :fineract-war:war
```
The WAR will be created in the `fineract-war/build/libs` directory. Afterwards deploy the WAR to your Tomcat Servlet Container.
We recommend using the JAR instead of the WAR file deployment, because it's much easier.
Note that with the 1.4 release the tenants database pool configuration changed from Tomcat DBCP in XML to an embedded Hikari, configured by environment variables, see above.
INSTRUCTIONS: How to run tests
============
Unit tests
----------
Here's how to run the set of relatviely fast and indepedent Fineract tests:
```bash
./gradlew test -x :twofactor-tests:test -x :oauth2-tests:test -x :integration-tests:test
```
This runs nearly 1,000 tests and completes in a few minutes on decent hardware.
They shouldn't need any special servers/services running.
Integration tests
-----------------
Running tests with external dependencies yourself is a multi-step process with many moving parts.
Sometimes there are arbitrary failures and the prerequisite setup can be daunting.
A full local integration test run (on a developer workstation) covering every possible test using every external service and every supported relational database engine could take an entire day, and that's assuming everything is properly configured and runs as expected.
Right now we depend on GitHub to know if "the build" is passing (it's actually multiple builds).
The authoritative source of truth for what commands/services/tests to run, how, and when are the files in `.github/workflows/`.
Output from runs based on those configuration files appears at <https://github.com/apache/fineract/actions>.
Incorrect default Java-related executables may cause test failures.
To fix this on Debian and Ubuntu systems, run the following:
```bash
export JAVA_HOME=/usr/lib/jvm/zulu21
sudo update-alternatives --set java $JAVA_HOME/bin/java
sudo update-alternatives --set javac $JAVA_HOME/bin/javac
sudo update-alternatives --set javadoc $JAVA_HOME/bin/javadoc
```
This would correct, for example, a [class file verson error](https://en.wikipedia.org/wiki/Java_class_file#General_layout).
You might see something like this if a Java 11 executable (class file format version 56) was the system default, but the integration tests were using Java 21 (class file format version 65):
```
UnsupportedClassVersionError: com.example.package/ClassName has been compiled by a more recent version of the Java Runtime (class file version 65.0), this version of the Java Runtime only recognizes class file versions up to 55.0
```
The GitHub builds are run in [short-lived virtual machines](https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners), so locally reproducing the same may require additional effort, such as these extra clean-up procedures:
```bash
# Might fix `error: cannot find symbol` or other intermittent failures.
# `doc` here is a placeholder for any task(s) you are trying to run.
# 💚 This is generally very safe to run between builds.
./gradlew --refresh-dependencies doc
# Destroy anything untracked by git.
# ⚠️ This may delete something important, e.g. a finely-tuned IDE configuration.
git clean --force -dx
# Destroy various caches and configs.
# ⚠️ This may delete gibibytes of cached data, making the next build very slow.
rm -rf ~/.gradle ~/.m2 /tmp/cargo*
# Destroy any Java containers left running.
# 💚 This is generally very safe to run between builds.
ps auxwww | grep [c]argo | awk '{ print $2 }' | xargs -r kill
```
Integration test runs such as `./gradlew --no-daemon --console=plain test -x :twofactor-tests:test -x :oauth2-test:test :fineract-e2e-tests-runner:test -PdbType=postgresql` in `.github/workflows/build-postgresql.yml` often take an hour or longer to complete.
If you notice the `:integration-tests:test` task taking significantly less time, say, one minute, gradle may be skipping it.
Look for something like this in the test output:
```
> Task :integration-tests:test UP-TO-DATE 👀
Custom actions are attached to task ':integration-tests:test'.
Build cache key for task ':integration-tests:test' is 6aeeec3f58bf9703d4c100fbaa657f5c
Skipping task ':integration-tests:test' as it is up-to-date.
Resolve mutations for :integration-tests:cargoStopLocal (Thread[Execution worker Thread 11,5,main]) started.
:integration-tests:cargoStopLocal (Thread[Execution worker Thread 11,5,main]) started.
```
(This is with the `--info` gradle argument with eyeballs added for emphasis)
The `--rerun-tasks` gradle argument may help, or you can try destroying `~/.gradle` and other clean-up procedures as indicated above then re-running tests.
This is useful for repeated test runs (say, for timing) when gradle would otherwise assume a task is "up-to-date" and not re-run it.
Testing within IDEs
-----------------
See the next section for testing in Eclipse.
See <https://fineract-academy.com> for testing in IntelliJ.
INSTRUCTIONS: How to run and debug in Eclipse IDE
============
It is possible to run Fineract in Eclipse IDE and also to debug Fineract using Eclipse's debugging facilities.
To do this, you need to create the Eclipse project files and import the project into an Eclipse workspace:
1. Create Eclipse project files into the Fineract project by running `./gradlew cleanEclipse eclipse`
2. Import the fineract-provider project into your Eclipse workspace (File->Import->General->Existing Projects into Workspace, choose root directory fineract/fineract-provider)
3. Do a clean build of the project in Eclipse (Project->Clean...)
3. Run / debug Fineract by right clicking on org.apache.fineract.ServerApplication class and choosing Run As / Debug As -> Java Application. All normal Eclipse debugging features (breakpoints, watchpoints etc) should work as expected.
If you change the project settings (dependencies etc) in Gradle, you should redo step 1 and refresh the project in Eclipse.
You can also use Eclipse Junit support to run tests in Eclipse (Run As->Junit Test)
Finally, modifying source code in Eclipse automatically triggers hot code replace to a running instance, allowing you to immediately test your changes
INSTRUCTIONS: How to run using Docker and docker-compose
===================================================
How to run using Docker or Podman
---
It is possible to do a 'one-touch' installation of Fineract using containers (AKA "Docker").
Fineract now packs the mifos community-app web UI in it's docker deploy.
You can now run and test fineract with a GUI directly from the combined docker builds.
This includes the database running in a container.
This includes the database running in the container.
As Prerequisites, you must have `docker` and `docker-compose` installed on your machine; see
[Docker Install](https://docs.docker.com/install/) and
[Docker Compose Install](https://docs.docker.com/compose/install/).
As prerequisites, you must have `docker` and `docker-compose` installed on your machine; see
[Docker Install](https://docs.docker.com/install/) and [Docker Compose Install](https://docs.docker.com/compose/install/).
Alternatively, you can also use [Podman](https://github.com/containers/libpod)
(e.g. via `dnf install podman-docker`), and [Podman Compose](https://github.com/containers/podman-compose/)
(e.g. via `pip3 install podman-compose`) instead of Docker.
Now to run a new Fineract instance you can simply:
To run a new Fineract instance on Linux you can simply:
```bash
git clone https://github.com/apache/fineract.git
cd fineract
./gradlew :fineract-provider:jibDockerBuild -x test
```
On Windows, do this instead:
```cmd
git clone https://github.com/apache/fineract.git --config core.autocrlf=input
cd fineract
gradlew :fineract-provider:jibDockerBuild -x test
```
Install the Loki log driver and start:
```bash
docker plugin install grafana/loki-docker-driver:latest \
--alias loki --grant-all-permissions
docker compose -f docker-compose-development.yml up -d
```
The Fineract (back-end) should be running at https://localhost:8443/fineract-provider/ now.
Wait for https://localhost:8443/fineract-provider/actuator/health to return `{"status":"UP"}`.
You must go to https://localhost:8443 and remember to accept the self-signed SSL certificate of the API once in your browser.
1. `git clone https://github.com/apache/fineract.git ; cd fineract`
1. for windows, use `git clone https://github.com/apache/fineract.git --config core.autocrlf=input ; cd fineract`
1. `./gradlew :fineract-provider:jibDockerBuild -x test`
1. install the Loki log driver with `docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions`
1. `docker compose -f docker-compose-development.yml up -d`
1. fineract (back-end) is running at https://localhost:8443/fineract-provider/
1. wait for https://localhost:8443/fineract-provider/actuator/health to return `{"status":"UP"}`
1. you must go to https://localhost:8443 and remember to accept the self-signed SSL certificate of the API once in your browser, otherwise you get a message that is rather misleading from the UI.
1. community-app (UI) is running at http://localhost:9090/?baseApiUrl=https://localhost:8443/fineract-provider&tenantIdentifier=default
1. login using default _username_ `mifos` and _password_ `password`
https://hub.docker.com/r/apache/fineract has a pre-built container image of this project, built continuously.
[Docker Hub](https://hub.docker.com/r/apache/fineract) has a pre-built container image of this project, built continuously.
You must specify the MySQL tenants database JDBC URL by passing it to the `fineract` container via environment
variables; please consult the [`docker-compose.yml`](docker-compose.yml) for exact details how to specify those.
_(Note that in previous versions, the `mysqlserver` environment variable used at `docker build` time instead of at
`docker run` time did something similar; this has changed in [FINERACT-773](https://issues.apache.org/jira/browse/FINERACT-773)),
and the `mysqlserver` environment variable is now no longer supported.)_
The logfiles and the Java Flight Recorder output are available in `PROJECT_ROOT/build/fineract/logs`. If you use IntelliJ then you can double-click on the `.jfr` file and open it with the IDE. You can also download Azul Mission Control from here https://www.azul.com/products/components/azul-mission-control/ to analyze the Java Flight Recorder file.
The logfiles and the Java Flight Recorder output are available in `PROJECT_ROOT/build/fineract/logs`. If you use IntelliJ then you can double-click on the `.jfr` file and open it with the IDE. You can also download [Azul Mission Control](https://www.azul.com/products/components/azul-mission-control/) to analyze the Java Flight Recorder file.
NOTE: If you have issues with the file permissions and Docker Compose then you might need to change the variable values for `FINERACT_USER` and `FINERACT_GROUP` in `PROJECT_ROOT/config/docker/env/fineract-common.env`. You can find out what values you need to put there with the following commands:
```
id -u ${USER}
id -u ${GROUP}
```
Please make sure that you are not checking in your changed values. The defaults should normally work for most people.
INSTRUCTIONS: How to build documentation
===================================================
Run the following command:
```bash
./gradlew doc
id -u ${USER}
id -g ${GROUP}
```
Some dependencies are required (e.g. Ghostscript, Graphviz), see `.github/workflows/build-documentation.yml` for hints.
Additionally, IDEs such as IntelliJ are useful for editing the AsciiDoc source files while providing a live rendered preview.
HTML rendered from the AsciiDoc source files is also available online at <https://fineract.apache.org/docs/current/>.
A release version is derived from source control. The version will include `-SNAPSHOT` unless the current branch looks like a release or release maintenance branch. See `gitVersioning` settings in `build.gradle` for details.
Connection pool configuration
=============================
Please check `application.properties` to see which connection pool settings can be tweaked. The associated environment variables are prefixed with `FINERACT_HIKARI_*`. You can find more information about specific connection pool settings (Hikari) at https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby
NOTE: we'll keep backwards compatibility until one of the next releases to ensure that things are working as expected. Environment variables prefixed `fineract_tenants_*` can still be used to configure the database connection, but we strongly encourage using `FINERACT_HIKARI_*` with more options.
<br>SSL CONFIGURATION
=================
See [the HTTPS related doc](https://fineract.apache.org/docs/current/#_https).
Please make sure that you are not checking in your changed values. The defaults should work for most people.
<br>TOMCAT CONFIGURATION
====================
How to run on Kubernetes
---
Please refer to the `application.properties` and the official Spring Boot documentation (https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html) on how to do performance tuning for Tomcat. Note: you can set now the acceptable form POST size (default is 2MB) via environment variable `FINERACT_SERVER_TOMCAT_MAX_HTTP_FORM_POST_SIZE`.
<br>INSTRUCTIONS: How to run on Kubernetes
=================================
<br>General Clusters
----------------
### General Clusters
You can also run Fineract using containers on a Kubernetes cluster.
Make sure you set up and connect to your Kubernetes cluster.
@@ -321,72 +181,81 @@ You can follow [this](https://cwiki.apache.org/confluence/display/FINERACT/Insta
Now e.g. from your Google Cloud shell, run the following commands:
1. `git clone https://github.com/apache/fineract.git ; cd fineract/kubernetes`
1. `./kubectl-startup.sh`
```bash
git clone https://github.com/apache/fineract.git
cd fineract/kubernetes
./kubectl-startup.sh
```
To shutdown and reset your Cluster, run:
```bash
./kubectl-shutdown.sh
```
./kubectl-shutdown.sh
Using Minikube
--------------
### Using Minikube
Alternatively, you can run fineract on a local kubernetes cluster using [minikube](https://minikube.sigs.k8s.io/docs/).
As Prerequisites, you must have `minikube` and `kubectl` installed on your machine; see
As prerequisite you must have `minikube` and `kubectl` installed on your machine; see
[Minikube & Kubectl install](https://kubernetes.io/docs/tasks/tools/install-minikube/).
Now to run a new Fineract instance on Minikube you can simply:
To run a new Fineract instance on Minikube you can simply:
1. `git clone https://github.com/apache/fineract.git ; cd fineract/kubernetes`
1. `minikube start`
1. `./kubectl-startup.sh`
1. `minikube service fineract-server --url --https`
1. Fineract is now running at the printed URL (note HTTP), which you can check e.g. using:
```bash
git clone https://github.com/apache/fineract.git
cd fineract/kubernetes
minikube start
./kubectl-startup.sh
```
http --verify=no --timeout 240 --check-status get $(minikube service fineract-server --url --https)/fineract-provider/actuator/health
Wait for all pods to be ready:
```bash
kubectl get pods -w
```
Once all pods are running, access the Mifos web application:
```bash
minikube service mifos-community
```
This opens the Mifos X web application in your browser. The nginx reverse proxy in the mifos-community pod forwards API requests to the fineract-server backend.
**Default credentials:**
- Username: `mifos`
- Password: `password`
You can also access the Fineract API directly:
```bash
minikube service fineract-server --url --https
```
Fineract is now running at the printed URL, which you can check e.g. using:
```bash
http --verify=no --timeout 240 --check-status get $(minikube service fineract-server --url --https)/fineract-provider/actuator/health
```
To check the status of your containers on your local minikube Kubernetes cluster, run:
minikube dashboard
```bash
minikube dashboard
```
You can check Fineract logs using:
```bash
kubectl logs deployment/fineract-server
```
kubectl logs deployment/fineract-server
You can check Mifos web app logs using:
```bash
kubectl logs deployment/mifos-community
```
To shutdown and reset your cluster, run:
./kubectl-shutdown.sh
To shutdown and reset your cluster, run:
minikube ssh
sudo rm -rf /mnt/data/
We have [some open issues in JIRA with Kubernetes related enhancement ideas](https://jira.apache.org/jira/browse/FINERACT-783?jql=labels%20%3D%20kubernetes%20AND%20project%20%3D%20%22Apache%20Fineract%22%20) which you are welcome to contribute to.
```bash
./kubectl-shutdown.sh
```
INSTRUCTIONS: How to download Gradle wrapper
============
The file gradle/wrapper/gradle-wrapper.jar binary is checked into this projects Git source repository,
but won't exist in your copy of the Fineract codebase if you downloaded a released source archive from apache.org.
In that case, you need to download it using the commands below:
wget --no-check-certificate -P gradle/wrapper https://github.com/apache/fineract/raw/develop/gradle/wrapper/gradle-wrapper.jar
(or)
curl --insecure -L https://github.com/apache/fineract/raw/develop/gradle/wrapper/gradle-wrapper.jar > gradle/wrapper/gradle-wrapper.jar
INSTRUCTIONS: How to run Apache RAT (Release Audit Tool)
============
1. Extract the archive file to your local directory.
2. Run `./gradlew rat`. A report will be generated under build/reports/rat/rat-report.txt
INSTRUCTIONS: How to enable External Message Broker (ActiveMQ or Apache Kafka)
============
How to enable External Message Broker (ActiveMQ or Apache Kafka)
---
There are two use-cases where external message broker is needed:
- External Business Events / Reliable Event Framework
@@ -394,10 +263,11 @@ There are two use-cases where external message broker is needed:
External Events are business events, e.g.: `ClientCreated`, which might be important for third party systems. Apache Fineract supports ActiveMQ (or other JMS compliant brokers) and Apache Kafka endpoints for sending out Business Events. By default, they are not emitted.
In case of a large deployment with millions of accounts, the Close of Business Day Spring Batch job may run several hours. In order to speed up this task, remote partitioning of the job is supported. The Manager node partitions (breaks up) the COB job into smaller pieces (sub tasks) which then can be executed on multiple Worker nodes in parallel. The worker nodes are notified either by ActiveMQ or Kafka regarding their new sub tasks.
### Active MQ
In case of a large deployment with millions of accounts, the Close of Business Day Spring Batch job may run several hours. In order to speed up this task, remote partitioning of the job is supported. The Manager node partitions breaks up the COB job into smaller pieces (sub tasks), which then can be executed on multiple Worker nodes in parallel. The worker nodes are notified either by ActiveMQ or Kafka regarding their new sub tasks.
JMS based messaging is disabled by default. In `docker-compose-postgresql-activemq.yml` an example is shown where ActiveMQ is enabled. In that configuration one Spring Batch Manager instance and two Spring Batch Worker instances are created.
### ActiveMQ
JMS based messaging is disabled by default. In `docker-compose-postgresql-activemq.yml` an example is shown, where ActiveMQ is enabled. In that configuration one Spring Batch Manager instance and two Spring Batch Worker instances are created.
Spring based events should be disabled and jms based event handling should be enabled. Furthermore, proper broker JMS URL should be configured.
```
@@ -410,186 +280,92 @@ For additional ActiveMQ related configuration please take a look to the `applica
### Kafka
Kafka support also disabled by default. In `docker-compose-postgresql-kafka.yml` an example is shown where self-hosted Kafka is enabled for both External Events and Spring Batch Remote Job execution.
Kafka support is also disabled by default. In `docker-compose-postgresql-kafka.yml` an example is shown, where self-hosted Kafka is enabled for both External Events and Spring Batch Remote Job execution.
During the development Fineract was tested with PLAINTEXT Kafka brokers without authentication and with AWS MSK using IAM authentication. The extra [jar file](https://github.com/aws/aws-msk-iam-auth/releases) required for IAM authentication is already added to the classpath.
During the development Fineract was tested with PLAINTEXT Kafka brokers without authentication and with AWS MSK using IAM authentication. The extra [JAR file](https://github.com/aws/aws-msk-iam-auth/releases) required for IAM authentication is already added to the classpath.
An example MSK setup can be found in `docker-compose-postgresql-kafka-msk.yml`.
The full list of supported Kafka related properties are documented here: https://fineract.apache.org/docs/current/
The full list of supported Kafka related properties is documented in the [Fineract Platform documentation](https://fineract.apache.org/docs/current/).
Checkstyle and Spotless
DATABASE AND TABLES
===================
You can run the required version of the database server in a container, instead of having to install it, like this:
docker run --name mariadb-11.5 -p 3306:3306 -e MARIADB_ROOT_PASSWORD=mysql -d mariadb:11.5.2
and stop and destroy it like this:
docker rm -f mariadb-11.5
Beware that this container database keeps its state inside the container and not on the host filesystem. It is lost when you destroy (rm) this container. This is typically fine for development. See [Caveats: Where to Store Data on the database container documentation](https://hub.docker.com/_/mariadb) regarding how to make it persistent instead of ephemeral.
MySQL/MariaDB and UTC timezone
---
With release `1.8.0` we introduced improved date time handling in Fineract. Date time is stored in UTC, and UTC timezone enforced even on the JDBC driver, e. g. for MySQL:
```
serverTimezone=UTC&useLegacyDatetimeCode=false&sessionVariables=time_zone='-00:00'
```
If you use MySQL as Fineract database, the following configuration is highly recommended:
* Run the application in UTC (the default command line in our Docker image has the necessary parameters already set)
* Run the MySQL database server in UTC (if you use managed services like AWS RDS, then this should be the default anyway, but it would be good to double-check)
In case Fineract and MySQL do not run in UTC, MySQL might save date time values differently from PostgreSQL
Example scenario: If the Fineract instance runs in timezone: GMT+2, and the local date time is 2022-08-11 17:15 ...
* ... then PostgreSQL saves the LocalDateTime as is: 2022-08-11 17:15
* ... and MySQL saves the LocalDateTime in UTC: 2022-08-11 15:15
* ... but when we read the date time from PostgreSQL or from MySQL, both systems give us the same value: 2022-08-11 17:15 GMT+2
If a previously used Fineract instance didn't run in UTC (backward compatibility), all prior dates will be read wrongly by MySQL. This can cause issues, when you run the database migration scripts.
Recommendation: Shift all dates in your database by the timezone offset that your Fineract instance used.
CONNECTION POOL CONFIGURATION
=======
Please check `application.properties` to see which connection pool settings can be tweaked. The associated environment variables are prefixed with `FINERACT_HIKARI_*`. You can find more information about specific connection pool settings at the [HikariCP Github repository](https://github.com/brettwooldridge/HikariCP?tab=readme-ov-file#gear-configuration-knobs-baby).
NOTE: We keep backwards compatibility until one of the next releases to ensure that things are working as expected. Environment variables prefixed `fineract_tenants_*` can still be used to configure the database connection, but we strongly encourage using `FINERACT_HIKARI_*` with more options.
RELEASES
============
This project enforces its code conventions using [checkstyle.xml](config/checkstyle/checkstyle.xml) through Checkstyle and [fineract-formatting-preferences.xml](config/fineract-formatting-preferences.xml) through Spotless. They are configured to run automatically during the normal Gradle build, and fail if there are any violations detected. You can run the following command to automatically fix spotless violations:
[Official releases](https://fineract.apache.org/#downloads) are created quarterly, at the end of each quarter. Our [documented release procedure](https://fineract.apache.org/docs/current/#_releases) follows the [ASF release policy](https://www.apache.org/legal/release-policy.html).
`./gradlew spotlessApply`
See <https://cwiki.apache.org/confluence/display/FINERACT/Fineract+Releases> for an archive of historical release notes along with JIRA issues relevant to each release.
Since some checks are present in both Checkstyle and Spotless, the same command can help you fix some of the Checkstyle violations (but not all, other Checkstyle violations need to fixed manually).
See <https://github.com/apache/fineract/releases> for a list of PRs and contributors for each release.
You can also check for Spotless violations (only; but normally don't have to, because the regular build full already includes this anyway):
EOL/unsupported releases are [archived](https://www.apache.org/legal/release-policy.html#archived).
`./gradlew spotlessCheck`
We recommend that you configure your favourite Java IDE to match those conventions. For Eclipse, you can go to
Window > Java > Code Style and import our [config/fineractdev-formatter.xml](config/fineractdev-formatter.xml) under formatter section and [config/fineractdev-cleanup.xml](config/fineractdev-cleanup.xml) under Clean up section. The same fineractdev-formatter.xml configuration file (that can be used in Eclipse IDE) is also used by Spotless to both check for violations and autoformat code on the CLI.
You could also use Checkstyle directly in your IDE (but you don't neccesarily have to, it may just be more convenient for you). For Eclipse, use https://checkstyle.org/eclipse-cs/ and load our checkstyle.xml into it, for IntelliJ you can use [CheckStyle-IDEA](https://plugins.jetbrains.com/plugin/1065-checkstyle-idea).
Versioned build products created from the `develop` branch will include `-SNAPSHOT`. See [related settings](https://github.com/qoomon/gradle-git-versioning-plugin) near `version` and `gitVersioning` in `build.gradle` for details.
Code Coverage Reports
LICENSE
============
The project uses Jacoco to measure unit tests code coverage, to generate a report run the following command:
This project is licensed under [Apache License Version 2.0](https://github.com/apache/fineract/blob/develop/APACHE_LICENSETEXT.md).
`./gradlew clean build jacocoTestReport`
Generated reports can be found in build/code-coverage directory.
Versions
============
The latest stable release can be viewed on the develop branch: [Latest Release on Develop](https://github.com/apache/fineract/tree/develop "Latest Release").
The progress of this project can be viewed here: [View change log](https://github.com/apache/fineract/blob/develop/CHANGELOG.md "Latest release change log")
License
============
This project is licensed under Apache License Version 2.0. See <https://github.com/apache/fineract/blob/develop/APACHE_LICENSETEXT.md> for reference.
The Connector/J JDBC Driver client library from MariaDB.org, which is licensed under the LGPL,
is used in development when running integration tests that use the Liquibase library. That JDBC
driver is however not included in and distributed with the Fineract product and is not
required to use the product.
If you are developer and object to using the LGPL licensed Connector/J JDBC driver,
simply do not run the integration tests that use the Liquibase library and/or use another JDBC driver.
The Connector/J JDBC Driver client library from [MariaDB](https://www.mariadb.org) is licensed under the LGPL.
The library is often used in development when running integration tests that use the Liquibase library. That JDBC
driver is however not distributed with the Fineract product and is not required to use the product.
If you are a developer and object to using the LGPL licensed Connector/J JDBC driver,
simply do not run the integration tests that use the Liquibase library and use another JDBC driver.
As discussed in [LEGAL-462](https://issues.apache.org/jira/browse/LEGAL-462), this project therefore
complies with the [Apache Software Foundation third-party license policy](https://www.apache.org/legal/resolved.html).
<br><br>APACHE FINERACT PLATFORM API
PLATFORM API
============
The API for Fineract is documented in [apiLive.htm](fineract-provider/src/main/resources/static/legacy-docs/apiLive.htm), and the [apiLive.htm can be viewed on fineract.apache.org](https://fineract.apache.org/docs/legacy/ "API Documentation"). If you have your own Fineract instance running, you can find this documentation under [/fineract-provider/legacy-docs/apiLive.htm](https://localhost:8443/fineract-provider/legacy-docs/apiLive.htm).
Fineract does not provide a UI, but provides an API. Running Fineract locally, the Swagger documentation can be accessed under `https://localhost:8443/fineract-provider/swagger-ui/index.html`. A live version can be accessed via [this Sandbox](https://sandbox.mifos.community/fineract-provider/swagger-ui/index.html) (not hosted by us).
The Swagger documentation (work in progress; see [FINERACT-733](https://issues.apache.org/jira/browse/FINERACT-733)) can be accessed under [/fineract-provider/swagger-ui/index.html](https://localhost:8443/fineract-provider/swagger-ui/index.html) and [live Swagger UI here on Fineract.dev](https://sandbox.mifos.community/fineract-provider/swagger-ui/index.html).
Apache Fineract supports client code generation using [Swagger Codegen](https://github.com/swagger-api/swagger-codegen) based on the [OpenAPI Specification](https://swagger.io/specification/). For more instructions on how to generate the client code, check [fineract-doc/src/docs/en/chapters/sdk/client.adoc](fineract-doc/src/docs/en/chapters/sdk/client.adoc).
<br>API CLIENTS (Web UIs, Mobile, etc.)
============
* https://github.com/openMF/community-app/ is the "traditional" Reference Client App Web UI for the API offered by this project
* https://github.com/openMF/web-app is the next generation UI rewrite also using this project's API
* https://github.com/openMF/android-client is an Android Mobile App client for this project's API
* https://github.com/openMF has more related proejcts
<br>ONLINE DEMOS
============
* [sandbox.mifos.community](https://sandbox.mifos.community) always runs the latest version of this code
* [demo.mifos.io](https://demo.mifos.io) A demo account is provided for users to experience the functionality of the Community App. Users can use "mifos" for USERNAME and "password" for PASSWORD (without quotation marks).
* [Swagger-UI Demo video](https://www.youtube.com/watch?v=FlVd-0YAo6c) This is a demo video for Swagger-UI documentation, more information [here](https://github.com/apache/fineract#swagger-ui-documentation).
<br>DEVELOPERS
============
Please see <https://cwiki.apache.org/confluence/display/FINERACT/Contributor%27s+Zone> for the developers wiki page.
Please refer to <https://cwiki.apache.org/confluence/display/FINERACT/Fineract+101> for the first-time contribution to this project.
Please see <https://cwiki.apache.org/confluence/display/FINERACT/How-to+articles> for technical details to get started.
Please visit [our JIRA Dashboard](https://issues.apache.org/jira/secure/Dashboard.jspa?selectPageId=12335824) to find issues to work on, see what others are working on, or open new issues.
<br>VIDEO DEMONSTRATION
============
Apache Fineract / Mifos X Demo (November 2016) - <https://www.youtube.com/watch?v=h61g9TptMBo>
<br>SWAGGER UI DEMONSTRATION
============
We use Swagger-UI to generate and maintain our API documentation, you can see the demo video [here](https://www.youtube.com/watch?v=FlVd-0YAo6c) or a live version
[here](https://sandbox.mifos.community/fineract-provider/swagger-ui/index.html). If you interested to know more about Swagger-UI you can check their [website](https://swagger.io/).
<br>GOVERNANCE AND POLICIES
=======================
[Becoming a Committer](https://cwiki.apache.org/confluence/display/FINERACT/Becoming+a+Committer)
documents the process through which you can become a committer in this project.
<br>ERROR HANDLING GUIDELINES
------------------
* When catching exceptions, either rethrow them, or log them. Either way, include the root cause by using `catch (SomeException e)` and then either `throw AnotherException("..details..", e)` or `LOG.error("...context...", e)`.
* Completely empty catch blocks are VERY suspicous! Are you sure that you want to just "swallow" an exception? Really, 100% totally absolutely sure?? ;-) Such "normal exceptions which just happen sometimes but are actually not really errors" are almost always a bad idea, can be a performance issue, and typically are an indication of another problem - e.g. the use of a wrong API which throws an Exception for an expected condition, when really you would want to use another API that instead returns something empty or optional.
* In tests, you'll typically never catch exceptions, but just propagate them, with `@Test void testXYZ() throws SomeException, AnotherException`..., so that the test fails if the exception happens. Unless you actually really want to test for the occurence of a problem - in that case, use [JUnit's Assert.assertThrows()](https://github.com/junit-team/junit4/wiki/Exception-testing) (but not `@Test(expected = SomeException.class)`).
* Never catch `NullPointerException` & Co.
<br>LOGGING GUIDELINES
------------------
* We use [SLF4J](http://www.slf4j.org) as our logging API.
* Never, ever, use `System.out` and `System.err` or `printStackTrace()` anywhere, but always `LOG.info()` or `LOG.error()` instead.
* Use placeholder (`LOG.error("Could not... details: {}", something, exception)`) and never String concatenation (`LOG.error("Could not... details: " + something, exception)`)
* Which Log Level is appropriate?
* `LOG.error()` should be used to inform an "operator" running Fineract who supervises error logs of an unexpected condition. This includes technical problems with an external "environment" (e.g. can't reach a database), and situations which are likely bugs which need to be fixed in the code. They do NOT include e.g. validation errors for incoming API requests - that is signaled through the API response - and does (should) not be logged as an error. (Note that there is no _FATAL_ level in SLF4J; a "FATAL" event should just be logged as an _ERROR_.)
* `LOG.warn()` should be using sparingly. Make up your mind if it's an error (above) - or not!
* `LOG.info()` can be used notably for one-time actions taken during start-up. It should typically NOT be used to print out "regular" application usage information. The default logging configuration always outputs the application INFO logs, and in production under load, there's really no point to constantly spew out lots of information from frequently traversed paths in the code about what's going on. (Metrics are a better way.) `LOG.info()` *can* be used freely in tests though.
* `LOG.debug()` can be used anywhere in the code to log things that may be useful during investigations of specific problems. They are not shown in the default logging configuration, but can be enabled for troubleshooting. Developers should typically "turn down" most `LOG.info()` which they used while writing a new feature to "follow along what happens during local testing" to `LOG.debug()` for production before we merge their PRs.
* `LOG.trace()` is not used in Fineract.
Pull Requests
-------------
We request that your commit message include a FINERACT JIRA issue, and a one-liner that describe the changes.
Start with an upper case imperative verb (not past form), and a short but concise clear description. (E.g. "FINERACT-821: Add enforced HideUtilityClassConstructor checkstyle").
If your PR is failing to pass our CI build due to a test failure, then:
1. Understand if the failure is due to your PR or an unrelated unstable test.
1. If you suspect it is because of a "flaky" test, and not due to a change in your PR, then please do not simply wait for an active maintainer to come and help you, but instead be a proactive contributor to the project - see next steps. Do understand that we may not review PRs that are not green - it is the contributor's (that's you!) responsability to get a proposed PR to pass the build, not primarily the maintainers.
1. Search for the name of the failed test on https://issues.apache.org/jira/, e.g. for `AccountingScenarioIntegrationTest` you would find [FINERACT-899](https://issues.apache.org/jira/browse/FINERACT-899).
1. If you happen to read in such bugs that tests were just recently fixed, or ignored, then rebase your PR to pick up that change.
1. If you find previous comments "proving" that the same test has arbitrarily failed in at least 3 past PRs, then please do yourself raise a small separate new PR proposing to add an `@Disabled // TODO FINERACT-123` to the respective unstable test (e.g. [#774](https://github.com/apache/fineract/pull/774)) with the commit message mentioning said JIRA, as always. (Please do NOT just `@Disabled` any existing tests mixed in as part of your larger PR.)
1. If there is no existing JIRA for the test, then first please evaluate whether the failure couldn't be a (perhaps strange) impact of the change you are proposing after all. If it's not, then please raise a new JIRA to document the suspected Flaky Test, and link it to [FINERACT-850](https://issues.apache.org/jira/browse/FINERACT-850). This will allow the next person coming along hitting the same test failure to easily find it, and eventually propose to ignore the unstable test.
1. Then (only) Close and Reopen your PR, which will cause a new build, to see if it passes.
1. Of course, we very much appreciate you then jumping onto any such bugs and helping us figure out how to fix all ignored tests!
[Pull Request Size Limit](https://cwiki.apache.org/confluence/display/FINERACT/Pull+Request+Size+Limit)
documents that we cannot accept huge "code dump" Pull Requests, with some related suggestions.
Guideline for new Feature commits involving Refactoring: If you are submitting PR for a new Feature,
and it involves refactoring, try to differentiate "new Feature code" with "Refactored" by placing
them in different commits. This helps review to review your code faster.
We have an automated Bot which marks pull requests as "stale" after a while, and ultimately automatically closes them.
Merge Strategy
--------------
This project's committers typically prefer to bring your Pull Requests in through _Rebase and Merge_ instead of _Create a Merge Commit_. (If you are unfamiliar with GitHub's UI re. this, note the somewhat hidden little triangle drop-down at the bottom of PR, visible only to committers, not contributors.) This avoids the "merge commits" which we consider to be somewhat "polluting" the projects commits log history view. We understand this doesn't give an easy automatic reference to the original PR (which GitHub automatically adds to the Merge Commit message it generates), but we consider this an only very minor inconvenience; it's typically relatively easy to find the original PR even just from the commit message, and JIRA.
We expect most proposed PRs to typically consist of a single commit. Committers may use _Squash and merge_ to combine your commits at merge time, and if they do so will rewrite your commit message as they see fit.
Neither of these two are hard absolute rules, but mere conventions. Multiple commits in single PR make sense in certain cases (e.g. branch backports).
Dependency Upgrades
-------------------
This project uses a number of 3rd-party libraries, and this section provides some guidance for their updates. We have set-up [Renovate's bot](https://renovate.whitesourcesoftware.com) to automatically raise Pull Requests for our review when new dependencies are available [FINERACT-962](https://issues.apache.org/jira/browse/FINERACT-962).
Upgrades sometimes require package name changes. Changed code should ideally have test coverage.
Our `ClasspathHellDuplicatesCheckRuleTest` detects classes that appear in more than 1 JAR. If a version bump in [`build.gradle`](https://github.com/search?q=repo%3Aapache%2Ffineract+filename%3Abuild.gradle&type=Code&ref=advsearch&l=&l=) causes changes in transitives dependencies, then you may have to add related `exclude` to our [`dependencies.gradle`](https://github.com/apache/fineract/search?q=dependencies.gradle). Running `./gradlew dependencies` helps to understand what is required.
More Information
============
More details of the project can be found at <https://cwiki.apache.org/confluence/display/FINERACT>.
Apache Fineract supports client code generation using [Swagger Codegen](https://github.com/swagger-api/swagger-codegen) based on the [OpenAPI Specification](https://swagger.io/specification/). For more instructions on how to generate client code, check [this section](https://fineract.apache.org/docs/current/#_generate_api_client) of the Fineract documentation. [This video](https://www.youtube.com/watch?v=FlVd-0YAo6c) documents the use of the Swagger-UI.
+375
View File
@@ -0,0 +1,375 @@
{
"info": {
"_postman_id": "sanctions-screening-001",
"name": "Sanctions Screening API",
"description": "API collection for UN Security Council consolidated sanctions list screening.\n\nEndpoints:\n- Screen client names against sanctions list\n- Load sanctions XML data\n- Check sanctions database status\n\nBase URL: {{baseUrl}}/fineract-provider/api/v1",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"auth": {
"type": "basic",
"basic": [
{
"key": "username",
"value": "{{username}}",
"type": "string"
},
{
"key": "password",
"value": "{{password}}",
"type": "string"
}
]
},
"variable": [
{
"key": "baseUrl",
"value": "https://localhost:8443"
},
{
"key": "username",
"value": "mifos"
},
{
"key": "password",
"value": "password"
},
{
"key": "tenantId",
"value": "default"
}
],
"item": [
{
"name": "Sanctions Screening",
"description": "Screen client names against the UN Security Council consolidated sanctions list",
"item": [
{
"name": "Screen Name (GET)",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/sanctions/screen?name=Mohammad Ali",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "sanctions", "screen"],
"query": [
{
"key": "name",
"value": "Mohammad Ali",
"description": "Name to screen against sanctions list"
}
]
},
"description": "Screen a client name against the UN Security Council consolidated sanctions list using GET method.\n\n**Response when match found (FLAGGED):**\n```json\n{\n \"screenedName\": \"Mohammad Ali\",\n \"matchFound\": true,\n \"status\": \"FLAGGED\",\n \"message\": \"Potential match found in UN Security Council consolidated sanctions list. Manual review required before proceeding.\",\n \"matchCount\": 2,\n \"matches\": [...]\n}\n```\n\n**Response when no match (CLEAR):**\n```json\n{\n \"screenedName\": \"John Smith\",\n \"matchFound\": false,\n \"status\": \"CLEAR\",\n \"message\": \"No match found in UN Security Council consolidated sanctions list.\",\n \"matchCount\": 0,\n \"matches\": []\n}\n```"
}
},
{
"name": "Screen Name (POST)",
"request": {
"method": "POST",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"AL-QAIDA\"\n}"
},
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/sanctions/screen",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "sanctions", "screen"]
},
"description": "Screen a client name against sanctions list using POST method with JSON body."
}
},
{
"name": "Screen Name - Individual (FLAGGED example)",
"request": {
"method": "POST",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"ERIC BADEGE\"\n}"
},
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/sanctions/screen",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "sanctions", "screen"]
},
"description": "Example: Screen a known sanctioned individual name. Should return FLAGGED status."
}
},
{
"name": "Screen Name - Clear (no match)",
"request": {
"method": "POST",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"Aditya Kumar\"\n}"
},
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/sanctions/screen",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "sanctions", "screen"]
},
"description": "Example: Screen a name that does not exist in sanctions list. Should return CLEAR status."
}
},
{
"name": "Screen Name - Entity (FLAGGED example)",
"request": {
"method": "POST",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"TALIBAN\"\n}"
},
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/sanctions/screen",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "sanctions", "screen"]
},
"description": "Example: Screen a known sanctioned entity name. Should return FLAGGED status."
}
}
]
},
{
"name": "Sanctions Management",
"description": "Load and manage sanctions data",
"item": [
{
"name": "Get Sanctions Status",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/sanctions/status",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "sanctions", "status"]
},
"description": "Get the count of active sanctions entries in the database.\n\n**Response:**\n```json\n{\n \"totalActiveEntries\": 1002,\n \"status\": \"ACTIVE\"\n}\n```"
}
},
{
"name": "Load Sanctions XML",
"request": {
"method": "POST",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/xml",
"type": "text"
}
],
"body": {
"mode": "file",
"file": {
"src": ""
}
},
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/sanctions/load",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "sanctions", "load"]
},
"description": "Upload and load the UN consolidated sanctions XML file into the database.\n\nSelect the `consolidatedLegacyByPRN.xml` file in the Body > Binary section.\n\n**Response:**\n```json\n{\n \"message\": \"Sanctions data loaded successfully\",\n \"entriesLoaded\": 1002,\n \"totalActive\": 1002\n}\n```"
}
}
]
},
{
"name": "Screening Activity Logs",
"description": "View sanctions screening activity logs — every search is recorded",
"item": [
{
"name": "Get All Screening Logs",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/sanctions/logs",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "sanctions", "logs"]
},
"description": "Get all sanctions screening activity logs (most recent first).\n\n**Response:**\n```json\n[\n {\n \"id\": 1,\n \"screenedName\": \"ERIC BADEGE\",\n \"screeningResult\": \"FLAGGED\",\n \"matchCount\": 1,\n \"matchedNames\": \"ERIC BADEGE\",\n \"screeningSource\": \"CLIENT_CREATION\",\n \"screenedOn\": \"2026-03-16T10:30:00Z\",\n \"screenedBy\": 1\n }\n]\n```"
}
},
{
"name": "Get FLAGGED Logs Only",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/sanctions/logs?result=FLAGGED",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "sanctions", "logs"],
"query": [
{
"key": "result",
"value": "FLAGGED",
"description": "Filter by result: FLAGGED or CLEAR"
}
]
},
"description": "Get only FLAGGED screening logs — shows all searches that matched a sanctioned entry."
}
},
{
"name": "Get CLEAR Logs Only",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/sanctions/logs?result=CLEAR",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "sanctions", "logs"],
"query": [
{
"key": "result",
"value": "CLEAR",
"description": "Filter by result: FLAGGED or CLEAR"
}
]
},
"description": "Get only CLEAR screening logs — shows all searches that did not match any sanctioned entry."
}
},
{
"name": "Search Logs by Name",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenantId}}",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"url": {
"raw": "{{baseUrl}}/fineract-provider/api/v1/sanctions/logs?name=ERIC",
"host": ["{{baseUrl}}"],
"path": ["fineract-provider", "api", "v1", "sanctions", "logs"],
"query": [
{
"key": "name",
"value": "ERIC",
"description": "Search logs by screened name (partial match)"
}
]
},
"description": "Search screening logs by the name that was screened (partial, case-insensitive match)."
}
}
]
}
]
}
+235
View File
@@ -0,0 +1,235 @@
{
"info": {
"_postman_id": "savings-products-collection",
"name": "Fineract - Savings Products",
"description": "Postman collection for Savings Product APIs.\n\nThis resource exposes 6 endpoints:\n1. POST /v1/savingsproducts\n2. PUT /v1/savingsproducts/{productId}\n3. GET /v1/savingsproducts\n4. GET /v1/savingsproducts/{productId}\n5. GET /v1/savingsproducts/template\n6. DELETE /v1/savingsproducts/{productId}\n\nThe create request below uses the mandatory fields required by the backend validator. If you switch accountingRule to cash-based or accrual-periodic, you must also supply the corresponding accounting mappings.",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"auth": {
"type": "basic",
"basic": [
{
"key": "username",
"value": "{{username}}",
"type": "string"
},
{
"key": "password",
"value": "{{password}}",
"type": "string"
}
]
},
"variable": [
{
"key": "base_url",
"value": "https://localhost:8443/fineract-provider/api/v1",
"description": "Fineract API base URL"
},
{
"key": "tenant_id",
"value": "default",
"description": "Tenant identifier"
},
{
"key": "username",
"value": "mifos",
"description": "API username"
},
{
"key": "password",
"value": "password",
"description": "API password"
},
{
"key": "product_id",
"value": "1",
"description": "Savings product id returned by the create request"
}
],
"item": [
{
"name": "Savings Products",
"item": [
{
"name": "Create Savings Product",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"pm.test('Status code is 200', function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test('Resource id returned', function () {",
" var json = pm.response.json();",
" pm.expect(json).to.have.property('resourceId');",
" pm.collectionVariables.set('product_id', json.resourceId);",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenant_id}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"Passbook Savings\",\n \"shortName\": \"PBSV\",\n \"description\": \"Daily compounding savings product\",\n \"currencyCode\": \"USD\",\n \"digitsAfterDecimal\": 2,\n \"inMultiplesOf\": 0,\n \"locale\": \"en\",\n \"nominalAnnualInterestRate\": 5.0,\n \"interestCompoundingPeriodType\": 1,\n \"interestPostingPeriodType\": 4,\n \"interestCalculationType\": 1,\n \"interestCalculationDaysInYearType\": 365,\n \"accountingRule\": 1,\n \"withdrawalFeeForTransfers\": false,\n \"enforceMinRequiredBalance\": false,\n \"allowOverdraft\": false,\n \"withHoldTax\": false,\n \"isDormancyTrackingActive\": false,\n \"charges\": [\n {\n \"id\": 1\n }\n ]\n}"
},
"url": {
"raw": "{{base_url}}/savingsproducts",
"host": [
"{{base_url}}"
],
"path": [
"savingsproducts"
]
},
"description": "Create a savings product.\n\nRequired by backend validator:\n- name\n- shortName\n- currencyCode\n- digitsAfterDecimal\n- nominalAnnualInterestRate\n- interestCompoundingPeriodType\n- interestPostingPeriodType\n- interestCalculationType\n- interestCalculationDaysInYearType\n- accountingRule\n\nIf accountingRule = 2 or 3, the backend also expects accounting mappings."
},
"response": []
},
{
"name": "Update Savings Product",
"request": {
"method": "PUT",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenant_id}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"description\": \"Updated savings product description\",\n \"locale\": \"en\",\n \"nominalAnnualInterestRate\": 5.75\n}"
},
"url": {
"raw": "{{base_url}}/savingsproducts/{{product_id}}",
"host": [
"{{base_url}}"
],
"path": [
"savingsproducts",
"{{product_id}}"
]
},
"description": "Update an existing savings product by product id."
},
"response": []
},
{
"name": "List Savings Products",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenant_id}}"
}
],
"url": {
"raw": "{{base_url}}/savingsproducts",
"host": [
"{{base_url}}"
],
"path": [
"savingsproducts"
]
},
"description": "Lists all savings products."
},
"response": []
},
{
"name": "Get Savings Product By ID",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenant_id}}"
}
],
"url": {
"raw": "{{base_url}}/savingsproducts/{{product_id}}",
"host": [
"{{base_url}}"
],
"path": [
"savingsproducts",
"{{product_id}}"
]
},
"description": "Retrieves a savings product by id. You can also call the same endpoint with ?template=true."
},
"response": []
},
{
"name": "Get Savings Product Template",
"request": {
"method": "GET",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenant_id}}"
}
],
"url": {
"raw": "{{base_url}}/savingsproducts/template",
"host": [
"{{base_url}}"
],
"path": [
"savingsproducts",
"template"
]
},
"description": "Returns template data with dropdown options and default values for savings product creation."
},
"response": []
},
{
"name": "Delete Savings Product",
"request": {
"method": "DELETE",
"header": [
{
"key": "Fineract-Platform-TenantId",
"value": "{{tenant_id}}"
}
],
"url": {
"raw": "{{base_url}}/savingsproducts/{{product_id}}",
"host": [
"{{base_url}}"
],
"path": [
"savingsproducts",
"{{product_id}}"
]
},
"description": "Deletes a savings product by id."
},
"response": []
}
],
"description": "Savings product master APIs."
}
]
}
+91 -48
View File
@@ -26,6 +26,7 @@ buildscript {
fineractJavaProjects = subprojects.findAll{
[
'fineract-core',
'fineract-security',
'fineract-cob',
'fineract-validation',
'fineract-command',
@@ -37,25 +38,32 @@ buildscript {
'fineract-charge',
'fineract-rates',
'fineract-tax',
'fineract-loan-origination',
'fineract-loan',
'fineract-savings',
'fineract-current-product',
'fineract-report',
'fineract-mix',
'integration-tests',
'twofactor-tests',
'oauth2-tests',
'fineract-client',
'fineract-client-feign',
'fineract-avro-schemas',
'fineract-e2e-tests-core',
'fineract-e2e-tests-runner',
'fineract-progressive-loan',
'fineract-progressive-loan-embeddable-schedule-generator'
'fineract-progressive-loan-embeddable-schedule-generator',
'fineract-working-capital-loan'
].contains(it.name)
}
fineractPublishProjects = subprojects.findAll{
[
'fineract-avro-schemas',
'fineract-client',
'fineract-client-feign',
'fineract-core',
'fineract-security',
'fineract-cob',
'fineract-validation',
'fineract-command',
@@ -65,13 +73,15 @@ buildscript {
'fineract-charge',
'fineract-rates',
'fineract-tax',
'fineract-loan-origination',
'fineract-loan',
'fineract-savings',
'fineract-report',
'fineract-branch',
'fineract-document',
'fineract-progressive-loan',
'fineract-progressive-loan-embeddable-schedule-generator'
'fineract-progressive-loan-embeddable-schedule-generator',
'fineract-working-capital-loan'
].contains(it.name)
}
npmRepository = 'https://npm.pkg.github.com'
@@ -100,7 +110,7 @@ plugins {
id 'com.github.hierynomus.license' version '0.16.1' apply false
id 'com.github.jk1.dependency-license-report' version '2.9' apply false
id 'org.zeroturnaround.gradle.jrebel' version '1.2.0' apply false
id 'org.springframework.boot' version '3.5.5' apply false
id 'org.springframework.boot' version '3.5.6' apply false
id 'net.ltgt.errorprone' version '4.1.0' apply false
id 'io.swagger.core.v3.swagger-gradle-plugin' version '2.2.23' apply false
id 'com.gorylenko.gradle-git-properties' version '2.4.2' apply false
@@ -115,6 +125,8 @@ plugins {
id 'org.openapi.generator' version '7.8.0' apply false
id 'com.gradleup.shadow' version '8.3.5' apply false
id 'me.champeau.jmh' version '0.7.1' apply false
id 'org.cyclonedx.bom' version '3.1.0' apply false
id 'com.docktape.swagger-brake' version '2.7.0' apply false
}
apply from: "${rootDir}/buildSrc/src/main/groovy/org.apache.fineract.release.gradle"
@@ -161,6 +173,15 @@ allprojects {
mavenCentral()
}
configurations.configureEach {
resolutionStrategy {
dependencySubstitution {
// Substitution is to resolve CVE-2025-12183
substitute module('org.lz4:lz4-java') using module('at.yawk.lz4:lz4-java:1.10.1')
}
}
}
configurations {
implementation {
exclude group: 'commons-logging', module: 'commons-logging'
@@ -342,8 +363,7 @@ allprojects {
"**/generated/**/*MapperImpl.java",
'**/META-INF/fineract-test.config',
// Apache-specific GitHub metadata settings file
'/.asf.yaml',
'**/*.sh'
'/.asf.yaml'
]
}
}
@@ -379,7 +399,7 @@ configure(project.fineractJavaProjects) {
}
// Add performance optimizations
configurations.all {
configurations.configureEach {
resolutionStrategy {
cacheChangingModulesFor 0, 'seconds'
cacheDynamicVersionsFor 0, 'seconds'
@@ -427,40 +447,40 @@ configure(project.fineractJavaProjects) {
configurations {
api.setCanBeResolved(true)
}
tasks.withType(Copy) {
tasks.withType(Copy).configureEach {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
tasks.withType(JavaCompile) {
tasks.withType(JavaCompile).configureEach {
options.compilerArgs += [
"-Xlint:cast",
"-Xlint:auxiliaryclass",
"-Xlint:dep-ann",
"-Xlint:divzero",
"-Xlint:empty",
"-Xlint:exports",
"-Xlint:fallthrough",
"-Xlint:finally",
"-Xlint:module",
"-Xlint:opens",
"-Xlint:options",
"-Xlint:overloads",
"-Xlint:overrides",
"-Xlint:path",
"-Xlint:processing",
"-Xlint:removal",
"-Xlint:requires-automatic",
"-Xlint:requires-transitive-automatic",
"-Xlint:try",
"-Xlint:varargs",
"-Xlint:preview",
"-Xlint:static",
// -Werror needs to be disabled because EclipseLink's static weaving doesn't generate warning-free code
// and during an IntelliJ recompilation, it fails
//"-Werror",
"-Xmaxwarns",
"1500",
"-Xmaxerrs",
"1500"
"-Xlint:cast",
"-Xlint:auxiliaryclass",
"-Xlint:dep-ann",
"-Xlint:divzero",
"-Xlint:empty",
"-Xlint:exports",
"-Xlint:fallthrough",
"-Xlint:finally",
"-Xlint:module",
"-Xlint:opens",
"-Xlint:options",
"-Xlint:overloads",
"-Xlint:overrides",
"-Xlint:path",
"-Xlint:processing",
"-Xlint:removal",
"-Xlint:requires-automatic",
"-Xlint:requires-transitive-automatic",
"-Xlint:try",
"-Xlint:varargs",
"-Xlint:preview",
"-Xlint:static",
// -Werror needs to be disabled because EclipseLink's static weaving doesn't generate warning-free code
// and during an IntelliJ recompilation, it fails
//"-Werror",
"-Xmaxwarns",
"1500",
"-Xmaxerrs",
"1500"
]
// TODO FINERACT-959 (gradually) enable -Xlint:all (see "javac -help -X")
@@ -558,6 +578,9 @@ configure(project.fineractJavaProjects) {
if (project.path == ':fineract-client') {
excludedPaths = '.*/build/generated/java/src/main/java/.*'
}
if (project.path == ':fineract-client-feign') {
excludedPaths = '.*/build/generated/java/src/main/java/.*'
}
disable(
// TODO Remove disabled checks from this list, by fixing remaining usages
"UnusedVariable",
@@ -743,11 +766,13 @@ configure(project.fineractJavaProjects) {
]
}
tasks.withType(Javadoc) {
options.addStringOption('Xdoclint:none', '-quiet')
options.encoding = 'UTF-8'
// Ignore any errors during javadoc generation
failOnError = false
tasks.withType(Javadoc).tap {
configureEach {
options.addStringOption('Xdoclint:none', '-quiet')
options.encoding = 'UTF-8'
// Ignore any errors during javadoc generation
failOnError = false
}
}
}
@@ -873,17 +898,35 @@ configure(project.fineractPublishProjects) {
}
}
task printSourceSetInformation() {
doLast{
// Configuration for CycloneDX SBOM generator
// https://github.com/CycloneDX/cyclonedx-gradle-plugin
apply plugin: 'org.cyclonedx.bom'
cyclonedxBom {
projectType = "application"
includeBomSerialNumber = true
includeLicenseText = true
}
tasks.withType(org.cyclonedx.gradle.CyclonedxDirectTask).configureEach {
includeMetadataResolution.set(false)
}
tasks.register('printSourceSetInformation') {
doLast {
sourceSets.each { srcSet ->
println "["+srcSet.name+"]"
print "-->Source directories: "+srcSet.allJava.srcDirs+"\n"
print "-->Output directories: "+srcSet.output.classesDirs.files+"\n"
println "[" + srcSet.name + "]"
print "-->Source directories: " + srcSet.allJava.srcDirs + "\n"
print "-->Output directories: " + srcSet.output.classesDirs.files + "\n"
print "-->Compile classpath:\n"
srcSet.compileClasspath.files.each {
print " "+it.path+"\n"
print " " + it.path + "\n"
}
println ""
}
}
}
tasks.withType(Checkstyle).configureEach {
exclude("**/*.avsc")
}
+9
View File
@@ -41,6 +41,15 @@ repositories {
mavenCentral()
}
configurations.all {
resolutionStrategy {
dependencySubstitution {
// Substitution is to resolve CVE-2025-12183
substitute module('org.lz4:lz4-java') using module('at.yawk.lz4:lz4-java:1.10.1')
}
}
}
dependencies {
implementation 'com.sun.activation:jakarta.activation'
implementation 'com.sun.mail:jakarta.mail'
@@ -25,12 +25,12 @@ dependencyManagement {
mavenBom 'com.squareup.okhttp3:okhttp-bom:4.12.0'
mavenBom 'org.slf4j:slf4j-bom:2.0.17'
mavenBom 'io.micrometer:micrometer-bom:1.13.6'
mavenBom 'org.springframework.boot:spring-boot-dependencies:3.5.5'
mavenBom 'org.springframework.boot:spring-boot-dependencies:3.5.6'
mavenBom 'io.awspring.cloud:spring-cloud-aws-dependencies:3.2.1'
mavenBom 'io.opentelemetry:opentelemetry-bom:1.44.1'
mavenBom 'org.jetbrains.kotlin:kotlin-bom:2.0.21'
mavenBom 'org.junit:junit-bom:5.11.3'
mavenBom 'com.fasterxml.jackson:jackson-bom:2.18.3'
mavenBom 'com.fasterxml.jackson:jackson-bom:2.19.2'
mavenBom 'io.cucumber:cucumber-bom:7.20.1'
mavenBom 'org.mockito:mockito-bom:5.14.2'
mavenBom 'software.amazon.awssdk:bom:2.29.9'
@@ -44,8 +44,8 @@ dependencyManagement {
// We do not use :+ to get the latest available version available on Maven Central, as that could suddenly break things.
// We use the Renovate Bot to automatically propose Pull Requests (PRs) when upgrades for all of these versions are available.
dependency 'ch.qos.logback:logback-core:1.5.17'
dependency 'ch.qos.logback:logback-classic:1.5.17'
dependency 'ch.qos.logback:logback-core:1.5.19'
dependency 'ch.qos.logback:logback-classic:1.5.19'
dependency 'ch.qos.logback.contrib:logback-json-classic:0.1.5'
dependency 'ch.qos.logback.contrib:logback-jackson:0.1.5'
dependency 'org.codehaus.janino:janino:3.1.12'
@@ -55,7 +55,7 @@ dependencyManagement {
dependency 'com.google.code.gson:gson:2.11.0'
dependency 'com.google.googlejavaformat:google-java-format:1.24.0'
dependency 'org.apache.commons:commons-collections4:4.4'
dependency 'org.apache.commons:commons-compress:1.26.0'
dependency 'org.apache.commons:commons-compress:1.28.0'
dependency ('software.amazon.msk:aws-msk-iam-auth:2.2.0') {
exclude 'commons-logging:commons-logging:'
}
@@ -64,7 +64,7 @@ dependencyManagement {
exclude 'javax.activation:activation'
}
dependency 'commons-io:commons-io:2.18.0'
dependency 'com.github.librepdf:openpdf:2.0.3'
dependency 'com.github.librepdf:openpdf:3.0.0'
dependency ('org.mnode.ical4j:ical4j:3.2.19') {
exclude 'com.sun.mail:javax.mail'
exclude 'org.codehaus.groovy:groovy'
@@ -74,17 +74,17 @@ dependencyManagement {
dependency 'org.ehcache:ehcache:3.10.8'
dependency 'com.github.spullara.mustache.java:compiler:0.9.14'
dependency 'com.jayway.jsonpath:json-path:2.9.0'
dependency ('org.apache.tika:tika-core:2.9.3') {
dependency ('org.apache.tika:tika-core:3.2.3') {
exclude 'commons-logging:commons-logging'
}
dependency ('org.apache.tika:tika-core:2.9.3') {
dependency ('org.apache.tika:tika-core:3.2.3') {
exclude 'commons-logging:commons-logging'
}
dependency ('org.apache.tika:tika-parser-miscoffice-module:2.9.3') {
dependency ('org.apache.tika:tika-parser-miscoffice-module:3.2.3') {
exclude 'org.bouncycastle:bcprov-jdk15on'
exclude 'org.bouncycastle:bcmail-jdk15on'
exclude 'org.bouncycastle:bcjmail-jdk15on'
exclude 'org.bouncycastle:bcprov-jdk18on'
exclude 'org.bouncycastle:bcmail-jdk18on'
exclude 'org.bouncycastle:bcjmail-jdk18on'
exclude 'commons-logging:commons-logging'
exclude 'org.apache.logging.log4j:log4j-api'
exclude 'org.slf4j:slf4j-api'
@@ -97,11 +97,28 @@ dependencyManagement {
exclude 'org.apache.commons:commons-compress'
exclude 'xml-apis:xml-apis'
}
dependency ('org.apache.tika:tika-parser-microsoft-module:2.9.3') {
dependency ('org.apache.tika:tika-parser-microsoft-module:3.2.3') {
exclude 'org.bouncycastle:bcprov-jdk15on'
exclude 'org.bouncycastle:bcmail-jdk15on'
exclude 'org.bouncycastle:bcjmail-jdk15on'
exclude 'org.bouncycastle:bcprov-jdk18on'
exclude 'org.bouncycastle:bcmail-jdk18on'
exclude 'org.bouncycastle:bcjmail-jdk18on'
exclude 'commons-logging:commons-logging'
exclude 'org.apache.logging.log4j:log4j-api'
exclude 'org.slf4j:slf4j-api'
exclude 'commons-io:commons-io'
exclude 'commons-codec:commons-codec'
exclude 'org.apache.commons:commons-lang3'
exclude 'org.apache.poi:poi'
exclude 'org.apache.poi:poi-scratchpad'
exclude 'org.glassfish.jaxb:jaxb-runtime'
exclude 'org.apache.commons:commons-compress'
exclude 'xml-apis:xml-apis'
}
dependency ('org.apache.tika:tika-parser-image-module:3.2.3') {
exclude 'org.bouncycastle:bcprov-jdk15on'
exclude 'org.bouncycastle:bcjmail-jdk15on'
exclude 'org.bouncycastle:bcprov-jdk18on'
exclude 'org.bouncycastle:bcjmail-jdk18on'
exclude 'commons-logging:commons-logging'
exclude 'org.apache.logging.log4j:log4j-api'
exclude 'org.slf4j:slf4j-api'
@@ -152,14 +169,14 @@ dependencyManagement {
dependency "commons-codec:commons-codec:1.17.1"
dependency "org.projectlombok:lombok:1.18.36"
dependency 'org.bouncycastle:bcpkix-jdk18on:1.80'
dependency 'org.bouncycastle:bcprov-jdk18on:1.80'
dependency 'org.bouncycastle:bcutil-jdk18on:1.80'
dependency 'org.bouncycastle:bcpg-jdk18on:1.80'
dependency 'org.bouncycastle:bcpkix-jdk18on:1.81'
dependency 'org.bouncycastle:bcprov-jdk18on:1.81'
dependency 'org.bouncycastle:bcutil-jdk18on:1.81'
dependency 'org.bouncycastle:bcpg-jdk18on:1.81'
dependency 'org.eclipse.jgit:org.eclipse.jgit:7.2.0.202503040940-r'
dependency 'org.eclipse.jgit:org.eclipse.jgit.gpg.bc:7.2.0.202503040940-r'
dependency 'org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.2.0.202503040940-r'
dependency 'org.eclipse.jgit:org.eclipse.jgit:7.2.1.202505142326-r'
dependency 'org.eclipse.jgit:org.eclipse.jgit.gpg.bc:7.2.1.202505142326-r'
dependency 'org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.2.1.202505142326-r'
dependency ('com.tmatesoft.svnkit:svnkit:1.10.12')
dependency 'com.vdurmont:semver4j:3.1.0'
@@ -185,7 +202,7 @@ dependencyManagement {
dependency 'jakarta.annotation:jakarta.annotation-api:3.0.0'
dependency 'jakarta.activation:jakarta.activation-api:2.1.3'
dependency ('com.sun.mail:jakarta.mail:2.0.1') {
dependency ('com.sun.mail:jakarta.mail:2.0.2') {
// Spring needs this version
exclude 'com.sun.activation:jakarta.activation'
}
@@ -195,10 +212,10 @@ dependencyManagement {
dependency 'jakarta.validation:jakarta.validation-api:3.1.1'
dependency 'org.hibernate.validator:hibernate-validator:9.0.1.Final'
dependency ('org.liquibase:liquibase-core:4.31.1') {
dependency ('org.liquibase:liquibase-core:4.33.0') {
exclude 'javax.xml.bind:jaxb-api'
}
dependency 'org.liquibase.ext:liquibase-postgresql:4.31.1'
dependency 'org.liquibase.ext:liquibase-postgresql:4.33.0'
dependency ('org.dom4j:dom4j:2.1.4') {
exclude 'relaxngDatatype:relaxngDatatype' // already in com.sun.xml.bind:jaxb-osgi:2.3.0.1
@@ -221,8 +238,8 @@ dependencyManagement {
entry 'json-path'
entry 'xml-path'
}
dependency 'org.apache.groovy:groovy-xml:4.0.26'
dependency 'org.apache.groovy:groovy-json:4.0.26'
dependency 'org.apache.groovy:groovy-xml:5.0.2'
dependency 'org.apache.groovy:groovy-json:5.0.2'
dependency 'org.mapstruct:mapstruct:1.6.3'
dependency 'org.mapstruct:mapstruct-processor:1.6.3'
@@ -235,9 +252,9 @@ dependencyManagement {
}
dependency 'org.postgresql:postgresql:42.7.8'
dependency 'com.mysql:mysql-connector-j:9.2.0'
dependency 'com.mysql:mysql-connector-j:9.3.0'
dependency 'org.assertj:assertj-core:3.26.3'
dependency 'org.assertj:assertj-core:3.27.7'
dependency 'org.apache.commons:commons-math3:3.6.1'
dependency 'commons-beanutils:commons-beanutils:1.11.0'
@@ -265,6 +282,12 @@ dependencyManagement {
dependency 'com.lmax:disruptor:3.4.4'
dependency ('com.github.romankh3:image-comparison:4.4.0') {
exclude 'org.junit.jupiter:junit-jupiter-api'
exclude 'org.junit.jupiter:junit-jupiter-engine'
exclude 'org.mockito:mockito-core'
}
dependency 'com.ibm.icu:icu4j:76.1'
dependency 'org.yakworks:spring-icu4j:0.4.2'
dependency 'org.apache.commons:commons-lang3:3.18.0'
@@ -273,9 +296,15 @@ dependencyManagement {
dependency 'org.springframework:spring-core:6.2.11'
// Force Spring Framework version: CVE-2025-41248
dependency 'org.springframework.security:spring-security-core:6.5.4'
// Force netty-codec version: CVE-2025-58057
dependency 'io.netty:netty-codec:4.1.125.Final'
// Force netty-codec version: CVE-2025-67735
dependency 'io.netty:netty-codec:4.1.129.Final'
// Force netty-codec version: CVE-2025-58056
dependency 'io.netty:netty-codec-http:4.1.125.Final'
dependency 'io.netty:netty-codec-http:4.1.129.Final'
// Force lz4-java version: CVE-2025-12183
dependency 'at.yawk.lz4:lz4-java:1.10.1'
// Force tomcat-embed-core version: CVE-2025-24813
dependency 'org.apache.tomcat.embed:tomcat-embed-core:10.1.49'
dependency 'org.apache.tomcat.embed:tomcat-embed-el:10.1.49'
dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:10.1.49'
}
}
@@ -196,36 +196,10 @@ fineract {
step10: [
order: 10,
description: 'Start voting on the dev mailing list',
email: [
from: "${findProperty('fineract.config.email')}",
name: "${findProperty('fineract.config.name')}",
to: "${findProperty('fineract.config.email')}",
// to: 'dev@fineract.apache.org',
mime: 'text/plain',
subjectTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step10.vote.subject.ftl"
],
messageTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step10.vote.message.ftl"
]
]
],
step11: [
order: 11,
description: 'Conclude the vote and announce results',
email: [
from: "${findProperty('fineract.config.email')}",
name: "${findProperty('fineract.config.name')}",
to: "${findProperty('fineract.config.email')}",
// to: 'dev@fineract.apache.org',
mime: 'text/plain',
subjectTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step11.vote.subject.ftl"
],
messageTemplate: [
templateFile: "${projectDir}/buildSrc/src/main/resources/email/release.step11.vote.message.ftl"
]
]
],
step12: [
order: 12,
@@ -18,7 +18,6 @@
*/
package org.apache.fineract.gradle
import groovy.json.JsonSlurper
import org.apache.commons.io.FileUtils
import org.apache.commons.io.filefilter.NotFileFilter
import org.apache.commons.io.filefilter.PrefixFileFilter
@@ -384,23 +383,7 @@ class FineractPlugin implements Plugin<Project> {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step10")
String version = project.properties?['fineract.release.version']
if(!version) {
TextIO textIO = TextIoFactory.getTextIO();
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
// TODO: input validation, see FINERACT-1610
this.context?.project?['fineract.release.version'] = version
if(step.email) {
emailService.send( processEmailParams(step.email, this.context) )
}
printInstructions(project, "step10")
}
}
@@ -409,28 +392,7 @@ class FineractPlugin implements Plugin<Project> {
doFirst {
FineractPluginExtension.FineractPluginStep step = step(extension, "step11")
String version = project.properties?['fineract.release.version']
if(!version) {
TextIO textIO = TextIoFactory.getTextIO()
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
// TODO: input validation, see FINERACT-1610
this.context?.project?['fineract.release.version'] = version
def jsonSlurper = new JsonSlurper()
def data = jsonSlurper.parse(new File("buildSrc/src/main/resources/vote/result.${version}.json"))
this.context?.project?['fineract.vote'] = data
if(step.email) {
emailService.send( processEmailParams(step.email, this.context) )
}
printInstructions(project, "step11")
}
}
@@ -470,36 +432,7 @@ class FineractPlugin implements Plugin<Project> {
doFirst {
log.warn("Release step 15: send email to announcement mailing list")
FineractPluginExtension.FineractPluginStep step = step(extension, "step15")
String version = project.properties?['fineract.release.version']
if(!version) {
TextIO textIO = TextIoFactory.getTextIO()
version = textIO.newStringInputReader()
.withPattern("\\d+.\\d+.\\d+")
.read("Release Version");
}
// TODO: input validation, see FINERACT-1610
FineractPluginExtension.FineractPluginJiraParams issues = jiraService.search(step.jira)
def versions = jiraService.getProjectVersions(step.jira.projectId)
def filteredVersions = versions.findAll {
log.warn(">>>> VERSION: ${it.id} - ${it.name} - ${it.description}")
it.name == version
}
this.context?.project?['fineract.release.version'] = version
this.context?.project?['fineract.release.issues'] = issues.result
this.context?.project?['fineract.release.projectId'] = step.jira.projectId
this.context?.project?['fineract.release.versionId'] = filteredVersions[0]?.id
if(step.email) {
emailService.send( processEmailParams(step.email, this.context) )
}
printInstructions(project, "step15")
}
}
}
@@ -1,53 +0,0 @@
<#--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
Hello everyone,
We are proud to present Apache Fineract ${project['fineract.release.version']}, with the artifacts below up for a vote. Releases are important for a number of reasons: They put a stamp of approval on a set of code changes and they build momentum for future improvements.
Release notes and ChangeLog: https://cwiki.apache.org/confluence/display/FINERACT/${project['fineract.release.version']}+-+Apache+Fineract
Source and binary artifacts: https://dist.apache.org/repos/dist/dev/fineract/${project['fineract.release.version']}/
Tagged as ${project['fineract.release.version']}
Committer PGP keys, including the release signing key: https://dist.apache.org/repos/dist/dev/fineract/KEYS
Note that this release candidate contains source and binary artifacts.
This vote will be open for 72 hours:
[ ] +1 approve
[ ] +0 no opinion
[ ] -1 disapprove (and reason why)
Please indicate if you are a binding vote (member of the PMC). Note: PMC members are required to download, compile, and test the artifacts before submitting their +1 vote.
Please also indicate with "Tested: YES/NO/PARTIAL" if you have locally built and/or tested these artifacts and/or a clone of the code checked out to the release commit, following the form:
Tested: YES ... Verified integrity and signatures of release artifacts locally, built from source, ran jar/war: Did everything mentioned in the current release candidate verification guidance ( https://fineract.apache.org/docs/rc/#_artifact_verification ). If you did more than that, please specify.
Tested: NO ... No testing performed on release candidate, e.g. relying on testing performed by other contributors and/or output of GitHub Actions, while exercising my right to vote.
Tested: PARTIAL ... Please specify.
Cheers,
${project['fineract.config.name']}
@@ -1,89 +0,0 @@
<#--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<#if (project['fineract.vote'].approve.binding?size + project['fineract.vote'].approve.nonBinding?size > project['fineract.vote'].disapprove.binding?size + project['fineract.vote'].disapprove.nonBinding?size)>
Voting is now closed and has passed with the following tally,
Binding +1s: ${project['fineract.vote'].approve.binding?size}
Non binding +1s: ${project['fineract.vote'].approve.nonBinding?size}
<#else>
Voting is now closed and has not passed with the following tally,
Binding +1s: ${project['fineract.vote'].approve.binding?size}
Non binding +1s: ${project['fineract.vote'].approve.nonBinding?size}
Binding -1s: ${project['fineract.vote'].disapprove.binding?size}
Non binding -1s: ${project['fineract.vote'].disapprove.nonBinding?size}
</#if>
Here are the detailed results:
<#list project['fineract.vote'].approve.binding>
Binding +1s:
<#items as item>
- ${item.name} (${item.email})
</#items>
</#list>
<#list project['fineract.vote'].approve.nonBinding>
Non binding +1s:
<#items as item>
- ${item.name} (${item.email})
</#items>
</#list>
<#list project['fineract.vote'].disapprove.binding>
Binding -1s:
<#items as item>
- ${item.name} (${item.email})
</#items>
</#list>
<#list project['fineract.vote'].disapprove.nonBinding>
Non binding -1s:
<#items as item>
- ${item.name} (${item.email})
</#items>
</#list>
<#list project['fineract.vote'].noOpinion.binding>
Binding +0s:
<#items as item>
- ${item.name} (${item.email})
</#items>
</#list>
<#list project['fineract.vote'].noOpinion.nonBinding>
Non binding +0s:
<#items as item>
- ${item.name} (${item.email})
</#items>
</#list>
<#if (project['fineract.vote'].approve.binding?size + project['fineract.vote'].approve.nonBinding?size > project['fineract.vote'].disapprove.binding?size + project['fineract.vote'].disapprove.nonBinding?size)>
Thanks to everyone who voted! I'll now continue with the rest of the release process.
<#else>
Thanks to everyone who voted! Looks like we have to repeat the vote.
</#if>
${project['fineract.config.name']}
@@ -1,43 +0,0 @@
<#--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
The Apache Fineract project is pleased to announce
the release of Apache Fineract ${project['fineract.release.version']}.
The release is available for download from
https://fineract.apache.org/#downloads
Fineract provides a reliable, robust, and affordable solution for entrepreneurs,
financial institutions, and service providers to offer financial services to the
world’s 2 billion underbanked and unbanked. Fineract is aimed at innovative mobile
and cloud-based solutions, and enables digital transaction accounts for all.
This release addressed ${project['fineract.release.issues']?size} issues.
Readme: https://github.com/apache/fineract/blob/${project['fineract.release.version']}/README.md
Release page: https://cwiki.apache.org/confluence/display/FINERACT/${project['fineract.release.version']}+-+Apache+Fineract
List of fixed issues:
https://issues.apache.org/jira/secure/ReleaseNote.jspa?version=${project['fineract.release.versionId']}&styleName=Html&projectId=${project['fineract.release.projectId']}
For more information on Apache Fineract please visit
project home page: https://fineract.apache.org
The Apache Fineract Team
@@ -18,4 +18,12 @@
under the License.
-->
[ANNOUNCE] Apache Fineract ${project['fineract.release.version']} Release
[INSTRUCTIONS:START]
Initiate voting with the ATR (ASF Trusted Releases) tool.
https://release-test.apache.org
[INSTRUCTIONS:END]
@@ -18,4 +18,12 @@
under the License.
-->
[FINERACT] [VOTE] 🗳️ ${project['fineract.release.version']} for release
[INSTRUCTIONS:START]
Conclude voting with the ATR (ASF Trusted Releases) tool.
https://release-test.apache.org
[INSTRUCTIONS:END]
@@ -24,6 +24,7 @@
Move the release candidate from the dev area to the release area using a Subversion server-side copy:
svn mv https://dist.apache.org/repos/dist/dev/fineract/${project['fineract.release.version']} https://dist.apache.org/repos/dist/release /fineract/
# this is a remote operation
svn mv -m "Release Fineract 1.14.0 🚢" https://dist.apache.org/repos/dist/dev/fineract/${project['fineract.release.version']} https://dist.apache.org/repos/dist/release/fineract/
[INSTRUCTIONS:END]
@@ -30,5 +30,6 @@ and make sure that everything on the release tag is merged to develop and that e
>> git push origin develop
>> git branch -D release/${project['fineract.release.version']}
>> git push origin :release/${project['fineract.release.version']}
>> git describe
[INSTRUCTIONS:END]
@@ -18,4 +18,12 @@
under the License.
-->
[FINERACT] [VOTE] [RESULT] 🧾️ ${project['fineract.release.version']} for release
[INSTRUCTIONS:START]
Manually draft an email using your Apache ID.
See https://fineract.apache.org/docs/current/#_step_15_announcement_email
[INSTRUCTIONS:END]
@@ -22,17 +22,6 @@
[INSTRUCTIONS:START]
Following are the typical things we need to verify before voting on a release candidate. And the release manager should verify them too before calling out a vote.
Make sure release artifacts are hosted at https://dist.apache.org/repos/dist/dev/fineract
* Release candidates should be in format apache-fineract-bin-${project['fineract.release.version']}.tar.gz
* Verify signatures and hashes. You may have to import the public key of the release manager to verify the signatures. (gpg --import KEYS or gpg --recv-key <key id>)
* Git tag matches the released bits (diff -rf)
* Can compile docs and code successfully from source
* Verify DISCLAIMER, NOTICE and LICENSE (year etc)
* All files have correct headers (Rat check should be clean - gradlew rat)
* No jar files in the source artifacts
* Integration tests should work
See https://fineract.apache.org/docs/current/#_step_9_verify_distribution_staging
[INSTRUCTIONS:END]
@@ -0,0 +1,56 @@
{
"approve": {
"binding": [
{
"name": "James Dailey",
"email": "jdailey@apache.org"
},
{
"name": "Victor Manuel Romero Rodriguez",
"email": "victor.romero@fintecheando.mx"
},
{
"name": "Terence Monteiro",
"email": "terence.placker@gmail.com"
}
],
"nonBinding": [
{
"name": "Adam Monsen",
"email": "amonsen@mifos.org"
},
{
"name": "Bharath Gowda",
"email": "bgowda@mifos.org"
},
{
"name": "Aleksandar Vidakovic",
"email": "cheetah@monkeysintown.com"
},
{
"name": "Ed Cable",
"email": "edcable@mifos.org"
},
{
"name": "Felix van Hove",
"email": "fvanhove@gmx.de.invalid"
},
{
"name": "Ahmed adel",
"email": "a7med3del1973@gmail.com"
}
]
},
"disapprove": {
"binding": [
],
"nonBinding": [
]
},
"noOpinion": {
"binding": [
],
"nonBinding": [
]
}
}
+46
View File
@@ -0,0 +1,46 @@
@echo off
echo Stopping any running Gradle daemons...
call gradlew.bat --stop
echo.
echo Deleting corrupted build folders...
if exist fineract-provider\build (
echo Deleting fineract-provider\build...
rmdir /s /q fineract-provider\build
)
if exist fineract-command\build (
echo Deleting fineract-command\build...
rmdir /s /q fineract-command\build
)
if exist fineract-core\build (
echo Deleting fineract-core\build...
rmdir /s /q fineract-core\build
)
if exist fineract-accounting\build (
echo Deleting fineract-accounting\build...
rmdir /s /q fineract-accounting\build
)
if exist fineract-charge\build (
echo Deleting fineract-charge\build...
rmdir /s /q fineract-charge\build
)
if exist fineract-cob\build (
echo Deleting fineract-cob\build...
rmdir /s /q fineract-cob\build
)
if exist fineract-document\build (
echo Deleting fineract-document\build...
rmdir /s /q fineract-document\build
)
if exist fineract-branch\build (
echo Deleting fineract-branch\build...
rmdir /s /q fineract-branch\build
)
if exist fineract-avro-schemas\build (
echo Deleting fineract-avro-schemas\build...
rmdir /s /q fineract-avro-schemas\build
)
echo.
echo Build folders deleted successfully.
echo Now you can run: .\gradlew devRun
echo.
pause
+116
View File
@@ -0,0 +1,116 @@
> Task :buildSrc:extractPluginRequests UP-TO-DATE
> Task :buildSrc:generatePluginAdapters UP-TO-DATE
> Task :buildSrc:compileJava UP-TO-DATE
> Task :buildSrc:compileGroovy UP-TO-DATE
> Task :buildSrc:compileGroovyPlugins UP-TO-DATE
> Task :buildSrc:pluginDescriptors UP-TO-DATE
> Task :buildSrc:processResources UP-TO-DATE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar UP-TO-DATE
> Configure project :
matching ref: COMMIT - 011239f3995f9ea94a893ed4a60251a3b154564c
ref configuration: COMMIT - pattern: null
version: ${describe.tag.version.major}.${describe.tag.version.minor.next}.0-SNAPSHOT
describeTagPattern: .*(\d+\.\d+\.\d+).*
describeTagFirstParent: false
project version: 0.1.0-SNAPSHOT
> Task :fineract-core:processResources UP-TO-DATE
> Task :fineract-tax:processResources UP-TO-DATE
> Task :fineract-accounting:processResources UP-TO-DATE
> Task :spotlessInternalRegisterDependencies UP-TO-DATE
> Task :fineract-document:processResources NO-SOURCE
> Task :fineract-avro-schemas:preprocessAvroSchemas UP-TO-DATE
> Task :fineract-charge:processResources UP-TO-DATE
> Task :fineract-branch:processResources UP-TO-DATE
> Task :fineract-cob:processResources UP-TO-DATE
> Task :fineract-rates:processResources UP-TO-DATE
> Task :fineract-investor:processResources UP-TO-DATE
> Task :fineract-mix:processResources NO-SOURCE
> Task :fineract-loan:processResources UP-TO-DATE
> Task :fineract-loan-origination:processResources UP-TO-DATE
> Task :fineract-progressive-loan:processResources UP-TO-DATE
> Task :fineract-security:processResources NO-SOURCE
> Task :fineract-report:processResources UP-TO-DATE
> Task :fineract-savings:processResources UP-TO-DATE
> Task :fineract-working-capital-loan:processResources UP-TO-DATE
> Task :fineract-validation:compileJava UP-TO-DATE
> Task :fineract-command:compileJava UP-TO-DATE
> Task :fineract-validation:processResources UP-TO-DATE
> Task :fineract-command:processResources UP-TO-DATE
> Task :fineract-command:classes UP-TO-DATE
> Task :fineract-validation:classes UP-TO-DATE
> Task :fineract-command:jar UP-TO-DATE
> Task :fineract-validation:jar UP-TO-DATE
> Task :fineract-avro-schemas:spotlessJson UP-TO-DATE
> Task :fineract-avro-schemas:spotlessJsonApply UP-TO-DATE
> Task :fineract-avro-schemas:buildJavaSdk UP-TO-DATE
> Task :fineract-avro-schemas:compileJava UP-TO-DATE
> Task :fineract-avro-schemas:processResources UP-TO-DATE
> Task :fineract-avro-schemas:classes UP-TO-DATE
> Task :fineract-avro-schemas:jar UP-TO-DATE
> Task :fineract-working-capital-loan:compileJava UP-TO-DATE
> Task :fineract-working-capital-loan:classes UP-TO-DATE
> Task :fineract-working-capital-loan:jar UP-TO-DATE
> Task :fineract-core:compileJava UP-TO-DATE
> Task :fineract-core:classes UP-TO-DATE
> Task :fineract-core:jar UP-TO-DATE
> Task :fineract-mix:compileJava UP-TO-DATE
> Task :fineract-mix:classes UP-TO-DATE
> Task :fineract-mix:jar UP-TO-DATE
> Task :fineract-cob:compileJava UP-TO-DATE
> Task :fineract-cob:classes UP-TO-DATE
> Task :fineract-report:compileJava UP-TO-DATE
> Task :fineract-tax:compileJava UP-TO-DATE
> Task :fineract-rates:compileJava UP-TO-DATE
> Task :fineract-tax:classes UP-TO-DATE
> Task :fineract-rates:classes UP-TO-DATE
> Task :fineract-cob:jar UP-TO-DATE
> Task :fineract-report:classes UP-TO-DATE
> Task :fineract-tax:jar UP-TO-DATE
> Task :fineract-rates:jar UP-TO-DATE
> Task :fineract-report:jar UP-TO-DATE
> Task :fineract-charge:compileJava UP-TO-DATE
> Task :fineract-charge:classes UP-TO-DATE
> Task :fineract-security:compileJava UP-TO-DATE
> Task :fineract-charge:jar UP-TO-DATE
> Task :fineract-security:classes UP-TO-DATE
> Task :fineract-document:compileJava UP-TO-DATE
> Task :fineract-document:classes UP-TO-DATE
> Task :fineract-security:jar UP-TO-DATE
> Task :fineract-document:jar UP-TO-DATE
> Task :fineract-accounting:compileJava UP-TO-DATE
> Task :fineract-accounting:classes UP-TO-DATE
> Task :fineract-accounting:jar UP-TO-DATE
> Task :fineract-branch:compileJava UP-TO-DATE
> Task :fineract-branch:classes UP-TO-DATE
> Task :fineract-branch:jar UP-TO-DATE
> Task :fineract-savings:compileJava UP-TO-DATE
> Task :fineract-loan:compileJava UP-TO-DATE
> Task :fineract-loan:classes UP-TO-DATE
> Task :fineract-savings:classes UP-TO-DATE
> Task :fineract-loan:jar UP-TO-DATE
> Task :fineract-savings:jar UP-TO-DATE
> Task :fineract-progressive-loan:compileJava UP-TO-DATE
> Task :fineract-progressive-loan:classes UP-TO-DATE
> Task :fineract-loan-origination:compileJava UP-TO-DATE
> Task :fineract-loan-origination:classes UP-TO-DATE
> Task :fineract-investor:compileJava UP-TO-DATE
> Task :fineract-investor:classes UP-TO-DATE
> Task :fineract-loan-origination:jar UP-TO-DATE
> Task :fineract-investor:jar UP-TO-DATE
> Task :fineract-progressive-loan:jar UP-TO-DATE
> Task :fineract-provider:compileJava UP-TO-DATE
[Incubating] Problems report is available at: file:///D:/CompanyProject/GitCBS/JAVA-Backend-CBS-MAIN/build/reports/problems/problems-report.html
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
For more on this, please refer to https://docs.gradle.org/8.14.3/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
BUILD SUCCESSFUL in 23s
71 actionable tasks: 71 up-to-date
+3 -4
View File
@@ -16,16 +16,15 @@
# under the License.
#
version: "3.8"
services:
fineract:
# user: "${FINERACT_USER}:${FINERACT_GROUP}"
image: ${IMAGE_NAME:-apache/fineract:latest}
image: fineract:latest
user: "${FINERACT_USER}:${FINERACT_GROUP}"
volumes:
- ${PWD}/config/docker/logback/logback-override.xml:/app/logback-override.xml
- ${PWD}/config/docker/aws/etc/credentials:/etc/aws/credentials:ro
- ${PWD}/build/fineract/logs:/var/logs/fineract:rw
# - ${PWD}/build/fineract/tmp:/tmp:rw
healthcheck:
test: ["CMD", 'sh', '-c', 'echo -e "Checking for the availability of Fineract server deployment"; while ! nc -z "localhost" 8443; do sleep 1; printf "-"; done; echo -e " >> Fineract server has started";' ]
timeout: 1s
+2 -1
View File
@@ -54,6 +54,7 @@ FINERACT_MANAGEMENT_ENDPOINT_WEB_EXPOSURE_INCLUDE=health,info,prometheus
FINERACT_MANAGEMENT_METRICS_TAGS_APPLICATION=fineract
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_SPRING_EVENTS_ENABLED=true
FINERACT_INSECURE_HTTP_CLIENT=true
FINERACT_CONTENT_FILESYSTEM_ROOT_FOLDER=/tmp
SPRING_PROFILES_ACTIVE=test,diagnostics
OTEL_SERVICE_NAME=fineract
JAVA_TOOL_OPTIONS="-Xmx1G -XX:MinRAMPercentage=25 -XX:MaxRAMPercentage=80 -XX:TieredStopAtLevel=1 -XX:+UseContainerSupport -XX:+UseStringDeduplication --add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED"
JAVA_TOOL_OPTIONS="-Xmx1G -XX:MinRAMPercentage=25 -XX:MaxRAMPercentage=80 -XX:TieredStopAtLevel=1 -XX:+UseContainerSupport -XX:+UseStringDeduplication -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5000 --add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED"
+12 -7
View File
@@ -17,11 +17,16 @@
# under the License.
#
KAFKA_CFG_NODE_ID=0
KAFKA_CFG_PROCESS_ROLES=controller,broker
KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093
KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka:9093
KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
ALLOW_PLAINTEXT_LISTENER=yes
KAFKA_NODE_ID=0
KAFKA_PROCESS_ROLES=controller,broker
KAFKA_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093
KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
KAFKA_CONTROLLER_QUORUM_VOTERS=0@kafka:9093
KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=1
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR=1
KAFKA_LOG_DIRS=/var/lib/kafka/data
KAFKA_BROKER_ID=0
+20
View File
@@ -0,0 +1,20 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
MARIADB_ROOT_PASSWORD=mysql
+3 -1
View File
@@ -50,8 +50,10 @@ scrape_configs:
# Override the global default and scrape targets from this job every 5 seconds.
scrape_interval: 5s
metrics_path: "/actuator/prometheus"
metrics_path: "/fineract-provider/actuator/prometheus"
scheme: "https"
tls_config:
insecure_skip_verify: true
static_configs:
- targets: ["fineract-server:8443"]
+4
View File
@@ -0,0 +1,4 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
@@ -20,8 +20,7 @@ package com.acme.fineract.event.starter;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
@AutoConfiguration
@ComponentScans({ @ComponentScan("com.acme.fineract.event.externalevent") })
@ComponentScan("com.acme.fineract.event.externalevent")
public class AcmeEventAutoConfiguration {}
@@ -26,11 +26,9 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
@AutoConfiguration
@ComponentScans({ @ComponentScan("com.acme.fineract.loan.cob"), @ComponentScan("com.acme.fineract.loan.processor"),
@ComponentScan("com.acme.fineract.loan.job") })
@ComponentScan(basePackages = { "com.acme.fineract.loan.cob", "com.acme.fineract.loan.processor", "com.acme.fineract.loan.job" })
@ConditionalOnProperty("acme.loan.enabled")
public class AcmeLoanAutoConfiguration {
@@ -51,7 +51,7 @@ public class AcmeBusinessStepDefinitions implements En {
});
When("/^The user retrieves the step service with step class (.*) and name (.*)$/", (String stepClass, String stepName) -> {
contextRunner.run((ctx) -> {
contextRunner.run(ctx -> {
this.businessStepService = ctx.getBean(COBBusinessStepService.class);
this.businessStep = ctx.getBean((Class<COBBusinessStep<Loan>>) Class.forName(stepClass));
@@ -64,7 +64,6 @@ public class AcmeBusinessStepDefinitions implements En {
Then("/^The step service should have a result$/", () -> {
assertThat(this.businessStep).isNotNull();
assertThat(this.result).isNotNull();
// log.warn(">>>>>>>>>>>>>>>>>> RESULT: {}", this.result);
});
}
}
@@ -19,9 +19,12 @@
package com.acme.fineract.portfolio.note.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.note.data.NoteCreateRequest;
import org.apache.fineract.portfolio.note.data.NoteCreateResponse;
import org.apache.fineract.portfolio.note.data.NoteDeleteRequest;
import org.apache.fineract.portfolio.note.data.NoteDeleteResponse;
import org.apache.fineract.portfolio.note.data.NoteUpdateRequest;
import org.apache.fineract.portfolio.note.data.NoteUpdateResponse;
import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -38,27 +41,17 @@ public class AcmeNoteWritePlatformService implements NoteWritePlatformService, I
}
@Override
public CommandProcessingResult createNote(JsonCommand command) {
public NoteCreateResponse createNote(NoteCreateRequest request) {
throw new UnsupportedOperationException("createNote() is not yet implemented.");
}
@Override
public void createLoanTransactionNote(Long loanTransactionId, String note) {
throw new UnsupportedOperationException("createLoanTransactionNote() is not yet implemented.");
}
@Override
public CommandProcessingResult updateNote(JsonCommand command) {
public NoteUpdateResponse updateNote(NoteUpdateRequest request) {
throw new UnsupportedOperationException("updateNote() is not yet implemented.");
}
@Override
public CommandProcessingResult deleteNote(JsonCommand command) {
public NoteDeleteResponse deleteNote(NoteDeleteRequest request) {
throw new UnsupportedOperationException("deleteNote() is not yet implemented.");
}
@Override
public void createAndPersistClientNote(Client client, JsonCommand command) {
log.warn("createAndPersistClientNote() is intentionally left empty and does nothing.");
}
}
@@ -21,7 +21,6 @@ package com.acme.fineract.portfolio.note.starter;
import static org.mockito.Mockito.mock;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.database.RoutingDataSource;
import org.apache.fineract.infrastructure.core.service.database.RoutingDataSourceServiceFactory;
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
@@ -29,7 +28,6 @@ import org.apache.fineract.portfolio.group.domain.GroupRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.note.domain.NoteRepository;
import org.apache.fineract.portfolio.note.serialization.NoteCommandFromApiJsonDeserializer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
@@ -38,11 +36,6 @@ import org.springframework.jdbc.core.JdbcTemplate;
public class TestDefaultConfiguration {
// NOTE: unfortunately an abastract base class that contains all these mock functions won't work
@Bean
public FromJsonHelper fromJsonHelper() {
return mock(FromJsonHelper.class);
}
@Bean
public RoutingDataSourceServiceFactory routingDataSourceServiceFactory() {
return mock(RoutingDataSourceServiceFactory.class);
@@ -82,9 +75,4 @@ public class TestDefaultConfiguration {
public LoanTransactionRepository loanTransactionRepository() {
return mock(LoanTransactionRepository.class);
}
@Bean
public NoteCommandFromApiJsonDeserializer fromApiJsonDeserializer(FromJsonHelper fromJsonHelper) {
return new NoteCommandFromApiJsonDeserializer(fromJsonHelper);
}
}
@@ -20,7 +20,6 @@ package com.acme.fineract.portfolio.note.starter;
import static org.mockito.Mockito.mock;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.database.RoutingDataSource;
import org.apache.fineract.infrastructure.core.service.database.RoutingDataSourceServiceFactory;
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
@@ -28,7 +27,6 @@ import org.apache.fineract.portfolio.group.domain.GroupRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.note.domain.NoteRepository;
import org.apache.fineract.portfolio.note.serialization.NoteCommandFromApiJsonDeserializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.jdbc.core.JdbcTemplate;
@@ -37,11 +35,6 @@ import org.springframework.jdbc.core.JdbcTemplate;
public class TestOverrideConfiguration {
// NOTE: unfortunately an abastract base class that contains all these mock functions won't work
@Bean
public FromJsonHelper fromJsonHelper() {
return mock(FromJsonHelper.class);
}
@Bean
public RoutingDataSourceServiceFactory routingDataSourceServiceFactory() {
return mock(RoutingDataSourceServiceFactory.class);
@@ -81,9 +74,4 @@ public class TestOverrideConfiguration {
public LoanTransactionRepository loanTransactionRepository() {
return mock(LoanTransactionRepository.class);
}
@Bean
public NoteCommandFromApiJsonDeserializer fromApiJsonDeserializer(FromJsonHelper fromJsonHelper) {
return new NoteCommandFromApiJsonDeserializer(fromJsonHelper);
}
}
+81
View File
@@ -0,0 +1,81 @@
-- Complete migration: Add all custom/enhanced fields to m_savings_product table
-- Uses IF NOT EXISTS to safely handle columns that may already exist
DO $$
BEGIN
-- Scalar custom columns
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='account_type') THEN
ALTER TABLE m_savings_product ADD COLUMN account_type VARCHAR(50) DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='customer_segment') THEN
ALTER TABLE m_savings_product ADD COLUMN customer_segment VARCHAR(50) DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='product_code') THEN
ALTER TABLE m_savings_product ADD COLUMN product_code VARCHAR(20) DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='effective_date') THEN
ALTER TABLE m_savings_product ADD COLUMN effective_date DATE DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='effective_to') THEN
ALTER TABLE m_savings_product ADD COLUMN effective_to DATE DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='end_date') THEN
ALTER TABLE m_savings_product ADD COLUMN end_date DATE DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='introducer_required') THEN
ALTER TABLE m_savings_product ADD COLUMN introducer_required BOOLEAN DEFAULT FALSE;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='share_linking_enabled') THEN
ALTER TABLE m_savings_product ADD COLUMN share_linking_enabled BOOLEAN DEFAULT FALSE;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='nomination_facility') THEN
ALTER TABLE m_savings_product ADD COLUMN nomination_facility BOOLEAN DEFAULT FALSE;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='dormancy_alert') THEN
ALTER TABLE m_savings_product ADD COLUMN dormancy_alert BOOLEAN DEFAULT FALSE;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='interest_calculate_selector') THEN
ALTER TABLE m_savings_product ADD COLUMN interest_calculate_selector INT DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='average_period') THEN
ALTER TABLE m_savings_product ADD COLUMN average_period VARCHAR(20) DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='auto_post') THEN
ALTER TABLE m_savings_product ADD COLUMN auto_post BOOLEAN DEFAULT FALSE;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='frequency_fix_day') THEN
ALTER TABLE m_savings_product ADD COLUMN frequency_fix_day INT DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='last_interest_date') THEN
ALTER TABLE m_savings_product ADD COLUMN last_interest_date DATE DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='enable_lockin_period') THEN
ALTER TABLE m_savings_product ADD COLUMN enable_lockin_period BOOLEAN DEFAULT FALSE;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='balance_frequency') THEN
ALTER TABLE m_savings_product ADD COLUMN balance_frequency VARCHAR(50) DEFAULT NULL;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='m_savings_product' AND column_name='account_closed_zero_balance_in_day_closing') THEN
ALTER TABLE m_savings_product ADD COLUMN account_closed_zero_balance_in_day_closing BOOLEAN DEFAULT FALSE;
END IF;
END $$;
-- Add comments for documentation (safe to run multiple times)
COMMENT ON COLUMN m_savings_product.account_type IS 'Account type classification';
COMMENT ON COLUMN m_savings_product.customer_segment IS 'Customer segment classification';
COMMENT ON COLUMN m_savings_product.product_code IS 'Unique product code identifier';
COMMENT ON COLUMN m_savings_product.effective_date IS 'Date from which the product becomes effective';
COMMENT ON COLUMN m_savings_product.effective_to IS 'Date until which the product is effective';
COMMENT ON COLUMN m_savings_product.end_date IS 'Product end/expiry date';
COMMENT ON COLUMN m_savings_product.introducer_required IS 'Whether an introducer is required for this product';
COMMENT ON COLUMN m_savings_product.share_linking_enabled IS 'Whether share linking is enabled for this product';
COMMENT ON COLUMN m_savings_product.nomination_facility IS 'Whether nomination facility is available';
COMMENT ON COLUMN m_savings_product.dormancy_alert IS 'Whether dormancy alerts are enabled';
COMMENT ON COLUMN m_savings_product.interest_calculate_selector IS 'Interest calculation selector type';
COMMENT ON COLUMN m_savings_product.average_period IS 'Average period for calculations';
COMMENT ON COLUMN m_savings_product.auto_post IS 'Whether auto posting of interest is enabled';
COMMENT ON COLUMN m_savings_product.frequency_fix_day IS 'Fixed day for frequency calculations';
COMMENT ON COLUMN m_savings_product.last_interest_date IS 'Last date interest was calculated';
COMMENT ON COLUMN m_savings_product.enable_lockin_period IS 'Whether lock-in period is enabled';
COMMENT ON COLUMN m_savings_product.balance_frequency IS 'Frequency for balance calculation (e.g., Daily, Monthly, Quarterly)';
COMMENT ON COLUMN m_savings_product.account_closed_zero_balance_in_day_closing IS 'Flag to indicate if account should be closed when balance reaches zero at day end';
@@ -0,0 +1,173 @@
-- ============================================================
-- Facility Charge Auto-Seed Migration
-- Step 1: Create mapping table
-- Step 2: Insert charges into m_charge
-- Step 3: Insert mapping with charge IDs
-- charge_applies_to_enum: 2=SAVINGS, 5=CURRENT_ACCOUNT
-- charge_time_enum: 3=SAVINGS_ACTIVATION, 6=ANNUAL_FEE, 7=MONTHLY_FEE
-- charge_calculation_enum: 1=FLAT
-- ============================================================
-- Step 1: Mapping table
CREATE TABLE IF NOT EXISTS m_facility_charge_mapping (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
facility_type VARCHAR(60) NOT NULL UNIQUE,
charge_id BIGINT,
charge_name VARCHAR(200) NOT NULL,
default_amount DECIMAL(19,6) NOT NULL DEFAULT 0.000000,
charge_time_type INT NOT NULL DEFAULT 3,
charge_applies_to INT NOT NULL DEFAULT 2,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
FOREIGN KEY (charge_id) REFERENCES m_charge(id)
);
-- Step 2: Insert charges into m_charge (only if not already exists)
-- income_or_liability_account_id = first available INCOME DETAIL GL account
INSERT IGNORE INTO m_charge
(name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted,
is_free_withdrawal, is_payment_type, income_or_liability_account_id)
SELECT 'ATM Facility Charge', 'INR', 100.00, 2, 3, 1, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'ATM Facility Charge' AND is_deleted = 0);
INSERT IGNORE INTO m_charge
(name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted,
is_free_withdrawal, is_payment_type, income_or_liability_account_id)
SELECT 'ATM Withdrawal Charge', 'INR', 20.00, 2, 3, 1, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'ATM Withdrawal Charge' AND is_deleted = 0);
INSERT IGNORE INTO m_charge
(name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted,
is_free_withdrawal, is_payment_type, income_or_liability_account_id)
SELECT 'UPI Facility Charge', 'INR', 100.00, 2, 3, 1, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'UPI Facility Charge' AND is_deleted = 0);
INSERT IGNORE INTO m_charge
(name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted,
is_free_withdrawal, is_payment_type, income_or_liability_account_id)
SELECT 'POS Transaction Charge', 'INR', 100.00, 2, 3, 1, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'POS Transaction Charge' AND is_deleted = 0);
INSERT IGNORE INTO m_charge
(name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted,
is_free_withdrawal, is_payment_type, income_or_liability_account_id)
SELECT 'Cheque Book Charge', 'INR', 300.00, 2, 3, 1, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'Cheque Book Charge' AND is_deleted = 0);
INSERT IGNORE INTO m_charge
(name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted,
is_free_withdrawal, is_payment_type, income_or_liability_account_id)
SELECT 'IMPS Facility Charge', 'INR', 5.00, 2, 3, 1, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'IMPS Facility Charge' AND is_deleted = 0);
INSERT IGNORE INTO m_charge
(name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted,
is_free_withdrawal, is_payment_type, income_or_liability_account_id)
SELECT 'SMS Alert Charge', 'INR', 15.00, 2, 7, 1, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'SMS Alert Charge' AND is_deleted = 0);
INSERT IGNORE INTO m_charge
(name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted,
is_free_withdrawal, is_payment_type, income_or_liability_account_id)
SELECT 'BBPS Facility Charge', 'INR', 2.00, 2, 3, 1, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'BBPS Facility Charge' AND is_deleted = 0);
INSERT IGNORE INTO m_charge
(name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted,
is_free_withdrawal, is_payment_type, income_or_liability_account_id)
SELECT 'Insurance Cover Charge', 'INR', 500.00, 2, 6, 1, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'Insurance Cover Charge' AND is_deleted = 0);
INSERT IGNORE INTO m_charge
(name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted,
is_free_withdrawal, is_payment_type, income_or_liability_account_id)
SELECT 'BEPG Facility Charge', 'INR', 5.00, 2, 3, 1, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'BEPG Facility Charge' AND is_deleted = 0);
INSERT IGNORE INTO m_charge
(name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted,
is_free_withdrawal, is_payment_type, income_or_liability_account_id)
SELECT 'NEFT Facility Charge', 'INR', 5.00, 2, 3, 1, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'NEFT Facility Charge' AND is_deleted = 0);
INSERT IGNORE INTO m_charge
(name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted,
is_free_withdrawal, is_payment_type, income_or_liability_account_id)
SELECT 'RTGS Facility Charge', 'INR', 25.00, 2, 3, 1, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'RTGS Facility Charge' AND is_deleted = 0);
INSERT IGNORE INTO m_charge
(name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted,
is_free_withdrawal, is_payment_type, income_or_liability_account_id)
SELECT 'Inter-Bank Cap Charge', 'INR', 0.00, 2, 3, 1, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'Inter-Bank Cap Charge' AND is_deleted = 0);
INSERT IGNORE INTO m_charge
(name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted,
is_free_withdrawal, is_payment_type, income_or_liability_account_id)
SELECT 'Withdrawal Facility Charge', 'INR', 5.00, 2, 3, 1, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'Withdrawal Facility Charge' AND is_deleted = 0);
INSERT IGNORE INTO m_charge
(name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted,
is_free_withdrawal, is_payment_type, income_or_liability_account_id)
SELECT 'Deposit KYC Charge', 'INR', 5.00, 2, 3, 1, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'Deposit KYC Charge' AND is_deleted = 0);
INSERT IGNORE INTO m_charge
(name, currency_code, amount, charge_applies_to_enum, charge_time_enum,
charge_calculation_enum, is_penalty, is_active, is_deleted,
is_free_withdrawal, is_payment_type, income_or_liability_account_id)
SELECT 'Virtual Debit Card Charge', 'INR', 199.00, 2, 3, 1, 0, 1, 0, 0, 0,
(SELECT id FROM acc_gl_account WHERE classification_enum = 4 AND usage_enum = 1 AND disabled = 0 ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM m_charge WHERE name = 'Virtual Debit Card Charge' AND is_deleted = 0);
-- Step 3: Insert mapping linking facility_type → charge_id
INSERT IGNORE INTO m_facility_charge_mapping
(facility_type, charge_name, default_amount, charge_time_type, charge_applies_to, charge_id)
VALUES
('ATM_FACILITY', 'ATM Facility Charge', 100.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'ATM Facility Charge' AND is_deleted = 0 LIMIT 1)),
('ATM_WITHDRAWAL', 'ATM Withdrawal Charge', 20.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'ATM Withdrawal Charge' AND is_deleted = 0 LIMIT 1)),
('UPI_FACILITY', 'UPI Facility Charge', 100.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'UPI Facility Charge' AND is_deleted = 0 LIMIT 1)),
('POS_TRANSACTIONS', 'POS Transaction Charge', 100.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'POS Transaction Charge' AND is_deleted = 0 LIMIT 1)),
('CHEQUE_BOOK', 'Cheque Book Charge', 300.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'Cheque Book Charge' AND is_deleted = 0 LIMIT 1)),
('IMPS_FACILITY', 'IMPS Facility Charge', 5.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'IMPS Facility Charge' AND is_deleted = 0 LIMIT 1)),
('SMS_ALERTS', 'SMS Alert Charge', 15.00, 7, 2, (SELECT id FROM m_charge WHERE name = 'SMS Alert Charge' AND is_deleted = 0 LIMIT 1)),
('BBPS_FACILITY', 'BBPS Facility Charge', 2.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'BBPS Facility Charge' AND is_deleted = 0 LIMIT 1)),
('INSURANCE_COVER', 'Insurance Cover Charge', 500.00, 6, 2, (SELECT id FROM m_charge WHERE name = 'Insurance Cover Charge' AND is_deleted = 0 LIMIT 1)),
('BEPG_FACILITY', 'BEPG Facility Charge', 5.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'BEPG Facility Charge' AND is_deleted = 0 LIMIT 1)),
('NEFT_FACILITY', 'NEFT Facility Charge', 5.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'NEFT Facility Charge' AND is_deleted = 0 LIMIT 1)),
('RTGS_FACILITY', 'RTGS Facility Charge', 25.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'RTGS Facility Charge' AND is_deleted = 0 LIMIT 1)),
('INTER_BANK_TOTAL_CAP','Inter-Bank Cap Charge', 0.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'Inter-Bank Cap Charge' AND is_deleted = 0 LIMIT 1)),
('WITHDRAWAL_FACILITY', 'Withdrawal Facility Charge', 5.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'Withdrawal Facility Charge' AND is_deleted = 0 LIMIT 1)),
('DEPOSIT_FACILITY_KYC','Deposit KYC Charge', 5.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'Deposit KYC Charge' AND is_deleted = 0 LIMIT 1)),
('VIRTUAL_DEBIT_CARD', 'Virtual Debit Card Charge', 199.00, 3, 2, (SELECT id FROM m_charge WHERE name = 'Virtual Debit Card Charge' AND is_deleted = 0 LIMIT 1));
+79
View File
@@ -0,0 +1,79 @@
-- Migration: Add facility charge columns to product facility config tables
-- Each facility (ATM, UPI, IMPS, etc.) links to an existing m_charge entry
-- When account is activated, enabled facilities auto-apply their linked charge
-- Current Product Facility Config
ALTER TABLE m_current_product_facility_config
ADD COLUMN IF NOT EXISTS atm_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS atm_withdrawal_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS upi_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS pos_transactions_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS cheque_book_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS imps_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS sms_alerts_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS bbps_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS insurance_cover_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS bepg_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS neft_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS rtgs_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS inter_bank_total_cap_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS withdrawal_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS deposit_facility_kyc_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS virtual_debit_card_charge_id BIGINT NULL;
-- Foreign key constraints for current product
ALTER TABLE m_current_product_facility_config
ADD CONSTRAINT fk_cpfc_atm_charge FOREIGN KEY (atm_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_atm_wd_charge FOREIGN KEY (atm_withdrawal_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_upi_charge FOREIGN KEY (upi_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_pos_charge FOREIGN KEY (pos_transactions_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_chq_charge FOREIGN KEY (cheque_book_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_imps_charge FOREIGN KEY (imps_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_sms_charge FOREIGN KEY (sms_alerts_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_bbps_charge FOREIGN KEY (bbps_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_ins_charge FOREIGN KEY (insurance_cover_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_bepg_charge FOREIGN KEY (bepg_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_neft_charge FOREIGN KEY (neft_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_rtgs_charge FOREIGN KEY (rtgs_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_ibtc_charge FOREIGN KEY (inter_bank_total_cap_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_wd_charge FOREIGN KEY (withdrawal_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_dep_charge FOREIGN KEY (deposit_facility_kyc_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_cpfc_vdc_charge FOREIGN KEY (virtual_debit_card_charge_id) REFERENCES m_charge(id);
-- Savings Product Facility Config
ALTER TABLE m_savings_product_facility_config
ADD COLUMN IF NOT EXISTS atm_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS atm_withdrawal_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS upi_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS pos_transactions_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS cheque_book_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS imps_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS sms_alerts_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS bbps_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS insurance_cover_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS bepg_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS neft_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS rtgs_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS inter_bank_total_cap_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS withdrawal_facility_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS deposit_facility_kyc_charge_id BIGINT NULL,
ADD COLUMN IF NOT EXISTS virtual_debit_card_charge_id BIGINT NULL;
-- Foreign key constraints for savings product
ALTER TABLE m_savings_product_facility_config
ADD CONSTRAINT fk_spfc_atm_charge FOREIGN KEY (atm_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_atm_wd_charge FOREIGN KEY (atm_withdrawal_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_upi_charge FOREIGN KEY (upi_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_pos_charge FOREIGN KEY (pos_transactions_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_chq_charge FOREIGN KEY (cheque_book_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_imps_charge FOREIGN KEY (imps_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_sms_charge FOREIGN KEY (sms_alerts_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_bbps_charge FOREIGN KEY (bbps_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_ins_charge FOREIGN KEY (insurance_cover_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_bepg_charge FOREIGN KEY (bepg_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_neft_charge FOREIGN KEY (neft_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_rtgs_charge FOREIGN KEY (rtgs_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_ibtc_charge FOREIGN KEY (inter_bank_total_cap_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_wd_charge FOREIGN KEY (withdrawal_facility_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_dep_charge FOREIGN KEY (deposit_facility_kyc_charge_id) REFERENCES m_charge(id),
ADD CONSTRAINT fk_spfc_vdc_charge FOREIGN KEY (virtual_debit_card_charge_id) REFERENCES m_charge(id);
-1
View File
@@ -16,7 +16,6 @@
# under the License.
#
version: "3.8"
services:
# Frontend service
community-app:
-1
View File
@@ -17,7 +17,6 @@
#
# You can replace and test a more recent version of docker compose
version: "3.8"
services:
db:
extends:
+4 -2
View File
@@ -27,8 +27,6 @@ x-logging: &default-logging
- regex:
expression: '^(?P<date>\d{4}-\d{2}-\d{2}) (?P<time>\d{2}:\d{2}:\d{2}.\d{3}) (?P<message>(?s:.*))$$'
version: "3.8"
services:
loki:
extends:
@@ -88,3 +86,7 @@ services:
# - ./config/docker/env/cloudwatch.env
- ./config/docker/env/debug.env
logging: *default-logging
networks:
default:
aliases:
- fineract-server
+34
View File
@@ -0,0 +1,34 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
services:
mariadb:
container_name: mariadb
image: mariadb:11.4
volumes:
- ./config/docker/mysql/conf.d/server_collation.cnf:/etc/mysql/conf.d/server_collation.cnf:ro
- ./config/docker/mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d:Z,ro
restart: always
env_file:
- ./config/docker/env/mariadb.env
healthcheck:
test: [ "CMD", "healthcheck.sh", "--su-mysql", "--connect", "--innodb_initialized" ]
timeout: 10s
retries: 10
ports:
- "3306:3306"
+34
View File
@@ -0,0 +1,34 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
services:
mysql:
container_name: mysql
image: mysql:8
volumes:
- ${PWD}/config/docker/mysql/conf.d/server_collation.cnf:/etc/mysql/conf.d/server_collation.cnf:ro
- ${PWD}/config/docker/mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d:Z,ro
restart: always
env_file:
- ${PWD}/config/docker/env/mysql.env
healthcheck:
test: [ "CMD", "healthcheck.sh", "--su-mysql", "--connect", "--innodb_initialized" ]
timeout: 10s
retries: 10
ports:
- "3306:3306"
+1 -1
View File
@@ -20,7 +20,7 @@
version: "3.7"
services:
kafka:
image: "bitnamilegacy/kafka:4.0.0-debian-12-r10"
image: "apache/kafka:4.1.1-rc2"
ports:
- "9092:9092"
env_file:
+1 -2
View File
@@ -16,8 +16,6 @@
# under the License.
#
version: "3.8"
services:
db:
@@ -31,6 +29,7 @@ services:
service: fineract
ports:
- "8443:8443"
- "5000:5000"
depends_on:
db:
condition: service_healthy
+51
View File
@@ -0,0 +1,51 @@
/*
* Builds the cbsnexora-dsc-helper.jar consumed by the JNLP from /api/v1/dsc/helper.jar.
*
* Targets Java 1.8 so the broadest set of client JREs (incl. legacy OpenWebStart installs)
* can launch the helper. The JAR is a "fat" jar with Gson shaded in — JNLP <resources> can only
* list one <jar href="..."> cleanly, and avoiding network round-trips for tiny Gson is a win.
*/
plugins {
id 'java'
}
group = 'com.cbsnexora'
version = '1.0.0'
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.google.code.gson:gson:2.10.1'
}
jar {
archiveBaseName = 'cbsnexora-dsc-helper'
archiveClassifier = ''
manifest {
attributes(
'Main-Class' : 'com.cbsnexora.dsc.CbsnexoraDscHelper',
'Permissions' : 'all-permissions',
'Application-Name' : 'CBSnexora DSC Helper',
'Codebase' : '*',
'Implementation-Version': project.version
)
}
// Shade Gson into the same jar so JNLP only needs one <jar> entry.
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
// Strip signatures from shaded deps so re-signing the fat jar is clean.
exclude 'META-INF/*.SF'
exclude 'META-INF/*.DSA'
exclude 'META-INF/*.RSA'
}
+113
View File
@@ -0,0 +1,113 @@
# Generate a realistic self-signed DSC for testing the CBSnexora helper.
#
# Replaces the UUID-CN bare-bones dev cert with one that has:
# - Real-looking subject DN: CN=Test User, O=CBSnexora Pvt Ltd, OU=IT, L=Mumbai, ST=Maharashtra, C=IN
# - KeyUsage = DigitalSignature (matches real Indian Class 3 DSCs)
# - Strong private-key protection enabled — Windows pops a PIN dialog at creation time, then
# prompts you for that PIN on every signing operation (matches the UX of a real USB token).
#
# Run from PowerShell (no elevation needed; user store only):
# .\generate-test-dsc.ps1
#
# Why certreq + INF instead of New-SelfSignedCertificate:
# New-SelfSignedCertificate (Windows PowerShell 5.x) doesn't expose ProtectKey / strong-protection
# knobs — it creates a CNG/KSP key that won't prompt. certreq.exe with an INF using the legacy
# "Microsoft Enhanced RSA and AES Cryptographic Provider" CSP IS the supported path for forcing
# user-confirmation on key access.
$ErrorActionPreference = "Stop"
Write-Host "=== Step 1: Clean up old UUID-CN test certs from Windows-MY ===" -ForegroundColor Cyan
$old = Get-ChildItem Cert:\CurrentUser\My | Where-Object {
$_.Subject -match "^CN=[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"
}
if ($old) {
Write-Host "Found $($old.Count) old UUID-CN cert(s) - removing:" -ForegroundColor Yellow
foreach ($c in $old) {
Write-Host " $($c.Thumbprint) $($c.Subject)"
Remove-Item "Cert:\CurrentUser\My\$($c.Thumbprint)" -Force
}
} else {
Write-Host "No old UUID-CN test certs in the user store."
}
Write-Host ""
Write-Host "=== Step 2: Build INF for certreq ===" -ForegroundColor Cyan
# Random container name avoids collisions when the script is re-run before old containers expire.
$containerName = "CBSnexora-DSC-Test-$(Get-Random -Maximum 999999)"
$inf = @"
[Version]
Signature = "`$Windows NT`$"
[NewRequest]
Subject = "CN=Test User, OU=IT Department, O=CBSnexora Pvt Ltd, L=Mumbai, ST=Maharashtra, C=IN"
KeyAlgorithm = RSA
KeyLength = 2048
KeyUsage = "CERT_DIGITAL_SIGNATURE_KEY_USAGE"
MachineKeySet = false
KeyContainer = "$containerName"
ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider"
KeySpec = AT_SIGNATURE
ProtectKey = true
SMIME = false
RequestType = Cert
Exportable = false
HashAlgorithm = SHA256
ValidityPeriod = Years
ValidityPeriodUnits = 2
[Extensions]
2.5.29.37 = "{text}1.3.6.1.5.5.7.3.4"
"@
$infPath = Join-Path $env:TEMP "cbsnexora-test-dsc.inf"
$cerPath = Join-Path $env:TEMP "cbsnexora-test-dsc.cer"
$inf | Out-File -FilePath $infPath -Encoding ASCII -Force
Write-Host "INF written to $infPath"
Write-Host ""
Write-Host "=== Step 3: certreq -new ===" -ForegroundColor Cyan
Write-Host "WINDOWS WILL POP A DIALOG asking 'Creating an RSA exchange key':" -ForegroundColor Yellow
Write-Host " -> Click 'Set Security Level...'" -ForegroundColor Yellow
Write-Host " -> Select 'High'" -ForegroundColor Yellow
Write-Host " -> Click 'Next', then set a PIN you'll remember" -ForegroundColor Yellow
Write-Host " -> Click 'Finish' then 'OK'" -ForegroundColor Yellow
Write-Host ""
& certreq.exe -new $infPath $cerPath
if ($LASTEXITCODE -ne 0) {
Write-Error "certreq -new failed with exit code $LASTEXITCODE"
exit $LASTEXITCODE
}
Write-Host ""
Write-Host "=== Step 4: New cert summary ===" -ForegroundColor Cyan
$new = Get-ChildItem Cert:\CurrentUser\My | Where-Object {
$_.Subject -like "*CN=Test User*"
} | Sort-Object NotBefore -Descending | Select-Object -First 1
if ($new) {
Write-Host "Subject: $($new.Subject)" -ForegroundColor Green
Write-Host "Issuer: $($new.Issuer)"
Write-Host "Serial: $($new.SerialNumber)"
Write-Host "Thumbprint: $($new.Thumbprint)"
Write-Host "Valid From: $($new.NotBefore)"
Write-Host "Valid Until: $($new.NotAfter)"
Write-Host "KeyUsage: $($new.Extensions | Where-Object { $_.Oid.Value -eq '2.5.29.15' } | ForEach-Object { $_.KeyUsages })"
} else {
Write-Warning "Cert created but not findable - check certmgr.msc -> Personal -> Certificates"
}
# Cleanup temp files
Remove-Item $infPath -Force -ErrorAction SilentlyContinue
Remove-Item $cerPath -Force -ErrorAction SilentlyContinue
Write-Host ""
Write-Host "=== Done ===" -ForegroundColor Green
Write-Host "Next:"
Write-Host " 1. Clear JWS cache:"
Write-Host " Remove-Item -Recurse -Force `"`$env:USERPROFILE\.cache\icedtea-web`""
Write-Host " 2. Trigger DSC enroll from the web UI."
Write-Host " 3. Pick 'Test User' from the cert table - now shows real Holder Name and Organisation."
Write-Host " 4. Windows pops the PIN prompt; enter the PIN you set above. Helper proceeds to enroll."
+21
View File
@@ -0,0 +1,21 @@
/*
* Standalone Gradle project for the CBSnexora DSC helper JAR. Intentionally NOT included in the
* root fineract settings.gradle — different Java target (1.8) and a tiny dependency tree.
*
* Build:
* cd dsc-helper && gradle clean build
* → dsc-helper/build/libs/cbsnexora-dsc-helper.jar (fat jar with Gson embedded)
*
* Sign (self-signed for now; replace cert later without rebuilding):
* keytool -genkeypair -alias cbsnexora -keyalg RSA -keysize 2048 \
* -validity 730 -keystore cbsnexora-keystore.jks
* jarsigner -keystore cbsnexora-keystore.jks build/libs/cbsnexora-dsc-helper.jar cbsnexora
*
* Deploy:
* cp build/libs/cbsnexora-dsc-helper.jar ~/.cbsnexora/dsc-helper.jar
* (path is configurable via fineract.dsc.helper.jar.path; default is ~/.cbsnexora/dsc-helper.jar)
*
* Fineract will then stream it on demand at GET /api/v1/dsc/helper.jar — the JNLP <jar href="..."/>
* already points at that URL.
*/
rootProject.name = 'cbsnexora-dsc-helper'
@@ -0,0 +1,178 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cbsnexora.dsc;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Window;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.SwingConstants;
/**
* Shared branding chrome for every CBSnexora DSC helper window — logo header bar, primary/secondary
* button styling, palette constants. Centralised so the look is identical across the token picker
* and the certificate selection screen, and so a future logo swap is one file change.
*/
final class Branding {
/** Brand colours sampled from cbsnexora-logo.png. */
static final Color ACCENT_BLUE = new Color(0x2BAEEC);
static final Color ACCENT_BLUE_DARK = new Color(0x1E8FC4);
static final Color TITLE_DARK = new Color(0x1F2D3D);
static final Color SUBTITLE_GREY = new Color(0x6B7280);
static final Color HEADER_BG = Color.WHITE;
static final Color BODY_BG = new Color(0xF7F9FC);
static final Color DIVIDER = new Color(0xE2E8F0);
static final Color BUTTON_NEUTRAL_BG = new Color(0xF1F5F9);
static final Color BUTTON_NEUTRAL_FG = new Color(0x334155);
private static final String LOGO_RESOURCE = "/cbsnexora-logo.png";
private static final int HEADER_LOGO_HEIGHT = 36;
private Branding() {
}
/**
* Header bar: logo on the left, title + subtitle in the middle, a thin divider at the bottom.
* Title falls back to a plain text wordmark if the logo resource can't be loaded — keeps the
* UI usable on a stripped-down JRE that didn't bundle the resources jar entry for some reason.
*/
static JPanel buildHeader(final String title, final String subtitle) {
final JPanel header = new JPanel(new BorderLayout());
header.setBackground(HEADER_BG);
header.setBorder(BorderFactory.createEmptyBorder(14, 18, 14, 18));
final JPanel row = new JPanel(new GridBagLayout());
row.setOpaque(false);
final GridBagConstraints g = new GridBagConstraints();
g.gridy = 0;
g.anchor = GridBagConstraints.WEST;
g.insets = new Insets(0, 0, 0, 14);
final JLabel logo = loadLogo();
if (logo != null) {
g.gridx = 0;
row.add(logo, g);
}
final JPanel text = new JPanel();
text.setOpaque(false);
text.setLayout(new javax.swing.BoxLayout(text, javax.swing.BoxLayout.Y_AXIS));
final JLabel titleLabel = new JLabel(title != null ? title : "CBSnexora");
titleLabel.setForeground(TITLE_DARK);
titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD, 16f));
titleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
text.add(titleLabel);
if (subtitle != null && !subtitle.isEmpty()) {
final JLabel sub = new JLabel(subtitle);
sub.setForeground(SUBTITLE_GREY);
sub.setFont(sub.getFont().deriveFont(Font.PLAIN, 12f));
sub.setAlignmentX(Component.LEFT_ALIGNMENT);
sub.setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0));
text.add(sub);
}
g.gridx = 1;
g.insets = new Insets(0, 0, 0, 0);
g.weightx = 1.0;
g.fill = GridBagConstraints.HORIZONTAL;
row.add(text, g);
header.add(row, BorderLayout.CENTER);
final JSeparator divider = new JSeparator(SwingConstants.HORIZONTAL);
divider.setForeground(DIVIDER);
divider.setBackground(DIVIDER);
header.add(divider, BorderLayout.SOUTH);
return header;
}
/** Primary CTA — solid accent fill, white text. */
static JButton primaryButton(final String text) {
final JButton b = new JButton(text);
b.setBackground(ACCENT_BLUE);
b.setForeground(Color.WHITE);
b.setFocusPainted(false);
b.setBorderPainted(false);
b.setOpaque(true);
b.setFont(b.getFont().deriveFont(Font.BOLD, 13f));
b.setBorder(BorderFactory.createEmptyBorder(8, 18, 8, 18));
b.addPropertyChangeListener("enabled", e -> {
b.setBackground(b.isEnabled() ? ACCENT_BLUE : ACCENT_BLUE_DARK.darker());
b.setForeground(b.isEnabled() ? Color.WHITE : new Color(0xD0D5DB));
});
return b;
}
/** Secondary button — light fill, slate text. Used for Cancel and the like. */
static JButton secondaryButton(final String text) {
final JButton b = new JButton(text);
b.setBackground(BUTTON_NEUTRAL_BG);
b.setForeground(BUTTON_NEUTRAL_FG);
b.setFocusPainted(false);
b.setBorderPainted(false);
b.setOpaque(true);
b.setFont(b.getFont().deriveFont(Font.PLAIN, 13f));
b.setBorder(BorderFactory.createEmptyBorder(8, 18, 8, 18));
return b;
}
/** Set the same logo as the window icon so the taskbar / titlebar stays branded. */
static void applyWindowIcon(final Window window) {
final URL url = Branding.class.getResource(LOGO_RESOURCE);
if (url == null) {
return;
}
try {
window.setIconImage(ImageIO.read(url));
} catch (final IOException ignored) {
// Non-fatal — falls back to the JVM default icon.
}
}
private static JLabel loadLogo() {
final URL url = Branding.class.getResource(LOGO_RESOURCE);
if (url == null) {
return null;
}
try {
final Image raw = ImageIO.read(url);
if (raw == null) {
return null;
}
// Scale to a fixed header height preserving aspect ratio. SCALE_SMOOTH for the text in
// the wordmark — the bitmap render is otherwise legibly poor at small heights.
final int targetH = HEADER_LOGO_HEIGHT;
final int targetW = Math.max(1, raw.getWidth(null) * targetH / Math.max(1, raw.getHeight(null)));
final Image scaled = raw.getScaledInstance(targetW, targetH, Image.SCALE_SMOOTH);
return new JLabel(new ImageIcon(scaled));
} catch (final IOException e) {
return null;
}
}
}
@@ -0,0 +1,622 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cbsnexora.dsc;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* Entry point launched by Java Web Start. Reads its single argument as JSON:
* <pre>
* {
* "randomNo": "...",
* "mode": "ENROLL" | "SIGN_CHALLENGE",
* "callbackUrl": "https://.../api/v1/dsc/{enroll|login/dsc}/{rn}/{callback|verify}",
* "challenge": "<hex>", // SIGN_CHALLENGE only
* "expectedSerial": "<hex>", // optional — locks selection to a single cert
* "title": "...",
* "codebase": "..."
* }
* </pre>
*
* <p>Windows uses the OS keystore ({@code Windows-MY}). Linux/Mac falls back to a PKCS11 token
* driver the user points at via {@link TokenFrame}.
*
* <p>Server is the source of truth for what gets persisted — this helper extracts and POSTs;
* the server parses, validates, and stores.
*/
public final class CbsnexoraDscHelper {
/** A freshly-launched JNLP must be less than this many minutes old. Prevents stale-link reuse. */
private static final long MAX_LINK_AGE_MINUTES = 5;
private static HelperArgs cfg;
private static KeyStore keyStore;
private static Provider pkcs11Provider; // Linux/Mac
public static void main(final String[] args) {
try {
if (!validateJnlpAge()) {
showError("This signing link has expired. Please request a fresh one from the portal.",
"Link Expired");
System.exit(1);
}
if (args == null || args.length < 1 || args[0] == null || args[0].isEmpty()) {
showError("Helper launched without arguments.", "Bad Launch");
System.exit(1);
}
cfg = HelperArgs.parse(args[0]);
// Token picker is now shown on every OS — Windows users can pick either the OS-native
// certificate store or a PKCS11 .dll for vendor-specific USB tokens (ePass2003, eToken,
// ProxKey, etc.). Linux / macOS only see PKCS11 options.
SwingUtilities.invokeLater(CbsnexoraDscHelper::launchTokenPicker);
} catch (final Exception e) {
e.printStackTrace();
showError("Unexpected error: " + e.getMessage(), "Error");
System.exit(1);
}
}
// ── Token picker (cross-platform) ───────────────────────────────────────
private static void launchTokenPicker() {
final TokenFrame tokenFrame = new TokenFrame();
tokenFrame.setProceedAction(selection -> {
try {
if (TokenFrame.SELECTION_WINDOWS_MY.equals(selection)) {
// OS-native keystore — no PIN dialog needed; the platform handles authn.
keyStore = KeyStore.getInstance("Windows-MY");
keyStore.load(null, null);
} else {
// PKCS11 library — drives a Swing PIN dialog via the callback handler.
initializePkcs11(selection);
keyStore = loadPkcs11KeyStore();
if (keyStore == null) {
showError("Failed to open token keystore. Wrong PIN?", "Login Failed");
return;
}
}
tokenFrame.dispose();
showCertSelectionWindow();
} catch (final Exception e) {
e.printStackTrace();
showError("Could not open keystore: " + e.getMessage(), "Token Error");
}
});
tokenFrame.setVisible(true);
}
private static void initializePkcs11(final String libraryPath) throws Exception {
final String config = "name = CBSnexoraDSC\nlibrary = " + libraryPath + "\n";
final ByteArrayInputStream in = new ByteArrayInputStream(config.getBytes(StandardCharsets.UTF_8));
try {
// Java 9+
final Class<?> sunPkcs11 = Class.forName("sun.security.pkcs11.SunPKCS11");
pkcs11Provider = (Provider) sunPkcs11.getConstructor(InputStream.class).newInstance(in);
} catch (final ClassNotFoundException e) {
// Fallback — same path; preserved for old JREs that resolve the class differently.
pkcs11Provider = (Provider) Class.forName("sun.security.pkcs11.SunPKCS11")
.getConstructor(InputStream.class).newInstance(in);
}
Security.removeProvider(pkcs11Provider.getName());
Security.addProvider(pkcs11Provider);
}
private static KeyStore loadPkcs11KeyStore() throws Exception {
final AuthProvider authProvider = (AuthProvider) pkcs11Provider;
final KeyStore.CallbackHandlerProtection protection = new KeyStore.CallbackHandlerProtection(
new SwingPasswordCallbackHandler());
final KeyStore.Builder builder = KeyStore.Builder.newInstance("PKCS11", authProvider, protection);
return builder.getKeyStore();
}
// ── Certificate selection UI (shared between Windows and PKCS11) ────────
private static void showCertSelectionWindow() {
final String windowTitle = cfg.title != null ? cfg.title : "Select Digital Signature Certificate";
final JFrame frame = new JFrame("CBSnexora — " + windowTitle);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Branding.applyWindowIcon(frame);
frame.setLayout(new BorderLayout());
frame.getContentPane().setBackground(Branding.BODY_BG);
final String subtitle = "ENROLL".equals(cfg.mode)
? "Pick the certificate to map to your CBSnexora account."
: "Pick the certificate to sign the login challenge.";
frame.add(Branding.buildHeader(windowTitle, subtitle), BorderLayout.NORTH);
final DefaultTableModel model = new DefaultTableModel(
new Object[] { "Holder Name", "Alias", "Organisation", "DSC Type", "Serial No",
"Issuer (CA)", "Valid From", "Expires" }, 0) {
@Override
public boolean isCellEditable(final int row, final int column) {
return false;
}
};
final JTable table = new JTable(model);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setRowHeight(26);
table.setGridColor(Branding.DIVIDER);
table.setShowVerticalLines(false);
table.getTableHeader().setFont(table.getTableHeader().getFont().deriveFont(java.awt.Font.BOLD, 12f));
table.getTableHeader().setBackground(Branding.HEADER_BG);
table.getTableHeader().setForeground(Branding.TITLE_DARK);
// Holder Name / Alias / Organisation / DSC Type / Serial / Issuer / Valid From / Expires.
// Alias is the keystore label (Windows friendly name or auto-generated handle) — useful when
// the CN is opaque (UUID test cert, hashed-CN tokens) but the user has set a recognisable
// friendly name in certmgr.msc.
final int[] widths = { 160, 130, 160, 110, 150, 210, 100, 100 };
for (int i = 0; i < widths.length; i++) {
table.getColumnModel().getColumn(i).setPreferredWidth(widths[i]);
}
final SimpleDateFormat dateFmt = new SimpleDateFormat("dd-MMM-yyyy");
// Fully qualified — java.awt.List exists too and collides with the wildcard import.
final java.util.List<String> aliasList = new ArrayList<>();
try {
final Enumeration<String> en = keyStore.aliases();
while (en.hasMoreElements()) {
final String alias = en.nextElement();
final java.security.cert.Certificate cert = keyStore.getCertificate(alias);
if (!(cert instanceof X509Certificate)) {
continue;
}
final X509Certificate x = (X509Certificate) cert;
final String serial = x.getSerialNumber().toString(16).toUpperCase(Locale.ROOT);
// SIGN_CHALLENGE with expectedSerial → only show the one cert the server expects.
if ("SIGN_CHALLENGE".equals(cfg.mode) && cfg.expectedSerial != null
&& !cfg.expectedSerial.equalsIgnoreCase(serial)) {
continue;
}
final String subjectDn = x.getSubjectX500Principal().getName();
model.addRow(new Object[] {
extractCN(subjectDn),
alias,
extractRdn(subjectDn, "O"),
extractDscType(x),
serial,
extractCN(x.getIssuerX500Principal().getName()),
dateFmt.format(x.getNotBefore()),
dateFmt.format(x.getNotAfter())
});
aliasList.add(alias);
}
} catch (final KeyStoreException e) {
e.printStackTrace();
showError("Could not read certificates: " + e.getMessage(), "Keystore Error");
return;
}
if (model.getRowCount() == 0) {
showError("SIGN_CHALLENGE".equals(cfg.mode)
? "No DSC matching the expected serial was found in this keystore."
: "No certificates were found in the keystore.", "Nothing to Sign");
System.exit(1);
}
final JButton proceed = Branding.primaryButton(
cfg.mode.equals("ENROLL") ? "Map Selected DSC" : "Sign with Selected DSC");
proceed.setEnabled(false);
table.getSelectionModel().addListSelectionListener(e -> proceed.setEnabled(table.getSelectedRow() >= 0));
final JButton cancel = Branding.secondaryButton("Cancel");
cancel.addActionListener(e -> System.exit(0));
proceed.addActionListener(e -> {
final int row = table.getSelectedRow();
if (row < 0) {
return;
}
final String alias = aliasList.get(row);
try {
if ("ENROLL".equals(cfg.mode)) {
performEnroll(alias);
} else if ("SIGN_CHALLENGE".equals(cfg.mode)) {
performSignChallenge(alias);
} else {
showError("Unknown mode: " + cfg.mode, "Bad Mode");
return;
}
JOptionPane.showMessageDialog(frame, "Done. You can close this window and return to the browser.",
"Success", JOptionPane.INFORMATION_MESSAGE);
frame.dispose();
System.exit(0);
} catch (final Exception ex) {
ex.printStackTrace();
showError("Operation failed: " + ex.getMessage(), "Error");
}
});
final JPanel buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 14));
buttons.setBackground(Branding.BODY_BG);
buttons.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 18, 10, 18));
buttons.add(cancel);
buttons.add(proceed);
final JScrollPane scroll = new JScrollPane(table);
scroll.getViewport().setBackground(Color.WHITE);
scroll.setBorder(javax.swing.BorderFactory.createCompoundBorder(
javax.swing.BorderFactory.createEmptyBorder(12, 18, 6, 18),
javax.swing.BorderFactory.createLineBorder(Branding.DIVIDER)));
frame.add(scroll, BorderLayout.CENTER);
frame.add(buttons, BorderLayout.SOUTH);
frame.setSize(1220, 480);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
// ── ENROLL: send the cert (base64 DER) up to the server ─────────────────
private static void performEnroll(final String alias) throws Exception {
final X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias);
// Proof-of-possession: sign a 32-byte random nonce with the private key and verify with
// the cert's public key. Two effects in one call —
// 1. forces the PKCS11 PIN dialog (initSign needs the private key, triggers the callback)
// 2. proves the user actually has the matching private key, not just a readable cert
//
// Signing (not RSA encrypt/decrypt) is used because it works for every cert we care about:
// - RSA Signing-only certs (Indian Class 3 DSCs — most common case)
// - RSA Signing+Encryption certs
// - EC / ECDSA certs (newer DSCs, test certs)
// The encrypt/decrypt approach used by the legacy MultiTenderActionSign fails on Signing-
// only certs (no keyEncipherment KeyUsage) and on EC keys (no RSA cipher support).
proveKeyPossessionViaSignature(alias, cert);
final String certB64 = Base64.getEncoder().encodeToString(cert.getEncoded());
final Map<String, String> body = new LinkedHashMap<>();
body.put("certB64", certB64);
body.put("dscName", extractCN(cert.getSubjectX500Principal().getName()));
body.put("dscType", resolveDscTypeForBackend(cert));
postJson(cfg.callbackUrl, body);
}
/**
* Signs a random nonce with the alias's private key and verifies it with the cert's public
* key. Chooses the signature algorithm from the key's algorithm — SHA256withRSA for RSA,
* SHA256withECDSA for EC. The {@code initSign} call cascades into the PKCS11
* {@link SwingPasswordCallbackHandler}, which is what shows the PIN dialog at enroll time.
*/
private static void proveKeyPossessionViaSignature(final String alias, final X509Certificate cert)
throws GeneralSecurityException {
final java.security.PrivateKey privateKey = (java.security.PrivateKey) keyStore.getKey(alias, null);
if (privateKey == null) {
throw new GeneralSecurityException("Private key for alias " + alias + " is not accessible");
}
final String sigAlgo = signatureAlgorithmFor(cert.getPublicKey().getAlgorithm());
final byte[] nonce = new byte[32];
new SecureRandom().nextBytes(nonce);
final Signature signer = Signature.getInstance(sigAlgo);
signer.initSign(privateKey); // PIN prompt happens here on PKCS11
signer.update(nonce);
final byte[] signature = signer.sign();
final Signature verifier = Signature.getInstance(sigAlgo);
verifier.initVerify(cert.getPublicKey());
verifier.update(nonce);
if (!verifier.verify(signature)) {
throw new GeneralSecurityException(
"Cert/key pair verification failed — the certificate's public key did not verify "
+ "the signature produced by the private key on the token.");
}
}
/** Maps a public key's algorithm name to the SHA-256 signature variant that key supports. */
private static String signatureAlgorithmFor(final String keyAlgorithm) throws GeneralSecurityException {
if (keyAlgorithm == null) {
throw new GeneralSecurityException("Certificate public key has no algorithm identifier");
}
final String normalised = keyAlgorithm.toUpperCase(Locale.ROOT);
if ("RSA".equals(normalised)) {
return "SHA256withRSA";
}
if ("EC".equals(normalised) || "ECDSA".equals(normalised)) {
return "SHA256withECDSA";
}
if ("DSA".equals(normalised)) {
return "SHA256withDSA";
}
throw new GeneralSecurityException("Unsupported key algorithm: " + keyAlgorithm);
}
// ── SIGN_CHALLENGE: RSA-SHA256(challenge bytes) → POST signature ────────
private static void performSignChallenge(final String alias) throws Exception {
final PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, null);
if (privateKey == null) {
throw new GeneralSecurityException("Private key for alias " + alias + " is not accessible");
}
final X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias);
// Sanity: chain + cert exist (also surfaces token issues early).
final Certificate[] chain = keyStore.getCertificateChain(alias);
if (chain == null || chain.length == 0) {
throw new GeneralSecurityException("Cert chain for alias " + alias + " is missing");
}
final Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey);
sig.update(cfg.challenge.getBytes(StandardCharsets.UTF_8));
final String signatureB64 = Base64.getEncoder().encodeToString(sig.sign());
final Map<String, String> body = new LinkedHashMap<>();
body.put("certSerial", cert.getSerialNumber().toString(16).toUpperCase(Locale.ROOT));
body.put("signatureB64", signatureB64);
postJson(cfg.callbackUrl, body);
}
// ── HTTP helpers ────────────────────────────────────────────────────────
private static void postJson(final String urlStr, final Object body) throws IOException {
final String json = new Gson().toJson(body);
final URL url = new URL(urlStr);
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
conn.setRequestProperty("Accept", "application/json");
// The helper runs outside the browser (JWS-launched JVM process) so it doesn't get the
// tenant header from any HTTP interceptor — pass it explicitly from the JNLP args. The
// backend's TenantAwareBasicAuthenticationFilter requires this header (or a
// ?tenantIdentifier= query param) on every /api/* request.
if (cfg != null && cfg.tenantIdentifier != null && !cfg.tenantIdentifier.isEmpty()) {
conn.setRequestProperty("Fineract-Platform-TenantId", cfg.tenantIdentifier);
}
conn.setDoOutput(true);
try (OutputStream out = conn.getOutputStream()) {
out.write(json.getBytes(StandardCharsets.UTF_8));
}
final int code = conn.getResponseCode();
// Read body for diagnostics; surface non-2xx to the user.
final String resp;
try (InputStream in = code >= 400 ? conn.getErrorStream() : conn.getInputStream();
ByteArrayOutputStream sink = new ByteArrayOutputStream()) {
if (in != null) {
final byte[] buf = new byte[2048];
int n;
while ((n = in.read(buf)) > 0) {
sink.write(buf, 0, n);
}
}
resp = sink.toString("UTF-8");
}
if (code < 200 || code >= 300) {
throw new IOException("Server returned HTTP " + code + ": " + resp);
}
}
// ── JNLP age check — same shape as your existing tender helper ──────────
private static boolean validateJnlpAge() {
// Look for our JNLP in Downloads — and crucially, match browser-renamed copies too
// ("cbsnexora-dsc (1).jnlp", "cbsnexora-dsc (2).jnlp", ...). Browsers add a numeric suffix
// when a file by the same name already exists, so a strict exact-name match would pick up
// the original (stale) download and ignore the freshly-saved one.
//
// Decision rule: take the youngest matching JNLP across all prefixes. Accept if it's
// within MAX_LINK_AGE_MINUTES, else fail. If no JNLP on disk at all, accept (JWS may have
// streamed it directly without saving). The server-side challenge TTL is the real
// authority on staleness — this is a fast pre-flight to avoid prompting for the PIN on
// a link the server has already expired.
try {
final String[] prefixes = { "cbsnexora-dsc", "SignTender" };
final String userProfile = System.getenv("USERPROFILE");
final String downloads = (userProfile != null ? userProfile : System.getProperty("user.home"))
+ File.separator + "Downloads";
final File dir = new File(downloads);
if (!dir.isDirectory()) {
return true;
}
final File[] candidates = dir.listFiles((d, name) -> {
if (name == null || !name.toLowerCase(Locale.ROOT).endsWith(".jnlp")) {
return false;
}
final String base = name.toLowerCase(Locale.ROOT);
for (final String prefix : prefixes) {
if (base.startsWith(prefix.toLowerCase(Locale.ROOT))) {
return true;
}
}
return false;
});
if (candidates == null || candidates.length == 0) {
return true; // no JNLP on disk → JWS likely streamed it
}
long youngestMs = Long.MIN_VALUE;
for (final File f : candidates) {
if (f.lastModified() > youngestMs) {
youngestMs = f.lastModified();
}
}
final long youngestMinutes = (System.currentTimeMillis() - youngestMs) / 60_000L;
return youngestMinutes <= MAX_LINK_AGE_MINUTES;
} catch (final Exception ignored) {
return true; // never block on env weirdness
}
}
// ── Misc ────────────────────────────────────────────────────────────────
private static String extractCN(final String dn) {
return extractRdn(dn, "CN");
}
/**
* Pulls a single RDN value (CN, O, OU…) out of an X.500 distinguished name string. Uses
* {@link javax.naming.ldap.LdapName} so escaped commas inside values (e.g.
* {@code CN=Smith\, John, O=Foo}) parse correctly — the legacy split-on-comma approach
* would have returned "Smith\\" in that case. Falls back to the simple parser only if
* LdapName rejects the string.
*/
private static String extractRdn(final String dn, final String key) {
if (dn == null || dn.isEmpty()) {
return "";
}
try {
final javax.naming.ldap.LdapName ldap = new javax.naming.ldap.LdapName(dn);
for (final javax.naming.ldap.Rdn rdn : ldap.getRdns()) {
if (key.equalsIgnoreCase(rdn.getType())) {
final Object value = rdn.getValue();
return value != null ? value.toString() : "";
}
}
return "";
} catch (final javax.naming.InvalidNameException ignored) {
// Fall back to a permissive comma-split parse — covers malformed-but-readable DNs.
for (final String part : dn.split(",")) {
final String[] kv = part.trim().split("=", 2);
if (kv.length == 2 && key.equalsIgnoreCase(kv[0].trim())) {
return kv[1].trim();
}
}
return "";
}
}
/**
* Display-friendly DSC type. Resolution order:
* 1. {@code cfg.dscType} from JNLP args (server-supplied hint) if present
* 2. cert's KeyUsage extension bits (RFC 5280: 0=digitalSignature, 2=keyEncipherment, 3=dataEncipherment)
* 3. fallback to "Signing" — the dominant DSC purpose, matches backend's default
*/
private static String extractDscType(final X509Certificate cert) {
final String hint = cfg != null ? cfg.dscType : null;
if (hint != null && !hint.isEmpty()) {
// Normalise "SIGNING"/"signing"/"Signing" to display form
final String upper = hint.toUpperCase(Locale.ROOT);
if ("SIGNING".equals(upper)) {
return "Signing";
}
if ("ENCRYPTION".equals(upper)) {
return "Encryption";
}
return hint; // unknown value — show as-is rather than silently dropping it
}
final boolean[] ku = cert.getKeyUsage();
if (ku == null) {
return "Signing";
}
final boolean signing = ku.length > 0 && ku[0];
final boolean encryption = (ku.length > 2 && ku[2]) || (ku.length > 3 && ku[3]);
if (signing && encryption) {
return "Signing + Encryption";
}
if (signing) {
return "Signing";
}
if (encryption) {
return "Encryption";
}
return "Signing";
}
/**
* Map the cert's type to the {@code DscType} enum the backend persists. JNLP arg wins, falls
* back to KeyUsage, finally defaults to SIGNING. Same resolution order as {@link #extractDscType}.
*/
private static String resolveDscTypeForBackend(final X509Certificate cert) {
final String hint = cfg != null ? cfg.dscType : null;
if (hint != null && !hint.isEmpty()) {
final String upper = hint.toUpperCase(Locale.ROOT);
if ("SIGNING".equals(upper) || "ENCRYPTION".equals(upper)) {
return upper;
}
}
final boolean[] ku = cert.getKeyUsage();
if (ku != null) {
final boolean signing = ku.length > 0 && ku[0];
final boolean encryption = (ku.length > 2 && ku[2]) || (ku.length > 3 && ku[3]);
// Backend enum has only SIGNING / ENCRYPTION. A combined cert is treated as SIGNING
// because our login-2FA flow uses the signing key — encryption is incidental.
if (signing) {
return "SIGNING";
}
if (encryption) {
return "ENCRYPTION";
}
}
return "SIGNING";
}
private static void showError(final String message, final String title) {
JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
}
/** Parsed JSON-arg payload. Mutable for convenience; not exposed beyond this class. */
static final class HelperArgs {
String randomNo;
String mode; // ENROLL | SIGN_CHALLENGE
String callbackUrl;
String challenge; // SIGN_CHALLENGE only
String expectedSerial; // optional
String codebase;
String tenantIdentifier; // Fineract tenant — sent as Fineract-Platform-TenantId on callback
String title;
String dscType; // optional server hint: "SIGNING" | "ENCRYPTION". Falls back to
// KeyUsage derivation when absent. Matches the legacy pattern
// where the server told the helper which type to label/store.
static HelperArgs parse(final String json) {
final JsonElement root = JsonParser.parseString(json);
if (!root.isJsonObject()) {
throw new IllegalArgumentException("argument is not a JSON object");
}
final JsonObject o = root.getAsJsonObject();
final HelperArgs a = new HelperArgs();
a.randomNo = optString(o, "randomNo");
a.mode = optString(o, "mode");
a.callbackUrl = optString(o, "callbackUrl");
a.challenge = optString(o, "challenge");
a.expectedSerial = optString(o, "expectedSerial");
a.codebase = optString(o, "codebase");
a.tenantIdentifier = optString(o, "tenantIdentifier");
a.title = optString(o, "title");
a.dscType = optString(o, "dscType");
if (a.mode == null || a.callbackUrl == null || a.randomNo == null) {
throw new IllegalArgumentException("randomNo, mode, callbackUrl are required");
}
if ("SIGN_CHALLENGE".equals(a.mode) && (a.challenge == null || a.challenge.isEmpty())) {
throw new IllegalArgumentException("challenge is required for SIGN_CHALLENGE mode");
}
return a;
}
private static String optString(final JsonObject o, final String k) {
return o.has(k) && !o.get(k).isJsonNull() ? o.get(k).getAsString() : null;
}
}
private CbsnexoraDscHelper() {
}
}
@@ -0,0 +1,70 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cbsnexora.dsc;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.util.concurrent.CancellationException;
/**
* Pops a Swing dialog asking for the PKCS11 token PIN. Used by the {@code KeyStore.Builder} on
* Linux/Mac when opening a hardware-token keystore — Windows-MY doesn't need it (the OS keystore
* handles auth via the platform UI).
*/
public class SwingPasswordCallbackHandler implements CallbackHandler {
@Override
public void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (final Callback cb : callbacks) {
if (cb instanceof PasswordCallback) {
handlePasswordCallback((PasswordCallback) cb);
} else {
throw new UnsupportedCallbackException(cb, "Callback not supported: " + cb.getClass().getName());
}
}
}
private void handlePasswordCallback(final PasswordCallback passCb) {
final JPanel panel = new JPanel(new GridLayout(2, 1, 5, 5));
panel.add(new JLabel(passCb.getPrompt() != null ? passCb.getPrompt() : "Enter token PIN:"));
final JPasswordField pin = new JPasswordField(20);
panel.add(pin);
final JOptionPane pane = new JOptionPane(panel, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION);
final JDialog dialog = pane.createDialog(null, "Token PIN");
dialog.addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(final WindowEvent e) {
pin.requestFocusInWindow();
}
});
dialog.setVisible(true);
dialog.dispose();
final Object val = pane.getValue();
final int choice = val instanceof Integer ? (Integer) val : JOptionPane.CANCEL_OPTION;
if (choice == JOptionPane.OK_OPTION) {
passCb.setPassword(pin.getPassword());
} else {
// Mirrors the original tender helper: user cancel → exit. Throwing the cancel up to
// KeyStore.Builder would leave the JNLP launch hanging.
throw new CancellationException("Token PIN entry cancelled by user");
}
}
}
@@ -0,0 +1,210 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cbsnexora.dsc;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.io.File;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.function.Consumer;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
/**
* Cross-platform DSC token / store picker. On Windows the first entry is "Windows Certificate
* Store" (which routes to the {@code Windows-MY} keystore — what most Indian DSC USB tokens auto-
* register with). Below that are common PKCS11 libraries for ePass2003 / eToken / ProxKey / etc.,
* plus an "Other" option that takes a custom {@code .dll} / {@code .so} / {@code .dylib} path.
*
* <p>On Linux / macOS the Windows entry is omitted and only PKCS11 options are listed.
*
* <p>The caller registers a {@link #setProceedAction(Consumer)} callback that receives either:
* <ul>
* <li>{@link #SELECTION_WINDOWS_MY} — caller opens {@code KeyStore.getInstance("Windows-MY")}</li>
* <li>An absolute filesystem path to a PKCS11 module — caller spins up {@code SunPKCS11}</li>
* </ul>
*/
public class TokenFrame extends JFrame {
/** Sentinel returned to the proceed callback when the user picks the OS-native Windows store. */
public static final String SELECTION_WINDOWS_MY = "WINDOWS-MY";
private final Map<String, String> presets = new LinkedHashMap<>();
private final JComboBox<String> combo = new JComboBox<>();
private final JTextField customPath = new JTextField(28);
private final JButton browseButton = Branding.secondaryButton("Browse...");
private final JButton proceedButton = Branding.primaryButton("Proceed");
private final JButton cancelButton = Branding.secondaryButton("Cancel");
private final JPanel customPanel;
private Consumer<String> proceedAction = sel -> {};
public TokenFrame() {
super("CBSnexora — Select Digital Signature Token");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Branding.applyWindowIcon(this);
getContentPane().setBackground(Branding.BODY_BG);
loadPresets();
for (final String name : presets.keySet()) {
combo.addItem(name);
}
combo.addItem("Other (browse for library file)");
setLayout(new BorderLayout());
add(Branding.buildHeader("Select Digital Signature Token",
"Choose the keystore or token driver used to read your DSC."), BorderLayout.NORTH);
// Body: form rows on a GridBag so labels align cleanly even after the optional
// 'Library:' row appears.
final JPanel body = new JPanel(new GridBagLayout());
body.setBackground(Branding.BODY_BG);
body.setBorder(BorderFactory.createEmptyBorder(20, 24, 20, 24));
final GridBagConstraints g = new GridBagConstraints();
g.insets = new Insets(6, 6, 6, 6);
g.anchor = GridBagConstraints.WEST;
final JLabel comboLabel = new JLabel("Token / Keystore:");
comboLabel.setFont(comboLabel.getFont().deriveFont(Font.BOLD, 13f));
comboLabel.setForeground(Branding.TITLE_DARK);
g.gridx = 0; g.gridy = 0; body.add(comboLabel, g);
combo.setPreferredSize(new Dimension(360, 28));
g.gridx = 1; g.weightx = 1.0; g.fill = GridBagConstraints.HORIZONTAL;
body.add(combo, g);
customPanel = new JPanel(new GridBagLayout());
customPanel.setOpaque(false);
final GridBagConstraints cg = new GridBagConstraints();
cg.insets = new Insets(0, 0, 0, 6);
cg.anchor = GridBagConstraints.WEST;
final JLabel libLabel = new JLabel("Library:");
libLabel.setFont(libLabel.getFont().deriveFont(Font.PLAIN, 12f));
libLabel.setForeground(Branding.SUBTITLE_GREY);
cg.gridx = 0; cg.gridy = 0; customPanel.add(libLabel, cg);
cg.gridx = 1; cg.weightx = 1.0; cg.fill = GridBagConstraints.HORIZONTAL;
customPanel.add(customPath, cg);
cg.gridx = 2; cg.weightx = 0; cg.fill = GridBagConstraints.NONE;
customPanel.add(browseButton, cg);
customPanel.setVisible(false);
g.gridx = 0; g.gridy = 1; g.gridwidth = 2; g.weightx = 1.0;
g.fill = GridBagConstraints.HORIZONTAL;
body.add(customPanel, g);
add(body, BorderLayout.CENTER);
final JPanel actions = new JPanel(new java.awt.FlowLayout(java.awt.FlowLayout.RIGHT, 10, 14));
actions.setBackground(Branding.BODY_BG);
actions.setBorder(BorderFactory.createEmptyBorder(0, 18, 10, 18));
actions.add(cancelButton);
actions.add(proceedButton);
add(actions, BorderLayout.SOUTH);
// Hooks
combo.addActionListener(e -> {
final boolean isOther = combo.getSelectedItem() != null
&& combo.getSelectedItem().toString().startsWith("Other");
customPanel.setVisible(isOther);
revalidate();
pack();
});
browseButton.addActionListener(e -> {
final JFileChooser fc = new JFileChooser();
if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
customPath.setText(fc.getSelectedFile().getAbsolutePath());
}
});
cancelButton.addActionListener(e -> System.exit(0));
proceedButton.addActionListener(e -> {
final String selection = resolveSelection();
if (selection == null) {
JOptionPane.showMessageDialog(this,
"Please pick a token / keystore option (or browse to a library file).",
"No Selection", JOptionPane.WARNING_MESSAGE);
return;
}
proceedAction.accept(selection);
});
setMinimumSize(new Dimension(620, 240));
pack();
setLocationRelativeTo(null);
}
public void setProceedAction(final Consumer<String> action) {
this.proceedAction = action != null ? action : sel -> {};
}
/** Translates the UI state into the value handed to the proceed callback. */
private String resolveSelection() {
final Object item = combo.getSelectedItem();
if (item == null) {
return null;
}
if (item.toString().startsWith("Other")) {
final String path = customPath.getText();
return (path != null && !path.trim().isEmpty()) ? path.trim() : null;
}
return presets.get(item.toString());
}
/**
* Default token / keystore presets. Adjust paths per install if your customers run different
* driver locations — most CCA-licensed CA tokens stick to the defaults below.
*/
private void loadPresets() {
final String os = System.getProperty("os.name", "").toLowerCase(Locale.ROOT);
if (os.contains("win")) {
presets.put("Windows Certificate Store (default)", SELECTION_WINDOWS_MY);
presets.put("ePass2003 (eMudhra / Capricorn)", "C:\\Windows\\System32\\eps2003csp11.dll");
presets.put("ePass2003 v2", "C:\\Windows\\System32\\eps2003csp11v2.dll");
presets.put("SafeNet eToken 5110", "C:\\Windows\\System32\\eTPKCS11.dll");
presets.put("WatchData ProxKey", "C:\\Windows\\System32\\WDPKCS.dll");
presets.put("Aladdin eToken", "C:\\Windows\\System32\\eTPkcs11.dll");
presets.put("HYP2003 / Trust Key", "C:\\Windows\\System32\\SignatureP11.dll");
presets.put("mToken CryptoID", "C:\\Windows\\System32\\mToken.dll");
} else if (os.contains("mac") || os.contains("darwin")) {
presets.put("ePass2003 (eMudhra / Capricorn)", "/usr/local/lib/libcastle_v2.dylib");
presets.put("SafeNet eToken", "/usr/local/lib/libeTPkcs11.dylib");
presets.put("WatchData ProxKey", "/usr/local/lib/libwdpkcs.dylib");
} else {
// Linux
presets.put("ePass2003 (eMudhra / Capricorn)", "/usr/lib/x86_64-linux-gnu/libcastle_v2.so");
presets.put("ePass2003 alt path", "/usr/lib/libcastle_v2.so");
presets.put("SafeNet eToken", "/usr/lib/libeToken.so");
presets.put("SafeNet eToken (Ubuntu)", "/usr/lib/x86_64-linux-gnu/libeToken.so");
presets.put("WatchData ProxKey", "/usr/lib/libwdpkcs.so");
presets.put("OpenSC (generic)", "/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so");
}
// Drop entries whose underlying file doesn't exist — keeps the picker honest.
presets.values().removeIf(path -> !SELECTION_WINDOWS_MY.equals(path) && !new File(path).exists());
// Always keep at least Windows-MY on Windows even if the rest were stripped.
if (os.contains("win") && presets.isEmpty()) {
presets.put("Windows Certificate Store (default)", SELECTION_WINDOWS_MY);
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

@@ -24,7 +24,6 @@ import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
@@ -52,9 +51,13 @@ public class AccrualAccountingApiResource {
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Executes Periodic Accrual Accounting", method = "POST", description = "Mandatory Fields\n" + "\n" + "tillDate\n")
@Operation(summary = "Executes Periodic Accrual Accounting", method = "POST", description = """
Mandatory Fields
tillDate
""")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = AccrualAccountingApiResourceSwagger.PostRunaccrualsRequest.class)))
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
@ApiResponse(responseCode = "200", description = "OK")
public CommandProcessingResult executePeriodicAccrualAccounting(@Parameter(hidden = true) AccrualAccountRequest accrualAccountRequest) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().excuteAccrualAccounting()
.withJson(apiJsonSerializerService.serialize(accrualAccountRequest)).build();
@@ -25,7 +25,6 @@ import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
@@ -57,10 +56,17 @@ import org.springframework.stereotype.Component;
@Path("/v1/glclosures")
@Component
@Tag(name = "Accounting Closure", description = "An accounting closure indicates that no more journal entries may be logged (or reversed) in the system, either manually or via the portfolio with an entry date prior to the defined closure date\n"
+ "\n" + "Field Descriptions\n" + "closingDate\n" + "The date for which the accounting closure is defined\n" + "officeId\n"
+ "The identifer of the branch for which accounting has been closed\n" + "comments\n"
+ "Description associated with an Accounting closure")
@Tag(name = "Accounting Closure", description = """
An accounting closure indicates that no more journal entries may be logged (or reversed) in the system, either manually or via the portfolio with an entry date prior to the defined closure date
Field Descriptions
closingDate
The date for which the accounting closure is defined
officeId
The identifer of the branch for which accounting has been closed
comments
Description associated with an Accounting closure
""")
@RequiredArgsConstructor
public class GLClosuresApiResource {
@@ -76,9 +82,11 @@ public class GLClosuresApiResource {
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "List Accounting closures", description = "Example Requests:\n" + "\n" + "glclosures")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = GLClosuresApiResourceSwagger.GetGlClosureResponse.class)))) })
@Operation(summary = "List Accounting closures", description = """
Example Requests:
glclosures""")
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = GLClosuresApiResourceSwagger.GetGlClosureResponse.class))))
public List<GLClosureData> retrieveAllClosures(@QueryParam("officeId") @Parameter(name = "officeId") final Long officeId) {
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSION);
@@ -89,10 +97,14 @@ public class GLClosuresApiResource {
@Path("{glClosureId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve an Accounting Closure", description = "Example Requests:\n" + "\n" + "glclosures/1\n" + "\n" + "\n"
+ "/glclosures/1?fields=officeName,closingDate")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = GLClosuresApiResourceSwagger.GetGlClosureResponse.class))) })
@Operation(summary = "Retrieve an Accounting Closure", description = """
Example Requests:
glclosures/1
/glclosures/1?fields=officeName,closingDate""")
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = GLClosuresApiResourceSwagger.GetGlClosureResponse.class)))
public GLClosureData retreiveClosure(@PathParam("glClosureId") @Parameter(description = "glClosureId") final Long glClosureId,
@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSION);
@@ -112,8 +124,7 @@ public class GLClosuresApiResource {
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Create an Accounting Closure", description = "Mandatory Fields\n" + "officeId,closingDate")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = GLClosuresApiResourceSwagger.PostGlClosuresRequest.class, description = "Request Body")))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = GLClosuresApiResourceSwagger.PostGlClosuresResponse.class))) })
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = GLClosuresApiResourceSwagger.PostGlClosuresResponse.class)))
public CommandProcessingResult createGLClosure(@Parameter(hidden = true) GLClosureRequest glClosureRequest) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createGLClosure()
.withJson(apiJsonSerializerService.serialize(glClosureRequest)).build();
@@ -127,8 +138,7 @@ public class GLClosuresApiResource {
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Update an Accounting closure", description = "Once an accounting closure is created, only the comments associated with it may be edited")
@RequestBody(content = @Content(schema = @Schema(implementation = GLClosuresApiResourceSwagger.PutGlClosuresRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = GLClosuresApiResourceSwagger.PutGlClosuresResponse.class))) })
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = GLClosuresApiResourceSwagger.PutGlClosuresResponse.class)))
public CommandProcessingResult updateGLClosure(@PathParam("glClosureId") @Parameter(description = "glClosureId") final Long glClosureId,
@Parameter(hidden = true) GLClosureRequest glClosureRequest) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateGLClosure(glClosureId)
@@ -141,8 +151,7 @@ public class GLClosuresApiResource {
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Delete an accounting closure", description = "Note: Only the latest accounting closure associated with a branch may be deleted.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = GLClosuresApiResourceSwagger.DeleteGlClosuresResponse.class))) })
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = GLClosuresApiResourceSwagger.DeleteGlClosuresResponse.class)))
public CommandProcessingResult deleteGLClosure(
@PathParam("glClosureId") @Parameter(description = "glclosureId") final Long glClosureId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteGLClosure(glClosureId).build();
@@ -72,8 +72,7 @@ public final class FinancialActivityAccountDataValidator {
}
private DataValidatorBuilder getDataValidator(final List<ApiParameterError> dataValidationErrors) {
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("financialactivityaccount");
return baseDataValidator;
return new DataValidatorBuilder(dataValidationErrors).resource("financialactivityaccount");
}
public void validateForUpdate(final String json) {
@@ -18,6 +18,8 @@
*/
package org.apache.fineract.accounting.glaccount.domain;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -28,4 +30,33 @@ public interface TrialBalanceRepository extends JpaRepository<TrialBalance, Long
@Query(value = "select * from m_trial_balance where office_id=:officeId and account_id=:accountId and closing_balance is null order by created_date, entry_date", nativeQuery = true)
List<TrialBalance> findNewByOfficeAndAccount(@Param("officeId") Long officeId, @Param("accountId") Long accountId);
@Query("SELECT MAX(tb.transactionDate) FROM TrialBalance tb")
LocalDate findMaxCreatedDate();
@Query("""
SELECT tb.closingBalance
FROM TrialBalance tb
WHERE tb.officeId = :officeId
AND tb.glAccountId = :accountId
AND tb.closingBalance IS NOT NULL
ORDER BY tb.transactionDate DESC, tb.entryDate DESC
""")
List<BigDecimal> findLastClosingBalance(Long officeId, Long accountId);
@Query("""
SELECT DISTINCT tb.officeId
FROM TrialBalance tb
WHERE tb.closingBalance IS NULL
""")
List<Long> findDistinctOfficeIdsWithNullClosingBalance();
@Query("""
SELECT DISTINCT tb.glAccountId
FROM TrialBalance tb
WHERE tb.officeId = :officeId
AND tb.closingBalance IS NULL
""")
List<Long> findDistinctAccountIdsWithNullClosingBalanceByOfficeId(Long officeId);
}
@@ -19,7 +19,9 @@
package org.apache.fineract.accounting.glaccount.jobs.updatetrialbalancedetails;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.glaccount.domain.TrialBalanceRepository;
import org.apache.fineract.accounting.glaccount.domain.TrialBalanceRepositoryWrapper;
import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository;
import org.apache.fineract.infrastructure.core.service.database.RoutingDataSourceServiceFactory;
import org.apache.fineract.infrastructure.jobs.service.JobName;
import org.springframework.batch.core.Job;
@@ -40,6 +42,8 @@ public class UpdateTrialBalanceDetailsConfig {
private final PlatformTransactionManager transactionManager;
private final RoutingDataSourceServiceFactory dataSourceServiceFactory;
private final TrialBalanceRepositoryWrapper trialBalanceRepositoryWrapper;
private final TrialBalanceRepository trialBalanceRepository;
private final JournalEntryRepository journalEntryRepository;
@Bean
protected Step updateTrialBalanceDetailsStep() {
@@ -55,6 +59,7 @@ public class UpdateTrialBalanceDetailsConfig {
@Bean
public UpdateTrialBalanceDetailsTasklet updateTrialBalanceDetailsTasklet() {
return new UpdateTrialBalanceDetailsTasklet(dataSourceServiceFactory, trialBalanceRepositoryWrapper);
return new UpdateTrialBalanceDetailsTasklet(dataSourceServiceFactory, trialBalanceRepositoryWrapper, trialBalanceRepository,
journalEntryRepository);
}
}
@@ -24,7 +24,9 @@ import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.accounting.glaccount.domain.TrialBalance;
import org.apache.fineract.accounting.glaccount.domain.TrialBalanceRepository;
import org.apache.fineract.accounting.glaccount.domain.TrialBalanceRepositoryWrapper;
import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.core.service.database.RoutingDataSourceServiceFactory;
@@ -41,59 +43,87 @@ public class UpdateTrialBalanceDetailsTasklet implements Tasklet {
private final RoutingDataSourceServiceFactory dataSourceServiceFactory;
private final TrialBalanceRepositoryWrapper trialBalanceRepositoryWrapper;
private final TrialBalanceRepository trialBalanceRepository;
private final JournalEntryRepository journalEntryRepository;
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
final JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSourceServiceFactory.determineDataSourceService().retrieveDataSource());
final StringBuilder tbGapSqlBuilder = new StringBuilder(500);
tbGapSqlBuilder.append("select distinct(je.transaction_date) ").append("from acc_gl_journal_entry je ")
.append("where je.transaction_date > (select coalesce(MAX(created_date),'2010-01-01') from m_trial_balance)");
final List<LocalDate> tbGaps = jdbcTemplate.queryForList(tbGapSqlBuilder.toString(), LocalDate.class);
for (LocalDate tbGap : tbGaps) {
int days = DateUtils.getExactDifferenceInDays(tbGap, DateUtils.getBusinessLocalDate());
if (days < 1) {
continue;
}
final StringBuilder sqlBuilder = new StringBuilder(600);
sqlBuilder.append("Insert Into m_trial_balance(office_id, account_id, Amount, entry_date, created_date,closing_balance) ")
.append("Select je.office_id, je.account_id, SUM(CASE WHEN je.type_enum=1 THEN (-1) * je.amount ELSE je.amount END) ")
.append("as Amount, Date(je.entry_date) as Entry_Date, je.transaction_date as Created_Date,sum(je.amount) as closing_balance ")
.append("from acc_gl_journal_entry je WHERE je.transaction_date = ? ")
.append("group by je.account_id, je.office_id, je.transaction_date, Date(je.entry_date)");
final int result = jdbcTemplate.update(sqlBuilder.toString(), tbGap);
log.debug("{}: Records affected by updateTrialBalanceDetails: {}", ThreadLocalContextUtil.getTenant().getName(), result);
}
String distinctOfficeQuery = "select distinct(office_id) from m_trial_balance where closing_balance is null group by office_id";
final List<Long> officeIds = jdbcTemplate.queryForList(distinctOfficeQuery, Long.class);
for (Long officeId : officeIds) {
String distinctAccountQuery = "select distinct(account_id) from m_trial_balance where office_id=? and closing_balance is null group by account_id";
final List<Long> accountIds = jdbcTemplate.queryForList(distinctAccountQuery, Long.class, officeId);
for (Long accountId : accountIds) {
final String closingBalanceQuery = "select closing_balance from m_trial_balance where office_id=? and account_id=? and closing_balance "
+ "is not null order by created_date desc, entry_date desc limit 1";
List<BigDecimal> closingBalanceData = jdbcTemplate.queryForList(closingBalanceQuery, BigDecimal.class, officeId, accountId);
List<TrialBalance> tbRows = trialBalanceRepositoryWrapper.findNewByOfficeAndAccount(officeId, accountId);
BigDecimal closingBalance = null;
if (!CollectionUtils.isEmpty(closingBalanceData)) {
closingBalance = closingBalanceData.get(0);
}
if (CollectionUtils.isEmpty(closingBalanceData)) {
closingBalance = BigDecimal.ZERO;
for (TrialBalance row : tbRows) {
closingBalance = closingBalance.add(row.getAmount());
row.setClosingBalance(closingBalance);
}
} else {
for (TrialBalance tbRow : tbRows) {
if (closingBalance != null) {
closingBalance = closingBalance.add(tbRow.getAmount());
}
tbRow.setClosingBalance(closingBalance);
}
}
trialBalanceRepositoryWrapper.save(tbRows);
}
}
processTrialBalanceGaps(jdbcTemplate);
updateClosingBalances(jdbcTemplate);
return RepeatStatus.FINISHED;
}
private void processTrialBalanceGaps(JdbcTemplate jdbcTemplate) {
LocalDate maxCreatedDate = trialBalanceRepository.findMaxCreatedDate();
LocalDate baselineDate = maxCreatedDate != null ? maxCreatedDate : LocalDate.of(2010, 1, 1);
List<LocalDate> tbGaps = journalEntryRepository.findTransactionDatesAfter(baselineDate);
for (LocalDate tbGap : tbGaps) {
if (DateUtils.getExactDifferenceInDays(tbGap, DateUtils.getBusinessLocalDate()) < 1) {
continue;
}
insertTrialBalanceForDate(tbGap);
}
}
private void insertTrialBalanceForDate(LocalDate tbGap) {
List<Object[]> rows = journalEntryRepository.findTrialBalanceLinesForDate(tbGap);
List<TrialBalance> trialBalances = rows.stream().map(row -> {
TrialBalance tb = new TrialBalance();
tb.setOfficeId((Long) row[0]);
tb.setGlAccountId((Long) row[1]);
tb.setAmount((BigDecimal) row[2]);
tb.setEntryDate((LocalDate) row[3]);
tb.setTransactionDate((LocalDate) row[4]);
tb.setClosingBalance((BigDecimal) row[5]);
return tb;
}).toList();
trialBalanceRepositoryWrapper.save(trialBalances);
log.debug("{}: Records affected by updateTrialBalanceDetails: {}", ThreadLocalContextUtil.getTenant().getName(),
trialBalances.size());
}
private void updateClosingBalances(JdbcTemplate jdbcTemplate) {
final List<Long> officeIds = trialBalanceRepository.findDistinctOfficeIdsWithNullClosingBalance();
for (Long officeId : officeIds) {
updateClosingBalancesForOffice(jdbcTemplate, officeId);
}
}
private void updateClosingBalancesForOffice(JdbcTemplate jdbcTemplate, Long officeId) {
final List<Long> accountIds = trialBalanceRepository.findDistinctAccountIdsWithNullClosingBalanceByOfficeId(officeId);
for (Long accountId : accountIds) {
updateClosingBalanceForAccount(jdbcTemplate, officeId, accountId);
}
}
private void updateClosingBalanceForAccount(JdbcTemplate jdbcTemplate, Long officeId, Long accountId) {
BigDecimal closingBalance = getPreviousClosingBalance(officeId, accountId);
List<TrialBalance> tbRows = trialBalanceRepositoryWrapper.findNewByOfficeAndAccount(officeId, accountId);
updateTrialBalanceRows(tbRows, closingBalance);
}
private BigDecimal getPreviousClosingBalance(Long officeId, Long accountId) {
List<BigDecimal> closingBalanceData = trialBalanceRepository.findLastClosingBalance(officeId, accountId);
return CollectionUtils.isEmpty(closingBalanceData) ? BigDecimal.ZERO : closingBalanceData.getFirst();
}
private void updateTrialBalanceRows(List<TrialBalance> tbRows, BigDecimal initialClosingBalance) {
BigDecimal closingBalance = initialClosingBalance;
for (TrialBalance row : tbRows) {
if (closingBalance != null) {
closingBalance = closingBalance.add(row.getAmount());
}
row.setClosingBalance(closingBalance);
}
}
}
@@ -1,107 +1,109 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.journalentry;
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
import org.apache.fineract.accounting.journalentry.data.JournalEntryData;
import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.PortfolioProductType;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
@Mapper(config = MapstructMapperConfig.class)
public interface JournalEntryMapper {
@Mapping(target = "id", source = "id")
@Mapping(target = "officeId", source = "office.id")
@Mapping(target = "officeName", source = "office.name")
@Mapping(target = "glAccountId", source = "glAccount.id")
@Mapping(target = "glAccountCode", source = "glAccount.glCode")
@Mapping(target = "glAccountName", source = "glAccount.name")
@Mapping(target = "glAccountType", source = "glAccount.type", qualifiedByName = "glAccountType")
@Mapping(target = "transactionDate", source = "transactionDate")
@Mapping(target = "entryType", source = "type", qualifiedByName = "journalEntryType")
@Mapping(target = "amount", source = "amount")
@Mapping(target = "entityType", source = "entityType", qualifiedByName = "entityType")
@Mapping(target = "entityId", source = "entityId")
@Mapping(target = "submittedOnDate", source = "submittedOnDate")
@Mapping(target = "transactionId", source = "transactionId")
@Mapping(target = "currency", source = "currencyCode")
@Mapping(target = "manualEntry", source = "manualEntry")
@Mapping(target = "reversed", source = "reversed")
@Mapping(target = "referenceNumber", source = "referenceNumber")
@Mapping(target = "paymentTypeId", source = "paymentDetail.paymentType.id")
@Mapping(target = "accountNumber", source = "paymentDetail.accountNumber")
@Mapping(target = "checkNumber", source = "paymentDetail.checkNumber")
@Mapping(target = "routingCode", source = "paymentDetail.routingCode")
@Mapping(target = "receiptNumber", source = "paymentDetail.receiptNumber")
@Mapping(target = "bankNumber", source = "paymentDetail.bankNumber")
@Mapping(target = "createdDate", ignore = true)
@Mapping(target = "createdByUserId", ignore = true)
@Mapping(target = "createdByUserName", ignore = true)
@Mapping(target = "comments", ignore = true)
@Mapping(target = "officeRunningBalance", ignore = true)
@Mapping(target = "organizationRunningBalance", ignore = true)
@Mapping(target = "runningBalanceComputed", ignore = true)
@Mapping(target = "rowIndex", ignore = true)
@Mapping(target = "dateFormat", ignore = true)
@Mapping(target = "locale", ignore = true)
@Mapping(target = "credits", ignore = true)
@Mapping(target = "debits", ignore = true)
@Mapping(target = "transactionDetails", ignore = true)
@Mapping(target = "savingTransactionId", ignore = true)
@Mapping(target = "externalAssetOwner", ignore = true)
JournalEntryData map(JournalEntry journalEntry);
@Named("entityType")
default PortfolioProductType mapEntityType(Integer entityTypeId) {
return PortfolioProductType.fromInt(entityTypeId);
}
@Named("glAccountType")
default GLAccountType mapGlAccountType(Integer typeId) {
return GLAccountType.fromInt(typeId);
}
@Named("journalEntryType")
default JournalEntryType mapJournalEntryType(Integer typeId) {
return JournalEntryType.fromInt(typeId);
}
default EnumOptionData mapGlAccountType(GLAccountType accountType) {
return new EnumOptionData((long) accountType.getValue(), accountType.getCode(), accountType.name());
}
default EnumOptionData mapJournalEntryType(JournalEntryType journalEntryType) {
return new EnumOptionData((long) journalEntryType.getValue(), journalEntryType.getCode(), journalEntryType.name());
}
default EnumOptionData mapEntityType(PortfolioProductType entityType) {
return new EnumOptionData((long) entityType.getValue(), entityType.getCode(), entityType.name());
}
default CurrencyData mapCurrency(String currencyCode) {
return new CurrencyData(currencyCode);
}
}
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.journalentry;
import java.time.OffsetDateTime;
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
import org.apache.fineract.accounting.journalentry.data.JournalEntryData;
import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.PortfolioProductType;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
@Mapper(config = MapstructMapperConfig.class)
public interface JournalEntryMapper {
@Mapping(target = "id", source = "id")
@Mapping(target = "officeId", source = "office.id")
@Mapping(target = "officeName", source = "office.name")
@Mapping(target = "glAccountId", source = "glAccount.id")
@Mapping(target = "glAccountCode", source = "glAccount.glCode")
@Mapping(target = "glAccountName", source = "glAccount.name")
@Mapping(target = "glAccountType", source = "glAccount.type", qualifiedByName = "glAccountType")
@Mapping(target = "transactionDate", source = "transactionDate")
@Mapping(target = "entryType", source = "type", qualifiedByName = "journalEntryType")
@Mapping(target = "amount", source = "amount")
@Mapping(target = "entityType", source = "entityType", qualifiedByName = "entityType")
@Mapping(target = "entityId", source = "entityId")
@Mapping(target = "submittedOnDate", expression = "java(journalEntry.getCreatedDate().orElse(null))")
@Mapping(target = "transactionId", source = "transactionId")
@Mapping(target = "currency", source = "currencyCode")
@Mapping(target = "manualEntry", source = "manualEntry")
@Mapping(target = "reversed", source = "reversed")
@Mapping(target = "referenceNumber", source = "referenceNumber")
@Mapping(target = "paymentTypeId", source = "paymentDetail.paymentType.id")
@Mapping(target = "accountNumber", source = "paymentDetail.accountNumber")
@Mapping(target = "checkNumber", source = "paymentDetail.checkNumber")
@Mapping(target = "routingCode", source = "paymentDetail.routingCode")
@Mapping(target = "receiptNumber", source = "paymentDetail.receiptNumber")
@Mapping(target = "bankNumber", source = "paymentDetail.bankNumber")
@Mapping(target = "createdDate", ignore = true)
@Mapping(target = "createdByUserId", ignore = true)
@Mapping(target = "createdByUserName", ignore = true)
@Mapping(target = "comments", ignore = true)
@Mapping(target = "officeRunningBalance", ignore = true)
@Mapping(target = "organizationRunningBalance", ignore = true)
@Mapping(target = "runningBalanceComputed", ignore = true)
@Mapping(target = "rowIndex", ignore = true)
@Mapping(target = "dateFormat", ignore = true)
@Mapping(target = "locale", ignore = true)
@Mapping(target = "credits", ignore = true)
@Mapping(target = "debits", ignore = true)
@Mapping(target = "transactionDetails", ignore = true)
@Mapping(target = "savingTransactionId", ignore = true)
@Mapping(target = "externalAssetOwner", ignore = true)
@Mapping(target = "savingsAccountNo", ignore = true)
JournalEntryData map(JournalEntry journalEntry);
@Named("entityType")
default PortfolioProductType mapEntityType(Integer entityTypeId) {
return PortfolioProductType.fromInt(entityTypeId);
}
@Named("glAccountType")
default GLAccountType mapGlAccountType(Integer typeId) {
return GLAccountType.fromInt(typeId);
}
@Named("journalEntryType")
default JournalEntryType mapJournalEntryType(Integer typeId) {
return JournalEntryType.fromInt(typeId);
}
default EnumOptionData mapGlAccountType(GLAccountType accountType) {
return new EnumOptionData((long) accountType.getValue(), accountType.getCode(), accountType.name());
}
default EnumOptionData mapJournalEntryType(JournalEntryType journalEntryType) {
return new EnumOptionData((long) journalEntryType.getValue(), journalEntryType.getCode(), journalEntryType.name());
}
default EnumOptionData mapEntityType(PortfolioProductType entityType) {
return new EnumOptionData((long) entityType.getValue(), entityType.getCode(), entityType.name());
}
default CurrencyData mapCurrency(String currencyCode) {
return new CurrencyData(currencyCode);
}
}
@@ -0,0 +1,41 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.journalentry.data;
import java.math.BigDecimal;
import java.time.LocalDate;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DayWiseSummaryData {
private LocalDate date;
private BigDecimal openingBalance;
private BigDecimal totalDebit;
private BigDecimal totalCredit;
private BigDecimal netMovement;
private BigDecimal closingBalance;
private int entryCount;
}
@@ -0,0 +1,72 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.journalentry.data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
import org.apache.fineract.infrastructure.core.service.Page;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class GLAccountLedgerReportData {
/** GL Account details (name, code, type, description, usage) */
private GLAccountData glAccount;
/** Balance of the account before the fromDate (0 if no fromDate or no glAccountId) */
private BigDecimal openingBalance;
/** Balance of the account as of toDate = openingBalance + netMovement */
private BigDecimal closingBalance;
/** Sum of all DEBIT entries in the requested period */
private BigDecimal totalDebit;
/** Sum of all CREDIT entries in the requested period */
private BigDecimal totalCredit;
/**
* Net movement: for ASSET/EXPENSE (debit-normal) = totalDebit - totalCredit;
* for LIABILITY/INCOME/EQUITY (credit-normal) = totalCredit - totalDebit
*/
private BigDecimal netMovement;
/** true for ASSET and EXPENSE accounts (debit-normal balance convention) */
private boolean debitNormal;
private LocalDate fromDate;
private LocalDate toDate;
/** Day-wise breakdown of balances for the period */
private List<DayWiseSummaryData> dayWiseSummary;
/** Total number of journal entries matching the filter (for the KPI card) */
private long totalEntries;
/** Paginated journal entries for the period */
private Page<JournalEntryData> entries;
}
@@ -1,286 +1,291 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.journalentry.data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import lombok.Data;
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
/**
* Immutable object representing a General Ledger Account
*
* Note: no getter/setters required as google will produce json from fields of object.
*/
@Data
public class JournalEntryData {
private Long id;
private Long officeId;
@SuppressWarnings("unused")
private String officeName;
@SuppressWarnings("unused")
private String glAccountName;
private Long glAccountId;
@SuppressWarnings("unused")
private String glAccountCode;
private EnumOptionData glAccountType;
@SuppressWarnings("unused")
private LocalDate transactionDate;
private EnumOptionData entryType;
private BigDecimal amount;
@SuppressWarnings("unused")
private CurrencyData currency;
private String transactionId;
@SuppressWarnings("unused")
private Boolean manualEntry;
@SuppressWarnings("unused")
private EnumOptionData entityType;
@SuppressWarnings("unused")
private Long entityId;
@SuppressWarnings("unused")
private Long createdByUserId;
@SuppressWarnings("unused")
private LocalDate createdDate;
@SuppressWarnings("unused")
private String createdByUserName;
@SuppressWarnings("unused")
private String comments;
@SuppressWarnings("unused")
private Boolean reversed;
@SuppressWarnings("unused")
private String referenceNumber;
@SuppressWarnings("unused")
private BigDecimal officeRunningBalance;
@SuppressWarnings("unused")
private BigDecimal organizationRunningBalance;
@SuppressWarnings("unused")
private Boolean runningBalanceComputed;
@SuppressWarnings("unused")
private TransactionDetailData transactionDetails;
@SuppressWarnings("unused")
private LocalDate submittedOnDate;
// import fields
private transient Integer rowIndex;
private String dateFormat;
private String locale;
private List<CreditDebit> credits;
private List<CreditDebit> debits;
private Long paymentTypeId;
private String currencyCode;
private String accountNumber;
private String checkNumber;
private String routingCode;
private String receiptNumber;
private String bankNumber;
private String externalAssetOwner;
private transient Long savingTransactionId;
public JournalEntryData() {}
// for opening bal bulk import
public JournalEntryData(Long officeId, LocalDate transactionDate, String currencyCode, List<CreditDebit> credits,
List<CreditDebit> debits, String locale, String dateFormat) {
this.officeId = officeId;
this.dateFormat = dateFormat;
this.locale = locale;
this.transactionDate = transactionDate;
this.currencyCode = currencyCode;
this.credits = credits;
this.debits = debits;
this.rowIndex = null;
this.paymentTypeId = null;
this.accountNumber = null;
this.checkNumber = null;
this.routingCode = null;
this.receiptNumber = null;
this.bankNumber = null;
this.comments = null;
this.id = null;
this.officeName = null;
this.glAccountName = null;
this.glAccountId = null;
this.glAccountCode = null;
this.glAccountType = null;
this.entryType = null;
this.amount = null;
this.currency = null;
this.transactionId = null;
this.manualEntry = null;
this.entityType = null;
this.entityId = null;
this.createdByUserId = null;
this.createdDate = null;
this.createdByUserName = null;
this.reversed = null;
this.referenceNumber = null;
this.officeRunningBalance = null;
this.organizationRunningBalance = null;
this.runningBalanceComputed = null;
this.transactionDetails = null;
this.submittedOnDate = null;
}
private JournalEntryData(Long officeId, LocalDate transactionDate, String currencyCode, Long paymentTypeId, Integer rowIndex,
List<CreditDebit> credits, List<CreditDebit> debits, String accountNumber, String checkNumber, String routingCode,
String receiptNumber, String bankNumber, String comments, String locale, String dateFormat) {
this.officeId = officeId;
this.dateFormat = dateFormat;
this.locale = locale;
this.transactionDate = transactionDate;
this.currencyCode = currencyCode;
this.rowIndex = rowIndex;
this.credits = credits;
this.debits = debits;
this.paymentTypeId = paymentTypeId;
this.accountNumber = accountNumber;
this.checkNumber = checkNumber;
this.routingCode = routingCode;
this.receiptNumber = receiptNumber;
this.bankNumber = bankNumber;
this.comments = comments;
this.id = null;
this.officeName = null;
this.glAccountName = null;
this.glAccountId = null;
this.glAccountCode = null;
this.glAccountType = null;
this.entryType = null;
this.amount = null;
this.currency = null;
this.transactionId = null;
this.manualEntry = null;
this.entityType = null;
this.entityId = null;
this.createdByUserId = null;
this.submittedOnDate = null;
this.createdDate = null;
this.createdByUserName = null;
this.reversed = null;
this.referenceNumber = null;
this.officeRunningBalance = null;
this.organizationRunningBalance = null;
this.runningBalanceComputed = null;
this.transactionDetails = null;
}
public JournalEntryData(final Long id, final Long officeId, final String officeName, final String glAccountName, final Long glAccountId,
final String glAccountCode, final EnumOptionData glAccountClassification, final LocalDate transactionDate,
final EnumOptionData entryType, final BigDecimal amount, final String transactionId, final Boolean manualEntry,
final EnumOptionData entityType, final Long entityId, final Long createdByUserId, final LocalDate submittedOnDate,
final String createdByUserName, final String comments, final Boolean reversed, final String referenceNumber,
final BigDecimal officeRunningBalance, final BigDecimal organizationRunningBalance, final Boolean runningBalanceComputed,
final TransactionDetailData transactionDetailData, final CurrencyData currency, final String externalAssetOwner) {
this.id = id;
this.officeId = officeId;
this.officeName = officeName;
this.glAccountName = glAccountName;
this.glAccountId = glAccountId;
this.glAccountCode = glAccountCode;
this.glAccountType = glAccountClassification;
this.transactionDate = transactionDate;
this.entryType = entryType;
this.amount = amount;
this.transactionId = transactionId;
this.manualEntry = manualEntry;
this.entityType = entityType;
this.entityId = entityId;
this.createdByUserId = createdByUserId;
this.createdDate = submittedOnDate;
this.submittedOnDate = submittedOnDate;
this.createdByUserName = createdByUserName;
this.comments = comments;
this.reversed = reversed;
this.referenceNumber = referenceNumber;
this.officeRunningBalance = officeRunningBalance;
this.organizationRunningBalance = organizationRunningBalance;
this.runningBalanceComputed = runningBalanceComputed;
this.transactionDetails = transactionDetailData;
this.currency = currency;
this.externalAssetOwner = externalAssetOwner;
}
public static JournalEntryData importInstance(Long officeId, LocalDate transactionDate, String currencyCode, Long paymentTypeId,
Integer rowIndex, List<CreditDebit> credits, List<CreditDebit> debits, String accountNumber, String checkNumber,
String routingCode, String receiptNumber, String bankNumber, String comments, String locale, String dateFormat) {
return new JournalEntryData(officeId, transactionDate, currencyCode, paymentTypeId, rowIndex, credits, debits, accountNumber,
checkNumber, routingCode, receiptNumber, bankNumber, comments, locale, dateFormat);
}
public static JournalEntryData importInstance1(Long officeId, LocalDate transactionDate, String currencyCode, List<CreditDebit> credits,
List<CreditDebit> debits, String locale, String dateFormat) {
return new JournalEntryData(officeId, transactionDate, currencyCode, credits, debits, locale, dateFormat);
}
public static JournalEntryData fromGLAccountData(final GLAccountData glAccountData) {
final Long id = null;
final Long officeId = null;
final String officeName = null;
final String glAccountName = glAccountData.getName();
final Long glAccountId = glAccountData.getId();
final String glAccountCode = glAccountData.getGlCode();
final EnumOptionData glAccountClassification = glAccountData.getType();
final LocalDate transactionDate = null;
final EnumOptionData entryType = null;
final BigDecimal amount = null;
final String transactionId = null;
final Boolean manualEntry = null;
final EnumOptionData entityType = null;
final Long entityId = null;
final Long createdByUserId = null;
final LocalDate submittedOnDate = null;
final String createdByUserName = null;
final String comments = null;
final Boolean reversed = null;
final String referenceNumber = null;
final BigDecimal officeRunningBalance = null;
final BigDecimal organizationRunningBalance = null;
final Boolean runningBalanceComputed = null;
final TransactionDetailData transactionDetailData = null;
final CurrencyData currency = null;
final String externalAssetOwner = null;
return new JournalEntryData(id, officeId, officeName, glAccountName, glAccountId, glAccountCode, glAccountClassification,
transactionDate, entryType, amount, transactionId, manualEntry, entityType, entityId, createdByUserId, submittedOnDate,
createdByUserName, comments, reversed, referenceNumber, officeRunningBalance, organizationRunningBalance,
runningBalanceComputed, transactionDetailData, currency, externalAssetOwner);
}
public Integer getRowIndex() {
return rowIndex;
}
public LocalDate getTransactionDate() {
return transactionDate;
}
public void addDebits(CreditDebit debit) {
this.debits.add(debit);
}
public void addCredits(CreditDebit credit) {
this.credits.add(credit);
}
}
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.accounting.journalentry.data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.List;
import lombok.Data;
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
/**
* Immutable object representing a General Ledger Account
*
* Note: no getter/setters required as google will produce json from fields of object.
*/
@Data
public class JournalEntryData {
private Long id;
private Long officeId;
@SuppressWarnings("unused")
private String officeName;
@SuppressWarnings("unused")
private String glAccountName;
private Long glAccountId;
@SuppressWarnings("unused")
private String glAccountCode;
private EnumOptionData glAccountType;
@SuppressWarnings("unused")
private LocalDate transactionDate;
private EnumOptionData entryType;
private BigDecimal amount;
@SuppressWarnings("unused")
private CurrencyData currency;
private String transactionId;
@SuppressWarnings("unused")
private Boolean manualEntry;
@SuppressWarnings("unused")
private EnumOptionData entityType;
@SuppressWarnings("unused")
private Long entityId;
@SuppressWarnings("unused")
private String savingsAccountNo;
@SuppressWarnings("unused")
private Long createdByUserId;
@SuppressWarnings("unused")
private OffsetDateTime createdDate;
@SuppressWarnings("unused")
private String createdByUserName;
@SuppressWarnings("unused")
private String comments;
@SuppressWarnings("unused")
private Boolean reversed;
@SuppressWarnings("unused")
private String referenceNumber;
@SuppressWarnings("unused")
private BigDecimal officeRunningBalance;
@SuppressWarnings("unused")
private BigDecimal organizationRunningBalance;
@SuppressWarnings("unused")
private Boolean runningBalanceComputed;
@SuppressWarnings("unused")
private TransactionDetailData transactionDetails;
@SuppressWarnings("unused")
private OffsetDateTime submittedOnDate;
// import fields
private transient Integer rowIndex;
private String dateFormat;
private String locale;
private List<CreditDebit> credits;
private List<CreditDebit> debits;
private Long paymentTypeId;
private String currencyCode;
private String accountNumber;
private String checkNumber;
private String routingCode;
private String receiptNumber;
private String bankNumber;
private String externalAssetOwner;
private transient Long savingTransactionId;
public JournalEntryData() {}
// for opening bal bulk import
public JournalEntryData(Long officeId, LocalDate transactionDate, String currencyCode, List<CreditDebit> credits,
List<CreditDebit> debits, String locale, String dateFormat) {
this.officeId = officeId;
this.dateFormat = dateFormat;
this.locale = locale;
this.transactionDate = transactionDate;
this.currencyCode = currencyCode;
this.credits = credits;
this.debits = debits;
this.rowIndex = null;
this.paymentTypeId = null;
this.accountNumber = null;
this.checkNumber = null;
this.routingCode = null;
this.receiptNumber = null;
this.bankNumber = null;
this.comments = null;
this.id = null;
this.officeName = null;
this.glAccountName = null;
this.glAccountId = null;
this.glAccountCode = null;
this.glAccountType = null;
this.entryType = null;
this.amount = null;
this.currency = null;
this.transactionId = null;
this.manualEntry = null;
this.entityType = null;
this.entityId = null;
this.createdByUserId = null;
this.createdDate = null;
this.createdByUserName = null;
this.reversed = null;
this.referenceNumber = null;
this.officeRunningBalance = null;
this.organizationRunningBalance = null;
this.runningBalanceComputed = null;
this.transactionDetails = null;
this.submittedOnDate = null;
}
private JournalEntryData(Long officeId, LocalDate transactionDate, String currencyCode, Long paymentTypeId, Integer rowIndex,
List<CreditDebit> credits, List<CreditDebit> debits, String accountNumber, String checkNumber, String routingCode,
String receiptNumber, String bankNumber, String comments, String locale, String dateFormat) {
this.officeId = officeId;
this.dateFormat = dateFormat;
this.locale = locale;
this.transactionDate = transactionDate;
this.currencyCode = currencyCode;
this.rowIndex = rowIndex;
this.credits = credits;
this.debits = debits;
this.paymentTypeId = paymentTypeId;
this.accountNumber = accountNumber;
this.checkNumber = checkNumber;
this.routingCode = routingCode;
this.receiptNumber = receiptNumber;
this.bankNumber = bankNumber;
this.comments = comments;
this.id = null;
this.officeName = null;
this.glAccountName = null;
this.glAccountId = null;
this.glAccountCode = null;
this.glAccountType = null;
this.entryType = null;
this.amount = null;
this.currency = null;
this.transactionId = null;
this.manualEntry = null;
this.entityType = null;
this.entityId = null;
this.createdByUserId = null;
this.submittedOnDate = null;
this.createdDate = null;
this.createdByUserName = null;
this.reversed = null;
this.referenceNumber = null;
this.officeRunningBalance = null;
this.organizationRunningBalance = null;
this.runningBalanceComputed = null;
this.transactionDetails = null;
}
public JournalEntryData(final Long id, final Long officeId, final String officeName, final String glAccountName, final Long glAccountId,
final String glAccountCode, final EnumOptionData glAccountClassification, final LocalDate transactionDate,
final EnumOptionData entryType, final BigDecimal amount, final String transactionId, final Boolean manualEntry,
final EnumOptionData entityType, final Long entityId, final Long createdByUserId, final OffsetDateTime submittedOnDate,
final String createdByUserName, final String comments, final Boolean reversed, final String referenceNumber,
final BigDecimal officeRunningBalance, final BigDecimal organizationRunningBalance, final Boolean runningBalanceComputed,
final TransactionDetailData transactionDetailData, final CurrencyData currency, final String externalAssetOwner,
final String savingsAccountNo) {
this.id = id;
this.officeId = officeId;
this.officeName = officeName;
this.glAccountName = glAccountName;
this.glAccountId = glAccountId;
this.glAccountCode = glAccountCode;
this.glAccountType = glAccountClassification;
this.transactionDate = transactionDate;
this.entryType = entryType;
this.amount = amount;
this.transactionId = transactionId;
this.manualEntry = manualEntry;
this.entityType = entityType;
this.entityId = entityId;
this.createdByUserId = createdByUserId;
this.createdDate = submittedOnDate;
this.submittedOnDate = submittedOnDate;
this.createdByUserName = createdByUserName;
this.comments = comments;
this.reversed = reversed;
this.referenceNumber = referenceNumber;
this.officeRunningBalance = officeRunningBalance;
this.organizationRunningBalance = organizationRunningBalance;
this.runningBalanceComputed = runningBalanceComputed;
this.transactionDetails = transactionDetailData;
this.currency = currency;
this.externalAssetOwner = externalAssetOwner;
this.savingsAccountNo = savingsAccountNo;
}
public static JournalEntryData importInstance(Long officeId, LocalDate transactionDate, String currencyCode, Long paymentTypeId,
Integer rowIndex, List<CreditDebit> credits, List<CreditDebit> debits, String accountNumber, String checkNumber,
String routingCode, String receiptNumber, String bankNumber, String comments, String locale, String dateFormat) {
return new JournalEntryData(officeId, transactionDate, currencyCode, paymentTypeId, rowIndex, credits, debits, accountNumber,
checkNumber, routingCode, receiptNumber, bankNumber, comments, locale, dateFormat);
}
public static JournalEntryData importInstance1(Long officeId, LocalDate transactionDate, String currencyCode, List<CreditDebit> credits,
List<CreditDebit> debits, String locale, String dateFormat) {
return new JournalEntryData(officeId, transactionDate, currencyCode, credits, debits, locale, dateFormat);
}
public static JournalEntryData fromGLAccountData(final GLAccountData glAccountData) {
final Long id = null;
final Long officeId = null;
final String officeName = null;
final String glAccountName = glAccountData.getName();
final Long glAccountId = glAccountData.getId();
final String glAccountCode = glAccountData.getGlCode();
final EnumOptionData glAccountClassification = glAccountData.getType();
final LocalDate transactionDate = null;
final EnumOptionData entryType = null;
final BigDecimal amount = null;
final String transactionId = null;
final Boolean manualEntry = null;
final EnumOptionData entityType = null;
final Long entityId = null;
final Long createdByUserId = null;
final OffsetDateTime submittedOnDate = null;
final String createdByUserName = null;
final String comments = null;
final Boolean reversed = null;
final String referenceNumber = null;
final BigDecimal officeRunningBalance = null;
final BigDecimal organizationRunningBalance = null;
final Boolean runningBalanceComputed = null;
final TransactionDetailData transactionDetailData = null;
final CurrencyData currency = null;
final String externalAssetOwner = null;
return new JournalEntryData(id, officeId, officeName, glAccountName, glAccountId, glAccountCode, glAccountClassification,
transactionDate, entryType, amount, transactionId, manualEntry, entityType, entityId, createdByUserId, submittedOnDate,
createdByUserName, comments, reversed, referenceNumber, officeRunningBalance, organizationRunningBalance,
runningBalanceComputed, transactionDetailData, currency, externalAssetOwner, null);
}
public Integer getRowIndex() {
return rowIndex;
}
public LocalDate getTransactionDate() {
return transactionDate;
}
public void addDebits(CreditDebit debit) {
this.debits.add(debit);
}
public void addCredits(CreditDebit credit) {
this.credits.add(credit);
}
}
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.accounting.journalentry.domain;
import java.time.LocalDate;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -41,4 +42,27 @@ public interface JournalEntryRepository extends JpaRepository<JournalEntry, Long
@Query("select journalEntry from JournalEntry journalEntry where journalEntry.transactionId= :transactionId and journalEntry.reversed=false and journalEntry.entityType = :entityType order by journalEntry.transactionDate asc, journalEntry.createdDate asc, journalEntry.id asc")
List<JournalEntry> findJournalEntries(@Param("transactionId") String transactionId, @Param("entityType") Integer entityType);
@Query("""
SELECT DISTINCT je.transactionDate
FROM JournalEntry je
WHERE je.transactionDate>:afterDate
""")
List<LocalDate> findTransactionDatesAfter(LocalDate afterDate);
@Query("""
SELECT je.office.id,
je.glAccount.id,
SUM(CASE WHEN je.type = 1
THEN -1 * je.amount
ELSE je.amount
END),
je.transactionDate,
je.createdDate,
SUM(je.amount)
FROM JournalEntry je
WHERE je.transactionDate = :transactionDate
GROUP BY je.office.id, je.glAccount.id, je.transactionDate, je.createdDate
""")
List<Object[]> findTrialBalanceLinesForDate(@Param("transactionDate") LocalDate transactionDate);
}
@@ -45,10 +45,14 @@ import org.springframework.stereotype.Component;
*/
@Component
@RequiredArgsConstructor
public final class JournalEntryCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<JournalEntryCommand> {
public class JournalEntryCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<JournalEntryCommand> {
private final FromJsonHelper fromApiJsonHelper;
protected Set<String> getSupportedParameters() {
return JournalEntryJsonInputParams.getAllValues();
}
@Override
public JournalEntryCommand commandFromApiJson(final String json) {
if (StringUtils.isBlank(json)) {
@@ -56,7 +60,7 @@ public final class JournalEntryCommandFromApiJsonDeserializer extends AbstractFr
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
final Set<String> supportedParameters = JournalEntryJsonInputParams.getAllValues();
final Set<String> supportedParameters = this.getSupportedParameters();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
final JsonElement element = this.fromApiJsonHelper.parse(json);

Some files were not shown because too many files have changed in this diff Show More