/** * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mifosplatform.portfolio.savings.domain; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dateFormatParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeIntervalParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.localeParamName; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.joda.time.LocalDate; import org.joda.time.MonthDay; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.monetary.domain.MoneyHelper; import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType; import org.mifosplatform.portfolio.charge.domain.ChargeTimeType; import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeWithoutMandatoryFieldException; import org.springframework.data.jpa.domain.AbstractPersistable; /** * @author dv6 * */ @Entity @Table(name = "m_savings_account_charge") public class SavingsAccountCharge extends AbstractPersistable<Long> { @ManyToOne(optional = false) @JoinColumn(name = "savings_account_id", referencedColumnName = "id", nullable = false) private SavingsAccount savingsAccount; @ManyToOne(optional = false) @JoinColumn(name = "charge_id", referencedColumnName = "id", nullable = false) private Charge charge; @Column(name = "charge_time_enum", nullable = false) private Integer chargeTime; @Temporal(TemporalType.DATE) @Column(name = "charge_due_date") private Date dueDate; @Column(name = "fee_on_month", nullable = true) private Integer feeOnMonth; @Column(name = "fee_on_day", nullable = true) private Integer feeOnDay; @Column(name = "fee_interval", nullable = true) private Integer feeInterval; @Column(name = "charge_calculation_enum") private Integer chargeCalculation; @Column(name = "calculation_percentage", scale = 6, precision = 19, nullable = true) private BigDecimal percentage; // TODO AA: This field may not require for savings charges @Column(name = "calculation_on_amount", scale = 6, precision = 19, nullable = true) private BigDecimal amountPercentageAppliedTo; @Column(name = "amount", scale = 6, precision = 19, nullable = false) private BigDecimal amount; @Column(name = "amount_paid_derived", scale = 6, precision = 19, nullable = true) private BigDecimal amountPaid; @Column(name = "amount_waived_derived", scale = 6, precision = 19, nullable = true) private BigDecimal amountWaived; @Column(name = "amount_writtenoff_derived", scale = 6, precision = 19, nullable = true) private BigDecimal amountWrittenOff; @Column(name = "amount_outstanding_derived", scale = 6, precision = 19, nullable = false) private BigDecimal amountOutstanding; @Column(name = "is_penalty", nullable = false) private boolean penaltyCharge = false; @Column(name = "is_paid_derived", nullable = false) private boolean paid = false; @Column(name = "waived", nullable = false) private boolean waived = false; @Column(name = "is_active", nullable = false) private boolean status = true; @Temporal(TemporalType.DATE) @Column(name = "inactivated_on_date") private Date inactivationDate; public static SavingsAccountCharge createNewFromJson(final SavingsAccount savingsAccount, final Charge chargeDefinition, final JsonCommand command) { BigDecimal amount = command.bigDecimalValueOfParameterNamed(amountParamName); final LocalDate dueDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName); MonthDay feeOnMonthDay = command.extractMonthDayNamed(feeOnMonthDayParamName); Integer feeInterval = command.integerValueOfParameterNamed(feeIntervalParamName); final ChargeTimeType chargeTime = null; final ChargeCalculationType chargeCalculation = null; final boolean status = true; // If these values is not sent as parameter, then derive from Charge // definition amount = (amount == null) ? chargeDefinition.getAmount() : amount; feeOnMonthDay = (feeOnMonthDay == null) ? chargeDefinition.getFeeOnMonthDay() : feeOnMonthDay; feeInterval = (feeInterval == null) ? chargeDefinition.getFeeInterval() : feeInterval; return new SavingsAccountCharge(savingsAccount, chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, status, feeOnMonthDay, feeInterval); } public static SavingsAccountCharge createNewWithoutSavingsAccount(final Charge chargeDefinition, final BigDecimal amountPayable, final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate, final boolean status, final MonthDay feeOnMonthDay, final Integer feeInterval) { return new SavingsAccountCharge(null, chargeDefinition, amountPayable, chargeTime, chargeCalculation, dueDate, status, feeOnMonthDay, feeInterval); } protected SavingsAccountCharge() { // } private SavingsAccountCharge(final SavingsAccount savingsAccount, final Charge chargeDefinition, final BigDecimal amount, final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate, final boolean status, MonthDay feeOnMonthDay, final Integer feeInterval) { this.savingsAccount = savingsAccount; this.charge = chargeDefinition; this.penaltyCharge = chargeDefinition.isPenalty(); this.chargeTime = (chargeTime == null) ? chargeDefinition.getChargeTimeType() : chargeTime.getValue(); if (isOnSpecifiedDueDate()) { if (dueDate == null) { final String defaultUserMessage = "Savings Account charge is missing due date."; throw new SavingsAccountChargeWithoutMandatoryFieldException("savingsaccount.charge", dueAsOfDateParamName, defaultUserMessage, chargeDefinition.getId(), chargeDefinition.getName()); } } if (isAnnualFee() || isMonthlyFee()) { feeOnMonthDay = (feeOnMonthDay == null) ? chargeDefinition.getFeeOnMonthDay() : feeOnMonthDay; if (feeOnMonthDay == null) { final String defaultUserMessage = "Savings Account charge is missing due date."; throw new SavingsAccountChargeWithoutMandatoryFieldException("savingsaccount.charge", dueAsOfDateParamName, defaultUserMessage, chargeDefinition.getId(), chargeDefinition.getName()); } this.feeOnMonth = feeOnMonthDay.getMonthOfYear(); this.feeOnDay = feeOnMonthDay.getDayOfMonth(); } else if (isWeeklyFee()) { if (dueDate == null) { final String defaultUserMessage = "Savings Account charge is missing due date."; throw new SavingsAccountChargeWithoutMandatoryFieldException("savingsaccount.charge", dueAsOfDateParamName, defaultUserMessage, chargeDefinition.getId(), chargeDefinition.getName()); } /** * For Weekly fee feeOnDay is ISO standard day of the week. * Monday=1, Tuesday=2 */ this.feeOnDay = dueDate.getDayOfWeek(); } else { this.feeOnDay = null; this.feeOnMonth = null; this.feeInterval = null; } if (isMonthlyFee() || isWeeklyFee()) { this.feeInterval = (feeInterval == null) ? chargeDefinition.feeInterval() : feeInterval; } this.dueDate = (dueDate == null) ? null : dueDate.toDate(); this.chargeCalculation = chargeDefinition.getChargeCalculation(); if (chargeCalculation != null) { this.chargeCalculation = chargeCalculation.getValue(); } BigDecimal chargeAmount = chargeDefinition.getAmount(); if (amount != null) { chargeAmount = amount; } final BigDecimal transactionAmount = new BigDecimal(0); populateDerivedFields(transactionAmount, chargeAmount); if (this.isWithdrawalFee()) { this.amountOutstanding = BigDecimal.ZERO; } this.paid = determineIfFullyPaid(); this.status = status; } public void resetPropertiesForRecurringFees() { if (isMonthlyFee() || isAnnualFee() || isWeeklyFee()) { // FIXME: AA: If charge is percentage of x amount then need to // update amount outstanding accordingly. // Right now annual and monthly charges supports charge calculation // type flat. this.amountOutstanding = this.amount; this.paid = false;// reset to false for recurring fee. this.waived = false; } } private void populateDerivedFields(final BigDecimal transactionAmount, final BigDecimal chargeAmount) { switch (ChargeCalculationType.fromInt(this.chargeCalculation)) { case INVALID: this.percentage = null; this.amount = null; this.amountPercentageAppliedTo = null; this.amountPaid = null; this.amountOutstanding = BigDecimal.ZERO; this.amountWaived = null; this.amountWrittenOff = null; break; case FLAT: this.percentage = null; this.amount = chargeAmount; this.amountPercentageAppliedTo = null; this.amountPaid = null; this.amountOutstanding = chargeAmount; this.amountWaived = null; this.amountWrittenOff = null; break; case PERCENT_OF_AMOUNT: this.percentage = chargeAmount; this.amountPercentageAppliedTo = transactionAmount; this.amount = percentageOf(this.amountPercentageAppliedTo, this.percentage); this.amountPaid = null; this.amountOutstanding = calculateOutstanding(); this.amountWaived = null; this.amountWrittenOff = null; break; case PERCENT_OF_AMOUNT_AND_INTEREST: this.percentage = null; this.amount = null; this.amountPercentageAppliedTo = null; this.amountPaid = null; this.amountOutstanding = BigDecimal.ZERO; this.amountWaived = null; this.amountWrittenOff = null; break; case PERCENT_OF_INTEREST: this.percentage = null; this.amount = null; this.amountPercentageAppliedTo = null; this.amountPaid = null; this.amountOutstanding = BigDecimal.ZERO; this.amountWaived = null; this.amountWrittenOff = null; break; } } public void markAsFullyPaid() { this.amountPaid = this.amount; this.amountOutstanding = BigDecimal.ZERO; this.paid = true; } public void resetToOriginal(final MonetaryCurrency currency) { this.amountPaid = BigDecimal.ZERO; this.amountWaived = BigDecimal.ZERO; this.amountWrittenOff = BigDecimal.ZERO; this.amountOutstanding = calculateAmountOutstanding(currency); this.paid = false; this.waived = false; } public void undoPayment(final MonetaryCurrency currency, final Money transactionAmount) { Money amountPaid = getAmountPaid(currency); amountPaid = amountPaid.minus(transactionAmount); this.amountPaid = amountPaid.getAmount(); this.amountOutstanding = calculateAmountOutstanding(currency); if (this.isWithdrawalFee()) { this.amountOutstanding = BigDecimal.ZERO; } // to reset amount outstanding for annual and monthly fee resetPropertiesForRecurringFees(); updateToPreviousDueDate();// reset annual and monthly due date. this.paid = false; this.status = true; } public Money waive(final MonetaryCurrency currency) { Money amountWaivedToDate = Money.of(currency, this.amountWaived); Money amountOutstanding = Money.of(currency, this.amountOutstanding); this.amountWaived = amountWaivedToDate.plus(amountOutstanding).getAmount(); this.amountOutstanding = BigDecimal.ZERO; this.waived = true; resetPropertiesForRecurringFees(); updateNextDueDateForRecurringFees(); return amountOutstanding; } public void undoWaiver(final MonetaryCurrency currency, final Money transactionAmount) { Money amountWaived = getAmountWaived(currency); amountWaived = amountWaived.minus(transactionAmount); this.amountWaived = amountWaived.getAmount(); this.amountOutstanding = calculateAmountOutstanding(currency); this.waived = false; this.status = true; resetPropertiesForRecurringFees(); updateToPreviousDueDate(); } public Money pay(final MonetaryCurrency currency, final Money amountPaid) { Money amountPaidToDate = Money.of(currency, this.amountPaid); Money amountOutstanding = Money.of(currency, this.amountOutstanding); amountPaidToDate = amountPaidToDate.plus(amountPaid); amountOutstanding = amountOutstanding.minus(amountPaid); this.amountPaid = amountPaidToDate.getAmount(); this.amountOutstanding = amountOutstanding.getAmount(); this.paid = determineIfFullyPaid(); if (BigDecimal.ZERO.compareTo(this.amountOutstanding) == 0) { // full outstanding is paid, update to next due date updateNextDueDateForRecurringFees(); resetPropertiesForRecurringFees(); } return Money.of(currency, this.amountOutstanding); } private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) { return getAmount(currency).minus(getAmountWaived(currency)).minus(getAmountPaid(currency)).getAmount(); } public void update(final SavingsAccount savingsAccount) { this.savingsAccount = savingsAccount; } public void update(final BigDecimal amount, final LocalDate dueDate, final MonthDay feeOnMonthDay, final Integer feeInterval) { final BigDecimal transactionAmount = BigDecimal.ZERO; if (dueDate != null) { this.dueDate = dueDate.toDate(); if (isWeeklyFee()) { this.feeOnDay = dueDate.getDayOfWeek(); } } if (feeOnMonthDay != null) { this.feeOnMonth = feeOnMonthDay.getMonthOfYear(); this.feeOnDay = feeOnMonthDay.getDayOfMonth(); } if (feeInterval != null) { this.feeInterval = feeInterval; } if (amount != null) { switch (ChargeCalculationType.fromInt(this.chargeCalculation)) { case INVALID: break; case FLAT: this.amount = amount; break; case PERCENT_OF_AMOUNT: this.percentage = amount; this.amountPercentageAppliedTo = transactionAmount; this.amount = percentageOf(this.amountPercentageAppliedTo, this.percentage); this.amountOutstanding = calculateOutstanding(); break; case PERCENT_OF_AMOUNT_AND_INTEREST: this.percentage = amount; this.amount = null; this.amountPercentageAppliedTo = null; this.amountOutstanding = null; break; case PERCENT_OF_INTEREST: this.percentage = amount; this.amount = null; this.amountPercentageAppliedTo = null; this.amountOutstanding = null; break; } } } public Map<String, Object> update(final JsonCommand command) { final Map<String, Object> actualChanges = new LinkedHashMap<>(7); final String dateFormatAsInput = command.dateFormat(); final String localeAsInput = command.locale(); if (command.isChangeInLocalDateParameterNamed(dueAsOfDateParamName, getDueLocalDate())) { final String valueAsInput = command.stringValueOfParameterNamed(dueAsOfDateParamName); actualChanges.put(dueAsOfDateParamName, valueAsInput); actualChanges.put(dateFormatParamName, dateFormatAsInput); actualChanges.put(localeParamName, localeAsInput); final LocalDate newValue = command.localDateValueOfParameterNamed(dueAsOfDateParamName); this.dueDate = newValue.toDate(); if (this.isWeeklyFee()) { this.feeOnDay = newValue.getDayOfWeek(); } } if (command.hasParameter(feeOnMonthDayParamName)) { final MonthDay monthDay = command.extractMonthDayNamed(feeOnMonthDayParamName); final String actualValueEntered = command.stringValueOfParameterNamed(feeOnMonthDayParamName); final Integer dayOfMonthValue = monthDay.getDayOfMonth(); if (this.feeOnDay != dayOfMonthValue) { actualChanges.put(feeOnMonthDayParamName, actualValueEntered); actualChanges.put(localeParamName, localeAsInput); this.feeOnDay = dayOfMonthValue; } final Integer monthOfYear = monthDay.getMonthOfYear(); if (this.feeOnMonth != monthOfYear) { actualChanges.put(feeOnMonthDayParamName, actualValueEntered); actualChanges.put(localeParamName, localeAsInput); this.feeOnMonth = monthOfYear; } } if (command.isChangeInBigDecimalParameterNamed(amountParamName, this.amount)) { final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName); actualChanges.put(amountParamName, newValue); actualChanges.put(localeParamName, localeAsInput); switch (ChargeCalculationType.fromInt(this.chargeCalculation)) { case INVALID: break; case FLAT: this.amount = newValue; this.amountOutstanding = calculateOutstanding(); break; case PERCENT_OF_AMOUNT: this.percentage = newValue; this.amountPercentageAppliedTo = null; this.amount = percentageOf(this.amountPercentageAppliedTo, this.percentage); this.amountOutstanding = calculateOutstanding(); break; case PERCENT_OF_AMOUNT_AND_INTEREST: this.percentage = newValue; this.amount = null; this.amountPercentageAppliedTo = null; this.amountOutstanding = null; break; case PERCENT_OF_INTEREST: this.percentage = newValue; this.amount = null; this.amountPercentageAppliedTo = null; this.amountOutstanding = null; break; } } return actualChanges; } private boolean isGreaterThanZero(final BigDecimal value) { return value.compareTo(BigDecimal.ZERO) == 1; } public LocalDate getDueLocalDate() { LocalDate dueDate = null; if (this.dueDate != null) { dueDate = new LocalDate(this.dueDate); } return dueDate; } private boolean determineIfFullyPaid() { return BigDecimal.ZERO.compareTo(calculateOutstanding()) == 0; } private BigDecimal calculateOutstanding() { BigDecimal amountPaidLocal = BigDecimal.ZERO; if (this.amountPaid != null) { amountPaidLocal = this.amountPaid; } BigDecimal amountWaivedLocal = BigDecimal.ZERO; if (this.amountWaived != null) { amountWaivedLocal = this.amountWaived; } BigDecimal amountWrittenOffLocal = BigDecimal.ZERO; if (this.amountWrittenOff != null) { amountWrittenOffLocal = this.amountWrittenOff; } final BigDecimal totalAccountedFor = amountPaidLocal.add(amountWaivedLocal).add(amountWrittenOffLocal); return this.amount.subtract(totalAccountedFor); } private BigDecimal percentageOf(final BigDecimal value, final BigDecimal percentage) { BigDecimal percentageOf = BigDecimal.ZERO; if (isGreaterThanZero(value)) { final MathContext mc = new MathContext(8, MoneyHelper.getRoundingMode()); final BigDecimal multiplicand = percentage.divide(BigDecimal.valueOf(100l), mc); percentageOf = value.multiply(multiplicand, mc); } return percentageOf; } public BigDecimal amount() { return this.amount; } public BigDecimal amoutOutstanding() { return this.amountOutstanding; } public boolean isFeeCharge() { return !this.penaltyCharge; } public boolean isPenaltyCharge() { return this.penaltyCharge; } public boolean isNotFullyPaid() { return !isPaid(); } public boolean isPaid() { return this.paid; } public boolean isWaived() { return this.waived; } public boolean isPaidOrPartiallyPaid(final MonetaryCurrency currency) { final Money amountWaivedOrWrittenOff = getAmountWaived(currency).plus(getAmountWrittenOff(currency)); return Money.of(currency, this.amountPaid).plus(amountWaivedOrWrittenOff).isGreaterThanZero(); } public Money getAmount(final MonetaryCurrency currency) { return Money.of(currency, this.amount); } private Money getAmountPaid(final MonetaryCurrency currency) { return Money.of(currency, this.amountPaid); } public Money getAmountWaived(final MonetaryCurrency currency) { return Money.of(currency, this.amountWaived); } public Money getAmountWrittenOff(final MonetaryCurrency currency) { return Money.of(currency, this.amountWrittenOff); } public Money getAmountOutstanding(final MonetaryCurrency currency) { return Money.of(currency, this.amountOutstanding); } /** * @param incrementBy * Amount used to pay off this charge * @return Actual amount paid on this charge */ public Money updatePaidAmountBy(final Money incrementBy) { Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid); final Money amountOutstanding = Money.of(incrementBy.getCurrency(), this.amountOutstanding); Money amountPaidOnThisCharge = Money.zero(incrementBy.getCurrency()); if (incrementBy.isGreaterThanOrEqualTo(amountOutstanding)) { amountPaidOnThisCharge = amountOutstanding; amountPaidToDate = amountPaidToDate.plus(amountOutstanding); this.amountPaid = amountPaidToDate.getAmount(); this.amountOutstanding = BigDecimal.ZERO; } else { amountPaidOnThisCharge = incrementBy; amountPaidToDate = amountPaidToDate.plus(incrementBy); this.amountPaid = amountPaidToDate.getAmount(); final Money amountExpected = Money.of(incrementBy.getCurrency(), this.amount); this.amountOutstanding = amountExpected.minus(amountPaidToDate).getAmount(); } this.paid = determineIfFullyPaid(); return amountPaidOnThisCharge; } public String name() { return this.charge.getName(); } public String currencyCode() { return this.charge.getCurrencyCode(); } public Charge getCharge() { return this.charge; } public SavingsAccount savingsAccount() { return this.savingsAccount; } public boolean isOnSpecifiedDueDate() { return ChargeTimeType.fromInt(this.chargeTime).isOnSpecifiedDueDate(); } public boolean isSavingsActivation() { return ChargeTimeType.fromInt(this.chargeTime).isSavingsActivation(); } public boolean isSavingsClosure() { return ChargeTimeType.fromInt(this.chargeTime).isSavingsClosure(); } public boolean isWithdrawalFee() { return ChargeTimeType.fromInt(this.chargeTime).isWithdrawalFee(); } public boolean isOverdraftFee() { return ChargeTimeType.fromInt(this.chargeTime).isOverdraftFee(); } public boolean isAnnualFee() { return ChargeTimeType.fromInt(this.chargeTime).isAnnualFee(); } public boolean isMonthlyFee() { return ChargeTimeType.fromInt(this.chargeTime).isMonthlyFee(); } public boolean isWeeklyFee() { return ChargeTimeType.fromInt(this.chargeTime).isWeeklyFee(); } public boolean hasCurrencyCodeOf(final String matchingCurrencyCode) { if (this.currencyCode() == null || matchingCurrencyCode == null) { return false; } return this.currencyCode().equalsIgnoreCase(matchingCurrencyCode); } @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (obj == this) { return true; } if (obj.getClass() != getClass()) { return false; } final SavingsAccountCharge rhs = (SavingsAccountCharge) obj; return new EqualsBuilder().appendSuper(super.equals(obj)) // .append(getId(), rhs.getId()) // .append(this.charge.getId(), rhs.charge.getId()) // .append(this.amount, rhs.amount) // .append(getDueLocalDate(), rhs.getDueLocalDate()) // .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder(3, 5) // .append(getId()) // .append(this.charge.getId()) // .append(this.amount).append(getDueLocalDate()) // .toHashCode(); } public BigDecimal updateWithdralFeeAmount(final BigDecimal transactionAmount) { BigDecimal amountPaybale = BigDecimal.ZERO; if (ChargeCalculationType.fromInt(this.chargeCalculation).isFlat()) { amountPaybale = this.amount; } else if (ChargeCalculationType.fromInt(this.chargeCalculation).isPercentageOfAmount()) { amountPaybale = transactionAmount.multiply(this.percentage).divide(BigDecimal.valueOf(100l)); } this.amountOutstanding = amountPaybale; return amountPaybale; } public void updateToNextDueDateFrom(final LocalDate startingDate) { if (isAnnualFee() || isMonthlyFee() || isWeeklyFee()) { this.dueDate = getNextDueDateFrom(startingDate).toDate(); } } public LocalDate getNextDueDateFrom(final LocalDate startingDate) { LocalDate nextDueLocalDate = null; if (isAnnualFee() || isMonthlyFee()) { nextDueLocalDate = startingDate.withMonthOfYear(this.feeOnMonth); nextDueLocalDate = setDayOfMonth(nextDueLocalDate); while (startingDate.isAfter(nextDueLocalDate)) { nextDueLocalDate = calculateNextDueDate(nextDueLocalDate); } } else if (isWeeklyFee()) { nextDueLocalDate = getDueLocalDate(); while (startingDate.isAfter(nextDueLocalDate)) { nextDueLocalDate = calculateNextDueDate(nextDueLocalDate); } } else { nextDueLocalDate = calculateNextDueDate(startingDate); } return nextDueLocalDate; } private LocalDate calculateNextDueDate(final LocalDate date) { LocalDate nextDueLocalDate = null; if (isAnnualFee()) { nextDueLocalDate = date.withMonthOfYear(this.feeOnMonth).plusYears(1); nextDueLocalDate = setDayOfMonth(nextDueLocalDate); } else if (isMonthlyFee()) { nextDueLocalDate = date.plusMonths(this.feeInterval); nextDueLocalDate = setDayOfMonth(nextDueLocalDate); } else if (isWeeklyFee()) { nextDueLocalDate = date.plusWeeks(this.feeInterval); nextDueLocalDate = setDayOfWeek(nextDueLocalDate); } return nextDueLocalDate; } private LocalDate setDayOfMonth(LocalDate nextDueLocalDate) { int maxDayOfMonth = nextDueLocalDate.dayOfMonth().withMaximumValue().getDayOfMonth(); int newDayOfMonth = (this.feeOnDay.intValue() < maxDayOfMonth) ? this.feeOnDay.intValue() : maxDayOfMonth; nextDueLocalDate = nextDueLocalDate.withDayOfMonth(newDayOfMonth); return nextDueLocalDate; } private LocalDate setDayOfWeek(LocalDate nextDueLocalDate) { if (this.feeOnDay != nextDueLocalDate.getDayOfWeek()) { nextDueLocalDate = nextDueLocalDate.withDayOfWeek(this.feeOnDay); } return nextDueLocalDate; } public void updateNextDueDateForRecurringFees() { if (isAnnualFee() || isMonthlyFee() || isWeeklyFee()) { LocalDate nextDueLocalDate = new LocalDate(dueDate); nextDueLocalDate = calculateNextDueDate(nextDueLocalDate); this.dueDate = nextDueLocalDate.toDate(); } } public void updateToPreviousDueDate() { if (isAnnualFee() || isMonthlyFee() || isWeeklyFee()) { LocalDate nextDueLocalDate = new LocalDate(dueDate); if (isAnnualFee()) { nextDueLocalDate = nextDueLocalDate.withMonthOfYear(this.feeOnMonth).minusYears(1); nextDueLocalDate = setDayOfMonth(nextDueLocalDate); } else if (isMonthlyFee()) { nextDueLocalDate = nextDueLocalDate.minusMonths(this.feeInterval); nextDueLocalDate = setDayOfMonth(nextDueLocalDate); } else if (isWeeklyFee()) { nextDueLocalDate = nextDueLocalDate.minusDays(7 * this.feeInterval); nextDueLocalDate = setDayOfWeek(nextDueLocalDate); } this.dueDate = nextDueLocalDate.toDate(); } } public boolean feeSettingsNotSet() { return !feeSettingsSet(); } public boolean feeSettingsSet() { return this.feeOnDay != null && this.feeOnMonth != null; } public boolean isRecurringFee() { return isWeeklyFee() || isMonthlyFee() || isAnnualFee(); } public boolean isChargeIsDue(final LocalDate nextDueDate) { return this.getDueLocalDate().isBefore(nextDueDate); } public boolean isChargeIsOverPaid(final LocalDate nextDueDate) { final BigDecimal amountPaid = this.amountPaid == null ? BigDecimal.ZERO : amountPaid(); return this.getDueLocalDate().isAfter(nextDueDate) && amountPaid.compareTo(BigDecimal.ZERO) == 1; } private BigDecimal amountPaid() { return this.amountPaid; } public void inactiavateCharge(final LocalDate inactivationOnDate) { this.inactivationDate = inactivationOnDate.toDate(); this.status = false; this.amountOutstanding = BigDecimal.ZERO; this.paid = true; } public boolean isActive() { return this.status; } public boolean isNotActive() { return !isActive(); } /** * This method is to identify the charges which can override the savings * rules(for example if there is a minimum enforced balance of 1000 on * savings account with account balance of 1000, still these charges can be * collected as these charges are initiated by system and it can bring down * the balance below the enforced minimum balance). * */ public boolean canOverriteSavingAccountRules() { return !(this.isSavingsActivation() || this.isWithdrawalFee()); } }