/**
* 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.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.fenixedu.academic.domain.Person;
import org.fenixedu.academic.domain.exceptions.DomainException;
import org.fenixedu.academic.domain.exceptions.DomainExceptionWithLabelFormatter;
import org.fenixedu.academic.dto.accounting.CreditNoteEntryDTO;
import org.fenixedu.academic.util.LabelFormatter;
import org.fenixedu.academic.util.Money;
import org.fenixedu.bennu.core.domain.Bennu;
import org.joda.time.DateTime;
public class CreditNote extends CreditNote_Base {
public static Comparator<CreditNote> COMPARATOR_BY_NUMBER = new Comparator<CreditNote>() {
@Override
public int compare(CreditNote leftCreditNote, CreditNote rightCreditNote) {
int comparationResult = leftCreditNote.getNumber().compareTo(rightCreditNote.getNumber());
return (comparationResult == 0) ? leftCreditNote.getExternalId().compareTo(rightCreditNote.getExternalId()) : comparationResult;
}
};
private CreditNote() {
super();
final Integer year = new DateTime().getYear();
super.setNumber(generateCreditNoteNumber(year));
super.setRootDomainObject(Bennu.getInstance());
super.setWhenCreated(new DateTime());
super.setYear(year);
}
private CreditNote(final Receipt receipt, final Person responsible) {
this();
init(receipt, responsible);
}
private void init(Receipt receipt, Person responsible) {
checkParameters(receipt, responsible);
checkRulesToCreate(receipt);
super.setReceipt(receipt);
internalChangeState(responsible, CreditNoteState.EMITTED);
}
private void checkRulesToCreate(Receipt receipt) {
if (receipt.isAnnulled()) {
throw new DomainException("error.accounting.CreditNote.cannot.be.created.for.annulled.receipts");
}
}
private void checkParameters(Receipt receipt, Person responsible) {
if (receipt == null) {
throw new DomainException("error.accounting.CreditNote.receipt.cannot.be.null");
}
if (responsible == null) {
throw new DomainException("error.accounting.CreditNote.responsible.cannot.be.null");
}
}
private Integer generateCreditNoteNumber(final Integer year) {
final List<CreditNote> creditNotes = getCreditNotesForYear(year);
return creditNotes.isEmpty() ? Integer.valueOf(1) : Collections.max(creditNotes, CreditNote.COMPARATOR_BY_NUMBER)
.getNumber() + 1;
}
private List<CreditNote> getCreditNotesForYear(final Integer year) {
final List<CreditNote> result = new ArrayList<CreditNote>();
for (final CreditNote creditNote : Bennu.getInstance().getCreditNotesSet()) {
if (creditNote.getYear().equals(year)) {
result.add(creditNote);
}
}
return result;
}
@Override
public void setReceipt(Receipt receipt) {
throw new DomainException("error.accounting.CreditNote.cannot.modify.receipt");
}
@Override
public void setResponsible(Person responsible) {
throw new DomainException("error.accounting.CreditNote.cannot.modify.responsible");
}
@Override
public void setNumber(Integer number) {
throw new DomainException("error.accounting.CreditNote.cannot.modify.number");
}
@Override
public void setYear(Integer year) {
throw new DomainException("error.accounting.CreditNote.cannot.modify.year");
}
@Override
public void setWhenCreated(DateTime whenCreated) {
throw new DomainException("error.accounting.CreditNote.cannot.modify.whenCreated");
}
@Override
public void addCreditNoteEntries(CreditNoteEntry creditNoteEntry) {
throw new DomainException("error.accounting.CreditNote.cannot.add.creditNoteEntry");
}
@Override
public Set<CreditNoteEntry> getCreditNoteEntriesSet() {
return Collections.unmodifiableSet(super.getCreditNoteEntriesSet());
}
@Override
public void removeCreditNoteEntries(CreditNoteEntry creditNoteEntry) {
throw new DomainException("error.accounting.CreditNote.cannot.remove.creditNoteEntry");
}
@Override
public void setWhenUpdated(DateTime whenUpdated) {
throw new DomainException("error.accounting.CreditNote.cannot.modify.whenUpdated");
}
@Override
public void setState(CreditNoteState state) {
throw new DomainException("error.accounting.CreditNote.cannot.modify.state");
}
private void internalChangeState(final Person responsible, CreditNoteState state) {
super.setWhenUpdated(new DateTime());
if (getState() != null && getState() != CreditNoteState.EMITTED) {
throw new DomainException("error.accounting.CreditNote.only.emitted.credit.notes.can.be.changed");
}
super.setState(state);
super.setResponsible(responsible);
}
public void confirm(final Person responsible, final PaymentMode paymentMode) {
internalChangeState(responsible, CreditNoteState.PAYED);
for (final CreditNoteEntry creditNoteEntry : getCreditNoteEntriesSet()) {
creditNoteEntry.createAdjustmentAccountingEntry(responsible.getUser(), paymentMode);
}
}
public void annul(final Person responsible) {
internalChangeState(responsible, CreditNoteState.ANNULLED);
}
public void changeState(final Person responsible, final PaymentMode paymentMode, final CreditNoteState state) {
if (getState() == state) {
return;
}
if (state == CreditNoteState.ANNULLED) {
annul(responsible);
} else if (state == CreditNoteState.PAYED) {
confirm(responsible, paymentMode);
} else {
throw new DomainException("error.org.fenixedu.academic.domain.accounting.CreditNote.cannot.change.to.given.state");
}
}
public static CreditNote create(Receipt receipt, Person responsible, List<CreditNoteEntryDTO> entryDTOs) {
final CreditNote creditNote = new CreditNote(receipt, responsible);
if (entryDTOs == null || entryDTOs.isEmpty()) {
throw new DomainException("error.accounting.CreditNote.cannot.be.created.without.entries");
}
for (final CreditNoteEntryDTO entryDTO : entryDTOs) {
if (!entryDTO.getEntry().canApplyReimbursement(entryDTO.getAmountToPay().negate())) {
throw new DomainExceptionWithLabelFormatter(
"error.accounting.CreditNoteEntry.amount.to.reimburse.exceeds.entry.amount", entryDTO.getEntry()
.getDescription());
}
new CreditNoteEntry(creditNote, entryDTO.getEntry(), entryDTO.getAmountToPay());
}
checkIfEmittedCreditNotesExceedEventsMaxReimbursableAmounts(receipt);
return creditNote;
}
private static void checkIfEmittedCreditNotesExceedEventsMaxReimbursableAmounts(Receipt receipt) {
for (final Entry<Event, Money> each : calculateAmountsToReimburseByEvent(receipt).entrySet()) {
if (!each.getKey().canApplyReimbursement(each.getValue())) {
throw new DomainExceptionWithLabelFormatter(
"error.accounting.CreditNote.the.sum.credit.notes.in.emitted.state.exceeds.event.reimbursable.amount",
each.getKey().getDescription(), new LabelFormatter(each.getKey().getReimbursableAmount().toPlainString()));
}
}
}
private static Map<Event, Money> calculateAmountsToReimburseByEvent(Receipt receipt) {
final Map<Event, Money> amountToReimburseByEvent = new HashMap<Event, Money>();
for (final CreditNote creditNote : receipt.getEmittedCreditNotes()) {
for (final CreditNoteEntry creditNoteEntry : creditNote.getCreditNoteEntriesSet()) {
final Event event = creditNoteEntry.getAccountingEntry().getAccountingTransaction().getEvent();
final Money amountToReimburse = creditNoteEntry.getAmount();
if (amountToReimburseByEvent.containsKey(event)) {
amountToReimburseByEvent.put(event, amountToReimburseByEvent.get(event).add(amountToReimburse));
} else {
amountToReimburseByEvent.put(event, amountToReimburse);
}
}
}
return amountToReimburseByEvent;
}
public boolean isEmitted() {
return getState() == CreditNoteState.EMITTED;
}
public boolean isAnnulled() {
return getState() == CreditNoteState.ANNULLED;
}
public boolean isPayed() {
return getState() == CreditNoteState.PAYED;
}
public boolean isAllowedToChangeState() {
return isEmitted();
}
public Money getTotalAmount() {
Money totalAmount = Money.ZERO;
for (final CreditNoteEntry creditNoteEntry : getCreditNoteEntriesSet()) {
totalAmount = totalAmount.add(creditNoteEntry.getAmount());
}
return totalAmount;
}
}