/**
* Copyright © 2002 Instituto Superior Técnico
*
* This file is part of FenixEdu Academic.
*
* FenixEdu Academic is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FenixEdu Academic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FenixEdu Academic. If not, see <http://www.gnu.org/licenses/>.
*/
package org.fenixedu.academic.domain.accounting.postingRules.gratuity;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.fenixedu.academic.domain.ExecutionYear;
import org.fenixedu.academic.domain.accounting.Account;
import org.fenixedu.academic.domain.accounting.AccountingTransaction;
import org.fenixedu.academic.domain.accounting.EntryType;
import org.fenixedu.academic.domain.accounting.Event;
import org.fenixedu.academic.domain.accounting.EventType;
import org.fenixedu.academic.domain.accounting.Installment;
import org.fenixedu.academic.domain.accounting.PaymentPlan;
import org.fenixedu.academic.domain.accounting.ServiceAgreementTemplate;
import org.fenixedu.academic.domain.accounting.accountingTransactions.InstallmentAccountingTransaction;
import org.fenixedu.academic.domain.accounting.events.gratuity.GratuityEventWithPaymentPlan;
import org.fenixedu.academic.domain.accounting.serviceAgreementTemplates.DegreeCurricularPlanServiceAgreementTemplate;
import org.fenixedu.academic.domain.exceptions.DomainException;
import org.fenixedu.academic.domain.exceptions.DomainExceptionWithLabelFormatter;
import org.fenixedu.academic.dto.accounting.AccountingTransactionDetailDTO;
import org.fenixedu.academic.dto.accounting.EntryDTO;
import org.fenixedu.academic.dto.accounting.EntryWithInstallmentDTO;
import org.fenixedu.academic.util.Bundle;
import org.fenixedu.academic.util.Money;
import org.fenixedu.bennu.core.domain.User;
import org.fenixedu.bennu.core.i18n.BundleUtil;
import org.joda.time.DateTime;
public class GratuityWithPaymentPlanPR extends GratuityWithPaymentPlanPR_Base implements IGratuityPR {
protected GratuityWithPaymentPlanPR() {
super();
}
public GratuityWithPaymentPlanPR(DateTime startDate, DateTime endDate, ServiceAgreementTemplate serviceAgreementTemplate) {
this(EntryType.GRATUITY_FEE, EventType.GRATUITY, startDate, endDate, serviceAgreementTemplate);
}
public GratuityWithPaymentPlanPR(EntryType entryType, EventType eventType, DateTime startDate, DateTime endDate,
ServiceAgreementTemplate serviceAgreementTemplate) {
this();
super.init(entryType, eventType, startDate, endDate, serviceAgreementTemplate);
}
@Override
protected Money doCalculationForAmountToPay(Event event, DateTime when, boolean applyDiscount) {
final BigDecimal discountPercentage = applyDiscount ? getDiscountPercentage(event) : BigDecimal.ZERO;
return getPaymentPlan(event).calculateTotalAmount(event, when, discountPercentage);
}
@Override
protected Money subtractFromExemptions(Event event, DateTime when, boolean applyDiscount, Money amountToPay) {
return amountToPay;
}
public BigDecimal getDiscountPercentage(final Event event) {
PaymentPlan paymentPlan = getPaymentPlan(event);
if (paymentPlan == null) {
throw new DomainException("error.event.not.associated.paymentPlan", event.getClass().getName());
}
return ((GratuityEventWithPaymentPlan) event).calculateDiscountPercentage(paymentPlan.calculateBaseAmount(event));
}
@Override
public List<EntryDTO> calculateEntries(Event event, DateTime when) {
final List<EntryDTO> result = new ArrayList<EntryDTO>();
final Map<Installment, Money> amountsByInstallment =
getPaymentPlan(event).calculateInstallmentRemainingAmounts(event, when, getDiscountPercentage(event));
for (final Installment installment : getPaymentPlan(event).getInstallmentsSortedByEndDate()) {
final Money installmentAmount = amountsByInstallment.get(installment);
if (installmentAmount == null || !installmentAmount.isPositive()) {
continue;
}
result.add(new EntryWithInstallmentDTO(EntryType.GRATUITY_FEE, event, installmentAmount, event
.getDescriptionForEntryType(getEntryType()), installment));
}
if (needsTotalAmountEntry(getPaymentPlan(event), result, event, when)) {
final Money amountToPay = event.calculateAmountToPay(when);
result.add(new EntryDTO(EntryType.GRATUITY_FEE, event, amountToPay, event.getPayedAmount(), amountToPay, event
.getDescriptionForEntryType(getEntryType()), amountToPay));
}
return result;
}
private boolean needsTotalAmountEntry(final PaymentPlan paymentPlan, List<EntryDTO> result, final Event event,
final DateTime when) {
return (paymentPlan.getInstallmentsSet().size() != 1 && paymentPlan.getInstallmentsSet().size() == result.size());
}
@Override
protected Set<AccountingTransaction> internalProcess(User user, Collection<EntryDTO> entryDTOs, Event event,
Account fromAccount, Account toAccount, AccountingTransactionDetailDTO transactionDetail) {
final GratuityEventWithPaymentPlan gratuityEventWithPaymentPlan = (GratuityEventWithPaymentPlan) event;
if (entryDTOs.size() > 1) {
final Set<AccountingTransaction> result = new HashSet<AccountingTransaction>();
for (final EntryDTO each : entryDTOs) {
if (!(each instanceof EntryWithInstallmentDTO)) {
throw new DomainExceptionWithLabelFormatter(
"error.accounting.postingRules.gratuity.GratuityWithPaymentPlanPR.cannot.mix.installments.with.total.payments",
event.getDescriptionForEntryType(getEntryType()));
}
result.add(internalProcessInstallment(user, fromAccount, toAccount, each, gratuityEventWithPaymentPlan,
transactionDetail));
}
return result;
}
final EntryDTO entryDTO = entryDTOs.iterator().next();
if (entryDTO instanceof EntryWithInstallmentDTO) {
return Collections.singleton(internalProcessInstallment(user, fromAccount, toAccount, entryDTO,
gratuityEventWithPaymentPlan, transactionDetail));
}
return Collections.singleton(internalProcessTotal(user, fromAccount, toAccount, entryDTO, gratuityEventWithPaymentPlan,
transactionDetail));
}
private AccountingTransaction internalProcessTotal(User user, Account fromAccount, Account toAccount, EntryDTO entryDTO,
GratuityEventWithPaymentPlan event, AccountingTransactionDetailDTO transactionDetail) {
event.changeGratuityTotalPaymentCodeState(event.getPaymentCodeStateFor(transactionDetail.getPaymentMode()));
if (!transactionDetail.isSibsTransactionDetail()) {
checkIfCanAddAmount(entryDTO, transactionDetail.getWhenRegistered(), event);
}
return super.makeAccountingTransaction(user, event, fromAccount, toAccount, getEntryType(), entryDTO.getAmountToPay(),
transactionDetail);
}
private AccountingTransaction internalProcessInstallment(User user, Account fromAccount, Account toAccount,
EntryDTO entryDTO, GratuityEventWithPaymentPlan event, AccountingTransactionDetailDTO transactionDetail) {
final EntryWithInstallmentDTO entryWithInstallmentDTO = (EntryWithInstallmentDTO) entryDTO;
if (!transactionDetail.isSibsTransactionDetail()) {
checkIfCanAddAmountForInstallment(entryWithInstallmentDTO, transactionDetail.getWhenRegistered(), event);
}
event.changeInstallmentPaymentCodeState(entryWithInstallmentDTO.getInstallment(),
event.getPaymentCodeStateFor(transactionDetail.getPaymentMode()));
return makeAccountingTransactionForInstallment(user, event, fromAccount, toAccount, getEntryType(),
entryDTO.getAmountToPay(), (entryWithInstallmentDTO).getInstallment(), transactionDetail);
}
private void checkIfCanAddAmount(EntryDTO entryDTO, DateTime whenRegistered, Event event) {
if (entryDTO.getAmountToPay().compareTo(event.calculateAmountToPay(whenRegistered)) < 0) {
throw new DomainExceptionWithLabelFormatter(
"error.accounting.postingRules.gratuity.GratuityWithPaymentPlanPR.amount.to.pay.must.match.value",
event.getDescriptionForEntryType(getEntryType()));
}
}
private void checkIfCanAddAmountForInstallment(EntryWithInstallmentDTO entryDTO, DateTime whenRegistered, Event event) {
final Money installmentAmount =
getPaymentPlan(event).calculateRemainingAmountFor(entryDTO.getInstallment(), event, whenRegistered,
getDiscountPercentage(event));
if (entryDTO.getAmountToPay().compareTo(installmentAmount) < 0) {
throw new DomainExceptionWithLabelFormatter(
"error.accounting.postingRules.gratuity.GratuityWithPaymentPlanPR.amount.to.pay.must.match.value",
event.getDescriptionForEntryType(getEntryType()));
}
}
private PaymentPlan getPaymentPlan(Event event) {
return ((GratuityEventWithPaymentPlan) event).getGratuityPaymentPlan();
}
@Override
public DegreeCurricularPlanServiceAgreementTemplate getServiceAgreementTemplate() {
return (DegreeCurricularPlanServiceAgreementTemplate) super.getServiceAgreementTemplate();
}
protected AccountingTransaction makeAccountingTransactionForInstallment(User responsibleUser, Event event, Account from,
Account to, EntryType entryType, Money amount, Installment installment,
AccountingTransactionDetailDTO transactionDetail) {
return new InstallmentAccountingTransaction(responsibleUser, event, makeEntry(entryType, amount.negate(), from),
makeEntry(entryType, amount, to), installment, makeAccountingTransactionDetail(transactionDetail));
}
@Override
public Money getDefaultGratuityAmount(ExecutionYear executionYear) {
return getServiceAgreementTemplate().getDefaultPaymentPlan(executionYear).calculateOriginalTotalAmount();
}
public Money getDefaultGratuityAmount() {
return getDefaultGratuityAmount(ExecutionYear.readByDateTime(getCreationDate()));
}
@Override
protected void checkForDeletionBlockers(Collection<String> blockers) {
super.checkForDeletionBlockers(blockers);
if (getServiceAgreementTemplate().hasActivePostingRuleFor(EventType.STANDALONE_ENROLMENT_GRATUITY)) {
blockers.add(BundleUtil.getString(Bundle.APPLICATION,
"error.accounting.postingRules.gratuity.GratuityWithPaymentPlanPR.standalone.cannot.delete"));
}
}
}