/*
* Copyright (c) 2005-2011 Grameen Foundation USA
* All rights reserved.
*
* Licensed 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.
*
* See also http://www.apache.org/licenses/LICENSE-2.0.html for an
* explanation of the license and how it is applied.
*/
package org.mifos.accounts.savings.business;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.mifos.accounts.business.AccountActionDateEntity;
import org.mifos.accounts.business.AccountActionEntity;
import org.mifos.accounts.business.AccountBO;
import org.mifos.accounts.business.AccountCustomFieldEntity;
import org.mifos.accounts.business.AccountNotesEntity;
import org.mifos.accounts.business.AccountPaymentEntity;
import org.mifos.accounts.business.AccountStateEntity;
import org.mifos.accounts.business.AccountStatusChangeHistoryEntity;
import org.mifos.accounts.business.AccountTrxnEntity;
import org.mifos.accounts.exceptions.AccountException;
import org.mifos.accounts.financial.business.service.activity.BaseFinancialActivity;
import org.mifos.accounts.financial.business.service.activity.SavingsInterestPostingFinancialActivity;
import org.mifos.accounts.financial.exceptions.FinancialException;
import org.mifos.accounts.productdefinition.business.InterestCalcTypeEntity;
import org.mifos.accounts.productdefinition.business.RecommendedAmntUnitEntity;
import org.mifos.accounts.productdefinition.business.SavingsOfferingBO;
import org.mifos.accounts.productdefinition.business.SavingsTypeEntity;
import org.mifos.accounts.productdefinition.util.helpers.InterestCalcType;
import org.mifos.accounts.productdefinition.util.helpers.RecommendedAmountUnit;
import org.mifos.accounts.savings.interest.CalendarPeriod;
import org.mifos.accounts.savings.interest.InterestPostingPeriodResult;
import org.mifos.accounts.savings.interest.SavingsProductHistoricalInterestDetail;
import org.mifos.accounts.savings.interest.schedule.InterestScheduledEvent;
import org.mifos.accounts.savings.interest.schedule.SavingsInterestScheduledEventFactory;
import org.mifos.accounts.savings.persistence.SavingsDao;
import org.mifos.accounts.savings.persistence.SavingsPersistence;
import org.mifos.accounts.savings.util.helpers.SavingsHelper;
import org.mifos.accounts.util.helpers.AccountActionTypes;
import org.mifos.accounts.util.helpers.AccountExceptionConstants;
import org.mifos.accounts.util.helpers.AccountPaymentData;
import org.mifos.accounts.util.helpers.AccountState;
import org.mifos.accounts.util.helpers.AccountStates;
import org.mifos.accounts.util.helpers.AccountTypes;
import org.mifos.accounts.util.helpers.PaymentData;
import org.mifos.accounts.util.helpers.PaymentStatus;
import org.mifos.application.admin.servicefacade.InvalidDateException;
import org.mifos.application.holiday.business.Holiday;
import org.mifos.application.holiday.persistence.HolidayDao;
import org.mifos.application.master.business.PaymentTypeEntity;
import org.mifos.application.meeting.business.MeetingBO;
import org.mifos.application.servicefacade.ApplicationContextProvider;
import org.mifos.calendar.CalendarEvent;
import org.mifos.clientportfolio.newloan.domain.CreationDetail;
import org.mifos.config.AccountingRules;
import org.mifos.config.ProcessFlowRules;
import org.mifos.core.MifosRuntimeException;
import org.mifos.customers.api.CustomerLevel;
import org.mifos.customers.business.CustomerBO;
import org.mifos.customers.client.business.ClientBO;
import org.mifos.customers.exceptions.CustomerException;
import org.mifos.customers.office.business.OfficeBO;
import org.mifos.customers.persistence.CustomerPersistence;
import org.mifos.customers.personnel.business.PersonnelBO;
import org.mifos.customers.personnel.persistence.LegacyPersonnelDao;
import org.mifos.customers.util.helpers.ChildrenStateType;
import org.mifos.customers.util.helpers.CustomerStatus;
import org.mifos.dto.domain.CustomFieldDto;
import org.mifos.dto.domain.CustomerNoteDto;
import org.mifos.dto.domain.SavingsAccountDetailDto;
import org.mifos.dto.domain.SavingsPerformanceHistoryDto;
import org.mifos.dto.screen.SavingsRecentActivityDto;
import org.mifos.framework.exceptions.PersistenceException;
import org.mifos.framework.util.DateTimeService;
import org.mifos.framework.util.helpers.DateUtils;
import org.mifos.framework.util.helpers.Money;
import org.mifos.schedule.ScheduledDateGeneration;
import org.mifos.schedule.ScheduledEvent;
import org.mifos.schedule.ScheduledEventFactory;
import org.mifos.schedule.internal.HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration;
import org.mifos.security.util.UserContext;
import org.mifos.service.BusinessRuleException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A savings account is ...
*/
public class SavingsBO extends AccountBO {
private static final Logger logger = LoggerFactory.getLogger(SavingsBO.class);
private Money recommendedAmount;
private RecommendedAmntUnitEntity recommendedAmntUnit;
private Money savingsBalance;
private Date activationDate;
private Money interestToBePosted;
private Date lastIntPostDate;
private Date nextIntPostDate;
private SavingsOfferingBO savingsOffering;
private SavingsPerformanceEntity savingsPerformance;
// the follow three fields cant be null at present on savings_account table so populate them for now but dont use em.
private SavingsTypeEntity savingsType;
private InterestCalcTypeEntity interestCalcType;
private Double interestRate;
private Set<SavingsActivityEntity> savingsActivityDetails = new LinkedHashSet<SavingsActivityEntity>();
private final SavingsHelper helper = new SavingsHelper();
private SavingsTransactionActivityHelper savingsTransactionActivityHelper = new SavingsTransactionActivityHelperImpl();
private SavingsPaymentStrategy savingsPaymentStrategy = new SavingsPaymentStrategyImpl(savingsTransactionActivityHelper);
private SavingsPersistence savingsPersistence = null;
@Deprecated
public SavingsPersistence getSavingsPersistence() {
if (null == savingsPersistence) {
savingsPersistence = new SavingsPersistence();
}
return savingsPersistence;
}
@Deprecated
public void setSavingsPersistence(final SavingsPersistence savingsPersistence) {
this.savingsPersistence = savingsPersistence;
}
private LegacyPersonnelDao personnelPersistence = null;
@Deprecated
public LegacyPersonnelDao getPersonnelPersistence() {
if (null == personnelPersistence) {
personnelPersistence = ApplicationContextProvider.getBean(LegacyPersonnelDao.class);
}
return personnelPersistence;
}
@Deprecated
public void setPersonnelPersistence(final LegacyPersonnelDao personnelPersistence) {
this.personnelPersistence = personnelPersistence;
}
private CustomerPersistence customerPersistence;
@Deprecated
public CustomerPersistence getCustomerPersistence() {
if (null == customerPersistence) {
customerPersistence = new CustomerPersistence();
}
return customerPersistence;
}
@Deprecated
public void setCustomerPersistence(final CustomerPersistence customerPersistence) {
this.customerPersistence = customerPersistence;
}
/**
* Responsible for creating savings account in valid initial state.
*/
public static SavingsBO createOpeningBalanceIndividualSavingsAccount(CustomerBO customer, SavingsOfferingBO savingsProduct,
Money recommendedOrMandatoryAmount, AccountState savingsAccountState, LocalDate createdDate, Integer createdById,
SavingsAccountActivationDetail activationDetails, PersonnelBO createdBy, Money openingBalance) {
RecommendedAmountUnit recommendedAmountUnit = RecommendedAmountUnit.COMPLETE_GROUP;
CreationDetail creationDetail = new CreationDetail(createdDate.toDateMidnight().toDateTime(), createdById);
SavingsBO savingsAccount = new SavingsBO(savingsAccountState, customer, activationDetails, creationDetail, savingsProduct, recommendedAmountUnit, recommendedOrMandatoryAmount, createdBy, openingBalance);
return savingsAccount;
}
/**
* Responsible for creating savings account in valid initial state.
*/
public static SavingsBO createIndividalSavingsAccount(CustomerBO customer, SavingsOfferingBO savingsProduct,
Money recommendedOrMandatoryAmount, AccountState savingsAccountState, LocalDate createdDate, Integer createdById,
CalendarEvent calendarEvents, PersonnelBO createdBy) {
LocalDate activationDate = new LocalDate();
SavingsAccountActivationDetail activationDetails = determineAccountActivationDetails(customer, savingsProduct, recommendedOrMandatoryAmount, savingsAccountState, calendarEvents, activationDate);
Money startingBalance = Money.zero(savingsProduct.getCurrency());
RecommendedAmountUnit recommendedAmountUnit = RecommendedAmountUnit.COMPLETE_GROUP;
CreationDetail creationDetail = new CreationDetail(createdDate.toDateMidnight().toDateTime(), createdById);
SavingsBO savingsAccount = new SavingsBO(savingsAccountState, customer, activationDetails, creationDetail, savingsProduct, recommendedAmountUnit, recommendedOrMandatoryAmount, createdBy, startingBalance);
return savingsAccount;
}
public static SavingsBO createJointSavingsAccount(CustomerBO customer, SavingsOfferingBO savingsProduct,
Money recommendedOrMandatoryAmount, AccountState savingsAccountState, LocalDate createdDate, Integer createdById,
CalendarEvent calendarEvents, PersonnelBO createdBy, List<CustomerBO> activeAndOnHoldClients) {
SavingsAccountActivationDetail activationDetails = determineAccountActivationDetails(customer, savingsProduct, recommendedOrMandatoryAmount, savingsAccountState, calendarEvents, activeAndOnHoldClients);
Money startingBalance = Money.zero(savingsProduct.getCurrency());
RecommendedAmountUnit recommendedAmountUnit = RecommendedAmountUnit.PER_INDIVIDUAL;
CreationDetail creationDetail = new CreationDetail(createdDate.toDateMidnight().toDateTime(), createdById);
SavingsBO savingsAccount = new SavingsBO(savingsAccountState, customer, activationDetails, creationDetail, savingsProduct, recommendedAmountUnit, recommendedOrMandatoryAmount, createdBy, startingBalance);
return savingsAccount;
}
/**
* valid minimal legal constructor
*/
public SavingsBO(AccountState savingsAccountState, CustomerBO customer, SavingsAccountActivationDetail activationDetails, CreationDetail creationDetail, SavingsOfferingBO savingsProduct,
RecommendedAmountUnit recommendedAmountUnit, Money recommendedOrMandatoryAmount, PersonnelBO createdBy, Money startingBalance) {
super(AccountTypes.SAVINGS_ACCOUNT, savingsAccountState, customer, activationDetails.getScheduledPayments(), creationDetail);
this.savingsOffering = savingsProduct;
this.recommendedAmntUnit = new RecommendedAmntUnitEntity(recommendedAmountUnit);
this.recommendedAmount = recommendedOrMandatoryAmount;
this.savingsBalance = startingBalance;
this.savingsPerformance = new SavingsPerformanceEntity(this);
// inherited from savings product for now but should be removed and cleaned up.
this.interestRate = this.savingsOffering.getInterestRate();
this.interestCalcType = new InterestCalcTypeEntity(InterestCalcType.fromInt(this.savingsOffering.getInterestCalcType().getId()));
this.savingsType = new SavingsTypeEntity(this.savingsOffering.getSavingsTypeAsEnum());
if (savingsAccountState.isActiveSavingsAccountState()) {
this.activationDate = activationDetails.getActivationDate().toDateMidnight().toDate();
this.nextIntPostDate = activationDetails.getNextInterestPostingDate().toDateMidnight().toDate();
}
AccountStateEntity newStatus = new AccountStateEntity(savingsAccountState);
AccountStatusChangeHistoryEntity statusChange = new AccountStatusChangeHistoryEntity(null, newStatus, createdBy, this);
this.accountStatusChangeHistory.add(statusChange);
}
/**
* default constructor for hibernate usage
*/
protected SavingsBO() {
// default constructor for hibernate
}
public static SavingsAccountActivationDetail determineAccountActivationDetails(CustomerBO customer,
SavingsOfferingBO savingsProduct, Money recommendedOrMandatoryAmount, AccountState savingsAccountState,
CalendarEvent calendarEvents, List<CustomerBO> activeAndOnHoldClients) {
List<AccountActionDateEntity> scheduledPayments = new ArrayList<AccountActionDateEntity>();
LocalDate activationDate = new LocalDate();
LocalDate nextInterestPostingDate = new LocalDate();
if (savingsAccountState.isActiveSavingsAccountState()) {
activationDate = new LocalDate();
ScheduledEvent scheduledEvent = ScheduledEventFactory.createScheduledEventFrom(customer.getCustomerMeetingValue());
ScheduledDateGeneration dateGeneration = new HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration(calendarEvents.getWorkingDays(), calendarEvents.getHolidays());
for (CustomerBO client : activeAndOnHoldClients) {
List<DateTime> depositDates = dateGeneration.generateScheduledDates(10, activationDate.toDateTimeAtStartOfDay(), scheduledEvent, false);
short installmentNumber = 1;
for (DateTime date : depositDates) {
java.sql.Date depositDueDate = new java.sql.Date(date.toDate().getTime());
AccountActionDateEntity scheduledSavingsDeposit = new SavingsScheduleEntity(client, installmentNumber,
depositDueDate, PaymentStatus.UNPAID, recommendedOrMandatoryAmount, savingsProduct.getCurrency());
scheduledPayments.add(scheduledSavingsDeposit);
}
}
InterestScheduledEvent interestPostingEvent = new SavingsInterestScheduledEventFactory().createScheduledEventFrom(savingsProduct.getFreqOfPostIntcalc().getMeeting());
nextInterestPostingDate = interestPostingEvent.nextMatchingDateAfter(new LocalDate(startOfFiscalYear()),activationDate);
}
return new SavingsAccountActivationDetail(activationDate, nextInterestPostingDate, scheduledPayments);
}
public static SavingsAccountActivationDetail determineAccountActivationDetails(CustomerBO customer, SavingsOfferingBO savingsProduct,
Money recommendedOrMandatoryAmount, AccountState savingsAccountState, CalendarEvent calendarEvents, LocalDate activationDate) {
List<AccountActionDateEntity> scheduledPayments = new ArrayList<AccountActionDateEntity>();
LocalDate nextInterestPostingDate = new LocalDate();
if (savingsAccountState.isActiveSavingsAccountState()) {
ScheduledEvent scheduledEvent = ScheduledEventFactory.createScheduledEventFrom(customer.getCustomerMeetingValue());
ScheduledDateGeneration dateGeneration = new HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration(calendarEvents.getWorkingDays(), calendarEvents.getHolidays());
List<DateTime> depositDates = dateGeneration.generateScheduledDates(10, activationDate.toDateTimeAtStartOfDay(), scheduledEvent, false);
short installmentNumber = 1;
for (DateTime date : depositDates) {
java.sql.Date depositDueDate = new java.sql.Date(date.toDate().getTime());
AccountActionDateEntity scheduledSavingsDeposit = new SavingsScheduleEntity(customer, installmentNumber,
depositDueDate, PaymentStatus.UNPAID, recommendedOrMandatoryAmount, savingsProduct.getCurrency());
scheduledPayments.add(scheduledSavingsDeposit);
}
InterestScheduledEvent interestPostingEvent = new SavingsInterestScheduledEventFactory().createScheduledEventFrom(savingsProduct.getFreqOfPostIntcalc().getMeeting());
nextInterestPostingDate = interestPostingEvent.nextMatchingDateAfter(new LocalDate(startOfFiscalYear()), activationDate);
}
return new SavingsAccountActivationDetail(activationDate, nextInterestPostingDate, scheduledPayments);
}
public static SavingsAccountActivationDetail generateAccountActivationDetails(CustomerBO customer, SavingsOfferingBO savingsProduct,
Money recommendedOrMandatoryAmount, AccountState savingsAccountState, CalendarEvent calendarEvents, LocalDate activationDate) {
List<AccountActionDateEntity> scheduledPayments = new ArrayList<AccountActionDateEntity>();
LocalDate nextInterestPostingDate = new LocalDate();
if (savingsAccountState.isActiveSavingsAccountState()) {
ScheduledEvent scheduledEvent = ScheduledEventFactory.createScheduledEventFrom(customer.getCustomerMeetingValue());
ScheduledDateGeneration dateGeneration = new HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration(calendarEvents.getWorkingDays(), calendarEvents.getHolidays());
List<DateTime> depositDates = dateGeneration.generateScheduledDates(10, activationDate.toDateTimeAtStartOfDay(), scheduledEvent, false);
short installmentNumber = 0;
for (DateTime date : depositDates) {
java.sql.Date depositDueDate = new java.sql.Date(date.toDate().getTime());
AccountActionDateEntity scheduledSavingsDeposit = new SavingsScheduleEntity(customer, installmentNumber++,
depositDueDate, PaymentStatus.UNPAID, recommendedOrMandatoryAmount, savingsProduct.getCurrency());
scheduledPayments.add(scheduledSavingsDeposit);
}
InterestScheduledEvent interestPostingEvent = new SavingsInterestScheduledEventFactory().createScheduledEventFrom(savingsProduct.getFreqOfPostIntcalc().getMeeting());
nextInterestPostingDate = interestPostingEvent.nextMatchingDateAfter(new LocalDate(startOfFiscalYear()), activationDate);
}
return new SavingsAccountActivationDetail(activationDate, nextInterestPostingDate, scheduledPayments);
}
/**
* @deprecated use minimal legal constructor from builder
* create a constructor that doesnt take customFields or delegate to goActiveForFristTimeAndGenerateSavingsSchedule which contains persistence.
*/
@Deprecated
public SavingsBO(final UserContext userContext, final SavingsOfferingBO savingsOffering, final CustomerBO customer,
final AccountState accountState, final Money recommendedAmount, final List<CustomFieldDto> customFields)
throws AccountException {
super(userContext, customer, AccountTypes.SAVINGS_ACCOUNT, accountState);
this.savingsOffering = savingsOffering;
this.savingsPerformance = createSavingsPerformance();
this.savingsBalance = Money.zero();
// inherit details from savings product definition
this.interestRate = this.savingsOffering.getInterestRate();
this.interestCalcType = new InterestCalcTypeEntity(InterestCalcType.fromInt(this.savingsOffering.getInterestCalcType().getId()));
this.savingsType = new SavingsTypeEntity(this.savingsOffering.getSavingsTypeAsEnum());
this.recommendedAmntUnit = savingsOffering.getRecommendedAmntUnit();
this.recommendedAmount = recommendedAmount;
// FIXME - keithw - create constructor that does not take DTO of customFields
try {
addcustomFields(customFields);
} catch (InvalidDateException e) {
throw new AccountException(e);
}
// generated the deposit action dates only if savings account is being
// saved in approved state
if (isActive()) {
goActiveForFristTimeAndGenerateSavingsSchedule(customer);
}
}
/**
* use minimal constructor which generates scheduled savings payments correctly and does not
* do through this method.
*/
@Deprecated
private void goActiveForFristTimeAndGenerateSavingsSchedule(final CustomerBO customer) throws AccountException {
HolidayDao holidayDao = ApplicationContextProvider.getBean(HolidayDao.class);
CalendarEvent futureCalendarEventsApplicableToOffice = holidayDao.findCalendarEventsForThisYearAndNext(customer
.getOfficeId());
this.activationDate = new DateTime(new DateTimeService().getCurrentJavaDateTime()).toDate();
List<Days> workingDays = futureCalendarEventsApplicableToOffice.getWorkingDays();
List<Holiday> holidays = futureCalendarEventsApplicableToOffice.getHolidays();
logger.debug("In SavingsBO::generateDepositAccountActions()");
// deposit happens on each meeting date of the customer. If for
// center/group with individual deposits, insert row for every client
if (this.getCustomer().getCustomerMeeting() != null && this.getCustomer().getCustomerMeeting().getMeeting() != null) {
MeetingBO depositSchedule = this.getCustomer().getCustomerMeeting().getMeeting();
if (this.getCustomer().getCustomerLevel().getId().equals(CustomerLevel.CLIENT.getValue())
|| this.getCustomer().getCustomerLevel().getId().equals(CustomerLevel.GROUP.getValue())
&& this.getRecommendedAmntUnit().getId().equals(RecommendedAmountUnit.COMPLETE_GROUP.getValue())) {
this.generateDepositAccountActions(this.getCustomer(), depositSchedule, workingDays, holidays, new DateTime(this.activationDate));
} else {
List<CustomerBO> children;
try {
children = this.getCustomer().getChildren(CustomerLevel.CLIENT, ChildrenStateType.ACTIVE_AND_ONHOLD);
} catch (CustomerException ce) {
throw new AccountException(ce);
}
for (CustomerBO customer1 : children) {
this.generateDepositAccountActions(customer1, depositSchedule, workingDays, holidays, new DateTime(this.activationDate));
}
}
}
InterestScheduledEvent interestPostingEvent = new SavingsInterestScheduledEventFactory()
.createScheduledEventFrom(this.savingsOffering.getFreqOfPostIntcalc().getMeeting());
this.nextIntPostDate = interestPostingEvent.nextMatchingDateAfter(new LocalDate(startOfFiscalYear()),
new LocalDate(this.activationDate)).toDateMidnight().toDate();
}
public Money getRecommendedAmount() {
return recommendedAmount;
}
public void setRecommendedAmount(final Money recommendedAmount) {
this.recommendedAmount = recommendedAmount;
}
public Money getSavingsBalance() {
return savingsBalance;
}
public void setSavingsBalance(final Money savingsBalance) {
this.savingsBalance = savingsBalance;
}
public SavingsOfferingBO getSavingsOffering() {
return savingsOffering;
}
void setSavingsOffering(final SavingsOfferingBO savingsOffering) {
this.savingsOffering = savingsOffering;
}
public SavingsPerformanceEntity getSavingsPerformance() {
return savingsPerformance;
}
public Date getActivationDate() {
return activationDate;
}
public void setActivationDate(final Date activationDate) {
this.activationDate = activationDate;
}
/**
* Most callers will want to call {@link #getRecommendedAmountUnit()}.
*/
public RecommendedAmntUnitEntity getRecommendedAmntUnit() {
return recommendedAmntUnit;
}
void setRecommendedAmntUnit(final RecommendedAmntUnitEntity recommendedAmntUnit) {
this.recommendedAmntUnit = recommendedAmntUnit;
}
public RecommendedAmountUnit getRecommendedAmountUnit() {
return RecommendedAmountUnit.fromInt(recommendedAmntUnit.getId());
}
public void setRecommendedAmountUnit(final RecommendedAmountUnit unit) {
this.recommendedAmntUnit = new RecommendedAmntUnitEntity(unit);
}
public Money getInterestToBePosted() {
return interestToBePosted;
}
public void setInterestToBePosted(final Money interestToBePosted) {
this.interestToBePosted = interestToBePosted;
}
public Date getLastIntPostDate() {
return lastIntPostDate;
}
void setLastIntPostDate(final Date lastIntPostDate) {
this.lastIntPostDate = lastIntPostDate;
}
public Date getNextIntPostDate() {
return nextIntPostDate;
}
public void setNextIntPostDate(final Date nextIntPostDate) {
this.nextIntPostDate = nextIntPostDate;
}
public Set<SavingsActivityEntity> getSavingsActivityDetails() {
return savingsActivityDetails;
}
public void setSavingsActivityDetails(final Set<SavingsActivityEntity> savingsActivityDetails) {
this.savingsActivityDetails = savingsActivityDetails;
}
public void addSavingsActivityDetails(final SavingsActivityEntity savingsActivity) {
savingsActivityDetails.add(savingsActivity);
}
@Override
public AccountTypes getType() {
return AccountTypes.SAVINGS_ACCOUNT;
}
@Override
public boolean isOpen() {
return !(getAccountState().getId().equals(AccountState.SAVINGS_CANCELLED.getValue()) || getAccountState()
.getId().equals(AccountState.SAVINGS_CLOSED.getValue()));
}
/**
* @deprecated use {@link SavingsDao#save(SavingsBO)} to persist savings account.
*/
@Deprecated
public void save() throws AccountException {
logger.info("In SavingsBO::save(), Before Saving , accountId: " + getAccountId());
try {
this.addAccountStatusChangeHistory(new AccountStatusChangeHistoryEntity(this.getAccountState(), this
.getAccountState(), getPersonnelPersistence().getPersonnel(userContext.getId()), this));
getSavingsPersistence().createOrUpdate(this);
OfficeBO branch = getSavingsPersistence().getPersistentObject(OfficeBO.class, userContext.getBranchId());
this.globalAccountNum = generateId(branch.getGlobalOfficeNum());
getSavingsPersistence().createOrUpdate(this);
} catch (PersistenceException e) {
throw new AccountException(e);
}
logger.info("In SavingsBO::save(), Successfully saved , accountId: " + getAccountId());
}
public void update(final Money recommendedAmount, final Set<AccountCustomFieldEntity> accountCustomFields) {
if (isDepositScheduleBeRegenerated()) {
if (this.recommendedAmount != null && recommendedAmount != null && !this.recommendedAmount.equals(recommendedAmount)) {
for (AccountActionDateEntity scheduledDeposit : this.getAccountActionDates()) {
if (scheduledDeposit.isOnOrAfter(new LocalDate())) {
((SavingsScheduleEntity) scheduledDeposit).setDeposit(recommendedAmount);
}
}
}
}
this.recommendedAmount = recommendedAmount;
this.setAccountCustomFields(accountCustomFields);
}
public boolean isMandatory() {
return this.savingsOffering.isMandatory();
}
public boolean isVoluntary() {
return this.savingsOffering.isVoluntary();
}
public boolean isDepositScheduleBeRegenerated() {
return getAccountState().getId().shortValue() == AccountStates.SAVINGS_ACC_APPROVED
|| getAccountState().getId().shortValue() == AccountStates.SAVINGS_ACC_INACTIVE;
}
public boolean isActive() {
return AccountState.SAVINGS_ACTIVE.getValue().equals(this.getAccountState().getId());
}
public boolean isInActive() {
return AccountState.SAVINGS_INACTIVE.getValue().equals(this.getAccountState().getId());
}
public void postInterest(InterestScheduledEvent postingSchedule, InterestPostingPeriodResult interestPostingPeriodResult, PersonnelBO createdBy) {
Money actualInterestToBePosted = interestPostingPeriodResult.getDifferenceInInterest();
LocalDate currentPostingDate = interestPostingPeriodResult.getPostingPeriod().getEndDate();
LocalDate nextPostingDate = postingSchedule.nextMatchingDateFromAlreadyMatchingDate(currentPostingDate);
doPostInterest(currentPostingDate, actualInterestToBePosted, createdBy);
updatePostingDetails(nextPostingDate);
}
private void doPostInterest(LocalDate currentPostingDate, Money actualInterestToBePosted, PersonnelBO loggedInUser) {
this.savingsBalance = this.savingsBalance.add(actualInterestToBePosted);
this.savingsPerformance.setTotalInterestDetails(actualInterestToBePosted);
SavingsActivityEntity savingsActivity = SavingsActivityEntity.savingsInterestPosting(this, personnel, this.savingsBalance, actualInterestToBePosted, currentPostingDate.toDateMidnight().toDate());
savingsActivityDetails.add(savingsActivity);
AccountPaymentEntity interestPayment = AccountPaymentEntity.savingsInterestPosting(this, actualInterestToBePosted, currentPostingDate.toDateMidnight().toDate(), loggedInUser);
DateTime dueDate = new DateTime();
SavingsTrxnDetailEntity interestPostingTransaction = SavingsTrxnDetailEntity.savingsInterestPosting(interestPayment, this.customer, this.savingsBalance, currentPostingDate.toDateMidnight().toDate(), dueDate, loggedInUser);
interestPayment.addAccountTrxn(interestPostingTransaction);
this.addAccountPayment(interestPayment);
// NOTE: financial Transaction Processing should be decoupled from application domain model.
try {
BaseFinancialActivity baseFinancialActivity = new SavingsInterestPostingFinancialActivity(interestPostingTransaction);
baseFinancialActivity.buildAccountEntries();
} catch (FinancialException e) {
throw new MifosRuntimeException(e);
}
}
public void updatePostingDetails(LocalDate nextPostingDate) {
this.lastIntPostDate = this.nextIntPostDate;
this.nextIntPostDate = nextPostingDate.toDateMidnight().toDate();
this.interestToBePosted = Money.zero(this.getCurrency());
}
public void closeAccount(final AccountPaymentEntity payment, final AccountNotesEntity notes, final CustomerBO customer, PersonnelBO loggedInUser) {
AccountStateEntity previousAccountState = this.getAccountState();
AccountStateEntity closedAccountState = new AccountStateEntity(AccountState.SAVINGS_CLOSED);
AccountStatusChangeHistoryEntity statusChangeHistory = new AccountStatusChangeHistoryEntity(
previousAccountState, closedAccountState, loggedInUser, this);
this.addAccountStatusChangeHistory(statusChangeHistory);
this.setAccountState(closedAccountState);
Money interestOutstanding = payment.getAmount().subtract(this.savingsBalance);
if (interestOutstanding.isGreaterThanZero()) {
LocalDate currentPostingDate = new LocalDate(payment.getPaymentDate());
LocalDate nextPostingDate = new LocalDate();
doPostInterest(currentPostingDate, interestOutstanding, loggedInUser);
updatePostingDetails(nextPostingDate);
}
Date transactionDate = new DateTimeService().getCurrentDateMidnight().toDate();
if (payment.getAmount().isGreaterThanZero()) {
SavingsTrxnDetailEntity withdrawal = SavingsTrxnDetailEntity.savingsWithdrawal(payment, customer,
this.savingsBalance, payment.getAmount(), loggedInUser, transactionDate, transactionDate,
transactionDate);
payment.addAccountTrxn(withdrawal);
this.addAccountPayment(payment);
SavingsActivityEntity interestPostingActivity = SavingsActivityEntity.savingsWithdrawal(this, loggedInUser,
this.savingsBalance, payment.getAmount(), payment.getPaymentDate());
savingsActivityDetails.add(interestPostingActivity);
this.savingsPerformance.setTotalWithdrawals(this.savingsPerformance.getTotalWithdrawals().add(
payment.getAmount()));
try {
buildFinancialEntries(payment.getAccountTrxns());
} catch (AccountException e) {
throw new BusinessRuleException(e.getKey(), e);
}
}
this.addAccountNotes(notes);
// this.lastIntCalcDate = transactionDate;
this.lastIntPostDate = transactionDate;
// this.interIntCalcDate = null;
this.savingsBalance = new Money(getCurrency());
this.interestToBePosted = new Money(getCurrency());
this.setClosedDate(new DateTimeService().getCurrentJavaDateTime());
}
/**
* remove after usuage in constructor is removed.
*/
@Deprecated
public void generateAndUpdateDepositActionsForClient(final ClientBO client, final List<Days> workingDays,
final List<Holiday> holidays) throws AccountException {
if (client.getCustomerMeeting().getMeeting() != null) {
if (!(getCustomer().getLevel() == CustomerLevel.GROUP && getRecommendedAmntUnit().getId().equals(
RecommendedAmountUnit.COMPLETE_GROUP.getValue()))) {
DateTime today = new DateTime().toDateMidnight().toDateTime();
generateDepositAccountActions(client, client.getCustomerMeeting().getMeeting(), workingDays, holidays, today);
this.update();
}
}
}
@Deprecated
public void generateDepositAccountActions(final CustomerBO customer, final MeetingBO meeting,
final List<Days> workingDays, final List<Holiday> holidays, final DateTime startingFrom) {
ScheduledEvent scheduledEvent = ScheduledEventFactory.createScheduledEventFrom(meeting);
ScheduledDateGeneration dateGeneration = new HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration(
workingDays, holidays);
List<DateTime> depositDates = dateGeneration.generateScheduledDates(10, startingFrom, scheduledEvent, false);
short installmentNumber = 1;
for (DateTime date : depositDates) {
AccountActionDateEntity actionDate = helper.createActionDateObject(this, customer, installmentNumber++,
date.toDate(), userContext.getId(), getRecommendedAmount());
addAccountActionDate(actionDate);
logger.debug("In SavingsBO::generateDepositAccountActions(), Successfully added account action on date: "
+ date);
}
}
private void generateDepositAccountActions(final CustomerBO customer, final MeetingBO meeting,
final AccountActionDateEntity lastInstallment, final List<Days> workingDays, final List<Holiday> holidays) {
DateTime startFromDayAfterLastKnownInstallmentDate = new DateTime(lastInstallment.getActionDate()).plusDays(1);
ScheduledEvent scheduledEvent = ScheduledEventFactory.createScheduledEventFrom(meeting);
ScheduledDateGeneration dateGeneration = new HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration(
workingDays, holidays);
List<DateTime> depositDates = dateGeneration.generateScheduledDates(10,
startFromDayAfterLastKnownInstallmentDate, scheduledEvent, false);
short installmentNumber = lastInstallment.getInstallmentId();
for (DateTime depositDate : depositDates) {
AccountActionDateEntity actionDate = helper.createActionDateObject(this, customer, ++installmentNumber,
depositDate.toDate(), (short) 1, getRecommendedAmount());
addAccountActionDate(actionDate);
logger.debug("In SavingsBO::generateDepositAccountActions(), Successfully added account action on date: "
+ depositDate);
}
}
/**
* @deprecated for deposits use {@link SavingsBO#deposit(AccountPaymentEntity, Integer)} and withdrawals use
* {@link SavingsBO#withdraw(AccountPaymentEntity)}
*/
@Deprecated
@Override
protected AccountPaymentEntity makePayment(final PaymentData paymentData) throws AccountException {
Money totalAmount = paymentData.getTotalAmount();
Money enteredAmount = totalAmount;
Date transactionDate = paymentData.getTransactionDate();
List<AccountPaymentData> accountPayments = paymentData.getAccountPayments();
if(paymentData.getCustomer() == null) {
throw new NullPointerException("Customer in payment data during payment should not be null");
}
CustomerBO customer = paymentData.getCustomer();
AccountPaymentEntity accountPayment = new AccountPaymentEntity(this, totalAmount, paymentData.getReceiptNum(),
paymentData.getReceiptDate(), getPaymentTypeEntity(paymentData.getPaymentTypeId()), transactionDate);
accountPayment.setCreatedByUser(paymentData.getPersonnel());
accountPayment.setComment(paymentData.getComment());
// make savings account active if inactive
if (this.getState().getValue().equals(AccountState.SAVINGS_INACTIVE.getValue())) {
this.changeStatus(AccountState.SAVINGS_ACTIVE, null, "Account Made Active Due to Payment", paymentData.getPersonnel());
}
if (totalAmount.isGreaterThanZero() && paymentData.getAccountPayments().size() <= 0) {
SavingsTrxnDetailEntity accountTrxn = buildUnscheduledDeposit(accountPayment, totalAmount, paymentData
.getPersonnel(), customer, transactionDate);
accountPayment.addAccountTrxn(accountTrxn);
addSavingsActivityDetails(buildSavingsActivity(totalAmount, getSavingsBalance(),
AccountActionTypes.SAVINGS_DEPOSIT.getValue(), transactionDate, paymentData.getPersonnel()));
return accountPayment;
}
for (AccountPaymentData accountPaymentData : accountPayments) {
SavingsScheduleEntity accountAction = (SavingsScheduleEntity) getAccountActionDate(accountPaymentData
.getInstallmentId(), customer.getCustomerId());
if (accountAction != null && depositAmountIsInExcess(enteredAmount)) {
if (accountAction.isPaid()) {
throw new AccountException("errors.update", new String[] { getGlobalAccountNum() });
}
Money depositAmount = new Money(getCurrency());
PaymentStatus paymentStatus = PaymentStatus.UNPAID;
if (enteredAmount.isGreaterThanOrEqual(accountAction.getTotalDepositDue())) {
depositAmount = accountAction.getTotalDepositDue();
enteredAmount = enteredAmount.subtract(accountAction.getTotalDepositDue());
paymentStatus = PaymentStatus.PAID;
} else {
depositAmount = enteredAmount;
enteredAmount = new Money(getCurrency());
}
if (this.isVoluntary() && depositAmountIsInExcess(depositAmount)) {
paymentStatus = PaymentStatus.PAID;
}
savingsBalance = savingsBalance.add(depositAmount);
savingsPerformance.setPaymentDetails(depositAmount);
accountAction.setPaymentDetails(depositAmount, paymentStatus, new java.sql.Date(transactionDate
.getTime()));
Short installmentId = accountAction.getInstallmentId();
SavingsTrxnDetailEntity accountTrxn = SavingsTrxnDetailEntity.savingsDeposit(accountPayment, customer, this.savingsBalance,
depositAmount, paymentData.getPersonnel(), accountAction.getActionDate(), paymentData.getTransactionDate(), paymentData.getTransactionDate(), installmentId);
accountPayment.addAccountTrxn(accountTrxn);
}
}
if (depositAmountIsInExcess(enteredAmount)) {
SavingsTrxnDetailEntity accountTrxn = buildUnscheduledDeposit(accountPayment, enteredAmount, paymentData
.getPersonnel(), customer, transactionDate);
accountPayment.addAccountTrxn(accountTrxn);
}
addSavingsActivityDetails(buildSavingsActivity(totalAmount, getSavingsBalance(),
AccountActionTypes.SAVINGS_DEPOSIT.getValue(), transactionDate, paymentData.getPersonnel()));
return accountPayment;
}
private SavingsTrxnDetailEntity buildUnscheduledDeposit(final AccountPaymentEntity accountPayment,
final Money depositAmount, final PersonnelBO personnel, final CustomerBO customer,
final Date transactionDate) {
savingsBalance = savingsBalance.add(depositAmount);
savingsPerformance.setPaymentDetails(depositAmount);
Short installmentId = null;
return SavingsTrxnDetailEntity.savingsDeposit(accountPayment, customer, this.savingsBalance,
depositAmount, personnel, transactionDate, transactionDate, transactionDate, installmentId);
}
public void deposit(final AccountPaymentEntity payment, final CustomerBO payingCustomer) throws AccountException {
final Money amountToDeposit = payment.getAmount();
if (amountToDeposit.isLessThanOrEqualZero()) {
return;
}
final Money savingsBalanceBeforeDeposit = new Money(getCurrency(), this.savingsBalance.getAmount());
savingsBalance = savingsBalance.add(amountToDeposit);
savingsPerformance.setPaymentDetails(amountToDeposit);
final Date transactionDate = payment.getPaymentDate();
final SavingsActivityEntity savingsActivity = savingsTransactionActivityHelper.createSavingsActivityForDeposit(
payment.getCreatedByUser(), amountToDeposit, this.savingsBalance, transactionDate, this);
addSavingsActivityDetails(savingsActivity);
addAccountPayment(payment);
// make savings account active if inactive
if (this.getState().getValue().equals(AccountState.SAVINGS_INACTIVE.getValue())) {
this.changeStatus(AccountState.SAVINGS_ACTIVE, null, "Account Made Active Due to Payment", payment.getCreatedByUser());
}
final List<SavingsScheduleEntity> unpaidDepositsForPayingCustomer = findAllUnpaidInstallmentsForPayingCustomerUpTo(
transactionDate, payingCustomer.getCustomerId());
// make scheduled payments (if any) and make an unscheduled payment
// with any amount remaining
final Money amountRemaining = this.savingsPaymentStrategy.makeScheduledPayments(payment,
unpaidDepositsForPayingCustomer, payingCustomer, this.savingsOffering.getSavingsTypeAsEnum(),
savingsBalanceBeforeDeposit);
if (depositAmountIsInExcess(amountRemaining)) {
final SavingsTrxnDetailEntity excessDepositTrxn = this.savingsTransactionActivityHelper
.createSavingsTrxnForDeposit(payment, amountRemaining, payingCustomer, null, savingsBalance);
payment.addAccountTrxn(excessDepositTrxn);
}
buildFinancialEntries(payment.getAccountTrxns());
}
public void withdraw(final AccountPaymentEntity payment, final CustomerBO payingCustomer) throws AccountException {
final Money amountToWithdraw = payment.getAmount();
if (amountToWithdraw.isGreaterThan(savingsBalance)) {
throw new AccountException("errors.insufficentbalance", new String[] { getGlobalAccountNum() });
}
final Money maxWithdrawAmount = getSavingsOffering().getMaxAmntWithdrawl();
if (maxWithdrawAmount != null && maxWithdrawAmount.isNonZero()
&& amountToWithdraw.isGreaterThan(maxWithdrawAmount)) {
throw new AccountException("errors.exceedmaxwithdrawal", new String[] { getGlobalAccountNum() });
}
// make savings account active if inactive
if (this.getState().getValue().equals(AccountState.SAVINGS_INACTIVE.getValue())) {
this.changeStatus(AccountState.SAVINGS_ACTIVE, null, "Account Made Active Due to Payment", payment.getCreatedByUser());
}
this.addAccountPayment(payment);
savingsBalance = savingsBalance.subtract(amountToWithdraw);
savingsPerformance.setWithdrawDetails(amountToWithdraw);
final SavingsActivityEntity savingsActivity = this.savingsTransactionActivityHelper
.createSavingsActivityForWithdrawal(payment, this.savingsBalance, this);
addSavingsActivityDetails(savingsActivity);
final SavingsTrxnDetailEntity accountTrxnBO = this.savingsTransactionActivityHelper
.createSavingsTrxnForWithdrawal(payment, amountToWithdraw, payingCustomer, this.savingsBalance);
payment.addAccountTrxn(accountTrxnBO);
buildFinancialEntries(payment.getAccountTrxns());
}
/**
* @deprecated use {@link SavingsBO#withdraw(AccountPaymentEntity, CustomerBO)} instead and use save from
* DAO/Persistence for {@link SavingsBO}.
*/
@Deprecated
public AccountPaymentEntity withdraw(final PaymentData accountPaymentData, final boolean persist) throws AccountException {
Money totalAmount = accountPaymentData.getTotalAmount();
if (totalAmount.isGreaterThan(savingsBalance)) {
throw new AccountException("errors.insufficentbalance", new String[] { getGlobalAccountNum() });
}
Money maxWithdrawAmount = getSavingsOffering().getMaxAmntWithdrawl();
if (maxWithdrawAmount != null && maxWithdrawAmount.isNonZero() && totalAmount.isGreaterThan(maxWithdrawAmount)) {
throw new AccountException("errors.exceedmaxwithdrawal", new String[] { getGlobalAccountNum() });
}
savingsBalance = savingsBalance.subtract(totalAmount);
savingsPerformance.setWithdrawDetails(totalAmount);
CustomerBO customer = accountPaymentData.getCustomer();
AccountPaymentEntity accountPayment = new AccountPaymentEntity(this, totalAmount, accountPaymentData
.getReceiptNum(), accountPaymentData.getReceiptDate(), getPaymentTypeEntity(accountPaymentData
.getPaymentTypeId()), accountPaymentData.getTransactionDate());
SavingsTrxnDetailEntity accountTrxnBO = SavingsTrxnDetailEntity.savingsWithdrawal(accountPayment, customer, this.savingsBalance,
totalAmount, accountPaymentData.getPersonnel(), accountPaymentData.getTransactionDate(), accountPaymentData.getTransactionDate(), accountPaymentData.getTransactionDate());
accountPayment.addAccountTrxn(accountTrxnBO);
addAccountPayment(accountPayment);
addSavingsActivityDetails(buildSavingsActivity(totalAmount, getSavingsBalance(),
AccountActionTypes.SAVINGS_WITHDRAWAL.getValue(), accountPaymentData.getTransactionDate(),
accountPaymentData.getPersonnel()));
buildFinancialEntries(accountPayment.getAccountTrxns());
// make savings account active if inactive
if (this.getState().getValue().equals(AccountState.SAVINGS_INACTIVE.getValue())) {
this.changeStatus(AccountState.SAVINGS_ACTIVE, null, "Account Made Active Due to Payment", accountPayment.getCreatedByUser());
}
if (persist) {
try {
getSavingsPersistence().createOrUpdate(this);
} catch (PersistenceException e) {
throw new AccountException(e);
}
}
return accountPayment;
}
@Override
protected void activationDateHelper(final Short newStatusId) throws AccountException {
if (ProcessFlowRules.isSavingsPendingApprovalStateEnabled()) {
if (this.getAccountState().getId().shortValue() == AccountStates.SAVINGS_ACC_PENDINGAPPROVAL
&& newStatusId.shortValue() == AccountStates.SAVINGS_ACC_APPROVED) {
goActiveForFristTimeAndGenerateSavingsSchedule(customer);
}
} else {
if (this.getAccountState().getId().shortValue() == AccountStates.SAVINGS_ACC_PARTIALAPPLICATION
&& newStatusId.shortValue() == AccountStates.SAVINGS_ACC_APPROVED) {
goActiveForFristTimeAndGenerateSavingsSchedule(customer);
}
}
}
public AccountPaymentEntity adjustLastUserAction(Money amountAdjustedTo, String adjustmentNote,
PersonnelBO updatedBy) {
AccountPaymentEntity lastPayment = getLastPmnt();
return adjustUserAction(amountAdjustedTo, adjustmentNote, new LocalDate(lastPayment.getPaymentDate()), updatedBy, lastPayment);
}
public AccountPaymentEntity adjustUserAction(Money amountAdjustedTo, String adjustmentNote, LocalDate adjustmentDate,
PersonnelBO updatedBy, Integer paymentId) {
AccountPaymentEntity payment = findPaymentById(paymentId);
return adjustUserAction(amountAdjustedTo, adjustmentNote, adjustmentDate, updatedBy, payment);
}
private AccountPaymentEntity adjustUserAction(Money amountAdjustedTo, String adjustmentNote, LocalDate adjustmentDate,
PersonnelBO updatedBy, AccountPaymentEntity payment) {
AccountPaymentEntity newPayment = null;
try {
if (!isAdjustPossibleOnTrxn(amountAdjustedTo, payment)) {
throw new BusinessRuleException(AccountExceptionConstants.CANNOTADJUST);
}
AccountActionTypes savingsTransactionType = findFirstDepositOrWithdrawalTransaction(payment);
Date adjustedOn = new DateTimeService().getCurrentJavaDateTime();
List<AccountTrxnEntity> reversedTransactions = reverseTransaction(adjustmentNote, updatedBy, payment,
savingsTransactionType, adjustedOn);
buildFinancialEntries(new LinkedHashSet<AccountTrxnEntity>(reversedTransactions));
if (amountAdjustedTo.isGreaterThanZero()) {
Set<AccountTrxnEntity> adjustedPaymentTransactions = createNewAccountPaymentWithAdjustedAmount(
amountAdjustedTo, updatedBy, payment, savingsTransactionType, adjustedOn, adjustmentDate);
buildFinancialEntries(adjustedPaymentTransactions);
newPayment = adjustedPaymentTransactions.toArray(new AccountTrxnEntity[adjustedPaymentTransactions
.size()])[0].getAccountPayment();
}
} catch (AccountException e) {
throw new BusinessRuleException(e.getKey(), e);
}
goActiveDueToDepositOrWithdrawalOnAccount(updatedBy);
return newPayment;
}
private void goActiveDueToDepositOrWithdrawalOnAccount(PersonnelBO updatedBy) {
if (!this.isActive()) {
AccountStateEntity oldAccountState = this.getAccountState();
AccountStateEntity newAccountState = new AccountStateEntity(AccountState.SAVINGS_ACTIVE);
this.setAccountState(newAccountState);
AccountStatusChangeHistoryEntity savingsAccountToActive = new AccountStatusChangeHistoryEntity(oldAccountState, newAccountState, updatedBy, this);
this.accountStatusChangeHistory.add(savingsAccountToActive);
}
}
private Set<AccountTrxnEntity> createNewAccountPaymentWithAdjustedAmount(Money amountAdjustedTo,
PersonnelBO updatedBy, AccountPaymentEntity payment, AccountActionTypes savingsTransactionType,
Date adjustedOn, LocalDate adjustmentDate) {
AccountPaymentEntity newAccountPayment = new AccountPaymentEntity(this, amountAdjustedTo, payment.getReceiptNumber(),
payment.getReceiptDate(), payment.getPaymentType(), adjustmentDate.toDateMidnight().toDate());
newAccountPayment.setCreatedByUser(updatedBy);
newAccountPayment.setAmount(amountAdjustedTo);
Set<AccountTrxnEntity> accountTrxns = new HashSet<AccountTrxnEntity>();
if (isMandatory() && savingsTransactionType.equals(AccountActionTypes.SAVINGS_DEPOSIT)) {
accountTrxns = createDepositTrxnsForMandatoryAccountsAfterAdjust(newAccountPayment, payment, amountAdjustedTo, adjustmentDate, updatedBy);
} else if (isVoluntary() && savingsTransactionType.equals(AccountActionTypes.SAVINGS_DEPOSIT)) {
accountTrxns = createDepositTrxnsForVolAccountsAfterAdjust(newAccountPayment, payment, amountAdjustedTo, adjustmentDate, updatedBy);
} else {
accountTrxns = createWithdrawalTrxnsAfterAdjust(newAccountPayment, payment, amountAdjustedTo, adjustmentDate, updatedBy);
}
for (AccountTrxnEntity accountTrxn : accountTrxns) {
newAccountPayment.addAccountTrxn(accountTrxn);
}
this.addAccountPayment(newAccountPayment);
AccountActionEntity depositOrWithdrawalTransactionType = new AccountActionEntity(savingsTransactionType);
SavingsActivityEntity depositOrWithdrawalActivity = new SavingsActivityEntity(updatedBy, depositOrWithdrawalTransactionType, amountAdjustedTo, this.savingsBalance, adjustedOn, this);
this.savingsActivityDetails.add(depositOrWithdrawalActivity);
return newAccountPayment.getAccountTrxns();
}
private List<AccountTrxnEntity> reverseTransaction(String adjustmentNote, PersonnelBO updatedBy,
AccountPaymentEntity payment, AccountActionTypes savingsTransactionType, Date adjustedOn)
throws AccountException {
for (AccountTrxnEntity accntTrxn : payment.getAccountTrxns()) {
if (AccountActionTypes.SAVINGS_DEPOSIT.equals(savingsTransactionType)) {
adjustForDeposit(accntTrxn);
} else if (AccountActionTypes.SAVINGS_WITHDRAWAL.equals(savingsTransactionType)) {
adjustForWithdrawal(accntTrxn);
}
}
SavingsActivityEntity adjustment = SavingsActivityEntity.savingsAdjustment(this, updatedBy, this.savingsBalance, payment.getAmount(), adjustedOn);
savingsActivityDetails.add(adjustment);
return payment.reversalAdjustment(updatedBy, adjustmentNote);
}
private AccountActionTypes findFirstDepositOrWithdrawalTransaction(AccountPaymentEntity lastPayment) {
AccountActionTypes accountActionTypes = AccountActionTypes.SAVINGS_INTEREST_POSTING;
for (AccountTrxnEntity accntTrxn : lastPayment.getAccountTrxns()) {
if (!accntTrxn.getAccountActionEntity().isSavingsAdjustment()) {
accountActionTypes = AccountActionTypes.fromInt(accntTrxn.getAccountActionEntity().getId());
}
}
return accountActionTypes;
}
public AccountPaymentEntity findMostRecentDepositOrWithdrawalByDate() {
AccountPaymentEntity mostRecentPayment = null;
if (!this.accountPayments.isEmpty()) {
for (AccountPaymentEntity accountPaymentEntity : this.accountPayments) {
if (mostRecentPayment == null && accountPaymentEntity.isSavingsDepositOrWithdrawal()) {
mostRecentPayment = accountPaymentEntity;
} else if (mostRecentPayment != null) {
LocalDate paymentDate = new LocalDate(accountPaymentEntity.getPaymentDate());
if ((paymentDate.isAfter(new LocalDate(mostRecentPayment.getPaymentDate())) && paymentDate.isBefore(new LocalDate().plusDays(1))) ||
(paymentDate.isEqual(new LocalDate(mostRecentPayment.getPaymentDate())) &&
accountPaymentEntity.getPaymentId() != null && mostRecentPayment.getPaymentId() != null &&
accountPaymentEntity.getPaymentId() > mostRecentPayment.getPaymentId())
&& accountPaymentEntity.isSavingsDepositOrWithdrawal()) {
mostRecentPayment = accountPaymentEntity;
}
}
}
}
return mostRecentPayment;
}
private List<AccountActionDateEntity> getAccountActions(final Date dueDate, final Integer customerId) {
List<AccountActionDateEntity> accountActions = new ArrayList<AccountActionDateEntity>();
for (AccountActionDateEntity accountAction : getAccountActionDates()) {
if (accountAction.getActionDate().compareTo(dueDate) <= 0 &&
!accountAction.isPaid() &&
accountAction.getCustomer().getCustomerId() == customerId) {
accountActions.add(accountAction);
}
}
return accountActions;
}
private Set<AccountTrxnEntity> createWithdrawalTrxnsAfterAdjust(final AccountPaymentEntity newAccountPayment,
final AccountPaymentEntity lastAccountPayment, final Money newAmount, final LocalDate adjustmentDate, PersonnelBO loggedInUser) {
Set<AccountTrxnEntity> newTrxns = new LinkedHashSet<AccountTrxnEntity>();
SavingsTrxnDetailEntity accountTrxn = null;
// create transaction for withdrawal
SavingsTrxnDetailEntity oldSavingsAccntTrxn = null;
for (AccountTrxnEntity oldAccntTrxn : lastAccountPayment.getAccountTrxns()) {
oldSavingsAccntTrxn = (SavingsTrxnDetailEntity) oldAccntTrxn;
break;
}
this.savingsBalance = this.savingsBalance.subtract(newAmount);
Date transactionCreatedDate = new DateTimeService().getCurrentJavaDateTime();
accountTrxn = SavingsTrxnDetailEntity.savingsWithdrawal(newAccountPayment, oldSavingsAccntTrxn.getCustomer(), newAmount, newAmount, loggedInUser,
oldSavingsAccntTrxn.getDueDate(), adjustmentDate.toDateMidnight().toDate(), transactionCreatedDate);
this.savingsPerformance.setTotalWithdrawals(this.savingsPerformance.getTotalWithdrawals().add(accountTrxn.getWithdrawlAmount()));
newTrxns.add(accountTrxn);
return newTrxns;
}
private Set<AccountTrxnEntity> createDepositTrxnsForMandatoryAccountsAfterAdjust(
final AccountPaymentEntity newAccountPayment, final AccountPaymentEntity lastAccountPayment, Money newAmount, LocalDate adjustmentDate, PersonnelBO createdBy) {
Set<AccountTrxnEntity> newTrxns = new LinkedHashSet<AccountTrxnEntity>();
SavingsTrxnDetailEntity accountTrxn = null;
CustomerBO customer = null;
Date trxnDate = adjustmentDate.toDateMidnight().toDate();
for (AccountTrxnEntity oldAccntTrxn : lastAccountPayment.getAccountTrxns()) {
customer = oldAccntTrxn.getCustomer();
break;
}
List<AccountActionDateEntity> accountActionList = getAccountActions(lastAccountPayment.getPaymentDate(), customer.getCustomerId());
for (AccountActionDateEntity accountActionDateEntity : accountActionList) {
SavingsScheduleEntity accountAction = (SavingsScheduleEntity) accountActionDateEntity;
if (newAmount.isZero()) {
break;
}
accountTrxn = null;
// if payment covers required deposit
if (accountAction.getDeposit().isLessThanOrEqual(newAmount)) {
this.savingsBalance = this.savingsBalance.add(accountAction.getDeposit());
Short installmentId = accountAction.getInstallmentId();
Date dueDate = accountAction.getActionDate();
Date transactionCreatedDate = new DateTimeService().getCurrentJavaDateTime();
accountTrxn = SavingsTrxnDetailEntity.savingsDeposit(newAccountPayment, customer, this.savingsBalance, accountAction.getDeposit(), createdBy, dueDate, trxnDate, transactionCreatedDate, installmentId);
newAmount = newAmount.subtract(accountAction.getDeposit());
accountAction.setDepositPaid(accountAction.getDepositPaid().add(accountTrxn.getDepositAmount()));
accountAction.setPaymentStatus(PaymentStatus.PAID);
} else {
this.savingsBalance = this.savingsBalance.add(newAmount);
Short installmentId = accountAction.getInstallmentId();
Date dueDate = accountAction.getActionDate();
Date transactionCreatedDate = new DateTimeService().getCurrentJavaDateTime();
accountTrxn = SavingsTrxnDetailEntity.savingsDeposit(newAccountPayment, customer, this.savingsBalance, newAmount, createdBy, dueDate, trxnDate, transactionCreatedDate, installmentId);
newAmount = newAmount.subtract(newAmount);
accountAction.setDepositPaid(accountAction.getDepositPaid().add(accountTrxn.getDepositAmount()));
accountAction.setPaymentStatus(PaymentStatus.UNPAID);
}
accountAction.setPaymentDate(new DateTimeService().getCurrentJavaSqlDate());
getSavingsPerformance().setTotalDeposits(getSavingsPerformance().getTotalDeposits().add(accountTrxn.getDepositAmount()));
newTrxns.add(accountTrxn);
}
// add trxn for excess amount
if (newAmount.isGreaterThanZero()) {
this.savingsBalance = this.savingsBalance.add(newAmount);
Short installmentId = null;
Date dueDate = null;
Date transactionCreatedDate = new DateTimeService().getCurrentJavaDateTime();
accountTrxn = SavingsTrxnDetailEntity.savingsDeposit(newAccountPayment, customer, this.savingsBalance, newAmount, createdBy, dueDate, trxnDate, transactionCreatedDate, installmentId);
newAmount = newAmount.subtract(newAmount);
getSavingsPerformance().setTotalDeposits(getSavingsPerformance().getTotalDeposits().add(accountTrxn.getDepositAmount()));
newTrxns.add(accountTrxn);
}
return newTrxns;
}
/*
* FIXME - keithw - it doesnt make sense to be that voluntary account break up account payments into more than one account transaction
* just because the amount deposited is greater than the 'recommended' amount.
*
* As a result there is no need to make a distinction between the amount deposited (be it less or greater than recommended amount)
*/
private Set<AccountTrxnEntity> createDepositTrxnsForVolAccountsAfterAdjust(final AccountPaymentEntity newAccountPayment,
final AccountPaymentEntity lastAccountPayment, Money newAmount, LocalDate adjustmentDate, PersonnelBO loggedInUser) {
Set<AccountTrxnEntity> newTrxns = new LinkedHashSet<AccountTrxnEntity>();
SavingsTrxnDetailEntity accountTrxn = null;
CustomerBO customer = null;
Date trxnDate = adjustmentDate.toDateMidnight().toDate();
for (AccountTrxnEntity oldAccntTrxn : lastAccountPayment.getAccountTrxns()) {
customer = oldAccntTrxn.getCustomer();
break;
}
Short installmentId = null;
Date dueDate = null;
Date transactionCreatedDate = new DateTimeService().getCurrentJavaDateTime();
for (AccountTrxnEntity oldAccntTrxn : lastAccountPayment.getAccountTrxns()) {
if (oldAccntTrxn.getAccountActionEntity().getId().equals(AccountActionTypes.SAVINGS_DEPOSIT.getValue())) {
SavingsTrxnDetailEntity oldSavingsAccntTrxn = (SavingsTrxnDetailEntity) oldAccntTrxn;
if (oldAccntTrxn.getInstallmentId() != null) {
SavingsScheduleEntity accountAction = (SavingsScheduleEntity) getAccountActionDate(oldSavingsAccntTrxn.getInstallmentId(), oldSavingsAccntTrxn.getCustomer().getCustomerId());
installmentId = accountAction.getInstallmentId();
dueDate = accountAction.getActionDate();
// if recommended amount is covered by payment
if (accountAction.getDeposit().isLessThanOrEqual(newAmount)) {
this.savingsBalance = this.savingsBalance.add(accountAction.getDeposit());
accountTrxn = SavingsTrxnDetailEntity.savingsDeposit(newAccountPayment, customer, this.savingsBalance, accountAction.getDeposit(), loggedInUser, dueDate, trxnDate, transactionCreatedDate, installmentId);
newAmount = newAmount.subtract(accountAction.getDeposit());
accountAction.setDepositPaid(accountAction.getDepositPaid().add(accountTrxn.getDepositAmount()));
accountAction.setPaymentStatus(PaymentStatus.PAID);
accountAction.setPaymentDate(new DateTimeService().getCurrentJavaSqlDate());
this.savingsPerformance.setTotalDeposits(this.savingsPerformance.getTotalDeposits().add(accountTrxn.getDepositAmount()));
} else if (newAmount.isNonZero()) {
// not zero and amount paid is less that recommended amount
this.savingsBalance = this.savingsBalance.add(newAmount);
accountTrxn = SavingsTrxnDetailEntity.savingsDeposit(newAccountPayment, customer, this.savingsBalance, newAmount, loggedInUser, dueDate, trxnDate, transactionCreatedDate, installmentId);
newAmount = newAmount.subtract(newAmount);
accountAction.setDepositPaid(accountAction.getDepositPaid().add(accountTrxn.getDepositAmount()));
accountAction.setPaymentStatus(PaymentStatus.UNPAID);
accountAction.setPaymentDate(new DateTimeService().getCurrentJavaSqlDate());
this.savingsPerformance.setTotalDeposits(this.savingsPerformance.getTotalDeposits().add(accountTrxn.getDepositAmount()));
}
break;
}
}
}
if (accountTrxn != null) {
newTrxns.add(accountTrxn);
}
// Create a new transaction with remaining amount
if (newAmount.isGreaterThanZero()) {
this.savingsBalance = this.savingsBalance.add(newAmount);
accountTrxn = SavingsTrxnDetailEntity.savingsDeposit(newAccountPayment, customer, this.savingsBalance, newAmount, loggedInUser, dueDate, trxnDate, transactionCreatedDate, installmentId);
this.savingsPerformance.setTotalDeposits(this.savingsPerformance.getTotalDeposits().add(accountTrxn.getDepositAmount()));
newTrxns.add(accountTrxn);
}
return newTrxns;
}
private void adjustForDeposit(final AccountTrxnEntity accntTrxn) {
SavingsTrxnDetailEntity savingsTrxn = (SavingsTrxnDetailEntity) accntTrxn;
Money depositAmount = savingsTrxn.getDepositAmount();
Short installmentId = savingsTrxn.getInstallmentId();
this.savingsBalance = this.savingsBalance.subtract(depositAmount);
SavingsScheduleEntity accntActionDate = (SavingsScheduleEntity) getAccountActionDate(installmentId, accntTrxn.getCustomer().getCustomerId());
if (accntActionDate != null) {
accntActionDate.setDepositPaid(accntActionDate.getDepositPaid().subtract(depositAmount));
accntActionDate.setPaymentStatus(PaymentStatus.UNPAID);
accntActionDate.setPaymentDate(null);
}
this.savingsPerformance.setTotalDeposits(this.savingsPerformance.getTotalDeposits().subtract(depositAmount));
}
private void adjustForWithdrawal(final AccountTrxnEntity accntTrxn) {
SavingsTrxnDetailEntity savingsTrxn = (SavingsTrxnDetailEntity) accntTrxn;
setSavingsBalance(getSavingsBalance().add(savingsTrxn.getWithdrawlAmount()));
getSavingsPerformance().setTotalWithdrawals(
getSavingsPerformance().getTotalWithdrawals().subtract(savingsTrxn.getWithdrawlAmount()));
}
public boolean isAdjustPossibleOnTrxn(final Money amountAdjustedTo, AccountPaymentEntity accountPayment) {
boolean adjustmentIsPossible = false;
if (this.isActive() || this.isInActive()) {
if (paymentIsGreaterThanZero(accountPayment) && paymentIsADepositOrWithdrawal(accountPayment)) {
if (accountPayment.getAmount().equals(amountAdjustedTo)) {
adjustmentIsPossible = false;
} else {
adjustmentIsPossible = true;
}
if (adjustmentIsPossible && withdrawalAdjustmentIsValid(accountPayment, amountAdjustedTo)
&& withdrawalAdjustmentDoesNotMakeBalanceNegative(accountPayment, amountAdjustedTo)) {
adjustmentIsPossible = true;
} else {
adjustmentIsPossible = false;
}
}
}
return adjustmentIsPossible;
}
private boolean paymentIsADepositOrWithdrawal(AccountPaymentEntity accountPayment) {
return accountPayment.isSavingsDepositOrWithdrawal();
}
private boolean paymentIsGreaterThanZero(AccountPaymentEntity accountPayment) {
return accountPayment != null && accountPayment.getAmount().isGreaterThanZero();
}
private boolean withdrawalAdjustmentIsValid(final AccountPaymentEntity accountPayment, final Money withdrawalAmount) {
boolean withdrawalAdjustmentIsValid = true;
if (accountPayment.isSavingsWithdrawal() && withdrawalAmount != null && withdrawalAmount.isNonZero() &&
this.savingsOffering.isMaxWithdrawalAmountExceeded(withdrawalAmount)) {
withdrawalAdjustmentIsValid = false;
}
return withdrawalAdjustmentIsValid;
}
private boolean withdrawalAdjustmentDoesNotMakeBalanceNegative(final AccountPaymentEntity accountPayment, final Money amountAdjustedTo) {
boolean balanceIsPositive = true;
Money balanceAfterAdjust = this.savingsBalance;
for (AccountTrxnEntity accntTrxn : accountPayment.getAccountTrxns()) {
SavingsTrxnDetailEntity savingsTrxn = (SavingsTrxnDetailEntity) accntTrxn;
if (accountPayment.isSavingsWithdrawal() && amountAdjustedTo.isGreaterThan(savingsTrxn.getWithdrawlAmount())) {
balanceAfterAdjust = balanceAfterAdjust.subtract(amountAdjustedTo.subtract(savingsTrxn.getWithdrawlAmount()));
if (balanceAfterAdjust.isLessThanZero()) {
balanceIsPositive = false;
}
}
}
return balanceIsPositive;
}
public AccountNotesEntity createAccountNotes(final String comment) throws AccountException {
try {
AccountNotesEntity accountNotes = new AccountNotesEntity(new DateTimeService().getCurrentJavaSqlDate(),
comment, getPersonnelPersistence().getPersonnel(userContext.getId()), this);
return accountNotes;
} catch (PersistenceException e) {
throw new AccountException(e);
}
}
public Money getOverDueDepositAmount(final java.sql.Date meetingDate) {
Money overdueAmount = new Money(getCurrency());
if (isMandatory()) {
for (AccountActionDateEntity accountActionDate : getAccountActionDates()) {
if (!accountActionDate.isPaid() && accountActionDate.getActionDate().before(meetingDate)) {
overdueAmount = overdueAmount.add(((SavingsScheduleEntity) accountActionDate).getTotalDepositDue());
}
}
}
return overdueAmount;
}
public List<SavingsRecentActivityDto> getRecentAccountActivity(final Integer count) {
List<SavingsRecentActivityDto> accountActivityList = new ArrayList<SavingsRecentActivityDto>();
int activitiesAdded = 0;
for (SavingsActivityEntity activity : getSavingsActivityDetails()) {
if (count == null || activitiesAdded < count.intValue()) {
accountActivityList.add(createSavingsRecentActivityView(activity));
activitiesAdded++;
}
}
return accountActivityList;
}
private SavingsRecentActivityDto createSavingsRecentActivityView(final SavingsActivityEntity savingActivity) {
SavingsRecentActivityDto savingsRecentActivityDto = new SavingsRecentActivityDto();
savingsRecentActivityDto.setAccountTrxnId(savingActivity.getId());
savingsRecentActivityDto.setActionDate(savingActivity.getTrxnCreatedDate());
String preferredDate = DateUtils.getUserLocaleDate(this.userContext.getPreferredLocale(), savingActivity.getTrxnCreatedDate().toString());
savingsRecentActivityDto.setUserPrefferedDate(preferredDate);
savingsRecentActivityDto.setAmount(removeSign(savingActivity.getAmount()).toString());
savingsRecentActivityDto.setActivity(savingActivity.getActivity().getName());
savingsRecentActivityDto.setRunningBalance(savingActivity.getBalanceAmount().toString());
return savingsRecentActivityDto;
}
@Override
protected Money getDueAmount(final AccountActionDateEntity installment) {
return ((SavingsScheduleEntity) installment).getTotalDepositDue();
}
@Override
public Money getTotalAmountDue() {
return getTotalAmountInArrears().add(getTotalAmountDueForNextInstallment());
}
@Override
public Money getTotalAmountInArrears() {
List<AccountActionDateEntity> installmentsInArrears = getDetailsOfInstallmentsInArrears();
Money totalAmount = new Money(getCurrency());
if (installmentsInArrears != null && installmentsInArrears.size() > 0) {
for (AccountActionDateEntity accountAction : installmentsInArrears) {
if (!(accountAction.getCustomer().getCustomerLevel().getId().equals(CustomerLevel.CLIENT.getValue()) && accountAction
.getCustomer().getStatus().equals(CustomerStatus.CLIENT_CLOSED))) {
totalAmount = totalAmount.add(getDueAmount(accountAction));
}
}
}
return totalAmount;
}
public Money getTotalAmountDueForNextInstallment() {
AccountActionDateEntity nextAccountAction = getDetailsOfNextInstallment();
Money totalAmount = new Money(getCurrency());
if (nextAccountAction != null) {
if (null != getAccountActionDates() && getAccountActionDates().size() > 0) {
for (AccountActionDateEntity accntActionDate : getAccountActionDates()) {
if (accntActionDate.getInstallmentId().equals(nextAccountAction.getInstallmentId())
&& !accntActionDate.isPaid()) {
if (!(accntActionDate.getCustomer().getCustomerLevel().getId().equals(
CustomerLevel.CLIENT.getValue()) && accntActionDate.getCustomer().getStatus().equals(
CustomerStatus.CLIENT_CLOSED))) {
totalAmount = totalAmount.add(((SavingsScheduleEntity) accntActionDate)
.getTotalDepositDue());
}
}
}
}
}
return totalAmount;
}
/*
* private Money getTotalAmountDueForInstallment(Short installmentId) { Money totalAmount = new
* Money(getCurrency()); if (null != getAccountActionDates() && getAccountActionDates().size() > 0) { for
* (AccountActionDateEntity accntActionDate : getAccountActionDates()) { if
* (accntActionDate.getInstallmentId().equals(installmentId) && accntActionDate.getPaymentStatus().equals(
* PaymentStatus.UNPAID.getValue())) { totalAmount = totalAmount .add(((SavingsScheduleEntity) accntActionDate)
* .getTotalDepositDue()); } } }
*
* return totalAmount; }
*
* public Money getTotalAmountDueForNextInstallment() { AccountActionDateEntity nextAccountAction =
* getDetailsOfNextInstallment(); if (nextAccountAction != null) return
* getTotalAmountDueForInstallment(nextAccountAction .getInstallmentId()); return new Money(getCurrency()); }
*/
private List<AccountActionDateEntity> getNextInstallment() {
List<AccountActionDateEntity> nextInstallment = new ArrayList<AccountActionDateEntity>();
AccountActionDateEntity nextAccountAction = getDetailsOfNextInstallment();
if (nextAccountAction != null && null != getAccountActionDates() && getAccountActionDates().size() > 0) {
for (AccountActionDateEntity accntActionDate : getAccountActionDates()) {
if (accntActionDate.getInstallmentId().equals(nextAccountAction.getInstallmentId())
&& !accntActionDate.isPaid()) {
nextInstallment.add(accntActionDate);
}
}
}
return nextInstallment;
}
public void waiveNextDepositAmountDue(PersonnelBO loggedInUser) {
SavingsActivityEntity savingsActivity = SavingsActivityEntity.savingsWaiveAmountDueOnNextDeposit(this,
loggedInUser, this.savingsBalance, getTotalAmountDueForNextInstallment(), new DateTime().toDate());
savingsActivityDetails.add(savingsActivity);
List<AccountActionDateEntity> nextInstallments = getNextInstallment();
for (AccountActionDateEntity accountActionDate : nextInstallments) {
((SavingsScheduleEntity) accountActionDate).waiveDepositDue();
}
}
public void waiveAmountOverDue(PersonnelBO loggedInUser) {
SavingsActivityEntity savingsActivity = SavingsActivityEntity.savingsWaiveDepositAmountOverdue(this,
loggedInUser, this.savingsBalance, getTotalAmountInArrears(), new DateTime().toDate());
savingsActivityDetails.add(savingsActivity);
List<AccountActionDateEntity> installmentsInArrears = getDetailsOfInstallmentsInArrears();
for (AccountActionDateEntity accountActionDate : installmentsInArrears) {
((SavingsScheduleEntity) accountActionDate).waiveDepositDue();
}
}
/**
*/
@Deprecated
private SavingsActivityEntity buildSavingsActivity(final Money amount, final Money balanceAmount,
final short acccountActionId, final Date trxnDate, final PersonnelBO personnel) throws AccountException {
AccountActionEntity accountAction;
try {
accountAction = getSavingsPersistence().getPersistentObject(
AccountActionEntity.class, acccountActionId);
} catch (PersistenceException e) {
throw new AccountException(e);
}
return new SavingsActivityEntity(personnel, accountAction, amount, balanceAmount, trxnDate, this);
}
@Override
protected void regenerateFutureInstallments(final AccountActionDateEntity nextInstallment,
final List<Days> workingDays, final List<Holiday> holidays) throws AccountException {
MeetingBO customerMeeting = getCustomer().getCustomerMeetingValue();
ScheduledEvent scheduledEvent = ScheduledEventFactory.createScheduledEventFrom(customerMeeting);
LocalDate currentDate = new LocalDate();
LocalDate thisIntervalStartDate = customerMeeting.startDateForMeetingInterval(currentDate);
LocalDate nextMatchingDate = new LocalDate(scheduledEvent.nextEventDateAfter(thisIntervalStartDate
.toDateTimeAtStartOfDay()));
DateTime futureIntervalStartDate = customerMeeting.startDateForMeetingInterval(nextMatchingDate)
.toDateTimeAtStartOfDay();
ScheduledDateGeneration dateGeneration = new HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration(
workingDays, holidays);
int numberOfInstallmentsToGenerate = getLastInstallmentId();
List<DateTime> meetingDates = dateGeneration.generateScheduledDates(numberOfInstallmentsToGenerate,
futureIntervalStartDate, scheduledEvent, false);
if (getCustomer().getCustomerLevel().getId().equals(CustomerLevel.CLIENT.getValue())
|| getCustomer().getCustomerLevel().getId().equals(CustomerLevel.GROUP.getValue())
&& getRecommendedAmntUnit().getId().equals(RecommendedAmountUnit.COMPLETE_GROUP.getValue())) {
updateSchedule(nextInstallment.getInstallmentId(), meetingDates);
} else {
List<CustomerBO> children;
try {
children = getCustomer().getChildren(CustomerLevel.CLIENT, ChildrenStateType.OTHER_THAN_CLOSED);
} catch (CustomerException ce) {
throw new AccountException(ce);
}
updateSavingsSchedule(nextInstallment.getInstallmentId(), meetingDates, children);
}
}
private void updateSavingsSchedule(final Short nextInstallmentId, final List<DateTime> meetingDates,
final List<CustomerBO> children) {
short installmentId = nextInstallmentId;
for (int count = 0; count < meetingDates.size(); count++) {
for (CustomerBO customer : children) {
AccountActionDateEntity accountActionDate = getAccountActionDate(installmentId, customer
.getCustomerId());
if (accountActionDate != null) {
Date meetingDate = meetingDates.get(count).toDate();
((SavingsScheduleEntity) accountActionDate).setActionDate(new java.sql.Date(meetingDate.getTime()));
}
}
installmentId++;
}
}
public Money getTotalPaymentDue(final Integer customerId) {
return isMandatory() ? getTotalPaymentDueForManAccount(customerId)
: getTotalPaymentDueForVolAccount(customerId);
}
public List<AccountActionDateEntity> getTotalInstallmentsDue(final Integer customerId) {
return isMandatory() ? getInstallmentsDueForManAccount(customerId) : getInstallmentDueForVolAccount(customerId);
}
private List<AccountActionDateEntity> getInstallmentsDueForManAccount(final Integer customerId) {
List<AccountActionDateEntity> dueInstallements = new ArrayList<AccountActionDateEntity>();
Date currentDate = DateUtils.getCurrentDateWithoutTimeStamp();
if (getAccountActionDates() != null && getAccountActionDates().size() > 0) {
for (AccountActionDateEntity accountAction : getAccountActionDates()) {
if (accountAction.getActionDate().compareTo(currentDate) <= 0 && !accountAction.isPaid()
&& accountAction.getCustomer().getCustomerId().equals(customerId)) {
dueInstallements.add(accountAction);
}
}
}
return dueInstallements;
}
private List<AccountActionDateEntity> getInstallmentDueForVolAccount(final Integer customerId) {
List<AccountActionDateEntity> dueInstallments = new ArrayList<AccountActionDateEntity>();
List<AccountActionDateEntity> installments = getInstallmentsDueForManAccount(customerId);
if (installments != null && installments.size() > 0) {
dueInstallments.add(installments.get(installments.size() - 1));
}
return dueInstallments;
}
private Money getTotalPaymentDueForManAccount(final Integer customerId) {
List<AccountActionDateEntity> dueInstallments = getInstallmentsDueForManAccount(customerId);
Money dueAmount = new Money(getCurrency());
if (dueInstallments != null && dueInstallments.size() > 0) {
for (AccountActionDateEntity installment : dueInstallments) {
dueAmount = dueAmount.add(getDueAmount(installment));
}
}
return dueAmount;
}
private Money getTotalPaymentDueForVolAccount(final Integer customerId) {
List<AccountActionDateEntity> dueInstallments = getInstallmentDueForVolAccount(customerId);
Money dueAmount = new Money(getCurrency());
if (dueInstallments != null && dueInstallments.size() > 0) {
dueAmount = getDueAmount(dueInstallments.get(0));
}
return dueAmount;
}
public void getSavingPerformanceHistory() throws AccountException {
try {
String systemDate = DateUtils.getCurrentDate();
java.sql.Date currentDate = DateUtils.getLocaleDate(systemDate);
getSavingsPerformance().addMissedDeposits(
getSavingsPersistence().getMissedDeposits(getAccountId(), currentDate));
getSavingsPerformance().addMissedDeposits(
getSavingsPersistence().getMissedDepositsPaidAfterDueDate(getAccountId()));
} catch (InvalidDateException ide) {
throw new AccountException(ide);
} catch (PersistenceException e) {
throw new AccountException(e);
}
}
public void generateNextSetOfMeetingDates(final List<Days> workingDays, final List<Holiday> holidays)
throws AccountException {
CustomerBO customerBO = getCustomer();
if (customerBO.getCustomerMeeting() != null && customerBO.getCustomerMeeting().getMeeting() != null) {
MeetingBO depositSchedule = customerBO.getCustomerMeeting().getMeeting();
Date oldMeetingDate = depositSchedule.getStartDate();
Short lastInstallmentId = getLastInstallmentId();
AccountActionDateEntity lastInstallment = getAccountActionDate(lastInstallmentId);
if (lastInstallment == null) { // a special workaround for MIFOS-5107
lastInstallment = new SavingsScheduleEntity(this, this.getCustomer(), (short) 0,
new java.sql.Date(new LocalDate().minusDays(1).toDateMidnight().getMillis()), PaymentStatus.UNPAID,
new Money(Money.getDefaultCurrency(), 0.0));
}
depositSchedule.setMeetingStartDate(lastInstallment.getActionDate());
if (customerBO.getCustomerLevel().getId().equals(CustomerLevel.CLIENT.getValue())
|| customerBO.getCustomerLevel().getId().equals(CustomerLevel.GROUP.getValue())
&& getRecommendedAmntUnit().getId().equals(RecommendedAmountUnit.COMPLETE_GROUP.getValue())) {
generateDepositAccountActions(customerBO, depositSchedule, lastInstallment, workingDays, holidays);
} else {
List<CustomerBO> children;
try {
children = getCustomer().getChildren(CustomerLevel.CLIENT, ChildrenStateType.OTHER_THAN_CLOSED);
} catch (CustomerException ce) {
throw new AccountException(ce);
}
for (CustomerBO customer : children) {
generateDepositAccountActions(customer, depositSchedule, lastInstallment, workingDays, holidays);
}
}
depositSchedule.setStartDate(oldMeetingDate);
}
}
@Override
public Date getNextMeetingDate() {
AccountActionDateEntity nextAccountAction = getDetailsOfNextInstallment();
return nextAccountAction != null ? nextAccountAction.getActionDate() : null;
}
@Override
public boolean isTrxnDateValid(Date trxnDate, Date lastMeetingDate, boolean repaymentIndependentOfMeetingEnabled) {
LocalDate transactionLocalDate = new LocalDate(trxnDate);
LocalDate today = new LocalDate();
if (AccountingRules.isBackDatedTxnAllowed()) {
if (repaymentIndependentOfMeetingEnabled) {
Date activationDate = this.getActivationDate();
return trxnDate.compareTo(DateUtils.getDateWithoutTimeStamp(activationDate)) >= 0;
}
InterestScheduledEvent postingEvent = new SavingsInterestScheduledEventFactory()
.createScheduledEventFrom(this.getInterestPostingMeeting());
LocalDate nextPostingDate = new LocalDate(this.nextIntPostDate);
LocalDate currentPostingPeriodStartDate = postingEvent
.findFirstDateOfPeriodForMatchingDate(nextPostingDate);
if (getInterestPostingMeeting().isDaily()) {
if (lastIntPostDate == null) {
currentPostingPeriodStartDate = new LocalDate(activationDate);
} else {
currentPostingPeriodStartDate = new LocalDate(lastIntPostDate).plusDays(1);
}
}
// FIXME throw an exception with the correct reason instead of returning false
if (transactionLocalDate.isBefore(currentPostingPeriodStartDate)) {
return false;
}
LocalDate activationDate = new LocalDate(this.activationDate);
if (lastMeetingDate != null) {
LocalDate meetingDate = new LocalDate(lastMeetingDate);
return (transactionLocalDate.isAfter(meetingDate) || transactionLocalDate.isEqual(meetingDate))
&& (transactionLocalDate.isAfter(activationDate) || transactionLocalDate.isEqual(activationDate));
}
return (transactionLocalDate.isAfter(activationDate) || transactionLocalDate.isEqual(activationDate))
&& (transactionLocalDate.isBefore(today) || transactionLocalDate.isEqual(today));
}
return transactionLocalDate.isEqual(today);
}
public boolean isOfProductOffering(final SavingsOfferingBO productOffering) {
return savingsOffering.equals(productOffering);
}
private boolean depositAmountIsInExcess(final Money amountRemaining) {
return amountRemaining != null && amountRemaining.isGreaterThanZero();
}
private List<SavingsScheduleEntity> findAllUnpaidInstallmentsForPayingCustomerUpTo(final Date transactionDate,
final Integer customerId) {
final List<SavingsScheduleEntity> customerSchedulePayments = new ArrayList<SavingsScheduleEntity>();
for (AccountActionDateEntity accountActionDateEntity : getAccountActionDates()) {
if (accountActionDateEntity != null && !accountActionDateEntity.isPaid()
&& !accountActionDateEntity.getActionDate().after(transactionDate)) {
if (customerId == accountActionDateEntity.getCustomer().getCustomerId()) {
customerSchedulePayments.add((SavingsScheduleEntity) accountActionDateEntity);
}
}
}
return customerSchedulePayments;
}
/*
* In order to do audit logging, we need to get the name of the PaymentTypeEntity. A new instance constructed with
* the paymentTypeId is not good enough for this, we need to get the lookup value loaded so that we can resolve the
* name of the PaymentTypeEntity.
*/
private PaymentTypeEntity getPaymentTypeEntity(final short paymentTypeId) {
return getSavingsPersistence().loadPersistentObject(PaymentTypeEntity.class, paymentTypeId);
}
@Override
public MeetingBO getMeetingForAccount() {
return getCustomer().getCustomerMeetingValue();
}
public void removeRecommendedAmountOnFutureInstallments() {
Date currentDate = DateUtils.getCurrentDateWithoutTimeStamp();
if (getAccountActionDates() != null && getAccountActionDates().size() > 0) {
for (AccountActionDateEntity accountAction : getAccountActionDates()) {
if (accountAction.getActionDate().compareTo(currentDate) >= 0 && !accountAction.isPaid()) {
SavingsScheduleEntity savingsSchedule = (SavingsScheduleEntity) accountAction;
if (savingsSchedule.getDepositPaid().isGreaterThanZero()) {
savingsSchedule.setDeposit(savingsSchedule.getDepositPaid());
savingsSchedule.setPaymentStatus(PaymentStatus.PAID);
} else {
savingsSchedule.setDeposit(new Money(this.getCurrency()));
}
}
}
}
}
public void resetRecommendedAmountOnFutureInstallments() {
Date currentDate = DateUtils.getCurrentDateWithoutTimeStamp();
if (getAccountActionDates() != null && getAccountActionDates().size() > 0) {
for (AccountActionDateEntity accountAction : getAccountActionDates()) {
if (accountAction.getActionDate().compareTo(currentDate) >= 0 && !accountAction.isPaid()) {
SavingsScheduleEntity savingsSchedule = (SavingsScheduleEntity) accountAction;
savingsSchedule.setDeposit(this.getRecommendedAmount());
}
}
}
}
public MeetingBO getInterestCalculationMeeting() {
return this.savingsOffering.getTimePerForInstcalc().getMeeting();
}
public MeetingBO getInterestPostingMeeting() {
return this.savingsOffering.getFreqOfPostIntcalc().getMeeting();
}
public boolean isGroupModelWithIndividualAccountability() {
return this.customer.isCenter() || (this.customer.isGroup() && this.recommendedAmntUnit.isPerIndividual());
}
public InterestCalcType getInterestCalcType() {
return InterestCalcType.fromInt(this.savingsOffering.getInterestCalcType().getId());
}
public Double getInterestRate() {
return this.savingsOffering.getInterestRate();
}
public Money getMinAmntForInt() {
return this.savingsOffering.getMinAmntForInt();
}
public List<SavingsProductHistoricalInterestDetail> getHistoricalInterestDetailsForPeriod(CalendarPeriod period) {
List<SavingsProductHistoricalInterestDetail> validHistoricalDetails = new ArrayList<SavingsProductHistoricalInterestDetail>();
List<SavingsProductHistoricalInterestDetail> allHistoricalDetails = this.savingsOffering.getHistoricalInterestDetails();
for (SavingsProductHistoricalInterestDetail interestDetail : allHistoricalDetails) {
if (period.contains(interestDetail.getStartDate())) {
validHistoricalDetails.add(interestDetail);
}
}
return validHistoricalDetails;
}
private static Date startOfFiscalYear() {
return new LocalDate().withMonthOfYear(1).withDayOfYear(1).toDateMidnight().toDate();
}
private void setSavingsPerformance(final SavingsPerformanceEntity savingsPerformance) {
this.savingsPerformance = savingsPerformance;
}
private SavingsPerformanceEntity createSavingsPerformance() {
SavingsPerformanceEntity savingsPerformance = new SavingsPerformanceEntity(this);
logger.info("In SavingsBO::createSavingsPerformance(), SavingsPerformanceEntity created successfully ");
return savingsPerformance;
}
public void generateSystemId(String officeGlobalNumber) {
try {
this.globalAccountNum = generateId(officeGlobalNumber);
} catch (AccountException e) {
throw new BusinessRuleException(e.getKey(), e);
}
}
public SavingsAccountDetailDto toDto() {
List<SavingsRecentActivityDto> recentActivity = this.getRecentAccountActivity(3);
List<CustomerNoteDto> recentNoteDtos = new ArrayList<CustomerNoteDto>();
List<AccountNotesEntity> recentNotes = this.getRecentAccountNotes();
for (AccountNotesEntity accountNotesEntity : recentNotes) {
recentNoteDtos.add(new CustomerNoteDto(accountNotesEntity.getCommentDate(), accountNotesEntity.getComment(), accountNotesEntity.getPersonnelName()));
}
SavingsPerformanceHistoryDto savingsPerformanceHistoryDto = new SavingsPerformanceHistoryDto(getActivationDate(),
savingsPerformance.getTotalDeposits().toString(), savingsPerformance.getTotalWithdrawals().toString(),
savingsPerformance.getTotalInterestEarned().toString(),
savingsPerformance.getMissedDeposits() != null ? savingsPerformance.getMissedDeposits().toString() : "0");
AccountActionDateEntity nextInstallment = getDetailsOfNextInstallment();
return new SavingsAccountDetailDto(this.savingsOffering.toFullDto(), recentActivity, recentNoteDtos,
this.recommendedAmount.toString(), this.globalAccountNum, getAccountId(), getState().getValue(), getState().name(), getSavingsBalance().toString(),
nextInstallment != null ? nextInstallment.getActionDate() : null, getTotalAmountDue().toString(), getTotalAmountDueForNextInstallment().toString(),
getTotalAmountInArrears().toString(), savingsPerformanceHistoryDto, this.savingsOffering.getSavingsTypeAsEnum().name(), this.customer.getCustomerId());
}
public void setSavingsTransactionActivityHelper(SavingsTransactionActivityHelper savingsTransactionActivityHelper) {
this.savingsTransactionActivityHelper = savingsTransactionActivityHelper;
}
public void setSavingsPaymentStrategy(SavingsPaymentStrategy savingsPaymentStrategy) {
this.savingsPaymentStrategy = savingsPaymentStrategy;
}
public List<AccountPaymentEntity> getInterestPostingPaymentsForRemoval(Date fromDate) {
List<AccountPaymentEntity> paymentsForRemoval = new ArrayList<AccountPaymentEntity>();
Iterator<AccountPaymentEntity> paymentIter = this.accountPayments.iterator();
while (paymentIter.hasNext()) {
AccountPaymentEntity payment = paymentIter.next();
Date paymentDate = payment.getPaymentDate();
if (payment.isSavingsInterestPosting() && DateUtils.dateFallsOnOrBeforeDate(fromDate, paymentDate)) {
this.nextIntPostDate = paymentDate;
this.savingsBalance = this.savingsBalance.subtract(payment.getAmount());
this.savingsPerformance.substractFromTotalInterestDetails(payment.getAmount());
paymentsForRemoval.add(payment);
paymentIter.remove();
}
}
return paymentsForRemoval;
}
public List<SavingsActivityEntity> getInterestPostingActivitesForRemoval(Date fromDate) {
List<SavingsActivityEntity> activitesForRemoval = new ArrayList<SavingsActivityEntity>();
Iterator<SavingsActivityEntity> activityIter = this.savingsActivityDetails.iterator();
while (activityIter.hasNext()) {
SavingsActivityEntity activity = activityIter.next();
Date activityDate = activity.getTrxnCreatedDate();
if (activity.getActivity().isSavingsInterestPosting() && DateUtils.dateFallsOnOrBeforeDate(fromDate, activityDate)) {
activitesForRemoval.add(activity);
activityIter.remove();
}
}
return activitesForRemoval;
}
public int countInterestPostingsForRecalculation(Date fromDate) {
int postingsForRecalculation = 0;
for (AccountPaymentEntity payment : this.accountPayments) {
if (payment.isSavingsInterestPosting() && DateUtils.dateFallsOnOrBeforeDate(fromDate, payment.getPaymentDate())) {
postingsForRecalculation++;
}
}
return postingsForRecalculation;
}
}