/* * 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()); } }