Issue fixes on shares and dividends reported by QA

This commit is contained in:
Nazeer Hussain Shaik
2016-04-19 17:55:23 +05:30
parent fe6e1e88fe
commit 60588a78e0
27 changed files with 763 additions and 256 deletions
@@ -39,4 +39,8 @@ public interface JournalEntryRepository extends JpaRepository<JournalEntry, Long
@Query("from JournalEntry journalEntry where journalEntry.entityId= :entityId and journalEntry.entityType = :entityType")
List<JournalEntry> 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<JournalEntry> findJournalEntries(@Param("transactionId") String transactionId, @Param("entityType") Integer entityType) ;
}
@@ -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<ChargePaymentDTO> chargePaymentDTOs) {
final Map<GLAccount, BigDecimal> 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<GLAccount, BigDecimal> 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;
@@ -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,
@@ -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<String, Object> accountingBridgeData);
void revertShareAccountJournalEntries(final ArrayList<Long> transactionId, final Date transactionDate);
}
@@ -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<Long> transactionIds, final Date transactionDate) {
for (Long shareTransactionId : transactionIds) {
String transactionId = AccountingProcessorHelper.SHARE_TRANSACTION_IDENTIFIER + shareTransactionId.longValue();
List<JournalEntry> 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();
@@ -290,9 +290,11 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
final Collection<ChargeData> 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() {
@@ -34,6 +34,8 @@ public interface SavingsAccountReadPlatformService {
Collection<SavingsAccountData> retrieveActiveForLookup(Long clientId, DepositAccountType depositAccountType);
public Collection<SavingsAccountData> retrieveActiveForLookup(final Long clientId, DepositAccountType depositAccountType, String currencyCode) ;
SavingsAccountData retrieveOne(Long savingsId);
SavingsAccountData retrieveTemplate(Long clientId, Long groupId, Long productId, boolean staffInSelectedOfficeOnly);
@@ -132,6 +132,15 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
return this.jdbcTemplate.query(sqlBuilder.toString(), this.savingAccountMapper, queryParameters);
}
@Override
public Collection<SavingsAccountData> 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<SavingsAccountData> retrieveAll(final SearchParameters searchParameters) {
@@ -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<ChargeData> chargeOptions, final Boolean isActive) {
final Collection<ChargeData> 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;
}
@@ -61,7 +61,7 @@ public class ShareAccountData implements AccountData {
private ShareAccountSummaryData summary;
private Collection<PurchasedSharesData> purchasedShares;
private Collection<ShareAccountTransactionData> 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<ShareAccountChargeData> charges,
final Collection<PurchasedSharesData> purchasedSharesData, final Integer lockinPeriod, final EnumOptionData lockPeriodTypeEnum,
final Collection<ShareAccountTransactionData> 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<ShareAccountChargeData> charges,
final Collection<PurchasedSharesData> purchasedSharesData, final Integer lockinPeriod, final EnumOptionData lockPeriodTypeEnum,
final Collection<ShareAccountTransactionData> purchasedSharesData, final Integer lockinPeriod, final EnumOptionData lockPeriodTypeEnum,
final Integer minimumActivePeriod, final EnumOptionData minimumActivePeriodTypeEnum,
final Boolean allowDividendCalculationForInactiveClients, final Collection<ProductData> productOptions,
final Collection<ChargeData> chargeOptions, final Collection<SavingsAccountData> 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> purchasedSharesData = null;
Collection<ShareAccountTransactionData> 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<PurchasedSharesData> getPurchasedShares() {
public Collection<ShareAccountTransactionData> getPurchasedShares() {
return this.purchasedShares;
}
@@ -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() {
@@ -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<Long> {
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<Long> {
public Set<ShareAccountTransaction> getChargeTransactions() {
Set<ShareAccountTransaction> 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<Long> {
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<Long> {
this.closedDate = null;
this.closedBy = null;
this.totalSharesApproved = null;
Iterator<ShareAccountTransaction> 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<Long> {
this.status = ShareAccountStatusType.REJECTED.getValue();
this.totalSharesPending = null;
this.totalSharesApproved = null;
Iterator<ShareAccountTransaction> 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<Long> {
public Set<ShareAccountTransaction> getPendingForApprovalSharePurchaseTransactions() {
Set<ShareAccountTransaction> 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<Long> {
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<ShareAccountCharge> charges) {
this.charges.addAll(charges) ;
}
public Integer getLockinPeriodFrequency() {
return this.lockinPeriodFrequency ;
}
public PeriodFrequencyType getLockinPeriodFrequencyType() {
return this.lockinPeriodFrequencyType ;
}
}
@@ -83,7 +83,7 @@ public class ShareAccountCharge extends AbstractPersistable<Long> {
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<Long> {
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<Long> {
}
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<Long> {
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<Long> {
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<Long> {
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<Long> {
}
public boolean isActive() {
return this.status;
return this.active;
}
public boolean isNotActive() {
@@ -420,6 +412,10 @@ public class ShareAccountCharge extends AbstractPersistable<Long> {
return toReturnAmount;
}
public void setActive(boolean active) {
this.active = active ;
}
@Override
public boolean equals(final Object obj) {
if (obj == null) { return false; }
@@ -74,4 +74,9 @@ public class ShareAccountChargePaidBy extends AbstractPersistable<Long>{
public Long getShareChargeId() {
return this.shareAccountCharge.getId() ;
}
public void reset() {
this.shareAccountTransaction = null ;
this.shareAccountCharge = null ;
}
}
@@ -58,8 +58,11 @@ public class ShareAccountTransaction extends AbstractPersistable<Long> {
@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<Long> {
@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<ShareAccountChargePaidBy> shareAccountChargesPaid = new HashSet<>();
@@ -86,24 +92,27 @@ public class ShareAccountTransaction extends AbstractPersistable<Long> {
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<Long> {
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<Long> {
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<Long> {
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<Long> {
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<Long> {
}
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<ShareAccountChargePaidBy> getChargesPaidBy() {
@@ -201,9 +216,32 @@ public class ShareAccountTransaction extends AbstractPersistable<Long> {
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 ;
}
}
@@ -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<ShareAccountTransaction> 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<ShareAccountCharge> 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<ShareAccountTransaction> 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<ApiParameterError> 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<ShareAccountTransaction> transactions = account.getShareAccountTransactions();
List<Long> 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<ShareAccountCharge> updatedCharges = assembleListOfAccountChargesforUpdate(account, element, shareProduct.getCurrency()
.getCode());
if (!updatedCharges.isEmpty()) {
actualChanges.put(ShareAccountApiConstants.charges_paramname, new HashSet<ShareAccountCharge>());
final MonetaryCurrency currency = shareProduct.getCurrency();
Set<ShareAccountCharge> 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<ShareAccountCharge>());
}
}
}
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<ShareAccountTransaction> 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<ShareAccountCharge> charges = shareAccount.getCharges() ;
Set<ShareAccountTransaction> transactions = shareAccount.getShareAccountTransactions();
for (ShareAccountTransaction transaction : transactions) {
Set<ShareAccountChargePaidBy> 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<ShareAccountChargePaidBy> 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<String, Object> validateAndUndoApprove(JsonCommand jsonCommand, ShareAccount account) {
Map<String, Object> actualChanges = new HashMap<>();
if (StringUtils.isBlank(jsonCommand.json())) { throw new InvalidJsonException(); }
//final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
//this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, jsonCommand.json(), ShareAccountApiConstants.activateParameters);
//final List<ApiParameterError> 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<Map<String, Object>>()
// {}.getType();
// this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap,
// jsonCommand.json(), ShareAccountApiConstants.activateParameters);
// final List<ApiParameterError> 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<ShareAccountTransaction> 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<ShareAccountCharge> charges = shareAccount.getCharges() ;
for(ShareAccountCharge charge: charges) {
if(charge.isActive() && charge.isSharesPurchaseCharge()) {
charge.update(transactionAmount, charge.percentageOrAmount()) ;
charge.deriveChargeAmount(transactionAmount);
}
}
Set<ShareAccountTransaction> transactions = shareAccount.getShareAccountTransactions();
for (ShareAccountTransaction transaction : transactions) {
if (transaction.isActive()) {
if (!transaction.isChargeTransaction()) {
transaction.resetAmountPaid();
}
}
}
}
@SuppressWarnings("unused")
public Map<String, Object> validateAndReject(JsonCommand jsonCommand, ShareAccount account) {
Map<String, Object> actualChanges = new HashMap<>();
@@ -449,10 +532,17 @@ public class ShareAccountDataSerializer {
private void handlechargesOnActivation(final ShareAccount account) {
Set<ShareAccountCharge> charges = account.getCharges();
for (ShareAccountCharge charge : charges) {
if (charge.isShareAccountActivation()) {
if (charge.isActive() && charge.isShareAccountActivation()) {
charge.markAsFullyPaid();
}
}
Set<ShareAccountTransaction> transactions = account.getChargeTransactions();
for (ShareAccountTransaction transaction : transactions) {
if (transaction.isChargeTransaction()) {
transaction.updateAmountPaid(transaction.amount());
}
}
}
private Set<ShareAccountCharge> assembleListOfAccountCharges(final JsonElement element, final String currencyCode) {
@@ -484,49 +574,6 @@ public class ShareAccountDataSerializer {
return charges;
}
private Set<ShareAccountCharge> assembleListOfAccountChargesforUpdate(final ShareAccount account, final JsonElement element,
final String currencyCode) {
Set<ShareAccountCharge> 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<ShareAccountTransaction> 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<ShareAccountCharge> 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<Long> 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<Long> 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<String, Object> validateAndRedeemShares(JsonCommand jsonCommand, ShareAccount account) {
Map<String, Object> 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<ShareAccountTransaction> 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<ApiParameterError> 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<ShareAccountTransaction> 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<ShareAccountCharge> 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<String, Object> validateAndClose(JsonCommand jsonCommand, ShareAccount account) {
Map<String, Object> 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<ShareAccountTransaction> 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;
}
}
@@ -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<PurchasedSharesData> retrievePurchasedShares(final Long accountId) ;
public Collection<ShareAccountTransactionData> retrievePurchasedShares(final Long accountId) ;
}
@@ -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<PurchasedSharesData> retrievePurchasedShares(
public Collection<ShareAccountTransactionData> 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<PurchasedSharesData> {
private final static class PurchasedSharesDataRowMapper implements RowMapper<ShareAccountTransactionData> {
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() {
@@ -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<ChargeData> 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() {
@@ -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<EnumOptionData> minimumActivePeriodFrequencyTypeOptions = this.shareProductDropdownReadPlatformService
.retrieveMinimumActivePeriodFrequencyTypeOptions();
final Collection<SavingsAccountData> 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<ShareAccountChargeData> charges = this.shareAccountChargeReadPlatformService.retrieveAccountCharges(id, "active");
Collection<PurchasedSharesData> purchasedShares = this.purchasedSharesReadPlatformService.retrievePurchasedShares(id);
Collection<ShareAccountTransactionData> 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<EnumOptionData> lockinPeriodFrequencyTypeOptions = this.shareProductDropdownReadPlatformService
.retrieveLockinPeriodFrequencyTypeOptions();
final Collection<EnumOptionData> minimumActivePeriodFrequencyTypeOptions = lockinPeriodFrequencyTypeOptions;
final Collection<SavingsAccountData> clientSavingsAccounts = this.savingsAccountReadPlatformService.retrieveAllForLookup(data
.getClientId());
final Collection<SavingsAccountData> clientSavingsAccounts = this.savingsAccountReadPlatformService
.retrieveActiveForLookup(data.getClientId(), DepositAccountType.SAVINGS_DEPOSIT, productData.getCurrency().code());
Collection<ProductData> productOptions = service.retrieveAllForLookup();
final Collection<ChargeData> 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<ShareAccountData> {
private final Collection<ShareAccountChargeData> charges;
private final Collection<PurchasedSharesData> purchasedShares;
private final Collection<ShareAccountTransactionData> purchasedShares;
private final String schema;
public ShareAccountMapper(final Collection<ShareAccountChargeData> charges, final Collection<PurchasedSharesData> purchasedShares) {
public ShareAccountMapper(final Collection<ShareAccountChargeData> charges, final Collection<ShareAccountTransactionData> 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<ShareAccountChargeData> charges = null;
final Collection<PurchasedSharesData> purchasedSharesData = new ArrayList<>();
final Collection<ShareAccountTransactionData> 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<PurchasedSharesData> {
private final static class PurchasedSharesDataRowMapper implements RowMapper<ShareAccountTransactionData> {
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() {
@@ -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<String, Object> 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<Long> reversalIds = (ArrayList<Long>) 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<ShareAccountTransaction> transactions = account.getShareAccountTransactions();
Set<ShareAccountTransaction> 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<ShareAccountTransaction> transactions = account.getShareAccountTransactions();
Set<ShareAccountTransaction> 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<ShareAccountTransaction> transactions = account.getShareAccountTransactions() ;
ArrayList<Long> 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<Long> transactionIds = (ArrayList<Long>) changes.get(ShareAccountApiConstants.requestedshares_paramname);
Long totalSubscribedShares = new Long(0) ;
if (transactionIds != null) {
Set<ShareAccountTransaction> 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<String, Object> 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<ShareAccountTransaction> 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) {
}
@@ -226,7 +226,10 @@ public class ShareProduct extends AbstractAuditableCustom<AppUser, Long> {
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<AppUser, Long> {
public Long getSubscribedShares() {
return this.totalSubscribedShares ;
}
public Long getMinimumClientShares() {
return this.minimumShares ;
}
public Long getMaximumClientShares() {
return this.maximumShares ;
}
public Long getDefaultClientShares() {
return this.nominalShares ;
}
}
@@ -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);
}
}
@@ -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); }
}
}
@@ -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<ShareAccountData> 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<Long, Long> 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<PurchasedSharesData> purchasedShares = accountData.getPurchasedShares();
Collection<ShareAccountTransactionData> 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()
@@ -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");
@@ -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`),