use of org.killbill.billing.invoice.usage.details.UsageConsumableInArrearTierUnitAggregate in project killbill by killbill.
the class ContiguousIntervalConsumableUsageInArrear method computeToBeBilledConsumableInArrearWith_TOP_TIER.
UsageConsumableInArrearTierUnitAggregate computeToBeBilledConsumableInArrearWith_TOP_TIER(final List<TieredBlock> tieredBlocks, final List<UsageConsumableInArrearTierUnitAggregate> previousUsage, final Long units) throws CatalogApiException {
Long remainingUnits = units;
// By default last last tierBlock
TieredBlock targetBlock = tieredBlocks.get(tieredBlocks.size() - 1);
int targetTierNum = tieredBlocks.size();
int tierNum = 0;
// Loop through all tier block
for (final TieredBlock tieredBlock : tieredBlocks) {
tierNum++;
final long blockTierSize = tieredBlock.getSize().longValue();
final long tmp = remainingUnits / blockTierSize + (remainingUnits % blockTierSize == 0 ? 0 : 1);
if (tmp > tieredBlock.getMax()) {
/* Includes the case where max is unlimited (-1) */
remainingUnits -= tieredBlock.getMax().longValue() * blockTierSize;
} else {
targetBlock = tieredBlock;
targetTierNum = tierNum;
break;
}
}
final long lastBlockTierSize = targetBlock.getSize().longValue();
final long nbBlocks = units / lastBlockTierSize + (units % lastBlockTierSize == 0 ? 0 : 1);
return new UsageConsumableInArrearTierUnitAggregate(targetTierNum, targetBlock.getUnit().getName(), targetBlock.getPrice().getPrice(getCurrency()), targetBlock.getSize().longValue(), nbBlocks);
}
use of org.killbill.billing.invoice.usage.details.UsageConsumableInArrearTierUnitAggregate in project killbill by killbill.
the class ContiguousIntervalConsumableUsageInArrear method getBilledDetailsForUnitType.
@VisibleForTesting
List<UsageConsumableInArrearTierUnitAggregate> getBilledDetailsForUnitType(final Iterable<InvoiceItem> billedItems, final String unitType) {
// Aggregate on a per-tier level, will return a list with item per level -- for this 'unitType'
final Map<Integer, UsageConsumableInArrearTierUnitAggregate> resultMap = new TreeMap<Integer, UsageConsumableInArrearTierUnitAggregate>(Ordering.<Integer>natural());
List<UsageConsumableInArrearTierUnitAggregate> tierDetails = new ArrayList<UsageConsumableInArrearTierUnitAggregate>();
for (final InvoiceItem bi : billedItems) {
if (usageDetailMode == UsageDetailMode.DETAIL) {
final UsageConsumableInArrearTierUnitAggregate targetTierUnitDetail = fromJson(bi.getItemDetails(), new TypeReference<UsageConsumableInArrearTierUnitAggregate>() {
});
if (targetTierUnitDetail.getTierUnit().equals(unitType)) {
// See https://github.com/killbill/killbill/issues/1325
final Long quantity = bi.getQuantity().longValue();
tierDetails.add(new UsageConsumableInArrearTierUnitAggregate(targetTierUnitDetail.getTier(), targetTierUnitDetail.getTierUnit(), bi.getRate(), targetTierUnitDetail.getTierBlockSize(), quantity, bi.getAmount()));
}
} else {
final UsageConsumableInArrearAggregate usageDetail = fromJson(bi.getItemDetails(), new TypeReference<UsageConsumableInArrearAggregate>() {
});
for (final UsageConsumableInArrearTierUnitAggregate unitAgg : usageDetail.getTierDetails()) {
if (unitAgg.getTierUnit().equals(unitType)) {
tierDetails.add(unitAgg);
}
}
}
}
for (final UsageConsumableInArrearTierUnitAggregate curDetail : tierDetails) {
if (!resultMap.containsKey(curDetail.getTier())) {
resultMap.put(curDetail.getTier(), curDetail);
} else {
final UsageConsumableInArrearTierUnitAggregate perTierDetail = resultMap.get(curDetail.getTier());
perTierDetail.updateQuantityAndAmount(curDetail.getQuantity());
}
}
return ImmutableList.copyOf(resultMap.values());
}
use of org.killbill.billing.invoice.usage.details.UsageConsumableInArrearTierUnitAggregate in project killbill by killbill.
the class ContiguousIntervalConsumableUsageInArrear method getToBeBilledUsageDetails.
@Override
protected UsageInArrearAggregate getToBeBilledUsageDetails(final LocalDate startDate, final LocalDate endDate, final List<RolledUpUnit> rolledUpUnits, final Iterable<InvoiceItem> billedItems, final boolean areAllBilledItemsWithDetails) throws CatalogApiException {
final Map<String, List<UsageConsumableInArrearTierUnitAggregate>> previousUnitsUsage;
if (usageDetailMode == UsageDetailMode.DETAIL || areAllBilledItemsWithDetails) {
previousUnitsUsage = new HashMap<String, List<UsageConsumableInArrearTierUnitAggregate>>();
for (RolledUpUnit cur : rolledUpUnits) {
final List<UsageConsumableInArrearTierUnitAggregate> usageInArrearDetailForUnitType = getBilledDetailsForUnitType(billedItems, cur.getUnitType());
previousUnitsUsage.put(cur.getUnitType(), usageInArrearDetailForUnitType);
}
} else {
previousUnitsUsage = ImmutableMap.of();
}
final List<UsageConsumableInArrearTierUnitAggregate> usageConsumableInArrearTierUnitAggregates = new ArrayList<UsageConsumableInArrearTierUnitAggregate>();
for (final RolledUpUnit cur : rolledUpUnits) {
if (!unitTypes.contains(cur.getUnitType())) {
log.warn("ContiguousIntervalConsumableInArrear is skipping unitType " + cur.getUnitType());
continue;
}
final List<UsageConsumableInArrearTierUnitAggregate> previousUsage = previousUnitsUsage.containsKey(cur.getUnitType()) ? previousUnitsUsage.get(cur.getUnitType()) : ImmutableList.<UsageConsumableInArrearTierUnitAggregate>of();
final List<UsageConsumableInArrearTierUnitAggregate> toBeBilledConsumableInArrear = computeToBeBilledConsumableInArrear(startDate, endDate, cur, previousUsage);
usageConsumableInArrearTierUnitAggregates.addAll(toBeBilledConsumableInArrear);
}
final UsageInArrearAggregate toBeBilledUsageDetails = new UsageConsumableInArrearAggregate(usageConsumableInArrearTierUnitAggregates);
return toBeBilledUsageDetails;
}
use of org.killbill.billing.invoice.usage.details.UsageConsumableInArrearTierUnitAggregate in project killbill by killbill.
the class ContiguousIntervalConsumableUsageInArrear method populateResults.
@Override
protected void populateResults(final LocalDate startDate, final LocalDate endDate, final DateTime catalogEffectiveDate, final BigDecimal billedUsage, final BigDecimal toBeBilledUsage, final UsageInArrearAggregate toBeBilledUsageDetails, final boolean areAllBilledItemsWithDetails, final boolean isPeriodPreviouslyBilled, final List<InvoiceItem> result) throws InvoiceApiException {
// In the case past invoice items showed the details (areAllBilledItemsWithDetails=true), billed usage has already been taken into account
// as it part of the reconciliation logic, so no need to subtract it here
final BigDecimal amountToBill = (usage.getTierBlockPolicy() == TierBlockPolicy.ALL_TIERS && areAllBilledItemsWithDetails) ? toBeBilledUsage : toBeBilledUsage.subtract(billedUsage);
if (amountToBill.compareTo(BigDecimal.ZERO) < 0) {
if (isDryRun || invoiceConfig.isUsageMissingLenient(internalTenantContext)) {
return;
} else {
throw new InvoiceApiException(ErrorCode.UNEXPECTED_ERROR, String.format("ILLEGAL INVOICING STATE: Usage period start='%s', end='%s', amountToBill='%s', (previously billed amount='%s', new proposed amount='%s')", startDate, endDate, amountToBill, billedUsage, toBeBilledUsage));
}
} else /* amountToBill.compareTo(BigDecimal.ZERO) >= 0 */
{
if (!isPeriodPreviouslyBilled || amountToBill.compareTo(BigDecimal.ZERO) > 0) {
if (UsageDetailMode.DETAIL == usageDetailMode) {
for (UsageConsumableInArrearTierUnitAggregate toBeBilledUsageDetail : ((UsageConsumableInArrearAggregate) toBeBilledUsageDetails).getTierDetails()) {
final String itemDetails = toJson(toBeBilledUsageDetail);
// See https://github.com/killbill/killbill/issues/1325
// Our current sql schema limits to an int value ...
final Integer quantity = toBeBilledUsageDetail.getQuantity() <= Integer.MAX_VALUE ? toBeBilledUsageDetail.getQuantity().intValue() : -1;
final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getProductName(), getPlanName(), getPhaseName(), usage.getName(), catalogEffectiveDate, startDate, endDate, toBeBilledUsageDetail.getAmount(), toBeBilledUsageDetail.getTierPrice(), getCurrency(), quantity, itemDetails);
result.add(item);
}
} else {
final String itemDetails = toJson(toBeBilledUsageDetails);
final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getProductName(), getPlanName(), getPhaseName(), usage.getName(), catalogEffectiveDate, startDate, endDate, amountToBill, null, getCurrency(), null, itemDetails);
result.add(item);
}
}
}
}
use of org.killbill.billing.invoice.usage.details.UsageConsumableInArrearTierUnitAggregate in project killbill by killbill.
the class ContiguousIntervalConsumableUsageInArrear method computeToBeBilledConsumableInArrearWith_ALL_TIERS.
List<UsageConsumableInArrearTierUnitAggregate> computeToBeBilledConsumableInArrearWith_ALL_TIERS(final LocalDate startDate, final LocalDate endDate, final List<TieredBlock> tieredBlocks, final List<UsageConsumableInArrearTierUnitAggregate> previousUsage, final Long units) throws CatalogApiException {
List<UsageConsumableInArrearTierUnitAggregate> toBeBilledDetails = Lists.newLinkedList();
long remainingUnits = units;
int tierNum = 0;
// we count tier from 1, 2, ...
final int lastPreviousUsageTier = previousUsage.size();
final boolean hasPreviousUsage = lastPreviousUsageTier > 0;
for (final TieredBlock tieredBlock : tieredBlocks) {
tierNum++;
final long blockTierSize = tieredBlock.getSize().longValue();
final long tmp = remainingUnits / blockTierSize + (remainingUnits % blockTierSize == 0 ? 0 : 1);
long nbUsedTierBlocks;
if (tieredBlock.getMax() != (double) -1 && tmp > tieredBlock.getMax()) {
nbUsedTierBlocks = tieredBlock.getMax().longValue();
remainingUnits -= tieredBlock.getMax() * blockTierSize;
} else {
nbUsedTierBlocks = tmp;
remainingUnits = 0;
}
// We generate an entry if we consumed anything on this tier or if this is the first tier to also support $0 Usage item
if (hasPreviousUsage) {
final Long previousUsageQuantity = tierNum <= lastPreviousUsageTier ? previousUsage.get(tierNum - 1).getQuantity() : 0;
// Be lenient for dryRun use cases (as we could have plugin optimizations not returning full usage data) or unless configured.
if (!isDryRun && !invoiceConfig.isUsageMissingLenient(internalTenantContext)) {
if (tierNum < lastPreviousUsageTier) {
Preconditions.checkState(nbUsedTierBlocks == previousUsageQuantity, String.format("Expected usage for subscription='%s', targetDate='%s', startDt='%s', endDt='%s', tier='%d', unit='%s' to be full, instead found units='[%d/%d]'", getSubscriptionId(), targetDate, startDate, endDate, tierNum, tieredBlock.getUnit().getName(), nbUsedTierBlocks, previousUsageQuantity));
} else {
Preconditions.checkState(nbUsedTierBlocks - previousUsageQuantity >= 0, String.format("Expected usage for subscription='%s', targetDate='%s', startDt='%s', endDt='%s', tier='%d', unit='%s' to contain at least as much as current usage, instead found units='[%d/%d]'", getSubscriptionId(), targetDate, startDate, endDate, tierNum, tieredBlock.getUnit().getName(), nbUsedTierBlocks, previousUsageQuantity));
}
}
nbUsedTierBlocks = nbUsedTierBlocks - previousUsageQuantity;
}
if (tierNum == 1 || nbUsedTierBlocks > 0) {
toBeBilledDetails.add(new UsageConsumableInArrearTierUnitAggregate(tierNum, tieredBlock.getUnit().getName(), tieredBlock.getPrice().getPrice(getCurrency()), blockTierSize, nbUsedTierBlocks));
}
}
return toBeBilledDetails;
}
Aggregations