/** * 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.events; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.fenixedu.academic.domain.ExecutionYear; import org.fenixedu.academic.domain.Person; import org.fenixedu.academic.domain.accounting.Account; import org.fenixedu.academic.domain.accounting.AccountingTransaction; import org.fenixedu.academic.domain.accounting.Entry; import org.fenixedu.academic.domain.accounting.EntryType; import org.fenixedu.academic.domain.accounting.Event; import org.fenixedu.academic.domain.accounting.EventState; import org.fenixedu.academic.domain.accounting.EventType; import org.fenixedu.academic.domain.accounting.Exemption; import org.fenixedu.academic.domain.accounting.PaymentCode; import org.fenixedu.academic.domain.accounting.PaymentCodeState; import org.fenixedu.academic.domain.accounting.PaymentCodeType; import org.fenixedu.academic.domain.accounting.PaymentMode; import org.fenixedu.academic.domain.accounting.events.administrativeOfficeFee.IAdministrativeOfficeFeeEvent; import org.fenixedu.academic.domain.accounting.events.insurance.IInsuranceEvent; import org.fenixedu.academic.domain.accounting.paymentCodes.AccountingEventPaymentCode; import org.fenixedu.academic.domain.accounting.postingRules.AdministrativeOfficeFeeAndInsurancePR; import org.fenixedu.academic.domain.accounting.postingRules.AdministrativeOfficeFeePR; import org.fenixedu.academic.domain.accounting.serviceAgreementTemplates.AdministrativeOfficeServiceAgreementTemplate; import org.fenixedu.academic.domain.administrativeOffice.AdministrativeOffice; import org.fenixedu.academic.domain.candidacy.Candidacy; import org.fenixedu.academic.domain.candidacy.StudentCandidacy; import org.fenixedu.academic.domain.exceptions.DomainException; import org.fenixedu.academic.domain.organizationalStructure.Party; import org.fenixedu.academic.dto.accounting.EntryDTO; import org.fenixedu.academic.dto.accounting.SibsTransactionDetailDTO; import org.fenixedu.academic.util.Bundle; import org.fenixedu.academic.util.LabelFormatter; import org.fenixedu.academic.util.Money; import org.fenixedu.bennu.core.domain.User; import org.joda.time.DateTime; import org.joda.time.YearMonthDay; import pt.ist.fenixframework.dml.runtime.RelationAdapter; public class AdministrativeOfficeFeeAndInsuranceEvent extends AdministrativeOfficeFeeAndInsuranceEvent_Base implements IAdministrativeOfficeFeeEvent, IInsuranceEvent { static { getRelationPersonAccountingEvent().addListener(new RelationAdapter<Party, Event>() { @Override public void beforeAdd(Party party, Event event) { if (event instanceof AdministrativeOfficeFeeAndInsuranceEvent && party != null && party instanceof Person) { Person person = (Person) party; final AdministrativeOfficeFeeAndInsuranceEvent administrativeOfficeFeeAndInsuranceEvent = (AdministrativeOfficeFeeAndInsuranceEvent) event; if (person.hasAdministrativeOfficeFeeInsuranceEventFor(administrativeOfficeFeeAndInsuranceEvent .getExecutionYear())) { throw new DomainException( "error.org.fenixedu.academic.domain.accounting.events.AdministrativeOfficeFeeAndInsuranceEvent.event.is.already.defined.for.execution.year"); } } } }); } protected AdministrativeOfficeFeeAndInsuranceEvent() { super(); } public AdministrativeOfficeFeeAndInsuranceEvent(AdministrativeOffice administrativeOffice, Person person, ExecutionYear executionYear) { this(); init(administrativeOffice, EventType.ADMINISTRATIVE_OFFICE_FEE_INSURANCE, person, executionYear); } @Override public LabelFormatter getDescriptionForEntryType(EntryType entryType) { final LabelFormatter labelFormatter = new LabelFormatter(); labelFormatter.appendLabel(entryType.name(), Bundle.ENUMERATION).appendLabel(" - ") .appendLabel(getExecutionYear().getYear()); return labelFormatter; } @Override protected AdministrativeOfficeServiceAgreementTemplate getServiceAgreementTemplate() { return getAdministrativeOffice().getServiceAgreementTemplate(); } @Override protected Account getFromAccount() { return getPerson().getExternalAccount(); } @Override public Account getToAccount() { return getAdministrativeOffice().getUnit().getInternalAccount(); } public boolean isInsuranceAmountIncludedInDebt() { return !getPerson().hasInsuranceEventFor(getExecutionYear()); } public boolean hasToPayInsurance() { if (!isInsuranceAmountIncludedInDebt()) { return false; } if (hasInsuranceExemption()) { return false; } return getInsurancePayedAmount().lessThan(getInsuranceAmount()); } public boolean hasToPayAdministrativeOfficeFee() { return getAdministrativeOfficeFeePayedAmount().lessThan(getAdministrativeOfficeFeeAmount()); } private AdministrativeOfficeFeeAndInsurancePR getAdministrativeOfficeFeeAndInsurancePR() { return (AdministrativeOfficeFeeAndInsurancePR) getPostingRule(); } public Money getAdministrativeOfficeFeeAmount() { return getAdministrativeOfficeFeeAndInsurancePR().getAdministrativeOfficeFeeAmount(getStartDate(), getEndDate()); } public YearMonthDay getAdministrativeOfficeFeePaymentLimitDate() { return getPaymentEndDate() != null ? getPaymentEndDate() : getAdministrativeOfficeFeeAndInsurancePR() .getAdministrativeOfficeFeePaymentLimitDate(getStartDate(), getEndDate()); } public Money getAdministrativeOfficeFeePenaltyAmount() { return getAdministrativeOfficeFeeAndInsurancePR().getAdministrativeOfficeFeePenaltyAmount(getStartDate(), getEndDate()); } public Money getInsuranceAmount() { return getAdministrativeOfficeFeeAndInsurancePR().getInsuranceAmount(getStartDate(), getEndDate()); } @Override protected List<AccountingEventPaymentCode> createPaymentCodes() { AccountingEventPaymentCode paymentCode = findPaymentCodeInStudentCandidacy(); if (paymentCode != null) { paymentCode.setAccountingEvent(this); return Collections.singletonList(paymentCode); } final Money totalAmount = calculateTotalAmount(); return Collections.singletonList(AccountingEventPaymentCode.create( PaymentCodeType.ADMINISTRATIVE_OFFICE_FEE_AND_INSURANCE, new YearMonthDay(), calculatePaymentCodeEndDate(), this, totalAmount, totalAmount, getPerson())); } private AccountingEventPaymentCode findPaymentCodeInStudentCandidacy() { if (getPerson().getStudent() == null) { return null; } if (getPerson().getStudent().getActiveRegistrationsIn(getExecutionYear().getFirstExecutionPeriod()).size() != 1) { return null; } StudentCandidacy studentCandidacy = getActiveDgesCandidacy(getPerson()); if (studentCandidacy == null) { return null; } for (PaymentCode paymentCode : studentCandidacy.getAvailablePaymentCodesSet()) { if (!paymentCode.isNew()) { continue; } if (!paymentCode.getClass().equals(AccountingEventPaymentCode.class)) { continue; } AccountingEventPaymentCode accountingEventPaymentCode = (AccountingEventPaymentCode) paymentCode; if (accountingEventPaymentCode.getAccountingEvent() != null) { continue; } if (!PaymentCodeType.ADMINISTRATIVE_OFFICE_FEE_AND_INSURANCE.equals(accountingEventPaymentCode.getType())) { continue; } if (!getExecutionYear().containsDate(accountingEventPaymentCode.getStartDate().toDateTimeAtMidnight())) { continue; } return accountingEventPaymentCode; } return null; } private StudentCandidacy getActiveDgesCandidacy(Person person) { for (Candidacy candidacy : person.getCandidaciesSet()) { if (!candidacy.isActive()) { continue; } if (!(candidacy instanceof StudentCandidacy)) { continue; } return (StudentCandidacy) candidacy; } return null; } @Override protected List<AccountingEventPaymentCode> updatePaymentCodes() { final Money totalAmount = calculateTotalAmount(); final AccountingEventPaymentCode nonProcessedPaymentCode = getNonProcessedPaymentCode(); if (nonProcessedPaymentCode != null) { nonProcessedPaymentCode.update(new YearMonthDay(), calculatePaymentCodeEndDate(), totalAmount, totalAmount); } else { final AccountingEventPaymentCode paymentCode = getCancelledPaymentCode(); if (paymentCode != null) { paymentCode.update(new YearMonthDay(), calculatePaymentCodeEndDate(), totalAmount, totalAmount); paymentCode.setState(PaymentCodeState.NEW); } } return getNonProcessedPaymentCodes(); } private AccountingEventPaymentCode getCancelledPaymentCode() { return (getCancelledPaymentCodes().isEmpty() ? null : getCancelledPaymentCodes().iterator().next()); } private AccountingEventPaymentCode getNonProcessedPaymentCode() { return (getNonProcessedPaymentCodes().isEmpty() ? null : getNonProcessedPaymentCodes().iterator().next()); } private YearMonthDay calculatePaymentCodeEndDate() { final YearMonthDay today = new YearMonthDay(); final YearMonthDay administrativeOfficeFeePaymentLimitDate = getAdministrativeOfficeFeePaymentLimitDate(); return today.isBefore(administrativeOfficeFeePaymentLimitDate) ? administrativeOfficeFeePaymentLimitDate : calculateNextEndDate(today); } private Money calculateTotalAmount() { Money totalAmount = Money.ZERO; for (final EntryDTO entryDTO : calculateEntries()) { totalAmount = totalAmount.add(entryDTO.getAmountToPay()); } return totalAmount; } public AccountingEventPaymentCode calculatePaymentCode() { return calculatePaymentCodes().iterator().next(); } @Override protected Set<Entry> internalProcess(User responsibleUser, AccountingEventPaymentCode paymentCode, Money amountToPay, SibsTransactionDetailDTO transactionDetail) { return internalProcess(responsibleUser, buildEntryDTOsFrom(amountToPay), transactionDetail); } @Override public boolean isInDebt() { return isOpen() && ((getPaymentEndDate() != null && getPaymentEndDate().isBefore(new YearMonthDay())) || getSpecificPostingRule() .getWhenToApplyFixedAmountPenalty().isBefore(new YearMonthDay())); } private AdministrativeOfficeFeePR getSpecificPostingRule() { return (AdministrativeOfficeFeePR) getServiceAgreementTemplate().findPostingRuleBy(EventType.ADMINISTRATIVE_OFFICE_FEE, getStartDate(), getEndDate()); } private List<EntryDTO> buildEntryDTOsFrom(final Money amountToPay) { final List<EntryDTO> result = new ArrayList<EntryDTO>(2); Money insuranceAmountToDiscount = Money.ZERO; if (hasToPayInsurance()) { insuranceAmountToDiscount = getInsuranceAmount(); result.add(buildInsuranceEntryDTO(insuranceAmountToDiscount)); } final Money remainingAmount = amountToPay.subtract(insuranceAmountToDiscount); if (remainingAmount.isPositive()) { result.add(buildAdministrativeOfficeFeeEntryDTO(remainingAmount)); } return result; } private EntryDTO buildAdministrativeOfficeFeeEntryDTO(Money administrativeOfficeFeeAmountToDiscount) { return new EntryDTO(EntryType.ADMINISTRATIVE_OFFICE_FEE, this, administrativeOfficeFeeAmountToDiscount); } private EntryDTO buildInsuranceEntryDTO(Money insuranceAmountToDiscount) { return new EntryDTO(EntryType.INSURANCE_FEE, this, insuranceAmountToDiscount); } public void changePaymentCodeState(DateTime whenRegistered, PaymentMode paymentMode) { if (canCloseEvent(whenRegistered) && hasNonProcessedPaymentCode()) { getNonProcessedPaymentCode().setState(getPaymentCodeStateFor(paymentMode)); } } private boolean hasNonProcessedPaymentCode() { return (getNonProcessedPaymentCode() != null); } @Override public LabelFormatter getDescription() { final LabelFormatter labelFormatter = super.getDescription(); labelFormatter.appendLabel(" ").appendLabel(getExecutionYear().getYear()); return labelFormatter; } @Override public boolean isExemptionAppliable() { return true; } public boolean hasAdministrativeOfficeFeeAndInsurancePenaltyExemption() { return getAdministrativeOfficeFeeAndInsurancePenaltyExemption() != null; } public AdministrativeOfficeFeeAndInsurancePenaltyExemption getAdministrativeOfficeFeeAndInsurancePenaltyExemption() { for (final Exemption exemption : getExemptionsSet()) { if (exemption instanceof AdministrativeOfficeFeeAndInsurancePenaltyExemption) { return (AdministrativeOfficeFeeAndInsurancePenaltyExemption) exemption; } } return null; } public boolean hasAdministrativeOfficeFeeAndInsuranceExemption() { return getAdministrativeOfficeFeeAndInsuranceExemption() != null; } public Exemption getAdministrativeOfficeFeeAndInsuranceExemption() { for (final Exemption exemption : getExemptionsSet()) { if (exemption.isForAdministrativeOfficeFee()) { return exemption; } } return null; } @Override public void setPaymentEndDate(YearMonthDay paymentEndDate) { if (!isOpen()) { throw new DomainException( "error.org.fenixedu.academic.domain.accounting.events.AdministrativeOfficeFeeAndInsuranceEvent.payment.end.date.can.only.be.modified.on.open.events"); } super.setPaymentEndDate(paymentEndDate); } public Money getInsurancePayedAmount() { Money result = Money.ZERO; for (final AccountingTransaction transaction : getNonAdjustingTransactions()) { if (transaction.getToAccountEntry().getEntryType() == EntryType.INSURANCE_FEE) { result = result.add(transaction.getToAccountEntry().getAmountWithAdjustment()); } } return result; } public Money getInsurancePayedAmountFor(int civilYear) { Money result = Money.ZERO; for (final AccountingTransaction transaction : getNonAdjustingTransactions()) { if (transaction.getToAccountEntry().getEntryType() == EntryType.INSURANCE_FEE && transaction.isPayed(civilYear)) { result = result.add(transaction.getToAccountEntry().getAmountWithAdjustment()); } } return result; } public Money getAdministrativeOfficeFeePayedAmount() { Money result = Money.ZERO; for (final AccountingTransaction transaction : getNonAdjustingTransactions()) { if (transaction.getToAccountEntry().getEntryType() == EntryType.ADMINISTRATIVE_OFFICE_FEE) { result = result.add(transaction.getToAccountEntry().getAmountWithAdjustment()); } } return result; } public Money getAdministrativeOfficeFeePayedAmountFor(int civilYear) { Money result = Money.ZERO; for (final AccountingTransaction transaction : getNonAdjustingTransactions()) { if (transaction.getToAccountEntry().getEntryType() == EntryType.ADMINISTRATIVE_OFFICE_FEE && transaction.isPayed(civilYear)) { result = result.add(transaction.getToAccountEntry().getAmountWithAdjustment()); } } return result; } @Override public Money calculateAmountToPay(DateTime whenRegistered) { Money result = super.calculateAmountToPay(whenRegistered); if (result.isZero()) { return result; } result = result.subtract(getPerson().hasInsuranceEventFor(getExecutionYear()) ? getInsuranceAmount() : Money.ZERO); return result.isPositive() ? result : Money.ZERO; } @Override public Set<EntryType> getPossibleEntryTypesForDeposit() { final Set<EntryType> result = new HashSet<EntryType>(); result.add(EntryType.ADMINISTRATIVE_OFFICE_FEE); result.add(EntryType.INSURANCE_FEE); return result; } @Override public boolean isOpen() { if (isCancelled()) { return false; } return calculateAmountToPay(new DateTime()).greaterThan(Money.ZERO); } @Override public boolean isClosed() { if (isCancelled()) { return false; } return calculateAmountToPay(new DateTime()).lessOrEqualThan(Money.ZERO); } @Override public boolean isInState(final EventState eventState) { if (eventState == EventState.OPEN) { return isOpen(); } else if (eventState == EventState.CLOSED) { return isClosed(); } else if (eventState == EventState.CANCELLED) { return isCancelled(); } else { throw new DomainException( "error.org.fenixedu.academic.domain.accounting.events.gratuity.DfaGratuityEvent.unexpected.state.to.test"); } } @Override public boolean isAdministrativeOfficeAndInsuranceEvent() { return true; } public Exemption getInsuranceExemption() { for (final Exemption exemption : getExemptionsSet()) { if (exemption.isForInsurance()) { return exemption; } } return null; } public boolean hasInsuranceExemption() { return getInsuranceExemption() != null; } }