FINERACT-1495-interesest-posting-performance-improvement (#2027)
* Convert JPA to JDBC * FINERACT-1495-interesest-posting-performance-improvement Co-authored-by: BLasan <benuraab@gmail.com>
This commit is contained in:
+33
@@ -69,6 +69,39 @@ public class GLAccountData implements Serializable {
|
||||
return new GLAccountData(name, parentId, glCode, manualEntriesAllowed, type, usage, description, tagId, rowIndex);
|
||||
}
|
||||
|
||||
public static GLAccountData createFrom(final Long id) {
|
||||
return new GLAccountData(id);
|
||||
}
|
||||
|
||||
private GLAccountData(final Long id) {
|
||||
|
||||
this.name = null;
|
||||
this.parentId = null;
|
||||
this.glCode = null;
|
||||
this.manualEntriesAllowed = null;
|
||||
this.type = null;
|
||||
this.usage = null;
|
||||
this.description = null;
|
||||
this.tagId = null;
|
||||
this.rowIndex = null;
|
||||
this.id = id;
|
||||
this.disabled = null;
|
||||
this.nameDecorated = null;
|
||||
this.organizationRunningBalance = null;
|
||||
this.accountTypeOptions = null;
|
||||
this.usageOptions = null;
|
||||
this.assetHeaderAccountOptions = null;
|
||||
this.liabilityHeaderAccountOptions = null;
|
||||
this.equityHeaderAccountOptions = null;
|
||||
this.incomeHeaderAccountOptions = null;
|
||||
this.expenseHeaderAccountOptions = null;
|
||||
this.allowedAssetsTagOptions = null;
|
||||
this.allowedLiabilitiesTagOptions = null;
|
||||
this.allowedEquityTagOptions = null;
|
||||
this.allowedIncomeTagOptions = null;
|
||||
this.allowedExpensesTagOptions = null;
|
||||
}
|
||||
|
||||
private GLAccountData(String name, Long parentId, String glCode, Boolean manualEntriesAllowed, EnumOptionData type,
|
||||
EnumOptionData usage, String description, CodeValueData tagId, Integer rowIndex) {
|
||||
|
||||
|
||||
+64
-1
@@ -90,6 +90,7 @@ public class JournalEntryData {
|
||||
private String routingCode;
|
||||
private String receiptNumber;
|
||||
private String bankNumber;
|
||||
private transient Long savingTransactionId;
|
||||
|
||||
// for opening bal bulk import
|
||||
public JournalEntryData(Long officeId, LocalDate transactionDate, String currencyCode, List<CreditDebit> credits,
|
||||
@@ -131,7 +132,6 @@ public class JournalEntryData {
|
||||
this.organizationRunningBalance = null;
|
||||
this.runningBalanceComputed = null;
|
||||
this.transactionDetails = null;
|
||||
|
||||
}
|
||||
|
||||
public static JournalEntryData importInstance(Long officeId, LocalDate transactionDate, String currencyCode, Long paymentTypeId,
|
||||
@@ -240,6 +240,40 @@ public class JournalEntryData {
|
||||
this.currency = currency;
|
||||
}
|
||||
|
||||
public JournalEntryData(final Long id, final Long officeId, 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 LocalDate createdDate, final String currencyCode,
|
||||
final Long savingTransactionId) {
|
||||
this.id = id;
|
||||
this.officeId = officeId;
|
||||
this.officeName = null;
|
||||
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.savingTransactionId = savingTransactionId;
|
||||
this.manualEntry = manualEntry;
|
||||
this.entityType = entityType;
|
||||
this.entityId = entityId;
|
||||
this.createdByUserId = null;
|
||||
this.createdDate = createdDate;
|
||||
this.createdByUserName = null;
|
||||
this.comments = null;
|
||||
this.reversed = false;
|
||||
this.referenceNumber = null;
|
||||
this.officeRunningBalance = null;
|
||||
this.organizationRunningBalance = null;
|
||||
this.runningBalanceComputed = null;
|
||||
this.transactionDetails = null;
|
||||
this.currency = null;
|
||||
this.currencyCode = currencyCode;
|
||||
}
|
||||
|
||||
public static JournalEntryData fromGLAccountData(final GLAccountData glAccountData) {
|
||||
|
||||
final Long id = null;
|
||||
@@ -300,4 +334,33 @@ public class JournalEntryData {
|
||||
public String getTransactionId() {
|
||||
return transactionId;
|
||||
}
|
||||
|
||||
public Long getSavingTransactionId() {
|
||||
return this.savingTransactionId;
|
||||
}
|
||||
|
||||
public String getCurrencyCode() {
|
||||
return this.currencyCode;
|
||||
}
|
||||
|
||||
public boolean isManualEntry() {
|
||||
return this.manualEntry;
|
||||
}
|
||||
|
||||
public EnumOptionData getEntityType() {
|
||||
return this.entityType;
|
||||
}
|
||||
|
||||
public Long getEntityId() {
|
||||
return this.entityId;
|
||||
}
|
||||
|
||||
public LocalDate getCreatedDate() {
|
||||
return this.createdDate;
|
||||
}
|
||||
|
||||
public Long getPaymentTypeId() {
|
||||
return this.paymentTypeId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+8
@@ -222,4 +222,12 @@ public class JournalEntry extends AbstractAuditableCustom {
|
||||
return this.shareTransactionId;
|
||||
}
|
||||
|
||||
public boolean isReversed() {
|
||||
return this.reversed;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-1
@@ -23,5 +23,4 @@ import org.apache.fineract.accounting.journalentry.data.SavingsDTO;
|
||||
public interface AccountingProcessorForSavings {
|
||||
|
||||
void createJournalEntriesForSavings(SavingsDTO savingsDTO);
|
||||
|
||||
}
|
||||
|
||||
+16
-2
@@ -26,6 +26,7 @@ import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.sql.DataSource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.accounting.closure.domain.GLClosure;
|
||||
import org.apache.fineract.accounting.closure.domain.GLClosureRepository;
|
||||
@@ -59,6 +60,7 @@ import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGL
|
||||
import org.apache.fineract.accounting.producttoaccountmapping.exception.ProductToGLAccountMappingNotFoundException;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
|
||||
@@ -75,7 +77,11 @@ import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumD
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository;
|
||||
import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountTransactionEnumData;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@@ -97,6 +103,9 @@ public class AccountingProcessorHelper {
|
||||
private final SavingsAccountTransactionRepository savingsAccountTransactionRepository;
|
||||
private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
|
||||
private final ChargeRepositoryWrapper chargeRepositoryWrapper;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final DataSource dataSource;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AccountingProcessorHelper.class);
|
||||
|
||||
@Autowired
|
||||
public AccountingProcessorHelper(final JournalEntryRepository glJournalEntryRepository,
|
||||
@@ -107,8 +116,9 @@ public class AccountingProcessorHelper {
|
||||
final AccountTransfersReadPlatformService accountTransfersReadPlatformService,
|
||||
final GLAccountRepositoryWrapper accountRepositoryWrapper,
|
||||
final ClientTransactionRepositoryWrapper clientTransactionRepositoryWrapper,
|
||||
final ChargeRepositoryWrapper chargeRepositoryWrapper) {
|
||||
final ChargeRepositoryWrapper chargeRepositoryWrapper, final RoutingDataSource dataSource) {
|
||||
this.glJournalEntryRepository = glJournalEntryRepository;
|
||||
this.dataSource = dataSource;
|
||||
this.accountMappingRepository = accountMappingRepository;
|
||||
this.closureRepository = closureRepository;
|
||||
this.officeRepositoryWrapper = officeRepositoryWrapper;
|
||||
@@ -119,6 +129,7 @@ public class AccountingProcessorHelper {
|
||||
this.accountRepositoryWrapper = accountRepositoryWrapper;
|
||||
this.clientTransactionRepository = clientTransactionRepositoryWrapper;
|
||||
this.chargeRepositoryWrapper = chargeRepositoryWrapper;
|
||||
this.jdbcTemplate = new JdbcTemplate(this.dataSource);
|
||||
}
|
||||
|
||||
public LoanDTO populateLoanDtoFromMap(final Map<String, Object> accountingBridgeData, final boolean cashBasedAccountingEnabled,
|
||||
@@ -842,7 +853,8 @@ public class AccountingProcessorHelper {
|
||||
}
|
||||
|
||||
private void createCreditJournalEntryForSavings(final Office office, final String currencyCode, final GLAccount account,
|
||||
final Long savingsId, final String transactionId, final Date transactionDate, final BigDecimal amount) {
|
||||
final Long savingsId, final String transactionId, final Date transactionDate, final BigDecimal amount)
|
||||
throws DataAccessException {
|
||||
final boolean manualEntry = false;
|
||||
LoanTransaction loanTransaction = null;
|
||||
SavingsAccountTransaction savingsAccountTransaction = null;
|
||||
@@ -858,6 +870,7 @@ public class AccountingProcessorHelper {
|
||||
final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId,
|
||||
manualEntry, transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId,
|
||||
null, loanTransaction, savingsAccountTransaction, clientTransaction, shareTransactionId);
|
||||
|
||||
this.glJournalEntryRepository.saveAndFlush(journalEntry);
|
||||
}
|
||||
|
||||
@@ -948,6 +961,7 @@ public class AccountingProcessorHelper {
|
||||
final JournalEntry journalEntry = JournalEntry.createNew(office, paymentDetail, account, currencyCode, modifiedTransactionId,
|
||||
manualEntry, transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId, null,
|
||||
loanTransaction, savingsAccountTransaction, clientTransaction, shareTransactionId);
|
||||
|
||||
this.glJournalEntryRepository.saveAndFlush(journalEntry);
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -46,4 +46,5 @@ public interface JournalEntryWritePlatformService {
|
||||
void createJournalEntriesForShares(Map<String, Object> accountingBridgeData);
|
||||
|
||||
void revertShareAccountJournalEntries(ArrayList<Long> transactionId, Date transactionDate);
|
||||
|
||||
}
|
||||
|
||||
-1
@@ -530,7 +530,6 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
|
||||
.determineProcessor(savingsDTO);
|
||||
accountingProcessorForSavings.createJournalEntriesForSavings(savingsDTO);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Transactional
|
||||
|
||||
+2
@@ -31,6 +31,7 @@ import org.apache.fineract.useradministration.domain.Permission;
|
||||
import org.apache.fineract.useradministration.domain.PermissionRepository;
|
||||
import org.apache.fineract.useradministration.exception.PermissionNotFoundException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -368,6 +369,7 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
|
||||
return property.getValue();
|
||||
}
|
||||
|
||||
@Cacheable(value = "configByName", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#propertyName)")
|
||||
private GlobalConfigurationPropertyData getGlobalConfigurationPropertyData(final String propertyName) {
|
||||
String identifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier();
|
||||
String key = identifier + "_" + propertyName;
|
||||
|
||||
+22
@@ -61,6 +61,16 @@ public class CurrencyData implements Serializable {
|
||||
this.displayLabel = generateDisplayLabel();
|
||||
}
|
||||
|
||||
public CurrencyData(final String code, final int decimalPlaces, final Integer inMultiplesOf) {
|
||||
this.code = code;
|
||||
this.name = null;
|
||||
this.decimalPlaces = decimalPlaces;
|
||||
this.inMultiplesOf = inMultiplesOf;
|
||||
this.displaySymbol = null;
|
||||
this.nameCode = null;
|
||||
this.displayLabel = null;
|
||||
}
|
||||
|
||||
public String code() {
|
||||
return this.code;
|
||||
}
|
||||
@@ -108,4 +118,16 @@ public class CurrencyData implements Serializable {
|
||||
public int hashCode() {
|
||||
return Objects.hash(code, name, decimalPlaces, inMultiplesOf, displaySymbol, nameCode, displayLabel);
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public int getDecimalPlaces() {
|
||||
return this.decimalPlaces;
|
||||
}
|
||||
|
||||
public Integer getInMultiplesOf() {
|
||||
return this.inMultiplesOf;
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -20,6 +20,7 @@ package org.apache.fineract.organisation.monetary.domain;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
|
||||
@Embeddable
|
||||
public class MonetaryCurrency {
|
||||
@@ -54,6 +55,10 @@ public class MonetaryCurrency {
|
||||
applicationCurrency.getCurrencyInMultiplesOf());
|
||||
}
|
||||
|
||||
public static MonetaryCurrency fromCurrencyData(final CurrencyData currencyData) {
|
||||
return new MonetaryCurrency(currencyData.getCode(), currencyData.getDecimalPlaces(), currencyData.getInMultiplesOf());
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return this.code;
|
||||
}
|
||||
@@ -65,4 +70,5 @@ public class MonetaryCurrency {
|
||||
public Integer getCurrencyInMultiplesOf() {
|
||||
return this.inMultiplesOf;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+9
@@ -23,6 +23,7 @@ import java.math.RoundingMode;
|
||||
import java.util.Iterator;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
|
||||
@Embeddable
|
||||
public class Money implements Comparable<Money> {
|
||||
@@ -67,10 +68,18 @@ public class Money implements Comparable<Money> {
|
||||
currency.getCurrencyInMultiplesOf());
|
||||
}
|
||||
|
||||
public static Money of(final CurrencyData currency, final BigDecimal newAmount) {
|
||||
return new Money(currency.getCode(), currency.getDecimalPlaces(), defaultToZeroIfNull(newAmount), currency.getInMultiplesOf());
|
||||
}
|
||||
|
||||
public static Money zero(final MonetaryCurrency currency) {
|
||||
return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), BigDecimal.ZERO, currency.getCurrencyInMultiplesOf());
|
||||
}
|
||||
|
||||
public static Money zero(final CurrencyData currency) {
|
||||
return new Money(currency.getCode(), currency.getDecimalPlaces(), BigDecimal.ZERO, currency.getInMultiplesOf());
|
||||
}
|
||||
|
||||
protected Money() {
|
||||
this.currencyCode = null;
|
||||
this.currencyDigitsAfterDecimal = 0;
|
||||
|
||||
+66
@@ -129,6 +129,71 @@ public final class ClientData implements Comparable<ClientData>, Serializable {
|
||||
locale, dateFormat);
|
||||
}
|
||||
|
||||
public static ClientData createClientForInterestPosting(final Long id, final Long officeId) {
|
||||
return new ClientData(id, officeId);
|
||||
}
|
||||
|
||||
private ClientData(final Long clientId, final Long officeId) {
|
||||
this.rowIndex = null;
|
||||
this.dateFormat = null;
|
||||
this.locale = null;
|
||||
this.firstname = null;
|
||||
this.lastname = null;
|
||||
this.middlename = null;
|
||||
this.fullname = null;
|
||||
this.activationDate = null;
|
||||
this.submittedOnDate = null;
|
||||
this.active = null;
|
||||
this.externalId = null;
|
||||
this.officeId = officeId;
|
||||
this.staffId = null;
|
||||
this.legalFormId = null;
|
||||
this.mobileNo = null;
|
||||
this.dateOfBirth = null;
|
||||
this.clientTypeId = null;
|
||||
this.genderId = null;
|
||||
this.clientClassificationId = null;
|
||||
this.isStaff = false;
|
||||
this.address = null;
|
||||
this.accountNo = null;
|
||||
this.status = null;
|
||||
this.subStatus = null;
|
||||
this.displayName = null;
|
||||
this.gender = null;
|
||||
this.clientType = null;
|
||||
this.clientClassification = null;
|
||||
this.officeName = null;
|
||||
this.transferToOfficeId = null;
|
||||
this.transferToOfficeName = null;
|
||||
this.imageId = null;
|
||||
this.imagePresent = null;
|
||||
this.staffName = null;
|
||||
this.timeline = null;
|
||||
this.savingsProductId = null;
|
||||
this.savingsProductName = null;
|
||||
this.savingsAccountId = null;
|
||||
this.legalForm = null;
|
||||
this.groups = null;
|
||||
this.officeOptions = null;
|
||||
this.staffOptions = null;
|
||||
this.narrations = null;
|
||||
this.savingProductOptions = null;
|
||||
this.savingAccountOptions = null;
|
||||
this.genderOptions = null;
|
||||
this.clientTypeOptions = null;
|
||||
this.clientClassificationOptions = null;
|
||||
this.clientNonPersonConstitutionOptions = null;
|
||||
this.clientNonPersonMainBusinessLineOptions = null;
|
||||
this.clientLegalFormOptions = null;
|
||||
this.clientNonPersonDetails = null;
|
||||
this.isAddressEnabled = null;
|
||||
this.datatables = null;
|
||||
this.familyMemberOptions = null;
|
||||
this.emailAddress = null;
|
||||
this.clientCollateralManagements = null;
|
||||
this.id = clientId;
|
||||
}
|
||||
|
||||
public static ClientData importClientPersonInstance(Long legalFormId, Integer rowIndex, String firstname, String lastname,
|
||||
String middlename, LocalDate submittedOn, LocalDate activationDate, Boolean active, String externalId, Long officeId,
|
||||
Long staffId, String mobileNo, LocalDate dob, Long clientTypeId, Long genderId, Long clientClassificationId, Boolean isStaff,
|
||||
@@ -638,4 +703,5 @@ public final class ClientData implements Comparable<ClientData>, Serializable {
|
||||
public Boolean getIsAddressEnabled() {
|
||||
return this.isAddressEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+31
@@ -126,6 +126,37 @@ public class GroupGeneralData implements Serializable {
|
||||
this.timeline = null;
|
||||
}
|
||||
|
||||
public GroupGeneralData(Long id, Long officeId) {
|
||||
this.id = id;
|
||||
this.accountNo = null;
|
||||
this.name = null;
|
||||
this.externalId = null;
|
||||
this.status = null;
|
||||
this.active = null;
|
||||
this.activationDate = null;
|
||||
this.officeId = officeId;
|
||||
this.officeName = null;
|
||||
this.centerId = null;
|
||||
this.centerName = null;
|
||||
this.staffId = null;
|
||||
this.staffName = null;
|
||||
this.hierarchy = null;
|
||||
this.groupLevel = null;
|
||||
this.clientMembers = null;
|
||||
this.activeClientMembers = null;
|
||||
this.groupRoles = null;
|
||||
this.calendarsData = null;
|
||||
this.collectionMeetingCalendar = null;
|
||||
this.centerOptions = null;
|
||||
this.officeOptions = null;
|
||||
this.staffOptions = null;
|
||||
this.clientOptions = null;
|
||||
this.availableRoles = null;
|
||||
this.selectedRole = null;
|
||||
this.closureReasons = null;
|
||||
this.timeline = null;
|
||||
}
|
||||
|
||||
public GroupGeneralData(Long id) {
|
||||
this.id = id;
|
||||
this.accountNo = null;
|
||||
|
||||
+4
@@ -72,4 +72,8 @@ public class PaymentDetailData implements Serializable {
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, paymentType, accountNumber, checkNumber, routingCode, receiptNumber, bankNumber);
|
||||
}
|
||||
|
||||
public PaymentTypeData getPaymentType() {
|
||||
return this.paymentType;
|
||||
}
|
||||
}
|
||||
|
||||
+51
@@ -86,6 +86,8 @@ public class SavingsAccountChargeData implements Serializable {
|
||||
|
||||
private final Collection<ChargeData> chargeOptions;
|
||||
|
||||
private ChargeData chargeData;
|
||||
|
||||
public SavingsAccountChargeData(Long chargeId, BigDecimal amount, LocalDate dueDate) {
|
||||
this.chargeId = chargeId;
|
||||
this.amount = amount;
|
||||
@@ -115,6 +117,35 @@ public class SavingsAccountChargeData implements Serializable {
|
||||
this.restartFrequencyEnum = null;
|
||||
}
|
||||
|
||||
public SavingsAccountChargeData(Long chargeId, BigDecimal amount, EnumOptionData chargeTimeType, boolean isPenalty) {
|
||||
this.chargeId = chargeId;
|
||||
this.amount = amount;
|
||||
this.dueDate = null;
|
||||
this.id = null;
|
||||
this.accountId = null;
|
||||
this.name = null;
|
||||
this.chargeTimeType = chargeTimeType;
|
||||
this.feeOnMonthDay = null;
|
||||
this.feeInterval = null;
|
||||
this.chargeCalculationType = null;
|
||||
this.percentage = null;
|
||||
this.amountPercentageAppliedTo = null;
|
||||
this.currency = null;
|
||||
this.amountPaid = null;
|
||||
this.amountWaived = null;
|
||||
this.amountWrittenOff = null;
|
||||
this.amountOutstanding = null;
|
||||
this.amountOrPercentage = null;
|
||||
this.penalty = isPenalty;
|
||||
this.isActive = null;
|
||||
this.inactivationDate = null;
|
||||
this.chargeOptions = null;
|
||||
this.isFreeWithdrawal = null;
|
||||
this.freeWithdrawalChargeFrequency = null;
|
||||
this.restartFrequency = null;
|
||||
this.restartFrequencyEnum = null;
|
||||
}
|
||||
|
||||
public static SavingsAccountChargeData template(final Collection<ChargeData> chargeOptions) {
|
||||
final Long id = null;
|
||||
final Long chargeId = null;
|
||||
@@ -163,6 +194,10 @@ public class SavingsAccountChargeData implements Serializable {
|
||||
restartFrequencyEnum, inactivationDate);
|
||||
}
|
||||
|
||||
public boolean isFeeCharge() {
|
||||
return !this.penalty;
|
||||
}
|
||||
|
||||
private SavingsAccountChargeData(final Long id, final Long chargeId, final Long accountId, final String name,
|
||||
final EnumOptionData chargeTimeType, final LocalDate dueAsOfDate, final EnumOptionData chargeCalculationType,
|
||||
final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo, final CurrencyData currency, final BigDecimal amount,
|
||||
@@ -223,4 +258,20 @@ public class SavingsAccountChargeData implements Serializable {
|
||||
return this.accountId;
|
||||
}
|
||||
|
||||
public boolean isPenaltyCharge() {
|
||||
return this.penalty;
|
||||
}
|
||||
|
||||
public ChargeData getCharge() {
|
||||
return this.chargeData;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public boolean isPenalty() {
|
||||
return this.penalty;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+295
-1
@@ -21,18 +21,28 @@ package org.apache.fineract.portfolio.savings.data;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
import org.apache.fineract.organisation.staff.data.StaffData;
|
||||
import org.apache.fineract.portfolio.charge.data.ChargeData;
|
||||
import org.apache.fineract.portfolio.client.data.ClientData;
|
||||
import org.apache.fineract.portfolio.group.data.GroupGeneralData;
|
||||
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionSummaryWrapper;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsHelper;
|
||||
import org.apache.fineract.portfolio.tax.data.TaxGroupData;
|
||||
|
||||
/**
|
||||
@@ -79,7 +89,6 @@ public final class SavingsAccountData implements Serializable {
|
||||
private final Integer daysToDormancy;
|
||||
private final Integer daysToEscheat;
|
||||
private final BigDecimal savingsAmountOnHold;
|
||||
|
||||
// associations
|
||||
private final SavingsAccountSummaryData summary;
|
||||
@SuppressWarnings("unused")
|
||||
@@ -104,6 +113,7 @@ public final class SavingsAccountData implements Serializable {
|
||||
private final SavingsAccountChargeData annualFee;
|
||||
private final BigDecimal nominalAnnualInterestRateOverdraft;
|
||||
private final BigDecimal minOverdraftForInterestCalculation;
|
||||
private transient List<SavingsAccountTransactionData> savingsAccountTransactionData = new ArrayList<>();
|
||||
|
||||
private List<DatatableData> datatables = null;
|
||||
|
||||
@@ -112,7 +122,23 @@ public final class SavingsAccountData implements Serializable {
|
||||
private String locale;
|
||||
private String dateFormat;
|
||||
private transient Integer rowIndex;
|
||||
private transient Date startInterestCalculationDate;
|
||||
private LocalDate submittedOnDate;
|
||||
private transient SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper;
|
||||
private transient SavingsHelper savingsHelper;
|
||||
|
||||
private transient SavingsAccountSummaryData savingsAccountSummaryData;
|
||||
private transient Date activatedOnDate;
|
||||
private transient LocalDate lockedInUntilDate;
|
||||
private transient ClientData clientData;
|
||||
private transient SavingsProductData savingsProductData;
|
||||
private transient List<SavingsAccountTransactionData> newSavingsAccountTransactionData = new ArrayList<>();
|
||||
private transient GroupGeneralData groupGeneralData;
|
||||
private transient Long officeId;
|
||||
private transient Set<Long> existingTransactionIds = new HashSet<>();
|
||||
private transient Set<Long> existingReversedTransactionIds = new HashSet<>();
|
||||
private transient Long glAccountIdForSavingsControl;
|
||||
private transient Long glAccountIdForInterestOnSavings;
|
||||
|
||||
public static SavingsAccountData importInstanceIndividual(Long clientId, Long productId, Long fieldOfficerId, LocalDate submittedOnDate,
|
||||
BigDecimal nominalAnnualInterestRate, EnumOptionData interestCompoundingPeriodTypeEnum,
|
||||
@@ -207,6 +233,63 @@ public final class SavingsAccountData implements Serializable {
|
||||
}
|
||||
};
|
||||
|
||||
public void setNewSavingsAccountTransactionData(final SavingsAccountTransactionData savingsAccountTransactionData) {
|
||||
this.newSavingsAccountTransactionData.add(savingsAccountTransactionData);
|
||||
}
|
||||
|
||||
public List<SavingsAccountTransactionData> getNewSavingsAccountTransactionData() {
|
||||
return this.newSavingsAccountTransactionData;
|
||||
}
|
||||
|
||||
public void setSavingsAccountSummaryData(final SavingsAccountSummaryData savingsAccountSummaryData) {
|
||||
this.savingsAccountSummaryData = savingsAccountSummaryData;
|
||||
}
|
||||
|
||||
public void setSavingsProduct(final SavingsProductData savingsProductData) {
|
||||
this.savingsProductData = savingsProductData;
|
||||
}
|
||||
|
||||
public void setSavingsAccountTransactionData(final SavingsAccountTransactionData savingsAccountTransactionData) {
|
||||
this.savingsAccountTransactionData.add(savingsAccountTransactionData);
|
||||
}
|
||||
|
||||
public void setSubmittedOnDate(final LocalDate submittedOnDate) {
|
||||
this.submittedOnDate = submittedOnDate;
|
||||
}
|
||||
|
||||
public void setLockedInUntilDate(final LocalDate lockedInUntilDate) {
|
||||
this.lockedInUntilDate = lockedInUntilDate;
|
||||
}
|
||||
|
||||
public void setStartInterestCalculationDate(final Date startInterestCalculationDate) {
|
||||
this.startInterestCalculationDate = startInterestCalculationDate;
|
||||
}
|
||||
|
||||
public CurrencyData getCurrency() {
|
||||
return this.currency;
|
||||
}
|
||||
|
||||
public void setClientData(final ClientData clientData) {
|
||||
this.clientData = clientData;
|
||||
}
|
||||
|
||||
public void setGroupGeneralData(final GroupGeneralData groupGeneralData) {
|
||||
this.groupGeneralData = groupGeneralData;
|
||||
}
|
||||
|
||||
public void setUpdatedTransactions(List<SavingsAccountTransactionData> savingsAccountTransactionDataList) {
|
||||
this.savingsAccountTransactionData = new ArrayList<>();
|
||||
this.savingsAccountTransactionData.addAll(savingsAccountTransactionDataList);
|
||||
}
|
||||
|
||||
public void setOfficeId(final Long officeId) {
|
||||
this.officeId = officeId;
|
||||
}
|
||||
|
||||
public Long getOfficeId() {
|
||||
return this.officeId;
|
||||
}
|
||||
|
||||
public String getClientName() {
|
||||
return clientName;
|
||||
}
|
||||
@@ -215,6 +298,22 @@ public final class SavingsAccountData implements Serializable {
|
||||
return accountNo;
|
||||
}
|
||||
|
||||
public BigDecimal getNominalAnnualInterestRate() {
|
||||
return this.nominalAnnualInterestRate;
|
||||
}
|
||||
|
||||
public BigDecimal getNominalAnnualInterestRateOverdraft() {
|
||||
return this.nominalAnnualInterestRateOverdraft;
|
||||
}
|
||||
|
||||
public boolean isAllowOverdraft() {
|
||||
return this.allowOverdraft;
|
||||
}
|
||||
|
||||
public TaxGroupData getTaxGroupData() {
|
||||
return this.taxGroup;
|
||||
}
|
||||
|
||||
public Long getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
@@ -231,6 +330,169 @@ public final class SavingsAccountData implements Serializable {
|
||||
return timeline;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void updateTransactions(final SavingsAccountTransactionData savingsAccountTransactionData) {
|
||||
this.savingsAccountTransactionData.add(savingsAccountTransactionData);
|
||||
}
|
||||
|
||||
public boolean withHoldTax() {
|
||||
return this.withHoldTax;
|
||||
}
|
||||
|
||||
public DepositAccountType depositAccountType() {
|
||||
return DepositAccountType.fromInt(this.depositType.getId().intValue());
|
||||
}
|
||||
|
||||
public void setGlAccountIdForSavingsControl(final Long glAccountIdForSavingsControl) {
|
||||
this.glAccountIdForSavingsControl = glAccountIdForSavingsControl;
|
||||
}
|
||||
|
||||
public void setGlAccountIdForInterestOnSavings(final Long glAccountIdForInterestOnSavings) {
|
||||
this.glAccountIdForInterestOnSavings = glAccountIdForInterestOnSavings;
|
||||
}
|
||||
|
||||
public Long getGlAccountIdForSavingsControl() {
|
||||
return this.glAccountIdForSavingsControl;
|
||||
}
|
||||
|
||||
public Long getGlAccountIdForInterestOnSavings() {
|
||||
return this.glAccountIdForInterestOnSavings;
|
||||
}
|
||||
|
||||
public SavingsAccountSummaryData getSavingsAccountSummaryData() {
|
||||
return this.savingsAccountSummaryData;
|
||||
}
|
||||
|
||||
public List<SavingsAccountTransactionData> getSavingsAccountTransactionData() {
|
||||
return this.savingsAccountTransactionData;
|
||||
}
|
||||
|
||||
public void setHelpers(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper,
|
||||
final SavingsHelper savingsHelper) {
|
||||
this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper;
|
||||
this.savingsHelper = savingsHelper;
|
||||
}
|
||||
|
||||
public BigDecimal getMinBalanceForInterestCalculation() {
|
||||
return this.minBalanceForInterestCalculation;
|
||||
}
|
||||
|
||||
public BigDecimal getMinOverdraftForInterestCalculation() {
|
||||
return this.minOverdraftForInterestCalculation;
|
||||
}
|
||||
|
||||
public Integer getInterestPostingPeriodType() {
|
||||
return this.interestPostingPeriodType.getId().intValue();
|
||||
}
|
||||
|
||||
public Integer getDepositType() {
|
||||
return this.depositType.getId().intValue();
|
||||
}
|
||||
|
||||
public Integer getInterestCompoundingPeriodType() {
|
||||
return this.interestCompoundingPeriodType.getId().intValue();
|
||||
}
|
||||
|
||||
public Integer getInterestCalculationType() {
|
||||
return this.interestCalculationType.getId().intValue();
|
||||
}
|
||||
|
||||
public Integer getInterestCalculationDaysInYearType() {
|
||||
return this.interestCalculationDaysInYearType.getId().intValue();
|
||||
}
|
||||
|
||||
public LocalDate getLockedInUntilDate() {
|
||||
return this.lockedInUntilDate;
|
||||
}
|
||||
|
||||
public SavingsAccountTransactionData findLastTransaction(final LocalDate date) {
|
||||
|
||||
SavingsAccountTransactionData savingsTransaction = null;
|
||||
List<SavingsAccountTransactionData> trans = getTransactions();
|
||||
for (final SavingsAccountTransactionData transaction : trans) {
|
||||
if (transaction.isNotReversed() && transaction.occursOn(date)) {
|
||||
savingsTransaction = transaction;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return savingsTransaction;
|
||||
}
|
||||
|
||||
public List<SavingsAccountTransactionData> getTransactions() {
|
||||
return this.savingsAccountTransactionData;
|
||||
}
|
||||
|
||||
public LocalDate getStartInterestCalculationDate() {
|
||||
LocalDate startInterestCalculationLocalDate = null;
|
||||
if (this.startInterestCalculationDate != null) {
|
||||
startInterestCalculationLocalDate = LocalDate.ofInstant(this.startInterestCalculationDate.toInstant(),
|
||||
DateUtils.getDateTimeZoneOfTenant());
|
||||
} else {
|
||||
startInterestCalculationLocalDate = getActivationLocalDate();
|
||||
}
|
||||
return startInterestCalculationLocalDate;
|
||||
}
|
||||
|
||||
public LocalDate getActivationLocalDate() {
|
||||
LocalDate activationLocalDate = null;
|
||||
if (this.timeline.getActivatedOnDate() != null) {
|
||||
activationLocalDate = this.timeline.getActivatedOnDate();
|
||||
}
|
||||
return activationLocalDate;
|
||||
}
|
||||
|
||||
public Integer getLockinPeriodFrequencyType() {
|
||||
return this.lockinPeriodFrequencyType.getId().intValue();
|
||||
}
|
||||
|
||||
public Collection<Long> findCurrentTransactionIdsWithPivotDateConfig() {
|
||||
|
||||
final Collection<Long> ids = new ArrayList<>();
|
||||
List<SavingsAccountTransactionData> trans = this.savingsAccountTransactionData;
|
||||
for (final SavingsAccountTransactionData transaction : trans) {
|
||||
ids.add(transaction.getId());
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
public Collection<Long> findCurrentReversedTransactionIdsWithPivotDateConfig() {
|
||||
final Collection<Long> ids = new ArrayList<>();
|
||||
List<SavingsAccountTransactionData> trans = this.savingsAccountTransactionData;
|
||||
// time consuming
|
||||
for (final SavingsAccountTransactionData transaction : trans) {
|
||||
if (transaction.isReversed()) {
|
||||
ids.add(transaction.getId());
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
public Long officeId() {
|
||||
Long officeId = null;
|
||||
if (this.clientData != null) {
|
||||
officeId = this.clientData.officeId();
|
||||
} else if (this.groupId != null) {
|
||||
officeId = this.groupGeneralData.officeId();
|
||||
}
|
||||
return officeId;
|
||||
}
|
||||
|
||||
public List<SavingsAccountTransactionData> getSavingsAccountTransactionsWithPivotConfig() {
|
||||
return this.transactions.stream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Boolean isAccrualBasedAccountingEnabledOnSavingsProduct() {
|
||||
return this.savingsProductData.isAccrualBasedAccountingEnabled();
|
||||
}
|
||||
|
||||
public Boolean isCashBasedAccountingEnabledOnSavingsProduct() {
|
||||
return this.savingsProductData.isCashBasedAccountingEnabled();
|
||||
}
|
||||
|
||||
public static SavingsAccountData importInstanceGroup(Long groupId, Long productId, Long fieldOfficerId, LocalDate submittedOnDate,
|
||||
BigDecimal nominalAnnualInterestRate, EnumOptionData interestCompoundingPeriodTypeEnum,
|
||||
EnumOptionData interestPostingPeriodTypeEnum, EnumOptionData interestCalculationTypeEnum,
|
||||
@@ -765,6 +1027,26 @@ public final class SavingsAccountData implements Serializable {
|
||||
return this.currency;
|
||||
}
|
||||
|
||||
public SavingsAccountTransactionSummaryWrapper getSavingsAccountTransactionSummaryWrapper() {
|
||||
return this.savingsAccountTransactionSummaryWrapper;
|
||||
}
|
||||
|
||||
public void setExistingTransactionIds(final Set<Long> existingTransactionIds) {
|
||||
if (existingTransactionIds != null) {
|
||||
this.existingTransactionIds.addAll(existingTransactionIds);
|
||||
}
|
||||
}
|
||||
|
||||
public void setExistingReversedTransactionIds(final Set<Long> existingReversedTransactionIds) {
|
||||
if (existingReversedTransactionIds != null) {
|
||||
this.existingReversedTransactionIds.addAll(existingReversedTransactionIds);
|
||||
}
|
||||
}
|
||||
|
||||
public SavingsHelper getSavingsHelper() {
|
||||
return this.savingsHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
|
||||
@@ -793,4 +1075,16 @@ public final class SavingsAccountData implements Serializable {
|
||||
public void setDatatables(final List<DatatableData> datatables) {
|
||||
this.datatables = datatables;
|
||||
}
|
||||
|
||||
public SavingsAccountSummaryData getSummary() {
|
||||
return this.summary;
|
||||
}
|
||||
|
||||
public Set<Long> getExistingTransactionIds() {
|
||||
return this.existingTransactionIds;
|
||||
}
|
||||
|
||||
public Set<Long> getExistingReversedTransactionIds() {
|
||||
return this.existingReversedTransactionIds;
|
||||
}
|
||||
}
|
||||
|
||||
+250
-15
@@ -21,7 +21,16 @@ package org.apache.fineract.portfolio.savings.data;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionSummaryWrapper;
|
||||
import org.apache.fineract.portfolio.savings.domain.interest.PostingPeriod;
|
||||
|
||||
/**
|
||||
* Immutable data object representing Savings Account summary information.
|
||||
@@ -30,21 +39,23 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
public class SavingsAccountSummaryData implements Serializable {
|
||||
|
||||
private final CurrencyData currency;
|
||||
private final BigDecimal totalDeposits;
|
||||
private final BigDecimal totalWithdrawals;
|
||||
private final BigDecimal totalWithdrawalFees;
|
||||
private final BigDecimal totalAnnualFees;
|
||||
private final BigDecimal totalInterestEarned;
|
||||
private final BigDecimal totalInterestPosted;
|
||||
private final BigDecimal accountBalance;
|
||||
private final BigDecimal totalFeeCharge;
|
||||
private final BigDecimal totalPenaltyCharge;
|
||||
private final BigDecimal totalOverdraftInterestDerived;
|
||||
private final BigDecimal totalWithholdTax;
|
||||
private final BigDecimal interestNotPosted;
|
||||
private final LocalDate lastInterestCalculationDate;
|
||||
private final BigDecimal availableBalance;
|
||||
private final LocalDate interestPostedTillDate;
|
||||
private BigDecimal totalDeposits;
|
||||
private BigDecimal totalWithdrawals;
|
||||
private BigDecimal totalWithdrawalFees;
|
||||
private BigDecimal totalAnnualFees;
|
||||
private BigDecimal totalInterestEarned;
|
||||
private BigDecimal totalInterestPosted;
|
||||
private BigDecimal accountBalance;
|
||||
private BigDecimal totalFeeCharge;
|
||||
private BigDecimal totalPenaltyCharge;
|
||||
private BigDecimal totalOverdraftInterestDerived;
|
||||
private BigDecimal totalWithholdTax;
|
||||
private BigDecimal interestNotPosted;
|
||||
private LocalDate lastInterestCalculationDate;
|
||||
private BigDecimal availableBalance;
|
||||
private LocalDate interestPostedTillDate;
|
||||
private LocalDate prevInterestPostedTillDate;
|
||||
private transient BigDecimal runningBalanceOnInterestPostingTillDate = BigDecimal.ZERO;
|
||||
|
||||
public SavingsAccountSummaryData(final CurrencyData currency, final BigDecimal totalDeposits, final BigDecimal totalWithdrawals,
|
||||
final BigDecimal totalWithdrawalFees, final BigDecimal totalAnnualFees, final BigDecimal totalInterestEarned,
|
||||
@@ -69,4 +80,228 @@ public class SavingsAccountSummaryData implements Serializable {
|
||||
this.availableBalance = availableBalance;
|
||||
this.interestPostedTillDate = interestPostedTillDate;
|
||||
}
|
||||
|
||||
public void setPrevInterestPostedTillDate(LocalDate interestPostedTillDate) {
|
||||
this.prevInterestPostedTillDate = interestPostedTillDate;
|
||||
}
|
||||
|
||||
public LocalDate getPrevInterestPostedTillDate() {
|
||||
return this.prevInterestPostedTillDate;
|
||||
}
|
||||
|
||||
public LocalDate getInterestPostedTillDate() {
|
||||
return this.interestPostedTillDate;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalInterestPosted() {
|
||||
return this.totalInterestPosted;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalWithdrawalFees() {
|
||||
return this.totalWithdrawalFees;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalInterestEarned() {
|
||||
return this.totalInterestEarned;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalDeposits() {
|
||||
return this.totalDeposits;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalWithdrawals() {
|
||||
return this.totalWithdrawals;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalFeeCharge() {
|
||||
return this.totalFeeCharge;
|
||||
}
|
||||
|
||||
public BigDecimal getAvailableBalance() {
|
||||
return this.availableBalance;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalOverdraftInterestDerived() {
|
||||
return this.totalOverdraftInterestDerived;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalWithholdTax() {
|
||||
return this.totalWithholdTax;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalPenaltyCharge() {
|
||||
return this.totalPenaltyCharge;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalAnnualFees() {
|
||||
return this.totalAnnualFees;
|
||||
}
|
||||
|
||||
public LocalDate getLastInterestCalculationDate() {
|
||||
return this.lastInterestCalculationDate;
|
||||
}
|
||||
|
||||
public BigDecimal getRunningBalanceOnPivotDate() {
|
||||
return this.runningBalanceOnInterestPostingTillDate;
|
||||
}
|
||||
|
||||
public void updateSummaryWithPivotConfig(final CurrencyData currency, final SavingsAccountTransactionSummaryWrapper wrapper,
|
||||
final SavingsAccountTransaction transaction, final List<SavingsAccountTransactionData> savingsAccountTransactions) {
|
||||
|
||||
if (transaction != null) {
|
||||
Money transactionAmount = Money.of(currency, transaction.getAmount());
|
||||
switch (SavingsAccountTransactionType.fromInt(transaction.getTypeOf())) {
|
||||
case DEPOSIT:
|
||||
if (transaction.isDepositAndNotReversed() || transaction.isDividendPayoutAndNotReversed()) {
|
||||
this.totalDeposits = Money.of(currency, this.totalDeposits).plus(transactionAmount).getAmount();
|
||||
this.accountBalance = Money.of(currency, this.accountBalance).plus(transactionAmount).getAmount();
|
||||
}
|
||||
break;
|
||||
case WITHDRAWAL:
|
||||
if (transaction.isWithdrawal() && transaction.isNotReversed()) {
|
||||
this.totalWithdrawals = Money.of(currency, this.totalWithdrawals).plus(transactionAmount).getAmount();
|
||||
this.accountBalance = Money.of(currency, this.accountBalance).minus(transactionAmount).getAmount();
|
||||
}
|
||||
break;
|
||||
case WITHDRAWAL_FEE:
|
||||
if (transaction.isWithdrawalFeeAndNotReversed() && transaction.isNotReversed()) {
|
||||
this.totalWithdrawalFees = Money.of(currency, this.totalWithdrawalFees).plus(transactionAmount).getAmount();
|
||||
this.totalFeeCharge = Money.of(currency, this.totalFeeCharge).plus(transactionAmount).getAmount();
|
||||
this.accountBalance = Money.of(currency, this.accountBalance).minus(transactionAmount).getAmount();
|
||||
}
|
||||
break;
|
||||
case ANNUAL_FEE:
|
||||
if (transaction.isAnnualFeeAndNotReversed() && transaction.isNotReversed()) {
|
||||
this.totalAnnualFees = Money.of(currency, this.totalAnnualFees).plus(transactionAmount).getAmount();
|
||||
this.totalFeeCharge = Money.of(currency, this.totalFeeCharge).plus(transactionAmount).getAmount();
|
||||
this.accountBalance = Money.of(currency, this.accountBalance).minus(transactionAmount).getAmount();
|
||||
}
|
||||
break;
|
||||
case WAIVE_CHARGES:
|
||||
if (transaction.isWaiveFeeChargeAndNotReversed()) {
|
||||
this.totalFeeCharge = Money.of(currency, this.totalFeeCharge).plus(transactionAmount.getAmount()).getAmount();
|
||||
} else if (transaction.isWaivePenaltyChargeAndNotReversed()) {
|
||||
this.totalPenaltyCharge = Money.of(currency, this.totalPenaltyCharge).plus(transactionAmount.getAmount())
|
||||
.getAmount();
|
||||
}
|
||||
break;
|
||||
case PAY_CHARGE:
|
||||
if (transaction.isFeeChargeAndNotReversed()) {
|
||||
this.totalFeeCharge = Money.of(currency, this.totalFeeCharge).plus(transactionAmount).getAmount();
|
||||
} else if (transaction.isPenaltyChargeAndNotReversed()) {
|
||||
this.totalPenaltyCharge = Money.of(currency, this.totalPenaltyCharge).plus(transactionAmount).getAmount();
|
||||
}
|
||||
if (transaction.isFeeChargeAndNotReversed() || transaction.isPenaltyChargeAndNotReversed()) {
|
||||
this.accountBalance = Money.of(currency, this.accountBalance).minus(transactionAmount).getAmount();
|
||||
}
|
||||
break;
|
||||
case OVERDRAFT_INTEREST:
|
||||
if (transaction.isOverdraftInterestAndNotReversed()) {
|
||||
this.totalOverdraftInterestDerived = Money.of(currency, this.totalOverdraftInterestDerived).plus(transactionAmount)
|
||||
.getAmount();
|
||||
this.accountBalance = Money.of(currency, this.accountBalance).minus(transactionAmount).getAmount();
|
||||
}
|
||||
break;
|
||||
case WITHHOLD_TAX:
|
||||
if (transaction.isWithHoldTaxAndNotReversed()) {
|
||||
this.totalWithholdTax = Money.of(currency, this.totalWithholdTax).plus(transactionAmount).getAmount();
|
||||
this.accountBalance = Money.of(currency, this.accountBalance).minus(transactionAmount).getAmount();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// boolean isUpdated = false;
|
||||
Money interestTotal = Money.of(currency, this.totalInterestPosted);
|
||||
Money withHoldTaxTotal = Money.of(currency, this.totalWithholdTax);
|
||||
|
||||
final HashMap<String, Money> map = updateRunningBalanceAndPivotDate(true, savingsAccountTransactions, interestTotal,
|
||||
withHoldTaxTotal, currency);
|
||||
interestTotal = map.get("interestTotal");
|
||||
withHoldTaxTotal = map.get("withHoldTax");
|
||||
this.totalInterestPosted = interestTotal.getAmountDefaultedToNullIfZero();
|
||||
this.totalWithholdTax = withHoldTaxTotal.getAmountDefaultedToNullIfZero();
|
||||
this.accountBalance = Money.of(currency, this.accountBalance).plus(this.totalInterestPosted).minus(this.totalWithholdTax)
|
||||
.getAmount();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateFromInterestPeriodSummaries(final MonetaryCurrency currency, final List<PostingPeriod> allPostingPeriods) {
|
||||
|
||||
Money totalEarned = Money.zero(currency);
|
||||
LocalDate interestCalculationDate = DateUtils.getLocalDateOfTenant();
|
||||
for (final PostingPeriod period : allPostingPeriods) {
|
||||
Money interestEarned = period.interest();
|
||||
interestEarned = interestEarned == null ? Money.zero(currency) : interestEarned;
|
||||
totalEarned = totalEarned.plus(interestEarned);
|
||||
}
|
||||
this.lastInterestCalculationDate = interestCalculationDate;
|
||||
this.totalInterestEarned = totalEarned.getAmount();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private HashMap<String, Money> updateRunningBalanceAndPivotDate(final boolean backdatedTxnsAllowedTill,
|
||||
final List<SavingsAccountTransactionData> savingsAccountTransactions, Money interestTotal, Money withHoldTaxTotal,
|
||||
CurrencyData currency) {
|
||||
boolean isUpdated = false;
|
||||
HashMap<String, Money> map = new HashMap<>();
|
||||
for (int i = savingsAccountTransactions.size() - 1; i >= 0; i--) {
|
||||
final SavingsAccountTransactionData savingsAccountTransaction = savingsAccountTransactions.get(i);
|
||||
if (savingsAccountTransaction.isInterestPostingAndNotReversed() && savingsAccountTransaction.isNotReversed() && !isUpdated) {
|
||||
setRunningBalanceOnPivotDate(savingsAccountTransaction.getRunningBalance(currency).getAmount());
|
||||
setInterestPostedTillDate(savingsAccountTransaction.getTransactionDate());
|
||||
isUpdated = true;
|
||||
if (!backdatedTxnsAllowedTill) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
if (savingsAccountTransaction.isInterestPostingAndNotReversed() && savingsAccountTransaction.isNotReversed()) {
|
||||
interestTotal = interestTotal.plus(savingsAccountTransaction.getAmount());
|
||||
}
|
||||
|
||||
if (savingsAccountTransaction.isWithHoldTaxAndNotReversed()) {
|
||||
withHoldTaxTotal = withHoldTaxTotal.plus(savingsAccountTransaction.getAmount());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
map.put("interestTotal", interestTotal);
|
||||
map.put("withHoldTax", withHoldTaxTotal);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public void updateSummary(final CurrencyData currency, final SavingsAccountTransactionSummaryWrapper wrapper,
|
||||
final List<SavingsAccountTransactionData> transactions) {
|
||||
|
||||
this.totalDeposits = wrapper.calculateTotalDeposits(currency, transactions);
|
||||
this.totalWithdrawals = wrapper.calculateTotalWithdrawals(currency, transactions);
|
||||
this.totalInterestPosted = wrapper.calculateTotalInterestPosted(currency, transactions);
|
||||
this.totalWithdrawalFees = wrapper.calculateTotalWithdrawalFees(currency, transactions);
|
||||
this.totalAnnualFees = wrapper.calculateTotalAnnualFees(currency, transactions);
|
||||
this.totalFeeCharge = wrapper.calculateTotalFeesCharge(currency, transactions);
|
||||
this.totalPenaltyCharge = wrapper.calculateTotalPenaltyCharge(currency, transactions);
|
||||
this.totalFeeCharge = wrapper.calculateTotalFeesChargeWaived(currency, transactions);
|
||||
this.totalPenaltyCharge = wrapper.calculateTotalPenaltyChargeWaived(currency, transactions);
|
||||
this.totalOverdraftInterestDerived = wrapper.calculateTotalOverdraftInterest(currency, transactions);
|
||||
this.totalWithholdTax = wrapper.calculateTotalWithholdTaxWithdrawal(currency, transactions);
|
||||
|
||||
// boolean isUpdated = false;
|
||||
updateRunningBalanceAndPivotDate(false, transactions, null, null, currency);
|
||||
|
||||
this.accountBalance = Money.of(currency, this.totalDeposits).plus(this.totalInterestPosted).minus(this.totalWithdrawals)
|
||||
.minus(this.totalWithdrawalFees).minus(this.totalAnnualFees).minus(this.totalFeeCharge).minus(this.totalPenaltyCharge)
|
||||
.minus(totalOverdraftInterestDerived).minus(totalWithholdTax).getAmount();
|
||||
}
|
||||
|
||||
public void setRunningBalanceOnPivotDate(final BigDecimal runningBalanceOnPivotDate) {
|
||||
this.runningBalanceOnInterestPostingTillDate = runningBalanceOnPivotDate;
|
||||
}
|
||||
|
||||
public void setInterestPostedTillDate(final LocalDate date) {
|
||||
this.interestPostedTillDate = date;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+672
-6
@@ -21,15 +21,32 @@ package org.apache.fineract.portfolio.savings.data;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
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.data.CodeValueData;
|
||||
import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.account.data.AccountTransferData;
|
||||
import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
|
||||
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
|
||||
import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountChargesPaidByData;
|
||||
import org.apache.fineract.portfolio.savings.domain.interest.EndOfDayBalance;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
|
||||
import org.apache.fineract.portfolio.tax.data.TaxComponentData;
|
||||
import org.apache.fineract.portfolio.tax.data.TaxDetailsData;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Immutable data object representing a savings account transaction.
|
||||
@@ -37,7 +54,7 @@ import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
|
||||
@SuppressWarnings("unused")
|
||||
public final class SavingsAccountTransactionData implements Serializable {
|
||||
|
||||
private final Long id;
|
||||
private Long id;
|
||||
private final SavingsAccountTransactionEnumData transactionType;
|
||||
private final Long accountId;
|
||||
private final String accountNo;
|
||||
@@ -46,13 +63,15 @@ public final class SavingsAccountTransactionData implements Serializable {
|
||||
private final PaymentDetailData paymentDetailData;
|
||||
private final BigDecimal amount;
|
||||
private final BigDecimal outstandingChargeAmount;
|
||||
private final BigDecimal runningBalance;
|
||||
private final boolean reversed;
|
||||
private BigDecimal runningBalance;
|
||||
private boolean reversed;
|
||||
private final AccountTransferData transfer;
|
||||
private final LocalDate submittedOnDate;
|
||||
private Date submittedOnDate;
|
||||
private final boolean interestedPostedAsOn;
|
||||
private final String submittedByUsername;
|
||||
private final String note;
|
||||
private final boolean isManualTransaction;
|
||||
private Set<SavingsAccountChargesPaidByData> chargesPaidByData = new HashSet<>();
|
||||
|
||||
// templates
|
||||
final Collection<PaymentTypeData> paymentTypeOptions;
|
||||
@@ -70,6 +89,13 @@ public final class SavingsAccountTransactionData implements Serializable {
|
||||
private String routingCode;
|
||||
private String receiptNumber;
|
||||
private String bankNumber;
|
||||
private BigDecimal cumulativeBalance;
|
||||
private LocalDate balanceEndDate;
|
||||
private transient List<TaxDetailsData> taxDetails = new ArrayList<>();
|
||||
private Integer balanceNumberOfDays;
|
||||
private BigDecimal overdraftAmount;
|
||||
private transient Long modifiedId;
|
||||
private transient String refNo;
|
||||
|
||||
public static SavingsAccountTransactionData importInstance(BigDecimal transactionAmount, LocalDate transactionDate, Long paymentTypeId,
|
||||
String accountNumber, String checkNumber, String routingCode, String receiptNumber, String bankNumber, Long savingsAccountId,
|
||||
@@ -78,6 +104,442 @@ public final class SavingsAccountTransactionData implements Serializable {
|
||||
receiptNumber, bankNumber, savingsAccountId, transactionType, rowIndex, locale, dateFormat);
|
||||
}
|
||||
|
||||
public static SavingsAccountTransactionData interestPosting(final SavingsAccountData savingsAccount, final LocalDate date,
|
||||
final Money amount, final boolean isManualTransaction) {
|
||||
final boolean isReversed = false;
|
||||
final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.INTEREST_POSTING;
|
||||
SavingsAccountTransactionEnumData savingsAccountTransactionEnumData = new SavingsAccountTransactionEnumData(
|
||||
savingsAccountTransactionType.getValue().longValue(), savingsAccountTransactionType.getCode(),
|
||||
savingsAccountTransactionType.getValue().toString());
|
||||
return new SavingsAccountTransactionData(amount.getAmount(), date, savingsAccount.getId(), savingsAccountTransactionEnumData,
|
||||
isReversed, null, isManualTransaction);
|
||||
}
|
||||
|
||||
public static SavingsAccountTransactionData overdraftInterest(final SavingsAccountData savingsAccount, final LocalDate date,
|
||||
final Money amount, final boolean isManualTransaction) {
|
||||
final boolean isReversed = false;
|
||||
final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.OVERDRAFT_INTEREST;
|
||||
SavingsAccountTransactionEnumData savingsAccountTransactionEnumData = new SavingsAccountTransactionEnumData(
|
||||
savingsAccountTransactionType.getValue().longValue(), savingsAccountTransactionType.getCode(),
|
||||
savingsAccountTransactionType.getValue().toString());
|
||||
return new SavingsAccountTransactionData(amount.getAmount(), date, savingsAccount.getId(), savingsAccountTransactionEnumData,
|
||||
isReversed, null, isManualTransaction);
|
||||
}
|
||||
|
||||
public List<TaxDetailsData> getTaxDetails() {
|
||||
return this.taxDetails;
|
||||
}
|
||||
|
||||
public boolean isInterestPostingAndNotReversed() {
|
||||
return this.transactionType.isInterestPosting() && isNotReversed();
|
||||
}
|
||||
|
||||
public void setTaxDetails(final TaxDetailsData taxDetails) {
|
||||
this.taxDetails.add(taxDetails);
|
||||
}
|
||||
|
||||
public boolean isOverdraftInterestAndNotReversed() {
|
||||
return this.transactionType.isIncomeFromInterest() && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isDebit() {
|
||||
return isWithdrawal() || isWithdrawalFeeAndNotReversed() || isAnnualFeeAndNotReversed() || isPayCharge()
|
||||
|| isOverdraftInterestAndNotReversed() || isWithHoldTaxAndNotReversed();
|
||||
}
|
||||
|
||||
public boolean isWithdrawalFeeAndNotReversed() {
|
||||
return this.transactionType.isWithdrawalFee() && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isPayCharge() {
|
||||
return this.transactionType.isPayCharge();
|
||||
}
|
||||
|
||||
public void updateRunningBalance(final Money balance) {
|
||||
this.runningBalance = balance.getAmount();
|
||||
}
|
||||
|
||||
public void updateOverdraftAmount(BigDecimal overdraftAmount) {
|
||||
this.overdraftAmount = overdraftAmount;
|
||||
}
|
||||
|
||||
public boolean isAmountOnHold() {
|
||||
return this.transactionType.isAmountOnHold();
|
||||
}
|
||||
|
||||
public boolean isAnnualFeeAndNotReversed() {
|
||||
return isAnnualFee() && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isAnnualFee() {
|
||||
return this.transactionType.isAnnualFee();
|
||||
}
|
||||
|
||||
public Money getRunningBalance(final CurrencyData currency) {
|
||||
return Money.of(currency, this.runningBalance);
|
||||
}
|
||||
|
||||
public boolean isDepositAndNotReversed() {
|
||||
return this.transactionType.isDeposit() && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isDividendPayoutAndNotReversed() {
|
||||
return this.transactionType.isDividendPayout() && isNotReversed();
|
||||
}
|
||||
|
||||
public void setRefNo(final String uuid) {
|
||||
this.refNo = uuid;
|
||||
}
|
||||
|
||||
public String getRefNo() {
|
||||
return this.refNo;
|
||||
}
|
||||
|
||||
public void setBalanceNumberOfDays(final Integer balanceNumberOfDays) {
|
||||
this.balanceNumberOfDays = balanceNumberOfDays;
|
||||
}
|
||||
|
||||
public Integer getBalanceNumberOfDays() {
|
||||
return this.balanceNumberOfDays;
|
||||
}
|
||||
|
||||
public EndOfDayBalance toEndOfDayBalance(final Money openingBalance) {
|
||||
final MonetaryCurrency currency = openingBalance.getCurrency();
|
||||
Money endOfDayBalance = openingBalance.copy();
|
||||
if (isDeposit() || isDividendPayoutAndNotReversed()) {
|
||||
endOfDayBalance = openingBalance.plus(getAmount());
|
||||
} else if (isWithdrawal() || isChargeTransactionAndNotReversed()) {
|
||||
|
||||
if (openingBalance.isGreaterThanZero()) {
|
||||
endOfDayBalance = openingBalance.minus(getAmount());
|
||||
} else {
|
||||
endOfDayBalance = Money.of(currency, this.runningBalance);
|
||||
}
|
||||
}
|
||||
|
||||
return EndOfDayBalance.from(getTransactionLocalDate(), openingBalance, endOfDayBalance, this.balanceNumberOfDays);
|
||||
}
|
||||
|
||||
public boolean isChargeTransactionAndNotReversed() {
|
||||
return this.transactionType.isChargeTransaction() && isNotReversed();
|
||||
}
|
||||
|
||||
public LocalDate getLastTransactionDate() {
|
||||
return this.transactionDate;
|
||||
}
|
||||
|
||||
public boolean occursOn(final LocalDate occursOnDate) {
|
||||
return getTransactionLocalDate().isEqual(occursOnDate);
|
||||
}
|
||||
|
||||
public LocalDate getTransactionLocalDate() {
|
||||
return this.transactionDate;
|
||||
}
|
||||
|
||||
public EndOfDayBalance toEndOfDayBalanceBoundedBy(final Money openingBalance, final LocalDateInterval boundedBy) {
|
||||
|
||||
final MonetaryCurrency currency = openingBalance.getCurrency();
|
||||
Money endOfDayBalance = openingBalance.copy();
|
||||
|
||||
int numberOfDaysOfBalance = this.balanceNumberOfDays;
|
||||
|
||||
LocalDate balanceStartDate = getTransactionLocalDate();
|
||||
LocalDate balanceEndDate = getEndOfBalanceLocalDate();
|
||||
|
||||
if (boundedBy.startDate().isAfter(balanceStartDate)) {
|
||||
balanceStartDate = boundedBy.startDate();
|
||||
final LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate);
|
||||
numberOfDaysOfBalance = spanOfBalance.daysInPeriodInclusiveOfEndDate();
|
||||
} else {
|
||||
if (isDeposit() || isDividendPayoutAndNotReversed()) {
|
||||
endOfDayBalance = endOfDayBalance.plus(getAmount());
|
||||
} else if (isWithdrawal() || isChargeTransactionAndNotReversed()) {
|
||||
if (endOfDayBalance.isGreaterThanZero()) {
|
||||
endOfDayBalance = endOfDayBalance.minus(getAmount());
|
||||
} else {
|
||||
endOfDayBalance = Money.of(currency, this.runningBalance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (balanceEndDate.isAfter(boundedBy.endDate())) {
|
||||
balanceEndDate = boundedBy.endDate();
|
||||
final LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate);
|
||||
numberOfDaysOfBalance = spanOfBalance.daysInPeriodInclusiveOfEndDate();
|
||||
}
|
||||
|
||||
return EndOfDayBalance.from(balanceStartDate, openingBalance, endOfDayBalance, numberOfDaysOfBalance);
|
||||
}
|
||||
|
||||
public void reverse() {
|
||||
this.reversed = true;
|
||||
}
|
||||
|
||||
public boolean fallsWithin(final LocalDateInterval periodInterval) {
|
||||
final LocalDateInterval balanceInterval = LocalDateInterval.create(getTransactionLocalDate(), getEndOfBalanceLocalDate());
|
||||
return periodInterval.contains(balanceInterval);
|
||||
}
|
||||
|
||||
public LocalDate getEndOfBalanceLocalDate() {
|
||||
return this.balanceEndDate == null ? null : this.balanceEndDate;
|
||||
}
|
||||
|
||||
public void zeroBalanceFields() {
|
||||
this.runningBalance = null;
|
||||
this.cumulativeBalance = null;
|
||||
this.balanceEndDate = null;
|
||||
this.balanceNumberOfDays = null;
|
||||
}
|
||||
|
||||
public boolean isAmountRelease() {
|
||||
return this.transactionType.isAmountRelease();
|
||||
}
|
||||
|
||||
public boolean isCredit() {
|
||||
return isDeposit() || isInterestPostingAndNotReversed() || isDividendPayoutAndNotReversed();
|
||||
}
|
||||
|
||||
public boolean isDeposit() {
|
||||
return this.transactionType.isDeposit();
|
||||
}
|
||||
|
||||
public static SavingsAccountTransactionData copyTransaction(SavingsAccountTransactionData accountTransaction) {
|
||||
return new SavingsAccountTransactionData(accountTransaction.getSavingsAccountId(), null, accountTransaction.getPaymentDetailData(),
|
||||
accountTransaction.getTransactionType(), accountTransaction.getTransactionLocalDate(),
|
||||
accountTransaction.getSubmittedOnDate(), accountTransaction.getAmount(), accountTransaction.isReversed(), null,
|
||||
accountTransaction.isManualTransaction());
|
||||
}
|
||||
|
||||
private SavingsAccountTransactionData(final Long savingsId, final Long officeId, final PaymentDetailData paymentDetailData,
|
||||
final SavingsAccountTransactionEnumData savingsAccountTransactionType, final LocalDate transactionDate, final Date createdDate,
|
||||
final BigDecimal amount, final boolean isReversed, final Long userId, final boolean isManualTransaction) {
|
||||
this.savingsAccountId = savingsId;
|
||||
this.paymentDetailData = paymentDetailData;
|
||||
this.transactionType = savingsAccountTransactionType;
|
||||
this.transactionDate = transactionDate;
|
||||
this.submittedOnDate = createdDate;
|
||||
this.amount = amount;
|
||||
this.isManualTransaction = isManualTransaction;
|
||||
this.id = null;
|
||||
this.accountId = null;
|
||||
this.accountNo = null;
|
||||
this.date = null;
|
||||
this.currency = null;
|
||||
this.outstandingChargeAmount = null;
|
||||
this.runningBalance = null;
|
||||
this.reversed = isReversed;
|
||||
this.transfer = null;
|
||||
this.interestedPostedAsOn = false;
|
||||
this.rowIndex = null;
|
||||
this.dateFormat = null;
|
||||
this.locale = null;
|
||||
this.transactionAmount = null;
|
||||
this.paymentTypeId = null;
|
||||
this.accountNumber = null;
|
||||
this.checkNumber = null;
|
||||
this.routingCode = null;
|
||||
this.receiptNumber = null;
|
||||
this.bankNumber = null;
|
||||
this.paymentTypeOptions = null;
|
||||
this.submittedByUsername = null;
|
||||
this.note = null;
|
||||
}
|
||||
|
||||
public boolean isChargeTransaction() {
|
||||
return this.transactionType.isChargeTransaction();
|
||||
}
|
||||
|
||||
public Set<SavingsAccountChargesPaidByData> getSavingsAccountChargesPaid() {
|
||||
return this.chargesPaidByData;
|
||||
}
|
||||
|
||||
public void updateCumulativeBalanceAndDates(final MonetaryCurrency currency, final LocalDate endOfBalanceDate) {
|
||||
// balance end date should not be before transaction date
|
||||
if (endOfBalanceDate != null && endOfBalanceDate.isBefore(this.transactionDate)) {
|
||||
this.balanceEndDate = this.transactionDate;
|
||||
} else if (endOfBalanceDate != null) {
|
||||
this.balanceEndDate = endOfBalanceDate;
|
||||
} else {
|
||||
this.balanceEndDate = null;
|
||||
}
|
||||
this.balanceNumberOfDays = LocalDateInterval.create(getTransactionLocalDate(), endOfBalanceDate).daysInPeriodInclusiveOfEndDate();
|
||||
this.cumulativeBalance = Money.of(currency, this.runningBalance).multipliedBy(this.balanceNumberOfDays).getAmount();
|
||||
}
|
||||
|
||||
public boolean hasNotAmount(final Money amountToCheck) {
|
||||
final Money transactionAmount = getAmount(amountToCheck.getCurrency());
|
||||
return transactionAmount.isNotEqualTo(amountToCheck);
|
||||
}
|
||||
|
||||
public boolean isFeeChargeAndNotReversed() {
|
||||
return isFeeCharge() && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isFeeCharge() {
|
||||
final SavingsAccountChargesPaidByData chargePaidBy = getSavingsAccountChargePaidBy();
|
||||
return (isPayCharge() && chargePaidBy != null) ? chargePaidBy.isFeeCharge() : false;
|
||||
}
|
||||
|
||||
public void setChargesPaidByData(final SavingsAccountChargesPaidByData savingsAccountChargesPaidByData) {
|
||||
this.chargesPaidByData.add(savingsAccountChargesPaidByData);
|
||||
}
|
||||
|
||||
public void setOverdraftAmount(final BigDecimal overdraftAmount) {
|
||||
this.overdraftAmount = overdraftAmount;
|
||||
}
|
||||
|
||||
public boolean isPenaltyChargeAndNotReversed() {
|
||||
return isPenaltyCharge() && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isPenaltyCharge() {
|
||||
final SavingsAccountChargesPaidByData chargePaidBy = getSavingsAccountChargePaidBy();
|
||||
return (isPayCharge() && chargePaidBy != null) ? chargePaidBy.isPenaltyCharge() : false;
|
||||
}
|
||||
|
||||
public boolean isWaiveFeeChargeAndNotReversed() {
|
||||
return isWaiveFeeCharge() && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isWaiveFeeCharge() {
|
||||
final SavingsAccountChargesPaidByData chargePaidBy = getSavingsAccountChargePaidBy();
|
||||
return (isWaiveCharge() && chargePaidBy != null) ? chargePaidBy.isFeeCharge() : false;
|
||||
}
|
||||
|
||||
public boolean isWaiveCharge() {
|
||||
return SavingsAccountTransactionType.fromInt(this.transactionType.getId().intValue()).isWaiveCharge();
|
||||
}
|
||||
|
||||
public boolean isWaivePenaltyChargeAndNotReversed() {
|
||||
return isWaivePenaltyCharge() && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isWaivePenaltyCharge() {
|
||||
final SavingsAccountChargesPaidByData chargePaidBy = getSavingsAccountChargePaidBy();
|
||||
return (isWaiveCharge() && chargePaidBy != null) ? chargePaidBy.isPenaltyCharge() : false;
|
||||
}
|
||||
|
||||
private SavingsAccountChargesPaidByData getSavingsAccountChargePaidBy() {
|
||||
if (!CollectionUtils.isEmpty(this.chargesPaidByData)) {
|
||||
return this.chargesPaidByData.iterator().next();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Money getAmount(final MonetaryCurrency currency) {
|
||||
return Money.of(currency, this.amount);
|
||||
}
|
||||
|
||||
public static SavingsAccountTransactionData withHoldTax(final SavingsAccountData savingsAccount, final LocalDate date,
|
||||
final Money amount, final Map<TaxComponentData, BigDecimal> taxDetails) {
|
||||
final boolean isReversed = false;
|
||||
final boolean isManualTransaction = false;
|
||||
SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.WITHHOLD_TAX;
|
||||
SavingsAccountTransactionEnumData transactionType = new SavingsAccountTransactionEnumData(
|
||||
savingsAccountTransactionType.getValue().longValue(), savingsAccountTransactionType.getCode(),
|
||||
savingsAccountTransactionType.getValue().toString());
|
||||
SavingsAccountTransactionData accountTransaction = new SavingsAccountTransactionData(amount.getAmount(), date,
|
||||
savingsAccount.getId(), transactionType, isReversed, null, isManualTransaction);
|
||||
updateTaxDetails(taxDetails, accountTransaction);
|
||||
return accountTransaction;
|
||||
}
|
||||
|
||||
public static void updateTaxDetails(final Map<TaxComponentData, BigDecimal> taxDetails,
|
||||
final SavingsAccountTransactionData accountTransaction) {
|
||||
if (taxDetails != null) {
|
||||
for (Map.Entry<TaxComponentData, BigDecimal> mapEntry : taxDetails.entrySet()) {
|
||||
accountTransaction.getTaxDetails().add(new TaxDetailsData(mapEntry.getKey(), mapEntry.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> toMapData(final CurrencyData currencyData, final Long officeId) {
|
||||
final Map<String, Object> thisTransactionData = new LinkedHashMap<>();
|
||||
|
||||
final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations
|
||||
.transactionType(this.transactionType.getId().intValue());
|
||||
|
||||
thisTransactionData.put("id", getId());
|
||||
thisTransactionData.put("officeId", officeId);
|
||||
thisTransactionData.put("type", transactionType);
|
||||
thisTransactionData.put("reversed", Boolean.valueOf(isReversed()));
|
||||
thisTransactionData.put("date", getTransactionLocalDate());
|
||||
thisTransactionData.put("currency", currencyData);
|
||||
thisTransactionData.put("amount", this.amount);
|
||||
thisTransactionData.put("overdraftAmount", this.overdraftAmount);
|
||||
|
||||
if (this.paymentDetailData != null) {
|
||||
thisTransactionData.put("paymentTypeId", this.paymentDetailData.getPaymentType().getId());
|
||||
}
|
||||
|
||||
/***
|
||||
* Sending data in a map, though in savings we currently expect a transaction to always repay a single charge
|
||||
* (or may repay a part of a single charge too)
|
||||
***/
|
||||
if (!this.chargesPaidByData.isEmpty()) {
|
||||
final List<Map<String, Object>> savingsChargesPaidData = new ArrayList<>();
|
||||
for (final SavingsAccountChargesPaidByData chargePaidBy : this.chargesPaidByData) {
|
||||
final Map<String, Object> savingChargePaidData = new LinkedHashMap<>();
|
||||
savingChargePaidData.put("chargeId", chargePaidBy.getSavingsAccountCharge());
|
||||
savingChargePaidData.put("isPenalty", chargePaidBy.getSavingsAccountCharge().isPenalty());
|
||||
savingChargePaidData.put("savingsChargeId", chargePaidBy.getSavingsAccountCharge().getId());
|
||||
savingChargePaidData.put("amount", chargePaidBy.getAmount());
|
||||
|
||||
savingsChargesPaidData.add(savingChargePaidData);
|
||||
}
|
||||
thisTransactionData.put("savingsChargesPaid", savingsChargesPaidData);
|
||||
}
|
||||
|
||||
if (this.taxDetails != null && !this.taxDetails.isEmpty()) {
|
||||
final List<Map<String, Object>> taxData = new ArrayList<>();
|
||||
for (final TaxDetailsData taxDetails : this.taxDetails) {
|
||||
final Map<String, Object> taxDetailsData = new HashMap<>();
|
||||
taxDetailsData.put("amount", taxDetails.getAmount());
|
||||
if (taxDetails.getTaxComponent().getCreditAcount() != null) {
|
||||
taxDetailsData.put("creditAccountId", taxDetails.getTaxComponent().getCreditAcount().getId());
|
||||
}
|
||||
taxData.add(taxDetailsData);
|
||||
}
|
||||
thisTransactionData.put("taxDetails", taxData);
|
||||
}
|
||||
|
||||
return thisTransactionData;
|
||||
}
|
||||
|
||||
private SavingsAccountTransactionData(BigDecimal transactionAmount, LocalDate transactionDate, Long savingsAccountId,
|
||||
SavingsAccountTransactionEnumData transactionType, boolean isReversed, String locale, boolean isManualTransaction) {
|
||||
this.id = null;
|
||||
this.transactionType = transactionType;
|
||||
this.accountId = null;
|
||||
this.accountNo = null;
|
||||
this.date = transactionDate;
|
||||
this.currency = null;
|
||||
this.paymentDetailData = null;
|
||||
this.amount = transactionAmount;
|
||||
this.outstandingChargeAmount = null;
|
||||
this.runningBalance = null;
|
||||
this.reversed = isReversed;
|
||||
this.transfer = null;
|
||||
this.submittedOnDate = Date.from(transactionDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
|
||||
this.interestedPostedAsOn = false;
|
||||
this.rowIndex = null;
|
||||
this.savingsAccountId = savingsAccountId;
|
||||
this.dateFormat = null;
|
||||
this.locale = locale;
|
||||
this.transactionDate = transactionDate;
|
||||
this.transactionAmount = transactionAmount;
|
||||
this.paymentTypeId = null;
|
||||
this.accountNumber = null;
|
||||
this.checkNumber = null;
|
||||
this.routingCode = null;
|
||||
this.receiptNumber = null;
|
||||
this.bankNumber = null;
|
||||
this.paymentTypeOptions = null;
|
||||
this.submittedByUsername = null;
|
||||
this.note = null;
|
||||
this.isManualTransaction = isManualTransaction;
|
||||
}
|
||||
|
||||
private SavingsAccountTransactionData(BigDecimal transactionAmount, LocalDate transactionDate, Long paymentTypeId, String accountNumber,
|
||||
String checkNumber, String routingCode, String receiptNumber, String bankNumber, Long savingsAccountId,
|
||||
SavingsAccountTransactionEnumData transactionType, Integer rowIndex, String locale, String dateFormat) {
|
||||
@@ -110,6 +572,44 @@ public final class SavingsAccountTransactionData implements Serializable {
|
||||
this.paymentTypeOptions = null;
|
||||
this.submittedByUsername = null;
|
||||
this.note = null;
|
||||
this.isManualTransaction = false;
|
||||
}
|
||||
|
||||
private SavingsAccountTransactionData(Integer id, BigDecimal transactionAmount, LocalDate transactionDate, Long paymentTypeId,
|
||||
String accountNumber, String checkNumber, String routingCode, String receiptNumber, String bankNumber, Long savingsAccountId,
|
||||
SavingsAccountTransactionEnumData transactionType, Integer rowIndex, String locale, String dateFormat,
|
||||
BigDecimal cumulativeBalance) {
|
||||
this.id = null;
|
||||
this.transactionType = transactionType;
|
||||
this.accountId = null;
|
||||
this.accountNo = null;
|
||||
this.date = null;
|
||||
this.currency = null;
|
||||
this.paymentDetailData = null;
|
||||
this.amount = null;
|
||||
this.outstandingChargeAmount = null;
|
||||
this.runningBalance = null;
|
||||
this.reversed = false;
|
||||
this.submittedOnDate = null;
|
||||
this.interestedPostedAsOn = false;
|
||||
this.rowIndex = rowIndex;
|
||||
this.savingsAccountId = savingsAccountId;
|
||||
this.dateFormat = dateFormat;
|
||||
this.locale = locale;
|
||||
this.transactionDate = transactionDate;
|
||||
this.transactionAmount = transactionAmount;
|
||||
this.paymentTypeId = paymentTypeId;
|
||||
this.accountNumber = accountNumber;
|
||||
this.checkNumber = checkNumber;
|
||||
this.routingCode = routingCode;
|
||||
this.receiptNumber = receiptNumber;
|
||||
this.bankNumber = bankNumber;
|
||||
this.paymentTypeOptions = null;
|
||||
this.submittedByUsername = null;
|
||||
this.note = null;
|
||||
this.cumulativeBalance = cumulativeBalance;
|
||||
this.transfer = null;
|
||||
this.isManualTransaction = false;
|
||||
}
|
||||
|
||||
public Integer getRowIndex() {
|
||||
@@ -120,10 +620,128 @@ public final class SavingsAccountTransactionData implements Serializable {
|
||||
return savingsAccountId;
|
||||
}
|
||||
|
||||
public PaymentDetailData getPaymentDetailData() {
|
||||
return this.paymentDetailData;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public SavingsAccountTransactionEnumData getTransactionType() {
|
||||
return transactionType;
|
||||
}
|
||||
|
||||
public LocalDate getTransactionDate() {
|
||||
return this.transactionDate;
|
||||
}
|
||||
|
||||
public LocalDate getDate() {
|
||||
return this.date;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
public boolean isWithdrawal() {
|
||||
return this.transactionType.isWithdrawal();
|
||||
}
|
||||
|
||||
public boolean isInterestPosting() {
|
||||
return this.transactionType.isInterestPosting() || this.transactionType.isOverDraftInterestPosting();
|
||||
}
|
||||
|
||||
public boolean isManualTransaction() {
|
||||
return this.isManualTransaction;
|
||||
}
|
||||
|
||||
public boolean isReversed() {
|
||||
return this.reversed;
|
||||
}
|
||||
|
||||
public BigDecimal getRunningBalance() {
|
||||
return this.runningBalance;
|
||||
}
|
||||
|
||||
public BigDecimal getCumulativeBalance() {
|
||||
return this.cumulativeBalance;
|
||||
}
|
||||
|
||||
public LocalDate getBalanceEndDate() {
|
||||
return this.balanceEndDate;
|
||||
}
|
||||
|
||||
public Date getSubmittedOnDate() {
|
||||
return this.submittedOnDate;
|
||||
}
|
||||
|
||||
public boolean isWithHoldTaxAndNotReversed() {
|
||||
return SavingsAccountTransactionType.fromInt(this.transactionType.getId().intValue()).isWithHoldTax() && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isNotReversed() {
|
||||
return !isReversed();
|
||||
}
|
||||
|
||||
public BigDecimal getOverdraftAmount() {
|
||||
return this.overdraftAmount;
|
||||
}
|
||||
|
||||
public boolean spansAnyPortionOf(final LocalDateInterval periodInterval) {
|
||||
final LocalDateInterval balanceInterval = LocalDateInterval.create(getTransactionLocalDate(), getEndOfBalanceLocalDate());
|
||||
return balanceInterval.containsPortionOf(periodInterval);
|
||||
}
|
||||
|
||||
public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType,
|
||||
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
|
||||
final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance,
|
||||
final boolean reversed, final LocalDate submittedOnDate, final boolean interestedPostedAsOn, final BigDecimal cumulativeBalance,
|
||||
final LocalDate balanceEndDate) {
|
||||
final Collection<PaymentTypeData> paymentTypeOptions = null;
|
||||
return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency,
|
||||
amount, outstandingChargeAmount, runningBalance, reversed, submittedOnDate, paymentTypeOptions, interestedPostedAsOn,
|
||||
cumulativeBalance, balanceEndDate);
|
||||
}
|
||||
|
||||
private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType,
|
||||
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
|
||||
final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance,
|
||||
final boolean reversed, final LocalDate submittedOnDate, final Collection<PaymentTypeData> paymentTypeOptions,
|
||||
final boolean interestedPostedAsOn, final BigDecimal cumulativeBalance, final LocalDate balanceEndDate) {
|
||||
|
||||
this(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency, amount, outstandingChargeAmount,
|
||||
runningBalance, reversed, paymentTypeOptions, submittedOnDate, interestedPostedAsOn, cumulativeBalance, balanceEndDate);
|
||||
}
|
||||
|
||||
private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType,
|
||||
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
|
||||
final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance,
|
||||
final boolean reversed, final Collection<PaymentTypeData> paymentTypeOptions, final LocalDate submittedOnDate,
|
||||
final boolean interestedPostedAsOn, final BigDecimal cumulativeBalance, final LocalDate balanceEndDate) {
|
||||
this.id = id;
|
||||
this.transactionDate = date;
|
||||
this.transactionType = transactionType;
|
||||
this.paymentDetailData = paymentDetailData;
|
||||
this.accountId = savingsId;
|
||||
this.accountNo = savingsAccountNo;
|
||||
this.date = date;
|
||||
this.currency = currency;
|
||||
this.amount = amount;
|
||||
this.outstandingChargeAmount = outstandingChargeAmount;
|
||||
this.runningBalance = runningBalance;
|
||||
this.reversed = reversed;
|
||||
this.paymentTypeOptions = paymentTypeOptions;
|
||||
this.submittedOnDate = Date.from(submittedOnDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
|
||||
this.interestedPostedAsOn = interestedPostedAsOn;
|
||||
this.cumulativeBalance = cumulativeBalance;
|
||||
this.transfer = null;
|
||||
this.submittedByUsername = null;
|
||||
this.note = null;
|
||||
this.balanceEndDate = balanceEndDate;
|
||||
this.isManualTransaction = false;
|
||||
}
|
||||
|
||||
public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType,
|
||||
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
|
||||
final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance,
|
||||
@@ -146,6 +764,12 @@ public final class SavingsAccountTransactionData implements Serializable {
|
||||
interestedPostedAsOn, submittedByUsername, note);
|
||||
}
|
||||
|
||||
public static SavingsAccountTransactionData create(final Long id) {
|
||||
final Collection<PaymentTypeData> paymentTypeOptions = null;
|
||||
return new SavingsAccountTransactionData(id, null, null, null, null, null, null, null, null, null, false, null, paymentTypeOptions,
|
||||
null, false, null, null);
|
||||
}
|
||||
|
||||
public static SavingsAccountTransactionData template(final Long savingsId, final String savingsAccountNo,
|
||||
final LocalDate defaultLocalDate, final CurrencyData currency) {
|
||||
final Long id = null;
|
||||
@@ -182,7 +806,7 @@ public final class SavingsAccountTransactionData implements Serializable {
|
||||
final boolean interestedPostedAsOn, final String submittedByUsername, final String note) {
|
||||
|
||||
this(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency, amount, outstandingChargeAmount,
|
||||
runningBalance, reversed, transfer, paymentTypeOptions, null, interestedPostedAsOn, submittedByUsername, note);
|
||||
runningBalance, reversed, transfer, paymentTypeOptions, date, interestedPostedAsOn, submittedByUsername, note);
|
||||
}
|
||||
|
||||
private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType,
|
||||
@@ -203,10 +827,47 @@ public final class SavingsAccountTransactionData implements Serializable {
|
||||
this.reversed = reversed;
|
||||
this.transfer = transfer;
|
||||
this.paymentTypeOptions = paymentTypeOptions;
|
||||
this.submittedOnDate = submittedOnDate;
|
||||
if (submittedOnDate != null) {
|
||||
this.submittedOnDate = Date.from(submittedOnDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
|
||||
} else {
|
||||
this.submittedOnDate = null;
|
||||
}
|
||||
|
||||
this.interestedPostedAsOn = interestedPostedAsOn;
|
||||
this.submittedByUsername = submittedByUsername;
|
||||
this.note = note;
|
||||
this.isManualTransaction = false;
|
||||
}
|
||||
|
||||
private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType,
|
||||
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
|
||||
final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance,
|
||||
final boolean reversed, final AccountTransferData transfer, final Collection<PaymentTypeData> paymentTypeOptions,
|
||||
final LocalDate submittedOnDate, final boolean interestedPostedAsOn, final BigDecimal cumulativeBalance) {
|
||||
this.id = id;
|
||||
this.transactionType = transactionType;
|
||||
this.paymentDetailData = paymentDetailData;
|
||||
this.accountId = savingsId;
|
||||
this.accountNo = savingsAccountNo;
|
||||
this.date = date;
|
||||
this.currency = currency;
|
||||
this.amount = amount;
|
||||
this.outstandingChargeAmount = outstandingChargeAmount;
|
||||
this.runningBalance = runningBalance;
|
||||
this.reversed = reversed;
|
||||
this.transfer = transfer;
|
||||
this.paymentTypeOptions = paymentTypeOptions;
|
||||
if (submittedOnDate != null) {
|
||||
this.submittedOnDate = Date.from(submittedOnDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
|
||||
} else {
|
||||
this.submittedOnDate = null;
|
||||
}
|
||||
|
||||
this.interestedPostedAsOn = interestedPostedAsOn;
|
||||
this.submittedByUsername = null;
|
||||
this.note = null;
|
||||
this.cumulativeBalance = cumulativeBalance;
|
||||
this.isManualTransaction = false;
|
||||
}
|
||||
|
||||
public static SavingsAccountTransactionData withWithDrawalTransactionDetails(
|
||||
@@ -225,4 +886,9 @@ public final class SavingsAccountTransactionData implements Serializable {
|
||||
savingsAccountTransactionData.interestedPostedAsOn, savingsAccountTransactionData.submittedByUsername,
|
||||
savingsAccountTransactionData.note);
|
||||
}
|
||||
|
||||
public void setId(final Long id) {
|
||||
this.id = id;
|
||||
this.modifiedId = id;
|
||||
}
|
||||
}
|
||||
|
||||
+24
@@ -100,6 +100,10 @@ public class SavingsAccountTransactionEnumData implements Serializable {
|
||||
return this.interestPosting;
|
||||
}
|
||||
|
||||
public boolean isIncomeFromInterest() {
|
||||
return this.overdraftInterest;
|
||||
}
|
||||
|
||||
public boolean isFeeDeduction() {
|
||||
return this.feeDeduction;
|
||||
}
|
||||
@@ -152,4 +156,24 @@ public class SavingsAccountTransactionEnumData implements Serializable {
|
||||
return this.amountRelease;
|
||||
}
|
||||
|
||||
public boolean isOverDraftInterestPosting() {
|
||||
return this.overdraftInterest;
|
||||
}
|
||||
|
||||
public boolean isChargeTransaction() {
|
||||
return isPayCharge() || isWithdrawalFee() || isAnnualFee();
|
||||
}
|
||||
|
||||
public boolean isAnnualFee() {
|
||||
return this.value.equals(SavingsAccountTransactionType.ANNUAL_FEE.getValue().toString());
|
||||
}
|
||||
|
||||
public boolean isPayCharge() {
|
||||
return this.value.equals(SavingsAccountTransactionType.PAY_CHARGE.getValue().toString());
|
||||
}
|
||||
|
||||
public boolean isWithdrawalFee() {
|
||||
return this.value.equals(SavingsAccountTransactionType.WITHDRAWAL_FEE.getValue().toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+78
@@ -339,6 +339,67 @@ public final class SavingsProductData implements Serializable {
|
||||
daysToDormancy, daysToEscheat, accountMappingForPayment);
|
||||
}
|
||||
|
||||
public static SavingsProductData createForInterestPosting(final Long id, final EnumOptionData accountingRule) {
|
||||
return new SavingsProductData(id, accountingRule);
|
||||
}
|
||||
|
||||
private SavingsProductData(final Long id, final EnumOptionData accountingRule) {
|
||||
this.id = id;
|
||||
this.name = null;
|
||||
this.shortName = null;
|
||||
this.description = null;
|
||||
this.currency = null;
|
||||
this.nominalAnnualInterestRate = null;
|
||||
this.interestCompoundingPeriodType = null;
|
||||
this.interestPostingPeriodType = null;
|
||||
this.interestCalculationType = null;
|
||||
this.interestCalculationDaysInYearType = null;
|
||||
this.accountingRule = accountingRule;
|
||||
this.minRequiredOpeningBalance = null;
|
||||
this.lockinPeriodFrequency = null;
|
||||
this.lockinPeriodFrequencyType = null;
|
||||
this.withdrawalFeeForTransfers = false;
|
||||
|
||||
this.currencyOptions = null;
|
||||
this.interestCompoundingPeriodTypeOptions = null;
|
||||
this.interestPostingPeriodTypeOptions = null;
|
||||
this.interestCalculationTypeOptions = null;
|
||||
this.interestCalculationDaysInYearTypeOptions = null;
|
||||
this.lockinPeriodFrequencyTypeOptions = null;
|
||||
this.withdrawalFeeTypeOptions = null;
|
||||
|
||||
this.paymentTypeOptions = null;
|
||||
this.accountingMappingOptions = null;
|
||||
this.accountingRuleOptions = null;
|
||||
this.accountingMappings = null;
|
||||
|
||||
this.paymentChannelToFundSourceMappings = null;
|
||||
|
||||
this.charges = null;// charges associated with Savings product
|
||||
this.chargeOptions = null;// charges available for adding to
|
||||
// Savings product
|
||||
this.penaltyOptions = null;// penalties available for adding
|
||||
// to Savings product
|
||||
|
||||
this.feeToIncomeAccountMappings = null;
|
||||
this.penaltyToIncomeAccountMappings = null;
|
||||
this.allowOverdraft = false;
|
||||
this.overdraftLimit = null;
|
||||
this.minRequiredBalance = null;
|
||||
this.enforceMinRequiredBalance = false;
|
||||
this.minBalanceForInterestCalculation = null;
|
||||
this.nominalAnnualInterestRateOverdraft = null;
|
||||
this.minOverdraftForInterestCalculation = null;
|
||||
this.taxGroup = null;
|
||||
this.withHoldTax = false;
|
||||
this.taxGroupOptions = null;
|
||||
this.isDormancyTrackingActive = null;
|
||||
this.daysToInactive = null;
|
||||
this.daysToDormancy = null;
|
||||
this.daysToEscheat = null;
|
||||
this.accountMappingForPayment = null;
|
||||
}
|
||||
|
||||
private SavingsProductData(final Long id, final String name, final String shortName, final String description,
|
||||
final CurrencyData currency, final BigDecimal nominalAnnualInterestRate, final EnumOptionData interestCompoundingPeriodType,
|
||||
final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
|
||||
@@ -509,4 +570,21 @@ public final class SavingsProductData implements Serializable {
|
||||
public boolean isWithdrawalFeeForTransfers() {
|
||||
return withdrawalFeeForTransfers;
|
||||
}
|
||||
|
||||
public boolean isCashBasedAccountingEnabled() {
|
||||
return AccountingRuleType.CASH_BASED.getValue().toString().equals(this.accountingRule.getValue());
|
||||
}
|
||||
|
||||
public boolean isAccrualBasedAccountingEnabled() {
|
||||
return isUpfrontAccrualAccounting() || isPeriodicAccrualAccounting();
|
||||
}
|
||||
|
||||
public boolean isUpfrontAccrualAccounting() {
|
||||
return AccountingRuleType.ACCRUAL_UPFRONT.getValue().toString().equals(this.accountingRule.getValue());
|
||||
}
|
||||
|
||||
public boolean isPeriodicAccrualAccounting() {
|
||||
return AccountingRuleType.ACCRUAL_PERIODIC.getValue().toString().equals(this.accountingRule.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+110
-1
@@ -1003,7 +1003,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
|
||||
if (overdraftAmount.isGreaterThanZero()) {
|
||||
accountTransaction.updateOverdraftAmount(overdraftAmount.getAmount());
|
||||
}
|
||||
accountTransaction.updateRunningBalance(runningBalance);
|
||||
// accountTransaction.updateRunningBalance(runningBalance);
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
addTransactionToExisting(accountTransaction);
|
||||
} else {
|
||||
@@ -1108,6 +1108,11 @@ public class SavingsAccount extends AbstractPersistableCustom {
|
||||
this.sub_status = SavingsAccountSubStatusEnum.NONE.getValue();
|
||||
}
|
||||
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
this.summary.updateSummaryWithPivotConfig(this.currency, this.savingsAccountTransactionSummaryWrapper, transaction,
|
||||
this.savingsAccountTransactions);
|
||||
}
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
@@ -1119,10 +1124,18 @@ public class SavingsAccount extends AbstractPersistableCustom {
|
||||
return activationLocalDate;
|
||||
}
|
||||
|
||||
public AppUser getActivatedBy() {
|
||||
return this.activatedBy;
|
||||
}
|
||||
|
||||
public LocalDate getWithdrawnOnDate() {
|
||||
return withdrawnOnDate == null ? null : LocalDate.ofInstant(withdrawnOnDate.toInstant(), DateUtils.getDateTimeZoneOfTenant());
|
||||
}
|
||||
|
||||
public AppUser getWithdrawnBy() {
|
||||
return this.withdrawnBy;
|
||||
}
|
||||
|
||||
// startInterestCalculationDate is set during migration so that there is no
|
||||
// interference with interest posting of previous system
|
||||
public LocalDate getStartInterestCalculationDate() {
|
||||
@@ -1950,6 +1963,10 @@ public class SavingsAccount extends AbstractPersistableCustom {
|
||||
return gsim;
|
||||
}
|
||||
|
||||
public Long getSavingsProductId() {
|
||||
return this.savingsProduct().getId();
|
||||
}
|
||||
|
||||
public void setGsim(GroupSavingsIndividualMonitoring gsim) {
|
||||
this.gsim = gsim;
|
||||
}
|
||||
@@ -2063,6 +2080,10 @@ public class SavingsAccount extends AbstractPersistableCustom {
|
||||
return rejectedOnDate == null ? null : LocalDate.ofInstant(rejectedOnDate.toInstant(), DateUtils.getDateTimeZoneOfTenant());
|
||||
}
|
||||
|
||||
public AppUser getRejectedBy() {
|
||||
return this.rejectedBy;
|
||||
}
|
||||
|
||||
public void removeSavingsOfficer(final LocalDate unassignDate) {
|
||||
|
||||
final SavingsOfficerAssignmentHistory latestHistoryRecord = findLatestIncompleteHistoryRecord();
|
||||
@@ -2169,6 +2190,18 @@ public class SavingsAccount extends AbstractPersistableCustom {
|
||||
return this.nominalAnnualInterestRate;
|
||||
}
|
||||
|
||||
public Integer getInterestCompoundingPeriodType() {
|
||||
return this.interestCompoundingPeriodType;
|
||||
}
|
||||
|
||||
public Integer getInterestPostingPeriodType() {
|
||||
return this.interestPostingPeriodType;
|
||||
}
|
||||
|
||||
public Integer getInterestCalculationType() {
|
||||
return this.interestCalculationType;
|
||||
}
|
||||
|
||||
public BigDecimal getNominalAnnualInterestRateOverdraft() {
|
||||
return this.nominalAnnualInterestRateOverdraft;
|
||||
}
|
||||
@@ -2775,6 +2808,10 @@ public class SavingsAccount extends AbstractPersistableCustom {
|
||||
return this.closedOnDate == null ? null : LocalDate.ofInstant(this.closedOnDate.toInstant(), DateUtils.getDateTimeZoneOfTenant());
|
||||
}
|
||||
|
||||
public AppUser getClosedBy() {
|
||||
return this.closedBy;
|
||||
}
|
||||
|
||||
public SavingsAccountSummary getSummary() {
|
||||
return this.summary;
|
||||
}
|
||||
@@ -3213,6 +3250,14 @@ public class SavingsAccount extends AbstractPersistableCustom {
|
||||
}
|
||||
}
|
||||
|
||||
public AppUser getSubmittedBy() {
|
||||
return this.submittedBy;
|
||||
}
|
||||
|
||||
public AppUser getApprovedBy() {
|
||||
return this.approvedBy;
|
||||
}
|
||||
|
||||
public boolean allowDeposit() {
|
||||
return true;
|
||||
}
|
||||
@@ -3665,4 +3710,68 @@ public class SavingsAccount extends AbstractPersistableCustom {
|
||||
private boolean isOverdraft() {
|
||||
return allowOverdraft;
|
||||
}
|
||||
|
||||
public Long getGroupId() {
|
||||
return this.groupId();
|
||||
}
|
||||
|
||||
public Integer getInterestCalculationDaysInYearType() {
|
||||
return this.interestCalculationDaysInYearType;
|
||||
}
|
||||
|
||||
public BigDecimal getMinRequiredOpeningBalance() {
|
||||
return this.minRequiredOpeningBalance;
|
||||
}
|
||||
|
||||
public Integer getLockinPeriodFrequency() {
|
||||
return this.lockinPeriodFrequency;
|
||||
}
|
||||
|
||||
public Integer getLockinPeriodFrequencyType() {
|
||||
return this.lockinPeriodFrequencyType;
|
||||
}
|
||||
|
||||
public boolean isWithdrawalFeeForTransfer() {
|
||||
return this.withdrawalFeeApplicableForTransfer;
|
||||
}
|
||||
|
||||
public boolean isAllowOverdraft() {
|
||||
return this.allowOverdraft;
|
||||
}
|
||||
|
||||
public BigDecimal getOverdraftLimit() {
|
||||
return this.overdraftLimit;
|
||||
}
|
||||
|
||||
public BigDecimal getMinOverdraftForInterestCalculation() {
|
||||
return this.minOverdraftForInterestCalculation;
|
||||
}
|
||||
|
||||
public Date getLockedInUntilDate() {
|
||||
return this.lockedInUntilDate;
|
||||
}
|
||||
|
||||
public Integer getDepositType() {
|
||||
return this.depositType;
|
||||
}
|
||||
|
||||
public BigDecimal getMinRequiredBalance() {
|
||||
return this.minRequiredBalance;
|
||||
}
|
||||
|
||||
public boolean isEnforceMinRequiredBalance() {
|
||||
return this.enforceMinRequiredBalance;
|
||||
}
|
||||
|
||||
public BigDecimal getMinBalanceForInterestCalculation() {
|
||||
return this.minBalanceForInterestCalculation;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public boolean isWithHoldTax() {
|
||||
return this.withHoldTax;
|
||||
}
|
||||
}
|
||||
|
||||
+17
@@ -73,6 +73,7 @@ import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYea
|
||||
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
|
||||
import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
|
||||
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.slf4j.Logger;
|
||||
@@ -361,6 +362,18 @@ public class SavingsAccountAssembler {
|
||||
return account;
|
||||
}
|
||||
|
||||
public SavingsAccountData assembleSavings(final SavingsAccountData account) {
|
||||
|
||||
// Update last running balance on account level
|
||||
if (account.getTransactions() != null && account.getTransactions().size() != 0) {
|
||||
account.getSummary().setRunningBalanceOnPivotDate(account.getTransactions().get(account.getTransactions().size() - 1)
|
||||
.getRunningBalance(account.getCurrency()).getAmount());
|
||||
}
|
||||
|
||||
account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
|
||||
return account;
|
||||
}
|
||||
|
||||
public boolean getPivotConfigStatus() {
|
||||
return this.configurationDomainService.retrievePivotDateConfig();
|
||||
}
|
||||
@@ -425,4 +438,8 @@ public class SavingsAccountAssembler {
|
||||
public void assignSavingAccountHelpers(final SavingsAccount savingsAccount) {
|
||||
savingsAccount.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
|
||||
}
|
||||
|
||||
public void assignSavingAccountHelpers(final SavingsAccountData savingsAccountData) {
|
||||
savingsAccountData.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
|
||||
}
|
||||
}
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.savings.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
|
||||
|
||||
public class SavingsAccountChargesPaidByData implements Serializable {
|
||||
|
||||
private final Long chargeId;
|
||||
private final BigDecimal amount;
|
||||
private SavingsAccountChargeData savingsAccountChargeData;
|
||||
|
||||
public SavingsAccountChargesPaidByData(final Long chargeId, final BigDecimal amount) {
|
||||
this.chargeId = chargeId;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
public Long getChargeId() {
|
||||
return this.chargeId;
|
||||
}
|
||||
|
||||
public void setSavingsAccountChargeData(final SavingsAccountChargeData savingsAccountChargeData) {
|
||||
this.savingsAccountChargeData = savingsAccountChargeData;
|
||||
}
|
||||
|
||||
public static SavingsAccountChargesPaidByData instance(final Long savingsAccountChargeId, final BigDecimal amount) {
|
||||
return new SavingsAccountChargesPaidByData(savingsAccountChargeId, amount);
|
||||
}
|
||||
|
||||
public boolean isFeeCharge() {
|
||||
return (this.savingsAccountChargeData == null) ? false : this.savingsAccountChargeData.isFeeCharge();
|
||||
}
|
||||
|
||||
public boolean isPenaltyCharge() {
|
||||
return (this.savingsAccountChargeData == null) ? false : this.savingsAccountChargeData.isPenaltyCharge();
|
||||
}
|
||||
|
||||
public SavingsAccountChargeData getSavingsAccountCharge() {
|
||||
return this.savingsAccountChargeData;
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -170,4 +170,5 @@ public class SavingsAccountRepositoryWrapper {
|
||||
account.loadLazyCollections();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+35
@@ -286,4 +286,39 @@ public final class SavingsAccountSummary {
|
||||
return this.runningBalanceOnInterestPostingTillDate;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalWithdrawals() {
|
||||
return this.totalWithdrawals;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalDeposits() {
|
||||
return this.totalDeposits;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalWithdrawalFees() {
|
||||
return this.totalWithdrawalFees;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalFeeCharge() {
|
||||
return this.totalFeeCharge;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalPenaltyCharge() {
|
||||
return this.totalPenaltyCharge;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalAnnualFees() {
|
||||
return this.totalAnnualFees;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalInterestEarned() {
|
||||
return this.totalInterestEarned;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalOverdraftInterestDerived() {
|
||||
return this.totalOverdraftInterestDerived;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalWithholdTax() {
|
||||
return this.totalWithholdTax;
|
||||
}
|
||||
}
|
||||
|
||||
+54
@@ -201,6 +201,14 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom {
|
||||
isReversed, appUser, isManualTransaction);
|
||||
}
|
||||
|
||||
public static SavingsAccountTransaction from(final Integer transactionTypeEnum, final LocalDate transactionDate,
|
||||
final BigDecimal amount, final boolean isReversed, final BigDecimal runningBalance, final BigDecimal cumulativeBalance,
|
||||
final LocalDate balanceEndDate, final Integer balanceNumberOfDays, final BigDecimal overdraftAmount,
|
||||
final LocalDate createdDate, final boolean isManualTransaction, final Long releaseIdOfHoldAmountTransaction) {
|
||||
return new SavingsAccountTransaction(transactionTypeEnum, transactionDate, amount, isReversed, runningBalance, cumulativeBalance,
|
||||
balanceEndDate, balanceNumberOfDays, overdraftAmount, createdDate, isManualTransaction, releaseIdOfHoldAmountTransaction);
|
||||
}
|
||||
|
||||
public static SavingsAccountTransaction waiver(final SavingsAccount savingsAccount, final Office office, final LocalDate date,
|
||||
final Money amount, final AppUser appUser) {
|
||||
final boolean isReversed = false;
|
||||
@@ -301,6 +309,24 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom {
|
||||
this.isManualTransaction = isManualTransaction;
|
||||
}
|
||||
|
||||
private SavingsAccountTransaction(final Integer transactionTypeEnum, final LocalDate transactionDate, final BigDecimal amount,
|
||||
final boolean isReversed, final BigDecimal runningBalance, final BigDecimal cumulativeBalance, final LocalDate balanceEndDate,
|
||||
final Integer balanceNumberOfDays, final BigDecimal overdraftAmount, final LocalDate createdDate,
|
||||
final boolean isManualTransaction, final Long releaseIdOfHoldAmountTransaction) {
|
||||
this.typeOf = transactionTypeEnum;
|
||||
this.dateOf = Date.from(transactionDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
|
||||
this.amount = amount;
|
||||
this.reversed = isReversed;
|
||||
this.runningBalance = runningBalance;
|
||||
this.cumulativeBalance = cumulativeBalance;
|
||||
this.balanceEndDate = Date.from(balanceEndDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
|
||||
this.balanceNumberOfDays = balanceNumberOfDays;
|
||||
this.overdraftAmount = overdraftAmount;
|
||||
this.createdDate = Date.from(createdDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
|
||||
this.isManualTransaction = isManualTransaction;
|
||||
this.releaseIdOfHoldAmountTransaction = releaseIdOfHoldAmountTransaction;
|
||||
}
|
||||
|
||||
public static SavingsAccountTransaction holdAmount(final SavingsAccount savingsAccount, final Office office,
|
||||
final PaymentDetail paymentDetail, final LocalDate date, final Money amount, Date createdDate, final AppUser appUser) {
|
||||
final boolean isReversed = false;
|
||||
@@ -809,4 +835,32 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom {
|
||||
public boolean isAmountOnHoldNotReleased() {
|
||||
return (isAmountOnHold() && getReleaseIdOfHoldAmountTransaction() == null);
|
||||
}
|
||||
|
||||
public Long getOfficeId() {
|
||||
return this.office.getId();
|
||||
}
|
||||
|
||||
public Date getBalanceEndDate() {
|
||||
return this.balanceEndDate;
|
||||
}
|
||||
|
||||
public BigDecimal getCumulativeBalance() {
|
||||
return this.cumulativeBalance;
|
||||
}
|
||||
|
||||
public Integer getBalanceNumberOfDays() {
|
||||
return this.balanceNumberOfDays;
|
||||
}
|
||||
|
||||
public Long getAppUserId() {
|
||||
return this.appUser.getId();
|
||||
}
|
||||
|
||||
public Date getCreatedDate() {
|
||||
return this.createdDate;
|
||||
}
|
||||
|
||||
public boolean getIsManualTransaction() {
|
||||
return this.isManualTransaction;
|
||||
}
|
||||
}
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.savings.domain;
|
||||
|
||||
import java.util.Comparator;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
|
||||
|
||||
public class SavingsAccountTransactionDataComparator implements Comparator<SavingsAccountTransactionData> {
|
||||
|
||||
@Override
|
||||
public int compare(final SavingsAccountTransactionData o1, final SavingsAccountTransactionData o2) {
|
||||
int compareResult = 0;
|
||||
|
||||
final int comparsion = o1.getTransactionDate().compareTo(o2.getLastTransactionDate());
|
||||
if (comparsion == 0) {
|
||||
compareResult = o1.getSubmittedOnDate().compareTo(o2.getSubmittedOnDate());
|
||||
if (compareResult == 0 && o1.getId() != null && o2.getId() != null) {
|
||||
compareResult = o1.getId().compareTo(o2.getId());
|
||||
} else {
|
||||
compareResult = comparsion;
|
||||
}
|
||||
} else {
|
||||
compareResult = comparsion;
|
||||
}
|
||||
return compareResult;
|
||||
}
|
||||
|
||||
}
|
||||
+113
@@ -20,8 +20,10 @@ package org.apache.fineract.portfolio.savings.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -41,6 +43,16 @@ public final class SavingsAccountTransactionSummaryWrapper {
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalDeposits(final CurrencyData currency, final List<SavingsAccountTransactionData> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final SavingsAccountTransactionData transaction : transactions) {
|
||||
if (transaction.isDepositAndNotReversed() || transaction.isDividendPayoutAndNotReversed()) {
|
||||
total = total.plus(transaction.getAmount());
|
||||
}
|
||||
}
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalWithdrawals(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final SavingsAccountTransaction transaction : transactions) {
|
||||
@@ -51,6 +63,16 @@ public final class SavingsAccountTransactionSummaryWrapper {
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalWithdrawals(final CurrencyData currency, final List<SavingsAccountTransactionData> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final SavingsAccountTransactionData transaction : transactions) {
|
||||
if (transaction.isWithdrawal() && transaction.isNotReversed()) {
|
||||
total = total.plus(transaction.getAmount());
|
||||
}
|
||||
}
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalInterestPosted(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final SavingsAccountTransaction transaction : transactions) {
|
||||
@@ -61,6 +83,16 @@ public final class SavingsAccountTransactionSummaryWrapper {
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalInterestPosted(final CurrencyData currency, final List<SavingsAccountTransactionData> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final SavingsAccountTransactionData transaction : transactions) {
|
||||
if (transaction.isInterestPostingAndNotReversed() && transaction.isNotReversed()) {
|
||||
total = total.plus(transaction.getAmount());
|
||||
}
|
||||
}
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalInterestPosted(final MonetaryCurrency currency, final BigDecimal currentInterestPosted,
|
||||
final List<SavingsAccountTransaction> savingsAccountTransactions) {
|
||||
Money total = Money.of(currency, currentInterestPosted);
|
||||
@@ -82,6 +114,16 @@ public final class SavingsAccountTransactionSummaryWrapper {
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalWithdrawalFees(final CurrencyData currency, final List<SavingsAccountTransactionData> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final SavingsAccountTransactionData transaction : transactions) {
|
||||
if (transaction.isWithdrawalFeeAndNotReversed() && transaction.isNotReversed()) {
|
||||
total = total.plus(transaction.getAmount());
|
||||
}
|
||||
}
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalAnnualFees(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final SavingsAccountTransaction transaction : transactions) {
|
||||
@@ -92,6 +134,16 @@ public final class SavingsAccountTransactionSummaryWrapper {
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalAnnualFees(final CurrencyData currency, final List<SavingsAccountTransactionData> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final SavingsAccountTransactionData transaction : transactions) {
|
||||
if (transaction.isAnnualFeeAndNotReversed() && transaction.isNotReversed()) {
|
||||
total = total.plus(transaction.getAmount());
|
||||
}
|
||||
}
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalFeesCharge(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final SavingsAccountTransaction transaction : transactions) {
|
||||
@@ -102,6 +154,16 @@ public final class SavingsAccountTransactionSummaryWrapper {
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalFeesCharge(final CurrencyData currency, final List<SavingsAccountTransactionData> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final SavingsAccountTransactionData transaction : transactions) {
|
||||
if (transaction.isFeeChargeAndNotReversed()) {
|
||||
total = total.plus(transaction.getAmount());
|
||||
}
|
||||
}
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalFeesChargeWaived(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final SavingsAccountTransaction transaction : transactions) {
|
||||
@@ -112,6 +174,16 @@ public final class SavingsAccountTransactionSummaryWrapper {
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalFeesChargeWaived(final CurrencyData currency, final List<SavingsAccountTransactionData> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final SavingsAccountTransactionData transaction : transactions) {
|
||||
if (transaction.isWaiveFeeChargeAndNotReversed()) {
|
||||
total = total.plus(transaction.getAmount());
|
||||
}
|
||||
}
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalPenaltyCharge(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final SavingsAccountTransaction transaction : transactions) {
|
||||
@@ -122,6 +194,16 @@ public final class SavingsAccountTransactionSummaryWrapper {
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalPenaltyCharge(final CurrencyData currency, final List<SavingsAccountTransactionData> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final SavingsAccountTransactionData transaction : transactions) {
|
||||
if (transaction.isPenaltyChargeAndNotReversed()) {
|
||||
total = total.plus(transaction.getAmount());
|
||||
}
|
||||
}
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalPenaltyChargeWaived(final MonetaryCurrency currency,
|
||||
final List<SavingsAccountTransaction> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
@@ -133,6 +215,17 @@ public final class SavingsAccountTransactionSummaryWrapper {
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalPenaltyChargeWaived(final CurrencyData currency,
|
||||
final List<SavingsAccountTransactionData> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final SavingsAccountTransactionData transaction : transactions) {
|
||||
if (transaction.isWaivePenaltyChargeAndNotReversed()) {
|
||||
total = total.plus(transaction.getAmount());
|
||||
}
|
||||
}
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalOverdraftInterest(MonetaryCurrency currency, BigDecimal overdraftPosted,
|
||||
List<SavingsAccountTransaction> transactions) {
|
||||
Money total = Money.of(currency, overdraftPosted);
|
||||
@@ -154,6 +247,16 @@ public final class SavingsAccountTransactionSummaryWrapper {
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalOverdraftInterest(CurrencyData currency, List<SavingsAccountTransactionData> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final SavingsAccountTransactionData transaction : transactions) {
|
||||
if (transaction.isOverdraftInterestAndNotReversed()) {
|
||||
total = total.plus(transaction.getAmount());
|
||||
}
|
||||
}
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalWithholdTaxWithdrawal(MonetaryCurrency currency, List<SavingsAccountTransaction> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final SavingsAccountTransaction transaction : transactions) {
|
||||
@@ -163,4 +266,14 @@ public final class SavingsAccountTransactionSummaryWrapper {
|
||||
}
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
|
||||
public BigDecimal calculateTotalWithholdTaxWithdrawal(CurrencyData currency, List<SavingsAccountTransactionData> transactions) {
|
||||
Money total = Money.zero(currency);
|
||||
for (final SavingsAccountTransactionData transaction : transactions) {
|
||||
if (transaction.isWithHoldTaxAndNotReversed()) {
|
||||
total = total.plus(transaction.getAmount());
|
||||
}
|
||||
}
|
||||
return total.getAmountDefaultedToNullIfZero();
|
||||
}
|
||||
}
|
||||
|
||||
+75
@@ -32,6 +32,7 @@ import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
|
||||
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
|
||||
|
||||
public final class PostingPeriod {
|
||||
@@ -96,6 +97,7 @@ public final class PostingPeriod {
|
||||
boolean interestTransfered = false;
|
||||
Money openingDayBalance = periodStartingBalance;
|
||||
Money closeOfDayBalance = openingDayBalance;
|
||||
|
||||
for (final SavingsAccountTransaction transaction : orderedListOfTransactions) {
|
||||
|
||||
if (transaction.fallsWithin(periodInterval)) {
|
||||
@@ -155,6 +157,79 @@ public final class PostingPeriod {
|
||||
minOverdraftForInterestCalculation, isUserPosting, financialYearBeginningMonth);
|
||||
}
|
||||
|
||||
public static PostingPeriod createFromDTO(final LocalDateInterval periodInterval, final Money periodStartingBalance,
|
||||
final List<SavingsAccountTransactionData> orderedListOfTransactions, final MonetaryCurrency currency,
|
||||
final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
|
||||
final SavingsInterestCalculationType interestCalculationType, final BigDecimal interestRateAsFraction, final long daysInYear,
|
||||
final LocalDate upToInterestCalculationDate, Collection<Long> interestPostTransactions, boolean isInterestTransfer,
|
||||
final Money minBalanceForInterestCalculation, final boolean isSavingsInterestPostingAtCurrentPeriodEnd,
|
||||
final BigDecimal overdraftInterestRateAsFraction, final Money minOverdraftForInterestCalculation, boolean isUserPosting,
|
||||
int financialYearBeginningMonth) {
|
||||
|
||||
final List<EndOfDayBalance> accountEndOfDayBalances = new ArrayList<>();
|
||||
boolean interestTransfered = false;
|
||||
Money openingDayBalance = periodStartingBalance;
|
||||
Money closeOfDayBalance = openingDayBalance;
|
||||
|
||||
for (final SavingsAccountTransactionData transaction : orderedListOfTransactions) {
|
||||
|
||||
if (transaction.fallsWithin(periodInterval)) {
|
||||
// the balance of the transaction falls entirely within this
|
||||
// period so no need to do any cropping/bounding
|
||||
final EndOfDayBalance endOfDayBalance = transaction.toEndOfDayBalance(openingDayBalance);
|
||||
accountEndOfDayBalances.add(endOfDayBalance);
|
||||
|
||||
openingDayBalance = endOfDayBalance.closingBalance();
|
||||
|
||||
} else if (transaction.spansAnyPortionOf(periodInterval)) {
|
||||
final EndOfDayBalance endOfDayBalance = transaction.toEndOfDayBalanceBoundedBy(openingDayBalance, periodInterval);
|
||||
accountEndOfDayBalances.add(endOfDayBalance);
|
||||
|
||||
closeOfDayBalance = endOfDayBalance.closingBalance();
|
||||
openingDayBalance = closeOfDayBalance;
|
||||
}
|
||||
|
||||
// this check is to make sure to add interest if withdrawal is
|
||||
// happened for already
|
||||
if (transaction.occursOn(periodInterval.endDate().plusDays(1))) {
|
||||
if (transaction.getId() == null) {
|
||||
interestTransfered = isInterestTransfer;
|
||||
} else if (interestPostTransactions.contains(transaction.getId())) {
|
||||
interestTransfered = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (accountEndOfDayBalances.isEmpty()) {
|
||||
LocalDate balanceStartDate = periodInterval.startDate();
|
||||
LocalDate balanceEndDate = periodInterval.endDate();
|
||||
Integer numberOfDaysOfBalance = periodInterval.daysInPeriodInclusiveOfEndDate();
|
||||
|
||||
if (balanceEndDate.isAfter(upToInterestCalculationDate)) {
|
||||
balanceEndDate = upToInterestCalculationDate;
|
||||
final LocalDateInterval spanOfBalance = LocalDateInterval.create(balanceStartDate, balanceEndDate);
|
||||
numberOfDaysOfBalance = spanOfBalance.daysInPeriodInclusiveOfEndDate();
|
||||
}
|
||||
|
||||
final EndOfDayBalance endOfDayBalance = EndOfDayBalance.from(balanceStartDate, openingDayBalance, closeOfDayBalance,
|
||||
numberOfDaysOfBalance);
|
||||
|
||||
accountEndOfDayBalances.add(endOfDayBalance);
|
||||
|
||||
closeOfDayBalance = endOfDayBalance.closingBalance();
|
||||
openingDayBalance = closeOfDayBalance;
|
||||
}
|
||||
|
||||
final List<CompoundingPeriod> compoundingPeriods = compoundingPeriodsInPostingPeriod(periodInterval, interestCompoundingPeriodType,
|
||||
accountEndOfDayBalances, upToInterestCalculationDate, financialYearBeginningMonth);
|
||||
|
||||
return new PostingPeriod(periodInterval, currency, periodStartingBalance, openingDayBalance, interestCompoundingPeriodType,
|
||||
interestCalculationType, interestRateAsFraction, daysInYear, compoundingPeriods, interestTransfered,
|
||||
minBalanceForInterestCalculation, isSavingsInterestPostingAtCurrentPeriodEnd, overdraftInterestRateAsFraction,
|
||||
minOverdraftForInterestCalculation, isUserPosting, financialYearBeginningMonth);
|
||||
}
|
||||
|
||||
private PostingPeriod(final LocalDateInterval periodInterval, final MonetaryCurrency currency, final Money openingBalance,
|
||||
final Money closingBalance, final SavingsCompoundingInterestPeriodType interestCompoundingType,
|
||||
final SavingsInterestCalculationType interestCalculationType, final BigDecimal interestRateAsFraction, final long daysInYear,
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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.savings.service;
|
||||
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
|
||||
|
||||
public interface SavingsAccountInterestPostingService {
|
||||
|
||||
SavingsAccountData postInterest(MathContext mc, LocalDate interestPostingUpToDate, boolean isInterestTransfer,
|
||||
boolean isSavingsInterestPostingAtCurrentPeriodEnd, Integer financialYearBeginningMonth, LocalDate postInterestOnDate,
|
||||
boolean backdatedTxnsAllowedTill, SavingsAccountData savingsAccountData);
|
||||
|
||||
}
|
||||
+564
@@ -0,0 +1,564 @@
|
||||
/**
|
||||
* 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.savings.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
||||
import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
|
||||
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
|
||||
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
|
||||
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountChargesPaidByData;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionDataComparator;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsHelper;
|
||||
import org.apache.fineract.portfolio.savings.domain.interest.PostingPeriod;
|
||||
import org.apache.fineract.portfolio.tax.data.TaxComponentData;
|
||||
import org.apache.fineract.portfolio.tax.service.TaxUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class SavingsAccountInterestPostingServiceImpl implements SavingsAccountInterestPostingService {
|
||||
|
||||
private final SavingsHelper savingsHelper;
|
||||
|
||||
@Autowired
|
||||
public SavingsAccountInterestPostingServiceImpl(final SavingsHelper savingsHelper) {
|
||||
this.savingsHelper = savingsHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SavingsAccountData postInterest(final MathContext mc, final LocalDate interestPostingUpToDate, final boolean isInterestTransfer,
|
||||
final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
|
||||
final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill, final SavingsAccountData savingsAccountData) {
|
||||
|
||||
Money interestPostedToDate = Money.zero(savingsAccountData.currency());
|
||||
LocalDate startInterestDate = getStartInterestCalculationDate(savingsAccountData);
|
||||
|
||||
if (backdatedTxnsAllowedTill && savingsAccountData.getSummary().getInterestPostedTillDate() != null) {
|
||||
interestPostedToDate = Money.of(savingsAccountData.currency(), savingsAccountData.getSummary().getTotalInterestPosted());
|
||||
SavingsAccountTransactionData savingsAccountTransactionData = retrieveLastTransactions(savingsAccountData);
|
||||
Date lastTransactionDate = Date.from(
|
||||
savingsAccountTransactionData.getLastTransactionDate().atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant());
|
||||
savingsAccountData.setStartInterestCalculationDate(lastTransactionDate);
|
||||
} else {
|
||||
savingsAccountData.setStartInterestCalculationDate(
|
||||
Date.from(startInterestDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()));
|
||||
}
|
||||
|
||||
final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer,
|
||||
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill,
|
||||
savingsAccountData);
|
||||
|
||||
boolean recalucateDailyBalanceDetails = false;
|
||||
boolean applyWithHoldTax = isWithHoldTaxApplicableForInterestPosting(savingsAccountData);
|
||||
final List<SavingsAccountTransactionData> withholdTransactions = new ArrayList<>();
|
||||
|
||||
withholdTransactions.addAll(findWithHoldSavingsTransactionsWithPivotConfig(savingsAccountData));
|
||||
|
||||
for (final PostingPeriod interestPostingPeriod : postingPeriods) {
|
||||
|
||||
final LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction();
|
||||
final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned();
|
||||
|
||||
if (!interestPostingTransactionDate.isAfter(interestPostingUpToDate)) {
|
||||
interestPostedToDate = interestPostedToDate.plus(interestEarnedToBePostedForPeriod);
|
||||
|
||||
final SavingsAccountTransactionData postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate,
|
||||
savingsAccountData);
|
||||
|
||||
if (postingTransaction == null) {
|
||||
SavingsAccountTransactionData newPostingTransaction;
|
||||
if (interestEarnedToBePostedForPeriod.isGreaterThanOrEqualTo(Money.zero(savingsAccountData.currency()))) {
|
||||
newPostingTransaction = SavingsAccountTransactionData.interestPosting(savingsAccountData,
|
||||
interestPostingTransactionDate, interestEarnedToBePostedForPeriod, interestPostingPeriod.isUserPosting());
|
||||
} else {
|
||||
newPostingTransaction = SavingsAccountTransactionData.interestPosting(savingsAccountData,
|
||||
interestPostingTransactionDate, interestEarnedToBePostedForPeriod.negated(),
|
||||
interestPostingPeriod.isUserPosting());
|
||||
}
|
||||
|
||||
savingsAccountData.updateTransactions(newPostingTransaction);
|
||||
|
||||
if (applyWithHoldTax) {
|
||||
createWithHoldTransaction(interestEarnedToBePostedForPeriod.getAmount(), interestPostingTransactionDate,
|
||||
savingsAccountData);
|
||||
}
|
||||
recalucateDailyBalanceDetails = true;
|
||||
} else {
|
||||
boolean correctionRequired = false;
|
||||
if (postingTransaction.isInterestPostingAndNotReversed()) {
|
||||
correctionRequired = postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod);
|
||||
} else {
|
||||
correctionRequired = postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod.negated());
|
||||
}
|
||||
if (correctionRequired) {
|
||||
boolean applyWithHoldTaxForOldTransaction = false;
|
||||
postingTransaction.reverse();
|
||||
|
||||
final SavingsAccountTransactionData withholdTransaction = findTransactionFor(interestPostingTransactionDate,
|
||||
withholdTransactions);
|
||||
|
||||
if (withholdTransaction != null) {
|
||||
withholdTransaction.reverse();
|
||||
applyWithHoldTaxForOldTransaction = true;
|
||||
}
|
||||
SavingsAccountTransactionData newPostingTransaction;
|
||||
if (interestEarnedToBePostedForPeriod.isGreaterThanOrEqualTo(Money.zero(savingsAccountData.currency()))) {
|
||||
newPostingTransaction = SavingsAccountTransactionData.interestPosting(savingsAccountData,
|
||||
interestPostingTransactionDate, interestEarnedToBePostedForPeriod,
|
||||
interestPostingPeriod.isUserPosting());
|
||||
} else {
|
||||
newPostingTransaction = SavingsAccountTransactionData.overdraftInterest(savingsAccountData,
|
||||
interestPostingTransactionDate, interestEarnedToBePostedForPeriod.negated(),
|
||||
interestPostingPeriod.isUserPosting());
|
||||
}
|
||||
|
||||
savingsAccountData.updateTransactions(newPostingTransaction);
|
||||
|
||||
if (applyWithHoldTaxForOldTransaction) {
|
||||
createWithHoldTransaction(interestEarnedToBePostedForPeriod.getAmount(), interestPostingTransactionDate,
|
||||
savingsAccountData);
|
||||
}
|
||||
recalucateDailyBalanceDetails = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (recalucateDailyBalanceDetails) {
|
||||
// no openingBalance concept supported yet but probably will to
|
||||
// allow
|
||||
// for migrations.
|
||||
Money openingAccountBalance = Money.zero(savingsAccountData.currency());
|
||||
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
if (savingsAccountData.getSummary().getLastInterestCalculationDate() == null) {
|
||||
openingAccountBalance = Money.zero(savingsAccountData.currency());
|
||||
} else {
|
||||
openingAccountBalance = Money.of(savingsAccountData.currency(),
|
||||
savingsAccountData.getSummary().getRunningBalanceOnPivotDate());
|
||||
}
|
||||
}
|
||||
|
||||
// update existing transactions so derived balance fields are
|
||||
// correct.
|
||||
recalculateDailyBalances(openingAccountBalance, interestPostingUpToDate, backdatedTxnsAllowedTill, savingsAccountData);
|
||||
}
|
||||
|
||||
if (!backdatedTxnsAllowedTill) {
|
||||
savingsAccountData.getSummary().updateSummary(savingsAccountData.currency(),
|
||||
savingsAccountData.getSavingsAccountTransactionSummaryWrapper(), savingsAccountData.getSavingsAccountTransactionData());
|
||||
} else {
|
||||
savingsAccountData.getSummary().updateSummaryWithPivotConfig(savingsAccountData.currency(),
|
||||
savingsAccountData.getSavingsAccountTransactionSummaryWrapper(), null,
|
||||
savingsAccountData.getSavingsAccountTransactionData());
|
||||
}
|
||||
|
||||
return savingsAccountData;
|
||||
}
|
||||
|
||||
protected SavingsAccountTransactionData findTransactionFor(final LocalDate postingDate,
|
||||
final List<SavingsAccountTransactionData> transactions) {
|
||||
SavingsAccountTransactionData transaction = null;
|
||||
for (final SavingsAccountTransactionData savingsAccountTransaction : transactions) {
|
||||
if (savingsAccountTransaction.occursOn(postingDate)) {
|
||||
transaction = savingsAccountTransaction;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return transaction;
|
||||
}
|
||||
|
||||
public List<PostingPeriod> calculateInterestUsing(final MathContext mc, final LocalDate upToInterestCalculationDate,
|
||||
boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
|
||||
final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill, final SavingsAccountData savingsAccountData) {
|
||||
|
||||
// no openingBalance concept supported yet but probably will to allow
|
||||
// for migrations.
|
||||
Money openingAccountBalance = null;
|
||||
|
||||
// Check global configurations and 'pivot' date is null
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
openingAccountBalance = Money.of(savingsAccountData.currency(), savingsAccountData.getSummary().getRunningBalanceOnPivotDate());
|
||||
} else {
|
||||
openingAccountBalance = Money.zero(savingsAccountData.currency());
|
||||
}
|
||||
|
||||
// update existing transactions so derived balance fields are
|
||||
// correct.
|
||||
recalculateDailyBalances(openingAccountBalance, upToInterestCalculationDate, backdatedTxnsAllowedTill, savingsAccountData);
|
||||
|
||||
// 1. default to calculate interest based on entire history OR
|
||||
// 2. determine latest 'posting period' and find interest credited to
|
||||
// that period
|
||||
|
||||
// A generate list of EndOfDayBalances (not including interest postings)
|
||||
|
||||
final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType
|
||||
.fromInt(savingsAccountData.getInterestPostingPeriodType());
|
||||
|
||||
final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType
|
||||
.fromInt(savingsAccountData.getInterestCompoundingPeriodType());
|
||||
|
||||
final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType
|
||||
.fromInt(savingsAccountData.getInterestCalculationDaysInYearType());
|
||||
|
||||
List<LocalDate> postedAsOnDates = getManualPostingDates(savingsAccountData);
|
||||
if (postInterestOnDate != null) {
|
||||
postedAsOnDates.add(postInterestOnDate);
|
||||
}
|
||||
final List<LocalDateInterval> postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods(
|
||||
savingsAccountData.getStartInterestCalculationDate(), upToInterestCalculationDate, postingPeriodType,
|
||||
financialYearBeginningMonth, postedAsOnDates);
|
||||
|
||||
final List<PostingPeriod> allPostingPeriods = new ArrayList<>();
|
||||
|
||||
Money periodStartingBalance;
|
||||
if (savingsAccountData.getStartInterestCalculationDate() != null) {
|
||||
final SavingsAccountTransactionData transaction = retrieveLastTransactions(savingsAccountData);
|
||||
|
||||
if (transaction == null) {
|
||||
final String defaultUserMessage = "No transactions were found on the specified date "
|
||||
+ savingsAccountData.getStartInterestCalculationDate().toString() + " for account number "
|
||||
+ savingsAccountData.getAccountNo() + " and resource id " + savingsAccountData.getId();
|
||||
|
||||
final ApiParameterError error = ApiParameterError.parameterError(
|
||||
"error.msg.savingsaccount.transaction.incorrect.start.interest.calculation.date", defaultUserMessage,
|
||||
"transactionDate", savingsAccountData.getStartInterestCalculationDate().toString());
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
dataValidationErrors.add(error);
|
||||
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
|
||||
periodStartingBalance = transaction.getRunningBalance(savingsAccountData.currency());
|
||||
} else {
|
||||
periodStartingBalance = Money.zero(savingsAccountData.currency());
|
||||
}
|
||||
|
||||
final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType
|
||||
.fromInt(savingsAccountData.getInterestCalculationType());
|
||||
final BigDecimal interestRateAsFraction = getEffectiveInterestRateAsFraction(mc, upToInterestCalculationDate, savingsAccountData);
|
||||
final BigDecimal overdraftInterestRateAsFraction = getEffectiveOverdraftInterestRateAsFraction(mc, savingsAccountData);
|
||||
final Collection<Long> interestPostTransactions = this.savingsHelper.fetchPostInterestTransactionIds(savingsAccountData.getId());
|
||||
final Money minBalanceForInterestCalculation = Money.of(savingsAccountData.currency(),
|
||||
minBalanceForInterestCalculation(savingsAccountData));
|
||||
final Money minOverdraftForInterestCalculation = Money.of(savingsAccountData.currency(),
|
||||
savingsAccountData.getMinOverdraftForInterestCalculation());
|
||||
final MonetaryCurrency monetaryCurrency = MonetaryCurrency.fromCurrencyData(savingsAccountData.currency());
|
||||
|
||||
for (final LocalDateInterval periodInterval : postingPeriodIntervals) {
|
||||
|
||||
boolean isUserPosting = false;
|
||||
if (postedAsOnDates.contains(periodInterval.endDate().plusDays(1))) {
|
||||
isUserPosting = true;
|
||||
}
|
||||
final PostingPeriod postingPeriod = PostingPeriod.createFromDTO(periodInterval, periodStartingBalance,
|
||||
retreiveOrderedNonInterestPostingTransactions(savingsAccountData), monetaryCurrency, compoundingPeriodType,
|
||||
interestCalculationType, interestRateAsFraction, daysInYearType.getValue(), upToInterestCalculationDate,
|
||||
interestPostTransactions, isInterestTransfer, minBalanceForInterestCalculation,
|
||||
isSavingsInterestPostingAtCurrentPeriodEnd, overdraftInterestRateAsFraction, minOverdraftForInterestCalculation,
|
||||
isUserPosting, financialYearBeginningMonth);
|
||||
|
||||
periodStartingBalance = postingPeriod.closingBalance();
|
||||
|
||||
allPostingPeriods.add(postingPeriod);
|
||||
}
|
||||
|
||||
this.savingsHelper.calculateInterestForAllPostingPeriods(monetaryCurrency, allPostingPeriods,
|
||||
getLockedInUntilLocalDate(savingsAccountData), false);
|
||||
|
||||
savingsAccountData.getSummary().updateFromInterestPeriodSummaries(monetaryCurrency, allPostingPeriods);
|
||||
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
savingsAccountData.getSummary().updateSummaryWithPivotConfig(savingsAccountData.currency(),
|
||||
savingsAccountData.getSavingsAccountTransactionSummaryWrapper(), null, savingsAccountData.getTransactions());
|
||||
} else {
|
||||
savingsAccountData.getSummary().updateSummary(savingsAccountData.currency(),
|
||||
savingsAccountData.getSavingsAccountTransactionSummaryWrapper(), savingsAccountData.getTransactions());
|
||||
}
|
||||
|
||||
return allPostingPeriods;
|
||||
}
|
||||
|
||||
private List<SavingsAccountTransactionData> retreiveOrderedNonInterestPostingTransactions(final SavingsAccountData savingsAccountData) {
|
||||
final List<SavingsAccountTransactionData> listOfTransactionsSorted = retreiveListOfTransactions(savingsAccountData);
|
||||
|
||||
final List<SavingsAccountTransactionData> orderedNonInterestPostingTransactions = new ArrayList<>();
|
||||
|
||||
for (final SavingsAccountTransactionData transaction : listOfTransactionsSorted) {
|
||||
if (!(transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed())
|
||||
&& transaction.isNotReversed()) {
|
||||
orderedNonInterestPostingTransactions.add(transaction);
|
||||
}
|
||||
}
|
||||
orderedNonInterestPostingTransactions.sort(new SavingsAccountTransactionDataComparator());
|
||||
return orderedNonInterestPostingTransactions;
|
||||
}
|
||||
|
||||
private List<SavingsAccountTransactionData> retreiveListOfTransactions(final SavingsAccountData savingsAccountData) {
|
||||
if (savingsAccountData.getTransactions() != null && savingsAccountData.getTransactions().size() == 1) {
|
||||
return savingsAccountData.getTransactions();
|
||||
}
|
||||
|
||||
final List<SavingsAccountTransactionData> listOfTransactionsSorted = new ArrayList<>();
|
||||
listOfTransactionsSorted.addAll(savingsAccountData.getTransactions());
|
||||
|
||||
final SavingsAccountTransactionDataComparator transactionComparator = new SavingsAccountTransactionDataComparator();
|
||||
Collections.sort(listOfTransactionsSorted, transactionComparator);
|
||||
return listOfTransactionsSorted;
|
||||
}
|
||||
|
||||
protected LocalDate getLockedInUntilLocalDate(final SavingsAccountData savingsAccount) {
|
||||
LocalDate lockedInUntilLocalDate = null;
|
||||
if (savingsAccount.getLockedInUntilDate() != null) {
|
||||
lockedInUntilLocalDate = savingsAccount.getActivationLocalDate();
|
||||
// lockedInUntilLocalDate = LocalDate.ofInstant(this.lockedInUntilDate.toInstant(),
|
||||
// DateUtils.getDateTimeZoneOfTenant());
|
||||
}
|
||||
return lockedInUntilLocalDate;
|
||||
}
|
||||
|
||||
private BigDecimal minBalanceForInterestCalculation(final SavingsAccountData savingsAccountData) {
|
||||
return savingsAccountData.getMinBalanceForInterestCalculation();
|
||||
}
|
||||
|
||||
private BigDecimal getEffectiveOverdraftInterestRateAsFraction(MathContext mc, final SavingsAccountData savingsAccountData) {
|
||||
return savingsAccountData.getNominalAnnualInterestRateOverdraft().divide(BigDecimal.valueOf(100L), mc);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate upToInterestCalculationDate,
|
||||
final SavingsAccountData savingsAccountData) {
|
||||
return savingsAccountData.getNominalAnnualInterestRate().divide(BigDecimal.valueOf(100L), mc);
|
||||
}
|
||||
|
||||
public List<SavingsAccountTransactionData> getTransactions(final SavingsAccountData savingsAccountData) {
|
||||
return savingsAccountData.getTransactions();
|
||||
}
|
||||
|
||||
private SavingsAccountTransactionData retrieveLastTransactions(final SavingsAccountData savingsAccountData) {
|
||||
if (savingsAccountData.getTransactions() != null && savingsAccountData.getTransactions().size() == 1) {
|
||||
return savingsAccountData.getTransactions().get(0);
|
||||
}
|
||||
final List<SavingsAccountTransactionData> listOfTransactionsSorted = new ArrayList<>();
|
||||
listOfTransactionsSorted.addAll(savingsAccountData.getTransactions());
|
||||
final SavingsAccountTransactionDataComparator transactionComparator = new SavingsAccountTransactionDataComparator();
|
||||
Collections.sort(listOfTransactionsSorted, transactionComparator);
|
||||
return listOfTransactionsSorted.get(0);
|
||||
}
|
||||
|
||||
public LocalDate getStartInterestCalculationDate(final SavingsAccountData savingsAccountData) {
|
||||
LocalDate startInterestCalculationLocalDate = null;
|
||||
if (savingsAccountData.getStartInterestCalculationDate() != null) {
|
||||
startInterestCalculationLocalDate = savingsAccountData.getStartInterestCalculationDate();
|
||||
} else {
|
||||
startInterestCalculationLocalDate = getActivationLocalDate(savingsAccountData);
|
||||
}
|
||||
return startInterestCalculationLocalDate;
|
||||
}
|
||||
|
||||
public LocalDate getActivationLocalDate(final SavingsAccountData savingsAccountData) {
|
||||
LocalDate activationLocalDate = null;
|
||||
if (savingsAccountData.getActivationLocalDate() != null) {
|
||||
activationLocalDate = savingsAccountData.getActivationLocalDate();
|
||||
}
|
||||
return activationLocalDate;
|
||||
}
|
||||
|
||||
public List<LocalDate> getManualPostingDates(final SavingsAccountData savingsAccountData) {
|
||||
List<LocalDate> transactions = new ArrayList<>();
|
||||
for (SavingsAccountTransactionData trans : savingsAccountData.getSavingsAccountTransactionData()) {
|
||||
if (trans.isInterestPosting() && trans.isNotReversed() && trans.isManualTransaction()) {
|
||||
transactions.add(trans.getTransactionLocalDate());
|
||||
}
|
||||
}
|
||||
return transactions;
|
||||
}
|
||||
|
||||
protected void recalculateDailyBalances(final Money openingAccountBalance, final LocalDate interestPostingUpToDate,
|
||||
final boolean backdatedTxnsAllowedTill, final SavingsAccountData savingsAccountData) {
|
||||
|
||||
Money runningBalance = openingAccountBalance.copy();
|
||||
|
||||
List<SavingsAccountTransactionData> accountTransactionsSorted = retreiveListOfTransactions(savingsAccountData);
|
||||
|
||||
boolean isTransactionsModified = false;
|
||||
|
||||
for (final SavingsAccountTransactionData transaction : accountTransactionsSorted) {
|
||||
if (transaction.isReversed()) {
|
||||
transaction.zeroBalanceFields();
|
||||
} else {
|
||||
Money overdraftAmount = Money.zero(savingsAccountData.currency());
|
||||
Money transactionAmount = Money.zero(savingsAccountData.currency());
|
||||
if (transaction.isCredit() || transaction.isAmountRelease()) {
|
||||
if (runningBalance.isLessThanZero()) {
|
||||
Money diffAmount = Money.of(savingsAccountData.currency(), transaction.getAmount()).plus(runningBalance);
|
||||
if (diffAmount.isGreaterThanZero()) {
|
||||
overdraftAmount = Money.of(savingsAccountData.currency(), transaction.getAmount()).minus(diffAmount);
|
||||
} else {
|
||||
overdraftAmount = Money.of(savingsAccountData.currency(), transaction.getAmount());
|
||||
}
|
||||
}
|
||||
transactionAmount = transactionAmount.plus(transaction.getAmount());
|
||||
} else if (transaction.isDebit() || transaction.isAmountOnHold()) {
|
||||
if (runningBalance.isLessThanZero()) {
|
||||
overdraftAmount = Money.of(savingsAccountData.currency(), transaction.getAmount());
|
||||
}
|
||||
transactionAmount = transactionAmount.minus(transaction.getAmount());
|
||||
}
|
||||
|
||||
runningBalance = runningBalance.plus(transactionAmount);
|
||||
transaction.updateRunningBalance(runningBalance);
|
||||
if (overdraftAmount.isZero() && runningBalance.isLessThanZero()) {
|
||||
overdraftAmount = overdraftAmount.plus(runningBalance.getAmount().negate());
|
||||
}
|
||||
if (transaction.getId() == null && overdraftAmount.isGreaterThanZero()) {
|
||||
transaction.updateOverdraftAmount(overdraftAmount.getAmount());
|
||||
} else if (overdraftAmount.isNotEqualTo(Money.of(savingsAccountData.currency(), transaction.getOverdraftAmount()))) {
|
||||
SavingsAccountTransactionData accountTransaction = SavingsAccountTransactionData.copyTransaction(transaction);
|
||||
if (transaction.isChargeTransaction()) {
|
||||
Set<SavingsAccountChargesPaidByData> chargesPaidBy = transaction.getSavingsAccountChargesPaid();
|
||||
final Set<SavingsAccountChargesPaidByData> newChargePaidBy = new HashSet<>();
|
||||
chargesPaidBy.forEach(
|
||||
x -> newChargePaidBy.add(SavingsAccountChargesPaidByData.instance(x.getChargeId(), x.getAmount())));
|
||||
accountTransaction.getSavingsAccountChargesPaid().addAll(newChargePaidBy);
|
||||
}
|
||||
transaction.reverse();
|
||||
if (overdraftAmount.isGreaterThanZero()) {
|
||||
accountTransaction.updateOverdraftAmount(overdraftAmount.getAmount());
|
||||
}
|
||||
accountTransaction.updateRunningBalance(runningBalance);
|
||||
addTransactionToExisting(accountTransaction, savingsAccountData);
|
||||
|
||||
isTransactionsModified = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (isTransactionsModified) {
|
||||
accountTransactionsSorted = retreiveListOfTransactions(savingsAccountData);
|
||||
}
|
||||
resetAccountTransactionsEndOfDayBalances(accountTransactionsSorted, interestPostingUpToDate, savingsAccountData);
|
||||
}
|
||||
|
||||
public void addTransactionToExisting(final SavingsAccountTransactionData transaction, final SavingsAccountData savingsAccountData) {
|
||||
savingsAccountData.updateTransactions(transaction);
|
||||
}
|
||||
|
||||
private List<SavingsAccountTransactionData> findWithHoldSavingsTransactionsWithPivotConfig(
|
||||
final SavingsAccountData savingsAccountData) {
|
||||
final List<SavingsAccountTransactionData> withholdTransactions = new ArrayList<>();
|
||||
List<SavingsAccountTransactionData> trans = savingsAccountData.getSavingsAccountTransactionData();
|
||||
for (final SavingsAccountTransactionData transaction : trans) {
|
||||
if (transaction.isWithHoldTaxAndNotReversed()) {
|
||||
withholdTransactions.add(transaction);
|
||||
}
|
||||
}
|
||||
return withholdTransactions;
|
||||
}
|
||||
|
||||
private boolean createWithHoldTransaction(final BigDecimal amount, final LocalDate date, final SavingsAccountData savingsAccountData) {
|
||||
boolean isTaxAdded = false;
|
||||
if (savingsAccountData.getTaxGroupData() != null && amount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
Map<TaxComponentData, BigDecimal> taxSplit = TaxUtils.splitTaxData(amount, date,
|
||||
savingsAccountData.getTaxGroupData().getTaxAssociations().stream().collect(Collectors.toSet()), amount.scale());
|
||||
BigDecimal totalTax = TaxUtils.totalTaxDataAmount(taxSplit);
|
||||
if (totalTax.compareTo(BigDecimal.ZERO) > 0) {
|
||||
SavingsAccountTransactionData withholdTransaction = SavingsAccountTransactionData.withHoldTax(savingsAccountData, date,
|
||||
Money.of(savingsAccountData.currency(), totalTax), taxSplit);
|
||||
|
||||
savingsAccountData.getSavingsAccountTransactionData().add(withholdTransaction);
|
||||
// if (backdatedTxnsAllowedTill) {
|
||||
// addTransactionToExisting(withholdTransaction);
|
||||
// } else {
|
||||
// addTransaction(withholdTransaction);
|
||||
// }
|
||||
isTaxAdded = true;
|
||||
}
|
||||
}
|
||||
return isTaxAdded;
|
||||
}
|
||||
|
||||
protected SavingsAccountTransactionData findInterestPostingTransactionFor(final LocalDate postingDate,
|
||||
final SavingsAccountData savingsAccountData) {
|
||||
SavingsAccountTransactionData postingTransation = null;
|
||||
List<SavingsAccountTransactionData> trans = savingsAccountData.getSavingsAccountTransactionData();
|
||||
for (final SavingsAccountTransactionData transaction : trans) {
|
||||
if ((transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed())
|
||||
&& transaction.occursOn(postingDate)) {
|
||||
postingTransation = transaction;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return postingTransation;
|
||||
}
|
||||
|
||||
protected void resetAccountTransactionsEndOfDayBalances(final List<SavingsAccountTransactionData> accountTransactionsSorted,
|
||||
final LocalDate interestPostingUpToDate, final SavingsAccountData savingsAccountData) {
|
||||
// loop over transactions in reverse
|
||||
LocalDate endOfBalanceDate = interestPostingUpToDate;
|
||||
for (int i = accountTransactionsSorted.size() - 1; i >= 0; i--) {
|
||||
final SavingsAccountTransactionData transaction = accountTransactionsSorted.get(i);
|
||||
if (transaction.isNotReversed()
|
||||
&& !(transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed())) {
|
||||
transaction.updateCumulativeBalanceAndDates(MonetaryCurrency.fromCurrencyData(savingsAccountData.currency()),
|
||||
endOfBalanceDate);
|
||||
// this transactions transaction date is end of balance date for
|
||||
// previous transaction.
|
||||
endOfBalanceDate = transaction.getTransactionLocalDate().minusDays(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isWithHoldTaxApplicableForInterestPosting(final SavingsAccountData savingsAccountData) {
|
||||
return this.withHoldTax(savingsAccountData) && this.depositAccountType(savingsAccountData).isSavingsDeposit();
|
||||
}
|
||||
|
||||
private boolean withHoldTax(final SavingsAccountData savingsAccountData) {
|
||||
return savingsAccountData.withHoldTax();
|
||||
}
|
||||
|
||||
public DepositAccountType depositAccountType(final SavingsAccountData savingsAccountData) {
|
||||
return savingsAccountData.depositAccountType();
|
||||
}
|
||||
|
||||
}
|
||||
+5
@@ -63,4 +63,9 @@ public interface SavingsAccountReadPlatformService {
|
||||
String retrieveAccountNumberByAccountId(Long accountId);
|
||||
|
||||
List<Long> getAccountsIdsByStatusPaged(Integer status, int pageSize, Long maxSavingsIdInList);
|
||||
|
||||
List<SavingsAccountData> retrieveAllSavingsDataForInterestPosting(boolean backdatedTxnsAllowedTill, int pageSize, Integer status,
|
||||
Long maxSavingsId);
|
||||
|
||||
List<SavingsAccountTransactionData> retrieveAllTransactionData(List<String> refNo);
|
||||
}
|
||||
|
||||
+536
-3
@@ -27,8 +27,12 @@ import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.accounting.common.AccountingRuleType;
|
||||
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
@@ -69,15 +73,20 @@ import org.apache.fineract.portfolio.savings.data.SavingsAccountSummaryData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountChargesPaidByData;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountSubStatusEnum;
|
||||
import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
|
||||
import org.apache.fineract.portfolio.tax.data.TaxComponentData;
|
||||
import org.apache.fineract.portfolio.tax.data.TaxDetailsData;
|
||||
import org.apache.fineract.portfolio.tax.data.TaxGroupData;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -98,7 +107,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
// mappers
|
||||
private final SavingsAccountTransactionTemplateMapper transactionTemplateMapper;
|
||||
private final SavingsAccountTransactionsMapper transactionsMapper;
|
||||
private final SavingsAccountTransactionsForBatchMapper savingsAccountTransactionsForBatchMapper;
|
||||
private final SavingAccountMapper savingAccountMapper;
|
||||
private final SavingAccountMapperForInterestPosting savingAccountMapperForInterestPosting;
|
||||
// private final SavingsAccountAnnualFeeMapper annualFeeMapper;
|
||||
|
||||
// pagination
|
||||
@@ -106,6 +117,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
|
||||
private final EntityDatatableChecksReadService entityDatatableChecksReadService;
|
||||
private final ColumnValidator columnValidator;
|
||||
private final SavingsAccountAssembler savingAccountAssembler;
|
||||
|
||||
@Autowired
|
||||
public SavingsAccountReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
|
||||
@@ -113,7 +125,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
final SavingsProductReadPlatformService savingProductReadPlatformService,
|
||||
final StaffReadPlatformService staffReadPlatformService, final SavingsDropdownReadPlatformService dropdownReadPlatformService,
|
||||
final ChargeReadPlatformService chargeReadPlatformService,
|
||||
final EntityDatatableChecksReadService entityDatatableChecksReadService, final ColumnValidator columnValidator) {
|
||||
final EntityDatatableChecksReadService entityDatatableChecksReadService, final ColumnValidator columnValidator,
|
||||
final SavingsAccountAssembler savingAccountAssembler) {
|
||||
this.context = context;
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
this.clientReadPlatformService = clientReadPlatformService;
|
||||
@@ -123,11 +136,14 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
this.dropdownReadPlatformService = dropdownReadPlatformService;
|
||||
this.transactionTemplateMapper = new SavingsAccountTransactionTemplateMapper();
|
||||
this.transactionsMapper = new SavingsAccountTransactionsMapper();
|
||||
this.savingsAccountTransactionsForBatchMapper = new SavingsAccountTransactionsForBatchMapper();
|
||||
this.savingAccountMapper = new SavingAccountMapper();
|
||||
// this.annualFeeMapper = new SavingsAccountAnnualFeeMapper();
|
||||
this.chargeReadPlatformService = chargeReadPlatformService;
|
||||
this.entityDatatableChecksReadService = entityDatatableChecksReadService;
|
||||
this.columnValidator = columnValidator;
|
||||
this.savingAccountMapperForInterestPosting = new SavingAccountMapperForInterestPosting();
|
||||
this.savingAccountAssembler = savingAccountAssembler;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -230,6 +246,495 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SavingsAccountTransactionData> retrieveAllTransactionData(final List<String> refNo) throws DataAccessException {
|
||||
String inSql = String.join(",", Collections.nCopies(refNo.size(), "?"));
|
||||
String sql = "select " + this.savingsAccountTransactionsForBatchMapper.schema() + " where tr.ref_no in (%s)";
|
||||
Object[] params = new Object[refNo.size()];
|
||||
int i = 0;
|
||||
for (String element : refNo) {
|
||||
params[i] = element;
|
||||
i++;
|
||||
}
|
||||
return this.jdbcTemplate.query(String.format(sql, inSql), this.savingsAccountTransactionsForBatchMapper, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SavingsAccountData> retrieveAllSavingsDataForInterestPosting(final boolean backdatedTxnsAllowedTill, final int pageSize,
|
||||
final Integer status, final Long maxSavingsId) {
|
||||
LocalDate currentDate = DateUtils.getLocalDateOfTenant().minusDays(1);
|
||||
|
||||
String sql = "select " + this.savingAccountMapperForInterestPosting.schema()
|
||||
+ "join (select a.id from m_savings_account a where a.id > ? and a.status_enum = ? limit ?) b on b.id = sa.id ";
|
||||
if (backdatedTxnsAllowedTill) {
|
||||
sql = sql
|
||||
+ "where if (sa.interest_posted_till_date is not null, tr.transaction_date >= sa.interest_posted_till_date, tr.transaction_date >= sa.activatedon_date) ";
|
||||
}
|
||||
|
||||
sql = sql + "and apm.product_type=2 and sa.interest_posted_till_date<" + java.sql.Date.valueOf(currentDate);
|
||||
sql = sql + " order by sa.id, tr.transaction_date, tr.created_date, tr.id";
|
||||
|
||||
List<SavingsAccountData> savingsAccountDataList = this.jdbcTemplate.query(sql, this.savingAccountMapperForInterestPosting,
|
||||
new Object[] { maxSavingsId, status, pageSize });
|
||||
for (SavingsAccountData savingsAccountData : savingsAccountDataList) {
|
||||
this.savingAccountAssembler.assembleSavings(savingsAccountData);
|
||||
}
|
||||
return savingsAccountDataList;
|
||||
}
|
||||
|
||||
private static final class SavingAccountMapperForInterestPosting implements ResultSetExtractor<List<SavingsAccountData>> {
|
||||
|
||||
private final String schemaSql;
|
||||
|
||||
SavingAccountMapperForInterestPosting() {
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, ");
|
||||
sqlBuilder.append("sa.deposit_type_enum as depositType, ");
|
||||
sqlBuilder.append("c.id as clientId, c.office_id as clientOfficeId, ");
|
||||
sqlBuilder.append("g.id as groupId, g.office_id as groupOfficeId, ");
|
||||
sqlBuilder.append("sa.status_enum as statusEnum, ");
|
||||
sqlBuilder.append("sa.sub_status_enum as subStatusEnum, ");
|
||||
sqlBuilder.append("sa.submittedon_date as submittedOnDate,");
|
||||
|
||||
sqlBuilder.append("sa.rejectedon_date as rejectedOnDate,");
|
||||
|
||||
sqlBuilder.append("sa.withdrawnon_date as withdrawnOnDate,");
|
||||
|
||||
sqlBuilder.append("sa.approvedon_date as approvedOnDate,");
|
||||
|
||||
sqlBuilder.append("sa.activatedon_date as activatedOnDate,");
|
||||
|
||||
sqlBuilder.append("sa.closedon_date as closedOnDate,");
|
||||
|
||||
sqlBuilder.append(
|
||||
"sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, ");
|
||||
|
||||
sqlBuilder.append("sa.nominal_annual_interest_rate as nominalAnnualInterestRate, ");
|
||||
sqlBuilder.append("sa.interest_compounding_period_enum as interestCompoundingPeriodType, ");
|
||||
sqlBuilder.append("sa.interest_posting_period_enum as interestPostingPeriodType, ");
|
||||
sqlBuilder.append("sa.interest_calculation_type_enum as interestCalculationType, ");
|
||||
sqlBuilder.append("sa.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, ");
|
||||
sqlBuilder.append("sa.min_required_opening_balance as minRequiredOpeningBalance, ");
|
||||
sqlBuilder.append("sa.lockin_period_frequency as lockinPeriodFrequency,");
|
||||
sqlBuilder.append("sa.lockin_period_frequency_enum as lockinPeriodFrequencyType, ");
|
||||
sqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, ");
|
||||
sqlBuilder.append("sa.allow_overdraft as allowOverdraft, ");
|
||||
sqlBuilder.append("sa.overdraft_limit as overdraftLimit, ");
|
||||
sqlBuilder.append("sa.nominal_annual_interest_rate_overdraft as nominalAnnualInterestRateOverdraft, ");
|
||||
sqlBuilder.append("sa.min_overdraft_for_interest_calculation as minOverdraftForInterestCalculation, ");
|
||||
sqlBuilder.append("sa.total_deposits_derived as totalDeposits, ");
|
||||
sqlBuilder.append("sa.total_withdrawals_derived as totalWithdrawals, ");
|
||||
sqlBuilder.append("sa.total_withdrawal_fees_derived as totalWithdrawalFees, ");
|
||||
sqlBuilder.append("sa.total_annual_fees_derived as totalAnnualFees, ");
|
||||
sqlBuilder.append("sa.total_interest_earned_derived as totalInterestEarned, ");
|
||||
sqlBuilder.append("sa.total_interest_posted_derived as totalInterestPosted, ");
|
||||
sqlBuilder.append("sa.total_overdraft_interest_derived as totalOverdraftInterestDerived, ");
|
||||
sqlBuilder.append("sa.account_balance_derived as accountBalance, ");
|
||||
sqlBuilder.append("sa.total_fees_charge_derived as totalFeeCharge, ");
|
||||
sqlBuilder.append("sa.total_penalty_charge_derived as totalPenaltyCharge, ");
|
||||
sqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation,");
|
||||
sqlBuilder.append("sa.min_required_balance as minRequiredBalance, ");
|
||||
sqlBuilder.append("sa.enforce_min_required_balance as enforceMinRequiredBalance, ");
|
||||
sqlBuilder.append("sa.on_hold_funds_derived as onHoldFunds, ");
|
||||
sqlBuilder.append("sa.withhold_tax as withHoldTax, ");
|
||||
sqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax, ");
|
||||
sqlBuilder.append("sa.last_interest_calculation_date as lastInterestCalculationDate, ");
|
||||
sqlBuilder.append("sa.total_savings_amount_on_hold as onHoldAmount, ");
|
||||
sqlBuilder.append("sa.interest_posted_till_date as interestPostedTillDate, ");
|
||||
sqlBuilder.append("tg.id as taxGroupId, ");
|
||||
sqlBuilder.append("(select IFNULL(max(sat.transaction_date),sa.activatedon_date) ");
|
||||
sqlBuilder.append("from m_savings_account_transaction as sat ");
|
||||
sqlBuilder.append("where sat.is_reversed = 0 ");
|
||||
sqlBuilder.append("and sat.transaction_type_enum in (1,2) ");
|
||||
sqlBuilder.append("and sat.savings_account_id = sa.id) as lastActiveTransactionDate, ");
|
||||
sqlBuilder.append("sp.id as productId, ");
|
||||
sqlBuilder.append("sp.is_dormancy_tracking_active as isDormancyTrackingActive, ");
|
||||
sqlBuilder.append("sp.days_to_inactive as daysToInactive, ");
|
||||
sqlBuilder.append("sp.days_to_dormancy as daysToDormancy, ");
|
||||
sqlBuilder.append("sp.days_to_escheat as daysToEscheat, ");
|
||||
sqlBuilder.append("sp.accounting_type as accountingType, ");
|
||||
sqlBuilder.append("tr.id as transactionId, tr.transaction_type_enum as transactionType, ");
|
||||
sqlBuilder.append("tr.transaction_date as transactionDate, tr.amount as transactionAmount,");
|
||||
sqlBuilder.append("tr.created_date as createdDate,tr.cumulative_balance_derived as cumulativeBalance,");
|
||||
sqlBuilder.append("tr.running_balance_derived as runningBalance, tr.is_reversed as reversed,");
|
||||
sqlBuilder.append("tr.balance_end_date_derived as balanceEndDate, tr.overdraft_amount_derived as overdraftAmount,");
|
||||
sqlBuilder.append("tr.is_manual as manualTransaction,tr.office_id as officeId, ");
|
||||
sqlBuilder.append("pd.payment_type_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, ");
|
||||
sqlBuilder.append("pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, ");
|
||||
sqlBuilder.append("pt.value as paymentTypeName, ");
|
||||
sqlBuilder.append("msacpb.amount as paidByAmount, msacpb.id as chargesPaidById, ");
|
||||
sqlBuilder.append(
|
||||
"msac.id as chargeId, msac.amount as chargeAmount, msac.charge_time_enum as chargeTimeType, msac.is_penalty as isPenaltyCharge, ");
|
||||
sqlBuilder.append("txd.id as taxDetailsId, txd.amount as taxAmount, ");
|
||||
sqlBuilder.append("apm.gl_account_id as glAccountIdForInterestOnSavings, apm1.gl_account_id as glAccountIdForSavingsControl, ");
|
||||
sqlBuilder.append(
|
||||
"mtc.id as taxComponentId, mtc.debit_account_id as debitAccountId, mtc.credit_account_id as creditAccountId, mtc.percentage as taxPercentage ");
|
||||
sqlBuilder.append("from m_savings_account sa ");
|
||||
sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id ");
|
||||
sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
|
||||
sqlBuilder.append("join m_savings_account_transaction tr on sa.id = tr.savings_account_id ");
|
||||
sqlBuilder.append("left join m_payment_detail pd on pd.id = tr.payment_detail_id ");
|
||||
sqlBuilder.append("left join m_payment_type pt on pt.id = pd.payment_type_id ");
|
||||
sqlBuilder.append("left join m_savings_account_charge_paid_by msacpb on msacpb.savings_account_transaction_id = tr.id ");
|
||||
sqlBuilder.append("left join m_savings_account_charge msac on msac.id = msacpb.savings_account_charge_id ");
|
||||
sqlBuilder.append("left join m_client c ON c.id = sa.client_id ");
|
||||
sqlBuilder.append("left join m_group g ON g.id = sa.group_id ");
|
||||
sqlBuilder.append("left join m_tax_group tg on tg.id = sa.tax_group_id ");
|
||||
sqlBuilder.append("left join m_savings_account_transaction_tax_details txd on txd.savings_transaction_id = tr.id ");
|
||||
sqlBuilder.append("left join m_tax_component mtc on mtc.id = txd.tax_component_id ");
|
||||
sqlBuilder.append("left join acc_product_mapping apm on apm.product_id = sp.id and apm.financial_account_type=3 ");
|
||||
sqlBuilder.append("left join acc_product_mapping apm1 on apm1.product_id = sp.id and apm1.financial_account_type=2 ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
return this.schemaSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SavingsAccountData> extractData(final ResultSet rs) throws SQLException {
|
||||
|
||||
List<SavingsAccountData> savingsAccountDataList = new ArrayList<>();
|
||||
HashMap<String, Long> savingsMap = new HashMap<>();
|
||||
String currencyCode = null;
|
||||
Integer currencyDigits = null;
|
||||
Integer inMultiplesOf = null;
|
||||
CurrencyData currency = null;
|
||||
HashMap<String, Long> transMap = new HashMap<>();
|
||||
HashMap<String, Long> taxDetails = new HashMap<>();
|
||||
HashMap<String, Long> chargeDetails = new HashMap<>();
|
||||
SavingsAccountTransactionData savingsAccountTransactionData = null;
|
||||
SavingsAccountData savingsAccountData = null;
|
||||
int count = 0;
|
||||
|
||||
while (rs.next()) {
|
||||
final Long id = rs.getLong("id");
|
||||
final Long transactionId = rs.getLong("transactionId");
|
||||
final Long taxDetailId = JdbcSupport.getLongDefaultToNullIfZero(rs, "taxDetailsId");
|
||||
final Long taxComponentId = JdbcSupport.getLongDefaultToNullIfZero(rs, "taxComponentId");
|
||||
final String accountNo = rs.getString("accountNo");
|
||||
final Long chargeId = rs.getLong("chargeId");
|
||||
|
||||
if (!savingsMap.containsValue(id)) {
|
||||
if (count > 0) {
|
||||
savingsAccountDataList.add(savingsAccountData);
|
||||
}
|
||||
count++;
|
||||
savingsMap.put("id", id);
|
||||
|
||||
final String externalId = rs.getString("externalId");
|
||||
final Integer depositTypeId = rs.getInt("depositType");
|
||||
final EnumOptionData depositType = SavingsEnumerations.depositType(depositTypeId);
|
||||
final Long groupId = JdbcSupport.getLong(rs, "groupId");
|
||||
final Long groupOfficeId = JdbcSupport.getLong(rs, "groupOfficeId");
|
||||
final GroupGeneralData groupGeneralData = new GroupGeneralData(groupId, groupOfficeId);
|
||||
|
||||
final Long clientId = JdbcSupport.getLong(rs, "clientId");
|
||||
final Long clientOfficeId = JdbcSupport.getLong(rs, "clientOfficeId");
|
||||
final ClientData clientData = ClientData.createClientForInterestPosting(clientId, clientOfficeId);
|
||||
|
||||
final Long glAccountIdForInterestOnSavings = rs.getLong("glAccountIdForInterestOnSavings");
|
||||
final Long glAccountIdForSavingsControl = rs.getLong("glAccountIdForSavingsControl");
|
||||
|
||||
final Long productId = rs.getLong("productId");
|
||||
final Integer accountType = rs.getInt("accountingType");
|
||||
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(accountType);
|
||||
final EnumOptionData enumOptionDataForAccounting = new EnumOptionData(accountType.longValue(),
|
||||
accountingRuleType.getCode(), accountingRuleType.getValue().toString());
|
||||
final SavingsProductData savingsProductData = SavingsProductData.createForInterestPosting(productId,
|
||||
enumOptionDataForAccounting);
|
||||
|
||||
final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum");
|
||||
final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusEnum);
|
||||
final Integer subStatusEnum = JdbcSupport.getInteger(rs, "subStatusEnum");
|
||||
final SavingsAccountSubStatusEnumData subStatus = SavingsEnumerations.subStatus(subStatusEnum);
|
||||
final LocalDate lastActiveTransactionDate = JdbcSupport.getLocalDate(rs, "lastActiveTransactionDate");
|
||||
final boolean isDormancyTrackingActive = rs.getBoolean("isDormancyTrackingActive");
|
||||
final Integer numDaysToInactive = JdbcSupport.getInteger(rs, "daysToInactive");
|
||||
final Integer numDaysToDormancy = JdbcSupport.getInteger(rs, "daysToDormancy");
|
||||
final Integer numDaysToEscheat = JdbcSupport.getInteger(rs, "daysToEscheat");
|
||||
Integer daysToInactive = null;
|
||||
Integer daysToDormancy = null;
|
||||
Integer daysToEscheat = null;
|
||||
|
||||
LocalDate localTenantDate = DateUtils.getLocalDateOfTenant();
|
||||
if (isDormancyTrackingActive && statusEnum.equals(SavingsAccountStatusType.ACTIVE.getValue())) {
|
||||
if (subStatusEnum < SavingsAccountSubStatusEnum.ESCHEAT.getValue()) {
|
||||
daysToEscheat = Math.toIntExact(
|
||||
ChronoUnit.DAYS.between(localTenantDate, lastActiveTransactionDate.plusDays(numDaysToEscheat)));
|
||||
}
|
||||
if (subStatusEnum < SavingsAccountSubStatusEnum.DORMANT.getValue()) {
|
||||
daysToDormancy = Math.toIntExact(
|
||||
ChronoUnit.DAYS.between(localTenantDate, lastActiveTransactionDate.plusDays(numDaysToDormancy)));
|
||||
}
|
||||
if (subStatusEnum < SavingsAccountSubStatusEnum.INACTIVE.getValue()) {
|
||||
daysToInactive = Math.toIntExact(
|
||||
ChronoUnit.DAYS.between(localTenantDate, lastActiveTransactionDate.plusDays(numDaysToInactive)));
|
||||
}
|
||||
}
|
||||
final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate");
|
||||
final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate");
|
||||
final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
|
||||
final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, "activatedOnDate");
|
||||
final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate");
|
||||
final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate, null,
|
||||
null, null, null, null, null, null, withdrawnOnDate, null, null, null, approvedOnDate, null, null, null,
|
||||
activatedOnDate, null, null, null, closedOnDate, null, null, null);
|
||||
|
||||
currencyCode = rs.getString("currencyCode");
|
||||
currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
|
||||
inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
|
||||
currency = new CurrencyData(currencyCode, currencyDigits, inMultiplesOf);
|
||||
|
||||
final BigDecimal totalDeposits = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalDeposits");
|
||||
final BigDecimal totalWithdrawals = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawals");
|
||||
final BigDecimal totalWithdrawalFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithdrawalFees");
|
||||
final BigDecimal totalAnnualFees = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalAnnualFees");
|
||||
|
||||
final BigDecimal totalInterestEarned = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalInterestEarned");
|
||||
final BigDecimal totalInterestPosted = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalInterestPosted");
|
||||
final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "accountBalance");
|
||||
final BigDecimal totalFeeCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalFeeCharge");
|
||||
final BigDecimal totalPenaltyCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalPenaltyCharge");
|
||||
final BigDecimal totalOverdraftInterestDerived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs,
|
||||
"totalOverdraftInterestDerived");
|
||||
final BigDecimal totalWithholdTax = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithholdTax");
|
||||
final LocalDate interestPostedTillDate = JdbcSupport.getLocalDate(rs, "interestPostedTillDate");
|
||||
|
||||
final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
|
||||
"minBalanceForInterestCalculation");
|
||||
final BigDecimal onHoldFunds = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "onHoldFunds");
|
||||
|
||||
final BigDecimal onHoldAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "onHoldAmount");
|
||||
|
||||
BigDecimal availableBalance = accountBalance;
|
||||
if (availableBalance != null && onHoldFunds != null) {
|
||||
|
||||
availableBalance = availableBalance.subtract(onHoldFunds);
|
||||
}
|
||||
|
||||
if (availableBalance != null && onHoldAmount != null) {
|
||||
|
||||
availableBalance = availableBalance.subtract(onHoldAmount);
|
||||
}
|
||||
|
||||
BigDecimal interestNotPosted = BigDecimal.ZERO;
|
||||
LocalDate lastInterestCalculationDate = null;
|
||||
if (totalInterestEarned != null) {
|
||||
interestNotPosted = totalInterestEarned.subtract(totalInterestPosted).add(totalOverdraftInterestDerived);
|
||||
lastInterestCalculationDate = JdbcSupport.getLocalDate(rs, "lastInterestCalculationDate");
|
||||
}
|
||||
|
||||
final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals,
|
||||
totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge,
|
||||
totalPenaltyCharge, totalOverdraftInterestDerived, totalWithholdTax, interestNotPosted,
|
||||
lastInterestCalculationDate, availableBalance, interestPostedTillDate);
|
||||
summary.setPrevInterestPostedTillDate(interestPostedTillDate);
|
||||
|
||||
final boolean withHoldTax = rs.getBoolean("withHoldTax");
|
||||
final Long taxGroupId = JdbcSupport.getLongDefaultToNullIfZero(rs, "taxGroupId");
|
||||
TaxGroupData taxGroupData = null;
|
||||
if (taxGroupId != null) {
|
||||
taxGroupData = TaxGroupData.lookup(taxGroupId, null);
|
||||
}
|
||||
|
||||
final BigDecimal nominalAnnualInterestRate = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
|
||||
"nominalAnnualInterestRate");
|
||||
|
||||
final EnumOptionData interestCompoundingPeriodType = SavingsEnumerations.compoundingInterestPeriodType(
|
||||
SavingsCompoundingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, "interestCompoundingPeriodType")));
|
||||
|
||||
final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType(
|
||||
SavingsPostingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, "interestPostingPeriodType")));
|
||||
|
||||
final EnumOptionData interestCalculationType = SavingsEnumerations.interestCalculationType(
|
||||
SavingsInterestCalculationType.fromInt(JdbcSupport.getInteger(rs, "interestCalculationType")));
|
||||
|
||||
final EnumOptionData interestCalculationDaysInYearType = SavingsEnumerations
|
||||
.interestCalculationDaysInYearType(SavingsInterestCalculationDaysInYearType
|
||||
.fromInt(JdbcSupport.getInteger(rs, "interestCalculationDaysInYearType")));
|
||||
|
||||
final BigDecimal minRequiredOpeningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
|
||||
"minRequiredOpeningBalance");
|
||||
|
||||
final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, "lockinPeriodFrequency");
|
||||
EnumOptionData lockinPeriodFrequencyType = null;
|
||||
final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType");
|
||||
if (lockinPeriodFrequencyTypeValue != null) {
|
||||
final SavingsPeriodFrequencyType lockinPeriodType = SavingsPeriodFrequencyType
|
||||
.fromInt(lockinPeriodFrequencyTypeValue);
|
||||
lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType);
|
||||
}
|
||||
|
||||
final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers");
|
||||
|
||||
final boolean allowOverdraft = rs.getBoolean("allowOverdraft");
|
||||
final BigDecimal overdraftLimit = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "overdraftLimit");
|
||||
final BigDecimal nominalAnnualInterestRateOverdraft = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
|
||||
"nominalAnnualInterestRateOverdraft");
|
||||
final BigDecimal minOverdraftForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
|
||||
"minOverdraftForInterestCalculation");
|
||||
|
||||
final BigDecimal minRequiredBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredBalance");
|
||||
final boolean enforceMinRequiredBalance = rs.getBoolean("enforceMinRequiredBalance");
|
||||
savingsAccountData = SavingsAccountData.instance(id, accountNo, depositType, externalId, null, null, null, null,
|
||||
productId, null, null, null, status, subStatus, timeline, currency, nominalAnnualInterestRate,
|
||||
interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType,
|
||||
interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType,
|
||||
withdrawalFeeForTransfers, summary, allowOverdraft, overdraftLimit, minRequiredBalance,
|
||||
enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft,
|
||||
minOverdraftForInterestCalculation, withHoldTax, taxGroupData, lastActiveTransactionDate,
|
||||
isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, onHoldAmount);
|
||||
|
||||
savingsAccountData.setClientData(clientData);
|
||||
savingsAccountData.setGroupGeneralData(groupGeneralData);
|
||||
savingsAccountData.setSavingsProduct(savingsProductData);
|
||||
savingsAccountData.setGlAccountIdForInterestOnSavings(glAccountIdForInterestOnSavings);
|
||||
savingsAccountData.setGlAccountIdForSavingsControl(glAccountIdForSavingsControl);
|
||||
}
|
||||
|
||||
if (!transMap.containsValue(transactionId)) {
|
||||
|
||||
final int transactionTypeInt = JdbcSupport.getInteger(rs, "transactionType");
|
||||
final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations.transactionType(transactionTypeInt);
|
||||
|
||||
final LocalDate date = JdbcSupport.getLocalDate(rs, "transactionDate");
|
||||
final LocalDate balanceEndDate = JdbcSupport.getLocalDate(rs, "balanceEndDate");
|
||||
final LocalDate transSubmittedOnDate = JdbcSupport.getLocalDate(rs, "createdDate");
|
||||
final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transactionAmount");
|
||||
final BigDecimal overdraftAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "overdraftAmount");
|
||||
final BigDecimal outstandingChargeAmount = null;
|
||||
final BigDecimal runningBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "runningBalance");
|
||||
final boolean reversed = rs.getBoolean("reversed");
|
||||
final boolean isManualTransaction = rs.getBoolean("manualTransaction");
|
||||
final Long officeId = rs.getLong("officeId");
|
||||
final BigDecimal cumulativeBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "cumulativeBalance");
|
||||
|
||||
final boolean postInterestAsOn = false;
|
||||
|
||||
PaymentDetailData paymentDetailData = null;
|
||||
if (transactionType.isDepositOrWithdrawal()) {
|
||||
final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentType");
|
||||
if (paymentTypeId != null) {
|
||||
final String typeName = rs.getString("paymentTypeName");
|
||||
final PaymentTypeData paymentTypeData = new PaymentTypeData(paymentTypeId, typeName, null, false, null);
|
||||
paymentDetailData = new PaymentDetailData(id, paymentTypeData, null, null, null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
savingsAccountTransactionData = SavingsAccountTransactionData.create(transactionId, transactionType, paymentDetailData,
|
||||
id, accountNo, date, currency, amount, outstandingChargeAmount, runningBalance, reversed, transSubmittedOnDate,
|
||||
postInterestAsOn, cumulativeBalance, balanceEndDate);
|
||||
savingsAccountTransactionData.setOverdraftAmount(overdraftAmount);
|
||||
|
||||
transMap.put("id", transactionId);
|
||||
if (savingsAccountData.getOfficeId() == null) {
|
||||
savingsAccountData.setOfficeId(officeId);
|
||||
}
|
||||
|
||||
savingsAccountData.setSavingsAccountTransactionData(savingsAccountTransactionData);
|
||||
}
|
||||
|
||||
if (chargeId != null && !chargeDetails.containsValue(chargeId)) {
|
||||
final boolean isPenalty = rs.getBoolean("isPenaltyCharge");
|
||||
final BigDecimal chargeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "chargeAmount");
|
||||
final Integer chargesTimeType = rs.getInt("chargeTimeType");
|
||||
final EnumOptionData enumOptionDataForChargesTimeType = new EnumOptionData(chargesTimeType.longValue(), null, null);
|
||||
final SavingsAccountChargeData savingsAccountChargeData = new SavingsAccountChargeData(chargeId, chargeAmount,
|
||||
enumOptionDataForChargesTimeType, isPenalty);
|
||||
|
||||
final Long chargesPaidById = rs.getLong("chargesPaidById");
|
||||
final BigDecimal chargesPaid = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "paidByAmount");
|
||||
final SavingsAccountChargesPaidByData savingsAccountChargesPaidByData = new SavingsAccountChargesPaidByData(
|
||||
chargesPaidById, chargesPaid);
|
||||
savingsAccountChargesPaidByData.setSavingsAccountChargeData(savingsAccountChargeData);
|
||||
if (savingsAccountChargesPaidByData != null) {
|
||||
savingsAccountTransactionData.setChargesPaidByData(savingsAccountChargesPaidByData);
|
||||
}
|
||||
|
||||
chargeDetails.put("id", chargeId);
|
||||
}
|
||||
|
||||
if (taxDetailId != null && !taxDetails.containsValue(taxDetailId)) {
|
||||
final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "taxAmount");
|
||||
final BigDecimal percentage = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "taxPercentage");
|
||||
final Long debitId = rs.getLong("debitAccountId");
|
||||
final Long creditId = rs.getLong("creditAccountId");
|
||||
final GLAccountData debitAccount = GLAccountData.createFrom(debitId);
|
||||
final GLAccountData creditAccount = GLAccountData.createFrom(creditId);
|
||||
|
||||
if (taxComponentId != null) {
|
||||
final TaxComponentData taxComponent = TaxComponentData.createTaxComponent(taxComponentId, percentage, debitAccount,
|
||||
creditAccount);
|
||||
savingsAccountTransactionData.setTaxDetails(new TaxDetailsData(taxComponent, amount));
|
||||
}
|
||||
|
||||
taxDetails.put("id", taxDetailId);
|
||||
}
|
||||
|
||||
}
|
||||
if (savingsAccountData != null) {
|
||||
savingsAccountDataList.add(savingsAccountData);
|
||||
}
|
||||
return savingsAccountDataList;
|
||||
|
||||
// final String productName = rs.getString("productName");
|
||||
|
||||
// final String fieldOfficerName = rs.getString("fieldOfficerName");
|
||||
|
||||
// final String submittedByUsername = rs.getString("submittedByUsername");
|
||||
// final String submittedByFirstname = rs.getString("submittedByFirstname");
|
||||
// final String submittedByLastname = rs.getString("submittedByLastname");
|
||||
//
|
||||
|
||||
// final String rejectedByUsername = rs.getString("rejectedByUsername");
|
||||
// final String rejectedByFirstname = rs.getString("rejectedByFirstname");
|
||||
// final String rejectedByLastname = rs.getString("rejectedByLastname");
|
||||
//
|
||||
|
||||
// final String withdrawnByUsername = rs.getString("withdrawnByUsername");
|
||||
// final String withdrawnByFirstname = rs.getString("withdrawnByFirstname");
|
||||
// final String withdrawnByLastname = rs.getString("withdrawnByLastname");
|
||||
|
||||
// final String approvedByUsername = rs.getString("approvedByUsername");
|
||||
// final String approvedByFirstname = rs.getString("approvedByFirstname");
|
||||
// final String approvedByLastname = rs.getString("approvedByLastname");
|
||||
|
||||
// final String activatedByUsername = rs.getString("activatedByUsername");
|
||||
// final String activatedByFirstname = rs.getString("activatedByFirstname");
|
||||
// final String activatedByLastname = rs.getString("activatedByLastname");
|
||||
|
||||
// final String closedByUsername = rs.getString("closedByUsername");
|
||||
// final String closedByFirstname = rs.getString("closedByFirstname");
|
||||
// final String closedByLastname = rs.getString("closedByLastname");
|
||||
|
||||
// final String currencyName = rs.getString("currencyName");
|
||||
// final String currencyNameCode = rs.getString("currencyNameCode");
|
||||
// final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
||||
|
||||
/*
|
||||
* final BigDecimal withdrawalFeeAmount = rs.getBigDecimal("withdrawalFeeAmount");
|
||||
*
|
||||
* EnumOptionData withdrawalFeeType = null; final Integer withdrawalFeeTypeValue =
|
||||
* JdbcSupport.getInteger(rs, "withdrawalFeeTypeEnum"); if (withdrawalFeeTypeValue != null) {
|
||||
* withdrawalFeeType = SavingsEnumerations.withdrawalFeeType(withdrawalFeeTypeValue); }
|
||||
*/
|
||||
|
||||
/*
|
||||
* final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount");
|
||||
*
|
||||
* MonthDay annualFeeOnMonthDay = null; final Integer annualFeeOnMonth = JdbcSupport.getInteger(rs,
|
||||
* "annualFeeOnMonth"); final Integer annualFeeOnDay = JdbcSupport.getInteger(rs, "annualFeeOnDay"); if
|
||||
* (annualFeeAmount != null && annualFeeOnDay != null) { annualFeeOnMonthDay = new
|
||||
* MonthDay(annualFeeOnMonth, annualFeeOnDay); }
|
||||
*
|
||||
* final LocalDate annualFeeNextDueDate = JdbcSupport.getLocalDate(rs, "annualFeeNextDueDate");
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SavingAccountMapper implements RowMapper<SavingsAccountData> {
|
||||
|
||||
private final String schemaSql;
|
||||
@@ -285,7 +790,6 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
// withdrawalFeeAmount,");
|
||||
// sqlBuilder.append("sa.withdrawal_fee_type_enum as
|
||||
// withdrawalFeeTypeEnum, ");
|
||||
sqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, ");
|
||||
sqlBuilder.append("sa.allow_overdraft as allowOverdraft, ");
|
||||
sqlBuilder.append("sa.overdraft_limit as overdraftLimit, ");
|
||||
sqlBuilder.append("sa.nominal_annual_interest_rate_overdraft as nominalAnnualInterestRateOverdraft, ");
|
||||
@@ -315,6 +819,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
sqlBuilder.append("sa.last_interest_calculation_date as lastInterestCalculationDate, ");
|
||||
sqlBuilder.append("sa.interest_posted_till_date as interestPostedTillDate, ");
|
||||
sqlBuilder.append("sa.total_savings_amount_on_hold as onHoldAmount, ");
|
||||
sqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, ");
|
||||
sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName, ");
|
||||
sqlBuilder.append("(select IFNULL(max(sat.transaction_date),sa.activatedon_date) ");
|
||||
sqlBuilder.append("from m_savings_account_transaction as sat ");
|
||||
@@ -337,7 +842,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
sqlBuilder.append("left join m_appuser abu on abu.id = sa.approvedon_userid ");
|
||||
sqlBuilder.append("left join m_appuser avbu on avbu.id = sa.activatedon_userid ");
|
||||
sqlBuilder.append("left join m_appuser cbu on cbu.id = sa.closedon_userid ");
|
||||
sqlBuilder.append("left join m_tax_group tg on tg.id = sa.tax_group_id ");
|
||||
sqlBuilder.append("left join m_tax_group tg on tg.id = sa.tax_group_id ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
@@ -357,6 +862,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
|
||||
final Long groupId = JdbcSupport.getLong(rs, "groupId");
|
||||
final String groupName = rs.getString("groupName");
|
||||
|
||||
final Long clientId = JdbcSupport.getLong(rs, "clientId");
|
||||
final String clientName = rs.getString("clientName");
|
||||
|
||||
@@ -765,6 +1271,33 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
new Object[] { savingsId, depositAccountType.getValue(), transactionId });
|
||||
}
|
||||
|
||||
private static final class SavingsAccountTransactionsForBatchMapper implements RowMapper<SavingsAccountTransactionData> {
|
||||
|
||||
private final String schemaSql;
|
||||
|
||||
SavingsAccountTransactionsForBatchMapper() {
|
||||
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("tr.id as transactionId, tr.ref_no as refNo ");
|
||||
sqlBuilder.append("from m_savings_account_transaction tr");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
return this.schemaSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SavingsAccountTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
final Long id = rs.getLong("transactionId");
|
||||
SavingsAccountTransactionData savingsAccountTransactionData = SavingsAccountTransactionData.create(id);
|
||||
final String refNo = rs.getString("refNo");
|
||||
savingsAccountTransactionData.setRefNo(refNo);
|
||||
return savingsAccountTransactionData;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @Override public Collection<SavingsAccountAnnualFeeData> retrieveAccountsWithAnnualFeeDue() { final String sql =
|
||||
* "select " + this.annualFeeMapper.schema() +
|
||||
|
||||
+7
@@ -25,6 +25,7 @@ import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.organisation.staff.domain.Staff;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
|
||||
|
||||
@@ -87,6 +88,12 @@ public interface SavingsAccountWritePlatformService {
|
||||
|
||||
void postInterest(SavingsAccount account, boolean postInterestAs, LocalDate transactionDate, boolean backdatedTxnsAllowedTill);
|
||||
|
||||
// SavingsAccountData postInterest(SavingsAccountData account, boolean postInterestAs, LocalDate transactionDate,
|
||||
// boolean backdatedTxnsAllowedTill);
|
||||
|
||||
SavingsAccountData postInterest(SavingsAccountData account, boolean postInterestAs, LocalDate transactionDate,
|
||||
boolean backdatedTxnsAllowedTill);
|
||||
|
||||
CommandProcessingResult blockAccount(Long savingsId);
|
||||
|
||||
CommandProcessingResult unblockAccount(Long savingsId);
|
||||
|
||||
+70
-2
@@ -55,6 +55,7 @@ import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRu
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
|
||||
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
|
||||
import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService;
|
||||
@@ -93,8 +94,10 @@ import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
|
||||
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
|
||||
import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeDataValidator;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountDataValidator;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDTO;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDataValidator;
|
||||
import org.apache.fineract.portfolio.savings.domain.DepositAccountOnHoldTransaction;
|
||||
import org.apache.fineract.portfolio.savings.domain.DepositAccountOnHoldTransactionRepository;
|
||||
@@ -123,6 +126,7 @@ import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -156,6 +160,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
private final StandingInstructionRepository standingInstructionRepository;
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final GSIMRepositoy gsimRepository;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final SavingsAccountInterestPostingService savingsAccountInterestPostingService;
|
||||
|
||||
@Autowired
|
||||
public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
|
||||
@@ -177,7 +183,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository,
|
||||
final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService,
|
||||
final AppUserRepositoryWrapper appuserRepository, final StandingInstructionRepository standingInstructionRepository,
|
||||
final BusinessEventNotifierService businessEventNotifierService, final GSIMRepositoy gsimRepository) {
|
||||
final BusinessEventNotifierService businessEventNotifierService, final GSIMRepositoy gsimRepository,
|
||||
final RoutingDataSource dataSource, final SavingsAccountInterestPostingService savingsAccountInterestPostingService) {
|
||||
this.context = context;
|
||||
this.savingAccountRepositoryWrapper = savingAccountRepositoryWrapper;
|
||||
this.savingsAccountTransactionRepository = savingsAccountTransactionRepository;
|
||||
@@ -204,6 +211,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
this.standingInstructionRepository = standingInstructionRepository;
|
||||
this.businessEventNotifierService = businessEventNotifierService;
|
||||
this.gsimRepository = gsimRepository;
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
this.savingsAccountInterestPostingService = savingsAccountInterestPostingService;
|
||||
}
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SavingsAccountWritePlatformServiceJpaRepositoryImpl.class);
|
||||
@@ -582,6 +591,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
|
||||
.isSavingsInterestPostingAtCurrentPeriodEnd();
|
||||
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
|
||||
|
||||
if (account.getNominalAnnualInterestRate().compareTo(BigDecimal.ZERO) > 0
|
||||
|| (account.allowOverdraft() && account.getNominalAnnualInterestRateOverdraft().compareTo(BigDecimal.ZERO) > 0)) {
|
||||
final Set<Long> existingTransactionIds = new HashSet<>();
|
||||
@@ -623,6 +633,52 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public SavingsAccountData postInterest(SavingsAccountData savingsAccountData, final boolean postInterestAs,
|
||||
final LocalDate transactionDate, final boolean backdatedTxnsAllowedTill) {
|
||||
|
||||
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
|
||||
.isSavingsInterestPostingAtCurrentPeriodEnd();
|
||||
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
|
||||
|
||||
if (savingsAccountData.getNominalAnnualInterestRate().compareTo(BigDecimal.ZERO) > 0 || (savingsAccountData.isAllowOverdraft()
|
||||
&& savingsAccountData.getNominalAnnualInterestRateOverdraft().compareTo(BigDecimal.ZERO) > 0)) {
|
||||
final Set<Long> existingTransactionIds = new HashSet<>();
|
||||
final Set<Long> existingReversedTransactionIds = new HashSet<>();
|
||||
updateExistingTransactionsDetails(savingsAccountData, existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
final LocalDate today = DateUtils.getLocalDateOfTenant();
|
||||
final MathContext mc = new MathContext(10, MoneyHelper.getRoundingMode());
|
||||
boolean isInterestTransfer = false;
|
||||
LocalDate postInterestOnDate = null;
|
||||
if (postInterestAs) {
|
||||
postInterestOnDate = transactionDate;
|
||||
}
|
||||
|
||||
long startPosting = System.currentTimeMillis();
|
||||
LOG.info("Interest Posting Start Here at {}", startPosting);
|
||||
|
||||
savingsAccountData = this.savingsAccountInterestPostingService.postInterest(mc, today, isInterestTransfer,
|
||||
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill,
|
||||
savingsAccountData);
|
||||
long endPosting = System.currentTimeMillis();
|
||||
LOG.info("Interest Posting Ends within {}", endPosting - startPosting);
|
||||
|
||||
if (!backdatedTxnsAllowedTill) {
|
||||
List<SavingsAccountTransactionData> transactions = savingsAccountData.getTransactions();
|
||||
for (SavingsAccountTransactionData accountTransaction : transactions) {
|
||||
if (accountTransaction.getId() == null) {
|
||||
savingsAccountData.setNewSavingsAccountTransactionData(accountTransaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
savingsAccountData.setExistingTransactionIds(existingTransactionIds);
|
||||
savingsAccountData.setExistingReversedTransactionIds(existingReversedTransactionIds);
|
||||
}
|
||||
return savingsAccountData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult undoTransaction(final Long savingsId, final Long transactionId,
|
||||
final boolean allowAccountTransferModification) {
|
||||
@@ -630,7 +686,6 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
|
||||
.isSavingsInterestPostingAtCurrentPeriodEnd();
|
||||
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
|
||||
|
||||
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
|
||||
final Set<Long> existingTransactionIds = new HashSet<>();
|
||||
final Set<Long> existingReversedTransactionIds = new HashSet<>();
|
||||
@@ -1394,12 +1449,25 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds());
|
||||
}
|
||||
|
||||
private void updateExistingTransactionsDetails(SavingsAccountData account, Set<Long> existingTransactionIds,
|
||||
Set<Long> existingReversedTransactionIds) {
|
||||
existingTransactionIds.addAll(account.findCurrentTransactionIdsWithPivotDateConfig());
|
||||
existingReversedTransactionIds.addAll(account.findCurrentReversedTransactionIdsWithPivotDateConfig());
|
||||
}
|
||||
|
||||
private void updateSavingsTransactionsDetails(SavingsAccount account, Set<Long> existingTransactionIds,
|
||||
Set<Long> existingReversedTransactionIds) {
|
||||
existingTransactionIds.addAll(account.findCurrentTransactionIdsWithPivotDateConfig());
|
||||
existingReversedTransactionIds.addAll(account.findCurrentReversedTransactionIdsWithPivotDateConfig());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void updateSavingsTransactionsDetails(SavingsAccountData account, Set<Long> existingTransactionIds,
|
||||
Set<Long> existingReversedTransactionIds) {
|
||||
existingTransactionIds.addAll(account.findCurrentTransactionIdsWithPivotDateConfig());
|
||||
existingReversedTransactionIds.addAll(account.findCurrentReversedTransactionIdsWithPivotDateConfig());
|
||||
}
|
||||
|
||||
private void postJournalEntries(final SavingsAccount savingsAccount, final Set<Long> existingTransactionIds,
|
||||
final Set<Long> existingReversedTransactionIds, final boolean backdatedTxnsAllowedTill) {
|
||||
|
||||
|
||||
+249
-18
@@ -19,25 +19,40 @@
|
||||
package org.apache.fineract.portfolio.savings.service;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import java.math.BigDecimal;
|
||||
import java.security.SecureRandom;
|
||||
import java.sql.Date;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
|
||||
import org.apache.fineract.batch.command.CommandStrategyProvider;
|
||||
import org.apache.fineract.batch.service.ResolutionHelper;
|
||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountSummaryData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.dao.CannotAcquireLockException;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.orm.ObjectOptimisticLockingFailureException;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
/**
|
||||
* @author manoj
|
||||
@@ -49,22 +64,38 @@ public class SavingsSchedularInterestPoster implements Callable<Void> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SavingsSchedularInterestPoster.class);
|
||||
private static final SecureRandom random = new SecureRandom();
|
||||
private static final String SAVINGS_TRANSACTION_IDENTIFIER = "S";
|
||||
|
||||
private Collection<Long> savingsIds;
|
||||
private Collection<SavingsAccountData> savingAccounts;
|
||||
private SavingsAccountWritePlatformService savingsAccountWritePlatformService;
|
||||
private SavingsAccountRepositoryWrapper savingsAccountRepository;
|
||||
private SavingsAccountAssembler savingAccountAssembler;
|
||||
private FineractPlatformTenant tenant;
|
||||
private ConfigurationDomainService configurationDomainService;
|
||||
private boolean backdatedTxnsAllowedTill;
|
||||
private List<SavingsAccountData> savingsAccountDataList = new ArrayList<>();
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
private TransactionTemplate transactionTemplate;
|
||||
private CommandStrategyProvider strategyProvider;
|
||||
private ResolutionHelper resolutionHelper;
|
||||
private SavingsAccountReadPlatformService savingsAccountReadPlatformService;
|
||||
|
||||
public void setSavingsIds(Collection<Long> savingsIds) {
|
||||
this.savingsIds = savingsIds;
|
||||
public void setSavings(Collection<SavingsAccountData> savingAccounts) {
|
||||
this.savingAccounts = savingAccounts;
|
||||
}
|
||||
|
||||
public void setBackdatedTxnsAllowedTill(final boolean backdatedTxnsAllowedTill) {
|
||||
this.backdatedTxnsAllowedTill = backdatedTxnsAllowedTill;
|
||||
}
|
||||
|
||||
public void setSavingsAccountWritePlatformService(SavingsAccountWritePlatformService savingsAccountWritePlatformService) {
|
||||
this.savingsAccountWritePlatformService = savingsAccountWritePlatformService;
|
||||
}
|
||||
|
||||
public void setSavingsAccountReadPlatformService(SavingsAccountReadPlatformService savingsAccountReadPlatformService) {
|
||||
this.savingsAccountReadPlatformService = savingsAccountReadPlatformService;
|
||||
}
|
||||
|
||||
public void setSavingsAccountRepository(SavingsAccountRepositoryWrapper savingsAccountRepository) {
|
||||
this.savingsAccountRepository = savingsAccountRepository;
|
||||
}
|
||||
@@ -77,6 +108,22 @@ public class SavingsSchedularInterestPoster implements Callable<Void> {
|
||||
this.tenant = tenant;
|
||||
}
|
||||
|
||||
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
|
||||
this.transactionTemplate = transactionTemplate;
|
||||
}
|
||||
|
||||
public void setResolutionHelper(ResolutionHelper resolutionHelper) {
|
||||
this.resolutionHelper = resolutionHelper;
|
||||
}
|
||||
|
||||
public void setStrategyProvider(CommandStrategyProvider commandStrategyProvider) {
|
||||
this.strategyProvider = commandStrategyProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressFBWarnings(value = {
|
||||
"DMI_RANDOM_USED_ONLY_ONCE" }, justification = "False positive for random object created and used only once")
|
||||
@@ -84,31 +131,38 @@ public class SavingsSchedularInterestPoster implements Callable<Void> {
|
||||
ThreadLocalContextUtil.setTenant(tenant);
|
||||
Integer maxNumberOfRetries = tenant.getConnection().getMaxRetriesOnDeadlock();
|
||||
Integer maxIntervalBetweenRetries = tenant.getConnection().getMaxIntervalBetweenRetries();
|
||||
final boolean backdatedTxnsAllowedTill = this.configurationDomainService.retrievePivotDateConfig();
|
||||
|
||||
// List<BatchResponse> responseList = new ArrayList<>();
|
||||
long start = System.currentTimeMillis();
|
||||
LOG.info("Thread Execution Started at {}", start);
|
||||
|
||||
List<Throwable> errors = new ArrayList<>();
|
||||
int i = 0;
|
||||
if (!savingsIds.isEmpty()) {
|
||||
List<Throwable> errors = new ArrayList<>();
|
||||
for (Long savingsId : savingsIds) {
|
||||
LOG.info("Savings ID {}", savingsId);
|
||||
if (!savingAccounts.isEmpty()) {
|
||||
for (SavingsAccountData savingsAccountData : savingAccounts) {
|
||||
Integer numberOfRetries = 0;
|
||||
while (numberOfRetries <= maxNumberOfRetries) {
|
||||
try {
|
||||
SavingsAccount savingsAccount = this.savingsAccountRepository.findOneWithNotFoundDetection(savingsId);
|
||||
this.savingAccountAssembler.assignSavingAccountHelpers(savingsAccount);
|
||||
boolean postInterestAsOn = false;
|
||||
LocalDate transactionDate = null;
|
||||
this.savingsAccountWritePlatformService.postInterest(savingsAccount, postInterestAsOn, transactionDate,
|
||||
backdatedTxnsAllowedTill);
|
||||
long startPosting = System.currentTimeMillis();
|
||||
SavingsAccountData savingsAccountDataRet = savingsAccountWritePlatformService.postInterest(savingsAccountData,
|
||||
postInterestAsOn, transactionDate, backdatedTxnsAllowedTill);
|
||||
long endPosting = System.currentTimeMillis();
|
||||
savingsAccountDataList.add(savingsAccountDataRet);
|
||||
|
||||
LOG.info("Posting Completed Within {}", endPosting - startPosting);
|
||||
|
||||
numberOfRetries = maxNumberOfRetries + 1;
|
||||
} catch (CannotAcquireLockException | ObjectOptimisticLockingFailureException exception) {
|
||||
LOG.info("Interest posting job for savings ID {} has been retried {} time(s)", savingsId, numberOfRetries);
|
||||
LOG.info("Interest posting job for savings ID {} has been retried {} time(s)", savingsAccountData.getId(),
|
||||
numberOfRetries);
|
||||
// Fail if the transaction has been retired for
|
||||
// maxNumberOfRetries
|
||||
if (numberOfRetries >= maxNumberOfRetries) {
|
||||
LOG.error(
|
||||
"Interest posting job for savings ID {} has been retried for the max allowed attempts of {} and will be rolled back",
|
||||
savingsId, numberOfRetries);
|
||||
savingsAccountData.getId(), numberOfRetries);
|
||||
errors.add(exception);
|
||||
break;
|
||||
}
|
||||
@@ -124,18 +178,195 @@ public class SavingsSchedularInterestPoster implements Callable<Void> {
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Interest posting job for savings failed for account {}", savingsId, e);
|
||||
LOG.error("Interest posting job for savings failed for account {}", savingsAccountData.getId(), e);
|
||||
numberOfRetries = maxNumberOfRetries + 1;
|
||||
errors.add(e);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
LOG.info("Savings count {}", i);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (errors.isEmpty()) {
|
||||
try {
|
||||
batchUpdate(savingsAccountDataList);
|
||||
} catch (DataAccessException exception) {
|
||||
LOG.error("Batch update failed due to DataAccessException", exception);
|
||||
errors.add(exception);
|
||||
} catch (NullPointerException exception) {
|
||||
LOG.error("Batch update failed due to NullPointerException", exception);
|
||||
errors.add(exception);
|
||||
}
|
||||
}
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
throw new JobExecutionException(errors);
|
||||
}
|
||||
}
|
||||
|
||||
long end = System.currentTimeMillis();
|
||||
LOG.info("Time To Finish the batch {} by thread {} for accounts {}", end - start, Thread.currentThread().getId(),
|
||||
savingAccounts.size());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED, rollbackFor = Exception.class)
|
||||
private void batchUpdateJournalEntries(final List<SavingsAccountData> savingsAccountDataList,
|
||||
final HashMap<String, SavingsAccountTransactionData> savingsAccountTransactionDataHashMap)
|
||||
throws DataAccessException, NullPointerException {
|
||||
String queryForJGLUpdate = batchQueryForJournalEntries();
|
||||
List<Object[]> paramsForGLInsertion = new ArrayList<>();
|
||||
for (SavingsAccountData savingsAccountData : savingsAccountDataList) {
|
||||
String currencyCode = savingsAccountData.getCurrency().getCode();
|
||||
|
||||
List<SavingsAccountTransactionData> savingsAccountTransactionDataList = savingsAccountData.getSavingsAccountTransactionData();
|
||||
for (SavingsAccountTransactionData savingsAccountTransactionData : savingsAccountTransactionDataList) {
|
||||
if (savingsAccountTransactionData.getId() == null) {
|
||||
final String key = savingsAccountTransactionData.getRefNo();
|
||||
if (savingsAccountTransactionDataHashMap.containsKey(key)) {
|
||||
LocalDate currentDate = DateUtils.getLocalDateOfTenant();
|
||||
final SavingsAccountTransactionData dataFromFetch = savingsAccountTransactionDataHashMap.get(key);
|
||||
savingsAccountTransactionData.setId(dataFromFetch.getId());
|
||||
|
||||
paramsForGLInsertion.add(
|
||||
new Object[] { savingsAccountData.getGlAccountIdForSavingsControl(), savingsAccountData.getOfficeId(), null,
|
||||
currencyCode, SAVINGS_TRANSACTION_IDENTIFIER + savingsAccountTransactionData.getId().toString(),
|
||||
savingsAccountTransactionData.getId(), null, false, null, false,
|
||||
Date.from(savingsAccountTransactionData.getTransactionDate()
|
||||
.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()),
|
||||
JournalEntryType.CREDIT.getValue().longValue(), savingsAccountTransactionData.getAmount(), null,
|
||||
JournalEntryType.CREDIT.getValue().longValue(), savingsAccountData.getId(),
|
||||
Date.from(currentDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()),
|
||||
Date.from(currentDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()), false,
|
||||
BigDecimal.ZERO, BigDecimal.ZERO, null,
|
||||
Date.from(savingsAccountTransactionData.getTransactionDate()
|
||||
.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()),
|
||||
null, Integer.valueOf(1), Integer.valueOf(1) });
|
||||
|
||||
paramsForGLInsertion.add(new Object[] { savingsAccountData.getGlAccountIdForInterestOnSavings(),
|
||||
savingsAccountData.getOfficeId(), null, currencyCode,
|
||||
SAVINGS_TRANSACTION_IDENTIFIER + savingsAccountTransactionData.getId().toString(),
|
||||
savingsAccountTransactionData.getId(), null, false, null, false,
|
||||
Date.from(savingsAccountTransactionData.getTransactionDate()
|
||||
.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()),
|
||||
JournalEntryType.DEBIT.getValue().longValue(), savingsAccountTransactionData.getAmount(), null,
|
||||
JournalEntryType.DEBIT.getValue().longValue(), savingsAccountData.getId(),
|
||||
Date.from(currentDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()),
|
||||
Date.from(currentDate.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()), false,
|
||||
BigDecimal.ZERO, BigDecimal.ZERO, null,
|
||||
Date.from(savingsAccountTransactionData.getTransactionDate()
|
||||
.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()),
|
||||
null, Integer.valueOf(1), Integer.valueOf(1) });
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (paramsForGLInsertion != null && paramsForGLInsertion.size() > 0) {
|
||||
this.jdbcTemplate.batchUpdate(queryForJGLUpdate, paramsForGLInsertion);
|
||||
}
|
||||
}
|
||||
|
||||
private String batchQueryForJournalEntries() {
|
||||
StringBuilder query = new StringBuilder(100);
|
||||
|
||||
query.append("INSERT INTO acc_gl_journal_entry(account_id,office_id,reversal_id,currency_code,transaction_id,");
|
||||
query.append("savings_transaction_id,client_transaction_id,reversed,ref_num,manual_entry,entry_date,type_enum,");
|
||||
query.append("amount,description,entity_type_enum,entity_id,created_date,");
|
||||
query.append("lastmodified_date,is_running_balance_calculated,office_running_balance,organization_running_balance,");
|
||||
query.append("payment_details_id,transaction_date,share_transaction_id, createdby_id, lastmodifiedby_id) ");
|
||||
query.append("VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
|
||||
return query.toString();
|
||||
}
|
||||
|
||||
private List<SavingsAccountTransactionData> fetchTransactionsFromIds(final List<String> refNo) throws DataAccessException {
|
||||
return this.savingsAccountReadPlatformService.retrieveAllTransactionData(refNo);
|
||||
}
|
||||
|
||||
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
|
||||
@SuppressWarnings("unused")
|
||||
private void batchUpdate(final List<SavingsAccountData> savingsAccountDataList) throws DataAccessException {
|
||||
String queryForSavingsUpdate = batchQueryForSavingsSummaryUpdate();
|
||||
String queryForTransactionInsertion = batchQueryForTransactionInsertion();
|
||||
List<Object[]> paramsForTransactionInsertion = new ArrayList<>();
|
||||
List<Object[]> paramsForSavingsSummary = new ArrayList<>();
|
||||
List<String> transRefNo = new ArrayList<>();
|
||||
for (SavingsAccountData savingsAccountData : savingsAccountDataList) {
|
||||
SavingsAccountSummaryData savingsAccountSummaryData = savingsAccountData.getSummary();
|
||||
paramsForSavingsSummary.add(new Object[] { savingsAccountSummaryData.getTotalDeposits(),
|
||||
savingsAccountSummaryData.getTotalWithdrawals(), savingsAccountSummaryData.getTotalInterestEarned(),
|
||||
savingsAccountSummaryData.getTotalInterestPosted(), savingsAccountSummaryData.getTotalWithdrawalFees(),
|
||||
savingsAccountSummaryData.getTotalFeeCharge(), savingsAccountSummaryData.getTotalPenaltyCharge(),
|
||||
savingsAccountSummaryData.getTotalAnnualFees(), savingsAccountSummaryData.getAvailableBalance(),
|
||||
savingsAccountSummaryData.getTotalOverdraftInterestDerived(), savingsAccountSummaryData.getTotalWithholdTax(),
|
||||
Date.from(savingsAccountSummaryData.getLastInterestCalculationDate().atStartOfDay(DateUtils.getDateTimeZoneOfTenant())
|
||||
.toInstant()),
|
||||
Date.from(savingsAccountSummaryData.getInterestPostedTillDate().atStartOfDay(DateUtils.getDateTimeZoneOfTenant())
|
||||
.toInstant()),
|
||||
savingsAccountData.getId() });
|
||||
List<SavingsAccountTransactionData> savingsAccountTransactionDataList = savingsAccountData.getSavingsAccountTransactionData();
|
||||
for (SavingsAccountTransactionData savingsAccountTransactionData : savingsAccountTransactionDataList) {
|
||||
if (savingsAccountTransactionData.getId() == null) {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
savingsAccountTransactionData.setRefNo(uuid.toString());
|
||||
transRefNo.add(uuid.toString());
|
||||
java.util.Date balanceEndDate = null;
|
||||
if (savingsAccountTransactionData.getBalanceEndDate() != null) {
|
||||
balanceEndDate = Date.from(savingsAccountTransactionData.getBalanceEndDate()
|
||||
.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant());
|
||||
}
|
||||
paramsForTransactionInsertion.add(new Object[] { savingsAccountData.getId(), savingsAccountData.getOfficeId(),
|
||||
savingsAccountTransactionData.getTransactionType().getId(),
|
||||
Date.from(savingsAccountTransactionData.getTransactionDate().atStartOfDay(DateUtils.getDateTimeZoneOfTenant())
|
||||
.toInstant()),
|
||||
savingsAccountTransactionData.getAmount(), balanceEndDate,
|
||||
savingsAccountTransactionData.getBalanceNumberOfDays(), savingsAccountTransactionData.getRunningBalance(),
|
||||
savingsAccountTransactionData.getCumulativeBalance(), savingsAccountTransactionData.getSubmittedOnDate(),
|
||||
Integer.valueOf(1), savingsAccountTransactionData.isManualTransaction(),
|
||||
savingsAccountTransactionData.getRefNo() });
|
||||
}
|
||||
}
|
||||
savingsAccountData.setUpdatedTransactions(savingsAccountTransactionDataList);
|
||||
}
|
||||
|
||||
if (transRefNo.size() > 0) {
|
||||
this.jdbcTemplate.batchUpdate(queryForSavingsUpdate, paramsForSavingsSummary);
|
||||
this.jdbcTemplate.batchUpdate(queryForTransactionInsertion, paramsForTransactionInsertion);
|
||||
|
||||
List<SavingsAccountTransactionData> savingsAccountTransactionDataList = fetchTransactionsFromIds(transRefNo);
|
||||
if (savingsAccountDataList != null) {
|
||||
LOG.info("Fetched Transactions from DB: {}", savingsAccountTransactionDataList.size());
|
||||
}
|
||||
|
||||
HashMap<String, SavingsAccountTransactionData> savingsAccountTransactionMap = new HashMap<>();
|
||||
for (SavingsAccountTransactionData savingsAccountTransactionData : savingsAccountTransactionDataList) {
|
||||
final String key = savingsAccountTransactionData.getRefNo();
|
||||
savingsAccountTransactionMap.put(key, savingsAccountTransactionData);
|
||||
}
|
||||
batchUpdateJournalEntries(savingsAccountDataList, savingsAccountTransactionMap);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String batchQueryForTransactionInsertion() {
|
||||
StringBuilder query = new StringBuilder(100);
|
||||
query.append("INSERT INTO m_savings_account_transaction (savings_account_id, office_id, is_reversed,");
|
||||
query.append("transaction_type_enum, transaction_date, amount, balance_end_date_derived,");
|
||||
query.append("balance_number_of_days_derived, running_balance_derived, cumulative_balance_derived,");
|
||||
query.append("created_date, appuser_id, is_manual, is_loan_disbursement, ref_no) VALUES ");
|
||||
query.append("(?, ?, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?)");
|
||||
return query.toString();
|
||||
|
||||
}
|
||||
|
||||
private String batchQueryForSavingsSummaryUpdate() {
|
||||
StringBuilder query = new StringBuilder(100);
|
||||
query.append("update m_savings_account set total_deposits_derived=?, total_withdrawals_derived=?, ");
|
||||
query.append("total_interest_earned_derived=?, total_interest_posted_derived=?, total_withdrawal_fees_derived=?, ");
|
||||
query.append("total_fees_charge_derived=?, total_penalty_charge_derived=?, total_annual_fees_derived=?, ");
|
||||
query.append("account_balance_derived=?, total_overdraft_interest_derived=?, total_withhold_tax_derived=?, ");
|
||||
query.append("last_interest_calculation_date=?, interest_posted_till_date=? where id=?");
|
||||
return query.toString();
|
||||
}
|
||||
}
|
||||
|
||||
+103
-22
@@ -21,28 +21,36 @@ package org.apache.fineract.portfolio.savings.service;
|
||||
import static org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType.ACTIVE;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
@Service
|
||||
public class SavingsSchedularServiceImpl implements SavingsSchedularService {
|
||||
@@ -54,17 +62,27 @@ public class SavingsSchedularServiceImpl implements SavingsSchedularService {
|
||||
private final SavingsAccountReadPlatformService savingAccountReadPlatformService;
|
||||
private final SavingsAccountRepositoryWrapper savingsAccountRepository;
|
||||
private final ApplicationContext applicationContext;
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final TransactionTemplate transactionTemplate;
|
||||
private Queue<List<SavingsAccountData>> queue = new ArrayDeque<>();
|
||||
private int queueSize = 1;
|
||||
|
||||
@Autowired
|
||||
public SavingsSchedularServiceImpl(final SavingsAccountAssembler savingAccountAssembler,
|
||||
final SavingsAccountWritePlatformService savingsAccountWritePlatformService,
|
||||
final SavingsAccountReadPlatformService savingAccountReadPlatformService,
|
||||
final SavingsAccountRepositoryWrapper savingsAccountRepository, final ApplicationContext applicationContext) {
|
||||
final SavingsAccountRepositoryWrapper savingsAccountRepository, final ApplicationContext applicationContext,
|
||||
final ConfigurationDomainService configurationDomainService, final RoutingDataSource dataSource,
|
||||
final TransactionTemplate transactionTemplate) {
|
||||
this.savingAccountAssembler = savingAccountAssembler;
|
||||
this.savingsAccountWritePlatformService = savingsAccountWritePlatformService;
|
||||
this.savingAccountReadPlatformService = savingAccountReadPlatformService;
|
||||
this.savingsAccountRepository = savingsAccountRepository;
|
||||
this.applicationContext = applicationContext;
|
||||
this.configurationDomainService = configurationDomainService;
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
this.transactionTemplate = transactionTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,28 +95,43 @@ public class SavingsSchedularServiceImpl implements SavingsSchedularService {
|
||||
Long maxSavingsIdInList = 0L;
|
||||
// initialise the executor service with fetched configurations
|
||||
final ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);
|
||||
final boolean backdatedTxnsAllowedTill = this.configurationDomainService.retrievePivotDateConfig();
|
||||
|
||||
List<Long> savingsIds = Collections.synchronizedList(
|
||||
this.savingAccountReadPlatformService.getAccountsIdsByStatusPaged(ACTIVE.getValue(), pageSize, maxSavingsIdInList));
|
||||
if (!CollectionUtils.isEmpty(savingsIds)) {
|
||||
do {
|
||||
int totalFilteredRecords = savingsIds.size();
|
||||
LOG.info("Starting Interest posting - total filtered records - {}", totalFilteredRecords);
|
||||
postInterest(savingsIds, threadPoolSize, batchSize, executorService);
|
||||
maxSavingsIdInList = savingsIds.get(savingsIds.size() - 1);
|
||||
savingsIds = Collections.synchronizedList(
|
||||
this.savingAccountReadPlatformService.getAccountsIdsByStatusPaged(ACTIVE.getValue(), pageSize, maxSavingsIdInList));
|
||||
} while (!CollectionUtils.isEmpty(savingsIds));
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
LOG.info("Reading Savings Account Data!");
|
||||
List<SavingsAccountData> savingsAccounts = this.savingAccountReadPlatformService
|
||||
.retrieveAllSavingsDataForInterestPosting(backdatedTxnsAllowedTill, pageSize, ACTIVE.getValue(), maxSavingsIdInList);
|
||||
|
||||
if (savingsAccounts != null && savingsAccounts.size() > 0) {
|
||||
savingsAccounts = Collections.synchronizedList(savingsAccounts);
|
||||
long finish = System.currentTimeMillis();
|
||||
LOG.info("Done fetching Data within {} milliseconds", finish - start);
|
||||
if (savingsAccounts != null) {
|
||||
queue.add(savingsAccounts);
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(queue)) {
|
||||
do {
|
||||
int totalFilteredRecords = savingsAccounts.size();
|
||||
LOG.info("Starting Interest posting - total records - {}", totalFilteredRecords);
|
||||
List<SavingsAccountData> queueElement = queue.element();
|
||||
maxSavingsIdInList = queueElement.get(queueElement.size() - 1).getId();
|
||||
postInterest(queue.remove(), threadPoolSize, batchSize, executorService, backdatedTxnsAllowedTill, pageSize,
|
||||
maxSavingsIdInList);
|
||||
} while (!CollectionUtils.isEmpty(queue));
|
||||
}
|
||||
// shutdown the executor when done
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
// shutdown the executor when done
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
|
||||
private void postInterest(List<Long> savingsIds, int threadPoolSize, int batchSize, ExecutorService executorService) {
|
||||
private void postInterest(List<SavingsAccountData> savingsAccounts, int threadPoolSize, int batchSize, ExecutorService executorService,
|
||||
final boolean backdatedTxnsAllowedTill, final int pageSize, Long maxSavingsIdInList) {
|
||||
List<Callable<Void>> posters = new ArrayList<>();
|
||||
int fromIndex = 0;
|
||||
// get the size of current paginated dataset
|
||||
int size = savingsIds.size();
|
||||
int size = savingsAccounts.size();
|
||||
// calculate the batch size
|
||||
batchSize = (int) Math.ceil((double) size / threadPoolSize);
|
||||
|
||||
@@ -107,21 +140,50 @@ public class SavingsSchedularServiceImpl implements SavingsSchedularService {
|
||||
}
|
||||
|
||||
int toIndex = (batchSize > size - 1) ? size : batchSize;
|
||||
while (toIndex < size && savingsIds.get(toIndex - 1).equals(savingsIds.get(toIndex))) {
|
||||
while (toIndex < size && savingsAccounts.get(toIndex - 1).getId().equals(savingsAccounts.get(toIndex).getId())) {
|
||||
toIndex++;
|
||||
}
|
||||
boolean lastBatch = false;
|
||||
int loopCount = size / batchSize + 1;
|
||||
|
||||
FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
|
||||
Long finalMaxSavingsIdInList = maxSavingsIdInList;
|
||||
|
||||
Callable<Void> fetchData = () -> {
|
||||
ThreadLocalContextUtil.setTenant(tenant);
|
||||
Long maxId = finalMaxSavingsIdInList;
|
||||
if (!queue.isEmpty()) {
|
||||
maxId = Math.max(finalMaxSavingsIdInList, queue.element().get(queue.element().size() - 1).getId());
|
||||
}
|
||||
|
||||
while (queue.size() <= queueSize) {
|
||||
LOG.info("Fetching while threads are running!");
|
||||
List<SavingsAccountData> savingsAccountDataList = Collections.synchronizedList(this.savingAccountReadPlatformService
|
||||
.retrieveAllSavingsDataForInterestPosting(backdatedTxnsAllowedTill, pageSize, ACTIVE.getValue(), maxId));
|
||||
if (savingsAccountDataList == null || savingsAccountDataList.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
maxId = savingsAccountDataList.get(savingsAccountDataList.size() - 1).getId();
|
||||
queue.add(savingsAccountDataList);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
posters.add(fetchData);
|
||||
|
||||
for (long i = 0; i < loopCount; i++) {
|
||||
List<Long> subList = safeSubList(savingsIds, fromIndex, toIndex);
|
||||
List<SavingsAccountData> subList = safeSubList(savingsAccounts, fromIndex, toIndex);
|
||||
SavingsSchedularInterestPoster poster = (SavingsSchedularInterestPoster) this.applicationContext
|
||||
.getBean("savingsSchedularInterestPoster");
|
||||
poster.setSavingsIds(subList);
|
||||
poster.setTenant(ThreadLocalContextUtil.getTenant());
|
||||
poster.setSavings(subList);
|
||||
poster.setTenant(tenant);
|
||||
poster.setSavingsAccountWritePlatformService(savingsAccountWritePlatformService);
|
||||
poster.setSavingsAccountReadPlatformService(savingAccountReadPlatformService);
|
||||
poster.setSavingsAccountRepository(savingsAccountRepository);
|
||||
poster.setSavingAccountAssembler(savingAccountAssembler);
|
||||
poster.setJdbcTemplate(jdbcTemplate);
|
||||
poster.setBackdatedTxnsAllowedTill(backdatedTxnsAllowedTill);
|
||||
poster.setTransactionTemplate(transactionTemplate);
|
||||
|
||||
posters.add(poster);
|
||||
|
||||
if (lastBatch) {
|
||||
@@ -132,13 +194,32 @@ public class SavingsSchedularServiceImpl implements SavingsSchedularService {
|
||||
}
|
||||
fromIndex = fromIndex + (toIndex - fromIndex);
|
||||
toIndex = (toIndex + batchSize > size - 1) ? size : toIndex + batchSize;
|
||||
while (toIndex < size && savingsIds.get(toIndex - 1).equals(savingsIds.get(toIndex))) {
|
||||
while (toIndex < size && savingsAccounts.get(toIndex - 1).getId().equals(savingsAccounts.get(toIndex).getId())) {
|
||||
toIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
List<Future<Void>> responses = executorService.invokeAll(posters);
|
||||
Long maxId = maxSavingsIdInList;
|
||||
if (!queue.isEmpty()) {
|
||||
maxId = Math.max(maxSavingsIdInList, queue.element().get(queue.element().size() - 1).getId());
|
||||
}
|
||||
|
||||
while (queue.size() <= queueSize) {
|
||||
LOG.info("Fetching while threads are running!..:: this is not supposed to run........");
|
||||
savingsAccounts = Collections.synchronizedList(this.savingAccountReadPlatformService
|
||||
.retrieveAllSavingsDataForInterestPosting(backdatedTxnsAllowedTill, pageSize, ACTIVE.getValue(), maxId));
|
||||
if (savingsAccounts == null || savingsAccounts.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
maxId = savingsAccounts.get(savingsAccounts.size() - 1).getId();
|
||||
LOG.info("Add to the Queue");
|
||||
queue.add(savingsAccounts);
|
||||
}
|
||||
|
||||
checkCompletion(responses);
|
||||
LOG.info("Queue size {}", queue.size());
|
||||
} catch (InterruptedException e1) {
|
||||
LOG.error("Interrupted while postInterest", e1);
|
||||
}
|
||||
|
||||
+56
@@ -109,4 +109,60 @@ public final class TaxComponentData implements Serializable {
|
||||
this.glAccountTypeOptions = glAccountTypeOptions;
|
||||
}
|
||||
|
||||
private TaxComponentData(final Long id, final BigDecimal percentage, final GLAccountData debitAcount,
|
||||
final GLAccountData creditAcount) {
|
||||
this.id = id;
|
||||
this.percentage = percentage;
|
||||
this.name = null;
|
||||
this.debitAccountType = null;
|
||||
this.debitAccount = debitAcount;
|
||||
this.creditAccountType = null;
|
||||
this.creditAccount = creditAcount;
|
||||
this.startDate = null;
|
||||
this.taxComponentHistories = null;
|
||||
this.glAccountOptions = null;
|
||||
this.glAccountTypeOptions = null;
|
||||
}
|
||||
|
||||
public static TaxComponentData createTaxComponent(final Long id, final BigDecimal percentage, final GLAccountData debitAccount,
|
||||
final GLAccountData creditAccount) {
|
||||
return new TaxComponentData(id, percentage, debitAccount, creditAccount);
|
||||
}
|
||||
|
||||
public BigDecimal getApplicablePercentage(final LocalDate date) {
|
||||
BigDecimal percentage = null;
|
||||
if (occursOnDayFrom(date)) {
|
||||
percentage = getPercentage();
|
||||
} else {
|
||||
for (TaxComponentHistoryData componentHistory : this.taxComponentHistories) {
|
||||
if (componentHistory.occursOnDayFromAndUpToAndIncluding(date)) {
|
||||
percentage = componentHistory.getPercentage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return percentage;
|
||||
}
|
||||
|
||||
public BigDecimal getPercentage() {
|
||||
return this.percentage;
|
||||
}
|
||||
|
||||
private boolean occursOnDayFrom(final LocalDate target) {
|
||||
return target != null && target.isAfter(startDate());
|
||||
}
|
||||
|
||||
public LocalDate startDate() {
|
||||
LocalDate startDate = null;
|
||||
if (this.startDate != null) {
|
||||
startDate = this.startDate;
|
||||
// startDate = LocalDate.ofInstant(this.startDate.toInstant(), DateUtils.getDateTimeZoneOfTenant());
|
||||
}
|
||||
return startDate;
|
||||
}
|
||||
|
||||
public GLAccountData getCreditAcount() {
|
||||
return this.creditAccount;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+27
@@ -36,4 +36,31 @@ public class TaxComponentHistoryData implements Serializable {
|
||||
this.startDate = startDate;
|
||||
this.endDate = endDate;
|
||||
}
|
||||
|
||||
public boolean occursOnDayFromAndUpToAndIncluding(final LocalDate target) {
|
||||
if (this.endDate == null) {
|
||||
return target != null && target.isAfter(startDate());
|
||||
}
|
||||
return target != null && target.isAfter(startDate()) && !target.isAfter(endDate());
|
||||
}
|
||||
|
||||
public LocalDate startDate() {
|
||||
LocalDate startDate = null;
|
||||
if (this.startDate != null) {
|
||||
startDate = this.startDate;
|
||||
}
|
||||
return startDate;
|
||||
}
|
||||
|
||||
public LocalDate endDate() {
|
||||
LocalDate endDate = null;
|
||||
if (this.endDate != null) {
|
||||
endDate = this.endDate;
|
||||
}
|
||||
return endDate;
|
||||
}
|
||||
|
||||
public BigDecimal getPercentage() {
|
||||
return this.percentage;
|
||||
}
|
||||
}
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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.tax.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
|
||||
public class TaxDetailsData {
|
||||
|
||||
private TaxComponentData taxComponent;
|
||||
|
||||
private BigDecimal amount;
|
||||
|
||||
protected TaxDetailsData() {}
|
||||
|
||||
public TaxDetailsData(final TaxComponentData taxComponent, final BigDecimal amount) {
|
||||
this.taxComponent = taxComponent;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public TaxComponentData getTaxComponent() {
|
||||
return this.taxComponent;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
public void updateAmount(Money amount) {
|
||||
this.amount = amount.getAmount();
|
||||
}
|
||||
|
||||
}
|
||||
+4
@@ -61,4 +61,8 @@ public final class TaxGroupData implements Serializable {
|
||||
this.taxComponents = taxComponents;
|
||||
}
|
||||
|
||||
public Collection<TaxGroupMappingsData> getTaxAssociations() {
|
||||
return this.taxAssociations;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+27
@@ -38,4 +38,31 @@ public class TaxGroupMappingsData implements Serializable {
|
||||
this.startDate = startDate;
|
||||
this.endDate = endDate;
|
||||
}
|
||||
|
||||
public TaxComponentData getTaxComponent() {
|
||||
return this.taxComponent;
|
||||
}
|
||||
|
||||
public boolean occursOnDayFromAndUpToAndIncluding(final LocalDate target) {
|
||||
if (this.endDate == null) {
|
||||
return target != null && target.isAfter(startDate());
|
||||
}
|
||||
return target != null && target.isAfter(startDate()) && !target.isAfter(endDate());
|
||||
}
|
||||
|
||||
public LocalDate startDate() {
|
||||
LocalDate startDate = null;
|
||||
if (this.startDate != null) {
|
||||
startDate = this.startDate;
|
||||
}
|
||||
return startDate;
|
||||
}
|
||||
|
||||
public LocalDate endDate() {
|
||||
LocalDate endDate = null;
|
||||
if (this.endDate != null) {
|
||||
endDate = this.endDate;
|
||||
}
|
||||
return endDate;
|
||||
}
|
||||
}
|
||||
|
||||
+31
@@ -25,6 +25,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.portfolio.tax.data.TaxComponentData;
|
||||
import org.apache.fineract.portfolio.tax.data.TaxGroupMappingsData;
|
||||
import org.apache.fineract.portfolio.tax.domain.TaxComponent;
|
||||
import org.apache.fineract.portfolio.tax.domain.TaxGroupMappings;
|
||||
|
||||
@@ -55,6 +57,27 @@ public final class TaxUtils {
|
||||
return map;
|
||||
}
|
||||
|
||||
public static Map<TaxComponentData, BigDecimal> splitTaxData(final BigDecimal amount, final LocalDate date,
|
||||
final Set<TaxGroupMappingsData> taxGroupMappings, final int scale) {
|
||||
Map<TaxComponentData, BigDecimal> map = new HashMap<>(3);
|
||||
if (amount != null) {
|
||||
final double amountVal = amount.doubleValue();
|
||||
double cent_percentage = Double.parseDouble("100.0");
|
||||
for (TaxGroupMappingsData groupMappings : taxGroupMappings) {
|
||||
if (groupMappings.occursOnDayFromAndUpToAndIncluding(date)) {
|
||||
TaxComponentData component = groupMappings.getTaxComponent();
|
||||
BigDecimal percentage = component.getApplicablePercentage(date);
|
||||
if (percentage != null) {
|
||||
double percentageVal = percentage.doubleValue();
|
||||
double tax = amountVal * percentageVal / cent_percentage;
|
||||
map.put(component, BigDecimal.valueOf(tax).setScale(scale, MoneyHelper.getRoundingMode()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public static BigDecimal incomeAmount(final BigDecimal amount, final LocalDate date, final Set<TaxGroupMappings> taxGroupMappings,
|
||||
final int scale) {
|
||||
Map<TaxComponent, BigDecimal> map = splitTax(amount, date, taxGroupMappings, scale);
|
||||
@@ -74,6 +97,14 @@ public final class TaxUtils {
|
||||
return totalTax;
|
||||
}
|
||||
|
||||
public static BigDecimal totalTaxDataAmount(final Map<TaxComponentData, BigDecimal> map) {
|
||||
BigDecimal totalTax = BigDecimal.ZERO;
|
||||
for (BigDecimal tax : map.values()) {
|
||||
totalTax = totalTax.add(tax);
|
||||
}
|
||||
return totalTax;
|
||||
}
|
||||
|
||||
public static BigDecimal addTax(final BigDecimal amount, final LocalDate date, final List<TaxGroupMappings> taxGroupMappings,
|
||||
final int scale) {
|
||||
BigDecimal totalAmount = null;
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
|
||||
ALTER TABLE `m_savings_account_transaction` ADD COLUMN `ref_no` VARCHAR(128) DEFAULT NULL;
|
||||
|
||||
ALTER TABLE `m_savings_account_transaction` ADD UNIQUE INDEX `transaction_ref_no` (`ref_no`);
|
||||
Reference in New Issue
Block a user