/**
* 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;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.fenixedu.academic.domain.accounting.accountingTransactions.detail.SibsTransactionDetail;
import org.fenixedu.academic.domain.exceptions.DomainException;
import org.fenixedu.academic.dto.accounting.AccountingTransactionDetailDTO;
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.Money;
import org.fenixedu.bennu.core.domain.Bennu;
import org.fenixedu.bennu.core.domain.User;
import org.fenixedu.bennu.core.i18n.BundleUtil;
import org.joda.time.DateTime;
import pt.ist.fenixframework.dml.runtime.RelationAdapter;
public abstract class PostingRule extends PostingRule_Base {
public static Comparator<PostingRule> COMPARATOR_BY_EVENT_TYPE = new Comparator<PostingRule>() {
@Override
public int compare(PostingRule leftPostingRule, PostingRule rightPostingRule) {
int comparationResult = leftPostingRule.getEventType().compareTo(rightPostingRule.getEventType());
return (comparationResult == 0) ? leftPostingRule.getExternalId().compareTo(rightPostingRule.getExternalId()) : comparationResult;
}
};
public static Comparator<PostingRule> COMPARATOR_BY_START_DATE = new Comparator<PostingRule>() {
@Override
public int compare(PostingRule leftPostingRule, PostingRule rightPostingRule) {
int comparationResult = leftPostingRule.getStartDate().compareTo(rightPostingRule.getStartDate());
return (comparationResult == 0) ? leftPostingRule.getExternalId().compareTo(rightPostingRule.getExternalId()) : comparationResult;
}
};
public static Comparator<PostingRule> COMPARATOR_BY_END_DATE = new Comparator<PostingRule>() {
@Override
public int compare(PostingRule left, PostingRule right) {
int comparationResult;
if (!left.hasEndDate() && !right.hasEndDate()) {
comparationResult = 0;
} else if (!left.hasEndDate()) {
comparationResult = 1;
} else if (!right.hasEndDate()) {
return comparationResult = -1;
} else {
comparationResult = left.getEndDate().compareTo(right.getEndDate());
}
return comparationResult == 0 ? left.getExternalId().compareTo(right.getExternalId()) : comparationResult;
}
};
static {
getRelationServiceAgreementTemplatePostingRule().addListener(
new RelationAdapter<ServiceAgreementTemplate, PostingRule>() {
@Override
public void beforeAdd(ServiceAgreementTemplate serviceAgreementTemplate, PostingRule postingRule) {
checkIfPostingRuleOverlapsExisting(serviceAgreementTemplate, postingRule);
}
private void checkIfPostingRuleOverlapsExisting(ServiceAgreementTemplate serviceAgreementTemplate,
PostingRule postingRule) {
if (serviceAgreementTemplate != null) {
for (final PostingRule existingPostingRule : serviceAgreementTemplate.getPostingRulesSet()) {
if (postingRule.overlaps(existingPostingRule)) {
throw new DomainException(
"error.accounting.agreement.ServiceAgreementTemplate.postingRule.overlaps.existing.one");
}
}
}
}
});
}
protected PostingRule() {
super();
super.setRootDomainObject(Bennu.getInstance());
super.setCreationDate(new DateTime());
}
protected void init(EventType eventType, DateTime startDate, DateTime endDate,
ServiceAgreementTemplate serviceAgreementTemplate) {
checkParameters(eventType, startDate, serviceAgreementTemplate);
super.setEventType(eventType);
super.setStartDate(startDate);
super.setEndDate(endDate);
super.setServiceAgreementTemplate(serviceAgreementTemplate);
}
private void checkParameters(EventType eventType, DateTime startDate, ServiceAgreementTemplate serviceAgreementTemplate) {
if (eventType == null) {
throw new DomainException("error.accounting.agreement.postingRule.eventType.cannot.be.null");
}
if (startDate == null) {
throw new DomainException("error.accounting.agreement.postingRule.startDate.cannot.be.null");
}
if (serviceAgreementTemplate == null) {
throw new DomainException("error.accounting.agreement.postingRule.serviceAgreementTemplate.cannot.be.null");
}
}
public Set<Entry> process(User user, Collection<EntryDTO> entryDTOs, Event event, Account fromAccount, Account toAccount,
AccountingTransactionDetailDTO transactionDetail) {
if (entryDTOs.isEmpty()) {
throw new DomainException("error.accounting.PostingRule.entries.to.process.cannot.be.empty");
}
for (final EntryDTO entryDTO : entryDTOs) {
if (entryDTO.getAmountToPay().lessOrEqualThan(Money.ZERO)) {
throw new DomainException("error.accounting.PostingRule.amount.to.pay.must.be.greater.than.zero");
}
}
final Set<AccountingTransaction> resultingTransactions =
internalProcess(user, entryDTOs, event, fromAccount, toAccount, transactionDetail);
return getResultingEntries(resultingTransactions);
}
protected Set<Entry> getResultingEntries(Set<AccountingTransaction> resultingTransactions) {
final Set<Entry> result = new HashSet<Entry>();
for (final AccountingTransaction transaction : resultingTransactions) {
result.add(transaction.getToAccountEntry());
}
return result;
}
public final List<EntryDTO> calculateEntries(Event event) {
return calculateEntries(event, new DateTime());
}
public boolean isActiveForDate(DateTime when) {
if (getStartDate().isAfter(when)) {
return false;
} else {
return (hasEndDate()) ? !when.isAfter(getEndDate()) : true;
}
}
public boolean isActive() {
return isActiveForDate(new DateTime());
}
public boolean hasEndDate() {
return getEndDate() != null;
}
public boolean overlaps(PostingRule postingRule) {
return overlaps(postingRule.getEventType(), postingRule.getStartDate(), postingRule.getEndDate());
}
public boolean overlaps(final EventType eventType, final DateTime startDate, final DateTime endDate) {
if (getEventType() == eventType) {
if (hasEndDate() && endDate != null) {
return isActiveForDate(startDate) || isActiveForDate(endDate);
} else if (hasEndDate()) {
return !startDate.isAfter(getEndDate());
} else if (endDate != null) {
return !getStartDate().isAfter(endDate);
}
return true;
}
return false;
}
@Override
public void setCreationDate(DateTime creationDate) {
throw new DomainException("error.accounting.agreement.postingRule.cannot.modify.creationDate");
}
@Override
public void setEventType(EventType eventType) {
throw new DomainException("error.accounting.agreement.postingRule.cannot.modify.eventType");
}
@Override
public void setEndDate(DateTime endDate) {
if (hasEndDate()) {
throw new DomainException("error.accounting.PostingRule.endDate.is.already.set");
}
super.setEndDate(endDate);
}
public void deactivate() {
deactivate(new DateTime());
}
public void deactivate(final DateTime when) {
super.setEndDate(when.minus(10000));
}
public void delete() {
DomainException.throwWhenDeleteBlocked(getDeletionBlockers());
super.setServiceAgreementTemplate(null);
setRootDomainObject(null);
removeOtherRelations();
deleteDomainObject();
}
protected void removeOtherRelations() {
}
@Override
public void setServiceAgreementTemplate(ServiceAgreementTemplate serviceAgreementTemplate) {
throw new DomainException("error.accounting.agreement.postingRule.cannot.modify.serviceAgreementTemplate");
}
protected Entry makeEntry(EntryType entryType, Money amount, Account account) {
return new Entry(entryType, amount, account);
}
protected AccountingTransaction makeAccountingTransaction(User responsibleUser, Event event, Account from, Account to,
EntryType entryType, Money amount, AccountingTransactionDetailDTO transactionDetail) {
return new AccountingTransaction(responsibleUser, event, makeEntry(entryType, amount.negate(), from), makeEntry(
entryType, amount, to), makeAccountingTransactionDetail(transactionDetail));
}
protected AccountingTransactionDetail makeAccountingTransactionDetail(AccountingTransactionDetailDTO transactionDetailDTO) {
if (transactionDetailDTO instanceof SibsTransactionDetailDTO) {
final SibsTransactionDetailDTO sibsTransactionDetailDTO = (SibsTransactionDetailDTO) transactionDetailDTO;
return new SibsTransactionDetail(sibsTransactionDetailDTO.getWhenRegistered(),
sibsTransactionDetailDTO.getSibsTransactionId(), sibsTransactionDetailDTO.getSibsCode(),
sibsTransactionDetailDTO.getComments());
} else {
return new AccountingTransactionDetail(transactionDetailDTO.getWhenRegistered(),
transactionDetailDTO.getPaymentMode(), transactionDetailDTO.getComments());
}
}
public boolean isVisible() {
return true;
}
public final Money calculateTotalAmountToPay(Event event, DateTime when) {
return calculateTotalAmountToPay(event, when, true);
}
public void addOtherPartyAmount(User responsibleUser, Event event, Account fromAcount, Account toAccount, Money amount,
AccountingTransactionDetailDTO transactionDetailDTO) {
if (!event.isOtherPartiesPaymentsSupported()) {
throw new DomainException("error.accounting.PostingRule.event.does.not.support.other.party.payments");
}
checkRulesToAddOtherPartyAmount(event, amount);
internalAddOtherPartyAmount(responsibleUser, event, fromAcount, toAccount, amount, transactionDetailDTO);
}
protected void checkRulesToAddOtherPartyAmount(Event event, Money amount) {
if (amount.greaterThan(calculateTotalAmountToPay(event, event.getWhenOccured(), false))) {
throw new DomainException(
"error.accounting.PostingRule.cannot.add.other.party.amount.that.exceeds.event.amount.to.pay");
}
}
public void internalAddOtherPartyAmount(User responsibleUser, Event event, Account fromAcount, Account toAccount,
Money amount, AccountingTransactionDetailDTO transactionDetailDTO) {
throw new DomainException("error.accounting.PostingRule.does.not.implement.internal.other.party.amount");
}
public boolean isActiveForPeriod(DateTime startDate, DateTime endDate) {
if (getStartDate().isAfter(endDate)) {
return false;
}
if (getEndDate() != null && getEndDate().isBefore(startDate)) {
return false;
}
return true;
}
public AccountingTransaction depositAmount(final User responsibleUser, final Event event, final Account fromAcount,
final Account toAccount, final Money amount, final AccountingTransactionDetailDTO transactionDetailDTO) {
throw new DomainException("error.accounting.PostingRule.does.not.implement.deposit.amount");
}
public AccountingTransaction depositAmount(final User responsibleUser, final Event event, final Account fromAcount,
final Account toAccount, final Money amount, final EntryType entryType,
final AccountingTransactionDetailDTO transactionDetailDTO) {
checkEntryTypeForDeposit(event, entryType);
return makeAccountingTransaction(responsibleUser, event, fromAcount, toAccount, entryType, amount, transactionDetailDTO);
}
protected void checkEntryTypeForDeposit(final Event event, final EntryType entryType) {
if (!event.getPossibleEntryTypesForDeposit().contains(entryType)) {
throw new DomainException("error.accounting.PostingRule.entry.type.not.supported.for.deposit");
}
}
public boolean isMostRecent() {
return Collections.max(getServiceAgreementTemplate().getAllPostingRulesFor(getEventType()),
PostingRule.COMPARATOR_BY_END_DATE).equals(this);
}
public String getFormulaDescription() {
return BundleUtil.getString(Bundle.APPLICATION, this.getClass().getSimpleName() + ".formulaDescription");
}
protected boolean has(final EventType eventType) {
return getEventType().equals(eventType);
}
public final Money calculateTotalAmountToPay(Event event, DateTime when, boolean applyDiscount) {
Money amountToPay = doCalculationForAmountToPay(event, when, applyDiscount);
if (!event.isExemptionAppliable()) {
return amountToPay;
}
return subtractFromExemptions(event, when, applyDiscount, amountToPay);
}
protected abstract Money doCalculationForAmountToPay(Event event, DateTime when, boolean applyDiscount);
protected abstract Money subtractFromExemptions(Event event, DateTime when, boolean applyDiscount, Money amountToPay);
public PaymentCodeType calculatePaymentCodeTypeFromEvent(Event event, DateTime when, boolean applyDiscount) {
throw new DomainException("error.accounting.PostingRule.cannot.calculate.payment.code.type");
}
abstract public List<EntryDTO> calculateEntries(Event event, DateTime when);
abstract protected Set<AccountingTransaction> internalProcess(User user, Collection<EntryDTO> entryDTOs, Event event,
Account fromAccount, Account toAccount, AccountingTransactionDetailDTO transactionDetail);
static public Collection<PostingRule> findPostingRules(final EventType eventType) {
final Collection<PostingRule> result = new HashSet<PostingRule>();
for (final PostingRule postingRule : Bennu.getInstance().getPostingRulesSet()) {
if (postingRule.has(eventType)) {
result.add(postingRule);
}
}
return result;
}
public Money doCalculationForAmountToPayWithoutExemptions(Event event, DateTime when, boolean applyDiscount) {
return doCalculationForAmountToPay(event, when, applyDiscount);
}
}