Details
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
index a824149..3e5676e 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
@@ -49,9 +49,11 @@ import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.account.api.AccountUserApi;
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.InvoiceNotifier;
import com.ning.billing.invoice.api.InvoiceUserApi;
import com.ning.billing.jaxrs.json.CustomFieldJson;
+import com.ning.billing.jaxrs.json.InvoiceItemJsonSimple;
import com.ning.billing.jaxrs.json.InvoiceJsonSimple;
import com.ning.billing.jaxrs.json.InvoiceJsonWithItems;
import com.ning.billing.jaxrs.json.PaymentJsonSimple;
@@ -65,6 +67,7 @@ import com.ning.billing.util.api.TagApiException;
import com.ning.billing.util.api.TagDefinitionApiException;
import com.ning.billing.util.api.TagUserApi;
import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.dao.ObjectType;
import com.google.inject.Inject;
@@ -88,6 +91,7 @@ public class InvoiceResource extends JaxRsResourceBase {
private final Context context;
private final JaxrsUriBuilder uriBuilder;
private final InvoiceNotifier invoiceNotifier;
+ private final Clock clock;
@Inject
public InvoiceResource(final AccountUserApi accountApi,
@@ -97,7 +101,8 @@ public class InvoiceResource extends JaxRsResourceBase {
final JaxrsUriBuilder uriBuilder,
final TagUserApi tagUserApi,
final CustomFieldUserApi customFieldUserApi,
- final InvoiceNotifier invoiceNotifier) {
+ final InvoiceNotifier invoiceNotifier,
+ final Clock clock) {
super(uriBuilder, tagUserApi, customFieldUserApi);
this.accountApi = accountApi;
this.invoiceApi = invoiceApi;
@@ -105,28 +110,39 @@ public class InvoiceResource extends JaxRsResourceBase {
this.context = context;
this.uriBuilder = uriBuilder;
this.invoiceNotifier = invoiceNotifier;
+ this.clock = clock;
}
@GET
@Produces(APPLICATION_JSON)
- public Response getInvoices(@QueryParam(QUERY_ACCOUNT_ID) final String accountId) throws AccountApiException {
+ public Response getInvoices(@QueryParam(QUERY_ACCOUNT_ID) final String accountId,
+ @QueryParam(QUERY_INVOICE_WITH_ITEMS) @DefaultValue("false") final boolean withItems) throws AccountApiException {
// Verify the account exists
accountApi.getAccountById(UUID.fromString(accountId));
final List<Invoice> invoices = invoiceApi.getInvoicesByAccount(UUID.fromString(accountId));
- final List<InvoiceJsonSimple> result = new LinkedList<InvoiceJsonSimple>();
- for (final Invoice invoice : invoices) {
- result.add(new InvoiceJsonSimple(invoice));
- }
+ if (withItems) {
+ final List<InvoiceJsonWithItems> result = new LinkedList<InvoiceJsonWithItems>();
+ for (final Invoice invoice : invoices) {
+ result.add(new InvoiceJsonWithItems(invoice));
+ }
- return Response.status(Status.OK).entity(result).build();
+ return Response.status(Status.OK).entity(result).build();
+ } else {
+ final List<InvoiceJsonSimple> result = new LinkedList<InvoiceJsonSimple>();
+ for (final Invoice invoice : invoices) {
+ result.add(new InvoiceJsonSimple(invoice));
+ }
+
+ return Response.status(Status.OK).entity(result).build();
+ }
}
@GET
@Path("/{invoiceId:" + UUID_PATTERN + "}/")
@Produces(APPLICATION_JSON)
public Response getInvoice(@PathParam("invoiceId") final String invoiceId,
- @QueryParam("withItems") @DefaultValue("false") final boolean withItems) throws InvoiceApiException {
+ @QueryParam(QUERY_INVOICE_WITH_ITEMS) @DefaultValue("false") final boolean withItems) throws InvoiceApiException {
final Invoice invoice = invoiceApi.getInvoice(UUID.fromString(invoiceId));
if (invoice == null) {
throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND);
@@ -166,6 +182,47 @@ public class InvoiceResource extends JaxRsResourceBase {
}
}
+ @POST
+ @Path("/{invoiceId:" + UUID_PATTERN + "}")
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ public Response adjustInvoiceItem(final InvoiceItemJsonSimple json,
+ @PathParam("invoiceId") final String invoiceId,
+ @QueryParam(QUERY_REQUESTED_DT) final String requestedDateTimeString,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment) throws AccountApiException, InvoiceApiException {
+ final Account account = accountApi.getAccountById(UUID.fromString(json.getAccountId()));
+
+ // Get the effective date of the adjustment, in the account timezone
+ final LocalDate requestedDate;
+ if (requestedDateTimeString == null) {
+ requestedDate = clock.getUTCToday();
+ } else {
+ final DateTime requestedDateTime = DATE_TIME_FORMATTER.parseDateTime(requestedDateTimeString);
+ requestedDate = requestedDateTime.toDateTime(account.getTimeZone()).toLocalDate();
+ }
+
+ final InvoiceItem adjustmentItem;
+ if (json.getAmount() == null) {
+ adjustmentItem = invoiceApi.insertInvoiceItemAdjustment(account.getId(),
+ UUID.fromString(invoiceId),
+ UUID.fromString(json.getInvoiceItemId()),
+ requestedDate,
+ context.createContext(createdBy, reason, comment));
+ } else {
+ adjustmentItem = invoiceApi.insertInvoiceItemAdjustment(account.getId(),
+ UUID.fromString(invoiceId),
+ UUID.fromString(json.getInvoiceItemId()),
+ requestedDate,
+ json.getAmount(),
+ json.getCurrency(),
+ context.createContext(createdBy, reason, comment));
+ }
+
+ return uriBuilder.buildResponse(InvoiceResource.class, "getInvoice", adjustmentItem.getInvoiceId());
+ }
+
@GET
@Path("/{invoiceId:" + UUID_PATTERN + "}/" + PAYMENTS)
@Produces(APPLICATION_JSON)
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 037d036..567a72b 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
@@ -51,6 +51,8 @@ public interface JaxrsResource {
public static final String QUERY_ACCOUNT_ID = "accountId";
+ public static final String QUERY_INVOICE_WITH_ITEMS = "withItems";
+
public static final String QUERY_PAYMENT_EXTERNAL = "externalPayment";
public static final String QUERY_PAYMENT_LAST4_CC = "last4CC";
public static final String QUERY_PAYMENT_NAME_ON_CC = "nameOnCC";
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
index 3b0168f..675159a 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
@@ -17,13 +17,16 @@
package com.ning.billing.jaxrs;
import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.util.List;
import org.joda.time.DateTime;
import org.testng.annotations.Test;
import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.InvoiceItemJsonSimple;
import com.ning.billing.jaxrs.json.InvoiceJsonSimple;
+import com.ning.billing.jaxrs.json.InvoiceJsonWithItems;
import com.ning.billing.jaxrs.json.PaymentJsonSimple;
import com.ning.billing.jaxrs.json.PaymentMethodJson;
import com.ning.billing.payment.provider.ExternalPaymentProviderPlugin;
@@ -135,4 +138,52 @@ public class TestInvoice extends TestJaxrsBase {
assertEquals(paymentMethodJson.getPluginName(), ExternalPaymentProviderPlugin.PLUGIN_NAME);
assertNull(paymentMethodJson.getPluginInfo());
}
+
+ @Test(groups = "slow")
+ public void testFullInvoiceItemAdjustment() throws Exception {
+ final AccountJson accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+ // Get the invoices
+ final List<InvoiceJsonWithItems> invoices = getInvoicesWithItemsForAccount(accountJson.getAccountId());
+ // 2 invoices but look for the non zero dollar one
+ assertEquals(invoices.size(), 2);
+ final InvoiceJsonWithItems invoice = invoices.get(1);
+ // Verify the invoice we picked is non zero
+ assertEquals(invoice.getAmount().compareTo(BigDecimal.ZERO), 1);
+ final InvoiceItemJsonSimple invoiceItem = invoice.getItems().get(0);
+ // Verify the item we picked is non zero
+ assertEquals(invoiceItem.getAmount().compareTo(BigDecimal.ZERO), 1);
+
+ // Adjust the full amount
+ adjustInvoiceItem(accountJson.getAccountId(), invoice.getInvoiceId(), invoiceItem.getInvoiceItemId(), null, null, null);
+
+ // Verify the new invoice balance is zero
+ final InvoiceJsonSimple adjustedInvoice = getInvoice(invoice.getInvoiceId());
+ assertEquals(adjustedInvoice.getAmount().compareTo(BigDecimal.ZERO), 1);
+ }
+
+ @Test(groups = "slow")
+ public void testPartialInvoiceItemAdjustment() throws Exception {
+ final AccountJson accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+ // Get the invoices
+ final List<InvoiceJsonWithItems> invoices = getInvoicesWithItemsForAccount(accountJson.getAccountId());
+ // 2 invoices but look for the non zero dollar one
+ assertEquals(invoices.size(), 2);
+ final InvoiceJsonWithItems invoice = invoices.get(1);
+ // Verify the invoice we picked is non zero
+ assertEquals(invoice.getAmount().compareTo(BigDecimal.ZERO), 1);
+ final InvoiceItemJsonSimple invoiceItem = invoice.getItems().get(0);
+ // Verify the item we picked is non zero
+ assertEquals(invoiceItem.getAmount().compareTo(BigDecimal.ZERO), 1);
+
+ // Adjust partially the item
+ final BigDecimal adjustedAmount = invoiceItem.getAmount().divide(BigDecimal.TEN);
+ adjustInvoiceItem(accountJson.getAccountId(), invoice.getInvoiceId(), invoiceItem.getInvoiceItemId(), null, adjustedAmount, null);
+
+ // Verify the new invoice balance
+ final InvoiceJsonSimple adjustedInvoice = getInvoice(invoice.getInvoiceId());
+ final BigDecimal adjustedInvoiceBalance = invoice.getBalance().add(adjustedAmount.negate().setScale(2, RoundingMode.HALF_UP));
+ assertEquals(adjustedInvoice.getBalance().compareTo(adjustedInvoiceBalance), 0);
+ }
}
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
index 3e007b3..34326cf 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -44,6 +44,7 @@ import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeSuite;
+import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
@@ -58,6 +59,7 @@ import com.ning.billing.api.TestApiListener;
import com.ning.billing.beatrix.glue.BeatrixModule;
import com.ning.billing.beatrix.integration.TestIntegration;
import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
import com.ning.billing.catalog.api.PriceListSet;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.catalog.glue.CatalogModule;
@@ -72,7 +74,9 @@ import com.ning.billing.invoice.notification.NullInvoiceNotifier;
import com.ning.billing.jaxrs.json.AccountJson;
import com.ning.billing.jaxrs.json.BillCycleDayJson;
import com.ning.billing.jaxrs.json.BundleJsonNoSubscriptions;
+import com.ning.billing.jaxrs.json.InvoiceItemJsonSimple;
import com.ning.billing.jaxrs.json.InvoiceJsonSimple;
+import com.ning.billing.jaxrs.json.InvoiceJsonWithItems;
import com.ning.billing.jaxrs.json.PaymentJsonSimple;
import com.ning.billing.jaxrs.json.PaymentMethodJson;
import com.ning.billing.jaxrs.json.PaymentMethodJson.PaymentMethodPluginDetailJson;
@@ -477,19 +481,38 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
}
protected List<InvoiceJsonSimple> getInvoicesForAccount(final String accountId) throws IOException {
+ final String invoicesURI = JaxrsResource.INVOICES_PATH;
+
final Map<String, String> queryParams = new HashMap<String, String>();
queryParams.put(JaxrsResource.QUERY_ACCOUNT_ID, accountId);
- final String invoicesURI = JaxrsResource.INVOICES_PATH;
+
final Response invoicesResponse = doGet(invoicesURI, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
assertEquals(invoicesResponse.getStatusCode(), Status.OK.getStatusCode());
- final String invoicesBaseJson = invoicesResponse.getResponseBody();
+ final String invoicesBaseJson = invoicesResponse.getResponseBody();
final List<InvoiceJsonSimple> invoices = mapper.readValue(invoicesBaseJson, new TypeReference<List<InvoiceJsonSimple>>() {});
assertNotNull(invoices);
return invoices;
}
+ protected List<InvoiceJsonWithItems> getInvoicesWithItemsForAccount(final String accountId) throws IOException {
+ final String invoicesURI = JaxrsResource.INVOICES_PATH;
+
+ final Map<String, String> queryParams = new HashMap<String, String>();
+ queryParams.put(JaxrsResource.QUERY_ACCOUNT_ID, accountId);
+ queryParams.put(JaxrsResource.QUERY_INVOICE_WITH_ITEMS, "true");
+
+ final Response invoicesResponse = doGet(invoicesURI, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(invoicesResponse.getStatusCode(), Status.OK.getStatusCode());
+
+ final String invoicesBaseJson = invoicesResponse.getResponseBody();
+ final List<InvoiceJsonWithItems> invoices = mapper.readValue(invoicesBaseJson, new TypeReference<List<InvoiceJsonWithItems>>() {});
+ assertNotNull(invoices);
+
+ return invoices;
+ }
+
protected InvoiceJsonSimple createDryRunInvoice(final String accountId, final DateTime futureDate) throws IOException {
final String uri = JaxrsResource.INVOICES_PATH;
@@ -522,6 +545,22 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
Assert.assertNotNull(location);
}
+ protected void adjustInvoiceItem(final String accountId, final String invoiceId, final String invoiceItemId,
+ @Nullable final DateTime requestedDate, @Nullable final BigDecimal amount, @Nullable final Currency currency) throws IOException {
+ final String uri = JaxrsResource.INVOICES_PATH + "/" + invoiceId;
+
+ final Map<String, String> queryParams = new HashMap<String, String>();
+ if (requestedDate != null) {
+ queryParams.put(JaxrsResource.QUERY_REQUESTED_DT, requestedDate.toDateTimeISO().toString());
+ }
+
+ final InvoiceItemJsonSimple adjustment = new InvoiceItemJsonSimple(invoiceItemId, null, accountId, null, null, null, null,
+ null, null, null, amount, currency, null);
+ final String adjustmentJson = mapper.writeValueAsString(adjustment);
+ final Response response = doPost(uri, adjustmentJson, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+ }
+
//
// PAYMENT UTILITIES
//