/*
* 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.customers.business;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import org.hibernate.Query;
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.AccountBO;
import org.mifos.accounts.business.AccountFeesActionDetailEntity;
import org.mifos.accounts.business.AccountFeesEntity;
import org.mifos.accounts.business.AccountPaymentEntity;
import org.mifos.accounts.business.AccountTrxnEntity;
import org.mifos.accounts.business.FeesTrxnDetailEntity;
import org.mifos.accounts.exceptions.AccountException;
import org.mifos.accounts.fees.business.AmountFeeBO;
import org.mifos.accounts.fees.business.FeeBO;
import org.mifos.accounts.fees.business.FeeDto;
import org.mifos.accounts.fees.util.helpers.FeeChangeType;
import org.mifos.accounts.fees.util.helpers.FeeStatus;
import org.mifos.accounts.persistence.LegacyAccountDao;
import org.mifos.accounts.util.helpers.AccountActionTypes;
import org.mifos.accounts.util.helpers.AccountConstants;
import org.mifos.accounts.util.helpers.AccountExceptionConstants;
import org.mifos.accounts.util.helpers.AccountState;
import org.mifos.accounts.util.helpers.AccountTypes;
import org.mifos.accounts.util.helpers.FeeInstallment;
import org.mifos.accounts.util.helpers.InstallmentDate;
import org.mifos.accounts.util.helpers.PaymentData;
import org.mifos.accounts.util.helpers.PaymentStatus;
import org.mifos.accounts.util.helpers.WaiveEnum;
import org.mifos.application.NamedQueryConstants;
import org.mifos.application.holiday.business.Holiday;
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.RecurringScheduledEventFactoryImpl;
import org.mifos.core.MifosRuntimeException;
import org.mifos.customers.exceptions.CustomerException;
import org.mifos.customers.group.util.helpers.GroupConstants;
import org.mifos.customers.personnel.business.PersonnelBO;
import org.mifos.customers.personnel.persistence.LegacyPersonnelDao;
import org.mifos.customers.util.helpers.CustomerConstants;
import org.mifos.customers.util.helpers.CustomerStatus;
import org.mifos.framework.components.batchjobs.exceptions.BatchJobException;
import org.mifos.framework.exceptions.PersistenceException;
import org.mifos.framework.exceptions.PropertyNotFoundException;
import org.mifos.framework.hibernate.helper.StaticHibernateUtil;
import org.mifos.framework.util.DateTimeService;
import org.mifos.framework.util.LocalizationConverter;
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.DailyScheduledEvent;
import org.mifos.schedule.internal.HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration;
import org.mifos.security.util.UserContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Clients, groups, and centers are stored in the db as customer accounts.
*/
public class CustomerAccountBO extends AccountBO {
private static final Logger logger = LoggerFactory.getLogger(CustomerAccountBO.class);
private List<CustomerActivityEntity> customerActivitDetails = new ArrayList<CustomerActivityEntity>();
private static int numberOfMeetingDatesToGenerate = 10;
public static CustomerAccountBO createNew(CustomerBO customer, List<AccountFeesEntity> accountFees,
MeetingBO customerMeeting, CalendarEvent applicableCalendarEvents) {
try {
CustomerAccountBO customerAccount = new CustomerAccountBO(customer, accountFees);
if (customer.isActive()) {
DateTime scheduleGenerationStartingFromDate = new DateTime(customer.getCustomerActivationDate());
customerAccount.createSchedulesAndFeeSchedulesForFirstTimeActiveCustomer(customer, accountFees, customerMeeting, applicableCalendarEvents, scheduleGenerationStartingFromDate);
}
return customerAccount;
} catch (AccountException e) {
throw new MifosRuntimeException(e);
}
}
/**
* Create an initial meeting schedule with fees attached, if any.
*
* <p>PostConditions:</p>
*
* <ul>
* <li> <code>numberOfMeetingDatesToGenerateOnCreation</code> {@link CustomerScheduleEntity}s are created
* starting with <code>customerMeeting</code>'s start date, scheduled according to <code>customerMeeting</code>'s
* frequency and recurrence, and subject to rules for scheduling around on working days and around holidays. See
* {@link HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration} for scheduling rules.</li>
* <li> One-time upfront fees are attached to the first meeting.</li>
* <li> Periodic fees are attached to the first meeting and subsequent meetings that match the fee's frequency
* and recurrence</li>
* <li> The <code>lastAppliedDate</code> for each fee is set to the date of the latest meeting to which the fee
* is attached
* </ul>
*/
public void createSchedulesAndFeeSchedulesForFirstTimeActiveCustomer(CustomerBO customer, List<AccountFeesEntity> accountFees, MeetingBO customerMeeting, CalendarEvent applicableCalendarEvents, DateTime scheduleGenerationStartingFrom) {
final ScheduledEvent customerMeetingEvent = new RecurringScheduledEventFactoryImpl().createScheduledEventFrom(customerMeeting);
DateTime beginningFrom = scheduleGenerationStartingFrom;
// synch up generated schedule for center/group/client or group/client hierarchy
CustomerBO upmostParent = upmostParentOf(customer);
if (upmostParent != null) {
LocalDate parentCustomerActiviationDate = new LocalDate(upmostParent.getCustomerActivationDate());
LocalDate childCustomerActiviationDate = new LocalDate(customer.getCustomerActivationDate());
LocalDate validCustomerMeetingMatch = null;
if (customerMeetingEvent instanceof DailyScheduledEvent) {
validCustomerMeetingMatch = new LocalDate(parentCustomerActiviationDate.toDateMidnight().toDateTime());
} else {
validCustomerMeetingMatch = new LocalDate(customerMeetingEvent.nearestMatchNotTakingIntoAccountScheduleFrequency(
parentCustomerActiviationDate.toDateMidnight().toDateTime()));
}
while (childCustomerActiviationDate.isAfter(validCustomerMeetingMatch)) {
validCustomerMeetingMatch = new LocalDate(customerMeetingEvent.rollFrowardDateByFrequency(validCustomerMeetingMatch.toDateMidnight().toDateTime()));
}
beginningFrom = validCustomerMeetingMatch.toDateMidnight().toDateTime();
}
DateTime meetingStartDate = new DateTime(customer.getCustomerMeetingValue().getMeetingStartDate());
if (beginningFrom.isBefore(meetingStartDate)) {
beginningFrom = meetingStartDate;
}
createInitialSetOfCustomerScheduleEntities(customer, beginningFrom, applicableCalendarEvents, customerMeetingEvent);
applyFeesToInitialSetOfInstallments(new ArrayList<AccountFeesEntity>(accountFees), customerMeetingEvent);
}
private CustomerBO upmostParentOf(CustomerBO customer) {
CustomerBO firstParent = customer.getParentCustomer();
CustomerBO upmostParent = firstParent;
if (firstParent != null) {
CustomerBO grandParent = firstParent.getParentCustomer();
if (grandParent != null) {
upmostParent = grandParent;
}
}
return upmostParent;
}
private void createInitialSetOfCustomerScheduleEntities (CustomerBO customer, DateTime meetingStartDate, CalendarEvent calendarEvents, final ScheduledEvent scheduledEvent) {
List<InstallmentDate> withHolidayInstallmentDates = this.generateInitialInstallmentDates(meetingStartDate, calendarEvents, scheduledEvent);
for (InstallmentDate installmentDate : withHolidayInstallmentDates) {
this.addAccountActionDate(new CustomerScheduleEntity(
this,
customer,
installmentDate.getInstallmentId(),
new java.sql.Date(installmentDate.getInstallmentDueDate().getTime()),
PaymentStatus.UNPAID));
}
}
private void applyFeesToInitialSetOfInstallments (List<AccountFeesEntity> accountFees, final ScheduledEvent scheduledEvent) {
List<FeeInstallment> mergedFeeInstallments = FeeInstallment.createMergedFeeInstallments(scheduledEvent, accountFees, numberOfMeetingDatesToGenerate);
for (AccountActionDateEntity accountAction : this.getAccountActionDates()) {
this.applyFeesToScheduledEvent((CustomerScheduleEntity) accountAction, mergedFeeInstallments);
}
this.setLastAppliedDatesForFees(accountFees);
}
private List<InstallmentDate> generateInitialInstallmentDates(DateTime startingFrom, CalendarEvent calendarEvents, ScheduledEvent meetingEvent) {
ScheduledDateGeneration dateGeneration = new HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration(calendarEvents.getWorkingDays(), calendarEvents.getHolidays());
List<DateTime> meetingDates = dateGeneration.generateScheduledDates(numberOfMeetingDatesToGenerate, startingFrom, meetingEvent, true);
return InstallmentDate.createInstallmentDates(meetingDates);
}
private void applyFeesToScheduledEvent(CustomerScheduleEntity customerScheduleEntity,
List<FeeInstallment> mergedFeeInstallments) {
for (FeeInstallment feeInstallment : mergedFeeInstallments) {
if (feeInstallment.getInstallmentId().equals(customerScheduleEntity.getInstallmentId())) {
CustomerFeeScheduleEntity customerFeeScheduleEntity = new CustomerFeeScheduleEntity(
customerScheduleEntity, feeInstallment.getAccountFeesEntity().getFees(), feeInstallment
.getAccountFeesEntity(), feeInstallment.getAccountFee());
customerScheduleEntity.addAccountFeesAction(customerFeeScheduleEntity);
}
}
}
private void setLastAppliedDatesForFees(List<AccountFeesEntity> accountFees) {
for (AccountFeesEntity accountFeeEntity : accountFees) {
accountFeeEntity.setLastAppliedDate(this.getLatestAppliedDateForFee (accountFeeEntity));
}
}
/**
* Return the latest action date for which
* the given fee was applied, if any, otherwise return null.
*
* @param accountFeeEntity the given fee we're searching for among all action dates
* @return null if the fee has not been applied, otherwise the latest date to which the fee installment was applied.
*/
private Date getLatestAppliedDateForFee (AccountFeesEntity accountFeeEntity) {
Date latestAppliedDate = null;
for (AccountActionDateEntity event : this.getAccountActionDates()) {
CustomerScheduleEntity customerEvent = (CustomerScheduleEntity) event;
for (AccountFeesActionDetailEntity feeActionDetail : customerEvent.getAccountFeesActionDetails()) {
if (feeActionDetail.getAccountFee().equals(accountFeeEntity)) {
Date actionDate = customerEvent.getActionDate();
if ((latestAppliedDate == null) || (actionDate.compareTo(latestAppliedDate) > 0)) {
latestAppliedDate = actionDate;
}
}
}
}
return latestAppliedDate;
}
/**
* default constructor for hibernate usage
*/
protected CustomerAccountBO() {
super();
}
private CustomerAccountBO(CustomerBO customer, List<AccountFeesEntity> accountFees) throws AccountException {
super(customer.getUserContext(), customer, AccountTypes.CUSTOMER_ACCOUNT, AccountState.CUSTOMER_ACCOUNT_ACTIVE);
for (AccountFeesEntity accountFee : accountFees) {
accountFee.setAccount(this);
this.addAccountFees(accountFee);
}
}
/**
* @deprecated - use static factory methods for creating {@link CustomerAccountBO}.
*/
@Deprecated
public CustomerAccountBO(final UserContext userContext, final CustomerBO customer, final List<FeeDto> fees)
throws AccountException {
super(userContext, customer, AccountTypes.CUSTOMER_ACCOUNT, AccountState.CUSTOMER_ACCOUNT_ACTIVE);
if (fees != null) {
for (FeeDto feeDto : fees) {
FeeBO fee = getFeeDao().findById(feeDto.getFeeIdValue());
this.addAccountFees(new AccountFeesEntity(this, fee, new LocalizationConverter()
.getDoubleValueForCurrentLocale(feeDto.getAmount())));
}
generateCustomerFeeSchedule(customer);
}
}
@Override
public AccountTypes getType() {
return AccountTypes.CUSTOMER_ACCOUNT;
}
public List<CustomerActivityEntity> getCustomerActivitDetails() {
return customerActivitDetails;
}
@SuppressWarnings("unused")
// see .hbm.xml file
private void setCustomerActivitDetails(final List<CustomerActivityEntity> customerActivitDetails) {
this.customerActivitDetails = customerActivitDetails;
}
public void addCustomerActivity(final CustomerActivityEntity customerActivityEntity) {
customerActivitDetails.add(customerActivityEntity);
}
private BigDecimal dueAmountForCustomerSchedule(CustomerScheduleEntity customerSchedule) {
BigDecimal totalAllUnpaidInstallments = customerSchedule.getMiscFeeDue().getAmount();
totalAllUnpaidInstallments = totalAllUnpaidInstallments.add(customerSchedule.getMiscPenaltyDue().getAmount());
for (AccountFeesActionDetailEntity accountFeesActionDetail : customerSchedule.getAccountFeesActionDetails()) {
CustomerFeeScheduleEntity customerFeeSchedule = (CustomerFeeScheduleEntity) accountFeesActionDetail;
totalAllUnpaidInstallments = totalAllUnpaidInstallments.add(customerFeeSchedule.getFeeDue().getAmount());
}
return totalAllUnpaidInstallments;
}
@Override
protected AccountPaymentEntity makePayment(final PaymentData paymentData) throws AccountException {
Money totalPaid = paymentData.getTotalAmount();
if (totalPaid.isZero()) {
throw new AccountException("errors.update",
new String[] { "Attempting to pay a customer account balance of zero for customer: "
+ paymentData.getCustomer().getGlobalCustNum() });
}
final List<CustomerScheduleEntity> customerAccountPayments = findAllUnpaidInstallmentsUpToDatePlusNextMeeting(paymentData
.getTransactionDate());
if (customerAccountPayments.isEmpty()) {
throw new AccountException(AccountConstants.NO_TRANSACTION_POSSIBLE, new String[] {"Trying to pay account charges before the due date."});
}
Money totalAllUnpaidInstallments = new Money(totalPaid.getCurrency(), "0.0");
for (CustomerScheduleEntity customerSchedule : customerAccountPayments) {
totalAllUnpaidInstallments = totalAllUnpaidInstallments.add(new Money(totalPaid.getCurrency(), dueAmountForCustomerSchedule(customerSchedule)));
}
if (totalAllUnpaidInstallments.compareTo(totalPaid) < 0) {
throw new AccountException(AccountConstants.NO_TRANSACTION_POSSIBLE, new String[] {"Overpayments are not supported"});
}
final AccountPaymentEntity accountPayment = new AccountPaymentEntity(this, paymentData.getTotalAmount(),
paymentData.getReceiptNum(), paymentData.getReceiptDate(), getPaymentTypeEntity(paymentData
.getPaymentTypeId()), paymentData.getTransactionDate());
BigDecimal leftFromPaidIn = totalPaid.getAmount();
for (CustomerScheduleEntity customerSchedule : customerAccountPayments) {
if (leftFromPaidIn.compareTo(BigDecimal.ZERO) == 0) {
break;
}
final List<FeesTrxnDetailEntity> feeTrxns = new ArrayList<FeesTrxnDetailEntity>();
for (AccountFeesActionDetailEntity accountFeesActionDetail : customerSchedule.getAccountFeesActionDetails()) {
if (leftFromPaidIn.compareTo(BigDecimal.ZERO) > 0) {
CustomerFeeScheduleEntity customerFeeSchedule = (CustomerFeeScheduleEntity) accountFeesActionDetail;
BigDecimal feeFromScheduleToPay = leftFromPaidIn.min(customerFeeSchedule.getFeeDue().getAmount());
customerFeeSchedule.makePayment(new Money(totalPaid.getCurrency(), feeFromScheduleToPay));
final FeesTrxnDetailEntity feesTrxnDetailBO = new FeesTrxnDetailEntity(null, customerFeeSchedule
.getAccountFee(), new Money(totalPaid.getCurrency(), feeFromScheduleToPay));
feeTrxns.add(feesTrxnDetailBO);
leftFromPaidIn = leftFromPaidIn.subtract(feeFromScheduleToPay);
}
}
BigDecimal miscPenaltyToPay = leftFromPaidIn.min(customerSchedule.getMiscPenaltyDue().getAmount());
if (miscPenaltyToPay.compareTo(BigDecimal.ZERO) > 0) {
customerSchedule.payMiscPenalty(new Money(totalPaid.getCurrency(), miscPenaltyToPay));
customerSchedule.setPaymentDate(new java.sql.Date(paymentData.getTransactionDate().getTime()));
leftFromPaidIn = leftFromPaidIn.subtract(miscPenaltyToPay);
}
BigDecimal miscFeeToPay = BigDecimal.ZERO;
if (leftFromPaidIn.compareTo(BigDecimal.ZERO) > 0) {
miscFeeToPay = leftFromPaidIn.min(customerSchedule.getMiscFeeDue().getAmount());
if (miscFeeToPay.compareTo(BigDecimal.ZERO) > 0) {
customerSchedule.payMiscFee(new Money(totalPaid.getCurrency(), miscFeeToPay));
customerSchedule.setPaymentDate(new java.sql.Date(paymentData.getTransactionDate().getTime()));
leftFromPaidIn = leftFromPaidIn.subtract(miscFeeToPay);
}
}
if (dueAmountForCustomerSchedule(customerSchedule).compareTo(BigDecimal.ZERO) == 0) {
customerSchedule.setPaymentStatus(PaymentStatus.PAID);
}
Money customerScheduleAmountPaid = new Money(totalPaid.getCurrency(), miscFeeToPay.add(miscPenaltyToPay));
final CustomerTrxnDetailEntity accountTrxn = new CustomerTrxnDetailEntity(accountPayment,
AccountActionTypes.CUSTOMER_ACCOUNT_REPAYMENT, customerSchedule.getInstallmentId(),
customerSchedule.getActionDate(), paymentData.getPersonnel(), paymentData.getTransactionDate(),
customerScheduleAmountPaid, AccountConstants.PAYMENT_RCVD, null, new Money(totalPaid.getCurrency(), miscFeeToPay),
new Money(totalPaid.getCurrency(), miscPenaltyToPay));
for (FeesTrxnDetailEntity feesTrxnDetailEntity : feeTrxns) {
accountTrxn.addFeesTrxnDetail(feesTrxnDetailEntity);
feesTrxnDetailEntity.setAccountTrxn(accountTrxn);
}
accountPayment.addAccountTrxn(accountTrxn);
}
addCustomerActivity(new CustomerActivityEntity(this, paymentData.getPersonnel(), paymentData.getTotalAmount(),
AccountConstants.PAYMENT_RCVD, paymentData.getTransactionDate()));
return accountPayment;
}
@Override
public boolean isAdjustPossibleOnLastTrxn() {
if (!getCustomer().isActive()) {
logger.debug(
"State is not active hence adjustment is not possible");
return false;
}
logger.debug(
"Total payments on this account is " + getAccountPayments().size());
if (null == findMostRecentNonzeroPaymentByPaymentDate()) {
return false;
}
logger.debug("Adjustment is possible");
return true;
}
@Override
protected void updateInstallmentAfterAdjustment(final List<AccountTrxnEntity> reversedTrxns, PersonnelBO loggedInUser)
throws AccountException {
if (null != reversedTrxns && reversedTrxns.size() > 0) {
Money totalAmountAdj = new Money(getCurrency());
for (AccountTrxnEntity accntTrxn : reversedTrxns) {
CustomerTrxnDetailEntity custTrxn = (CustomerTrxnDetailEntity) accntTrxn;
CustomerScheduleEntity accntActionDate = (CustomerScheduleEntity) getAccountActionDate(custTrxn
.getInstallmentId());
accntActionDate.setPaymentStatus(PaymentStatus.UNPAID);
accntActionDate.setPaymentDate(null);
accntActionDate.setMiscFeePaid(accntActionDate.getMiscFeePaid().add(custTrxn.getMiscFeeAmount()));
totalAmountAdj = totalAmountAdj.add(removeSign(custTrxn.getMiscFeeAmount()));
accntActionDate.setMiscPenaltyPaid(accntActionDate.getMiscPenaltyPaid().add(
custTrxn.getMiscPenaltyAmount()));
totalAmountAdj = totalAmountAdj.add(removeSign(custTrxn.getMiscPenaltyAmount()));
if (null != accntActionDate.getAccountFeesActionDetails()
&& accntActionDate.getAccountFeesActionDetails().size() > 0) {
for (AccountFeesActionDetailEntity accntFeesAction : accntActionDate.getAccountFeesActionDetails()) {
Money feeAmntAdjusted = custTrxn.getFeesTrxn(accntFeesAction.getAccountFee().getAccountFeeId())
.getFeeAmount();
((CustomerFeeScheduleEntity) accntFeesAction).setFeeAmountPaid(accntFeesAction
.getFeeAmountPaid().add(feeAmntAdjusted));
totalAmountAdj = totalAmountAdj.add(removeSign(feeAmntAdjusted));
}
}
}
addCustomerActivity(buildCustomerActivity(totalAmountAdj, AccountConstants.AMNT_ADJUSTED, userContext
.getId()));
}
}
public void waiveAmountDue() throws AccountException {
AccountActionDateEntity accountActionDateEntity = getUpcomingInstallment();
Money chargeWaived = ((CustomerScheduleEntity) accountActionDateEntity).waiveCharges();
if (chargeWaived != null && chargeWaived.isGreaterThanZero()) {
addCustomerActivity(buildCustomerActivity(chargeWaived, AccountConstants.AMNT_WAIVED, userContext.getId()));
}
}
@Override
public void waiveAmountOverDue(@SuppressWarnings("unused") final WaiveEnum chargeType) throws AccountException {
Money chargeWaived = new Money(getCurrency());
List<AccountActionDateEntity> accountActionDateList = getApplicableIdsForNextInstallmentAndArrears();
accountActionDateList.remove(accountActionDateList.size() - 1);
for (AccountActionDateEntity accountActionDateEntity : accountActionDateList) {
chargeWaived = chargeWaived.add(((CustomerScheduleEntity) accountActionDateEntity).waiveCharges());
}
if (chargeWaived != null && chargeWaived.isGreaterThanZero()) {
addCustomerActivity(buildCustomerActivity(chargeWaived, AccountConstants.AMNT_WAIVED, userContext.getId()));
}
}
public void applyPeriodicFeesToNewSchedule () {
for (AccountFeesEntity accountFee : getPeriodicFeeList()) {
applyOnePeriodicFeeToInstallments(accountFee, getAccountActionDatesSortedByInstallmentId());
}
}
public void applyPeriodicFeesToNextSetOfMeetingDates () {
for (AccountFeesEntity accountFee : getPeriodicFeeList()) {
applyOnePeriodicFeeToInstallments(accountFee, getInstallmentsAfterLatestInstallmentThatFeeWasAppliedTo (accountFee));
}
}
private void applyOnePeriodicFeeToInstallments(AccountFeesEntity accountFee, List<AccountActionDateEntity> actionDateEntities) {
if (actionDateEntities.size() > 0) {
ScheduledEvent scheduledEvent = ScheduledEventFactory.createScheduledEventFrom(this.getMeetingForAccount());
List<FeeInstallment> feeInstallmentList
= FeeInstallment
.createMergedFeeInstallmentsForOneFee(scheduledEvent, accountFee, getAccountActionDates().size());
applyFeeToInstallments(feeInstallmentList, actionDateEntities);
}
}
private List<AccountActionDateEntity> getInstallmentsAfterLatestInstallmentThatFeeWasAppliedTo (AccountFeesEntity accountFee) {
List<AccountActionDateEntity> installmentsToApply = new ArrayList<AccountActionDateEntity>();
List<AccountActionDateEntity> allInstallments = getAccountActionDatesSortedByInstallmentId();
for (int installmentId = getLatestInstallmentFeeIsAppliedTo(accountFee) + 1; installmentId <= allInstallments.size(); installmentId++) {
installmentsToApply.add(getAccountActionDate((short) installmentId));
}
return installmentsToApply;
}
short getLatestInstallmentFeeIsAppliedTo (AccountFeesEntity accountFee) {
List<AccountActionDateEntity> allInstallments = getAccountActionDatesSortedByInstallmentId();
for (int installmentId = allInstallments.size(); installmentId >=1; installmentId--) {
CustomerScheduleEntity scheduleEntity = (CustomerScheduleEntity) getAccountActionDate((short) installmentId);
if (feeIsAppliedTo(scheduleEntity, accountFee)) {
return (short) installmentId;
}
}
throw new MifosRuntimeException("Fee is attached to this customer but has never been applied to a scheduled event");
}
boolean feeIsAppliedTo(CustomerScheduleEntity scheduleEntity, AccountFeesEntity accountFee) {
for (AccountFeesActionDetailEntity feeActionDetail : scheduleEntity.getAccountFeesActionDetails()) {
if (feeActionDetail.getAccountFee().getAccountFeeId().equals(accountFee.getAccountFeeId())) {
// if (feeActionDetail.getAccountFee().equals(accountFee)) {
return true;
}
}
return false;
}
private CustomerActivityEntity buildCustomerActivity(final Money amount, final String description,
final Short personnelId) throws AccountException {
try {
PersonnelBO personnel = null;
if (personnelId != null) {
personnel = ApplicationContextProvider.getBean(LegacyPersonnelDao.class).getPersonnel(personnelId);
}
return new CustomerActivityEntity(this, personnel, amount, description, new DateTimeService()
.getCurrentJavaDateTime());
} catch (PersistenceException e) {
throw new AccountException(e);
}
}
@Override
public void updateAccountActivity(@SuppressWarnings("unused") final Money principal,
@SuppressWarnings("unused") final Money interest, final Money fee,
@SuppressWarnings("unused") final Money penalty, final Short personnelId, final String description)
throws AccountException {
addCustomerActivity(buildCustomerActivity(fee, description, personnelId));
}
@Override
public final void removeFeesAssociatedWithUpcomingAndAllKnownFutureInstallments(final Short feeId, final Short personnelId) throws AccountException {
List<Short> installmentIds = getApplicableInstallmentIdsForRemoveFees();
if (installmentIds != null && installmentIds.size() != 0 && isFeeActive(feeId)) {
updateAccountActionDateEntity(installmentIds, feeId);
}
FeeBO feesBO = getAccountFeesObject(feeId);
updateAccountFeesEntity(feeId);
String description = feesBO.getFeeName() + " " + AccountConstants.FEES_REMOVED;
updateAccountActivity(null, null, null, null, personnelId, description);
}
@Override
protected Money getDueAmount(final AccountActionDateEntity installment) {
return ((CustomerScheduleEntity) installment).getTotalDueWithFees();
}
@Override
protected void regenerateFutureInstallments(final AccountActionDateEntity nextInstallment, final List<Days> workingDays, final List<Holiday> holidays) throws AccountException {
int numberOfInstallmentsToGenerate = getLastInstallmentId();
MeetingBO meeting = getMeetingForAccount();
ScheduledEvent scheduledEvent = ScheduledEventFactory.createScheduledEventFrom(meeting);
LocalDate currentDate = new LocalDate();
LocalDate thisIntervalStartDate = meeting.startDateForMeetingInterval(currentDate);
LocalDate nextMatchingDate = new LocalDate(scheduledEvent.nextEventDateAfter(thisIntervalStartDate.toDateTimeAtStartOfDay()));
DateTime futureIntervalStartDate = meeting.startDateForMeetingInterval(nextMatchingDate).toDateTimeAtStartOfDay();
ScheduledDateGeneration dateGeneration = new HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration(workingDays, holidays);
List<DateTime> meetingDates = dateGeneration.generateScheduledDates(numberOfInstallmentsToGenerate, futureIntervalStartDate, scheduledEvent, true);
updateSchedule(nextInstallment.getInstallmentId(), meetingDates);
}
private List<CustomerScheduleEntity> findAllUnpaidInstallmentsUpToDatePlusNextMeeting(final Date transactionDate) {
List<AccountActionDateEntity> unpaidDates = new ArrayList<AccountActionDateEntity>();
for (AccountActionDateEntity accountActionDateEntity : getAccountActionDates()) {
if (accountActionDateEntity != null && !accountActionDateEntity.isPaid()) {
unpaidDates.add(accountActionDateEntity);
}
}
final List<CustomerScheduleEntity> customerSchedulePayments = new ArrayList<CustomerScheduleEntity>();
for (AccountActionDateEntity accountActionDateEntity : unpaidDates) {
if (!accountActionDateEntity.getActionDate().after(transactionDate)) {
customerSchedulePayments.add((CustomerScheduleEntity) accountActionDateEntity);
}
}
AccountActionDateEntity nextMeeting = null;
for (AccountActionDateEntity accountActionDateEntity : getAccountActionDates()) {
if (accountActionDateEntity != null && accountActionDateEntity.getActionDate().after(transactionDate)) {
if (nextMeeting == null || nextMeeting.getActionDate().after(accountActionDateEntity.getActionDate())) {
nextMeeting = accountActionDateEntity;
}
}
}
if (nextMeeting != null && !nextMeeting.isPaid()) {
customerSchedulePayments.add((CustomerScheduleEntity)nextMeeting);
}
Collections.sort(customerSchedulePayments, new Comparator<CustomerScheduleEntity> () {
@Override
public int compare(CustomerScheduleEntity o1, CustomerScheduleEntity o2) {
return o1.getActionDate().compareTo(o2.getActionDate());
}
});
return customerSchedulePayments;
}
@Override
public Money getTotalAmountInArrears() {
Map<String, Object> queryParameters = new HashMap<String, Object>();
queryParameters.put("ACTION_DATE", DateUtils.getCurrentDateWithoutTimeStamp());
queryParameters.put("CUSTOMER_ID", getAccountId());
Query query = StaticHibernateUtil.getSessionTL().getNamedQuery(NamedQueryConstants.RETRIEVE_TOTAL_AMOUNT_IN_ARREARS);
query.setProperties(queryParameters);
BigDecimal total = (BigDecimal) query.uniqueResult();
if (total == null) {
total = new BigDecimal(0);
}
return new Money(getCurrency(), total);
}
@Override
public Money getTotalPaymentDue() {
Money totalAmt = getTotalAmountInArrears();
AccountActionDateEntity nextInstallment = getDetailsOfNextInstallment();
if (nextInstallment != null && !nextInstallment.isPaid()) {
totalAmt = totalAmt.add(getDueAmount(nextInstallment));
}
return totalAmt;
}
public void generateNextSetOfMeetingDates(ScheduledDateGeneration scheduleGenerationStrategy) {
Short lastInstallmentId = Short.valueOf("0");
if (getLastInstallmentId() != null) {
lastInstallmentId = getLastInstallmentId();
}
AccountActionDateEntity lastInstallment = getAccountActionDate(lastInstallmentId);
MeetingBO meeting = getCustomer().getCustomerMeetingValue();
ScheduledEvent scheduledEvent = ScheduledEventFactory.createScheduledEventFrom(meeting);
Date lastInstallmentDate = new Date();
if (lastInstallment != null) {
lastInstallmentDate = lastInstallment.getActionDate();
}
/*
* Generate more scheduled dates starting with the date of the last generated installment.
* This ensures that the customer's meeting recurrence is taken into account. But then
* skip the first date when adding account actions because it's already there.
*/
DateTime dateOfLastInstallment = new DateTime(lastInstallmentDate).toDateMidnight().toDateTime();
List<DateTime> scheduledDates = scheduleGenerationStrategy.generateScheduledDates(numberOfMeetingDatesToGenerate + 1, dateOfLastInstallment, scheduledEvent, true);
int count = 1;
for (DateTime installmentDate : allButFirst(scheduledDates)) {
CustomerScheduleEntity customerScheduleEntity = new CustomerScheduleEntity(this, getCustomer(), Short
.valueOf(String.valueOf(count + lastInstallmentId)),
new java.sql.Date(installmentDate.toDate().getTime()),
PaymentStatus.UNPAID);
count++;
addAccountActionDate(customerScheduleEntity);
}
applyPeriodicFeesToNextSetOfMeetingDates();
}
private List<DateTime> allButFirst(List<DateTime> scheduledDates) {
List<DateTime> scheduledDatesButFirst = new ArrayList<DateTime>();
for (int i = 1; i < scheduledDates.size(); i++) {
scheduledDatesButFirst.add(scheduledDates.get(i));
}
return scheduledDatesButFirst;
}
@Override
public Money updateAccountActionDateEntity(final List<Short> intallmentIdList, final Short feeId) {
Money totalFeeAmount = new Money(getCurrency());
Set<AccountActionDateEntity> accountActionDateEntitySet = this.getAccountActionDates();
for (AccountActionDateEntity accountActionDateEntity : accountActionDateEntitySet) {
if (intallmentIdList.contains(accountActionDateEntity.getInstallmentId())) {
totalFeeAmount = totalFeeAmount.add(((CustomerScheduleEntity) accountActionDateEntity)
.removeFees(feeId));
}
}
return totalFeeAmount;
}
@Override
public void applyCharge(final Short feeId, final Double charge) throws AccountException {
if (!isCustomerValid()) {
if (feeId.equals(Short.valueOf(AccountConstants.MISC_FEES))
|| feeId.equals(Short.valueOf(AccountConstants.MISC_PENALTY))) {
throw new AccountException(AccountConstants.MISC_CHARGE_NOT_APPLICABLE);
}
addFeeToAccountFee(feeId, charge);
FeeBO fee = getFeeDao().findById(feeId);
updateCustomerActivity(feeId,
new Money(((AmountFeeBO) fee).getFeeAmount().getCurrency(), charge.toString()), fee.getFeeName()
+ AccountConstants.APPLIED);
} else {
Money chargeAmount = new Money(getCurrency(), String.valueOf(charge));
List<AccountActionDateEntity> dueInstallments = null;
if (feeId.equals(Short.valueOf(AccountConstants.MISC_FEES))
|| feeId.equals(Short.valueOf(AccountConstants.MISC_PENALTY))) {
dueInstallments = getTotalDueInstallments();
if (dueInstallments.isEmpty()) {
throw new AccountException(AccountConstants.NOMOREINSTALLMENTS);
}
applyMiscCharge(feeId, chargeAmount, dueInstallments.get(0));
} else {
dueInstallments = getTotalDueInstallments();
if (dueInstallments.isEmpty()) {
throw new AccountException(AccountConstants.NOMOREINSTALLMENTS);
}
FeeBO fee = getFeeDao().findById(feeId);
if (fee.getFeeFrequency().getFeePayment() != null) {
applyOneTimeFee(fee, chargeAmount);
} else {
applyPeriodicFee(fee, chargeAmount);
}
}
}
}
void applyOneTimeFee(final FeeBO fee, final Money chargeAmount) throws AccountException {
applyOneTimeFee (fee, chargeAmount, getTotalDueInstallments().get(0));
}
void applyPeriodicFee (final FeeBO fee, Money chargeAmount) throws AccountException {
applyPeriodicFee (fee, chargeAmount, getTotalDueInstallments());
}
public Date getUpcomingChargesDate() {
AccountActionDateEntity nextAccountAction = getNextUnpaidDueInstallment();
return nextAccountAction != null ? nextAccountAction.getActionDate() : new DateTimeService()
.getCurrentJavaSqlDate();
}
@Override
public Money getTotalAmountDue() {
Money totalAmt = getTotalAmountInArrears();
List<AccountActionDateEntity> dueActionDateList = getTotalDueInstallments();
if (dueActionDateList.size() > 0) {
AccountActionDateEntity nextInstallment = dueActionDateList.get(0);
totalAmt = totalAmt.add(getDueAmount(nextInstallment));
}
return totalAmt;
}
public AccountActionDateEntity getUpcomingInstallment() {
List<AccountActionDateEntity> dueActionDateList = getTotalDueInstallments();
if (dueActionDateList.size() > 0) {
return dueActionDateList.get(0);
}
return null;
}
private void addFeeToAccountFee(final Short feeId, final Double charge) {
FeeBO fee = getFeeDao().findById(feeId);
AccountFeesEntity accountFee = null;
if (fee.isPeriodic() && !isFeeAlreadyApplied(fee) || !fee.isPeriodic()) {
accountFee = new AccountFeesEntity(this, fee, charge, FeeStatus.ACTIVE.getValue(), new DateTimeService()
.getCurrentJavaDateTime(), null);
addAccountFees(accountFee);
} else {
accountFee = getAccountFees(fee.getFeeId());
accountFee.setFeeAmount(charge);
accountFee.setFeeStatus(FeeStatus.ACTIVE);
accountFee.setStatusChangeDate(new DateTimeService().getCurrentJavaDateTime());
}
}
/*
* Package-level visibility for testing
*/
void applyPeriodicFee(final FeeBO fee, final Money charge,
final List<AccountActionDateEntity> dueInstallments) throws AccountException {
AccountFeesEntity accountFee = getAccountFee(fee, charge.getAmountDoubleValue());
accountFee.setAccountFeeAmount(charge);
List<InstallmentDate> installmentDates = new ArrayList<InstallmentDate>();
for (AccountActionDateEntity accountActionDateEntity : dueInstallments) {
installmentDates.add(new InstallmentDate(accountActionDateEntity.getInstallmentId(),
accountActionDateEntity.getActionDate()));
}
// List<FeeInstallment> feeInstallmentList = mergeFeeInstallments(handlePeriodic(accountFee, installmentDates));
ScheduledEvent loanScheduledEvent = ScheduledEventFactory.createScheduledEventFrom(this.getMeetingForAccount());
List<FeeInstallment> feeInstallmentList
= FeeInstallment.createMergedFeeInstallmentsForOneFeeStartingWith(loanScheduledEvent,
accountFee,
dueInstallments.size(),
dueInstallments.get(0).getInstallmentId());
// MIFOS-3701: we want to display only fee charge, not the totalFeeAmountApplied
applyFeeToInstallments(feeInstallmentList, dueInstallments);
updateCustomerActivity(fee.getFeeId(), charge, fee.getFeeName() + AccountConstants.APPLIED);
accountFee.setFeeStatus(FeeStatus.ACTIVE);
}
private void applyOneTimeFee(final FeeBO fee, final Money charge,
final AccountActionDateEntity accountActionDateEntity) throws AccountException {
CustomerScheduleEntity customerScheduleEntity = (CustomerScheduleEntity) accountActionDateEntity;
AccountFeesEntity accountFee = new AccountFeesEntity(this, fee, charge.getAmountDoubleValue(), FeeStatus.ACTIVE
.getValue(), new DateTimeService().getCurrentJavaDateTime(), null);
List<AccountActionDateEntity> customerScheduleList = new ArrayList<AccountActionDateEntity>();
customerScheduleList.add(customerScheduleEntity);
List<InstallmentDate> installmentDates = new ArrayList<InstallmentDate>();
installmentDates.add(new InstallmentDate(accountActionDateEntity.getInstallmentId(), accountActionDateEntity
.getActionDate()));
List<FeeInstallment> feeInstallmentList = new ArrayList<FeeInstallment>();
feeInstallmentList.add(handleOneTime(accountFee, installmentDates));
Money totalFeeAmountApplied = applyFeeToInstallments(feeInstallmentList, customerScheduleList);
updateCustomerActivity(fee.getFeeId(), totalFeeAmountApplied, fee.getFeeName() + AccountConstants.APPLIED);
accountFee.setFeeStatus(FeeStatus.ACTIVE);
}
private void applyMiscCharge(final Short chargeType, final Money charge,
final AccountActionDateEntity accountActionDateEntity) throws AccountException {
CustomerScheduleEntity customerScheduleEntity = (CustomerScheduleEntity) accountActionDateEntity;
customerScheduleEntity.applyMiscCharge(chargeType, charge);
updateCustomerActivity(chargeType, charge, "");
}
private void updateCustomerActivity(final Short chargeType, final Money charge, final String comments)
throws AccountException {
try {
PersonnelBO personnel = ApplicationContextProvider.getBean(LegacyPersonnelDao.class).getPersonnel(getUserContext().getId());
CustomerActivityEntity customerActivityEntity = null;
if (chargeType != null && chargeType.equals(Short.valueOf(AccountConstants.MISC_PENALTY))) {
customerActivityEntity = new CustomerActivityEntity(this, personnel, charge,
AccountConstants.MISC_PENALTY_APPLIED, new DateTimeService().getCurrentJavaDateTime());
} else if (chargeType != null && chargeType.equals(Short.valueOf(AccountConstants.MISC_FEES))) {
customerActivityEntity = new CustomerActivityEntity(this, personnel, charge,
AccountConstants.MISC_FEES_APPLIED, new DateTimeService().getCurrentJavaDateTime());
} else {
customerActivityEntity = new CustomerActivityEntity(this, personnel, charge, comments,
new DateTimeService().getCurrentJavaDateTime());
}
addCustomerActivity(customerActivityEntity);
} catch (PersistenceException e) {
throw new AccountException(e);
}
}
private Money applyFeeToInstallments(final List<FeeInstallment> feeInstallmentList,
final List<AccountActionDateEntity> accountActionDateList) {
Date lastAppliedDate = null;
Money totalFeeAmountApplied = new Money(getCurrency());
AccountFeesEntity accountFeesEntity = null;
for (AccountActionDateEntity accountActionDateEntity : accountActionDateList) {
CustomerScheduleEntity customerScheduleEntity = (CustomerScheduleEntity) accountActionDateEntity;
for (FeeInstallment feeInstallment : feeInstallmentList) {
if (feeInstallment.getInstallmentId().equals(customerScheduleEntity.getInstallmentId())) {
lastAppliedDate = customerScheduleEntity.getActionDate();
totalFeeAmountApplied = totalFeeAmountApplied.add(feeInstallment.getAccountFee());
AccountFeesActionDetailEntity accountFeesActionDetailEntity = new CustomerFeeScheduleEntity(
customerScheduleEntity, feeInstallment.getAccountFeesEntity().getFees(), feeInstallment
.getAccountFeesEntity(), feeInstallment.getAccountFee());
customerScheduleEntity.addAccountFeesAction(accountFeesActionDetailEntity);
accountFeesEntity = feeInstallment.getAccountFeesEntity();
}
}
}
if (accountFeesEntity != null) {
accountFeesEntity.setLastAppliedDate(lastAppliedDate);
addAccountFees(accountFeesEntity);
}
return totalFeeAmountApplied;
}
private boolean isCustomerValid() {
if (getCustomer().getCustomerStatus().getId().equals(CustomerStatus.CENTER_ACTIVE.getValue())
|| getCustomer().getCustomerStatus().getId().equals(CustomerConstants.GROUP_ACTIVE_STATE)
|| getCustomer().getCustomerStatus().getId().equals(GroupConstants.HOLD)
|| getCustomer().getCustomerStatus().getId().equals(CustomerConstants.CLIENT_APPROVED)
|| getCustomer().getCustomerStatus().getId().equals(CustomerConstants.CLIENT_ONHOLD)) {
return true;
}
return false;
}
private AccountActionDateEntity getNextUnpaidDueInstallment() {
AccountActionDateEntity accountAction = null;
for (AccountActionDateEntity accountActionDate : getAccountActionDates()) {
if (!accountActionDate.isPaid()) {
if (accountActionDate.compareDate(DateUtils.getCurrentDateWithoutTimeStamp()) >= 0) {
if (accountAction == null) {
accountAction = accountActionDate;
} else {
if (accountAction.getInstallmentId() > accountActionDate.getInstallmentId()) {
accountAction = accountActionDate;
}
}
}
}
}
return accountAction;
}
/*
* This is currently (Jan 2010) used by jsp pages.
*/
public Money getNextDueAmount() {
AccountActionDateEntity accountAction = getNextUnpaidDueInstallment();
if (accountAction != null) {
return getDueAmount(accountAction);
}
return new Money(getCurrency(), "0.0");
}
public void generateCustomerAccountSystemId() throws CustomerException {
try {
if (getGlobalAccountNum() == null) {
this.setGlobalAccountNum(generateId(userContext.getBranchGlobalNum()));
} else {
throw new CustomerException(AccountExceptionConstants.IDGenerationException);
}
} catch (AccountException e) {
throw new CustomerException(e);
}
}
@Override
protected final List<FeeInstallment> handlePeriodic(final AccountFeesEntity accountFees,
final List<InstallmentDate> installmentDates, final List<InstallmentDate> nonAdjustedInstallmentDates)
throws AccountException {
Money accountFeeAmount = accountFees.getAccountFeeAmount();
MeetingBO feeMeetingFrequency = accountFees.getFees().getFeeFrequency().getFeeMeetingFrequency();
List<Date> feeDates = getFeeDates(feeMeetingFrequency, nonAdjustedInstallmentDates);
ListIterator<Date> feeDatesIterator = feeDates.listIterator();
List<FeeInstallment> feeInstallmentList = new ArrayList<FeeInstallment>();
while (feeDatesIterator.hasNext()) {
Date feeDate = feeDatesIterator.next();
logger.debug("Handling periodic fee.." + feeDate);
Short installmentId = getMatchingInstallmentId(installmentDates, feeDate);
feeInstallmentList.add(buildFeeInstallment(installmentId, accountFeeAmount, accountFees));
}
return feeInstallmentList;
}
/**
* @deprecated - use static factory methods for creating {@link CustomerAccountBO} and inject in installment dates.
*/
@Deprecated
private void generateMeetingSchedule() throws AccountException {
// generate dates that adjust for holidays
List<InstallmentDate> installmentDates = getInstallmentDates(getCustomer().getCustomerMeeting().getMeeting(),
(short) 10, (short) 0);
// generate dates without adjusting for holidays
List<InstallmentDate> nonAdjustedInstallmentDates = getInstallmentDates(getCustomer().getCustomerMeeting()
.getMeeting(), (short) 10, (short) 0, false, true);
logger.debug(
"RepamentSchedular:getRepaymentSchedule , installment dates obtained ");
List<FeeInstallment> feeInstallmentList = mergeFeeInstallments(getFeeInstallments(installmentDates,
nonAdjustedInstallmentDates));
logger.debug(
"RepamentSchedular:getRepaymentSchedule , fee installment obtained ");
for (InstallmentDate installmentDate : installmentDates) {
CustomerScheduleEntity customerScheduleEntity = new CustomerScheduleEntity(this, getCustomer(),
installmentDate.getInstallmentId(), new java.sql.Date(installmentDate.getInstallmentDueDate()
.getTime()), PaymentStatus.UNPAID);
addAccountActionDate(customerScheduleEntity);
for (FeeInstallment feeInstallment : feeInstallmentList) {
if (feeInstallment.getInstallmentId().equals(installmentDate.getInstallmentId())) {
CustomerFeeScheduleEntity customerFeeScheduleEntity = new CustomerFeeScheduleEntity(
customerScheduleEntity, feeInstallment.getAccountFeesEntity().getFees(), feeInstallment
.getAccountFeesEntity(), feeInstallment.getAccountFee());
customerScheduleEntity.addAccountFeesAction(customerFeeScheduleEntity);
}
}
}
logger.debug(
"RepamentSchedular:getRepaymentSchedule , repayment schedule generated ");
}
public void updateFee(final AccountFeesEntity fee, final FeeBO feeBO) throws BatchJobException {
boolean feeApplied = isFeeAlreadyApplied(fee, feeBO);
if (!feeApplied) {
// update this account fee
try {
if (feeBO.getFeeChangeType().equals(FeeChangeType.AMOUNT_AND_STATUS_UPDATED)) {
if (!feeBO.isActive()) {
removeFeesAssociatedWithUpcomingAndAllKnownFutureInstallments(feeBO.getFeeId(), Short.valueOf("1"));
fee.changeFeesStatus( FeeStatus.INACTIVE, new DateTimeService().getCurrentJavaDateTime());
updateAccountFee(fee, (AmountFeeBO)feeBO);
} else {
// generate repayment schedule and enable fee
fee.changeFeesStatus(FeeStatus.ACTIVE, new DateTimeService().getCurrentJavaDateTime());
updateAccountFee(fee, (AmountFeeBO)feeBO);
associateFeeWithAllKnownFutureInstallments(fee);
}
} else if (feeBO.getFeeChangeType().equals(FeeChangeType.STATUS_UPDATED)) {
if (!feeBO.isActive()) {
removeFeesAssociatedWithUpcomingAndAllKnownFutureInstallments(feeBO.getFeeId(), Short.valueOf("1"));
} else {
fee.changeFeesStatus(FeeStatus.ACTIVE, new DateTimeService().getCurrentJavaDateTime());
associateFeeWithAllKnownFutureInstallments(fee);
}
} else if (feeBO.getFeeChangeType().equals(FeeChangeType.AMOUNT_UPDATED)) {
updateAccountFee(fee, (AmountFeeBO)feeBO);
updateUpcomingAndFutureInstallments(fee);
}
} catch (PropertyNotFoundException e) {
throw new BatchJobException(e);
} catch (AccountException e) {
throw new BatchJobException(e);
}
}
}
/**
* @deprecated - use static factory methods for creating {@link CustomerAccountBO} and inject in installment dates
*/
@Deprecated
private void generateCustomerFeeSchedule(final CustomerBO customer) throws AccountException {
if (customer.getCustomerMeeting() != null && customer.isActiveViaLevel()) {
Date meetingStartDate = customer.getCustomerMeeting().getMeeting().getMeetingStartDate();
if (customer.getParentCustomer() != null) {
Date nextMeetingDate = customer.getParentCustomer().getCustomerAccount().getNextMeetingDate();
customer.getCustomerMeeting().getMeeting().setMeetingStartDate(nextMeetingDate);
}
generateMeetingSchedule();
customer.getCustomerMeeting().getMeeting().setMeetingStartDate(meetingStartDate);
}
}
private boolean isFeeAlreadyApplied(final AccountFeesEntity fee, final FeeBO feeBO) {
boolean feeApplied = false;
if (feeBO.isOneTime()) {
for (AccountActionDateEntity accountActionDateEntity : getPastInstallments()) {
CustomerScheduleEntity installment = (CustomerScheduleEntity) accountActionDateEntity;
if (installment.getAccountFeesAction(fee.getAccountFeeId()) != null) {
feeApplied = true;
break;
}
}
}
return feeApplied;
}
private void updateAccountFee(final AccountFeesEntity fee, final AmountFeeBO feeBO) {
fee.setFeeAmount(feeBO.getFeeAmount().getAmountDoubleValue());
fee.setAccountFeeAmount(feeBO.getFeeAmount());
}
private void updateUpcomingAndFutureInstallments(final AccountFeesEntity fee) {
CustomerScheduleEntity nextInstallment = (CustomerScheduleEntity) getDetailsOfNextInstallment();
AccountFeesActionDetailEntity nextAccountFeesActionDetail = null;
if(nextInstallment != null) {
nextAccountFeesActionDetail = nextInstallment.getAccountFeesAction(fee.getAccountFeeId());
}
if (nextAccountFeesActionDetail != null) {
((CustomerFeeScheduleEntity) nextAccountFeesActionDetail).setFeeAmount(fee.getAccountFeeAmount());
}
List<AccountActionDateEntity> futureInstallments = getFutureInstallments();
for (AccountActionDateEntity accountActionDateEntity : futureInstallments) {
CustomerScheduleEntity installment = (CustomerScheduleEntity) accountActionDateEntity;
AccountFeesActionDetailEntity accountFeesActionDetail = installment.getAccountFeesAction(fee.getAccountFeeId());
if (accountFeesActionDetail != null) {
((CustomerFeeScheduleEntity) accountFeesActionDetail).setFeeAmount(fee.getAccountFeeAmount());
}
}
}
private void associateFeeWithAllKnownFutureInstallments(final AccountFeesEntity fee) throws AccountException {
CustomerScheduleEntity nextInstallment = (CustomerScheduleEntity) getDetailsOfNextInstallment();
createCustomerFeeScheduleForInstallment(fee, nextInstallment);
List<AccountActionDateEntity> futureInstallments = getFutureInstallments();
for (AccountActionDateEntity accountActionDateEntity : futureInstallments) {
CustomerScheduleEntity installment = (CustomerScheduleEntity) accountActionDateEntity;
createCustomerFeeScheduleForInstallment(fee, installment);
}
}
private void createCustomerFeeScheduleForInstallment(final AccountFeesEntity fee,
CustomerScheduleEntity nextInstallment) throws AccountException {
CustomerFeeScheduleEntity accountFeesaction = new CustomerFeeScheduleEntity(nextInstallment, fee.getFees(), fee, fee.getAccountFeeAmount());
accountFeesaction.setFeeAmountPaid(new Money(fee.getAccountFeeAmount().getCurrency(),"0.0"));
nextInstallment.addAccountFeesAction(accountFeesaction);
String description = fee.getFees().getFeeName() + " " + AccountConstants.FEES_APPLIED;
try {
addCustomerActivity(new CustomerActivityEntity(this, ApplicationContextProvider.getBean(LegacyPersonnelDao.class).getPersonnel(Short
.valueOf("1")), fee.getAccountFeeAmount(), description, new DateTimeService()
.getCurrentJavaDateTime()));
} catch (PersistenceException e) {
throw new AccountException(e);
}
}
/*
* 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 ApplicationContextProvider.getBean(LegacyAccountDao.class).loadPersistentObject(PaymentTypeEntity.class, paymentTypeId);
}
@Override
public MeetingBO getMeetingForAccount() {
return getCustomer().getCustomerMeetingValue();
}
public boolean isActive() {
return AccountState.CUSTOMER_ACCOUNT_ACTIVE.equals(getState());
}
}