All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.killbill.billing.invoice.tree.Item Maven / Gradle / Ivy

There is a newer version: 0.24.12
Show newest version
/*
 * Copyright 2010-2014 Ning, Inc.
 * Copyright 2014-2015 Groupon, Inc
 * Copyright 2014-2015 The Billing Project, LLC
 *
 * The Billing Project licenses this file to you 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.killbill.billing.invoice.tree;

import java.math.BigDecimal;
import java.util.UUID;

import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.LocalDate;

import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.generator.InvoiceDateUtils;
import org.killbill.billing.invoice.model.RecurringInvoiceItem;
import org.killbill.billing.invoice.model.RepairAdjInvoiceItem;
import org.killbill.billing.util.currency.KillBillMoney;

import com.google.common.base.Objects;
import com.google.common.base.Preconditions;

/**
 * An generic invoice item that contains all pertinent fields regarding of its InvoiceItemType.
 * 

* It contains an action that determines what to do when building the tree (whether in normal or merge mode). It also * keeps track of current adjusted and repair amount so subsequent repair can be limited to what is left. */ public class Item { private final UUID id; private final UUID accountId; private final UUID bundleId; private final UUID subscriptionId; private final UUID targetInvoiceId; private final UUID invoiceId; private final String planName; private final String phaseName; private final LocalDate startDate; private final LocalDate endDate; private final BigDecimal amount; private final BigDecimal rate; private final Currency currency; private final DateTime createdDate; private final UUID linkedId; private BigDecimal currentRepairedAmount; private BigDecimal adjustedAmount; private final ItemAction action; public enum ItemAction { ADD, CANCEL } public Item(final Item item, final ItemAction action) { this.id = item.id; this.accountId = item.accountId; this.bundleId = item.bundleId; this.subscriptionId = item.subscriptionId; this.targetInvoiceId = item.targetInvoiceId; this.invoiceId = item.invoiceId; this.planName = item.planName; this.phaseName = item.phaseName; this.startDate = item.startDate; this.endDate = item.endDate; this.amount = item.amount; this.rate = item.rate; this.currency = item.currency; // In merge mode, the reverse item needs to correctly point to itself (repair of original item) this.linkedId = action == ItemAction.ADD ? item.linkedId : this.id; this.createdDate = item.createdDate; this.currentRepairedAmount = item.currentRepairedAmount; this.adjustedAmount = item.adjustedAmount; this.action = action; } public Item(final InvoiceItem item, final UUID targetInvoiceId, final ItemAction action) { this.id = item.getId(); this.accountId = item.getAccountId(); this.bundleId = item.getBundleId(); this.subscriptionId = item.getSubscriptionId(); this.targetInvoiceId = targetInvoiceId; this.invoiceId = item.getInvoiceId(); this.planName = item.getPlanName(); this.phaseName = item.getPhaseName(); this.startDate = item.getStartDate(); this.endDate = item.getEndDate(); this.amount = item.getAmount().abs(); this.rate = item.getRate(); this.currency = item.getCurrency(); this.linkedId = item.getLinkedItemId(); this.createdDate = item.getCreatedDate(); this.action = action; this.currentRepairedAmount = BigDecimal.ZERO; this.adjustedAmount = BigDecimal.ZERO; } public InvoiceItem toInvoiceItem() { return toProratedInvoiceItem(startDate, endDate); } public InvoiceItem toProratedInvoiceItem(final LocalDate newStartDate, final LocalDate newEndDate) { int nbTotalDays = Days.daysBetween(startDate, endDate).getDays(); final boolean prorated = !(newStartDate.compareTo(startDate) == 0 && newEndDate.compareTo(endDate) == 0); // Pro-ration is built by using the startDate, endDate and amount of this item instead of using the rate and a potential full period. final BigDecimal positiveAmount = prorated ? InvoiceDateUtils.calculateProrationBetweenDates(newStartDate, newEndDate, nbTotalDays) .multiply(amount) : amount; if (action == ItemAction.ADD) { return new RecurringInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, newStartDate, newEndDate, positiveAmount, rate, currency); } else { // We first compute the maximum amount after adjustment and that sets the amount limit of how much can be repaired. final BigDecimal maxAvailableAmountAfterAdj = amount.subtract(adjustedAmount); final BigDecimal maxAvailableAmountForRepair = maxAvailableAmountAfterAdj.subtract(currentRepairedAmount); final BigDecimal positiveAmountForRepair = positiveAmount.compareTo(maxAvailableAmountForRepair) <= 0 ? positiveAmount : maxAvailableAmountForRepair; return positiveAmountForRepair.compareTo(BigDecimal.ZERO) > 0 ? new RepairAdjInvoiceItem(targetInvoiceId, accountId, newStartDate, newEndDate, positiveAmountForRepair.negate(), currency, linkedId) : null; } } public void incrementAdjustedAmount(final BigDecimal increment) { Preconditions.checkState(increment.compareTo(BigDecimal.ZERO) > 0); adjustedAmount = adjustedAmount.add(increment); } public void incrementCurrentRepairedAmount(final BigDecimal increment) { Preconditions.checkState(increment.compareTo(BigDecimal.ZERO) > 0); currentRepairedAmount = currentRepairedAmount.add(increment); } public ItemAction getAction() { return action; } public UUID getLinkedId() { return linkedId; } public LocalDate getEndDate() { return endDate; } public LocalDate getStartDate() { return startDate; } public BigDecimal getAmount() { return amount; } public UUID getId() { return id; } public Currency getCurrency() { return currency; } /** * Compare two items to check whether there are the same kind; that is whether or not they build for the same product/plan. * * @param other item to compare with * @return */ public boolean isSameKind(final Item other) { final InvoiceItem otherItem = other.toInvoiceItem(); // See https://github.com/killbill/killbill/issues/286 return otherItem != null && !id.equals(otherItem.getId()) && // Finally, for the tricky part... In case of complete repairs, the new invoiceItem will always meet all of the // following conditions: same type, subscription, start date. Depending on the catalog configuration, the end // date check could also match (e.g. repair from annual to monthly). For that scenario, we need to default // to catalog checks (the rate check is a lame check for versioned catalogs). Objects.firstNonNull(planName, "").equals(Objects.firstNonNull(otherItem.getPlanName(), "")) && Objects.firstNonNull(phaseName, "").equals(Objects.firstNonNull(otherItem.getPhaseName(), "")) && Objects.firstNonNull(rate, BigDecimal.ZERO).compareTo(Objects.firstNonNull(otherItem.getRate(), BigDecimal.ZERO)) == 0; } @Override public String toString() { final StringBuilder sb = new StringBuilder("Item{"); sb.append("id=").append(id); sb.append(", accountId=").append(accountId); sb.append(", bundleId=").append(bundleId); sb.append(", subscriptionId=").append(subscriptionId); sb.append(", targetInvoiceId=").append(targetInvoiceId); sb.append(", invoiceId=").append(invoiceId); sb.append(", planName='").append(planName).append('\''); sb.append(", phaseName='").append(phaseName).append('\''); sb.append(", startDate=").append(startDate); sb.append(", endDate=").append(endDate); sb.append(", amount=").append(amount); sb.append(", rate=").append(rate); sb.append(", currency=").append(currency); sb.append(", createdDate=").append(createdDate); sb.append(", linkedId=").append(linkedId); sb.append(", currentRepairedAmount=").append(currentRepairedAmount); sb.append(", adjustedAmount=").append(adjustedAmount); sb.append(", action=").append(action); sb.append('}'); return sb.toString(); } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final Item item = (Item) o; if (accountId != null ? !accountId.equals(item.accountId) : item.accountId != null) { return false; } if (action != item.action) { return false; } if (adjustedAmount != null ? adjustedAmount.compareTo(item.adjustedAmount) != 0 : item.adjustedAmount != null) { return false; } if (amount != null ? amount.compareTo(item.amount) != 0 : item.amount != null) { return false; } if (bundleId != null ? !bundleId.equals(item.bundleId) : item.bundleId != null) { return false; } if (createdDate != null ? createdDate.compareTo(item.createdDate) != 0 : item.createdDate != null) { return false; } if (currency != item.currency) { return false; } if (currentRepairedAmount != null ? currentRepairedAmount.compareTo(item.currentRepairedAmount) != 0 : item.currentRepairedAmount != null) { return false; } if (endDate != null ? endDate.compareTo(item.endDate) != 0 : item.endDate != null) { return false; } if (id != null ? !id.equals(item.id) : item.id != null) { return false; } if (invoiceId != null ? !invoiceId.equals(item.invoiceId) : item.invoiceId != null) { return false; } if (linkedId != null ? !linkedId.equals(item.linkedId) : item.linkedId != null) { return false; } if (phaseName != null ? !phaseName.equals(item.phaseName) : item.phaseName != null) { return false; } if (planName != null ? !planName.equals(item.planName) : item.planName != null) { return false; } if (rate != null ? rate.compareTo(item.rate) != 0 : item.rate != null) { return false; } if (startDate != null ? startDate.compareTo(item.startDate) != 0 : item.startDate != null) { return false; } if (subscriptionId != null ? !subscriptionId.equals(item.subscriptionId) : item.subscriptionId != null) { return false; } if (targetInvoiceId != null ? !targetInvoiceId.equals(item.targetInvoiceId) : item.targetInvoiceId != null) { return false; } return true; } @Override public int hashCode() { int result = id != null ? id.hashCode() : 0; result = 31 * result + (accountId != null ? accountId.hashCode() : 0); result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0); result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0); result = 31 * result + (targetInvoiceId != null ? targetInvoiceId.hashCode() : 0); result = 31 * result + (invoiceId != null ? invoiceId.hashCode() : 0); result = 31 * result + (planName != null ? planName.hashCode() : 0); result = 31 * result + (phaseName != null ? phaseName.hashCode() : 0); result = 31 * result + (startDate != null ? startDate.hashCode() : 0); result = 31 * result + (endDate != null ? endDate.hashCode() : 0); result = 31 * result + (amount != null ? amount.hashCode() : 0); result = 31 * result + (rate != null ? rate.hashCode() : 0); result = 31 * result + (currency != null ? currency.hashCode() : 0); result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0); result = 31 * result + (linkedId != null ? linkedId.hashCode() : 0); result = 31 * result + (currentRepairedAmount != null ? currentRepairedAmount.hashCode() : 0); result = 31 * result + (adjustedAmount != null ? adjustedAmount.hashCode() : 0); result = 31 * result + (action != null ? action.hashCode() : 0); return result; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy