http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/60588a78/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java index 13c7b4d..1e63050 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java @@ -49,7 +49,6 @@ import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.loanproduct.exception.InvalidCurrencyException; -import org.apache.fineract.portfolio.savings.SavingsApiConstants; import org.apache.fineract.portfolio.savings.domain.SavingsAccount; import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper; import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccount; @@ -87,9 +86,8 @@ public class ShareAccountDataSerializer { @Autowired public ShareAccountDataSerializer(final PlatformSecurityContext platformSecurityContext, final FromJsonHelper fromApiJsonHelper, - final ChargeRepositoryWrapper chargeRepository, - final SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper, final ClientRepositoryWrapper clientRepositoryWrapper, - final ShareProductRepositoryWrapper shareProductRepository) { + final ChargeRepositoryWrapper chargeRepository, final SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper, + final ClientRepositoryWrapper clientRepositoryWrapper, final ShareProductRepositoryWrapper shareProductRepository) { this.platformSecurityContext = platformSecurityContext; this.fromApiJsonHelper = fromApiJsonHelper; this.chargeRepository = chargeRepository; @@ -117,7 +115,7 @@ public class ShareAccountDataSerializer { baseDataValidator.reset().parameter(ShareAccountApiConstants.submitteddate_paramname).value(submittedDate).notNull(); final String externalId = this.fromApiJsonHelper.extractStringNamed(ShareAccountApiConstants.externalid_paramname, element); - baseDataValidator.reset().parameter(ShareAccountApiConstants.externalid_paramname).value(externalId).notNull(); + // baseDataValidator.reset().parameter(ShareAccountApiConstants.externalid_paramname).value(externalId).notNull(); Long savingsAccountId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.savingsaccountid_paramname, element); baseDataValidator.reset().parameter(ShareAccountApiConstants.savingsaccountid_paramname).value(savingsAccountId).notNull() @@ -126,25 +124,16 @@ public class ShareAccountDataSerializer { final Long requestedShares = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.requestedshares_paramname, element); baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(requestedShares).notNull() .longGreaterThanZero(); - boolean allowed = shareProduct.isSharesAllowed(requestedShares) ; - if(!allowed) { - baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(requestedShares).failWithCode("differ.from.productdefinition", "Out of range");; + if(requestedShares < shareProduct.getMinimumClientShares()) { + baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(requestedShares) + .failWithCode("client.can.not.purchase.shares.lessthan.product.definition", "Client can not purchase shares less than product definition"); } - /*Long subscribedShares = shareProduct.getSubscribedShares() ; - if(subscribedShares == null) subscribedShares = new Long(0) ; - Long totalShares = shareProduct.getTotalShares() ; - Long issuedShares = shareProduct.getSharesIssued() ; - if(issuedShares == null) issuedShares = totalShares ; - if((subscribedShares+requestedShares) > issuedShares) { - throw new IssueableSharesExceededException() ; - } - shareProduct.addSubscribedShares(requestedShares);*/ + 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"); + } - /*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; } }
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/60588a78/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformService.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformService.java index 4f3b401..1f608bf 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformService.java @@ -20,9 +20,9 @@ package org.apache.fineract.portfolio.shareaccounts.service; import java.util.Collection; -import org.apache.fineract.portfolio.shareaccounts.data.PurchasedSharesData; +import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountTransactionData; public interface PurchasedSharesReadPlatformService { - public Collection<PurchasedSharesData> retrievePurchasedShares(final Long accountId) ; + public Collection<ShareAccountTransactionData> retrievePurchasedShares(final Long accountId) ; } http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/60588a78/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformServiceImpl.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformServiceImpl.java index 5aa7f73..195e6d8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/PurchasedSharesReadPlatformServiceImpl.java @@ -26,7 +26,7 @@ import java.util.Collection; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.service.RoutingDataSource; -import org.apache.fineract.portfolio.shareaccounts.data.PurchasedSharesData; +import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountTransactionData; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; @@ -44,26 +44,27 @@ public class PurchasedSharesReadPlatformServiceImpl implements this.jdbcTemplate = new JdbcTemplate(dataSource) ; } @Override - public Collection<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() { http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/60588a78/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountChargeReadPlatformServiceImpl.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountChargeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountChargeReadPlatformServiceImpl.java index b4ddd96..66fb61e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountChargeReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountChargeReadPlatformServiceImpl.java @@ -80,6 +80,7 @@ public class ShareAccountChargeReadPlatformServiceImpl implements .append("sc.charge_calculation_enum as chargeCalculation, c.is_active as isActive, ") .append("c.currency_code as currencyCode, oc.name as currencyName, ") .append("oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, ") + .append("sc.charge_amount_or_percentage, ") .append("oc.internationalized_name_code as currencyNameCode from m_charge c ") .append("join m_organisation_currency oc on c.currency_code = oc.code ") .append("join m_share_account_charge sc on sc.charge_id = c.id "); @@ -132,13 +133,14 @@ public class ShareAccountChargeReadPlatformServiceImpl implements final EnumOptionData chargeCalculationType = ChargeEnumerations .chargeCalculationType(chargeCalculation); final Boolean isActive = rs.getBoolean("isActive"); - + final BigDecimal chargeamountorpercentage = rs.getBigDecimal("charge_amount_or_percentage") ; + final Collection<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() { http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/60588a78/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java index 3635aaa..90ab5b0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java @@ -42,9 +42,10 @@ import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; import org.apache.fineract.portfolio.products.constants.ProductsApiConstants; import org.apache.fineract.portfolio.products.data.ProductData; import org.apache.fineract.portfolio.products.service.ProductReadPlatformService; +import org.apache.fineract.portfolio.savings.DepositAccountType; import org.apache.fineract.portfolio.savings.data.SavingsAccountData; import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService; -import org.apache.fineract.portfolio.shareaccounts.data.PurchasedSharesData; +import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountTransactionData; import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountApplicationTimelineData; import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountDividendData; import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountStatusEnumData; @@ -114,7 +115,7 @@ public class ShareAccountReadPlatformServiceImpl implements ShareAccountReadPlat final Collection<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() { http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/60588a78/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java index daad3be..e97f6e3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java @@ -18,7 +18,9 @@ */ package org.apache.fineract.portfolio.shareaccounts.service; +import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -32,6 +34,7 @@ import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccou import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.portfolio.accounts.constants.ShareAccountApiConstants; @@ -42,6 +45,8 @@ import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountChargePaid import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountRepositoryWrapper; import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountTransaction; import org.apache.fineract.portfolio.shareaccounts.serialization.ShareAccountDataSerializer; +import org.apache.fineract.portfolio.shareproducts.domain.ShareProduct; +import org.apache.fineract.portfolio.shareproducts.domain.ShareProductRepositoryWrapper; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; @@ -54,6 +59,8 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA private final ShareAccountRepositoryWrapper shareAccountRepository; + private final ShareProductRepositoryWrapper shareProductRepository ; + private final AccountNumberGenerator accountNumberGenerator; private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository; @@ -62,12 +69,14 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA @Autowired public ShareAccountWritePlatformServiceJpaRepositoryImpl(final ShareAccountDataSerializer accountDataSerializer, - final ShareAccountRepositoryWrapper shareAccountRepository, + final ShareAccountRepositoryWrapper shareAccountRepository, + final ShareProductRepositoryWrapper shareProductRepository, final AccountNumberGenerator accountNumberGenerator, final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, final JournalEntryWritePlatformService journalEntryWritePlatformService) { this.accountDataSerializer = accountDataSerializer; this.shareAccountRepository = shareAccountRepository; + this.shareProductRepository = shareProductRepository ; this.accountNumberGenerator = accountNumberGenerator; this.accountNumberFormatRepository = accountNumberFormatRepository; this.journalEntryWritePlatformService = journalEntryWritePlatformService; @@ -79,7 +88,6 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA ShareAccount account = this.accountDataSerializer.validateAndCreate(jsonCommand); this.shareAccountRepository.save(account); generateAccountNumber(account); - //this.shareProductRepository.save(account.getShareProduct()); //subscribed shares is increased journalEntryWritePlatformService.createJournalEntriesForShares(populateJournalEntries(account, account.getPendingForApprovalSharePurchaseTransactions())); return new CommandProcessingResultBuilder() // @@ -126,7 +134,16 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA final ShareAccountTransactionEnumData typeEnum = new ShareAccountTransactionEnumData(type.longValue(), null, null); transactionDto.put("status", statusEnum); transactionDto.put("type", typeEnum); - transactionDto.put("amount", transaction.amount()); + if(transaction.isPurchaseRejectedTransaction() || transaction.isRedeemTransaction()) { + BigDecimal amount = transaction.amount() ; + if(transaction.chargeAmount() != null) { + amount = amount.add(transaction.chargeAmount()) ; + } + transactionDto.put("amount", amount); + }else { + transactionDto.put("amount", transaction.amount()); + } + transactionDto.put("chargeAmount", transaction.chargeAmount()); transactionDto.put("paymentTypeId", null); // FIXME::make it cash // payment @@ -147,14 +164,24 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA return accountingBridgeData; } + @SuppressWarnings("unchecked") @Override public CommandProcessingResult updateShareAccount(Long accountId, JsonCommand jsonCommand) { try { + Date transactionDate = DateUtils.getDateOfTenant(); ShareAccount account = this.shareAccountRepository.findOneWithNotFoundDetection(accountId); Map<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) { } http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/60588a78/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/domain/ShareProduct.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/domain/ShareProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/domain/ShareProduct.java index 4fee7ef..6cf301e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/domain/ShareProduct.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/domain/ShareProduct.java @@ -226,7 +226,10 @@ public class ShareProduct extends AbstractAuditableCustom<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 ; + } } http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/60588a78/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/exception/ShareAccountsNotFoundException.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/exception/ShareAccountsNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/exception/ShareAccountsNotFoundException.java new file mode 100644 index 0000000..71b59bc --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/exception/ShareAccountsNotFoundException.java @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.shareproducts.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + + +public class ShareAccountsNotFoundException extends AbstractPlatformResourceNotFoundException{ + + public ShareAccountsNotFoundException(final Long productId) { + super("error.msg.shareproduct.shareaccounts.not.defined", " No Share Accounts found for this prodct " + productId, productId); + } +}