org.estatio.dom.lease.invoicing.InvoiceCalculationService.java Source code

Java tutorial

Introduction

Here is the source code for org.estatio.dom.lease.invoicing.InvoiceCalculationService.java

Source

/*
 *
 *  Copyright 2012-2014 Eurocommercial Properties NV
 *
 *
 *  Licensed under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.estatio.dom.lease.invoicing;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.inject.Inject;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.ObjectUtils;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.apache.isis.applib.annotation.DomainService;
import org.apache.isis.applib.annotation.Hidden;
import org.apache.isis.applib.annotation.Programmatic;
import org.estatio.dom.UdoDomainService;
import org.estatio.dom.charge.Charge;
import org.estatio.dom.invoice.Invoices;
import org.estatio.dom.invoice.InvoicingInterval;
import org.estatio.dom.lease.*;
import org.estatio.dom.valuetypes.AbstractInterval.IntervalEnding;
import org.estatio.dom.valuetypes.LocalDateInterval;
import org.estatio.services.settings.EstatioSettingsService;

//@RequestScoped  // TODO: this should be @RequestScoped, I think, since has a field
@DomainService(menuOrder = "50")
@Hidden
public class InvoiceCalculationService extends UdoDomainService<InvoiceCalculationService> {

    public InvoiceCalculationService() {
        super(InvoiceCalculationService.class);
    }

    /**
     * class to store the result a calculation
     * 
     */
    public static class CalculationResult {
        private static final BigDecimal ZERO = new BigDecimal("0.00");
        private BigDecimal value;
        private BigDecimal valueOnDueDate;
        private BigDecimal mockValue;;

        private InvoicingInterval invoicingInterval;
        private LocalDateInterval effectiveInterval;

        public CalculationResult() {
            this(null);
        }

        public CalculationResult(final InvoicingInterval interval) {
            this(interval, interval.asLocalDateInterval(), ZERO, ZERO, ZERO);
        }

        public CalculationResult(final InvoicingInterval interval, final LocalDateInterval effectiveInterval,
                final BigDecimal value, final BigDecimal valueOnDueDate, final BigDecimal mockValue) {
            this.invoicingInterval = interval;
            this.effectiveInterval = effectiveInterval;
            this.value = value;
            this.valueOnDueDate = valueOnDueDate;
            this.mockValue = mockValue;
        }

        public BigDecimal value() {
            return value;
        }

        public BigDecimal valueOnDueDate() {
            return valueOnDueDate;
        }

        public BigDecimal mockValue() {
            return mockValue;
        }

        public InvoicingInterval invoicingInterval() {
            return invoicingInterval;
        }

        public LocalDateInterval effectiveInterval() {
            return effectiveInterval;
        }

        @Override
        public String toString() {
            return invoicingInterval.toString().concat(" : ").concat(value.toString());
        }
    }

    /**
     * Utility class for collection of calculation results
     */
    public static class CalculationResultsUtil {
        public static BigDecimal sum(final List<CalculationResult> list) {
            BigDecimal sum = BigDecimal.ZERO;
            if (list == null) {
                return sum;
            }
            for (CalculationResult result : list) {
                sum = sum.add(result.value());
            }
            return sum;
        }
    }

    private LocalDate systemEpochDate() {
        return estatioSettingsService.fetchEpochDate();
    }

    private String interactionId;

    private void startInteraction(final String parameters) {
        if (interactionId == null) {
            interactionId = LocalDateTime.now().toString().concat(" - ").concat(parameters);
        }
    }

    private void endInteraction() {
        interactionId = null;
    }

    @Programmatic
    public String calculateAndInvoice(InvoiceCalculationParameters parameters) {
        String lastInteractionId = null;
        invoices.removeRuns(parameters);
        try {
            startInteraction(parameters.toString());
            for (Lease lease : parameters.leases() == null ? leases.findLeasesByProperty(parameters.property())
                    : parameters.leases()) {
                lease.verifyUntil(parameters.dueDateRange().endDateExcluding());
                if (lease.getStatus() != LeaseStatus.SUSPENDED) {
                    SortedSet<LeaseItem> leaseItems = parameters.leaseItem() == null ? lease.getItems()
                            : new TreeSet<LeaseItem>(Arrays.asList(parameters.leaseItem()));
                    for (LeaseItem leaseItem : leaseItems) {
                        if (!leaseItem.getStatus().equals(LeaseItemStatus.SUSPENDED)) {
                            if (parameters.leaseItemTypes() == null
                                    || parameters.leaseItemTypes().contains(leaseItem.getType())) {
                                SortedSet<LeaseTerm> leaseTerms = parameters.leaseTerm() == null
                                        ? leaseItem.getTerms()
                                        : new TreeSet<LeaseTerm>(Arrays.asList(parameters.leaseTerm()));
                                for (LeaseTerm leaseTerm : leaseTerms) {
                                    final List<CalculationResult> results;
                                    results = calculateDueDateRange(leaseTerm, parameters);
                                    createInvoiceItems(leaseTerm, parameters, results);
                                }
                            }
                        }
                    }
                }
            }
        } finally {
            lastInteractionId = interactionId;
            endInteraction();
        }
        return lastInteractionId;
    }

    /**
     * Calculates a term with a given invoicing frequency
     */
    @Programmatic
    public List<CalculationResult> calculateDueDateRange(final LeaseTerm leaseTerm,
            final InvoiceCalculationParameters parameters) {
        final List<CalculationResult> results = Lists.newArrayList();
        final LocalDateInterval termInterval = leaseTerm.getEffectiveInterval();
        final LocalDateInterval rangeInterval = parameters.invoiceRunType().equals(InvoiceRunType.RETRO_RUN)
                && leaseTerm.getLeaseItem().getLease().getStartDate()
                        .compareTo(parameters.dueDateRange().startDate()) < 0
                                ? new LocalDateInterval(leaseTerm.getLeaseItem().getLease().getStartDate(),
                                        parameters.dueDateRange().endDateExcluding(),
                                        IntervalEnding.EXCLUDING_END_DATE)
                                : parameters.dueDateRange();
        final InvoicingFrequency invoicingFrequency = leaseTerm.getLeaseItem().getInvoicingFrequency();
        // TODO: As a result of EST-413 the check for 'termInterval != null &&
        // termInterval.isValid()' is removed because this is blocking the
        // calculation of periods outside the interval of the leases. As a
        // result the invoice calculation will be more eager so improving
        // performance, EST-315, should get some attention.
        if (rangeInterval.isValid()) {
            final List<InvoicingInterval> intervals = invoicingFrequency.intervalsInDueDateRange(rangeInterval,
                    termInterval);
            for (final InvoicingInterval invoicingInterval : intervals) {
                final LocalDateInterval effectiveInterval = invoicingInterval.asLocalDateInterval()
                        .overlap(termInterval);
                if (effectiveInterval == null) {
                    results.add(new CalculationResult(invoicingInterval));
                } else {
                    final BigDecimal overlapDays = new BigDecimal(effectiveInterval.days());
                    final BigDecimal frequencyDays = new BigDecimal(invoicingInterval.days());
                    final BigDecimal rangeFactor = leaseTerm.valueType().equals(LeaseTermValueType.FIXED)
                            ? BigDecimal.ONE
                            : overlapDays.divide(frequencyDays, MathContext.DECIMAL64);
                    final BigDecimal annualFactor = invoicingFrequency.annualMultiplier();
                    final LocalDate epochDate = ObjectUtils.firstNonNull(leaseTerm.getLeaseItem().getEpochDate(),
                            systemEpochDate());
                    BigDecimal mockValue = BigDecimal.ZERO;
                    if (epochDate != null && invoicingInterval.dueDate().isBefore(epochDate)) {
                        mockValue = leaseTerm.valueForDate(epochDate);
                    }
                    results.add(new CalculationResult(invoicingInterval, effectiveInterval,
                            calculateValue(rangeFactor, annualFactor,
                                    leaseTerm.valueForDate(
                                            parameters.dueDateRange().endDateExcluding().minusDays(1))),
                            calculateValue(rangeFactor, annualFactor,
                                    leaseTerm.valueForDate(invoicingInterval.dueDate())),
                            calculateValue(rangeFactor, annualFactor, mockValue)));
                }
            }
        }
        return results;
    }

    /**
     * Multiplies a value with the range and annual factors
     * 
     * @param rangeFactor
     * @param annualFactor
     * @param value
     * @return
     */
    private BigDecimal calculateValue(final BigDecimal rangeFactor, final BigDecimal annualFactor,
            final BigDecimal value) {
        if (value != null && annualFactor != null && rangeFactor != null) {
            return value.multiply(annualFactor).multiply(rangeFactor).setScale(2, RoundingMode.HALF_UP);
        }
        return new BigDecimal("0.00");
    }

    /**
     * Calculates an invoice item with the difference between the already
     * invoiced and calculated value.
     * 
     * @param leaseTerm
     * @param dueDate
     * @param calculationResult
     * @param invoicingFrequency
     */
    void createInvoiceItems(final LeaseTerm leaseTerm, final InvoiceCalculationParameters parameters,
            final List<CalculationResult> results) {

        for (CalculationResult result : results) {
            // TODO: this is a hack to speed up processing by ignoring zero
            // values on a normal run
            if (result.value().compareTo(BigDecimal.ZERO) != 0
                    || parameters.invoiceRunType().equals(InvoiceRunType.RETRO_RUN)) {
                BigDecimal invoicedValue = invoiceItemsForLease.invoicedValue(leaseTerm,
                        result.invoicingInterval().asLocalDateInterval());
                BigDecimal newValue = result.value().subtract(invoicedValue).subtract(result.mockValue());
                if (newValue.compareTo(BigDecimal.ZERO) != 0) {
                    boolean adjustment = invoicedValue.add(result.mockValue()).compareTo(BigDecimal.ZERO) != 0;
                    InvoiceItemForLease invoiceItem = invoiceItemsForLease.createUnapprovedInvoiceItem(leaseTerm,
                            result.invoicingInterval().asLocalDateInterval(), parameters.invoiceDueDate(),
                            interactionId);
                    invoiceItem.setNetAmount(newValue);
                    invoiceItem.setQuantity(BigDecimal.ONE);
                    LeaseItem leaseItem = leaseTerm.getLeaseItem();
                    Charge charge = leaseItem.getCharge();
                    invoiceItem.setCharge(charge);
                    invoiceItem.setDescription(charge.getDescription());
                    invoiceItem.setDueDate(parameters.invoiceDueDate());
                    invoiceItem.setStartDate(result.invoicingInterval().startDate());
                    invoiceItem.setEndDate(result.invoicingInterval().endDate());

                    LocalDateInterval intervalToUse = adjustment ? result.invoicingInterval().asLocalDateInterval()
                            : result.effectiveInterval();
                    invoiceItem.setEffectiveStartDate(intervalToUse.startDate());
                    invoiceItem.setEffectiveEndDate(intervalToUse.endDate());

                    invoiceItem.setTax(leaseItem.getEffectiveTax());
                    invoiceItem.verify();
                    invoiceItem.setAdjustment(adjustment);
                }
            }
        }
    }

    // //////////////////////////////////////

    @Inject
    EstatioSettingsService estatioSettingsService;

    @Inject
    private Invoices invoices;

    @Inject
    private InvoiceItemsForLease invoiceItemsForLease;

    @Inject
    private Leases leases;

}