From 65b2ea99eae6c342a4cf68cf4b1cb4c298a0a29f Mon Sep 17 00:00:00 2001 From: kapil-panchal Date: Thu, 4 Sep 2025 12:37:39 +0530 Subject: [PATCH] FINERACT-2361: Refactor --- .../data/IndividualClientData.java | 69 ++--- .../data/IndividualCollectionSheetData.java | 11 +- ...IndividualCollectionSheetLoanFlatData.java | 94 +----- .../repository/CollectionSheetDao.java | 283 ++++++++++++++++++ ...ollectionSheetReadPlatformServiceImpl.java | 229 ++------------ .../starter/CollectionSheetConfiguration.java | 8 +- 6 files changed, 347 insertions(+), 347 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/repository/CollectionSheetDao.java diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualClientData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualClientData.java index 1585d84c400..c9507b587df 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualClientData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualClientData.java @@ -18,77 +18,50 @@ */ package org.apache.fineract.portfolio.collectionsheet.data; -import java.util.Collection; +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; /** * Immutable data object for clients with loans due for disbursement or collection. */ -public final class IndividualClientData { +@Getter +@Setter +@AllArgsConstructor +@Builder +public final class IndividualClientData implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; private final Long clientId; private final String clientName; - private Collection loans; - private Collection savings; + private List loans; + private List savings; public static IndividualClientData instance(final Long clientId, final String clientName) { - final Collection loans = null; - final Collection savings = null; - return new IndividualClientData(clientId, clientName, loans, savings); + return new IndividualClientData(clientId, clientName, new ArrayList<>(), new ArrayList<>()); } - public static IndividualClientData withSavings(final IndividualClientData client, final Collection savings) { - + public static IndividualClientData withSavings(final IndividualClientData client, final List savings) { return new IndividualClientData(client.clientId, client.clientName, client.loans, savings); } - public static IndividualClientData withLoans(final IndividualClientData client, final Collection loans) { - + public static IndividualClientData withLoans(final IndividualClientData client, final List loans) { return new IndividualClientData(client.clientId, client.clientName, loans, client.savings); } - /** - * @param clientId - * @param clientName - * @param loans - * @param savings - */ - private IndividualClientData(Long clientId, String clientName, Collection loans, Collection savings) { - this.clientId = clientId; - this.clientName = clientName; - this.loans = loans; - this.savings = savings; - } - - public Long getClientId() { - return this.clientId; - } - - public String getClientName() { - return this.clientName; - } - - public Collection getLoans() { - return this.loans; - } - - public void setLoans(final Collection loans) { - this.loans = loans; - } - public void addLoans(LoanDueData loans) { if (this.loans != null) { this.loans.add(loans); } } - public Collection getSavings() { - return this.savings; - } - - public void setSavings(Collection savings) { - this.savings = savings; - } - public void addSavings(SavingsDueData savings) { if (this.savings != null) { this.savings.add(savings); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualCollectionSheetData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualCollectionSheetData.java index 56ab0719124..6568de41dcf 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualCollectionSheetData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualCollectionSheetData.java @@ -21,7 +21,7 @@ import java.io.Serial; import java.io.Serializable; import java.time.LocalDate; -import java.util.Collection; +import java.util.List; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; @@ -36,12 +36,7 @@ public final class IndividualCollectionSheetData implements Serializable { @Serial private static final long serialVersionUID = 1L; - @SuppressWarnings("unused") private final LocalDate dueDate; - @SuppressWarnings("unused") - private final Collection clients; - - @SuppressWarnings("unused") - private final Collection paymentTypeOptions; - + private final List clients; + private final List paymentTypeOptions; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualCollectionSheetLoanFlatData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualCollectionSheetLoanFlatData.java index b4731e49a03..599a3fefaab 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualCollectionSheetLoanFlatData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/data/IndividualCollectionSheetLoanFlatData.java @@ -19,11 +19,17 @@ package org.apache.fineract.portfolio.collectionsheet.data; import java.math.BigDecimal; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; import org.apache.fineract.organisation.monetary.data.CurrencyData; /** * Immutable data object for extracting flat data for joint liability group's collection sheet. */ +@Getter +@Setter +@AllArgsConstructor public class IndividualCollectionSheetLoanFlatData { private final String clientName; @@ -43,85 +49,6 @@ public class IndividualCollectionSheetLoanFlatData { private BigDecimal feeDue = BigDecimal.ZERO; private BigDecimal feePaid = BigDecimal.ZERO; - public IndividualCollectionSheetLoanFlatData(final String clientName, final Long clientId, final Long loanId, final String accountId, - final Integer accountStatusId, final String productShortName, final Long productId, final CurrencyData currency, - final BigDecimal disbursementAmount, final BigDecimal principalDue, final BigDecimal principalPaid, - final BigDecimal interestDue, final BigDecimal interestPaid, final BigDecimal chargesDue, final BigDecimal feeDue, - final BigDecimal feePaid) { - this.clientName = clientName; - this.clientId = clientId; - this.loanId = loanId; - this.accountId = accountId; - this.accountStatusId = accountStatusId; - this.productShortName = productShortName; - this.productId = productId; - this.currency = currency; - this.disbursementAmount = disbursementAmount; - this.principalDue = principalDue; - this.principalPaid = principalPaid; - this.interestDue = interestDue; - this.interestPaid = interestPaid; - this.chargesDue = chargesDue; - this.feeDue = feeDue; - this.feePaid = feePaid; - } - - public String getClientName() { - return this.clientName; - } - - public Long getClientId() { - return this.clientId; - } - - public Long getLoanId() { - return this.loanId; - } - - public String getAccountId() { - return this.accountId; - } - - public Integer getAccountStatusId() { - return this.accountStatusId; - } - - public String getProductShortName() { - return this.productShortName; - } - - public Long getProductId() { - return this.productId; - } - - public CurrencyData getCurrency() { - return this.currency; - } - - public BigDecimal getDisbursementAmount() { - return this.disbursementAmount; - } - - public BigDecimal getPrincipalDue() { - return this.principalDue; - } - - public BigDecimal getPrincipalPaid() { - return this.principalPaid; - } - - public BigDecimal getInterestDue() { - return this.interestDue; - } - - public BigDecimal getInterestPaid() { - return this.interestPaid; - } - - public BigDecimal getChargesDue() { - return this.chargesDue; - } - public LoanDueData getLoanDueData() { return new LoanDueData(this.loanId, this.accountId, this.accountStatusId, this.productShortName, this.productId, this.currency, this.disbursementAmount, this.principalDue, this.principalPaid, this.interestDue, this.interestPaid, this.chargesDue, @@ -131,13 +58,4 @@ public LoanDueData getLoanDueData() { public IndividualClientData getClientData() { return IndividualClientData.instance(this.clientId, this.clientName); } - - public BigDecimal getFeeDue() { - return this.feeDue; - } - - public BigDecimal getFeePaid() { - return this.feePaid; - } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/repository/CollectionSheetDao.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/repository/CollectionSheetDao.java new file mode 100644 index 00000000000..dd9b7d702a1 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/repository/CollectionSheetDao.java @@ -0,0 +1,283 @@ +/** + * 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.portfolio.collectionsheet.repository; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.domain.JdbcSupport; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.portfolio.collectionsheet.data.IndividualClientData; +import org.apache.fineract.portfolio.collectionsheet.data.IndividualCollectionSheetLoanFlatData; +import org.apache.fineract.portfolio.collectionsheet.data.SavingsDueData; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class CollectionSheetDao { + + private final DatabaseSpecificSQLGenerator sqlGenerator; + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + public List getIndividualCollectionSheetFlatDataList(LocalDate transactionDate, + String officeHierarchy, Long officeId, Long staffId) { + final boolean checkForOfficeId = officeId != null; + final boolean checkForStaffId = staffId != null; + final String transactionDateStr = DateUtils.DEFAULT_DATE_FORMATTER.format(transactionDate); + + final StringBuilder sqlString = getIndividualCollectionSheetFlatDataSql(checkForOfficeId, checkForStaffId); + + final SqlParameterSource namedParameters = getNamedParameters(transactionDateStr, officeHierarchy, officeId, staffId); + + return this.namedParameterJdbcTemplate.query(sqlString.toString(), namedParameters, rowMapper()); + } + + private SqlParameterSource getNamedParameters(String transactionDateStr, String officeHierarchy, Long officeId, Long staffId) { + final SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("dueDate", transactionDateStr) + .addValue("officeHierarchy", officeHierarchy); + + final boolean checkForOfficeId = officeId != null; + final boolean checkForStaffId = staffId != null; + + if (checkForOfficeId) { + ((MapSqlParameterSource) namedParameters).addValue("officeId", officeId); + } + if (checkForStaffId) { + ((MapSqlParameterSource) namedParameters).addValue("staffId", staffId); + } + return namedParameters; + } + + private StringBuilder getIndividualCollectionSheetFlatDataSql(final boolean checkForOfficeId, final boolean checkforStaffId) { + StringBuilder sqlString = new StringBuilder(); + sqlString.append("SELECT loandata.*, SUM(lc.amount_outstanding_derived) AS chargesDue "); + sqlString.append("FROM (SELECT cl.display_name AS clientName, "); + sqlString.append("cl.id AS clientId, "); + sqlString.append("ln.id AS loanId, "); + sqlString.append("ln.account_no AS accountId, "); + sqlString.append("ln.loan_status_id AS accountStatusId, "); + sqlString.append("pl.short_name AS productShortName, "); + sqlString.append("ln.product_id AS productId, "); + sqlString.append("ln.currency_code AS currencyCode, "); + sqlString.append("ln.currency_digits AS currencyDigits, "); + sqlString.append("ln.currency_multiplesof AS inMultiplesOf, "); + sqlString.append("rc."); + sqlString.append(sqlGenerator.escape("name")); + sqlString.append(" AS currencyName, "); + sqlString.append("rc.display_symbol AS currencyDisplaySymbol, "); + sqlString.append("rc.internationalized_name_code AS currencyNameCode, "); + sqlString.append("(CASE WHEN ln.loan_status_id = 200 THEN ln.principal_amount ELSE NULL END) AS " + "disbursementAmount, "); + sqlString.append("SUM(COALESCE((CASE WHEN ln.loan_status_id = 300 THEN ls.principal_amount ELSE 0" + + ".0 END), 0.0) - COALESCE((CASE WHEN ln.loan_status_id = 300 THEN ls" + + ".principal_completed_derived ELSE 0.0 END), 0.0)) AS principalDue, "); + sqlString.append("ln.principal_repaid_derived AS principalPaid, "); + sqlString.append("SUM(COALESCE((CASE WHEN ln.loan_status_id = 300 THEN ls.interest_amount ELSE 0.0" + + " END), 0.0) - COALESCE((CASE WHEN ln.loan_status_id = 300 THEN ls" + + ".interest_completed_derived ELSE 0.0 END), 0.0)) AS interestDue, "); + sqlString.append("ln.interest_repaid_derived AS interestPaid, "); + sqlString.append("SUM(COALESCE((CASE WHEN ln.loan_status_id = 300 THEN ls.fee_charges_amount ELSE " + + "0.0 END), 0.0) - COALESCE((CASE WHEN ln.loan_status_id = 300 THEN ls" + + ".fee_charges_completed_derived ELSE 0.0 END), 0.0)) AS feeDue, "); + sqlString.append("ln.fee_charges_repaid_derived AS feePaid "); + sqlString.append("FROM m_loan ln "); + sqlString.append("JOIN m_client cl ON cl.id = ln.client_id "); + sqlString.append("LEFT JOIN m_office ofc ON ofc.id = cl.office_id AND ofc.hierarchy LIKE " + ":officeHierarchy "); + sqlString.append("LEFT JOIN m_product_loan pl ON pl.id = ln.product_id "); + sqlString.append("LEFT JOIN m_currency rc ON rc."); + sqlString.append(sqlGenerator.escape("code")); + sqlString.append(" = ln.currency_code "); + sqlString + .append("JOIN m_loan_repayment_schedule ls ON ls.loan_id = ln.id AND ls.completed_derived = 0 AND ls.duedate <= :dueDate "); + sqlString.append("WHERE "); + if (checkForOfficeId) { + sqlString.append("ofc.id = :officeId AND "); + } + if (checkforStaffId) { + sqlString.append("ln.loan_officer_id = :staffId AND "); + } + sqlString.append("(ln.loan_status_id = 300) "); + sqlString.append("AND ln.group_id IS NULL GROUP BY cl.id, ln.id ORDER BY cl.id, ln.id ) " + "loandata "); + sqlString.append( + "LEFT JOIN m_loan_charge lc ON lc.loan_id = loandata.loanId AND lc.is_paid_derived = false AND lc.is_active = true AND ( lc.due_for_collection_as_of_date <= :dueDate OR lc.charge_time_enum = 1) "); + sqlString.append("GROUP BY loandata.clientId, loandata.loanId ORDER BY loandata.clientId, loandata.loanId "); + + return sqlString; + } + + private RowMapper rowMapper() { + return (rs, rowNum) -> { + final String clientName = rs.getString("clientName"); + final Long clientId = JdbcSupport.getLong(rs, "clientId"); + final Long loanId = JdbcSupport.getLong(rs, "loanId"); + final String accountId = rs.getString("accountId"); + final Integer accountStatusId = JdbcSupport.getInteger(rs, "accountStatusId"); + final String productShortName = rs.getString("productShortName"); + final Long productId = JdbcSupport.getLong(rs, "productId"); + final String currencyCode = rs.getString("currencyCode"); + final String currencyName = rs.getString("currencyName"); + final String currencyNameCode = rs.getString("currencyNameCode"); + final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); + final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + + CurrencyData currencyData = null; + if (currencyCode != null) { + currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, + currencyNameCode); + } + + final BigDecimal disbursementAmount = rs.getBigDecimal("disbursementAmount"); + final BigDecimal principalDue = rs.getBigDecimal("principalDue"); + final BigDecimal principalPaid = rs.getBigDecimal("principalPaid"); + final BigDecimal interestDue = rs.getBigDecimal("interestDue"); + final BigDecimal interestPaid = rs.getBigDecimal("interestPaid"); + final BigDecimal chargesDue = rs.getBigDecimal("chargesDue"); + final BigDecimal feeDue = rs.getBigDecimal("feeDue"); + final BigDecimal feePaid = rs.getBigDecimal("feePaid"); + + return new IndividualCollectionSheetLoanFlatData(clientName, clientId, loanId, accountId, accountStatusId, productShortName, + productId, currencyData, disbursementAmount, principalDue, principalPaid, interestDue, interestPaid, chargesDue, feeDue, + feePaid); + }; + } + + public List getIndividualClientData(String transactionDateStr, String officeHierarchy, Long officeId, + Long staffId) { + final boolean checkForOfficeId = officeId != null; + final boolean checkForStaffId = staffId != null; + + final StringBuilder sqlString = getIndividualClientDataSql(checkForOfficeId, checkForStaffId); + + final SqlParameterSource namedParameters = getNamedParameters(transactionDateStr, officeHierarchy, officeId, staffId); + + return this.namedParameterJdbcTemplate.query(sqlString.toString(), namedParameters, individualClientDataExtractor()); + } + + private StringBuilder getIndividualClientDataSql(final boolean checkForOfficeId, final boolean checkForStaffId) { + final StringBuilder sqlString = new StringBuilder(400); + + sqlString.append("SELECT (CASE WHEN sa.deposit_type_enum=100 THEN 'Saving Deposit' ELSE (CASE WHEN" + + " sa.deposit_type_enum=300 THEN 'Recurring Deposit' ELSE 'Current " + + "Deposit' END) END) AS depositAccountType, cl.display_name AS clientName," + " cl.id AS clientId, "); + sqlString.append("sa.id AS savingsId, "); + sqlString.append("sa.account_no AS accountId, "); + sqlString.append("sa.status_enum AS accountStatusId, "); + sqlString.append("sp.short_name AS productShortName, "); + sqlString.append("sp.id AS productId, "); + sqlString.append("sa.currency_code AS currencyCode, "); + sqlString.append("sa.currency_digits AS currencyDigits, "); + sqlString.append("sa.currency_multiplesof AS inMultiplesOf, "); + sqlString.append("rc."); + sqlString.append(sqlGenerator.escape("name")); + sqlString.append(" AS currencyName, "); + sqlString.append("rc.display_symbol AS currencyDisplaySymbol, "); + sqlString.append("rc.internationalized_name_code AS currencyNameCode, "); + sqlString.append("SUM(COALESCE(mss.deposit_amount,0) - coalesce(mss" + ".deposit_amount_completed_derived,0)) AS dueAmount "); + sqlString.append("FROM m_savings_account sa "); + sqlString.append("JOIN m_client cl ON cl.id = sa.client_id "); + sqlString.append("JOIN m_savings_product sp ON sa.product_id = sp.id "); + sqlString.append( + "LEFT JOIN m_deposit_account_recurring_detail dard ON sa.id = dard.savings_account_id AND dard.is_mandatory = true AND dard.is_calendar_inherited = false "); + sqlString.append( + "LEFT JOIN m_mandatory_savings_schedule mss ON mss.savings_account_id=sa.id AND mss.completed_derived = 0 AND mss.duedate <= :dueDate "); + sqlString.append("LEFT JOIN m_office ofc ON ofc.id = cl.office_id AND ofc.hierarchy like " + ":officeHierarchy "); + sqlString.append("LEFT JOIN m_currency rc ON rc."); + sqlString.append(sqlGenerator.escape("code")); + sqlString.append(" = sa.currency_code "); + sqlString.append("WHERE sa.status_enum=300 AND sa.group_id is null AND sa" + ".deposit_type_enum in (100,300,400) "); + sqlString.append("AND (cl.status_enum = 300 OR (cl.status_enum = 600 AND cl.closedon_date" + " >= :dueDate)) "); + if (checkForOfficeId) { + sqlString.append("AND ofc.id = :officeId "); + } + if (checkForStaffId) { + sqlString.append("AND sa.field_officer_id = :staffId "); + } + sqlString.append("GROUP BY cl.id, sa.id ORDER BY cl.id, sa.id "); + return sqlString; + } + + private ResultSetExtractor> individualClientDataExtractor() { + return rs -> { + List clientData = new ArrayList<>(); + int rowNum = 0; + + IndividualClientData client = null; + Long previousClientId = null; + + while (rs.next()) { + final Long clientId = JdbcSupport.getLong(rs, "clientId"); + + // if we encounter a new client, create a fresh IndividualClientData + if (previousClientId == null || !clientId.equals(previousClientId)) { + final String clientName = rs.getString("clientName"); + + client = IndividualClientData.instance(clientId, clientName); + client = IndividualClientData.withSavings(client, new ArrayList()); + + clientData.add(client); + previousClientId = clientId; + } + + // map savings for this row and attach to current client + SavingsDueData saving = savingsDueDataRowMapper().mapRow(rs, rowNum); + client.addSavings(saving); + + rowNum++; + } + + return clientData; + }; + } + + private RowMapper savingsDueDataRowMapper() { + return (rs, rowNum) -> { + final Long savingsId = rs.getLong("savingsId"); + final String accountId = rs.getString("accountId"); + final Integer accountStatusId = JdbcSupport.getInteger(rs, "accountStatusId"); + final String productName = rs.getString("productShortName"); + final Long productId = rs.getLong("productId"); + final BigDecimal dueAmount = rs.getBigDecimal("dueAmount"); + + final String currencyCode = rs.getString("currencyCode"); + final String currencyName = rs.getString("currencyName"); + final String currencyNameCode = rs.getString("currencyNameCode"); + final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); + final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); + + final String depositAccountType = rs.getString("depositAccountType"); + + // build CurrencyData + final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, + currencyNameCode); + + return SavingsDueData.instance(savingsId, accountId, accountStatusId, productName, productId, currency, dueAmount, + depositAccountType); + }; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java index baa410f2f21..5c307c260b6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java @@ -30,9 +30,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Set; -import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.api.JsonQuery; import org.apache.fineract.infrastructure.core.data.EnumOptionData; @@ -43,7 +44,6 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType; -import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository; import org.apache.fineract.portfolio.calendar.domain.CalendarRepositoryWrapper; import org.apache.fineract.portfolio.calendar.exception.NotValidRecurringDateException; import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService; @@ -56,6 +56,7 @@ import org.apache.fineract.portfolio.collectionsheet.data.JLGGroupData; import org.apache.fineract.portfolio.collectionsheet.data.LoanDueData; import org.apache.fineract.portfolio.collectionsheet.data.SavingsDueData; +import org.apache.fineract.portfolio.collectionsheet.repository.CollectionSheetDao; import org.apache.fineract.portfolio.collectionsheet.serialization.CollectionSheetGenerateCommandFromApiJsonDeserializer; import org.apache.fineract.portfolio.group.data.CenterData; import org.apache.fineract.portfolio.group.data.GroupGeneralData; @@ -74,7 +75,9 @@ import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.stereotype.Service; +@Service public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetReadPlatformService { private final PlatformSecurityContext context; @@ -85,12 +88,11 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe private final CalendarRepositoryWrapper calendarRepositoryWrapper; private final AttendanceDropdownReadPlatformService attendanceDropdownReadPlatformService; private final MandatorySavingsCollectionsheetExtractor mandatorySavingsExtractor; - private final CodeValueReadPlatformService codeValueReadPlatformService; private final PaymentTypeReadPlatformService paymentTypeReadPlatformService; private final CalendarReadPlatformService calendarReadPlatformService; private final ConfigurationDomainService configurationDomainService; - private final CalendarInstanceRepository calendarInstanceRepository; private final DatabaseSpecificSQLGenerator sqlGenerator; + private final CollectionSheetDao collectionSheetDao; public CollectionSheetReadPlatformServiceImpl(final PlatformSecurityContext context, final NamedParameterJdbcTemplate namedParameterJdbcTemplate, final CenterReadPlatformService centerReadPlatformService, @@ -98,10 +100,9 @@ public CollectionSheetReadPlatformServiceImpl(final PlatformSecurityContext cont final CollectionSheetGenerateCommandFromApiJsonDeserializer collectionSheetGenerateCommandFromApiJsonDeserializer, final CalendarRepositoryWrapper calendarRepositoryWrapper, final AttendanceDropdownReadPlatformService attendanceDropdownReadPlatformService, - final CodeValueReadPlatformService codeValueReadPlatformService, final PaymentTypeReadPlatformService paymentTypeReadPlatformService, final CalendarReadPlatformService calendarReadPlatformService, final ConfigurationDomainService configurationDomainService, - final CalendarInstanceRepository calendarInstanceRepository, DatabaseSpecificSQLGenerator sqlGenerator) { + final DatabaseSpecificSQLGenerator sqlGenerator, final CollectionSheetDao collectionSheetDao) { this.context = context; this.centerReadPlatformService = centerReadPlatformService; this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; @@ -109,12 +110,11 @@ public CollectionSheetReadPlatformServiceImpl(final PlatformSecurityContext cont this.groupReadPlatformService = groupReadPlatformService; this.calendarRepositoryWrapper = calendarRepositoryWrapper; this.attendanceDropdownReadPlatformService = attendanceDropdownReadPlatformService; - this.codeValueReadPlatformService = codeValueReadPlatformService; this.paymentTypeReadPlatformService = paymentTypeReadPlatformService; this.calendarReadPlatformService = calendarReadPlatformService; this.configurationDomainService = configurationDomainService; - this.calendarInstanceRepository = calendarInstanceRepository; this.sqlGenerator = sqlGenerator; + this.collectionSheetDao = collectionSheetDao; mandatorySavingsExtractor = new MandatorySavingsCollectionsheetExtractor(sqlGenerator); } @@ -672,12 +672,12 @@ public SavingsDueData mapRow(ResultSet rs, int rowNum) throws SQLException { @Override public IndividualCollectionSheetData generateIndividualCollectionSheet(final JsonQuery query) { - this.collectionSheetGenerateCommandFromApiJsonDeserializer.validateForGenerateCollectionSheetOfIndividuals(query.json()); + collectionSheetGenerateCommandFromApiJsonDeserializer.validateForGenerateCollectionSheetOfIndividuals(query.json()); final LocalDate transactionDate = query.localDateValueOfParameterNamed(transactionDateParamName); final String transactionDateStr = DateUtils.DEFAULT_DATE_FORMATTER.format(transactionDate); - final AppUser currentUser = this.context.authenticatedUser(); + final AppUser currentUser = context.authenticatedUser(); final String hierarchy = currentUser.getOffice().getHierarchy(); final String officeHierarchy = hierarchy + "%"; @@ -686,9 +686,6 @@ public IndividualCollectionSheetData generateIndividualCollectionSheet(final Jso final boolean checkForOfficeId = officeId != null; final boolean checkForStaffId = staffId != null; - final IndividualCollectionSheetFaltDataMapper mapper = new IndividualCollectionSheetFaltDataMapper(checkForOfficeId, - checkForStaffId, sqlGenerator); - final SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("dueDate", transactionDateStr) .addValue("officeHierarchy", officeHierarchy); @@ -699,205 +696,37 @@ public IndividualCollectionSheetData generateIndividualCollectionSheet(final Jso ((MapSqlParameterSource) namedParameters).addValue("staffId", staffId); } - final Collection collectionSheetFlatDatas = this.namedParameterJdbcTemplate - .query(mapper.sqlSchema(), namedParameters, mapper); + final List collectionSheetFlatData = collectionSheetDao + .getIndividualCollectionSheetFlatDataList(transactionDate, officeHierarchy, officeId, staffId); - IndividualMandatorySavingsCollectionsheetExtractor mandatorySavingsExtractor = new IndividualMandatorySavingsCollectionsheetExtractor( - checkForOfficeId, checkForStaffId, sqlGenerator); - // mandatory savings data for collection sheet - Collection clientData = this.namedParameterJdbcTemplate - .query(mandatorySavingsExtractor.collectionSheetSchema(), namedParameters, mandatorySavingsExtractor); + final List clientData = collectionSheetDao.getIndividualClientData(transactionDateStr, officeHierarchy, + officeId, staffId); // merge savings data into loan data - mergeLoanData(collectionSheetFlatDatas, (List) clientData); + final List response = mergeLoanData(collectionSheetFlatData, clientData); - final Collection paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); + final List paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); - return new IndividualCollectionSheetData(transactionDate, clientData, paymentOptions); + return new IndividualCollectionSheetData(transactionDate, response, paymentOptions); } - private static final class IndividualCollectionSheetFaltDataMapper implements RowMapper { - - private final String sql; - - IndividualCollectionSheetFaltDataMapper(final boolean checkForOfficeId, final boolean checkforStaffId, - DatabaseSpecificSQLGenerator sqlGenerator) { - StringBuilder sb = new StringBuilder(); - sb.append("SELECT loandata.*, sum(lc.amount_outstanding_derived) as chargesDue "); - sb.append("from (SELECT cl.display_name As clientName, "); - sb.append("cl.id As clientId, ln.id As loanId, ln.account_no As accountId, ln.loan_status_id As accountStatusId,"); - sb.append(" pl.short_name As productShortName, ln.product_id As productId, "); - sb.append("ln.currency_code as currencyCode, ln.currency_digits as currencyDigits, ln.currency_multiplesof as inMultiplesOf, "); - sb.append("rc." + sqlGenerator.escape("name") - + " as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, "); - sb.append("(CASE WHEN ln.loan_status_id = 200 THEN ln.principal_amount ELSE null END) As disbursementAmount, "); - sb.append( - "sum(COALESCE((CASE WHEN ln.loan_status_id = 300 THEN ls.principal_amount ELSE 0.0 END), 0.0) - COALESCE((CASE WHEN ln.loan_status_id = 300 THEN ls.principal_completed_derived ELSE 0.0 END), 0.0)) As principalDue, "); - sb.append("ln.principal_repaid_derived As principalPaid, "); - sb.append( - "sum(COALESCE((CASE WHEN ln.loan_status_id = 300 THEN ls.interest_amount ELSE 0.0 END), 0.0) - COALESCE((CASE WHEN ln.loan_status_id = 300 THEN ls.interest_completed_derived ELSE 0.0 END), 0.0)) As interestDue, "); - sb.append("ln.interest_repaid_derived As interestPaid, "); - sb.append( - "sum(COALESCE((CASE WHEN ln.loan_status_id = 300 THEN ls.fee_charges_amount ELSE 0.0 END), 0.0) - COALESCE((CASE WHEN ln.loan_status_id = 300 THEN ls.fee_charges_completed_derived ELSE 0.0 END), 0.0)) As feeDue, "); - sb.append("ln.fee_charges_repaid_derived As feePaid "); - sb.append("FROM m_loan ln "); - sb.append("JOIN m_client cl ON cl.id = ln.client_id "); - sb.append("LEFT JOIN m_office of ON of.id = cl.office_id AND of.hierarchy like :officeHierarchy "); - sb.append("LEFT JOIN m_product_loan pl ON pl.id = ln.product_id "); - sb.append("LEFT JOIN m_currency rc on rc." + sqlGenerator.escape("code") + " = ln.currency_code "); - sb.append("JOIN m_loan_repayment_schedule ls ON ls.loan_id = ln.id AND ls.completed_derived = 0 AND ls.duedate <= :dueDate "); - sb.append("where "); - if (checkForOfficeId) { - sb.append("of.id = :officeId and "); - } - if (checkforStaffId) { - sb.append("ln.loan_officer_id = :staffId and "); - } - sb.append("(ln.loan_status_id = 300) "); - sb.append("and ln.group_id is null GROUP BY cl.id , ln.id ORDER BY cl.id , ln.id ) loandata "); - sb.append( - "LEFT JOIN m_loan_charge lc ON lc.loan_id = loandata.loanId AND lc.is_paid_derived = false AND lc.is_active = true AND ( lc.due_for_collection_as_of_date <= :dueDate OR lc.charge_time_enum = 1) "); - sb.append("GROUP BY loandata.clientId, loandata.loanId ORDER BY loandata.clientId, loandata.loanId "); - - sql = sb.toString(); - } - - public String sqlSchema() { - return sql; - } - - @Override - public IndividualCollectionSheetLoanFlatData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException { - final String clientName = rs.getString("clientName"); - final Long clientId = JdbcSupport.getLong(rs, "clientId"); - final Long loanId = JdbcSupport.getLong(rs, "loanId"); - final String accountId = rs.getString("accountId"); - final Integer accountStatusId = JdbcSupport.getInteger(rs, "accountStatusId"); - final String productShortName = rs.getString("productShortName"); - final Long productId = JdbcSupport.getLong(rs, "productId"); - - final String currencyCode = rs.getString("currencyCode"); - final String currencyName = rs.getString("currencyName"); - final String currencyNameCode = rs.getString("currencyNameCode"); - final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); - final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); - CurrencyData currencyData = null; - if (currencyCode != null) { - currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol, - currencyNameCode); - } - - final BigDecimal disbursementAmount = rs.getBigDecimal("disbursementAmount"); - final BigDecimal principalDue = rs.getBigDecimal("principalDue"); - final BigDecimal principalPaid = rs.getBigDecimal("principalPaid"); - final BigDecimal interestDue = rs.getBigDecimal("interestDue"); - final BigDecimal interestPaid = rs.getBigDecimal("interestPaid"); - final BigDecimal chargesDue = rs.getBigDecimal("chargesDue"); - final BigDecimal feeDue = rs.getBigDecimal("feeDue"); - final BigDecimal feePaid = rs.getBigDecimal("feePaid"); - - return new IndividualCollectionSheetLoanFlatData(clientName, clientId, loanId, accountId, accountStatusId, productShortName, - productId, currencyData, disbursementAmount, principalDue, principalPaid, interestDue, interestPaid, chargesDue, feeDue, - feePaid); - } - - } - - private static final class IndividualMandatorySavingsCollectionsheetExtractor - implements ResultSetExtractor> { - - private final SavingsDueDataMapper savingsDueDataMapper = new SavingsDueDataMapper(); - - private final String sql; - - IndividualMandatorySavingsCollectionsheetExtractor(final boolean checkForOfficeId, final boolean checkforStaffId, - DatabaseSpecificSQLGenerator sqlGenerator) { - - final StringBuilder sb = new StringBuilder(400); - sb.append( - "SELECT (CASE WHEN sa.deposit_type_enum=100 THEN 'Saving Deposit' ELSE (CASE WHEN sa.deposit_type_enum=300 THEN 'Recurring Deposit' ELSE 'Current Deposit' END) END) as depositAccountType, cl.display_name As clientName, cl.id As clientId, "); - sb.append("sa.id As savingsId, sa.account_no As accountId, sa.status_enum As accountStatusId, "); - sb.append("sp.short_name As productShortName, sp.id As productId, "); - sb.append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, "); - sb.append("rc." + sqlGenerator.escape("name") - + " as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, "); - sb.append("SUM(COALESCE(mss.deposit_amount,0) - coalesce(mss.deposit_amount_completed_derived,0)) as dueAmount "); - sb.append("FROM m_savings_account sa "); - sb.append("JOIN m_client cl ON cl.id = sa.client_id "); - sb.append("JOIN m_savings_product sp ON sa.product_id=sp.id "); - sb.append( - "LEFT JOIN m_deposit_account_recurring_detail dard ON sa.id = dard.savings_account_id AND dard.is_mandatory = true AND dard.is_calendar_inherited = false "); - sb.append( - "LEFT JOIN m_mandatory_savings_schedule mss ON mss.savings_account_id=sa.id AND mss.completed_derived = 0 AND mss.duedate <= :dueDate "); - sb.append("LEFT JOIN m_office of ON of.id = cl.office_id AND of.hierarchy like :officeHierarchy "); - sb.append("LEFT JOIN m_currency rc on rc." + sqlGenerator.escape("code") + " = sa.currency_code "); - sb.append("WHERE sa.status_enum=300 and sa.group_id is null and sa.deposit_type_enum in (100,300,400) "); - sb.append("and (cl.status_enum = 300 or (cl.status_enum = 600 and cl.closedon_date >= :dueDate)) "); - if (checkForOfficeId) { - sb.append("and of.id = :officeId "); - } - if (checkforStaffId) { - sb.append("and sa.field_officer_id = :staffId "); - } - sb.append("GROUP BY cl.id , sa.id ORDER BY cl.id , sa.id "); - - this.sql = sb.toString(); - } - - public String collectionSheetSchema() { - return this.sql; - } - - @SuppressWarnings("null") - @Override - public Collection extractData(ResultSet rs) throws SQLException, DataAccessException { - List clientData = new ArrayList<>(); - int rowNum = 0; + private List mergeLoanData(final List loanFlatDataList, + List clientDatas) { - IndividualClientData client = null; - Long previousClientId = null; + Map responseMap = new LinkedHashMap<>(); - while (rs.next()) { - final Long clientId = JdbcSupport.getLong(rs, "clientId"); - if (previousClientId == null || clientId.compareTo(previousClientId) != 0) { - final String clientName = rs.getString("clientName"); - client = IndividualClientData.instance(clientId, clientName); - client = IndividualClientData.withSavings(client, new ArrayList()); - clientData.add(client); - previousClientId = clientId; - } - SavingsDueData saving = savingsDueDataMapper.mapRow(rs, rowNum); - client.addSavings(saving); - rowNum++; - } - - return clientData; + for (IndividualClientData element : clientDatas) { + responseMap.putIfAbsent(element.getClientId(), element); } - } - - private void mergeLoanData(final Collection loanFlatDatas, - List clientDatas) { - IndividualClientData clientSavingsData = null; - for (IndividualCollectionSheetLoanFlatData loanFlatData : loanFlatDatas) { - IndividualClientData clientData = loanFlatData.getClientData(); - if (clientSavingsData == null || !clientSavingsData.equals(clientData)) { - if (clientDatas.contains(clientData)) { - final int index = clientDatas.indexOf(clientData); - if (index < 0) { - return; - } - clientSavingsData = clientDatas.get(index); - clientSavingsData.setLoans(new ArrayList()); - } else { - clientSavingsData = clientData; - clientSavingsData.setLoans(new ArrayList()); - clientDatas.add(clientSavingsData); - } - } - clientSavingsData.addLoans(loanFlatData.getLoanDueData()); + for (IndividualCollectionSheetLoanFlatData loanFlatData : loanFlatDataList) { + final IndividualClientData clientData = loanFlatData.getClientData(); + responseMap + .computeIfAbsent(clientData.getClientId(), + id -> IndividualClientData.instance(clientData.getClientId(), clientData.getClientName())) + .addLoans(loanFlatData.getLoanDueData()); } + return new ArrayList<>(responseMap.values()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/starter/CollectionSheetConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/starter/CollectionSheetConfiguration.java index d684f330cca..ba66902f64f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/starter/CollectionSheetConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/starter/CollectionSheetConfiguration.java @@ -26,6 +26,7 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarRepositoryWrapper; import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService; import org.apache.fineract.portfolio.collectionsheet.data.CollectionSheetTransactionDataValidator; +import org.apache.fineract.portfolio.collectionsheet.repository.CollectionSheetDao; import org.apache.fineract.portfolio.collectionsheet.serialization.CollectionSheetBulkDisbursalCommandFromApiJsonDeserializer; import org.apache.fineract.portfolio.collectionsheet.serialization.CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer; import org.apache.fineract.portfolio.collectionsheet.serialization.CollectionSheetGenerateCommandFromApiJsonDeserializer; @@ -61,11 +62,12 @@ public CollectionSheetReadPlatformService collectionSheetReadPlatformService(Pla AttendanceDropdownReadPlatformService attendanceDropdownReadPlatformService, CodeValueReadPlatformService codeValueReadPlatformService, PaymentTypeReadPlatformService paymentTypeReadPlatformService, CalendarReadPlatformService calendarReadPlatformService, ConfigurationDomainService configurationDomainService, - CalendarInstanceRepository calendarInstanceRepository, DatabaseSpecificSQLGenerator sqlGenerator) { + CalendarInstanceRepository calendarInstanceRepository, DatabaseSpecificSQLGenerator sqlGenerator, + CollectionSheetDao collectionSheetDao) { return new CollectionSheetReadPlatformServiceImpl(context, namedParameterJdbcTemplate, centerReadPlatformService, groupReadPlatformService, collectionSheetGenerateCommandFromApiJsonDeserializer, calendarRepositoryWrapper, - attendanceDropdownReadPlatformService, codeValueReadPlatformService, paymentTypeReadPlatformService, - calendarReadPlatformService, configurationDomainService, calendarInstanceRepository, sqlGenerator); + attendanceDropdownReadPlatformService, paymentTypeReadPlatformService, calendarReadPlatformService, + configurationDomainService, sqlGenerator, collectionSheetDao); } @Bean