From 60588a78e0c23cf492371e38c6c4d15ced89d628 Mon Sep 17 00:00:00 2001 From: Nazeer Hussain Shaik Date: Tue, 19 Apr 2016 17:55:23 +0530 Subject: [PATCH] Issue fixes on shares and dividends reported by QA --- .../domain/JournalEntryRepository.java | 4 + .../service/AccountingProcessorHelper.java | 31 ++ ...CashBasedAccountingProcessorForShares.java | 2 + .../JournalEntryWritePlatformService.java | 3 + ...WritePlatformServiceJpaRepositoryImpl.java | 40 ++ .../portfolio/charge/data/ChargeData.java | 4 +- .../SavingsAccountReadPlatformService.java | 2 + ...SavingsAccountReadPlatformServiceImpl.java | 9 + .../data/ShareAccountChargeData.java | 5 +- .../shareaccounts/data/ShareAccountData.java | 10 +- ....java => ShareAccountTransactionData.java} | 11 +- .../shareaccounts/domain/ShareAccount.java | 75 ++- .../domain/ShareAccountCharge.java | 32 +- .../domain/ShareAccountChargePaidBy.java | 5 + .../domain/ShareAccountTransaction.java | 64 ++- .../ShareAccountDataSerializer.java | 462 ++++++++++++------ .../PurchasedSharesReadPlatformService.java | 4 +- ...urchasedSharesReadPlatformServiceImpl.java | 17 +- ...eAccountChargeReadPlatformServiceImpl.java | 6 +- .../ShareAccountReadPlatformServiceImpl.java | 30 +- ...WritePlatformServiceJpaRepositoryImpl.java | 104 +++- .../shareproducts/domain/ShareProduct.java | 16 +- .../ShareAccountsNotFoundException.java | 29 ++ .../ShareProductDataSerializer.java | 30 +- .../ShareProductDividendAssembler.java | 16 +- .../ShareProductReadPlatformServiceImpl.java | 2 +- .../core_db/V299__share_products.sql | 6 +- 27 files changed, 763 insertions(+), 256 deletions(-) rename fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/data/{PurchasedSharesData.java => ShareAccountTransactionData.java} (85%) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/exception/ShareAccountsNotFoundException.java diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryRepository.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryRepository.java index 73714e043..ab61a707d 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryRepository.java @@ -39,4 +39,8 @@ public interface JournalEntryRepository extends JpaRepository findProvisioningJournalEntriesByEntityId(@Param("entityId") Long entityId, @Param("entityType") Integer entityType) ; + + @Query("from JournalEntry journalEntry where journalEntry.transactionId= :transactionId and journalEntry.reversed is false and journalEntry.entityType = :entityType") + List findJournalEntries(@Param("transactionId") String transactionId, @Param("entityType") Integer entityType) ; + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java index ec2c0dd15..3d64581f6 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java @@ -1001,6 +1001,37 @@ public class AccountingProcessorHelper { "Recent Portfolio changes w.r.t Charges for shares have Broken the accounting code"); } } + public void revertCashBasedJournalEntryForSharesCharges(final Office office, final String currencyCode, + final CASH_ACCOUNTS_FOR_SHARES accountTypeToBeCredited, final Long shareProductId, final Long shareAccountId, + final String transactionId, final Date transactionDate, final BigDecimal totalAmount, + final List chargePaymentDTOs) { + final Map creditDetailsMap = new LinkedHashMap<>(); + for (final ChargePaymentDTO chargePaymentDTO : chargePaymentDTOs) { + final GLAccount chargeSpecificAccount = getLinkedGLAccountForShareCharges(shareProductId, accountTypeToBeCredited.getValue(), + chargePaymentDTO.getChargeId()); + BigDecimal chargeSpecificAmount = chargePaymentDTO.getAmount(); + + // adjust net credit amount if the account is already present in the + // map + if (creditDetailsMap.containsKey(chargeSpecificAccount)) { + final BigDecimal existingAmount = creditDetailsMap.get(chargeSpecificAccount); + chargeSpecificAmount = chargeSpecificAmount.add(existingAmount); + } + creditDetailsMap.put(chargeSpecificAccount, chargeSpecificAmount); + } + + BigDecimal totalCreditedAmount = BigDecimal.ZERO; + for (final Map.Entry entry : creditDetailsMap.entrySet()) { + final GLAccount account = entry.getKey(); + final BigDecimal amount = entry.getValue(); + totalCreditedAmount = totalCreditedAmount.add(amount); + createDebitJournalEntryForShares(office, currencyCode, account, shareAccountId, transactionId, transactionDate, amount); + } + if (totalAmount.compareTo(totalCreditedAmount) != 0) { throw new PlatformDataIntegrityException( + "Recent Portfolio changes w.r.t Charges for shares have Broken the accounting code", + "Recent Portfolio changes w.r.t Charges for shares have Broken the accounting code"); } + } + private void createDebitJournalEntryForShares(final Office office, final String currencyCode, final GLAccount account, final Long shareAccountId, final String transactionId, final Date transactionDate, final BigDecimal amount) { final boolean manualEntry = false; diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForShares.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForShares.java index f379ed17a..3b7147420 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForShares.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForShares.java @@ -119,6 +119,8 @@ public class CashBasedAccountingProcessorForShares implements AccountingProcesso BigDecimal amountForJE = amount; if (chargeAmount != null && chargeAmount.compareTo(BigDecimal.ZERO) == 1) { amountForJE = amount.subtract(chargeAmount); + /*this.helper.revertCashBasedJournalEntryForSharesCharges(office, currencyCode, CASH_ACCOUNTS_FOR_SHARES.INCOME_FROM_FEES, + shareProductId, shareAccountId, transactionId, transactionDate, chargeAmount, feePayments);*/ } this.helper.createJournalEntriesForShares(office, currencyCode, CASH_ACCOUNTS_FOR_SHARES.SHARES_SUSPENSE.getValue(), CASH_ACCOUNTS_FOR_SHARES.SHARES_REFERENCE.getValue(), shareProductId, paymentTypeId, shareAccountId, transactionId, diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformService.java index 4428daaed..89c93647f 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformService.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.accounting.journalentry.service; +import java.util.ArrayList; import java.util.Date; import java.util.Map; @@ -44,4 +45,6 @@ public interface JournalEntryWritePlatformService { public String createProvisioningJournalEntries(ProvisioningEntry entry) ; void createJournalEntriesForShares(Map accountingBridgeData); + + void revertShareAccountJournalEntries(final ArrayList transactionId, final Date transactionDate); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java index aed5f7f42..205cb1af8 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java @@ -53,6 +53,7 @@ import org.apache.fineract.accounting.journalentry.exception.JournalEntriesNotFo import org.apache.fineract.accounting.journalentry.exception.JournalEntryInvalidException; import org.apache.fineract.accounting.journalentry.exception.JournalEntryInvalidException.GL_JOURNAL_ENTRY_INVALID_REASON; import org.apache.fineract.accounting.journalentry.serialization.JournalEntryCommandFromApiJsonDeserializer; +import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType; import org.apache.fineract.accounting.provisioning.domain.LoanProductProvisioningEntry; import org.apache.fineract.accounting.provisioning.domain.ProvisioningEntry; import org.apache.fineract.accounting.rule.domain.AccountingRule; @@ -383,6 +384,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa // save the reversal entry this.glJournalEntryRepository.save(reversalJournalEntry); journalEntry.setReversalJournalEntry(reversalJournalEntry); + journalEntry.setReversed(true); // save the updated journal entry this.glJournalEntryRepository.save(journalEntry); } @@ -516,6 +518,44 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa } + @Override + public void revertShareAccountJournalEntries(final ArrayList transactionIds, final Date transactionDate) { + for (Long shareTransactionId : transactionIds) { + String transactionId = AccountingProcessorHelper.SHARE_TRANSACTION_IDENTIFIER + shareTransactionId.longValue(); + List journalEntries = this.glJournalEntryRepository.findJournalEntries(transactionId, + PortfolioProductType.SHARES.getValue()); + if (journalEntries == null || journalEntries.isEmpty()) continue; + final Long officeId = journalEntries.get(0).getOffice().getId(); + final String reversalTransactionId = generateTransactionId(officeId); + for (final JournalEntry journalEntry : journalEntries) { + JournalEntry reversalJournalEntry; + String reversalComment = "Reversal entry for Journal Entry with id :" + journalEntry.getId() + " and transaction Id " + + journalEntry.getTransactionId(); + if (journalEntry.isDebitEntry()) { + reversalJournalEntry = JournalEntry.createNew(journalEntry.getOffice(), journalEntry.getPaymentDetails(), + journalEntry.getGlAccount(), journalEntry.getCurrencyCode(), reversalTransactionId, Boolean.FALSE, + transactionDate, JournalEntryType.CREDIT, journalEntry.getAmount(), reversalComment, + journalEntry.getEntityType(), journalEntry.getEntityId(), journalEntry.getReferenceNumber(), + journalEntry.getLoanTransaction(), journalEntry.getSavingsTransaction(), journalEntry.getClientTransaction(), + journalEntry.getShareTransactionId()); + } else { + reversalJournalEntry = JournalEntry.createNew(journalEntry.getOffice(), journalEntry.getPaymentDetails(), + journalEntry.getGlAccount(), journalEntry.getCurrencyCode(), reversalTransactionId, Boolean.FALSE, + transactionDate, JournalEntryType.DEBIT, journalEntry.getAmount(), reversalComment, + journalEntry.getEntityType(), journalEntry.getEntityId(), journalEntry.getReferenceNumber(), + journalEntry.getLoanTransaction(), journalEntry.getSavingsTransaction(), journalEntry.getClientTransaction(), + journalEntry.getShareTransactionId()); + } + // save the reversal entry + this.glJournalEntryRepository.save(reversalJournalEntry); + journalEntry.setReversalJournalEntry(reversalJournalEntry); + journalEntry.setReversed(true); + // save the updated journal entry + this.glJournalEntryRepository.save(journalEntry); + } + } + } + private void validateBusinessRulesForJournalEntries(final JournalEntryCommand command) { /** check if date of Journal entry is valid ***/ final LocalDate entryLocalDate = command.getTransactionDate(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/data/ChargeData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/data/ChargeData.java index 627eaf954..9828962cc 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/data/ChargeData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/data/ChargeData.java @@ -290,9 +290,11 @@ public class ChargeData implements Comparable, Serializable { final BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO; final Collection chargeOptions = null; final Boolean isActive = null; + final BigDecimal chargeAmountOrPercentage = BigDecimal.ZERO ; + return new ShareAccountChargeData(shareChargeId, this.id, shareAccountId, this.name, this.currency, this.amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, this.chargeTimeType, this.chargeCalculationType, percentage, - amountPercentageAppliedTo, chargeOptions, isActive); + amountPercentageAppliedTo, chargeOptions, isActive, chargeAmountOrPercentage); } public boolean isPenalty() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java index bcc165c1b..b9835f138 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java @@ -34,6 +34,8 @@ public interface SavingsAccountReadPlatformService { Collection retrieveActiveForLookup(Long clientId, DepositAccountType depositAccountType); + public Collection retrieveActiveForLookup(final Long clientId, DepositAccountType depositAccountType, String currencyCode) ; + SavingsAccountData retrieveOne(Long savingsId); SavingsAccountData retrieveTemplate(Long clientId, Long groupId, Long productId, boolean staffInSelectedOfficeOnly); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index 4865fa49f..022f61e79 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java @@ -132,6 +132,15 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead return this.jdbcTemplate.query(sqlBuilder.toString(), this.savingAccountMapper, queryParameters); } + @Override + public Collection retrieveActiveForLookup(final Long clientId, DepositAccountType depositAccountType, String currencyCode) { + final StringBuilder sqlBuilder = new StringBuilder("select " + this.savingAccountMapper.schema()); + sqlBuilder.append(" where sa.client_id = ? and sa.status_enum = 300 and sa.deposit_type_enum = ? and sa.currency_code = ? "); + + final Object[] queryParameters = new Object[] { clientId, depositAccountType.getValue(), currencyCode }; + return this.jdbcTemplate.query(sqlBuilder.toString(), this.savingAccountMapper, queryParameters); + } + @Override public Page retrieveAll(final SearchParameters searchParameters) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/data/ShareAccountChargeData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/data/ShareAccountChargeData.java index 497db0eb1..41eaf7420 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/data/ShareAccountChargeData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/data/ShareAccountChargeData.java @@ -69,7 +69,7 @@ public class ShareAccountChargeData { final CurrencyData currency, final BigDecimal amount, final BigDecimal amountPaid, final BigDecimal amountWaived, final BigDecimal amountWrittenOff, final BigDecimal amountOutstanding, final EnumOptionData chargeTimeType, final EnumOptionData chargeCalculationType, final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo, - final Collection chargeOptions, final Boolean isActive) { + final Collection chargeOptions, final Boolean isActive, final BigDecimal chargeamountorpercentage) { this.id = id; this.chargeId = chargeId; this.accountId = accountId; @@ -84,11 +84,12 @@ public class ShareAccountChargeData { this.amountWaived = amountWaived; this.amountWrittenOff = amountWrittenOff; this.amountOutstanding = amountOutstanding; - this.amountOrPercentage = getAmountOrPercentage(); + this.amountOrPercentage = chargeamountorpercentage; this.chargeOptions = chargeOptions; this.isActive = isActive; } + private BigDecimal getAmountOrPercentage() { return (this.chargeCalculationType != null) && (this.chargeCalculationType.getId().intValue() > 1) ? this.percentage : this.amount; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/data/ShareAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/data/ShareAccountData.java index 36d3e9b1b..d0e1f8474 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/data/ShareAccountData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/data/ShareAccountData.java @@ -61,7 +61,7 @@ public class ShareAccountData implements AccountData { private ShareAccountSummaryData summary; - private Collection purchasedShares; + private Collection purchasedShares; private Long savingsAccountId; @@ -92,7 +92,7 @@ public class ShareAccountData implements AccountData { final String savingsAccountNumber, final Long clientId, final String clientName, final Long productId, final String productName, final ShareAccountStatusEnumData status, final ShareAccountApplicationTimelineData timeline, final CurrencyData currency, final ShareAccountSummaryData summaryData, final Collection charges, - final Collection purchasedSharesData, final Integer lockinPeriod, final EnumOptionData lockPeriodTypeEnum, + final Collection purchasedSharesData, final Integer lockinPeriod, final EnumOptionData lockPeriodTypeEnum, final Integer minimumActivePeriod, final EnumOptionData minimumActivePeriodTypeEnum, Boolean allowdividendsforinactiveclients) { this.id = id; this.accountNo = accountNo; @@ -145,7 +145,7 @@ public class ShareAccountData implements AccountData { final String savingsAccountNumber, final Long clientId, final String clientName, final Long productId, final String productName, final ShareAccountStatusEnumData status, final ShareAccountApplicationTimelineData timeline, final CurrencyData currency, final ShareAccountSummaryData summaryData, final Collection charges, - final Collection purchasedSharesData, final Integer lockinPeriod, final EnumOptionData lockPeriodTypeEnum, + final Collection purchasedSharesData, final Integer lockinPeriod, final EnumOptionData lockPeriodTypeEnum, final Integer minimumActivePeriod, final EnumOptionData minimumActivePeriodTypeEnum, final Boolean allowDividendCalculationForInactiveClients, final Collection productOptions, final Collection chargeOptions, final Collection clientSavingsAccounts, @@ -200,7 +200,7 @@ public class ShareAccountData implements AccountData { public static ShareAccountData lookup(final Long id, final String accountNo, final Long clientId, final String clientName) { String externalId = null; ShareAccountSummaryData summaryData = null; - Collection purchasedSharesData = null; + Collection purchasedSharesData = null; GLAccountData suspenseAccount = null; Integer lockinPeriod = null; EnumOptionData lockPeriodTypeEnum = null; @@ -233,7 +233,7 @@ public class ShareAccountData implements AccountData { return this.id; } - public Collection getPurchasedShares() { + public Collection getPurchasedShares() { return this.purchasedShares; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/data/PurchasedSharesData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/data/ShareAccountTransactionData.java similarity index 85% rename from fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/data/PurchasedSharesData.java rename to fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/data/ShareAccountTransactionData.java index 95048c63b..dcb192e01 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/data/PurchasedSharesData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/data/ShareAccountTransactionData.java @@ -23,7 +23,8 @@ import java.math.BigDecimal; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.joda.time.LocalDate; -public class PurchasedSharesData { +@SuppressWarnings("unused") +public class ShareAccountTransactionData { private final Long id; @@ -43,8 +44,11 @@ public class PurchasedSharesData { private final BigDecimal chargeAmount ; - public PurchasedSharesData(final Long id, final Long accountId, final LocalDate purchasedDate, final Long numberOfShares, - final BigDecimal purchasedPrice, final EnumOptionData status, final EnumOptionData type, final BigDecimal amount, final BigDecimal chargeAmount) { + private final BigDecimal amountPaid ; + + public ShareAccountTransactionData(final Long id, final Long accountId, final LocalDate purchasedDate, final Long numberOfShares, + final BigDecimal purchasedPrice, final EnumOptionData status, final EnumOptionData type, final BigDecimal amount, final BigDecimal chargeAmount, + final BigDecimal amountPaid) { this.id = id; this.accountId = accountId; this.purchasedDate = purchasedDate; @@ -54,6 +58,7 @@ public class PurchasedSharesData { this.type = type; this.amount = amount ; this.chargeAmount = chargeAmount ; + this.amountPaid = amountPaid ; } public LocalDate getPurchasedDate() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccount.java index 9cd21cff8..649438357 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccount.java @@ -18,10 +18,8 @@ */ package org.apache.fineract.portfolio.shareaccounts.domain; -import java.math.BigDecimal; import java.util.Date; import java.util.HashSet; -import java.util.Iterator; import java.util.Set; import javax.persistence.CascadeType; @@ -261,6 +259,17 @@ public class ShareAccount extends AbstractPersistable { return true; } + public void addTransaction(final ShareAccountTransaction transaction) { + transaction.setShareAccount(this); + if(transaction.isPendingForApprovalTransaction()) { + this.totalSharesPending += transaction.getTotalShares() ; + }else if(transaction.isPurchasTransaction()) { + this.totalSharesApproved += transaction.getTotalShares() ; + } + + this.shareAccountTransactions.add(transaction); + } + public boolean setAllowDividendCalculationForInactiveClients(Boolean allowDividendCalculationForInactiveClients) { boolean returnValue = false; if (this.allowDividendCalculationForInactiveClients == null) { @@ -346,7 +355,7 @@ public class ShareAccount extends AbstractPersistable { public Set getChargeTransactions() { Set chargeTransactions = new HashSet<>(); for (ShareAccountTransaction transaction : this.shareAccountTransactions) { - if (transaction.isChargeTransaction()) { + if (transaction.isActive() && transaction.isChargeTransaction()) { chargeTransactions.add(transaction); } } @@ -380,18 +389,6 @@ public class ShareAccount extends AbstractPersistable { this.charges.add(charge); } - public ShareAccountCharge updateShareCharge(final Long id, final Long chargeId, final BigDecimal amount) { - ShareAccountCharge updatedCharge = null; - for (ShareAccountCharge charge : this.charges) { - if (charge.getId() == id && charge.getChargeId() == chargeId) { - updatedCharge = charge; - charge.update(amount); - break; - } - } - return updatedCharge; - } - public void approve(final Date approvedDate, final AppUser approvedUser) { this.approvedDate = approvedDate; this.approvedBy = approvedUser; @@ -418,10 +415,14 @@ public class ShareAccount extends AbstractPersistable { this.closedDate = null; this.closedBy = null; this.totalSharesApproved = null; - Iterator iter = this.shareAccountTransactions.iterator(); - ShareAccountTransaction share = iter.next(); - share.undoApprove(); - this.totalSharesPending = share.getTotalShares(); + Long tempTotalShares = new Long(0); + for (ShareAccountTransaction transaction : this.shareAccountTransactions) { + if(transaction.isPurchasTransaction()) { + transaction.undoApprove(); + tempTotalShares += transaction.getTotalShares() ; + } + } + this.totalSharesPending = tempTotalShares; } public void reject(final Date rejectedDate, final AppUser rejectedUser) { @@ -430,9 +431,11 @@ public class ShareAccount extends AbstractPersistable { this.status = ShareAccountStatusType.REJECTED.getValue(); this.totalSharesPending = null; this.totalSharesApproved = null; - Iterator iter = this.shareAccountTransactions.iterator(); - ShareAccountTransaction share = iter.next(); - share.reject(); + for (ShareAccountTransaction transaction : this.shareAccountTransactions) { + if(transaction.isPurchasTransaction()) { + transaction.reject(); + } + } } public void close(final Date closedDate, final AppUser closedBy) { @@ -469,7 +472,7 @@ public class ShareAccount extends AbstractPersistable { public Set getPendingForApprovalSharePurchaseTransactions() { Set purchaseTransactions = new HashSet<>(); for (ShareAccountTransaction transaction : this.shareAccountTransactions) { - if (transaction.isPendingForApprovalTransaction()) { + if (transaction.isActive() && transaction.isPendingForApprovalTransaction()) { purchaseTransactions.add(transaction); } } @@ -523,4 +526,30 @@ public class ShareAccount extends AbstractPersistable { public Date getApprovedDate() { return this.approvedDate ; } + + public void removeTransactions() { + for(ShareAccountTransaction transaction: this.shareAccountTransactions) { + transaction.setActive(false); + } + this.totalSharesApproved = new Long(0) ; + this.totalSharesPending = new Long(0) ; + } + + public void removeCharges() { + for(ShareAccountCharge charge: this.charges) { + charge.setActive(false); + } + } + + public void addCharges(Set charges) { + this.charges.addAll(charges) ; + } + + public Integer getLockinPeriodFrequency() { + return this.lockinPeriodFrequency ; + } + + public PeriodFrequencyType getLockinPeriodFrequencyType() { + return this.lockinPeriodFrequencyType ; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccountCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccountCharge.java index a415c3620..2587dd612 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccountCharge.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccountCharge.java @@ -83,7 +83,7 @@ public class ShareAccountCharge extends AbstractPersistable { private boolean waived = false; @Column(name = "is_active", nullable = false) - private boolean status = true; + private boolean active = true; @Column(name = "charge_amount_or_percentage") private BigDecimal amountOrPercentage; @@ -118,13 +118,13 @@ public class ShareAccountCharge extends AbstractPersistable { populateDerivedFields(transactionAmount, chargeAmount); this.paid = determineIfFullyPaid(); - this.status = status; + this.active = status; } - public void populateDerivedFields(final BigDecimal transactionAmount, final BigDecimal chargeAmount) { + private void populateDerivedFields(final BigDecimal transactionAmount, final BigDecimal chargeAmount) { + this.amountOrPercentage = chargeAmount ; if (this.chargeCalculation.equals(ChargeCalculationType.FLAT.getValue())) { this.percentage = null; - this.amountOrPercentage = chargeAmount ; this.amount = BigDecimal.ZERO; this.amountPercentageAppliedTo = null; this.amountPaid = null; @@ -149,6 +149,7 @@ public class ShareAccountCharge extends AbstractPersistable { } public void resetToOriginal(final MonetaryCurrency currency) { + this.amount = BigDecimal.ZERO ; this.amountPaid = BigDecimal.ZERO; this.amountWaived = BigDecimal.ZERO; this.amountWrittenOff = BigDecimal.ZERO; @@ -163,7 +164,7 @@ public class ShareAccountCharge extends AbstractPersistable { this.amountPaid = amountPaid.getAmount(); this.amountOutstanding = calculateAmountOutstanding(currency); this.paid = false; - this.status = true; + this.active = true; } public Money waive(final MonetaryCurrency currency) { @@ -181,7 +182,7 @@ public class ShareAccountCharge extends AbstractPersistable { this.amountWaived = amountWaived.getAmount(); this.amountOutstanding = calculateAmountOutstanding(currency); this.waived = false; - this.status = true; + this.active = true; } public Money pay(final MonetaryCurrency currency, final Money amountPaid) { @@ -203,17 +204,8 @@ public class ShareAccountCharge extends AbstractPersistable { this.shareAccount = shareAccount; } - public void update(final BigDecimal amount) { - final BigDecimal transactionAmount = BigDecimal.ZERO; - if (this.chargeCalculation.equals(ChargeCalculationType.FLAT.getValue())) { - this.amountOrPercentage = amount; - this.amountOutstanding = calculateOutstanding(); - } else if (this.chargeCalculation.equals(ChargeCalculationType.PERCENT_OF_AMOUNT.getValue())) { - this.percentage = amount; - this.amountPercentageAppliedTo = transactionAmount; - this.amount = percentageOf(this.amountPercentageAppliedTo, this.percentage); - this.amountOutstanding = calculateOutstanding(); - } + public void update(final BigDecimal transactionAmount, final BigDecimal amount) { + populateDerivedFields(transactionAmount, amount); } private boolean isGreaterThanZero(final BigDecimal value) { @@ -360,7 +352,7 @@ public class ShareAccountCharge extends AbstractPersistable { } public boolean isActive() { - return this.status; + return this.active; } public boolean isNotActive() { @@ -420,6 +412,10 @@ public class ShareAccountCharge extends AbstractPersistable { return toReturnAmount; } + public void setActive(boolean active) { + this.active = active ; + } + @Override public boolean equals(final Object obj) { if (obj == null) { return false; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccountChargePaidBy.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccountChargePaidBy.java index 97f1ce315..d9843ebf6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccountChargePaidBy.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccountChargePaidBy.java @@ -74,4 +74,9 @@ public class ShareAccountChargePaidBy extends AbstractPersistable{ public Long getShareChargeId() { return this.shareAccountCharge.getId() ; } + + public void reset() { + this.shareAccountTransaction = null ; + this.shareAccountCharge = null ; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccountTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccountTransaction.java index 662b2cd53..af2225e71 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccountTransaction.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/domain/ShareAccountTransaction.java @@ -58,8 +58,11 @@ public class ShareAccountTransaction extends AbstractPersistable { @Column(name = "amount") private BigDecimal amount ; + @Column(name = "amount_paid") + private BigDecimal amountPaid ; + @Column(name = "charge_amount") - private BigDecimal totalChargeAmount ; + private BigDecimal chargeAmount ; @Column(name = "status_enum", nullable = true) private Integer status; @@ -67,6 +70,9 @@ public class ShareAccountTransaction extends AbstractPersistable { @Column(name = "type_enum", nullable = true) private Integer type ; + @Column(name = "is_active", nullable = false) + private boolean active = true ; + @LazyCollection(LazyCollectionOption.FALSE) @OneToMany(cascade = CascadeType.ALL, mappedBy = "shareAccountTransaction", orphanRemoval = true) private final Set shareAccountChargesPaid = new HashSet<>(); @@ -86,24 +92,27 @@ public class ShareAccountTransaction extends AbstractPersistable { this.status = PurchasedSharesStatusType.APPLIED.getValue(); this.type = PurchasedSharesStatusType.PURCHASED.getValue() ; this.amount = shareValue.multiply(BigDecimal.valueOf(totalShares)) ; + this.amountPaid = new BigDecimal(this.amount.doubleValue()) ; } private ShareAccountTransaction(final Date transactionDate, final Long totalShares, final BigDecimal shareValue, - final Integer status, final Integer type, final BigDecimal amount, final BigDecimal chargeAmount) { + final Integer status, final Integer type, final BigDecimal amount, final BigDecimal chargeAmount, final BigDecimal amountPaid) { this.transactionDate = transactionDate; this.totalShares = totalShares; this.shareValue = shareValue; this.status = status ; this.type = type ; this.amount = amount ; - this.totalChargeAmount = chargeAmount ; + this.chargeAmount = chargeAmount ; + this.amountPaid = amountPaid ; } public static ShareAccountTransaction createRedeemTransaction(final Date transactionDate, final Long totalShares, final BigDecimal shareValue) { final Integer status = PurchasedSharesStatusType.APPROVED.getValue() ; final Integer type = PurchasedSharesStatusType.REDEEMED.getValue() ; final BigDecimal amount = shareValue.multiply(BigDecimal.valueOf(totalShares)) ; - return new ShareAccountTransaction(transactionDate, totalShares, shareValue, status, type, amount, null) ; + BigDecimal amountPaid = new BigDecimal(amount.doubleValue()) ; + return new ShareAccountTransaction(transactionDate, totalShares, shareValue, status, type, amount, null, amountPaid) ; } public static ShareAccountTransaction createChargeTransaction(final Date transactionDate, final ShareAccountCharge charge) { @@ -113,7 +122,8 @@ public class ShareAccountTransaction extends AbstractPersistable { final Integer type = PurchasedSharesStatusType.CHARGE_PAYMENT.getValue() ; BigDecimal amount = charge.percentageOrAmount() ; BigDecimal chargeAmount = null ; - return new ShareAccountTransaction(transactionDate, totalShares, unitPrice, status, type, amount, chargeAmount) ; + BigDecimal amountPaid = null ; + return new ShareAccountTransaction(transactionDate, totalShares, unitPrice, status, type, amount, chargeAmount, amountPaid) ; } public Date getPurchasedDate() { @@ -133,6 +143,7 @@ public class ShareAccountTransaction extends AbstractPersistable { this.totalShares = totalShares; this.shareValue = shareValue; this.amount = shareValue.multiply(BigDecimal.valueOf(totalShares)) ; + this.chargeAmount = BigDecimal.ZERO ; this.status = PurchasedSharesStatusType.APPLIED.getValue(); } @@ -146,8 +157,8 @@ public class ShareAccountTransaction extends AbstractPersistable { public void reject() { this.status = PurchasedSharesStatusType.REJECTED.getValue() ; - if(this.totalChargeAmount != null) { - this.amount = this.amount.subtract(totalChargeAmount) ; + if(this.chargeAmount != null) { + this.amount = this.amount.subtract(chargeAmount) ; } } @@ -167,6 +178,10 @@ public class ShareAccountTransaction extends AbstractPersistable { return this.status.equals(PurchasedSharesStatusType.APPROVED.getValue()) && this.type.equals(PurchasedSharesStatusType.CHARGE_PAYMENT.getValue()) ; } + public boolean isPurchaseRejectedTransaction() { + return this.status.equals(PurchasedSharesStatusType.REJECTED.getValue()) && this.type.equals(PurchasedSharesStatusType.PURCHASED.getValue()) ; + } + public void addShareAccountChargePaidBy(final ShareAccountChargePaidBy chargePaidBy) { this.shareAccountChargesPaid.add(chargePaidBy) ; } @@ -176,17 +191,17 @@ public class ShareAccountTransaction extends AbstractPersistable { } public BigDecimal chargeAmount() { - return this.totalChargeAmount ; + return this.chargeAmount ; } public void updateChargeAmount(BigDecimal totalChargeAmount) { this.amount = this.amount.add(totalChargeAmount); - this.totalChargeAmount = totalChargeAmount ; + this.chargeAmount = totalChargeAmount ; } public void deductChargesFromTotalAmount(BigDecimal totalChargeAmount) { this.amount = this.amount.subtract(totalChargeAmount); - this.totalChargeAmount = totalChargeAmount ; + this.chargeAmount = totalChargeAmount ; } public Set getChargesPaidBy() { @@ -201,9 +216,32 @@ public class ShareAccountTransaction extends AbstractPersistable { return this.type ; } - public void adjustRedeemAmount() { - if(this.totalChargeAmount != null) { - this.amount = this.amount.subtract(totalChargeAmount) ; + public void updateAmountPaid(final BigDecimal amountPaid) { + this.amountPaid = amountPaid ; + } + + public void addAmountPaid(final BigDecimal amountPaid) { + if(isRedeemTransaction()) { + this.amountPaid = this.amountPaid.subtract(amountPaid) ; + }else if(isPurchasTransaction() /*|| isPurchaseRejectedTransaction()*/) { + this.amountPaid = this.amountPaid.add(amountPaid) ; } } + + public void resetAmountPaid() { + this.amountPaid = BigDecimal.ZERO ; + } + + public void setActive(boolean active) { + this.active = active ; + if(!this.active) { + //this.shareAccountChargesPaid.clear(); + } + } + + public boolean isActive() { + return this.active ; + } + + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java index 13c7b4dac..1e6305068 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java @@ -49,7 +49,6 @@ import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.loanproduct.exception.InvalidCurrencyException; -import org.apache.fineract.portfolio.savings.SavingsApiConstants; import org.apache.fineract.portfolio.savings.domain.SavingsAccount; import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper; import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccount; @@ -87,9 +86,8 @@ public class ShareAccountDataSerializer { @Autowired public ShareAccountDataSerializer(final PlatformSecurityContext platformSecurityContext, final FromJsonHelper fromApiJsonHelper, - final ChargeRepositoryWrapper chargeRepository, - final SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper, final ClientRepositoryWrapper clientRepositoryWrapper, - final ShareProductRepositoryWrapper shareProductRepository) { + final ChargeRepositoryWrapper chargeRepository, final SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper, + final ClientRepositoryWrapper clientRepositoryWrapper, final ShareProductRepositoryWrapper shareProductRepository) { this.platformSecurityContext = platformSecurityContext; this.fromApiJsonHelper = fromApiJsonHelper; this.chargeRepository = chargeRepository; @@ -117,7 +115,7 @@ public class ShareAccountDataSerializer { baseDataValidator.reset().parameter(ShareAccountApiConstants.submitteddate_paramname).value(submittedDate).notNull(); final String externalId = this.fromApiJsonHelper.extractStringNamed(ShareAccountApiConstants.externalid_paramname, element); - baseDataValidator.reset().parameter(ShareAccountApiConstants.externalid_paramname).value(externalId).notNull(); + // baseDataValidator.reset().parameter(ShareAccountApiConstants.externalid_paramname).value(externalId).notNull(); Long savingsAccountId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.savingsaccountid_paramname, element); baseDataValidator.reset().parameter(ShareAccountApiConstants.savingsaccountid_paramname).value(savingsAccountId).notNull() @@ -126,25 +124,16 @@ public class ShareAccountDataSerializer { final Long requestedShares = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.requestedshares_paramname, element); baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(requestedShares).notNull() .longGreaterThanZero(); - boolean allowed = shareProduct.isSharesAllowed(requestedShares) ; - if(!allowed) { - baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(requestedShares).failWithCode("differ.from.productdefinition", "Out of range");; + if(requestedShares < shareProduct.getMinimumClientShares()) { + baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(requestedShares) + .failWithCode("client.can.not.purchase.shares.lessthan.product.definition", "Client can not purchase shares less than product definition"); } - /*Long subscribedShares = shareProduct.getSubscribedShares() ; - if(subscribedShares == null) subscribedShares = new Long(0) ; - Long totalShares = shareProduct.getTotalShares() ; - Long issuedShares = shareProduct.getSharesIssued() ; - if(issuedShares == null) issuedShares = totalShares ; - if((subscribedShares+requestedShares) > issuedShares) { - throw new IssueableSharesExceededException() ; + if(requestedShares > shareProduct.getMaximumClientShares()) { + baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(requestedShares) + .failWithCode("client.can.not.purchase.shares.morethan.product.definition", "Client can not purchase shares more than product definition"); } - shareProduct.addSubscribedShares(requestedShares);*/ - /*BigDecimal unitPrice = this.fromApiJsonHelper.extractBigDecimalNamed(ShareAccountApiConstants.purchasedprice_paramname, element, - locale); - baseDataValidator.reset().parameter(ShareAccountApiConstants.purchasedprice_paramname).value(unitPrice).notNull().positiveAmount();*/ - LocalDate applicationDate = this.fromApiJsonHelper.extractLocalDateNamed(ShareAccountApiConstants.applicationdate_param, element); baseDataValidator.reset().parameter(ShareAccountApiConstants.applicationdate_param).value(applicationDate).notNull(); @@ -185,7 +174,7 @@ public class ShareAccountDataSerializer { String accountNo = null; Long approvedShares = null; Long pendingShares = requestedShares; - BigDecimal unitPrice = shareProduct.deriveMarketPrice(applicationDate.toDate()) ; + BigDecimal unitPrice = shareProduct.deriveMarketPrice(applicationDate.toDate()); ShareAccountTransaction transaction = new ShareAccountTransaction(applicationDate.toDate(), requestedShares, unitPrice); Set sharesPurchased = new HashSet<>(); sharesPurchased.add(transaction); @@ -204,28 +193,28 @@ public class ShareAccountDataSerializer { charge.update(account); } } - createChargeTransaction(account, transaction); + createChargeTransaction(account); return account; } - private void createChargeTransaction(ShareAccount account, final ShareAccountTransaction transaction) { + private void createChargeTransaction(ShareAccount account) { BigDecimal totalChargeAmount = BigDecimal.ZERO; Set charges = account.getCharges(); Date currentDate = DateUtils.getLocalDateOfTenant().toDate(); for (ShareAccountCharge charge : charges) { - if (charge.isShareAccountActivation()) { - charge.deriveChargeAmount(totalChargeAmount) ; + if (charge.isActive() && charge.isShareAccountActivation()) { + charge.deriveChargeAmount(totalChargeAmount); ShareAccountTransaction chargeTransaction = ShareAccountTransaction.createChargeTransaction(currentDate, charge); ShareAccountChargePaidBy paidBy = new ShareAccountChargePaidBy(chargeTransaction, charge, charge.percentageOrAmount()); chargeTransaction.addShareAccountChargePaidBy(paidBy); account.addChargeTransaction(chargeTransaction); } } - + Set pendingApprovalTransaction = account.getPendingForApprovalSharePurchaseTransactions(); for (ShareAccountTransaction pending : pendingApprovalTransaction) { for (ShareAccountCharge charge : charges) { - if (charge.isSharesPurchaseCharge()) { + if (charge.isActive() && charge.isSharesPurchaseCharge()) { BigDecimal amount = charge.deriveChargeAmount(pending.amount()); ShareAccountChargePaidBy paidBy = new ShareAccountChargePaidBy(pending, charge, amount); pending.addShareAccountChargePaidBy(paidBy); @@ -245,7 +234,7 @@ public class ShareAccountDataSerializer { final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("sharesaccount"); JsonElement element = jsonCommand.parsedJson(); - ShareProduct shareProduct = account.getShareProduct() ; + ShareProduct shareProduct = account.getShareProduct(); final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject()); if (this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.productid_paramname, element)) { final Long productId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.productid_paramname, element); @@ -266,7 +255,7 @@ public class ShareAccountDataSerializer { if (this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.externalid_paramname, element)) { final String externalId = this.fromApiJsonHelper.extractStringNamed(ShareAccountApiConstants.externalid_paramname, element); - baseDataValidator.reset().parameter(ShareAccountApiConstants.externalid_paramname).value(externalId).notNull(); + // baseDataValidator.reset().parameter(ShareAccountApiConstants.externalid_paramname).value(externalId).notNull(); if (account.setExternalId(externalId)) { actualChanges.put(ShareAccountApiConstants.externalid_paramname, externalId); } @@ -274,25 +263,51 @@ public class ShareAccountDataSerializer { if (this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.savingsaccountid_paramname, element)) { Long savingsAccountId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.savingsaccountid_paramname, element); - SavingsAccount savingsAccount = this.savingsAccountRepositoryWrapper.findOneWithNotFoundDetection(savingsAccountId); - if (account.setSavingsAccount(savingsAccount)) { - actualChanges.put(ShareAccountApiConstants.savingsaccountid_paramname, savingsAccount); + baseDataValidator.reset().parameter(ShareAccountApiConstants.savingsaccountid_paramname).value(savingsAccountId).notNull() + .longGreaterThanZero(); + if(savingsAccountId != null) { + SavingsAccount savingsAccount = this.savingsAccountRepositoryWrapper.findOneWithNotFoundDetection(savingsAccountId); + if (account.setSavingsAccount(savingsAccount)) { + actualChanges.put(ShareAccountApiConstants.savingsaccountid_paramname, savingsAccount.getId()); + } } } + if (this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.requestedshares_paramname, element) + || this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.charges_paramname, element)) { + Set transactions = account.getShareAccountTransactions(); + List reveralIds = new ArrayList<>(); + for (ShareAccountTransaction transaction : transactions) { + reveralIds.add(transaction.getId()); + } + actualChanges.put("reversalIds", reveralIds); + account.removeTransactions(); + account.removeCharges(); + } + if (this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.requestedshares_paramname, element)) { Long requestedShares = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.requestedshares_paramname, element); - /*BigDecimal unitPrice = this.fromApiJsonHelper.extractBigDecimalNamed(ShareAccountApiConstants.purchasedprice_paramname, - element, locale);*/ + /* + * BigDecimal unitPrice = + * this.fromApiJsonHelper.extractBigDecimalNamed + * (ShareAccountApiConstants.purchasedprice_paramname, element, + * locale); + */ Date applicationDate = this.fromApiJsonHelper.extractLocalDateNamed(ShareAccountApiConstants.applicationdate_param, element) .toDate(); - BigDecimal unitPrice = shareProduct.deriveMarketPrice(applicationDate) ; - ShareAccountTransaction purchased = new ShareAccountTransaction(applicationDate, requestedShares, unitPrice); - account.updateRequestedShares(purchased); - actualChanges.put(ShareAccountApiConstants.requestedshares_paramname, purchased); - boolean allowed = shareProduct.isSharesAllowed(requestedShares) ; - if(!allowed) { - baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(requestedShares).failWithCode("differ.from.productdefinition", "Out of range");; + BigDecimal unitPrice = shareProduct.deriveMarketPrice(applicationDate); + ShareAccountTransaction transaction = new ShareAccountTransaction(applicationDate, requestedShares, unitPrice); + account.addTransaction(transaction); + actualChanges.put(ShareAccountApiConstants.requestedshares_paramname, "Transaction"); + + if(requestedShares < shareProduct.getMinimumClientShares()) { + baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(requestedShares) + .failWithCode("client.can.not.purchase.shares.lessthan.product.definition", "Client can not purchase shares less than product definition"); + } + + if(requestedShares > shareProduct.getMaximumClientShares()) { + baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(requestedShares) + .failWithCode("client.can.not.purchase.shares.morethan.product.definition", "Client can not purchase shares more than product definition"); } } @@ -340,13 +355,19 @@ public class ShareAccountDataSerializer { } if (this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.charges_paramname, element)) { shareProduct = account.getShareProduct(); - Set updatedCharges = assembleListOfAccountChargesforUpdate(account, element, shareProduct.getCurrency() - .getCode()); - if (!updatedCharges.isEmpty()) { - actualChanges.put(ShareAccountApiConstants.charges_paramname, new HashSet()); + final MonetaryCurrency currency = shareProduct.getCurrency(); + Set charges = assembleListOfAccountCharges(element, currency.getCode()); + if (charges != null) { + for (ShareAccountCharge charge : charges) { + charge.update(account); + } + account.addCharges(charges); + if (!charges.isEmpty()) { + actualChanges.put(ShareAccountApiConstants.charges_paramname, new HashSet()); + } } - } + createChargeTransaction(account); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } return actualChanges; } @@ -366,30 +387,57 @@ public class ShareAccountDataSerializer { final DateTimeFormatter formatter = DateTimeFormat.forPattern(jsonCommand.dateFormat()).withLocale(jsonCommand.extractLocale()); final String submittalDateAsString = formatter.print(submittalDate); baseDataValidator.reset().parameter(ShareAccountApiConstants.approveddate_paramname).value(submittalDateAsString) - .failWithCodeNoParameterAddedToErrorCode("cannot.be.before.submittal.date"); + .failWithCodeNoParameterAddedToErrorCode("approved.date.cannot.be.before.submitted.date"); } + + Set transactions = account.getShareAccountTransactions() ; + for(ShareAccountTransaction transaction: transactions) { + if(transaction.isActive() && transaction.isPendingForApprovalTransaction()) { + validateTotalSubsribedShares(account, transaction, baseDataValidator) ; + } + } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } AppUser approvedUser = this.platformSecurityContext.authenticatedUser(); - account.approve(approvedDate.toDate(), approvedUser); + account.approve(approvedDate.toDate(), approvedUser); actualChanges.put(ShareAccountApiConstants.id_paramname, account.getId()); updateTotalChargeDerived(account); return actualChanges; } + private void validateTotalSubsribedShares(final ShareAccount account, final ShareAccountTransaction transaction, final DataValidatorBuilder baseDataValidator) { + Long totalSubsribedShares = account.getShareProduct().getSubscribedShares() ; + Long requested = new Long(0) ; + if(transaction.isActive() && transaction.isPendingForApprovalTransaction()) { + requested +=transaction.getTotalShares() ; + } + Long totalSharesIssuable = account.getShareProduct().getSharesIssued() ; + if(totalSharesIssuable == null) totalSharesIssuable = account.getShareProduct().getTotalShares() ; + if(totalSubsribedShares == null) totalSubsribedShares = new Long(0) ; + if((totalSubsribedShares+requested) > totalSharesIssuable) { + baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(requested) + .failWithCodeNoParameterAddedToErrorCode("shares.requested.can.not.be.approved.exceeding.totalshares.issuable"); + } + } private void updateTotalChargeDerived(final ShareAccount shareAccount) { // Set charges = shareAccount.getCharges() ; Set transactions = shareAccount.getShareAccountTransactions(); for (ShareAccountTransaction transaction : transactions) { - Set paidBySet = transaction.getChargesPaidBy(); - if (paidBySet != null && !paidBySet.isEmpty()) { - for (ShareAccountChargePaidBy chargePaidBy : paidBySet) { - ShareAccountCharge charge = chargePaidBy.getCharge(); - if (charge.isSharesPurchaseCharge()) { - Money money = Money.of(shareAccount.getCurrency(), chargePaidBy.getAmount()); - charge.updatePaidAmountBy(money); + if (transaction.isActive()) { + Set paidBySet = transaction.getChargesPaidBy(); + if (paidBySet != null && !paidBySet.isEmpty()) { + for (ShareAccountChargePaidBy chargePaidBy : paidBySet) { + ShareAccountCharge charge = chargePaidBy.getCharge(); + if (charge.isActive() && charge.isSharesPurchaseCharge()) { + Money money = Money.of(shareAccount.getCurrency(), chargePaidBy.getAmount()); + charge.updatePaidAmountBy(money); + } } } + if (!transaction.isChargeTransaction()) { + transaction.addAmountPaid(transaction.chargeAmount()); + } } } } @@ -397,19 +445,54 @@ public class ShareAccountDataSerializer { public Map validateAndUndoApprove(JsonCommand jsonCommand, ShareAccount account) { Map actualChanges = new HashMap<>(); if (StringUtils.isBlank(jsonCommand.json())) { throw new InvalidJsonException(); } - //final Type typeOfMap = new TypeToken>() {}.getType(); - //this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, jsonCommand.json(), ShareAccountApiConstants.activateParameters); - //final List dataValidationErrors = new ArrayList<>(); - //final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("sharesaccount"); - //JsonElement element = jsonCommand.parsedJson(); - //String notes = this.fromApiJsonHelper.extractStringNamed(ShareAccountApiConstants.note_paramname, element); + // final Type typeOfMap = new TypeToken>() + // {}.getType(); + // this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, + // jsonCommand.json(), ShareAccountApiConstants.activateParameters); + // final List dataValidationErrors = new + // ArrayList<>(); + // final DataValidatorBuilder baseDataValidator = new + // DataValidatorBuilder(dataValidationErrors).resource("sharesaccount"); + // JsonElement element = jsonCommand.parsedJson(); + // String notes = + // this.fromApiJsonHelper.extractStringNamed(ShareAccountApiConstants.note_paramname, + // element); // baseDataValidator.reset().parameter(ShareAccountApiConstants.approveddate_paramname).validateDateAfter(account.get) - //AppUser approvedUser = this.platformSecurityContext.authenticatedUser(); + // AppUser approvedUser = + // this.platformSecurityContext.authenticatedUser(); account.undoApprove(); + updateTotalChargeDerivedForUndoApproval(account) ; actualChanges.put(ShareAccountApiConstants.charges_paramname, Boolean.TRUE); return actualChanges; } + private void updateTotalChargeDerivedForUndoApproval(final ShareAccount shareAccount) { + Set purchaseTransactions = shareAccount.getPendingForApprovalSharePurchaseTransactions() ; + //we will have only 1 purchase transaction. Take that + ShareAccountTransaction purchaseTransaction = null ; + for (ShareAccountTransaction transaction : purchaseTransactions) { + purchaseTransaction = transaction ; + } + BigDecimal transactionAmount = BigDecimal.ZERO ; + if(purchaseTransaction != null) { + transactionAmount = purchaseTransaction.amount().subtract(purchaseTransaction.chargeAmount()) ; + } + Set charges = shareAccount.getCharges() ; + for(ShareAccountCharge charge: charges) { + if(charge.isActive() && charge.isSharesPurchaseCharge()) { + charge.update(transactionAmount, charge.percentageOrAmount()) ; + charge.deriveChargeAmount(transactionAmount); + } + } + Set transactions = shareAccount.getShareAccountTransactions(); + for (ShareAccountTransaction transaction : transactions) { + if (transaction.isActive()) { + if (!transaction.isChargeTransaction()) { + transaction.resetAmountPaid(); + } + } + } + } @SuppressWarnings("unused") public Map validateAndReject(JsonCommand jsonCommand, ShareAccount account) { Map actualChanges = new HashMap<>(); @@ -449,10 +532,17 @@ public class ShareAccountDataSerializer { private void handlechargesOnActivation(final ShareAccount account) { Set charges = account.getCharges(); for (ShareAccountCharge charge : charges) { - if (charge.isShareAccountActivation()) { + if (charge.isActive() && charge.isShareAccountActivation()) { charge.markAsFullyPaid(); } } + + Set transactions = account.getChargeTransactions(); + for (ShareAccountTransaction transaction : transactions) { + if (transaction.isChargeTransaction()) { + transaction.updateAmountPaid(transaction.amount()); + } + } } private Set assembleListOfAccountCharges(final JsonElement element, final String currencyCode) { @@ -484,49 +574,6 @@ public class ShareAccountDataSerializer { return charges; } - private Set assembleListOfAccountChargesforUpdate(final ShareAccount account, final JsonElement element, - final String currencyCode) { - Set updated = new HashSet<>(); - if (this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.charges_paramname, element)) { - JsonArray chargesArray = this.fromApiJsonHelper.extractJsonArrayNamed(ShareAccountApiConstants.charges_paramname, element); - if (chargesArray != null) { - for (int i = 0; i < chargesArray.size(); i++) { - final JsonObject jsonObject = chargesArray.get(i).getAsJsonObject(); - if (jsonObject.has("id")) { - final Long id = jsonObject.get("id").getAsLong(); - final Long chargeId = jsonObject.get("chargeId").getAsLong(); - BigDecimal amount = jsonObject.get("amount").getAsBigDecimal(); - final Charge charge = this.chargeRepository.findOneWithNotFoundDetection(chargeId); - if (!currencyCode.equals(charge.getCurrencyCode())) { - final String errorMessage = "Charge and Share Account must have the same currency."; - throw new InvalidCurrencyException("charge", "attach.to.share.account", errorMessage); - } - ShareAccountCharge updatedCharge = account.updateShareCharge(id, chargeId, amount); - updated.add(updatedCharge); - } else { - if (jsonObject.has("chargeId")) { - final Long id = jsonObject.get("chargeId").getAsLong(); - BigDecimal amount = jsonObject.get("amount").getAsBigDecimal(); - final Charge charge = this.chargeRepository.findOneWithNotFoundDetection(id); - if (!currencyCode.equals(charge.getCurrencyCode())) { - final String errorMessage = "Charge and Share Account must have the same currency."; - throw new InvalidCurrencyException("charge", "attach.to.share.account", errorMessage); - } - ChargeTimeType chargeTime = ChargeTimeType.fromInt(charge.getChargeTimeType()); - ChargeCalculationType chargeCalculation = ChargeCalculationType.fromInt(charge.getChargeCalculation()); - Boolean status = Boolean.TRUE; - ShareAccountCharge accountCharge = ShareAccountCharge.createNewWithoutShareAccount(charge, amount, chargeTime, - chargeCalculation, status); - account.addShareAccountCharge(accountCharge); - updated.add(accountCharge); - } - } - } - } - } - return updated; - } - private PeriodFrequencyType extractPeriodType(String paramName, final JsonElement element) { PeriodFrequencyType frequencyType = PeriodFrequencyType.INVALID; frequencyType = PeriodFrequencyType.fromInt(this.fromApiJsonHelper.extractIntegerWithLocaleNamed(paramName, element)); @@ -546,15 +593,33 @@ public class ShareAccountDataSerializer { baseDataValidator.reset().parameter(ShareAccountApiConstants.requesteddate_paramname).value(requestedDate).notNull(); final Long sharesRequested = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.requestedshares_paramname, element); baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(sharesRequested).notNull(); - ShareProduct shareProduct = account.getShareProduct() ; - if(sharesRequested != null) { - boolean allowed = shareProduct.isSharesAllowed(sharesRequested) ; - if(!allowed) { - baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(sharesRequested).failWithCode("differ.from.productdefinition", "Out of range");; - } + ShareProduct shareProduct = account.getShareProduct(); + if (sharesRequested != null) { + Long totalSharesAfterapproval = account.getTotalApprovedShares() + sharesRequested; + if(totalSharesAfterapproval > shareProduct.getMaximumClientShares()) { + baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(sharesRequested) + .failWithCode("exceeding.maximum.limit.defined.in.the.shareproduct", "Existing and requested shares count is more than product definition"); + } } + boolean isTransactionBeforeExistingTransactions = false ; + Set transactions = account.getShareAccountTransactions() ; + for(ShareAccountTransaction transaction: transactions) { + if(!transaction.isChargeTransaction()) { + LocalDate transactionDate = new LocalDate(transaction.getPurchasedDate()) ; + if(requestedDate.isBefore(transactionDate)) { + isTransactionBeforeExistingTransactions = true ; + break ; + } + } + } + if(isTransactionBeforeExistingTransactions) { + baseDataValidator.reset().parameter(ShareAccountApiConstants.requesteddate_paramname) + .value(requestedDate) + .failWithCodeNoParameterAddedToErrorCode("purchase.transaction.date.cannot.be.before.existing.transactions"); + } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - final BigDecimal unitPrice = shareProduct.deriveMarketPrice(requestedDate.toDate()) ; + final BigDecimal unitPrice = shareProduct.deriveMarketPrice(requestedDate.toDate()); ShareAccountTransaction purchaseTransaction = new ShareAccountTransaction(requestedDate.toDate(), sharesRequested, unitPrice); account.addAdditionalPurchasedShares(purchaseTransaction); handleAdditionalSharesChargeTransactions(account, purchaseTransaction); @@ -566,7 +631,7 @@ public class ShareAccountDataSerializer { Set charges = account.getCharges(); BigDecimal totalChargeAmount = BigDecimal.ZERO; for (ShareAccountCharge charge : charges) { - if (charge.isSharesPurchaseCharge()) { + if (charge.isActive() && charge.isSharesPurchaseCharge()) { BigDecimal amount = charge.updateChargeDetailsForAdditionalSharesRequest(purchaseTransaction.amount()); ShareAccountChargePaidBy paidBy = new ShareAccountChargePaidBy(purchaseTransaction, charge, amount); purchaseTransaction.addShareAccountChargePaidBy(paidBy); @@ -588,20 +653,21 @@ public class ShareAccountDataSerializer { final ArrayList purchasedShares = new ArrayList<>(); if (this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.requestedshares_paramname, element)) { JsonArray array = this.fromApiJsonHelper.extractJsonArrayNamed(ShareAccountApiConstants.requestedshares_paramname, element); - long totalShares = 0 ; + long totalShares = 0; for (JsonElement arrayElement : array) { final Long purchasedSharesId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.id_paramname, arrayElement); - baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(purchasedSharesId).notBlank() ; + baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(purchasedSharesId).notBlank(); ShareAccountTransaction transaction = account.retrievePurchasedShares(purchasedSharesId); if (transaction != null) { - totalShares+=transaction.getTotalShares().longValue() ; + validateTotalSubsribedShares(account, transaction, baseDataValidator) ; + totalShares += transaction.getTotalShares().longValue(); transaction.approve(); updateTotalChargeDerivedForAdditonalShares(account, transaction); } purchasedShares.add(purchasedSharesId); } - if(totalShares > 0) { - account.updateApprovedShares(new Long(totalShares)) ; + if (totalShares > 0) { + account.updateApprovedShares(new Long(totalShares)); } } if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } @@ -614,11 +680,14 @@ public class ShareAccountDataSerializer { if (paidBySet != null && !paidBySet.isEmpty()) { for (ShareAccountChargePaidBy chargePaidBy : paidBySet) { ShareAccountCharge charge = chargePaidBy.getCharge(); - if (charge.isSharesPurchaseCharge()) { + if (charge.isActive() && charge.isSharesPurchaseCharge()) { Money money = Money.of(shareAccount.getCurrency(), chargePaidBy.getAmount()); charge.updatePaidAmountBy(money); } } + if (!transaction.isChargeTransaction()) { + transaction.addAmountPaid(transaction.chargeAmount()); + } } } @@ -631,20 +700,20 @@ public class ShareAccountDataSerializer { JsonElement element = jsonCommand.parsedJson(); final ArrayList purchasedShares = new ArrayList<>(); if (this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.requestedshares_paramname, element)) { - long totalShares = 0 ; + long totalShares = 0; JsonArray array = this.fromApiJsonHelper.extractJsonArrayNamed(ShareAccountApiConstants.requestedshares_paramname, element); for (JsonElement arrayElement : array) { final Long purchasedSharesId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.id_paramname, arrayElement); ShareAccountTransaction shares = account.retrievePurchasedShares(purchasedSharesId); if (shares != null) { shares.reject(); - updateTotalChargeDerivedForAdditonalSharesReject(account, shares) ; - totalShares +=shares.getTotalShares().longValue() ; + updateTotalChargeDerivedForAdditonalSharesReject(account, shares); + totalShares += shares.getTotalShares().longValue(); } purchasedShares.add(purchasedSharesId); } - if(totalShares > 0) { - account.removePendingShares(new Long(totalShares)) ; + if (totalShares > 0) { + account.removePendingShares(new Long(totalShares)); } } actualChanges.put(ShareAccountApiConstants.requestedshares_paramname, purchasedShares); @@ -656,14 +725,17 @@ public class ShareAccountDataSerializer { if (paidBySet != null && !paidBySet.isEmpty()) { for (ShareAccountChargePaidBy chargePaidBy : paidBySet) { ShareAccountCharge charge = chargePaidBy.getCharge(); - if (charge.isSharesPurchaseCharge()) { + if (charge.isActive() && charge.isSharesPurchaseCharge()) { Money money = Money.of(shareAccount.getCurrency(), chargePaidBy.getAmount()); charge.updatePaidAmountBy(money); } } + if (!transaction.isChargeTransaction()) { + transaction.addAmountPaid(transaction.chargeAmount()); + } } } - + public Map validateAndRedeemShares(JsonCommand jsonCommand, ShareAccount account) { Map actualChanges = new HashMap<>(); if (StringUtils.isBlank(jsonCommand.json())) { throw new InvalidJsonException(); } @@ -683,19 +755,116 @@ public class ShareAccountDataSerializer { final BigDecimal unitPrice = this.fromApiJsonHelper.extractBigDecimalNamed(ShareAccountApiConstants.purchasedprice_paramname, element, locale); baseDataValidator.reset().parameter(ShareAccountApiConstants.purchasedprice_paramname).value(unitPrice).notNull().positiveAmount(); + boolean isTransactionBeforeExistingTransactions = false ; + Set transactions = account.getShareAccountTransactions() ; + for(ShareAccountTransaction transaction: transactions) { + if(!transaction.isChargeTransaction()) { + LocalDate transactionDate = new LocalDate(transaction.getPurchasedDate()) ; + if(requestedDate.isBefore(transactionDate)) { + isTransactionBeforeExistingTransactions = true ; + break ; + } + } + } + if(isTransactionBeforeExistingTransactions) { + baseDataValidator.reset().parameter(ShareAccountApiConstants.requesteddate_paramname) + .value(requestedDate) + .failWithCodeNoParameterAddedToErrorCode("redeem.transaction.date.cannot.be.before.existing.transactions"); + } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + ShareAccountTransaction transaction = ShareAccountTransaction.createRedeemTransaction(requestedDate.toDate(), sharesRequested, unitPrice); account.addAdditionalPurchasedShares(transaction); actualChanges.put(ShareAccountApiConstants.requestedshares_paramname, transaction); + validateRedeemRequest(account, transaction, baseDataValidator, dataValidationErrors) ; handleRedeemSharesChargeTransactions(account, transaction); return actualChanges; } - + + private void validateRedeemRequest(final ShareAccount account, ShareAccountTransaction redeemTransaction, + final DataValidatorBuilder baseDataValidator, final List dataValidationErrors ) { + + if (account.getTotalApprovedShares() < redeemTransaction.getTotalShares()) { + baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname) + .value(redeemTransaction.getTotalShares()) + .failWithCodeNoParameterAddedToErrorCode("cannot.be.redeemed.due.to.insufficient.shares"); + } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + + LocalDate redeemDate = new LocalDate(redeemTransaction.getPurchasedDate()); + final Integer lockinPeriod = account.getLockinPeriodFrequency(); + final PeriodFrequencyType periodType = account.getLockinPeriodFrequencyType(); + if (lockinPeriod == null && periodType == null) { return; } + Long totalSharesCanBeRedeemed = new Long(0); + Long totalSharesPurchasedBeforeRedeem = new Long(0) ; + boolean isPurchaseTransactionExist = false ; + + Set transactions = account.getShareAccountTransactions(); + for (ShareAccountTransaction transaction : transactions) { + if (transaction.isActive() && !transaction.isChargeTransaction()) { + LocalDate purchaseDate = new LocalDate(transaction.getPurchasedDate()); + LocalDate lockinDate = deriveLockinPeriodDuration(lockinPeriod, periodType, purchaseDate); + if (!lockinDate.isAfter(redeemDate)) { + if (transaction.isPurchasTransaction()) { + totalSharesCanBeRedeemed += transaction.getTotalShares(); + } else if (transaction.isRedeemTransaction()) { + totalSharesCanBeRedeemed -= transaction.getTotalShares(); + } + } + + if(!purchaseDate.isAfter(redeemDate)) { + isPurchaseTransactionExist = true ; + if (transaction.isPurchasTransaction()) { + totalSharesPurchasedBeforeRedeem += transaction.getTotalShares(); + } else if (transaction.isRedeemTransaction()) { + totalSharesPurchasedBeforeRedeem -= transaction.getTotalShares(); + } + } + } + } + if(!isPurchaseTransactionExist) { + baseDataValidator.reset().parameter(ShareAccountApiConstants.requesteddate_paramname) + .value(redeemDate) + .failWithCodeNoParameterAddedToErrorCode("no.purchase.transaction.found.before.redeem.date"); + }else if (totalSharesPurchasedBeforeRedeem < redeemTransaction.getTotalShares()) { + baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname) + .value(redeemTransaction.getTotalShares()) + .failWithCodeNoParameterAddedToErrorCode("cannot.be.redeemed.due.to.insufficient.shares.for.this.redeem.date"); + }else if (totalSharesCanBeRedeemed < redeemTransaction.getTotalShares()) { + baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname) + .value(redeemTransaction.getTotalShares()) + .failWithCodeNoParameterAddedToErrorCode("cannot.be.redeemed.due.to.lockinperiod"); + } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + private LocalDate deriveLockinPeriodDuration(final Integer lockinPeriod, final PeriodFrequencyType periodType, LocalDate purchaseDate) { + LocalDate lockinDate = null ; + switch(periodType) { + case INVALID: //It never comes in to this state. + break ; + case DAYS: + lockinDate = purchaseDate.plusDays(lockinPeriod) ; + break ; + case WEEKS: + lockinDate = purchaseDate.plusWeeks(lockinPeriod) ; + break ; + case MONTHS: + lockinDate = purchaseDate.plusMonths(lockinPeriod) ; + break ; + case YEARS: + lockinDate = purchaseDate.plusYears(lockinPeriod) ; + break ; + } + return lockinDate ; + } + private void handleRedeemSharesChargeTransactions(final ShareAccount account, final ShareAccountTransaction transaction) { Set charges = account.getCharges(); BigDecimal totalChargeAmount = BigDecimal.ZERO; for (ShareAccountCharge charge : charges) { - if (charge.isSharesRedeemCharge()) { + if (charge.isActive() && charge.isSharesRedeemCharge()) { BigDecimal amount = charge.updateChargeDetailsForAdditionalSharesRequest(transaction.amount()); ShareAccountChargePaidBy paidBy = new ShareAccountChargePaidBy(transaction, charge, amount); transaction.addShareAccountChargePaidBy(paidBy); @@ -707,16 +876,15 @@ public class ShareAccountDataSerializer { if (paidBySet != null && !paidBySet.isEmpty()) { for (ShareAccountChargePaidBy chargePaidBy : paidBySet) { ShareAccountCharge charge = chargePaidBy.getCharge(); - if (charge.isSharesRedeemCharge()) { + if (charge.isActive() && charge.isSharesRedeemCharge()) { Money money = Money.of(account.getCurrency(), chargePaidBy.getAmount()); charge.updatePaidAmountBy(money); } } } - - // transaction.adjustRedeemAmount(); + transaction.addAmountPaid(transaction.chargeAmount()); } - + public Map validateAndClose(JsonCommand jsonCommand, ShareAccount account) { Map actualChanges = new HashMap<>(); if (StringUtils.isBlank(jsonCommand.json())) { throw new InvalidJsonException(); } @@ -727,15 +895,35 @@ public class ShareAccountDataSerializer { JsonElement element = jsonCommand.parsedJson(); LocalDate closedDate = this.fromApiJsonHelper.extractLocalDateNamed(ShareAccountApiConstants.closeddate_paramname, element); baseDataValidator.reset().parameter(ShareAccountApiConstants.approveddate_paramname).value(closedDate).notNull(); + boolean isTransactionBeforeExistingTransactions = false ; + Set transactions = account.getShareAccountTransactions() ; + for(ShareAccountTransaction transaction: transactions) { + if(!transaction.isChargeTransaction()) { + LocalDate transactionDate = new LocalDate(transaction.getPurchasedDate()) ; + if(closedDate.isBefore(transactionDate)) { + isTransactionBeforeExistingTransactions = true ; + break ; + } + } + } + if(isTransactionBeforeExistingTransactions) { + baseDataValidator.reset().parameter(ShareAccountApiConstants.closeddate_paramname) + .value(closedDate) + .failWithCodeNoParameterAddedToErrorCode("share.account.cannot.be.closed.before.existing.transactions"); + } + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + AppUser approvedUser = this.platformSecurityContext.authenticatedUser(); - final BigDecimal unitPrice = account.getShareProduct().deriveMarketPrice(DateUtils.getDateOfTenant()) ; - ShareAccountTransaction transaction = ShareAccountTransaction.createRedeemTransaction(closedDate.toDate(), account.getTotalApprovedShares(), unitPrice) ; + final BigDecimal unitPrice = account.getShareProduct().deriveMarketPrice(DateUtils.getDateOfTenant()); + ShareAccountTransaction transaction = ShareAccountTransaction.createRedeemTransaction(closedDate.toDate(), + account.getTotalApprovedShares(), unitPrice); account.addAdditionalPurchasedShares(transaction); account.close(closedDate.toDate(), approvedUser); - handleRedeemSharesChargeTransactions(account, transaction) ; + handleRedeemSharesChargeTransactions(account, transaction); actualChanges.put(ShareAccountApiConstants.requestedshares_paramname, transaction); updateTotalChargeDerived(account); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + return actualChanges; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformService.java index 4f3b4010f..1f608bff4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformService.java @@ -20,9 +20,9 @@ package org.apache.fineract.portfolio.shareaccounts.service; import java.util.Collection; -import org.apache.fineract.portfolio.shareaccounts.data.PurchasedSharesData; +import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountTransactionData; public interface PurchasedSharesReadPlatformService { - public Collection retrievePurchasedShares(final Long accountId) ; + public Collection retrievePurchasedShares(final Long accountId) ; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformServiceImpl.java index 5aa7f73f4..195e6d858 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformServiceImpl.java @@ -26,7 +26,7 @@ import java.util.Collection; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.service.RoutingDataSource; -import org.apache.fineract.portfolio.shareaccounts.data.PurchasedSharesData; +import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountTransactionData; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; @@ -44,26 +44,27 @@ public class PurchasedSharesReadPlatformServiceImpl implements this.jdbcTemplate = new JdbcTemplate(dataSource) ; } @Override - public Collection retrievePurchasedShares( + public Collection retrievePurchasedShares( Long accountId) { PurchasedSharesDataRowMapper mapper = new PurchasedSharesDataRowMapper() ; - final String sql = "select " + mapper.schema() + " where saps.account_id=?"; + final String sql = "select " + mapper.schema() + " where saps.account_id=? and saps.is_active = 1"; return this.jdbcTemplate.query(sql, mapper, new Object[] { accountId}); } - private final static class PurchasedSharesDataRowMapper implements RowMapper { + private final static class PurchasedSharesDataRowMapper implements RowMapper { private final String schema ; public PurchasedSharesDataRowMapper() { StringBuffer buff = new StringBuffer() .append("saps.id, saps.account_id, saps.transaction_date, saps.total_shares, saps.unit_price, ") - .append("saps.status_enum, saps.type_enum, saps.amount, saps.charge_amount as chargeamount ") + .append("saps.status_enum, saps.type_enum, saps.amount, saps.charge_amount as chargeamount, ") + .append("saps.amount_paid as amountPaid") .append(" from m_share_account_transactions saps "); schema = buff.toString() ; } @Override - public PurchasedSharesData mapRow(ResultSet rs, int rowNum) + public ShareAccountTransactionData mapRow(ResultSet rs, int rowNum) throws SQLException { final Long id = rs.getLong("id") ; final Long accountId = rs.getLong("account_id") ; @@ -76,7 +77,9 @@ public class PurchasedSharesReadPlatformServiceImpl implements final EnumOptionData typeEnum = SharesEnumerations.purchasedSharesEnum(type) ; final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amount") ; final BigDecimal chargeAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "chargeamount") ; - return new PurchasedSharesData(id,accountId, purchasedDate, numberOfShares, purchasedPrice, statusEnum, typeEnum, amount, chargeAmount); + final BigDecimal amountPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountPaid") ; + + return new ShareAccountTransactionData(id,accountId, purchasedDate, numberOfShares, purchasedPrice, statusEnum, typeEnum, amount, chargeAmount, amountPaid); } public String schema() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountChargeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountChargeReadPlatformServiceImpl.java index b4ddd962a..66fb61eab 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountChargeReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountChargeReadPlatformServiceImpl.java @@ -80,6 +80,7 @@ public class ShareAccountChargeReadPlatformServiceImpl implements .append("sc.charge_calculation_enum as chargeCalculation, c.is_active as isActive, ") .append("c.currency_code as currencyCode, oc.name as currencyName, ") .append("oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, ") + .append("sc.charge_amount_or_percentage, ") .append("oc.internationalized_name_code as currencyNameCode from m_charge c ") .append("join m_organisation_currency oc on c.currency_code = oc.code ") .append("join m_share_account_charge sc on sc.charge_id = c.id "); @@ -132,13 +133,14 @@ public class ShareAccountChargeReadPlatformServiceImpl implements final EnumOptionData chargeCalculationType = ChargeEnumerations .chargeCalculationType(chargeCalculation); final Boolean isActive = rs.getBoolean("isActive"); - + final BigDecimal chargeamountorpercentage = rs.getBigDecimal("charge_amount_or_percentage") ; + final Collection chargeOptions = null; return new ShareAccountChargeData(id, chargeId, accountId, name, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeTimeType, chargeCalculationType, percentageOf, - amountPercentageAppliedTo, chargeOptions, isActive); + amountPercentageAppliedTo, chargeOptions, isActive, chargeamountorpercentage); } public String schema() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java index 3635aaae6..90ab5b079 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java @@ -42,9 +42,10 @@ import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; import org.apache.fineract.portfolio.products.constants.ProductsApiConstants; import org.apache.fineract.portfolio.products.data.ProductData; import org.apache.fineract.portfolio.products.service.ProductReadPlatformService; +import org.apache.fineract.portfolio.savings.DepositAccountType; import org.apache.fineract.portfolio.savings.data.SavingsAccountData; import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService; -import org.apache.fineract.portfolio.shareaccounts.data.PurchasedSharesData; +import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountTransactionData; import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountApplicationTimelineData; import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountDividendData; import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountStatusEnumData; @@ -114,7 +115,7 @@ public class ShareAccountReadPlatformServiceImpl implements ShareAccountReadPlat final Collection minimumActivePeriodFrequencyTypeOptions = this.shareProductDropdownReadPlatformService .retrieveMinimumActivePeriodFrequencyTypeOptions(); final Collection clientSavingsAccounts = this.savingsAccountReadPlatformService - .retrieveAllForLookup(clientId); + .retrieveActiveForLookup(clientId, DepositAccountType.SAVINGS_DEPOSIT, productData.getCurrency().code()); toReturn = new ShareAccountData(client.id(), client.displayName(), productData.getCurrency(), charges, marketPrice, minimumActivePeriodFrequencyTypeOptions, lockinPeriodFrequencyTypeOptions, clientSavingsAccounts, productData.getNominaltShares()); } else { @@ -143,7 +144,7 @@ public class ShareAccountReadPlatformServiceImpl implements ShareAccountReadPlat @Override public ShareAccountData retrieveOne(final Long id, final boolean includeTemplate) { Collection charges = this.shareAccountChargeReadPlatformService.retrieveAccountCharges(id, "active"); - Collection purchasedShares = this.purchasedSharesReadPlatformService.retrievePurchasedShares(id); + Collection purchasedShares = this.purchasedSharesReadPlatformService.retrievePurchasedShares(id); ShareAccountMapper mapper = new ShareAccountMapper(charges, purchasedShares); String query = "select " + mapper.schema() + "where sa.id=?"; @@ -161,8 +162,8 @@ public class ShareAccountReadPlatformServiceImpl implements ShareAccountReadPlat final Collection lockinPeriodFrequencyTypeOptions = this.shareProductDropdownReadPlatformService .retrieveLockinPeriodFrequencyTypeOptions(); final Collection minimumActivePeriodFrequencyTypeOptions = lockinPeriodFrequencyTypeOptions; - final Collection clientSavingsAccounts = this.savingsAccountReadPlatformService.retrieveAllForLookup(data - .getClientId()); + final Collection clientSavingsAccounts = this.savingsAccountReadPlatformService + .retrieveActiveForLookup(data.getClientId(), DepositAccountType.SAVINGS_DEPOSIT, productData.getCurrency().code()); Collection productOptions = service.retrieveAllForLookup(); final Collection chargeOptions = this.chargeReadPlatformService.retrieveSharesApplicableCharges(); data = ShareAccountData.template(data, productOptions, chargeOptions, clientSavingsAccounts, lockinPeriodFrequencyTypeOptions, @@ -224,11 +225,11 @@ public class ShareAccountReadPlatformServiceImpl implements ShareAccountReadPlat private final static class ShareAccountMapper implements RowMapper { private final Collection charges; - private final Collection purchasedShares; + private final Collection purchasedShares; private final String schema; - public ShareAccountMapper(final Collection charges, final Collection purchasedShares) { + public ShareAccountMapper(final Collection charges, final Collection purchasedShares) { this.charges = charges; this.purchasedShares = purchasedShares; StringBuffer buff = new StringBuffer() @@ -392,7 +393,7 @@ public class ShareAccountReadPlatformServiceImpl implements ShareAccountReadPlat final Boolean allowdividendsforinactiveclients = null; final Collection charges = null; - final Collection purchasedSharesData = new ArrayList<>(); + final Collection purchasedSharesData = new ArrayList<>(); final Integer lockinPeriod = null; final EnumOptionData lockPeriodTypeEnum = null; final Integer minimumActivePeriod = null; @@ -418,19 +419,21 @@ public class ShareAccountReadPlatformServiceImpl implements ShareAccountReadPlat } } - private final static class PurchasedSharesDataRowMapper implements RowMapper { + private final static class PurchasedSharesDataRowMapper implements RowMapper { private final String schema; public PurchasedSharesDataRowMapper() { StringBuffer buff = new StringBuffer() .append("saps.id as purchasedId, saps.account_id as accountId, saps.transaction_date as transactionDate, saps.total_shares as purchasedShares, saps.unit_price as unitPrice, ") - .append("saps.status_enum as purchaseStatus, saps.type_enum as purchaseType, saps.amount as amount, saps.charge_amount as chargeamount "); + .append("saps.status_enum as purchaseStatus, saps.type_enum as purchaseType, saps.amount as amount, saps.charge_amount as chargeamount, ") + .append("saps.amount_paid as amountPaid "); + schema = buff.toString(); } @Override - public PurchasedSharesData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException { + public ShareAccountTransactionData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException { final Long id = rs.getLong("purchasedId"); final Long accountId = rs.getLong("accountId"); final LocalDate transactionDate = new LocalDate(rs.getDate("transactionDate")); @@ -442,8 +445,9 @@ public class ShareAccountReadPlatformServiceImpl implements ShareAccountReadPlat final EnumOptionData typeEnum = SharesEnumerations.purchasedSharesEnum(type); final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "amount"); final BigDecimal chargeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "chargeamount"); - return new PurchasedSharesData(id, accountId, transactionDate, numberOfShares, purchasedPrice, statusEnum, typeEnum, amount, - chargeAmount); + final BigDecimal amountPaid = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "amountPaid"); + return new ShareAccountTransactionData(id, accountId, transactionDate, numberOfShares, purchasedPrice, statusEnum, typeEnum, amount, + chargeAmount, amountPaid); } public String schema() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java index daad3be95..e97f6e30f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java @@ -18,7 +18,9 @@ */ package org.apache.fineract.portfolio.shareaccounts.service; +import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -32,6 +34,7 @@ import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccou import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; +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.portfolio.accounts.constants.ShareAccountApiConstants; @@ -42,6 +45,8 @@ import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountChargePaid import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountRepositoryWrapper; import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountTransaction; import org.apache.fineract.portfolio.shareaccounts.serialization.ShareAccountDataSerializer; +import org.apache.fineract.portfolio.shareproducts.domain.ShareProduct; +import org.apache.fineract.portfolio.shareproducts.domain.ShareProductRepositoryWrapper; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; @@ -54,6 +59,8 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA private final ShareAccountRepositoryWrapper shareAccountRepository; + private final ShareProductRepositoryWrapper shareProductRepository ; + private final AccountNumberGenerator accountNumberGenerator; private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository; @@ -62,12 +69,14 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA @Autowired public ShareAccountWritePlatformServiceJpaRepositoryImpl(final ShareAccountDataSerializer accountDataSerializer, - final ShareAccountRepositoryWrapper shareAccountRepository, + final ShareAccountRepositoryWrapper shareAccountRepository, + final ShareProductRepositoryWrapper shareProductRepository, final AccountNumberGenerator accountNumberGenerator, final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, final JournalEntryWritePlatformService journalEntryWritePlatformService) { this.accountDataSerializer = accountDataSerializer; this.shareAccountRepository = shareAccountRepository; + this.shareProductRepository = shareProductRepository ; this.accountNumberGenerator = accountNumberGenerator; this.accountNumberFormatRepository = accountNumberFormatRepository; this.journalEntryWritePlatformService = journalEntryWritePlatformService; @@ -79,7 +88,6 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA ShareAccount account = this.accountDataSerializer.validateAndCreate(jsonCommand); this.shareAccountRepository.save(account); generateAccountNumber(account); - //this.shareProductRepository.save(account.getShareProduct()); //subscribed shares is increased journalEntryWritePlatformService.createJournalEntriesForShares(populateJournalEntries(account, account.getPendingForApprovalSharePurchaseTransactions())); return new CommandProcessingResultBuilder() // @@ -126,7 +134,16 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA final ShareAccountTransactionEnumData typeEnum = new ShareAccountTransactionEnumData(type.longValue(), null, null); transactionDto.put("status", statusEnum); transactionDto.put("type", typeEnum); - transactionDto.put("amount", transaction.amount()); + if(transaction.isPurchaseRejectedTransaction() || transaction.isRedeemTransaction()) { + BigDecimal amount = transaction.amount() ; + if(transaction.chargeAmount() != null) { + amount = amount.add(transaction.chargeAmount()) ; + } + transactionDto.put("amount", amount); + }else { + transactionDto.put("amount", transaction.amount()); + } + transactionDto.put("chargeAmount", transaction.chargeAmount()); transactionDto.put("paymentTypeId", null); // FIXME::make it cash // payment @@ -147,14 +164,24 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA return accountingBridgeData; } + @SuppressWarnings("unchecked") @Override public CommandProcessingResult updateShareAccount(Long accountId, JsonCommand jsonCommand) { try { + Date transactionDate = DateUtils.getDateOfTenant(); ShareAccount account = this.shareAccountRepository.findOneWithNotFoundDetection(accountId); Map changes = this.accountDataSerializer.validateAndUpdate(jsonCommand, account); if (!changes.isEmpty()) { this.shareAccountRepository.save(account); } + // since we are reverting all journal entries we need to add journal + // entries for application request + if (changes.containsKey("reversalIds")) { + ArrayList reversalIds = (ArrayList) changes.get("reversalIds"); + this.journalEntryWritePlatformService.revertShareAccountJournalEntries(reversalIds, transactionDate); + journalEntryWritePlatformService.createJournalEntriesForShares(populateJournalEntries(account, + account.getPendingForApprovalSharePurchaseTransactions())); + } return new CommandProcessingResultBuilder() // .withCommandId(jsonCommand.commandId()) // .withEntityId(accountId) // @@ -205,8 +232,21 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA if (!changes.isEmpty()) { this.shareAccountRepository.save(account); } - this.journalEntryWritePlatformService.createJournalEntriesForShares(populateJournalEntries(account, - account.getShareAccountTransactions())); + Set transactions = account.getShareAccountTransactions(); + Set journalTransactions = new HashSet<>(); + Long totalSubsribedShares = new Long(0) ; + + for (ShareAccountTransaction transaction : transactions) { + if (transaction.isActive() && transaction.isPurchasTransaction()) { + journalTransactions.add(transaction); + totalSubsribedShares += transaction.getTotalShares() ; + } + } + ShareProduct shareProduct = account.getShareProduct() ; + shareProduct.addSubscribedShares(totalSubsribedShares); + this.shareProductRepository.save(shareProduct); + + this.journalEntryWritePlatformService.createJournalEntriesForShares(populateJournalEntries(account, journalTransactions)); return new CommandProcessingResultBuilder() // .withCommandId(jsonCommand.commandId()) // .withEntityId(accountId) // @@ -226,8 +266,14 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA if (!changes.isEmpty()) { this.shareAccountRepository.save(account); } - this.journalEntryWritePlatformService.createJournalEntriesForShares(populateJournalEntries(account, - account.getShareAccountTransactions())); + Set transactions = account.getShareAccountTransactions(); + Set journalTransactions = new HashSet<>(); + for (ShareAccountTransaction transaction : transactions) { + if (transaction.isActive() && !transaction.isChargeTransaction()) { + journalTransactions.add(transaction); + } + } + this.journalEntryWritePlatformService.createJournalEntriesForShares(populateJournalEntries(account, journalTransactions)); return new CommandProcessingResultBuilder() // .withCommandId(jsonCommand.commandId()) // .withEntityId(accountId) // @@ -247,11 +293,18 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA if (!changes.isEmpty()) { this.shareAccountRepository.save(account); } - /* - * this.journalEntryWritePlatformService.createJournalEntriesForShares - * (populateJournalEntries(account, - * account.getShareAccountTransactions())); - */ + + Set transactions = account.getShareAccountTransactions() ; + ArrayList journalEntryTransactions = new ArrayList<>() ; + for(ShareAccountTransaction transaction: transactions) { + if(transaction.isActive() && !transaction.isChargeTransaction()){ + journalEntryTransactions.add(transaction.getId()) ; + } + } + Date transactionDate = DateUtils.getDateOfTenant(); + this.journalEntryWritePlatformService.revertShareAccountJournalEntries(journalEntryTransactions, transactionDate); + journalEntryWritePlatformService.createJournalEntriesForShares(populateJournalEntries(account, + account.getPendingForApprovalSharePurchaseTransactions())); return new CommandProcessingResultBuilder() // .withCommandId(jsonCommand.commandId()) // .withEntityId(accountId) // @@ -285,6 +338,7 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA } } + @SuppressWarnings("unchecked") @Override public CommandProcessingResult approveAdditionalShares(Long accountId, JsonCommand jsonCommand) { @@ -294,14 +348,21 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA if (!changes.isEmpty()) { this.shareAccountRepository.save(account); ArrayList transactionIds = (ArrayList) changes.get(ShareAccountApiConstants.requestedshares_paramname); + Long totalSubscribedShares = new Long(0) ; if (transactionIds != null) { Set transactions = new HashSet<>(); for (Long id : transactionIds) { ShareAccountTransaction transaction = account.retrievePurchasedShares(id); transactions.add(transaction); + totalSubscribedShares += transaction.getTotalShares() ; } this.journalEntryWritePlatformService.createJournalEntriesForShares(populateJournalEntries(account, transactions)); } + if(!totalSubscribedShares.equals(new Long(0))) { + ShareProduct shareProduct = account.getShareProduct() ; + shareProduct.addSubscribedShares(totalSubscribedShares); + this.shareProductRepository.save(shareProduct); + } } return new CommandProcessingResultBuilder() // .withCommandId(jsonCommand.commandId()) // @@ -314,9 +375,9 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA } } + @SuppressWarnings("unchecked") @Override public CommandProcessingResult rejectAdditionalShares(Long accountId, JsonCommand jsonCommand) { - try { ShareAccount account = this.shareAccountRepository.findOneWithNotFoundDetection(accountId); Map changes = this.accountDataSerializer.validateAndRejectAddtionalShares(jsonCommand, account); @@ -352,7 +413,21 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA this.shareAccountRepository.save(account); ShareAccountTransaction transaction = (ShareAccountTransaction) changes .get(ShareAccountApiConstants.requestedshares_paramname); - transaction = account.getShareAccountTransaction(transaction); + transaction = account.getShareAccountTransaction(transaction); // after + // saving, + // entity + // will + // have + // different + // object. + // So + // need + // to + // retrieve + // to + // get + // the + // id Set transactions = new HashSet<>(); transactions.add(transaction); this.journalEntryWritePlatformService.createJournalEntriesForShares(populateJournalEntries(account, transactions)); @@ -398,6 +473,7 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA return CommandProcessingResult.empty(); } } + private void handleDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) { } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/domain/ShareProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/domain/ShareProduct.java index 4fee7efa0..6cf301e85 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/domain/ShareProduct.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/domain/ShareProduct.java @@ -226,7 +226,10 @@ public class ShareProduct extends AbstractAuditableCustom { public boolean setTotalIssuedShares(Long totalSharesIssued) { boolean returnValue = false; - if (!this.totalSharesIssued.equals(totalSharesIssued)) { + if(this.totalSharesIssued == null) { + this.totalSharesIssued = totalSharesIssued ; + returnValue = true ; + }else if (!this.totalSharesIssued.equals(totalSharesIssued)) { this.totalSharesIssued = totalSharesIssued; returnValue = true; } @@ -430,5 +433,16 @@ public class ShareProduct extends AbstractAuditableCustom { public Long getSubscribedShares() { return this.totalSubscribedShares ; } + + public Long getMinimumClientShares() { + return this.minimumShares ; + } + public Long getMaximumClientShares() { + return this.maximumShares ; + } + + public Long getDefaultClientShares() { + return this.nominalShares ; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/exception/ShareAccountsNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/exception/ShareAccountsNotFoundException.java new file mode 100644 index 000000000..71b59bc86 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/exception/ShareAccountsNotFoundException.java @@ -0,0 +1,29 @@ +/** + * 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.shareproducts.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + + +public class ShareAccountsNotFoundException extends AbstractPlatformResourceNotFoundException{ + + public ShareAccountsNotFoundException(final Long productId) { + super("error.msg.shareproduct.shareaccounts.not.defined", " No Share Accounts found for this prodct " + productId, productId); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/serialization/ShareProductDataSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/serialization/ShareProductDataSerializer.java index 345294b2c..02c08d975 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/serialization/ShareProductDataSerializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/serialization/ShareProductDataSerializer.java @@ -39,6 +39,7 @@ import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.portfolio.accounts.constants.ShareAccountApiConstants; import org.apache.fineract.portfolio.charge.domain.Charge; import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; @@ -102,14 +103,18 @@ public class ShareProductDataSerializer { Long totalNumberOfShares = this.fromApiJsonHelper.extractLongNamed(ShareProductApiConstants.totalshares_paramname, element); baseDataValidator.reset().parameter(ShareProductApiConstants.totalshares_paramname).value(totalNumberOfShares).notNull() .longGreaterThanZero(); - + final Long sharesIssued = this.fromApiJsonHelper.extractLongNamed(ShareProductApiConstants.totalsharesissued_paramname, element); + if(sharesIssued != null && totalNumberOfShares != null && sharesIssued > totalNumberOfShares) { + baseDataValidator.reset().parameter(ShareProductApiConstants.totalsharesissued_paramname).value(sharesIssued) + .failWithCodeNoParameterAddedToErrorCode("sharesIssued.cannot.be.greater.than.totalNumberOfShares"); + } final String currencyCode = this.fromApiJsonHelper.extractStringNamed(ShareProductApiConstants.currency_paramname, element); final Integer digitsAfterDecimal = this.fromApiJsonHelper.extractIntegerWithLocaleNamed( ShareProductApiConstants.digitsafterdecimal_paramname, element); final Integer inMultiplesOf = this.fromApiJsonHelper.extractIntegerWithLocaleNamed( ShareProductApiConstants.inmultiplesof_paramname, element); final MonetaryCurrency currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal, inMultiplesOf); - final Long sharesIssued = this.fromApiJsonHelper.extractLongNamed(ShareProductApiConstants.totalsharesissued_paramname, element); + final BigDecimal unitPrice = this.fromApiJsonHelper.extractBigDecimalNamed(ShareProductApiConstants.unitprice_paramname, element, locale); baseDataValidator.reset().parameter(ShareProductApiConstants.unitprice_paramname).value(unitPrice).notNull().positiveAmount(); @@ -132,12 +137,12 @@ public class ShareProductDataSerializer { Long nominalClientShares = this.fromApiJsonHelper.extractLongNamed(ShareProductApiConstants.nominaltshares_paramname, element); baseDataValidator.reset().parameter(ShareProductApiConstants.nominaltshares_paramname).value(nominalClientShares).notNull() .longGreaterThanZero(); - if (minimumClientShares != null && nominalClientShares != null) { + if (minimumClientShares != null && nominalClientShares != null && !minimumClientShares.equals(nominalClientShares)) { baseDataValidator.reset().parameter(ShareProductApiConstants.nominaltshares_paramname).value(nominalClientShares) .longGreaterThanNumber(minimumClientShares); } Long maximumClientShares = this.fromApiJsonHelper.extractLongNamed(ShareProductApiConstants.maximumshares_paramname, element); - if (maximumClientShares != null && nominalClientShares != null) { + if (maximumClientShares != null && nominalClientShares != null && !maximumClientShares.equals(nominalClientShares)) { baseDataValidator.reset().parameter(ShareProductApiConstants.maximumshares_paramname).value(maximumClientShares) .longGreaterThanNumber(nominalClientShares); } @@ -294,8 +299,16 @@ public class ShareProductDataSerializer { if (product.setTotalIssuedShares(sharesIssued)) { actualChanges.put(ShareProductApiConstants.totalsharesissued_paramname, sharesIssued); } + }else { + product.setTotalIssuedShares(sharesIssued) ; } - + + if(sharesIssued != null && product.getSubscribedShares() != null && sharesIssued < product.getSubscribedShares()) { + baseDataValidator.reset().parameter(ShareProductApiConstants.totalsharesissued_paramname).value(sharesIssued) + .failWithCodeNoParameterAddedToErrorCode("sharesissued.can.not.be.lessthan.accounts.subscribed.shares"); + } + + if (this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.currency_paramname, element) && this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.digitsafterdecimal_paramname, element) && this.fromApiJsonHelper.parameterExists(ShareProductApiConstants.inmultiplesof_paramname, element)) { @@ -416,17 +429,17 @@ public class ShareProductDataSerializer { } } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + BigDecimal shareCapitalValue; if (sharesIssued != null || unitPrice != null) { - if (sharesIssued == null) sharesIssued = product.getSharesIssued(); + if (sharesIssued == null) sharesIssued = product.getTotalShares(); if (unitPrice == null) unitPrice = product.getUnitPrice(); shareCapitalValue = BigDecimal.valueOf(sharesIssued).multiply(unitPrice); if (product.setshareCapitalValue(shareCapitalValue)) { actualChanges.put(ShareProductApiConstants.sharecapital_paramname, shareCapitalValue); } } - - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } return actualChanges; } @@ -455,6 +468,7 @@ public class ShareProductDataSerializer { ShareProductApiConstants.dividendAmountParamName, element); baseDataValidator.reset().parameter(ShareProductApiConstants.dividendAmountParamName).value(dividendAmount).notBlank() .positiveAmount(); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java index 1ff71c7de..12ac51e30 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductDividendAssembler.java @@ -26,13 +26,14 @@ import java.util.Map; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.products.service.ProductReadPlatformService; -import org.apache.fineract.portfolio.shareaccounts.data.PurchasedSharesData; +import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountTransactionData; import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountData; import org.apache.fineract.portfolio.shareaccounts.domain.PurchasedSharesStatusType; import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountDividendDetails; import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountReadPlatformService; import org.apache.fineract.portfolio.shareproducts.data.ShareProductData; import org.apache.fineract.portfolio.shareproducts.domain.ShareProductDividendPayOutDetails; +import org.apache.fineract.portfolio.shareproducts.exception.ShareAccountsNotFoundException; import org.joda.time.Days; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; @@ -59,8 +60,15 @@ public class ShareProductDividendAssembler { .getCurrency().currencyInMultiplesOf()); Collection shareAccountDatas = this.ShareAccountReadPlatformService.retrieveAllShareAccountDataForDividends( productId, product.getAllowDividendCalculationForInactiveClients(), dividendPeriodStartDate); + if(shareAccountDatas == null || shareAccountDatas.isEmpty()) { + throw new ShareAccountsNotFoundException(product.getId()) ; + } + ShareProductDividendPayOutDetails productDividendPayOutDetails = null; - final int minimumActivePeriod = product.getMinimumActivePeriod(); + int minimumActivePeriod = 0 ; + if(product.getMinimumActivePeriod() != null) { //minimum active period may be null + minimumActivePeriod = product.getMinimumActivePeriod(); + } final Map numberOfSharesdaysPerAccount = new HashMap<>(); long numberOfShareDays = calculateNumberOfShareDays(dividendPeriodEndDate, dividendPeriodStartDate, minimumActivePeriod, shareAccountDatas, numberOfSharesdaysPerAccount); @@ -88,10 +96,10 @@ public class ShareProductDividendAssembler { long numberOfShareDays = 0; for (ShareAccountData accountData : shareAccountDatas) { long numberOfShareDaysPerAccount = 0; - Collection purchasedShares = accountData.getPurchasedShares(); + Collection purchasedShares = accountData.getPurchasedShares(); long numberOfShares = 0; LocalDate lastDividendAppliedDate = null; - for (PurchasedSharesData purchasedSharesData : purchasedShares) { + for (ShareAccountTransactionData purchasedSharesData : purchasedShares) { final PurchasedSharesStatusType status = PurchasedSharesStatusType.fromInt(purchasedSharesData.getStatus().getId() .intValue()); final PurchasedSharesStatusType type = PurchasedSharesStatusType.fromInt(purchasedSharesData.getType().getId() diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductReadPlatformServiceImpl.java index cd3b42e55..64dc94edc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductReadPlatformServiceImpl.java @@ -243,7 +243,7 @@ public class ShareProductReadPlatformServiceImpl implements ProductReadPlatformS currencyDisplaySymbol, currencyNameCode); final Long totalShares = rs.getLong("total_shares"); - final Long issuedShares = rs.getLong("issued_shares"); + final Long issuedShares = JdbcSupport.getLongDefaultToNullIfZero(rs, "issued_shares"); final BigDecimal unitPrice = rs.getBigDecimal("unit_price"); final BigDecimal capitalAmount = rs.getBigDecimal("capital_amount"); final Long minimumClientShares = JdbcSupport.getLong(rs, "minimum_client_shares"); diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V299__share_products.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V299__share_products.sql index ec3a27c30..509d1679d 100644 --- a/fineract-provider/src/main/resources/sql/migrations/core_db/V299__share_products.sql +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V299__share_products.sql @@ -129,16 +129,18 @@ CREATE TABLE `m_share_account_transactions` ( `unit_price` DECIMAL(10,2) NULL DEFAULT NULL, `amount` DECIMAL(20,2) NULL DEFAULT NULL, `charge_amount` DECIMAL(20,2) NULL DEFAULT NULL, +`amount_paid` DECIMAL(20,2) NULL DEFAULT NULL, `status_enum` SMALLINT(5) NOT NULL DEFAULT '300', `type_enum` SMALLINT(5) NULL DEFAULT NULL, +`is_active` TINYINT(1) NOT NULL DEFAULT '1', PRIMARY KEY (`id`), CONSTRAINT `m_share_account_purchased_shares_ibfk_1` FOREIGN KEY (`account_id`) REFERENCES `m_share_account` (`id`) ) ; CREATE TABLE `m_share_account_charge_paid_by` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT, -`share_transaction_id` BIGINT(20) NOT NULL, -`charge_transaction_id` BIGINT(20) NOT NULL, +`share_transaction_id` BIGINT(20) NULL DEFAULT NULL, +`charge_transaction_id` BIGINT(20) NULL DEFAULT NULL, `amount` DECIMAL(20,2) NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `m_share_account_transactions_charge_mapping_ibfk1` FOREIGN KEY (`share_transaction_id`) REFERENCES `m_share_account_transactions` (`id`),