blob: 57e444b6fb658f69b67747520057b37f33508065 [file] [log] [blame]
/**
* 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.service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.products.service.ShareProductReadPlatformService;
import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountData;
import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountTransactionData;
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;
@RequiredArgsConstructor
public class ShareProductDividendAssembler {
private final ShareProductReadPlatformService shareProductReadPlatformService;
private final ShareAccountReadPlatformService shareAccountReadPlatformService;
public ShareProductDividendPayOutDetails calculateDividends(final Long productId, final BigDecimal amount,
final LocalDate dividendPeriodStartDate, final LocalDate dividendPeriodEndDate) {
ShareProductData product = (ShareProductData) this.shareProductReadPlatformService.retrieveOne(productId, false);
MonetaryCurrency currency = new MonetaryCurrency(product.getCurrency().getCode(), product.getCurrency().getDecimalPlaces(),
product.getCurrency().getInMultiplesOf());
Collection<ShareAccountData> shareAccountDatas = this.shareAccountReadPlatformService.retrieveAllShareAccountDataForDividends(
productId, product.getAllowDividendCalculationForInactiveClients(), dividendPeriodStartDate);
if (shareAccountDatas == null || shareAccountDatas.isEmpty()) {
throw new ShareAccountsNotFoundException(product.getId());
}
ShareProductDividendPayOutDetails productDividendPayOutDetails = null;
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);
if (numberOfShareDays > 0) {
double amountPerShareDay = amount.doubleValue() / numberOfShareDays;
productDividendPayOutDetails = new ShareProductDividendPayOutDetails(productId, Money.of(currency, amount).getAmount(),
dividendPeriodStartDate, dividendPeriodEndDate);
for (ShareAccountData accountData : shareAccountDatas) {
long numberOfShareDaysPerAccount = numberOfSharesdaysPerAccount.get(accountData.getId());
double amountForAccount = numberOfShareDaysPerAccount * amountPerShareDay;
final Money accountAmount = Money.of(currency, BigDecimal.valueOf(amountForAccount));
ShareAccountDividendDetails dividendDetails = new ShareAccountDividendDetails(accountData.getId(),
accountAmount.getAmount(), productDividendPayOutDetails);
productDividendPayOutDetails.getAccountDividendDetails().add(dividendDetails);
}
}
return productDividendPayOutDetails;
}
private long calculateNumberOfShareDays(final LocalDate postingDate, final LocalDate lastDividendPostDate,
final int minimumActivePeriod, final Collection<ShareAccountData> shareAccountDatas,
final Map<Long, Long> numberOfSharesdaysPerAccount) {
long numberOfShareDays = 0;
for (ShareAccountData accountData : shareAccountDatas) {
long numberOfShareDaysPerAccount = 0;
Collection<ShareAccountTransactionData> purchasedShares = accountData.getPurchasedShares();
long numberOfShares = 0;
LocalDate lastDividendAppliedDate = null;
for (ShareAccountTransactionData purchasedSharesData : purchasedShares) {
final PurchasedSharesStatusType status = PurchasedSharesStatusType
.fromInt(purchasedSharesData.getStatus().getId().intValue());
final PurchasedSharesStatusType type = PurchasedSharesStatusType.fromInt(purchasedSharesData.getType().getId().intValue());
if (status.isApproved() && !type.isChargePayment()) {
LocalDate shareStartDate = purchasedSharesData.getPurchasedDate();
if (DateUtils.isBefore(shareStartDate, lastDividendPostDate)) {
shareStartDate = lastDividendPostDate;
}
int numberOfPurchseDays = Math.toIntExact(ChronoUnit.DAYS.between(shareStartDate, postingDate));
if (type.isPurchased() && numberOfPurchseDays < minimumActivePeriod) {
continue;
}
if (lastDividendAppliedDate != null) {
numberOfShareDaysPerAccount += Math.toIntExact(ChronoUnit.DAYS.between(lastDividendAppliedDate, shareStartDate))
* numberOfShares;
}
lastDividendAppliedDate = shareStartDate;
if (type.isPurchased()) {
numberOfShares += purchasedSharesData.getNumberOfShares();
} else {
numberOfShares -= purchasedSharesData.getNumberOfShares();
}
}
}
if (lastDividendAppliedDate != null) {
numberOfShareDaysPerAccount += Math.toIntExact(ChronoUnit.DAYS.between(lastDividendAppliedDate, postingDate))
* numberOfShares;
}
numberOfShareDays += numberOfShareDaysPerAccount;
numberOfSharesdaysPerAccount.put(accountData.getId(), numberOfShareDaysPerAccount);
}
return numberOfShareDays;
}
}