killbill-memoizeit
Changes
entitlement/src/main/java/com/ning/billing/entitlement/api/transfer/DefaultEntitlementTransferApi.java 6(+3 -3)
invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java 137(+9 -128)
invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java 147(+147 -0)
junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java 5(+4 -1)
Details
diff --git a/api/src/main/java/com/ning/billing/catalog/api/Plan.java b/api/src/main/java/com/ning/billing/catalog/api/Plan.java
index 8518b94..551a9e1 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/Plan.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/Plan.java
@@ -45,6 +45,6 @@ public interface Plan {
public abstract PlanPhase findPhase(String name) throws CatalogApiException;
- public abstract DateTime dateOfFirstRecurringNonZeroCharge(DateTime subscriptionStartDate);
+ public abstract DateTime dateOfFirstRecurringNonZeroCharge(DateTime subscriptionStartDate, PhaseType intialPhaseType);
}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/transfer/EntitlementTransferApi.java b/api/src/main/java/com/ning/billing/entitlement/api/transfer/EntitlementTransferApi.java
index 796717a..06c08ae 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/transfer/EntitlementTransferApi.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/transfer/EntitlementTransferApi.java
@@ -24,7 +24,8 @@ import com.ning.billing.util.callcontext.CallContext;
public interface EntitlementTransferApi {
- public SubscriptionBundle transferBundle(final UUID sourceAccountId, final UUID destAccountId, final String bundleKey, final DateTime requestedDate, final boolean transferAddOn, final CallContext context)
+ public SubscriptionBundle transferBundle(final UUID sourceAccountId, final UUID destAccountId, final String bundleKey, final DateTime requestedDate,
+ final boolean transferAddOn, final boolean cancelImmediately, final CallContext context)
throws EntitlementTransferApiException;
}
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index 97f8824..8e000da 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -186,6 +186,10 @@ public enum ErrorCode {
INVOICE_NOT_FOUND(4006, "No invoice could be found for id %s."),
INVOICE_NOTHING_TO_DO(4007, "No invoice to generate for account %s and date %s"),
INVOICE_NO_SUCH_CREDIT(4008, "Credit Item for id %s does not exist"),
+ CREDIT_AMOUNT_INVALID(4009, "Credit amount %s should be strictly positive"),
+ INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID(4010, "Invoice adjustment amount %s should be strictly positive"),
+ INVOICE_ITEM_NOT_FOUND(4011, "No invoice item could be found for id %s."),
+ INVOICE_INVALID_FOR_INVOICE_ITEM_ADJUSTMENT(4012, "Invoice item %s doesn't belong to invoice %s."),
/*
*
@@ -199,7 +203,6 @@ public enum ErrorCode {
CHARGE_BACK_DOES_NOT_EXIST(4004, "Could not find chargeback for id %s."),
INVOICE_PAYMENT_BY_ATTEMPT_NOT_FOUND(4905, "No invoice payment could be found for paymentAttempt id %s."),
REFUND_AMOUNT_TOO_HIGH(4906, "Tried to refund %s of a %s payment."),
- CREDIT_AMOUNT_INVALID(4907, "Credit amount %s should be striclty positive"),
/*
*
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItemType.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItemType.java
index 93afad8..548bfd5 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItemType.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItemType.java
@@ -17,10 +17,19 @@
package com.ning.billing.invoice.api;
public enum InvoiceItemType {
+ // Fixed (one-time) charge
FIXED,
+ // Recurring charge
RECURRING,
+ // Internal adjustment, used for repair
REPAIR_ADJ,
+ // Internal adjustment, used as rollover credits
CBA_ADJ,
+ // Credit adjustment, either at the account level (on its own invoice) or against an existing invoice
+ // (invoice level adjustment)
CREDIT_ADJ,
+ // Invoice item adjustment
+ ITEM_ADJ,
+ // Refund adjustment (against a posted payment)
REFUND_ADJ
}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
index ef784e0..38543f2 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
@@ -22,6 +22,8 @@ import java.util.Collection;
import java.util.List;
import java.util.UUID;
+import javax.annotation.Nullable;
+
import org.joda.time.LocalDate;
import com.ning.billing.account.api.AccountApiException;
@@ -31,6 +33,12 @@ import com.ning.billing.util.callcontext.CallContext;
public interface InvoiceUserApi {
+ /**
+ * Get all invoices for a given account.
+ *
+ * @param accountId account id
+ * @return all invoices
+ */
public List<Invoice> getInvoicesByAccount(UUID accountId);
/**
@@ -42,12 +50,36 @@ public interface InvoiceUserApi {
*/
public List<Invoice> getInvoicesByAccount(UUID accountId, LocalDate fromDate);
+ /**
+ * Retrieve the account balance.
+ *
+ * @param accountId account id
+ * @return the account balance
+ */
public BigDecimal getAccountBalance(UUID accountId);
+ /**
+ * Retrieve an invoice by id.
+ *
+ * @param invoiceId invoice id
+ * @return the invoice
+ */
public Invoice getInvoice(UUID invoiceId);
+ /**
+ * Retrieve an invoice by invoice number.
+ *
+ * @param number invoice number
+ * @return the invoice
+ */
public Invoice getInvoiceByNumber(Integer number);
+ /**
+ * Record a payment for an invoice.
+ *
+ * @param invoicePayment invoice payment
+ * @param context call context
+ */
public void notifyOfPayment(InvoicePayment invoicePayment, CallContext context);
/**
@@ -71,10 +103,31 @@ public interface InvoiceUserApi {
*/
public Invoice triggerInvoiceGeneration(UUID accountId, LocalDate targetDate, boolean dryRun, CallContext context) throws InvoiceApiException;
+ /**
+ * Mark an invoice as written off.
+ *
+ * @param invoiceId invoice id
+ * @param context call context
+ * @throws TagApiException
+ */
public void tagInvoiceAsWrittenOff(UUID invoiceId, CallContext context) throws TagApiException;
+ /**
+ * Unmark an invoice as written off.
+ *
+ * @param invoiceId invoice id
+ * @param context call context
+ * @throws TagApiException
+ */
public void tagInvoiceAsNotWrittenOff(UUID invoiceId, CallContext context) throws TagApiException;
+ /**
+ * Retrieve a credit by id.
+ *
+ * @param creditId credit id
+ * @return the credit
+ * @throws InvoiceApiException
+ */
public InvoiceItem getCreditById(UUID creditId) throws InvoiceApiException;
/**
@@ -92,7 +145,7 @@ public interface InvoiceUserApi {
Currency currency, CallContext context) throws InvoiceApiException;
/**
- * Add a credit to an invoice.
+ * Add a credit to an invoice. This can be used to adjust invoices.
*
* @param accountId account id
* @param invoiceId invoice id
@@ -106,5 +159,30 @@ public interface InvoiceUserApi {
public InvoiceItem insertCreditForInvoice(UUID accountId, UUID invoiceId, BigDecimal amount, LocalDate effectiveDate,
Currency currency, CallContext context) throws InvoiceApiException;
+ /**
+ * Adjust a given invoice item.
+ *
+ * @param accountId account id
+ * @param invoiceId invoice id
+ * @param invoiceItemId invoice item id
+ * @param effectiveDate the effective date for this adjustment invoice item
+ * @param amount the adjustment amount. Pass null to adjust for the full amount of the original item
+ * @param currency adjustment currency. Pass null to use the original currency
+ * @param context the call context
+ * @return the adjustment invoice item
+ * @throws InvoiceApiException
+ */
+ public InvoiceItem insertInvoiceItemAdjustment(UUID accountId, UUID invoiceId, UUID invoiceItemId, LocalDate effectiveDate,
+ @Nullable BigDecimal amount, @Nullable Currency currency, CallContext context) throws InvoiceApiException;
+
+ /**
+ * Retrieve the invoice formatted in HTML.
+ *
+ * @param invoiceId invoice id
+ * @return the invoice in HTML format
+ * @throws AccountApiException
+ * @throws IOException
+ * @throws InvoiceApiException
+ */
public String getInvoiceAsHTML(UUID invoiceId) throws AccountApiException, IOException, InvoiceApiException;
}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java
new file mode 100644
index 0000000..cdd35dd
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.beatrix.integration;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import junit.framework.Assert;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+
+@Guice(modules = {BeatrixModule.class})
+public class TestBundleTransfer extends TestIntegrationBase {
+
+
+ @Test(groups = "slow")
+ public void testBundleTransferWithBPAnnualOnly() throws Exception {
+
+ final Account account = createAccountWithPaymentMethod(getAccountData(3));
+
+ // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+
+ final DateTime initialDate = new DateTime(2012, 4, 1, 0, 15, 42, 0, testTimeZone);
+
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+ final SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "mycutebundle", context);
+
+ final String productName = "Shotgun";
+ final BillingPeriod term = BillingPeriod.ANNUAL;
+ final String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+ //
+ // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+ //
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.INVOICE);
+ final PlanPhaseSpecifier bpPlanPhaseSpecifier = new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null);
+ final SubscriptionData bpSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+ bpPlanPhaseSpecifier,
+ null,
+ context));
+ assertNotNull(bpSubscription);
+ assertTrue(busHandler.isCompleted(DELAY));
+ assertListenerStatus();
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId()).size(), 1);
+
+ assertEquals(entitlementUserApi.getSubscriptionFromId(bpSubscription.getId()).getCurrentPlan().getBillingPeriod(), BillingPeriod.ANNUAL);
+
+ // Move out of trials for interesting invoices adjustments
+ busHandler.pushExpectedEvent(NextEvent.PHASE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ clock.addDays(40);
+ assertTrue(busHandler.isCompleted(DELAY));
+ assertListenerStatus();
+
+ // BUNDLE TRANSFER
+ final Account newAccount = createAccountWithPaymentMethod(getAccountData(17));
+
+ busHandler.pushExpectedEvent(NextEvent.TRANSFER);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ transferApi.transferBundle(account.getId(), newAccount.getId(), "mycutebundle", clock.getUTCNow(), false, false, context);
+ assertTrue(busHandler.isCompleted(DELAY));
+ assertListenerStatus();
+
+ List<Invoice> invoices =invoiceUserApi.getInvoicesByAccount(newAccount.getId());
+ assertEquals(invoices.size(), 1);
+
+ final List<InvoiceItem> invoiceItems = invoices.get(0).getInvoiceItems();
+ assertEquals(invoiceItems.size(), 1);
+ InvoiceItem theItem = invoiceItems.get(0);
+ Assert.assertTrue(theItem.getStartDate().compareTo(new LocalDate(2012,05,11)) == 0);
+ Assert.assertTrue(theItem.getEndDate().compareTo(new LocalDate(2013,05,11)) == 0);
+ Assert.assertTrue(theItem.getAmount().compareTo(new BigDecimal("2399.9500")) == 0);
+ }
+
+ @Test(groups = "slow")
+ public void testBundleTransferWithBPMonthlyOnly() throws Exception {
+
+ final Account account = createAccountWithPaymentMethod(getAccountData(9));
+
+ // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+
+ final DateTime initialDate = new DateTime(2012, 4, 1, 0, 15, 42, 0, testTimeZone);
+
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+ final SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "mycutebundle", context);
+
+ final String productName = "Shotgun";
+ final BillingPeriod term = BillingPeriod.MONTHLY;
+ final String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+ //
+ // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+ //
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.INVOICE);
+ final PlanPhaseSpecifier bpPlanPhaseSpecifier = new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null);
+ final SubscriptionData bpSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+ bpPlanPhaseSpecifier,
+ null,
+ context));
+ assertNotNull(bpSubscription);
+ assertTrue(busHandler.isCompleted(DELAY));
+ assertListenerStatus();
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId()).size(), 1);
+
+ assertEquals(entitlementUserApi.getSubscriptionFromId(bpSubscription.getId()).getCurrentPlan().getBillingPeriod(), BillingPeriod.MONTHLY);
+
+ // Move out of trials for interesting invoices adjustments
+ busHandler.pushExpectedEvent(NextEvent.PHASE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ clock.addDays(32);
+ assertTrue(busHandler.isCompleted(DELAY));
+ assertListenerStatus();
+
+ // BUNDLE TRANSFER
+ final Account newAccount = createAccountWithPaymentMethod(getAccountData(15));
+
+ busHandler.pushExpectedEvent(NextEvent.TRANSFER);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ transferApi.transferBundle(account.getId(), newAccount.getId(), "mycutebundle", clock.getUTCNow(), false, false, context);
+ assertTrue(busHandler.isCompleted(DELAY));
+ assertListenerStatus();
+
+ List<Invoice> invoices =invoiceUserApi.getInvoicesByAccount(newAccount.getId());
+ assertEquals(invoices.size(), 1);
+
+ final List<InvoiceItem> invoiceItems = invoices.get(0).getInvoiceItems();
+ assertEquals(invoiceItems.size(), 1);
+ InvoiceItem theItem = invoiceItems.get(0);
+ Assert.assertTrue(theItem.getStartDate().compareTo(new LocalDate(2012,05,03)) == 0);
+ Assert.assertTrue(theItem.getEndDate().compareTo(new LocalDate(2012,05,15)) == 0);
+ Assert.assertTrue(theItem.getAmount().compareTo(new BigDecimal("99.98")) == 0);
+ }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
index 48607a0..4aa10d8 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
@@ -53,6 +53,7 @@ import com.ning.billing.catalog.api.Currency;
import com.ning.billing.dbi.MysqlTestingHelper;
import com.ning.billing.entitlement.api.EntitlementService;
import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
+import com.ning.billing.entitlement.api.transfer.EntitlementTransferApi;
import com.ning.billing.entitlement.api.user.EntitlementUserApi;
import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.Subscription;
@@ -125,10 +126,14 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
@Inject
protected MysqlTestingHelper helper;
+
@Inject
protected EntitlementUserApi entitlementUserApi;
@Inject
+ protected EntitlementTransferApi transferApi;
+
+ @Inject
protected EntitlementTimelineApi repairApi;
@Inject
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java
index fcf35b5..efb16c9 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java
@@ -35,6 +35,7 @@ import org.joda.time.DateTime;
import com.ning.billing.ErrorCode;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.Product;
@@ -234,9 +235,17 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
}
@Override
- public DateTime dateOfFirstRecurringNonZeroCharge(final DateTime subscriptionStartDate) {
+ public DateTime dateOfFirstRecurringNonZeroCharge(final DateTime subscriptionStartDate, final PhaseType initialPhaseType) {
DateTime result = subscriptionStartDate.toDateTime();
+ boolean skipPhase = initialPhaseType == null ? false : true;
for (final PlanPhase phase : getAllPhases()) {
+ if (skipPhase) {
+ if (phase.getPhaseType() != initialPhaseType) {
+ continue;
+ } else {
+ skipPhase = false;
+ }
+ }
if (phase.getRecurringPrice() == null || phase.getRecurringPrice().isZero()) {
result = phase.getDuration().addToDateTime(result);
} else {
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java b/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java
index cf2987e..3af91ed 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java
@@ -50,8 +50,8 @@ public class TestPlan extends CatalogTestSuite {
final DefaultPlan p2 = MockPlan.createBicycleNoTrialEvergreen1USD();
final DateTime requestedDate = new DateTime();
- Assert.assertEquals(p0.dateOfFirstRecurringNonZeroCharge(requestedDate).compareTo(requestedDate.plusDays(30)), 0);
- Assert.assertEquals(p1.dateOfFirstRecurringNonZeroCharge(requestedDate).compareTo(requestedDate.plusDays(100)), 0);
- Assert.assertEquals(p2.dateOfFirstRecurringNonZeroCharge(requestedDate).compareTo(requestedDate.plusDays(0)), 0);
+ Assert.assertEquals(p0.dateOfFirstRecurringNonZeroCharge(requestedDate, null).compareTo(requestedDate.plusDays(30)), 0);
+ Assert.assertEquals(p1.dateOfFirstRecurringNonZeroCharge(requestedDate, null).compareTo(requestedDate.plusDays(100)), 0);
+ Assert.assertEquals(p2.dateOfFirstRecurringNonZeroCharge(requestedDate, null).compareTo(requestedDate.plusDays(0)), 0);
}
}
diff --git a/catalog/src/test/java/com/ning/billing/mock/catalog/MockPlan.java b/catalog/src/test/java/com/ning/billing/mock/catalog/MockPlan.java
index 426c690..720243a 100644
--- a/catalog/src/test/java/com/ning/billing/mock/catalog/MockPlan.java
+++ b/catalog/src/test/java/com/ning/billing/mock/catalog/MockPlan.java
@@ -23,6 +23,7 @@ import org.joda.time.DateTime;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.Product;
@@ -86,7 +87,7 @@ public class MockPlan implements Plan {
@Override
public DateTime dateOfFirstRecurringNonZeroCharge(
- final DateTime subscriptionStartDate) {
+ final DateTime subscriptionStartDate, PhaseType phaseType) {
return null;
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/transfer/DefaultEntitlementTransferApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/transfer/DefaultEntitlementTransferApi.java
index 99155bd..85d8c31 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/transfer/DefaultEntitlementTransferApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/transfer/DefaultEntitlementTransferApi.java
@@ -201,8 +201,7 @@ public class DefaultEntitlementTransferApi implements EntitlementTransferApi {
@Override
public SubscriptionBundle transferBundle(final UUID sourceAccountId, final UUID destAccountId,
final String bundleKey, final DateTime transferDate, final boolean transferAddOn,
- final CallContext context) throws EntitlementTransferApiException {
-
+ final boolean cancelImmediately, final CallContext context) throws EntitlementTransferApiException {
try {
final DateTime effectiveTransferDate = transferDate == null ? clock.getUTCNow() : transferDate;
@@ -235,7 +234,8 @@ public class DefaultEntitlementTransferApi implements EntitlementTransferApi {
} else {
// If BP or STANDALONE subscription, create the cancel event on effectiveCancelDate
- final DateTime effectiveCancelDate = oldSubscription.getChargedThroughDate() != null && effectiveTransferDate.isBefore(oldSubscription.getChargedThroughDate()) ?
+ final DateTime effectiveCancelDate = !cancelImmediately && oldSubscription.getChargedThroughDate() != null &&
+ effectiveTransferDate.isBefore(oldSubscription.getChargedThroughDate()) ?
oldSubscription.getChargedThroughDate() : effectiveTransferDate;
final EntitlementEvent cancelEvent = new ApiEventCancel(new ApiEventBuilder()
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/transfer/TestTransfer.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/transfer/TestTransfer.java
index be36ccb..50798e6 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/transfer/TestTransfer.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/transfer/TestTransfer.java
@@ -77,7 +77,7 @@ public class TestTransfer extends TestApiBase {
testListener.pushExpectedEvent(NextEvent.TRANSFER);
testListener.pushExpectedEvent(NextEvent.CANCEL);
- transferApi.transferBundle(bundle.getAccountId(), newAccountId, bundle.getKey(), transferRequestedDate, false, context);
+ transferApi.transferBundle(bundle.getAccountId(), newAccountId, bundle.getKey(), transferRequestedDate, false, false, context);
assertTrue(testListener.isCompleted(3000));
final DateTime afterTransferDate = clock.getUTCNow();
@@ -128,7 +128,7 @@ public class TestTransfer extends TestApiBase {
testListener.pushExpectedEvent(NextEvent.TRANSFER);
final DateTime transferRequestedDate = clock.getUTCNow();
- transferApi.transferBundle(bundle.getAccountId(), newAccountId, bundle.getKey(), transferRequestedDate, false, context);
+ transferApi.transferBundle(bundle.getAccountId(), newAccountId, bundle.getKey(), transferRequestedDate, false, false, context);
assertTrue(testListener.isCompleted(3000));
// CHECK OLD BASE IS CANCEL AT THE TRANSFER DATE
@@ -176,7 +176,7 @@ public class TestTransfer extends TestApiBase {
final DateTime transferRequestedDate = clock.getUTCNow();
testListener.pushExpectedEvent(NextEvent.TRANSFER);
testListener.pushExpectedEvent(NextEvent.CANCEL);
- transferApi.transferBundle(bundle.getAccountId(), newAccountId, bundle.getKey(), transferRequestedDate, false, context);
+ transferApi.transferBundle(bundle.getAccountId(), newAccountId, bundle.getKey(), transferRequestedDate, false, false, context);
assertTrue(testListener.isCompleted(3000));
final DateTime afterTransferDate = clock.getUTCNow();
@@ -227,7 +227,7 @@ public class TestTransfer extends TestApiBase {
final DateTime transferRequestedDate = clock.getUTCNow();
testListener.pushExpectedEvent(NextEvent.TRANSFER);
- transferApi.transferBundle(bundle.getAccountId(), newAccountId, bundle.getKey(), transferRequestedDate, false, context);
+ transferApi.transferBundle(bundle.getAccountId(), newAccountId, bundle.getKey(), transferRequestedDate, false, false, context);
assertTrue(testListener.isCompleted(3000));
// CHECK OLD BASE IS CANCEL AT THE TRANSFER DATE
@@ -322,7 +322,7 @@ public class TestTransfer extends TestApiBase {
final DateTime transferRequestedDate = clock.getUTCNow();
testListener.pushExpectedEvent(NextEvent.TRANSFER);
- transferApi.transferBundle(bundle.getAccountId(), newAccountId, bundle.getKey(), transferRequestedDate, true, context);
+ transferApi.transferBundle(bundle.getAccountId(), newAccountId, bundle.getKey(), transferRequestedDate, true, false, context);
assertTrue(testListener.isCompleted(3000));
// RETRIEVE NEW BUNDLE AND CHECK SUBSCRIPTIONS
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
index ea8e17a..2fc3623 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -22,6 +22,8 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
+import javax.annotation.Nullable;
+
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
@@ -156,6 +158,16 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
}
@Override
+ public InvoiceItem insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId,
+ final LocalDate effectiveDate, @Nullable final BigDecimal amount,
+ @Nullable final Currency currency, final CallContext context) throws InvoiceApiException {
+ if (amount != null && amount.compareTo(BigDecimal.ZERO) <= 0) {
+ throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID, amount);
+ }
+ return dao.insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItemId, effectiveDate, amount, currency, context);
+ }
+
+ @Override
public String getInvoiceAsHTML(final UUID invoiceId) throws AccountApiException, IOException, InvoiceApiException {
final Invoice invoice = getInvoice(invoiceId);
if (invoice == null) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
index 68cf45c..0de61e7 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
@@ -43,6 +43,7 @@ import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
import com.ning.billing.invoice.model.DefaultInvoice;
import com.ning.billing.invoice.model.DefaultInvoicePayment;
+import com.ning.billing.invoice.model.ItemAdjInvoiceItem;
import com.ning.billing.invoice.model.RecurringInvoiceItem;
import com.ning.billing.invoice.model.RefundAdjInvoiceItem;
import com.ning.billing.invoice.notification.NextBillingDatePoster;
@@ -56,6 +57,7 @@ import com.ning.billing.util.dao.ObjectType;
import com.ning.billing.util.dao.TableName;
import com.ning.billing.util.tag.ControlTagType;
+import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.inject.Inject;
@@ -441,31 +443,52 @@ public class DefaultInvoiceDao implements InvoiceDao {
return invoiceSqlDao.inTransaction(new Transaction<InvoiceItem, InvoiceSqlDao>() {
@Override
public InvoiceItem inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
- UUID invoiceIdForRefund = invoiceId;
- if (invoiceIdForRefund == null) {
- final Invoice invoiceForRefund = new DefaultInvoice(accountId, effectiveDate, effectiveDate, currency);
- transactional.create(invoiceForRefund, context);
- invoiceIdForRefund = invoiceForRefund.getId();
+ UUID invoiceIdForCredit = invoiceId;
+ // Create an invoice for that credit if it doesn't exist
+ if (invoiceIdForCredit == null) {
+ final Invoice invoiceForCredit = new DefaultInvoice(accountId, effectiveDate, effectiveDate, currency);
+ transactional.create(invoiceForCredit, context);
+ invoiceIdForCredit = invoiceForCredit.getId();
}
- final InvoiceItem credit = new CreditAdjInvoiceItem(invoiceIdForRefund, accountId, effectiveDate, positiveCreditAmount.negate(), currency);
- final InvoiceItemSqlDao transInvoiceItemDao = transactional.become(InvoiceItemSqlDao.class);
- transInvoiceItemDao.create(credit, context);
+ // Note! The amount is negated here!
+ final InvoiceItem credit = new CreditAdjInvoiceItem(invoiceIdForCredit, accountId, effectiveDate, positiveCreditAmount.negate(), currency);
+ createItemAndAddCBAIfNeeded(transactional, credit, context);
+ return credit;
+ }
+ });
+ }
- final Invoice invoice = transactional.getById(invoiceIdForRefund.toString());
- if (invoice != null) {
- populateChildren(invoice, transactional);
- } else {
- throw new IllegalStateException("Invoice shouldn't be null for credit at this stage " + invoiceIdForRefund);
+ @Override
+ public InvoiceItem insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId,
+ final LocalDate effectiveDate, @Nullable final BigDecimal positiveAdjAmount,
+ @Nullable final Currency currency, final CallContext context) {
+ return invoiceSqlDao.inTransaction(new Transaction<InvoiceItem, InvoiceSqlDao>() {
+ @Override
+ public InvoiceItem inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
+ // First, retrieve the invoice item in question
+ final InvoiceItemSqlDao invoiceItemSqlDao = transactional.become(InvoiceItemSqlDao.class);
+ final InvoiceItem invoiceItemToBeAdjusted = invoiceItemSqlDao.getById(invoiceItemId.toString());
+ if (invoiceItemToBeAdjusted == null) {
+ throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_NOT_FOUND, invoiceItemId);
}
- // If invoice balance becomes negative we add some CBA item
- if (invoice.getBalance().compareTo(BigDecimal.ZERO) < 0) {
- final InvoiceItem cbaAdjItem = new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(),
- invoice.getBalance().negate(), invoice.getCurrency());
- transInvoiceItemDao.create(cbaAdjItem, context);
+ // Validate the invoice it belongs to
+ if (!invoiceItemToBeAdjusted.getInvoiceId().equals(invoiceId)) {
+ throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_FOR_INVOICE_ITEM_ADJUSTMENT, invoiceItemId, invoiceId);
}
- return credit;
+
+ // Retrieve the amount and currency if needed
+ final BigDecimal amountToRefund = Objects.firstNonNull(positiveAdjAmount, invoiceItemToBeAdjusted.getAmount());
+ // TODO - should we enforce the currency (and respect the original one) here if the amount passed was null?
+ final Currency currencyForAdjustment = Objects.firstNonNull(currency, invoiceItemToBeAdjusted.getCurrency());
+
+ // Finally, create the adjustment
+ // Note! The amount is negated here!
+ final InvoiceItem invoiceItemAdjustment = new ItemAdjInvoiceItem(invoiceItemToBeAdjusted, effectiveDate,
+ amountToRefund.negate(), currencyForAdjustment);
+ createItemAndAddCBAIfNeeded(transactional, invoiceItemAdjustment, context);
+ return invoiceItemAdjustment;
}
});
}
@@ -475,6 +498,33 @@ public class DefaultInvoiceDao implements InvoiceDao {
invoiceSqlDao.test();
}
+ /**
+ * Create an invoice item and adjust the invoice with a CBA item if the new invoice balance is negative.
+ *
+ * @param transactional the InvoiceSqlDao
+ * @param item the invoice item to create
+ * @param context the call context
+ */
+ private void createItemAndAddCBAIfNeeded(final InvoiceSqlDao transactional, final InvoiceItem item, final CallContext context) {
+ final InvoiceItemSqlDao transInvoiceItemDao = transactional.become(InvoiceItemSqlDao.class);
+ transInvoiceItemDao.create(item, context);
+
+ final Invoice invoice = transactional.getById(item.getInvoiceId().toString());
+ if (invoice != null) {
+ populateChildren(invoice, transactional);
+ } else {
+ throw new IllegalStateException("Invoice shouldn't be null for this item at this stage " + item.getInvoiceId());
+ }
+
+ // If invoice balance becomes negative we add some CBA item
+ if (invoice.getBalance().compareTo(BigDecimal.ZERO) < 0) {
+ final InvoiceItem cbaAdjItem = new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(),
+ invoice.getBalance().negate(), invoice.getCurrency());
+ transInvoiceItemDao.create(cbaAdjItem, context);
+
+ }
+ }
+
private BigDecimal getAccountCBAFromTransaction(final UUID accountId, final InvoiceSqlDao transactional) {
BigDecimal cba = BigDecimal.ZERO;
final List<Invoice> invoices = getAllInvoicesByAccountFromTransaction(accountId, transactional);
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
index 9b8c893..8a352a3 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
@@ -24,7 +24,6 @@ import javax.annotation.Nullable;
import org.joda.time.LocalDate;
-import com.ning.billing.account.api.BillCycleDay;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceApiException;
@@ -88,4 +87,18 @@ public interface InvoiceDao {
InvoiceItem insertCredit(final UUID accountId, final UUID invoiceId, final BigDecimal amount,
final LocalDate effectiveDate, final Currency currency, final CallContext context);
+ /**
+ * Adjust an invoice item.
+ *
+ * @param accountId the account id
+ * @param invoiceId the invoice id
+ * @param invoiceItemId the invoice item id to adjust
+ * @param effectiveDate adjustment effective date, in the account timezone
+ * @param amount the amount to adjust. Pass null to adjust the full amount of the original item
+ * @param currency the currency of the amount. Pass null to default to the original currency used
+ * @param context the call context
+ * @return the newly created adjustment item
+ */
+ InvoiceItem insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final LocalDate effectiveDate,
+ @Nullable final BigDecimal amount, @Nullable final Currency currency, final CallContext context);
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
index e7ddfb8..8156f30 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
@@ -47,6 +47,7 @@ import com.ning.billing.invoice.api.InvoiceItemType;
import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.invoice.model.ItemAdjInvoiceItem;
import com.ning.billing.invoice.model.RecurringInvoiceItem;
import com.ning.billing.invoice.model.RefundAdjInvoiceItem;
import com.ning.billing.invoice.model.RepairAdjInvoiceItem;
@@ -149,8 +150,11 @@ public interface InvoiceItemSqlDao extends EntitySqlDao<InvoiceItem> {
case REPAIR_ADJ:
item = new RepairAdjInvoiceItem(id, invoiceId, accountId, startDate, endDate, amount, currency, linkedItemId);
break;
+ case ITEM_ADJ:
+ item = new ItemAdjInvoiceItem(id, invoiceId, accountId, startDate, amount, currency, linkedItemId);
+ break;
default:
- throw new RuntimeException("Unexpected type of event item " + item);
+ throw new RuntimeException("Unexpected type of event item " + type);
}
return item;
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
index 3b495a4..616e935 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
@@ -41,7 +41,7 @@ public class InvoiceItemList extends ArrayList<InvoiceItem> {
}
public BigDecimal getTotalAdjAmount() {
- return getAmoutForItems(InvoiceItemType.CREDIT_ADJ, InvoiceItemType.REFUND_ADJ);
+ return getAmoutForItems(InvoiceItemType.CREDIT_ADJ, InvoiceItemType.REFUND_ADJ, InvoiceItemType.ITEM_ADJ);
}
public BigDecimal getCreditAdjAmount() {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/ItemAdjInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/ItemAdjInvoiceItem.java
new file mode 100644
index 0000000..cbfe229
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/ItemAdjInvoiceItem.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.invoice.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+
+public class ItemAdjInvoiceItem extends AdjInvoiceItem {
+
+ public ItemAdjInvoiceItem(final InvoiceItem invoiceItem, final LocalDate effectiveDate,
+ final BigDecimal amount, final Currency currency) {
+ super(invoiceItem.getInvoiceId(), invoiceItem.getAccountId(), effectiveDate, effectiveDate,
+ amount, currency, invoiceItem.getId());
+ }
+
+ public ItemAdjInvoiceItem(final UUID id, final UUID invoiceId, final UUID accountId, final LocalDate startDate,
+ final BigDecimal amount, final Currency currency, final UUID linkedItemId) {
+ super(id, invoiceId, accountId, startDate, startDate, amount, currency, linkedItemId);
+ }
+
+ @Override
+ public InvoiceItemType getInvoiceItemType() {
+ return InvoiceItemType.ITEM_ADJ;
+ }
+
+ @Override
+ public String getDescription() {
+ return "item-adj";
+ }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/migration/InvoiceApiTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/api/migration/InvoiceApiTestBase.java
new file mode 100644
index 0000000..4d243c1
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/migration/InvoiceApiTestBase.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.invoice.api.migration;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Guice;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.catalog.MockPlan;
+import com.ning.billing.catalog.MockPlanPhase;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.invoice.InvoiceDispatcher;
+import com.ning.billing.invoice.MockBillingEventSet;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceMigrationApi;
+import com.ning.billing.invoice.api.InvoiceNotifier;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.generator.InvoiceGenerator;
+import com.ning.billing.invoice.notification.NullInvoiceNotifier;
+import com.ning.billing.invoice.tests.InvoicingTestBase;
+import com.ning.billing.junction.api.BillingApi;
+import com.ning.billing.junction.api.BillingEventSet;
+import com.ning.billing.mock.api.MockBillCycleDay;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.bus.DefaultBusService;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.globallocker.GlobalLocker;
+
+import com.google.inject.Inject;
+
+@Guice(modules = {MockModuleNoEntitlement.class})
+public abstract class InvoiceApiTestBase extends InvoicingTestBase {
+
+ protected static final Currency accountCurrency = Currency.USD;
+
+ @Inject
+ protected InvoiceUserApi invoiceUserApi;
+
+ @Inject
+ protected InvoicePaymentApi invoicePaymentApi;
+
+ @Inject
+ protected InvoiceMigrationApi migrationApi;
+
+ @Inject
+ protected InvoiceGenerator generator;
+
+ @Inject
+ protected BillingApi billingApi;
+
+ @Inject
+ protected AccountUserApi accountUserApi;
+
+ @Inject
+ protected BusService busService;
+
+ @Inject
+ protected InvoiceDao invoiceDao;
+
+ @Inject
+ protected GlobalLocker locker;
+
+ @Inject
+ protected Clock clock;
+
+ @BeforeSuite(groups = "slow")
+ public void setup() throws Exception {
+ busService.getBus().start();
+ }
+
+ @AfterSuite(groups = "slow")
+ public void tearDown() {
+ try {
+ ((DefaultBusService) busService).stopBus();
+ } catch (Exception e) {
+ log.warn("Failed to tearDown test properly ", e);
+ }
+ }
+
+ protected UUID generateRegularInvoice(final Account account, final DateTime targetDate) throws Exception {
+ final Subscription subscription = Mockito.mock(Subscription.class);
+ Mockito.when(subscription.getId()).thenReturn(UUID.randomUUID());
+ Mockito.when(subscription.getBundleId()).thenReturn(new UUID(0L, 0L));
+ final BillingEventSet events = new MockBillingEventSet();
+ final Plan plan = MockPlan.createBicycleNoTrialEvergreen1USD();
+ final PlanPhase planPhase = MockPlanPhase.create1USDMonthlyEvergreen();
+ final DateTime effectiveDate = new DateTime().minusDays(1);
+ final Currency currency = Currency.USD;
+ final BigDecimal fixedPrice = null;
+ events.add(createMockBillingEvent(account, subscription, effectiveDate, plan, planPhase,
+ fixedPrice, BigDecimal.ONE, currency, BillingPeriod.MONTHLY, 1,
+ BillingModeType.IN_ADVANCE, "", 1L, SubscriptionTransitionType.CREATE));
+
+ Mockito.when(billingApi.getBillingEventsForAccountAndUpdateAccountBCD(account.getId())).thenReturn(events);
+
+ final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
+ final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountUserApi, billingApi,
+ invoiceDao, invoiceNotifier, locker, busService.getBus(), clock);
+
+ final CallContext context = new DefaultCallContextFactory(clock).createCallContext("Unit test", CallOrigin.TEST, UserType.TEST);
+ Invoice invoice = dispatcher.processAccount(account.getId(), targetDate, true, context);
+ Assert.assertNotNull(invoice);
+
+ List<Invoice> invoices = invoiceDao.getInvoicesByAccount(account.getId());
+ Assert.assertEquals(invoices.size(), 0);
+
+ invoice = dispatcher.processAccount(account.getId(), targetDate, false, context);
+ Assert.assertNotNull(invoice);
+
+ invoices = invoiceDao.getInvoicesByAccount(account.getId());
+ Assert.assertEquals(invoices.size(), 1);
+
+ return invoice.getId();
+ }
+
+ protected Account createAccount() throws AccountApiException {
+ final UUID accountId = UUID.randomUUID();
+ final Account account = Mockito.mock(Account.class);
+ Mockito.when(accountUserApi.getAccountById(accountId)).thenReturn(account);
+ Mockito.when(account.getCurrency()).thenReturn(accountCurrency);
+ Mockito.when(account.getId()).thenReturn(accountId);
+ Mockito.when(account.isNotifiedForInvoices()).thenReturn(true);
+ Mockito.when(account.getBillCycleDay()).thenReturn(new MockBillCycleDay(31));
+
+ return account;
+ }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
index c8c3e71..007f2f6 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
@@ -23,130 +23,47 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
-import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
-import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.BeforeSuite;
-import org.testng.annotations.Guice;
import org.testng.annotations.Test;
-import com.google.inject.Inject;
import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.AccountUserApi;
-import com.ning.billing.catalog.MockPlan;
-import com.ning.billing.catalog.MockPlanPhase;
-import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.catalog.api.Plan;
-import com.ning.billing.catalog.api.PlanPhase;
-import com.ning.billing.entitlement.api.SubscriptionTransitionType;
-import com.ning.billing.entitlement.api.billing.BillingModeType;
-import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.invoice.InvoiceDispatcher;
-import com.ning.billing.invoice.MockBillingEventSet;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceMigrationApi;
-import com.ning.billing.invoice.api.InvoiceNotifier;
import com.ning.billing.invoice.api.InvoicePaymentApi;
import com.ning.billing.invoice.api.InvoiceUserApi;
-import com.ning.billing.invoice.dao.InvoiceDao;
-import com.ning.billing.invoice.generator.InvoiceGenerator;
-import com.ning.billing.invoice.notification.NullInvoiceNotifier;
-import com.ning.billing.invoice.tests.InvoicingTestBase;
-import com.ning.billing.junction.api.BillingApi;
-import com.ning.billing.junction.api.BillingEventSet;
-import com.ning.billing.mock.api.MockBillCycleDay;
-import com.ning.billing.util.bus.BusService;
-import com.ning.billing.util.bus.DefaultBusService;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.clock.ClockMock;
-import com.ning.billing.util.globallocker.GlobalLocker;
-
-@Guice(modules = {MockModuleNoEntitlement.class})
-public class TestDefaultInvoiceMigrationApi extends InvoicingTestBase {
- private final Logger log = LoggerFactory.getLogger(TestDefaultInvoiceMigrationApi.class);
-
- @Inject
- InvoiceUserApi invoiceUserApi;
-
- @Inject
- InvoicePaymentApi invoicePaymentApi;
-
- @Inject
- private InvoiceGenerator generator;
-
- @Inject
- private InvoiceDao invoiceDao;
-
- @Inject
- private GlobalLocker locker;
- @Inject
- private BusService busService;
-
- @Inject
- private InvoiceMigrationApi migrationApi;
+import com.google.inject.Inject;
- @Inject
- private BillingApi billingApi;
+public class TestDefaultInvoiceMigrationApi extends InvoiceApiTestBase {
- @Inject
- private AccountUserApi accountUserApi;
+ private final Logger log = LoggerFactory.getLogger(TestDefaultInvoiceMigrationApi.class);
- private Account account;
- private UUID accountId;
- private UUID subscriptionId;
private LocalDate date_migrated;
private DateTime date_regular;
+ private UUID accountId;
private UUID migrationInvoiceId;
private UUID regularInvoiceId;
private static final BigDecimal MIGRATION_INVOICE_AMOUNT = new BigDecimal("100.00");
private static final Currency MIGRATION_INVOICE_CURRENCY = Currency.USD;
- private final Clock clock = new ClockMock();
-
- @BeforeSuite(groups = "slow")
- public void setup() throws Exception {
- busService.getBus().start();
- }
-
@BeforeMethod(groups = "slow")
public void setupMethod() throws Exception {
- accountId = UUID.randomUUID();
- subscriptionId = UUID.randomUUID();
date_migrated = clock.getUTCToday().minusYears(1);
date_regular = clock.getUTCNow();
- account = Mockito.mock(Account.class);
- Mockito.when(accountUserApi.getAccountById(accountId)).thenReturn(account);
- Mockito.when(account.getCurrency()).thenReturn(Currency.USD);
- Mockito.when(account.getId()).thenReturn(accountId);
- Mockito.when(account.isNotifiedForInvoices()).thenReturn(true);
- Mockito.when(account.getBillCycleDay()).thenReturn(new MockBillCycleDay(31));
-
- migrationInvoiceId = createAndCheckMigrationInvoice();
- regularInvoiceId = generateRegularInvoice();
+ final Account account = createAccount();
+ accountId = account.getId();
+ migrationInvoiceId = createAndCheckMigrationInvoice(accountId);
+ regularInvoiceId = generateRegularInvoice(account, date_regular);
}
- @AfterSuite(groups = "slow")
- public void tearDown() {
- try {
- ((DefaultBusService) busService).stopBus();
- } catch (Exception e) {
- log.warn("Failed to tearDown test properly ", e);
- }
- }
-
- private UUID createAndCheckMigrationInvoice() {
+ private UUID createAndCheckMigrationInvoice(final UUID accountId) {
final UUID migrationInvoiceId = migrationApi.createMigrationInvoice(accountId, date_migrated, MIGRATION_INVOICE_AMOUNT, MIGRATION_INVOICE_CURRENCY);
Assert.assertNotNull(migrationInvoiceId);
//Double check it was created and values are correct
@@ -166,42 +83,6 @@ public class TestDefaultInvoiceMigrationApi extends InvoicingTestBase {
return migrationInvoiceId;
}
- private UUID generateRegularInvoice() throws Exception {
- final Subscription subscription = Mockito.mock(Subscription.class);
- Mockito.when(subscription.getId()).thenReturn(subscriptionId);
- Mockito.when(subscription.getBundleId()).thenReturn(new UUID(0L, 0L));
- final BillingEventSet events = new MockBillingEventSet();
- final Plan plan = MockPlan.createBicycleNoTrialEvergreen1USD();
- final PlanPhase planPhase = MockPlanPhase.create1USDMonthlyEvergreen();
- final DateTime effectiveDate = new DateTime().minusDays(1);
- final Currency currency = Currency.USD;
- final BigDecimal fixedPrice = null;
- events.add(createMockBillingEvent(account, subscription, effectiveDate, plan, planPhase,
- fixedPrice, BigDecimal.ONE, currency, BillingPeriod.MONTHLY, 1,
- BillingModeType.IN_ADVANCE, "", 1L, SubscriptionTransitionType.CREATE));
-
- Mockito.when(billingApi.getBillingEventsForAccountAndUpdateAccountBCD(accountId)).thenReturn(events);
-
- final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
- final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountUserApi, billingApi,
- invoiceDao, invoiceNotifier, locker, busService.getBus(), clock);
-
- final CallContext context = new DefaultCallContextFactory(clock).createCallContext("Migration test", CallOrigin.TEST, UserType.TEST);
- Invoice invoice = dispatcher.processAccount(accountId, date_regular, true, context);
- Assert.assertNotNull(invoice);
-
- List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId);
- Assert.assertEquals(invoices.size(), 0);
-
- invoice = dispatcher.processAccount(accountId, date_regular, false, context);
- Assert.assertNotNull(invoice);
-
- invoices = invoiceDao.getInvoicesByAccount(accountId);
- Assert.assertEquals(invoices.size(), 1);
-
- return invoice.getId();
- }
-
@Test(groups = "slow")
public void testUserApiAccess() {
final List<Invoice> byAccount = invoiceUserApi.getInvoicesByAccount(accountId);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
new file mode 100644
index 0000000..88ce414
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.invoice.api.user;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.invoice.api.migration.InvoiceApiTestBase;
+import com.ning.billing.invoice.model.InvoicingConfiguration;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
+
+public class TestDefaultInvoiceUserApi extends InvoiceApiTestBase {
+
+ private UUID accountId;
+ private UUID invoiceId;
+ private CallContext context;
+
+ @BeforeMethod(groups = "slow")
+ public void setupMethod() throws Exception {
+ final Account account = createAccount();
+ accountId = account.getId();
+ invoiceId = generateRegularInvoice(account, clock.getUTCNow());
+ context = new DefaultCallContextFactory(clock).createCallContext("Unit test", CallOrigin.TEST, UserType.TEST);
+ }
+
+ @Test(groups = "slow")
+ public void testAdjustFullInvoice() throws Exception {
+ // Verify the initial invoice balance
+ final BigDecimal invoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+ Assert.assertEquals(invoiceBalance.compareTo(BigDecimal.ZERO), 1);
+
+ // Verify the initial account balance
+ final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId);
+ Assert.assertEquals(accountBalance, invoiceBalance);
+
+ // Adjust the invoice for the full amount
+ final InvoiceItem creditInvoiceItem = invoiceUserApi.insertCreditForInvoice(accountId, invoiceId, invoiceBalance,
+ clock.getUTCToday(), accountCurrency, context);
+ Assert.assertEquals(creditInvoiceItem.getInvoiceId(), invoiceId);
+ Assert.assertEquals(creditInvoiceItem.getInvoiceItemType(), InvoiceItemType.CREDIT_ADJ);
+ Assert.assertEquals(creditInvoiceItem.getAccountId(), accountId);
+ Assert.assertEquals(creditInvoiceItem.getAmount(), invoiceBalance.negate());
+ Assert.assertEquals(creditInvoiceItem.getCurrency(), accountCurrency);
+ Assert.assertNull(creditInvoiceItem.getLinkedItemId());
+
+ // Verify the adjusted invoice balance
+ final BigDecimal adjustedInvoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+ Assert.assertEquals(adjustedInvoiceBalance.compareTo(BigDecimal.ZERO), 0);
+
+ // Verify the adjusted account balance
+ final BigDecimal adjustedAccountBalance = invoiceUserApi.getAccountBalance(accountId);
+ Assert.assertEquals(adjustedAccountBalance, adjustedInvoiceBalance);
+ }
+
+ @Test(groups = "slow")
+ public void testAdjustPartialInvoice() throws Exception {
+ // Verify the initial invoice balance
+ final BigDecimal invoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+ Assert.assertEquals(invoiceBalance.compareTo(BigDecimal.ZERO), 1);
+
+ // Verify the initial account balance
+ final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId);
+ Assert.assertEquals(accountBalance, invoiceBalance);
+
+ // Adjust the invoice for a fraction of the balance
+ final BigDecimal creditAmount = invoiceBalance.divide(BigDecimal.TEN);
+ final InvoiceItem creditInvoiceItem = invoiceUserApi.insertCreditForInvoice(accountId, invoiceId, creditAmount,
+ clock.getUTCToday(), accountCurrency, context);
+ Assert.assertEquals(creditInvoiceItem.getInvoiceId(), invoiceId);
+ Assert.assertEquals(creditInvoiceItem.getInvoiceItemType(), InvoiceItemType.CREDIT_ADJ);
+ Assert.assertEquals(creditInvoiceItem.getAccountId(), accountId);
+ Assert.assertEquals(creditInvoiceItem.getAmount(), creditAmount.negate());
+ Assert.assertEquals(creditInvoiceItem.getCurrency(), accountCurrency);
+ Assert.assertNull(creditInvoiceItem.getLinkedItemId());
+
+ // Verify the adjusted invoice balance
+ final BigDecimal adjustedInvoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+ // Note! The invoice code will round (see InvoiceItemList)
+ Assert.assertEquals(adjustedInvoiceBalance, invoiceBalance.add(creditAmount.negate()).setScale(InvoicingConfiguration.getNumberOfDecimals(),
+ InvoicingConfiguration.getRoundingMode()));
+
+ // Verify the adjusted account balance
+ final BigDecimal adjustedAccountBalance = invoiceUserApi.getAccountBalance(accountId);
+ Assert.assertEquals(adjustedAccountBalance, adjustedInvoiceBalance);
+ }
+
+ @Test(groups = "slow")
+ public void testCantAdjustInvoiceWithNegativeAmount() throws Exception {
+ try {
+ invoiceUserApi.insertCreditForInvoice(accountId, invoiceId, BigDecimal.TEN.negate(), clock.getUTCToday(), accountCurrency, context);
+ Assert.fail("Should not have been able to adjust an invoice with a negative amount");
+ } catch (InvoiceApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.CREDIT_AMOUNT_INVALID.getCode());
+ }
+ }
+
+ @Test(groups = "slow")
+ public void testAdjustFullInvoiceItem() throws Exception {
+ final InvoiceItem invoiceItem = invoiceUserApi.getInvoice(invoiceId).getInvoiceItems().get(0);
+ // Verify we picked a non zero item
+ Assert.assertEquals(invoiceItem.getAmount().compareTo(BigDecimal.ZERO), 1);
+
+ // Verify the initial invoice balance
+ final BigDecimal invoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+ Assert.assertEquals(invoiceBalance.compareTo(BigDecimal.ZERO), 1);
+
+ // Verify the initial account balance
+ final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId);
+ Assert.assertEquals(accountBalance, invoiceBalance);
+
+ // Adjust the invoice for the full amount
+ final InvoiceItem adjInvoiceItem = invoiceUserApi.insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItem.getId(),
+ clock.getUTCToday(), null, null, context);
+ Assert.assertEquals(adjInvoiceItem.getInvoiceId(), invoiceId);
+ Assert.assertEquals(adjInvoiceItem.getInvoiceItemType(), InvoiceItemType.ITEM_ADJ);
+ Assert.assertEquals(adjInvoiceItem.getAccountId(), accountId);
+ Assert.assertEquals(adjInvoiceItem.getAmount(), invoiceItem.getAmount().negate());
+ Assert.assertEquals(adjInvoiceItem.getCurrency(), accountCurrency);
+ Assert.assertEquals(adjInvoiceItem.getLinkedItemId(), invoiceItem.getId());
+
+ // Verify the adjusted invoice balance
+ final BigDecimal adjustedInvoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+ // Note! The invoice code will round (see InvoiceItemList)
+ Assert.assertEquals(adjustedInvoiceBalance, invoiceBalance.add(invoiceItem.getAmount().negate()).setScale(InvoicingConfiguration.getNumberOfDecimals(),
+ InvoicingConfiguration.getRoundingMode()));
+
+ // Verify the adjusted account balance
+ final BigDecimal adjustedAccountBalance = invoiceUserApi.getAccountBalance(accountId);
+ Assert.assertEquals(adjustedAccountBalance, adjustedInvoiceBalance);
+ }
+
+ @Test(groups = "slow")
+ public void testAdjustPartialInvoiceItem() throws Exception {
+ final InvoiceItem invoiceItem = invoiceUserApi.getInvoice(invoiceId).getInvoiceItems().get(0);
+ // Verify we picked a non zero item
+ Assert.assertEquals(invoiceItem.getAmount().compareTo(BigDecimal.ZERO), 1);
+
+ // Verify the initial invoice balance
+ final BigDecimal invoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+ Assert.assertEquals(invoiceBalance.compareTo(BigDecimal.ZERO), 1);
+
+ // Verify the initial account balance
+ final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId);
+ Assert.assertEquals(accountBalance, invoiceBalance);
+
+ // Adjust the invoice for a fraction of the balance
+ final BigDecimal adjAmount = invoiceItem.getAmount().divide(BigDecimal.TEN);
+ final InvoiceItem adjInvoiceItem = invoiceUserApi.insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItem.getId(),
+ clock.getUTCToday(), adjAmount, accountCurrency,
+ context);
+ Assert.assertEquals(adjInvoiceItem.getInvoiceId(), invoiceId);
+ Assert.assertEquals(adjInvoiceItem.getInvoiceItemType(), InvoiceItemType.ITEM_ADJ);
+ Assert.assertEquals(adjInvoiceItem.getAccountId(), accountId);
+ Assert.assertEquals(adjInvoiceItem.getAmount(), adjAmount.negate());
+ Assert.assertEquals(adjInvoiceItem.getCurrency(), accountCurrency);
+ Assert.assertEquals(adjInvoiceItem.getLinkedItemId(), invoiceItem.getId());
+
+ // Verify the adjusted invoice balance
+ final BigDecimal adjustedInvoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+ // Note! The invoice code will round (see InvoiceItemList)
+ Assert.assertEquals(adjustedInvoiceBalance, invoiceBalance.add(adjAmount.negate()).setScale(InvoicingConfiguration.getNumberOfDecimals(),
+ InvoicingConfiguration.getRoundingMode()));
+
+ // Verify the adjusted account balance
+ final BigDecimal adjustedAccountBalance = invoiceUserApi.getAccountBalance(accountId);
+ Assert.assertEquals(adjustedAccountBalance, adjustedInvoiceBalance);
+ }
+
+ @Test(groups = "slow")
+ public void testCantAdjustInvoiceItemWithNegativeAmount() throws Exception {
+ final InvoiceItem invoiceItem = invoiceUserApi.getInvoice(invoiceId).getInvoiceItems().get(0);
+
+ try {
+ invoiceUserApi.insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItem.getId(), clock.getUTCToday(),
+ BigDecimal.TEN.negate(), accountCurrency, context);
+ Assert.fail("Should not have been able to adjust an item with a negative amount");
+ } catch (InvoiceApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID.getCode());
+ }
+ }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
index 09c196b..a483c06 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
@@ -23,6 +23,8 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
+import javax.annotation.Nullable;
+
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
@@ -269,6 +271,12 @@ public class MockInvoiceDao implements InvoiceDao {
}
@Override
+ public InvoiceItem insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId,
+ final LocalDate effectiveDate, @Nullable final BigDecimal amount, @Nullable final Currency currency, final CallContext context) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public BigDecimal getAccountCBA(UUID accountId) {
return null;
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java
new file mode 100644
index 0000000..5e82f30
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.invoice.dao;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.util.callcontext.CallContext;
+
+public class TestInvoiceDaoForItemAdjustment extends InvoiceDaoTestBase {
+
+ private static final BigDecimal INVOICE_ITEM_AMOUNT = new BigDecimal("21.00");
+
+ @Test(groups = "slow")
+ public void testAddInvoiceItemAdjustmentForNonExistingInvoiceItemId() throws Exception {
+ final UUID accountId = UUID.randomUUID();
+ final UUID invoiceId = UUID.randomUUID();
+ final UUID invoiceItemId = UUID.randomUUID();
+ final LocalDate effectiveDate = new LocalDate();
+ final CallContext context = Mockito.mock(CallContext.class);
+
+ try {
+ invoiceDao.insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItemId, effectiveDate, null, null, context);
+ Assert.fail("Should not have been able to adjust a non existing invoice item");
+ } catch (Exception e) {
+ Assert.assertEquals(((InvoiceApiException) e.getCause()).getCode(), ErrorCode.INVOICE_ITEM_NOT_FOUND.getCode());
+ }
+ }
+
+ @Test(groups = "slow")
+ public void testAddInvoiceItemAdjustmentForWrongInvoice() throws Exception {
+ final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+ final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoice.getId(), invoice.getAccountId(), UUID.randomUUID(),
+ UUID.randomUUID(), "test plan", "test phase",
+ new LocalDate(2010, 1, 1), new LocalDate(2010, 4, 1),
+ INVOICE_ITEM_AMOUNT, new BigDecimal("7.00"), Currency.USD);
+ invoice.addInvoiceItem(invoiceItem);
+ invoiceDao.create(invoice, 1, context);
+
+ try {
+ invoiceDao.insertInvoiceItemAdjustment(invoice.getAccountId(), UUID.randomUUID(), invoiceItem.getId(), new LocalDate(2010, 1, 1), null, null, context);
+ Assert.fail("Should not have been able to adjust an item on a non existing invoice");
+ } catch (Exception e) {
+ Assert.assertEquals(((InvoiceApiException) e.getCause()).getCode(), ErrorCode.INVOICE_INVALID_FOR_INVOICE_ITEM_ADJUSTMENT.getCode());
+ }
+ }
+
+ @Test(groups = "slow")
+ public void testAddInvoiceItemAdjustmentForFullAmount() throws Exception {
+ final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+ final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoice.getId(), invoice.getAccountId(), UUID.randomUUID(),
+ UUID.randomUUID(), "test plan", "test phase",
+ new LocalDate(2010, 1, 1), new LocalDate(2010, 4, 1),
+ INVOICE_ITEM_AMOUNT, new BigDecimal("7.00"), Currency.USD);
+ invoice.addInvoiceItem(invoiceItem);
+ invoiceDao.create(invoice, 1, context);
+
+ final InvoiceItem adjustedInvoiceItem = createAndCheckAdjustment(invoice, invoiceItem, null);
+ Assert.assertEquals(adjustedInvoiceItem.getAmount().compareTo(invoiceItem.getAmount().negate()), 0);
+ }
+
+ @Test(groups = "slow")
+ public void testAddInvoiceItemAdjustmentForPartialAmount() throws Exception {
+ final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+ final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoice.getId(), invoice.getAccountId(), UUID.randomUUID(),
+ UUID.randomUUID(), "test plan", "test phase",
+ new LocalDate(2010, 1, 1), new LocalDate(2010, 4, 1),
+ INVOICE_ITEM_AMOUNT, new BigDecimal("7.00"), Currency.USD);
+ invoice.addInvoiceItem(invoiceItem);
+ invoiceDao.create(invoice, 1, context);
+
+ final InvoiceItem adjustedInvoiceItem = createAndCheckAdjustment(invoice, invoiceItem, BigDecimal.TEN);
+ Assert.assertEquals(adjustedInvoiceItem.getAmount().compareTo(BigDecimal.TEN.negate()), 0);
+ }
+
+ private InvoiceItem createAndCheckAdjustment(final Invoice invoice, final InvoiceItem invoiceItem, final BigDecimal amount) {
+ final LocalDate effectiveDate = new LocalDate(2010, 1, 1);
+ final InvoiceItem adjustedInvoiceItem = invoiceDao.insertInvoiceItemAdjustment(invoice.getAccountId(), invoice.getId(), invoiceItem.getId(),
+ effectiveDate, amount, null, context);
+ Assert.assertEquals(adjustedInvoiceItem.getAccountId(), invoiceItem.getAccountId());
+ Assert.assertNull(adjustedInvoiceItem.getBundleId());
+ Assert.assertEquals(adjustedInvoiceItem.getCurrency(), invoiceItem.getCurrency());
+ Assert.assertEquals(adjustedInvoiceItem.getDescription(), "item-adj");
+ Assert.assertEquals(adjustedInvoiceItem.getEndDate(), effectiveDate);
+ Assert.assertEquals(adjustedInvoiceItem.getInvoiceId(), invoiceItem.getInvoiceId());
+ Assert.assertEquals(adjustedInvoiceItem.getInvoiceItemType(), InvoiceItemType.ITEM_ADJ);
+ Assert.assertEquals(adjustedInvoiceItem.getLinkedItemId(), invoiceItem.getId());
+ Assert.assertNull(adjustedInvoiceItem.getPhaseName());
+ Assert.assertNull(adjustedInvoiceItem.getPlanName());
+ Assert.assertNull(adjustedInvoiceItem.getRate());
+ Assert.assertEquals(adjustedInvoiceItem.getStartDate(), effectiveDate);
+ Assert.assertNull(adjustedInvoiceItem.getSubscriptionId());
+
+ // Retrieve the item by id
+ final InvoiceItem retrievedInvoiceItem = invoiceItemSqlDao.getById(adjustedInvoiceItem.getId().toString());
+ Assert.assertEquals(retrievedInvoiceItem, adjustedInvoiceItem);
+
+ // Retrieve the item by invoice id
+ final Invoice retrievedInvoice = invoiceDao.getById(adjustedInvoiceItem.getInvoiceId());
+ final List<InvoiceItem> invoiceItems = retrievedInvoice.getInvoiceItems();
+ Assert.assertEquals(invoiceItems.size(), 2);
+ final InvoiceItem retrievedByInvoiceInvoiceItem;
+ if (invoiceItems.get(0).getId().equals(adjustedInvoiceItem.getId())) {
+ retrievedByInvoiceInvoiceItem = invoiceItems.get(0);
+ } else {
+ retrievedByInvoiceInvoiceItem = invoiceItems.get(1);
+ }
+ Assert.assertEquals(retrievedByInvoiceInvoiceItem, adjustedInvoiceItem);
+
+ // Verify the invoice balance
+ if (amount == null) {
+ Assert.assertEquals(retrievedInvoice.getBalance().compareTo(BigDecimal.ZERO), 0);
+ } else {
+ Assert.assertEquals(retrievedInvoice.getBalance().compareTo(INVOICE_ITEM_AMOUNT.add(amount.negate())), 0);
+ }
+
+ return adjustedInvoiceItem;
+ }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/model/TestItemAdjInvoiceItem.java b/invoice/src/test/java/com/ning/billing/invoice/model/TestItemAdjInvoiceItem.java
new file mode 100644
index 0000000..316b613
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/model/TestItemAdjInvoiceItem.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.invoice.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+
+public class TestItemAdjInvoiceItem {
+
+ @Test(groups = "fast")
+ public void testType() throws Exception {
+ final InvoiceItem invoiceItem = new ItemAdjInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(),
+ new LocalDate(2010, 1, 1), new BigDecimal("7.00"), Currency.USD,
+ UUID.randomUUID());
+ Assert.assertEquals(invoiceItem.getInvoiceItemType(), InvoiceItemType.ITEM_ADJ);
+ }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceItemJsonSimple.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceItemJsonSimple.java
index 5c2991c..bf09acf 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceItemJsonSimple.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceItemJsonSimple.java
@@ -17,20 +17,24 @@
package com.ning.billing.jaxrs.json;
import java.math.BigDecimal;
-import java.util.UUID;
+import java.util.List;
+
+import javax.annotation.Nullable;
-import org.joda.time.DateTime;
import org.joda.time.LocalDate;
-import com.fasterxml.jackson.annotation.JsonProperty;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.InvoiceItem;
-public class InvoiceItemJsonSimple {
- private final UUID invoiceId;
- private final UUID accountId;
- private final UUID bundleId;
- private final UUID subscriptionId;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class InvoiceItemJsonSimple extends JsonBase {
+
+ private final String invoiceItemId;
+ private final String invoiceId;
+ private final String accountId;
+ private final String bundleId;
+ private final String subscriptionId;
private final String planName;
private final String phaseName;
private final String description;
@@ -39,17 +43,21 @@ public class InvoiceItemJsonSimple {
private final BigDecimal amount;
private final Currency currency;
- public InvoiceItemJsonSimple(@JsonProperty("invoiceId") final UUID invoiceId,
- @JsonProperty("accountId") final UUID accountId,
- @JsonProperty("bundleId") final UUID bundleId,
- @JsonProperty("subscriptionId") final UUID subscriptionId,
+ public InvoiceItemJsonSimple(@JsonProperty("invoiceItemId") final String invoiceItemId,
+ @JsonProperty("invoiceId") final String invoiceId,
+ @JsonProperty("accountId") final String accountId,
+ @JsonProperty("bundleId") final String bundleId,
+ @JsonProperty("subscriptionId") final String subscriptionId,
@JsonProperty("planName") final String planName,
@JsonProperty("phaseName") final String phaseName,
@JsonProperty("description") final String description,
@JsonProperty("startDate") final LocalDate startDate,
@JsonProperty("endDate") final LocalDate endDate,
@JsonProperty("amount") final BigDecimal amount,
- @JsonProperty("currency") final Currency currency) {
+ @JsonProperty("currency") final Currency currency,
+ @JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
+ super(auditLogs);
+ this.invoiceItemId = invoiceItemId;
this.invoiceId = invoiceId;
this.accountId = accountId;
this.bundleId = bundleId;
@@ -64,24 +72,29 @@ public class InvoiceItemJsonSimple {
}
public InvoiceItemJsonSimple(final InvoiceItem item) {
- this(item.getInvoiceId(), item.getAccountId(), item.getBundleId(), item.getSubscriptionId(),
+ this(toString(item.getId()), toString(item.getInvoiceId()), toString(item.getAccountId()),
+ toString(item.getBundleId()), toString(item.getSubscriptionId()),
item.getPlanName(), item.getPhaseName(), item.getDescription(), item.getStartDate(), item.getEndDate(),
- item.getAmount(), item.getCurrency());
+ item.getAmount(), item.getCurrency(), null);
}
- public UUID getInvoiceId() {
+ public String getInvoiceItemId() {
+ return invoiceItemId;
+ }
+
+ public String getInvoiceId() {
return invoiceId;
}
- public UUID getAccountId() {
+ public String getAccountId() {
return accountId;
}
- public UUID getBundleId() {
+ public String getBundleId() {
return bundleId;
}
- public UUID getSubscriptionId() {
+ public String getSubscriptionId() {
return subscriptionId;
}
@@ -128,7 +141,7 @@ public class InvoiceItemJsonSimple {
return false;
}
if (!((amount == null && that.amount == null) ||
- (amount != null && that.amount != null && amount.compareTo(that.amount) == 0))) {
+ (amount != null && that.amount != null && amount.compareTo(that.amount) == 0))) {
return false;
}
if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null) {
@@ -141,7 +154,10 @@ public class InvoiceItemJsonSimple {
return false;
}
if (!((endDate == null && that.endDate == null) ||
- (endDate != null && that.endDate != null && endDate.compareTo(that.endDate) == 0))) {
+ (endDate != null && that.endDate != null && endDate.compareTo(that.endDate) == 0))) {
+ return false;
+ }
+ if (invoiceItemId != null ? !invoiceItemId.equals(that.invoiceItemId) : that.invoiceItemId != null) {
return false;
}
if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
@@ -154,7 +170,7 @@ public class InvoiceItemJsonSimple {
return false;
}
if (!((startDate == null && that.startDate == null) ||
- (startDate != null && that.startDate != null && startDate.compareTo(that.startDate) == 0))) {
+ (startDate != null && that.startDate != null && startDate.compareTo(that.startDate) == 0))) {
return false;
}
if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null) {
@@ -167,6 +183,7 @@ public class InvoiceItemJsonSimple {
@Override
public int hashCode() {
int result = invoiceId != null ? invoiceId.hashCode() : 0;
+ result = 31 * result + (invoiceItemId != null ? invoiceItemId.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);
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/JsonBase.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/JsonBase.java
index b3e078c..315e74d 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/JsonBase.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/JsonBase.java
@@ -17,6 +17,7 @@
package com.ning.billing.jaxrs.json;
import java.util.List;
+import java.util.UUID;
import javax.annotation.Nullable;
@@ -55,6 +56,10 @@ public abstract class JsonBase {
return auditLogs.get(0).getReasonCode();
}
+ protected static String toString(@Nullable final UUID id) {
+ return id == null ? null : id.toString();
+ }
+
public List<AuditLogJson> getAuditLogs() {
return auditLogs;
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
index 083a9a8..3863737 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
@@ -175,6 +175,7 @@ public class BundleResource extends JaxRsResourceBase {
public Response transferBundle(@PathParam(ID_PARAM_NAME) final String id,
@QueryParam(QUERY_REQUESTED_DT) final String requestedDate,
@QueryParam(QUERY_BUNDLE_TRANSFER_ADDON) @DefaultValue("true") final Boolean transferAddOn,
+ @QueryParam(QUERY_BUNDLE_TRANSFER_CANCEL_IMM) @DefaultValue("false") final Boolean cancelImmediatley,
final BundleJsonNoSubscriptions json,
@HeaderParam(HDR_CREATED_BY) final String createdBy,
@HeaderParam(HDR_REASON) final String reason,
@@ -184,7 +185,7 @@ public class BundleResource extends JaxRsResourceBase {
final SubscriptionBundle bundle = entitlementApi.getBundleFromId(UUID.fromString(id));
final DateTime inputDate = (requestedDate != null) ? DATE_TIME_FORMATTER.parseDateTime(requestedDate) : null;
final SubscriptionBundle newBundle = transferApi.transferBundle(bundle.getAccountId(), UUID.fromString(json.getAccountId()), bundle.getKey(), inputDate, transferAddOn,
- context.createContext(createdBy, reason, comment));
+ cancelImmediatley, context.createContext(createdBy, reason, comment));
return uriBuilder.buildResponse(BundleResource.class, "getBundle", newBundle.getId(), uriInfo.getBaseUri().toString());
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
index 4c9ceb0..037d036 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
@@ -62,6 +62,7 @@ public interface JaxrsResource {
public static final String QUERY_PAYMENT_METHOD_IS_DEFAULT = "isDefault";
public static final String QUERY_BUNDLE_TRANSFER_ADDON = "transferAddOn";
+ public static final String QUERY_BUNDLE_TRANSFER_CANCEL_IMM = "cancelImmediately";
public static final String ACCOUNTS = "accounts";
public static final String ACCOUNTS_PATH = PREFIX + "/" + ACCOUNTS;
diff --git a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceItemJsonSimple.java b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceItemJsonSimple.java
index 8168844..a182119 100644
--- a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceItemJsonSimple.java
+++ b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceItemJsonSimple.java
@@ -17,6 +17,7 @@
package com.ning.billing.jaxrs.json;
import java.math.BigDecimal;
+import java.util.List;
import java.util.UUID;
import org.joda.time.LocalDate;
@@ -30,27 +31,17 @@ import com.ning.billing.jaxrs.JaxrsTestSuite;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.DefaultClock;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializationFeature;
-import com.fasterxml.jackson.datatype.joda.JodaModule;
-
public class TestInvoiceItemJsonSimple extends JaxrsTestSuite {
- private static final ObjectMapper mapper = new ObjectMapper();
-
private final Clock clock = new DefaultClock();
- static {
- mapper.registerModule(new JodaModule());
- mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
- }
-
@Test(groups = "fast")
public void testJson() throws Exception {
- final UUID invoiceId = UUID.randomUUID();
- final UUID accountId = UUID.randomUUID();
- final UUID bundleId = UUID.randomUUID();
- final UUID subscriptionId = UUID.randomUUID();
+ final String invoiceItemId = UUID.randomUUID().toString();
+ final String invoiceId = UUID.randomUUID().toString();
+ final String accountId = UUID.randomUUID().toString();
+ final String bundleId = UUID.randomUUID().toString();
+ final String subscriptionId = UUID.randomUUID().toString();
final String planName = UUID.randomUUID().toString();
final String phaseName = UUID.randomUUID().toString();
final String description = UUID.randomUUID().toString();
@@ -58,9 +49,11 @@ public class TestInvoiceItemJsonSimple extends JaxrsTestSuite {
final LocalDate endDate = clock.getUTCToday();
final BigDecimal amount = BigDecimal.TEN;
final Currency currency = Currency.MXN;
- final InvoiceItemJsonSimple invoiceItemJsonSimple = new InvoiceItemJsonSimple(invoiceId, accountId, bundleId, subscriptionId,
+ final List<AuditLogJson> auditLogs = createAuditLogsJson();
+ final InvoiceItemJsonSimple invoiceItemJsonSimple = new InvoiceItemJsonSimple(invoiceItemId, invoiceId, accountId, bundleId, subscriptionId,
planName, phaseName, description, startDate, endDate,
- amount, currency);
+ amount, currency, auditLogs);
+ Assert.assertEquals(invoiceItemJsonSimple.getInvoiceItemId(), invoiceItemId);
Assert.assertEquals(invoiceItemJsonSimple.getInvoiceId(), invoiceId);
Assert.assertEquals(invoiceItemJsonSimple.getAccountId(), accountId);
Assert.assertEquals(invoiceItemJsonSimple.getBundleId(), bundleId);
@@ -72,20 +65,9 @@ public class TestInvoiceItemJsonSimple extends JaxrsTestSuite {
Assert.assertEquals(invoiceItemJsonSimple.getEndDate(), endDate);
Assert.assertEquals(invoiceItemJsonSimple.getAmount(), amount);
Assert.assertEquals(invoiceItemJsonSimple.getCurrency(), currency);
+ Assert.assertEquals(invoiceItemJsonSimple.getAuditLogs(), auditLogs);
final String asJson = mapper.writeValueAsString(invoiceItemJsonSimple);
- Assert.assertEquals(asJson, "{\"invoiceId\":\"" + invoiceItemJsonSimple.getInvoiceId().toString() + "\"," +
- "\"accountId\":\"" + invoiceItemJsonSimple.getAccountId().toString() + "\"," +
- "\"bundleId\":\"" + invoiceItemJsonSimple.getBundleId().toString() + "\"," +
- "\"subscriptionId\":\"" + invoiceItemJsonSimple.getSubscriptionId().toString() + "\"," +
- "\"planName\":\"" + invoiceItemJsonSimple.getPlanName() + "\"," +
- "\"phaseName\":\"" + invoiceItemJsonSimple.getPhaseName() + "\"," +
- "\"description\":\"" + invoiceItemJsonSimple.getDescription() + "\"," +
- "\"startDate\":\"" + invoiceItemJsonSimple.getStartDate().toString() + "\"," +
- "\"endDate\":\"" + invoiceItemJsonSimple.getEndDate().toString() + "\"," +
- "\"amount\":" + invoiceItemJsonSimple.getAmount().toString() + "," +
- "\"currency\":\"" + invoiceItemJsonSimple.getCurrency().toString() + "\"}");
-
final InvoiceItemJsonSimple fromJson = mapper.readValue(asJson, InvoiceItemJsonSimple.class);
Assert.assertEquals(fromJson, invoiceItemJsonSimple);
}
@@ -93,6 +75,7 @@ public class TestInvoiceItemJsonSimple extends JaxrsTestSuite {
@Test(groups = "fast")
public void testFromInvoiceItem() throws Exception {
final InvoiceItem invoiceItem = Mockito.mock(InvoiceItem.class);
+ Mockito.when(invoiceItem.getId()).thenReturn(UUID.randomUUID());
Mockito.when(invoiceItem.getInvoiceId()).thenReturn(UUID.randomUUID());
Mockito.when(invoiceItem.getAccountId()).thenReturn(UUID.randomUUID());
Mockito.when(invoiceItem.getBundleId()).thenReturn(UUID.randomUUID());
@@ -106,10 +89,11 @@ public class TestInvoiceItemJsonSimple extends JaxrsTestSuite {
Mockito.when(invoiceItem.getCurrency()).thenReturn(Currency.EUR);
final InvoiceItemJsonSimple invoiceItemJsonSimple = new InvoiceItemJsonSimple(invoiceItem);
- Assert.assertEquals(invoiceItemJsonSimple.getInvoiceId(), invoiceItem.getInvoiceId());
- Assert.assertEquals(invoiceItemJsonSimple.getAccountId(), invoiceItem.getAccountId());
- Assert.assertEquals(invoiceItemJsonSimple.getBundleId(), invoiceItem.getBundleId());
- Assert.assertEquals(invoiceItemJsonSimple.getSubscriptionId(), invoiceItem.getSubscriptionId());
+ Assert.assertEquals(invoiceItemJsonSimple.getInvoiceItemId(), invoiceItem.getId().toString());
+ Assert.assertEquals(invoiceItemJsonSimple.getInvoiceId(), invoiceItem.getInvoiceId().toString());
+ Assert.assertEquals(invoiceItemJsonSimple.getAccountId(), invoiceItem.getAccountId().toString());
+ Assert.assertEquals(invoiceItemJsonSimple.getBundleId(), invoiceItem.getBundleId().toString());
+ Assert.assertEquals(invoiceItemJsonSimple.getSubscriptionId(), invoiceItem.getSubscriptionId().toString());
Assert.assertEquals(invoiceItemJsonSimple.getPlanName(), invoiceItem.getPlanName());
Assert.assertEquals(invoiceItemJsonSimple.getPhaseName(), invoiceItem.getPhaseName());
Assert.assertEquals(invoiceItemJsonSimple.getDescription(), invoiceItem.getDescription());
diff --git a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceJsonWithItems.java b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceJsonWithItems.java
index c66bc4f..1b310f6 100644
--- a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceJsonWithItems.java
+++ b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceJsonWithItems.java
@@ -99,10 +99,10 @@ public class TestInvoiceJsonWithItems extends JaxrsTestSuite {
Assert.assertNull(invoiceJsonWithItems.getAuditLogs());
final InvoiceItemJsonSimple invoiceItemJsonSimple = invoiceJsonWithItems.getItems().get(0);
- Assert.assertEquals(invoiceItemJsonSimple.getInvoiceId(), invoiceItem.getInvoiceId());
- Assert.assertEquals(invoiceItemJsonSimple.getAccountId(), invoiceItem.getAccountId());
- Assert.assertEquals(invoiceItemJsonSimple.getBundleId(), invoiceItem.getBundleId());
- Assert.assertEquals(invoiceItemJsonSimple.getSubscriptionId(), invoiceItem.getSubscriptionId());
+ Assert.assertEquals(invoiceItemJsonSimple.getInvoiceId(), invoiceItem.getInvoiceId().toString());
+ Assert.assertEquals(invoiceItemJsonSimple.getAccountId(), invoiceItem.getAccountId().toString());
+ Assert.assertEquals(invoiceItemJsonSimple.getBundleId(), invoiceItem.getBundleId().toString());
+ Assert.assertEquals(invoiceItemJsonSimple.getSubscriptionId(), invoiceItem.getSubscriptionId().toString());
Assert.assertEquals(invoiceItemJsonSimple.getPlanName(), invoiceItem.getPlanName());
Assert.assertEquals(invoiceItemJsonSimple.getPhaseName(), invoiceItem.getPhaseName());
Assert.assertEquals(invoiceItemJsonSimple.getDescription(), invoiceItem.getDescription());
@@ -113,10 +113,11 @@ public class TestInvoiceJsonWithItems extends JaxrsTestSuite {
}
private InvoiceItemJsonSimple createInvoiceItemJson() {
- final UUID invoiceId = UUID.randomUUID();
- final UUID accountId = UUID.randomUUID();
- final UUID bundleId = UUID.randomUUID();
- final UUID subscriptionId = UUID.randomUUID();
+ final String invoiceItemId = UUID.randomUUID().toString();
+ final String invoiceId = UUID.randomUUID().toString();
+ final String accountId = UUID.randomUUID().toString();
+ final String bundleId = UUID.randomUUID().toString();
+ final String subscriptionId = UUID.randomUUID().toString();
final String planName = UUID.randomUUID().toString();
final String phaseName = UUID.randomUUID().toString();
final String description = UUID.randomUUID().toString();
@@ -124,9 +125,9 @@ public class TestInvoiceJsonWithItems extends JaxrsTestSuite {
final LocalDate endDate = clock.getUTCToday();
final BigDecimal amount = BigDecimal.TEN;
final Currency currency = Currency.MXN;
- return new InvoiceItemJsonSimple(invoiceId, accountId, bundleId, subscriptionId,
+ return new InvoiceItemJsonSimple(invoiceItemId, invoiceId, accountId, bundleId, subscriptionId,
planName, phaseName, description, startDate, endDate,
- amount, currency);
+ amount, currency, null);
}
private InvoiceItem createInvoiceItem() {
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
index fb8998e..8ecc101 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
@@ -29,6 +29,7 @@ import com.ning.billing.catalog.api.BillingAlignment;
import com.ning.billing.catalog.api.Catalog;
import com.ning.billing.catalog.api.CatalogApiException;
import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.PlanPhaseSpecifier;
@@ -118,7 +119,9 @@ public class BillCycleDayCalculator {
@VisibleForTesting
BillCycleDay calculateBcdFromSubscription(final Subscription subscription, final Plan plan, final Account account) throws AccountApiException {
- final DateTime date = plan.dateOfFirstRecurringNonZeroCharge(subscription.getStartDate());
+
+ final PhaseType currentPhaseType = subscription.getCurrentPhase() != null ? subscription.getCurrentPhase().getPhaseType() : null;
+ final DateTime date = plan.dateOfFirstRecurringNonZeroCharge(subscription.getStartDate(), currentPhaseType);
// There are really two kinds of billCycleDay:
// - a System billingCycleDay which should be computed from UTC time (in order to get the correct notification time at
// the end of each service period)
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java
index b5b327a..f243bbf 100644
--- a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java
@@ -60,7 +60,7 @@ public class TestBillCycleDayCalculator {
// Create a the base plan associated with that subscription
final Plan plan = Mockito.mock(Plan.class);
- Mockito.when(plan.dateOfFirstRecurringNonZeroCharge(bpStartDateUTC)).thenReturn(bpStartDateUTC);
+ Mockito.when(plan.dateOfFirstRecurringNonZeroCharge(bpStartDateUTC, null)).thenReturn(bpStartDateUTC);
final Catalog catalog = Mockito.mock(Catalog.class);
Mockito.when(catalog.findPlan(Mockito.anyString(), Mockito.<DateTime>any(), Mockito.<DateTime>any())).thenReturn(plan);
@@ -140,7 +140,7 @@ public class TestBillCycleDayCalculator {
Mockito.when(subscription.getStartDate()).thenReturn(startDateUTC);
final Plan plan = Mockito.mock(Plan.class);
- Mockito.when(plan.dateOfFirstRecurringNonZeroCharge(startDateUTC)).thenReturn(startDateUTC);
+ Mockito.when(plan.dateOfFirstRecurringNonZeroCharge(startDateUTC, null)).thenReturn(startDateUTC);
final Account account = Mockito.mock(Account.class);
Mockito.when(account.getTimeZone()).thenReturn(accountTimeZone);
diff --git a/util/src/test/java/com/ning/billing/mock/MockPlan.java b/util/src/test/java/com/ning/billing/mock/MockPlan.java
index 2e153c8..2d39621 100644
--- a/util/src/test/java/com/ning/billing/mock/MockPlan.java
+++ b/util/src/test/java/com/ning/billing/mock/MockPlan.java
@@ -24,6 +24,7 @@ import org.joda.time.DateTime;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.Product;
@@ -97,7 +98,7 @@ public class MockPlan implements Plan {
}
@Override
- public DateTime dateOfFirstRecurringNonZeroCharge(final DateTime subscriptionStartDate) {
+ public DateTime dateOfFirstRecurringNonZeroCharge(final DateTime subscriptionStartDate, PhaseType initialPhaseType) {
throw new UnsupportedOperationException();
}
}