killbill-aplcache

updates for tag api

5/2/2012 12:32:30 PM

Changes

Details

diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java b/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
index 923591c..a340b6f 100644
--- a/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
+++ b/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
@@ -87,31 +87,9 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
                 updatedBy, updatedDate);
 	}
 
-	/**
+	/*
 	 * This call is used for testing and update from an existing account
-	 * @param id
-	 * @param externalKey
-	 * @param email
-	 * @param name
-	 * @param firstNameLength
-	 * @param currency
-	 * @param billCycleDay
-	 * @param paymentProviderName
-	 * @param timeZone
-	 * @param locale
-	 * @param address1
-	 * @param address2
-	 * @param companyName
-	 * @param city
-	 * @param stateOrProvince
-	 * @param country
-	 * @param postalCode
-	 * @param phone
-     * @param isMigrated
-     * @param isNotifiedForInvoices
-	 * @param createdDate
-	 * @param updatedDate
-	 */
+     */
 	public DefaultAccount(final UUID id, final String externalKey, final String email,
                           final String name, final int firstNameLength,
                           final Currency currency, final int billCycleDay, final String paymentProviderName,
@@ -296,7 +274,7 @@ public class DefaultAccount extends ExtendedEntityBase implements Account {
 				", stateOrProvince=" + stateOrProvince +
 				", postalCode=" + postalCode +
 				", country=" + country +
-				", tags=" + tags +
+				", tagStore=" + tagStore +
                 ", fields=" + fields +
                 "]";
 	}
diff --git a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
index 6c7e78f..8f09d7d 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
@@ -32,7 +32,7 @@ import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallContextFactory;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.entity.EntityPersistenceException;
-import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
 import org.joda.time.DateTime;
 
 public class DefaultAccountUserApi implements com.ning.billing.account.api.AccountUserApi {
@@ -47,10 +47,10 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
 
     @Override
     public Account createAccount(final AccountData data, final List<CustomField> fields,
-                                 final List<Tag> tags, final CallContext context) throws AccountApiException {
+                                 final List<TagDefinition> tagDefinitions, final CallContext context) throws AccountApiException {
         Account account = new DefaultAccount(data);
         account.setFields(fields);
-        account.addTags(tags);
+        account.addTagsFromDefinitions(tagDefinitions);
 
         try {
             dao.create(account, context);
@@ -113,14 +113,14 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
 
 	@Override
 	public Account migrateAccount(final MigrationAccountData data, final List<CustomField> fields,
-                                  final List<Tag> tags, final CallContext context)
+                                  final List<TagDefinition> tagDefinitions, final CallContext context)
             throws AccountApiException {
         DateTime createdDate = data.getCreatedDate() == null ? context.getCreatedDate() : data.getCreatedDate();
         DateTime updatedDate = data.getUpdatedDate() == null ? context.getUpdatedDate() : data.getUpdatedDate();
         CallContext migrationContext = factory.toMigrationCallContext(context, createdDate, updatedDate);
 		Account account = new DefaultAccount(data);
         account.setFields(fields);
-        account.addTags(tags);
+        account.addTagsFromDefinitions(tagDefinitions);
 
         try {
             dao.create(account, migrationContext);
diff --git a/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
index 8e51da0..9396541 100644
--- a/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
+++ b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
@@ -22,11 +22,11 @@ import java.util.UUID;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.tag.TagDefinition;
 import org.joda.time.DateTimeZone;
 
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.customfield.CustomField;
-import com.ning.billing.util.tag.Tag;
 
 public class MockAccountUserApi implements AccountUserApi {
     private final CopyOnWriteArrayList<Account> accounts = new CopyOnWriteArrayList<Account>();
@@ -61,7 +61,7 @@ public class MockAccountUserApi implements AccountUserApi {
 
     @Override
     public Account createAccount(final AccountData data, final List<CustomField> fields,
-                                 final List<Tag> tags, final CallContext context) throws AccountApiException {
+                                 final List<TagDefinition> tagDefinitions, final CallContext context) throws AccountApiException {
         Account result = new DefaultAccount(data);
         accounts.add(result);
         return result;
@@ -119,7 +119,7 @@ public class MockAccountUserApi implements AccountUserApi {
 
 	@Override
 	public Account migrateAccount(final MigrationAccountData data,
-			final List<CustomField> fields, final List<Tag> tags, final CallContext context)
+			final List<CustomField> fields, final List<TagDefinition> tagDefinitions, final CallContext context)
 			throws AccountApiException {
 		Account result = new DefaultAccount(data);
         accounts.add(result);
diff --git a/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java b/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
index 3a51bcc..66e49bb 100644
--- a/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
+++ b/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
@@ -67,7 +67,7 @@ public abstract class AccountDaoTestBase {
             accountDao.test();
 
             Clock clock = injector.getInstance(Clock.class);
-            context = new DefaultCallContextFactory(clock).createCallContext("Vizzini", CallOrigin.TEST, UserType.TEST);
+            context = new DefaultCallContextFactory(clock).createCallContext("Account Dao Tests", CallOrigin.TEST, UserType.TEST);
 
 
             BusService busService = injector.getInstance(BusService.class);
diff --git a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
index 71cbeb1..20f372e 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
@@ -26,6 +26,7 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.UUID;
 
+import com.ning.billing.util.tag.TagDefinition;
 import org.apache.commons.io.IOUtils;
 import org.joda.time.DateTime;
 import org.testng.Assert;
@@ -87,8 +88,6 @@ import com.ning.billing.util.callcontext.UserType;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.DefaultClock;
 import com.ning.billing.util.tag.DefaultTagDefinition;
-import com.ning.billing.util.tag.DescriptiveTag;
-import com.ning.billing.util.tag.Tag;
 import com.ning.billing.util.tag.dao.TagDefinitionSqlDao;
 
 @Guice(modules = {AnalyticsTestModule.class, MockCatalogModule.class})
@@ -161,11 +160,11 @@ public class TestAnalyticsService {
 
         final MockAccount account = new MockAccount(UUID.randomUUID(), ACCOUNT_KEY, ACCOUNT_CURRENCY);
         try {
-            final List<Tag> tags = new ArrayList<Tag>();
-            tags.add(new DescriptiveTag(TAG_ONE));
-            tags.add(new DescriptiveTag(TAG_TWO));
+            final List<TagDefinition> tagDefinitions = new ArrayList<TagDefinition>();
+            tagDefinitions.add(TAG_ONE);
+            tagDefinitions.add(TAG_TWO);
 
-            final Account storedAccount = accountApi.createAccount(account, null, tags, context);
+            final Account storedAccount = accountApi.createAccount(account, null, tagDefinitions, context);
 
             // Create events for the bus and expected results
             createSubscriptionTransitionEvent(storedAccount);
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java b/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
index bd95758..159c707 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
@@ -19,6 +19,7 @@ package com.ning.billing.analytics;
 import java.util.List;
 import java.util.UUID;
 
+import com.ning.billing.util.tag.ControlTagType;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 
@@ -210,7 +211,12 @@ public class MockAccount implements Account
     }
 
     @Override
-    public boolean hasTag(String tagName) {
+    public boolean hasTag(TagDefinition tagDefinition) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean hasTag(ControlTagType controlTagType) {
         throw new UnsupportedOperationException();
     }
 
@@ -225,6 +231,11 @@ public class MockAccount implements Account
     }
 
     @Override
+    public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public void clearTags() {
         throw new UnsupportedOperationException();
     }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockAccountUserApi.java b/analytics/src/test/java/com/ning/billing/analytics/MockAccountUserApi.java
index f015561..795360f 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockAccountUserApi.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockAccountUserApi.java
@@ -20,6 +20,7 @@ import java.util.List;
 import java.util.UUID;
 
 import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.util.tag.TagDefinition;
 import org.apache.commons.lang.NotImplementedException;
 
 import com.ning.billing.account.api.Account;
@@ -31,7 +32,6 @@ import com.ning.billing.account.api.MigrationAccountData;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.customfield.CustomField;
-import com.ning.billing.util.tag.Tag;
 
 public class MockAccountUserApi implements AccountUserApi
 {
@@ -45,7 +45,8 @@ public class MockAccountUserApi implements AccountUserApi
     }
 
     @Override
-    public Account createAccount(final AccountData data, final List<CustomField> fields, final List<Tag> tags, final CallContext context)
+    public Account createAccount(final AccountData data, final List<CustomField> fields,
+                                 final List<TagDefinition> tagDefinitions, final CallContext context)
     {
         throw new UnsupportedOperationException();
     }
@@ -89,7 +90,7 @@ public class MockAccountUserApi implements AccountUserApi
 
     @Override
 	public Account migrateAccount(MigrationAccountData data,
-			List<CustomField> fields, List<Tag> tags, final CallContext context)
+			List<CustomField> fields, List<TagDefinition> tagDefinitions, final CallContext context)
 			throws AccountApiException {
 		throw new UnsupportedOperationException();
 	}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
index 7144edb..8937063 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
@@ -19,6 +19,7 @@ package com.ning.billing.analytics;
 import java.util.List;
 import java.util.UUID;
 
+import com.ning.billing.util.tag.ControlTagType;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 
@@ -36,7 +37,6 @@ import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.tag.Tag;
 import com.ning.billing.util.tag.TagDefinition;
 
-
 public class MockSubscription implements Subscription
 {
     private static final UUID ID = UUID.randomUUID();
@@ -212,7 +212,12 @@ public class MockSubscription implements Subscription
     }
 
     @Override
-    public boolean hasTag(String tagName) {
+    public boolean hasTag(TagDefinition tagDefinition) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean hasTag(ControlTagType controlTagType) {
         throw new UnsupportedOperationException();
     }
 
@@ -227,6 +232,11 @@ public class MockSubscription implements Subscription
     }
 
     @Override
+    public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public void clearTags() {
         throw new UnsupportedOperationException();
     }
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java b/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
index 5ea43d0..9609598 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
@@ -21,22 +21,23 @@ import java.util.UUID;
 
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.customfield.CustomField;
-import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
 
 import javax.annotation.Nullable;
 
 public interface AccountUserApi {
     public Account createAccount(AccountData data, @Nullable List<CustomField> fields,
-                                 @Nullable List<Tag> tags, CallContext context) throws AccountApiException;
+                                 @Nullable List<TagDefinition> tagDefinitions, CallContext context) throws AccountApiException;
 
     public Account migrateAccount(MigrationAccountData data, @Nullable List<CustomField> fields,
-                                  @Nullable List<Tag> tags, CallContext context) throws AccountApiException;
+                                  @Nullable List<TagDefinition> tagDefinitions, CallContext context) throws AccountApiException;
 
     /***
      *
      * Note: does not update the external key
-     * @param account
-     * @throws AccountApiException
+     * @param account account to be updated
+     * @param context contains specific information about the call
+     * @throws AccountApiException if a failure occurs
      */
     public void updateAccount(Account account, CallContext context) throws AccountApiException;
 
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
index b7ab8b1..cd7decd 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
@@ -46,5 +46,5 @@ public interface InvoiceItem extends Entity, Comparable<InvoiceItem> {
 
     Currency getCurrency();
 
-    InvoiceItem asCredit();
+    InvoiceItem asReversingItem();
 }
diff --git a/api/src/main/java/com/ning/billing/util/api/TagUserApi.java b/api/src/main/java/com/ning/billing/util/api/TagUserApi.java
index d5cb8f2..5d05bb0 100644
--- a/api/src/main/java/com/ning/billing/util/api/TagUserApi.java
+++ b/api/src/main/java/com/ning/billing/util/api/TagUserApi.java
@@ -57,7 +57,6 @@ public interface TagUserApi {
      */
     public void deleteTagDefinition(String definitionName, CallContext context) throws TagDefinitionApiException;
 
-    
 	/**
 	 * 
 	 * @param name
diff --git a/api/src/main/java/com/ning/billing/util/tag/Taggable.java b/api/src/main/java/com/ning/billing/util/tag/Taggable.java
index 3b2e8b0..bb8aa1d 100644
--- a/api/src/main/java/com/ning/billing/util/tag/Taggable.java
+++ b/api/src/main/java/com/ning/billing/util/tag/Taggable.java
@@ -21,11 +21,16 @@ import org.joda.time.DateTime;
 
 public interface Taggable {
     public List<Tag> getTagList();
-    public boolean hasTag(String tagName);
+
+    public boolean hasTag(TagDefinition definition);
+    public boolean hasTag(ControlTagType controlTagType);
+
     public void addTag(TagDefinition definition);
     public void addTags(List<Tag> tags);
+    public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions);
     public void clearTags();
     public void removeTag(TagDefinition definition);
+
     public boolean generateInvoice();
     public boolean processPayment();
 }
diff --git a/api/src/main/java/com/ning/billing/util/tag/TagStore.java b/api/src/main/java/com/ning/billing/util/tag/TagStore.java
index 705e203..22749b9 100644
--- a/api/src/main/java/com/ning/billing/util/tag/TagStore.java
+++ b/api/src/main/java/com/ning/billing/util/tag/TagStore.java
@@ -23,7 +23,9 @@ public interface TagStore extends EntityCollection<Tag> {
 
     public boolean generateInvoice();
 
-    public void remove(String tagName);
+    public boolean containsTagForDefinition(TagDefinition definition);
 
-    public boolean containsTag(String tagName);
+    public boolean containsTagForControlTagType(ControlTagType controlTagType);
+
+    public Tag remove(TagDefinition tagDefinition);
 }
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
index 8da1bb8..71a23d1 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
@@ -49,8 +49,6 @@ import com.ning.billing.entitlement.api.user.SubscriptionData;
 @Test(groups = "slow")
 @Guice(modules = {MockModule.class})
 public class TestIntegration extends TestIntegrationBase {
- 
-
     @Test(groups = "slow", enabled = true)
     public void testBasePlanCompleteWithBillingDayInPast() throws Exception {
         DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
@@ -99,8 +97,8 @@ public class TestIntegration extends TestIntegrationBase {
         }
     }
 
-   @Test(groups = "slow", enabled = true) 
-   public void testRepairChangeBPWithAddonIncluded() throws Exception {
+    @Test(groups = "slow", enabled = true)
+    public void testRepairChangeBPWithAddonIncluded() throws Exception {
         
         DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
         clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
@@ -150,9 +148,39 @@ public class TestIntegration extends TestIntegrationBase {
         busHandler.pushExpectedEvent(NextEvent.PAYMENT);
         clock.addDeltaFromReality(it.toDurationMillis());
         assertTrue(busHandler.isCompleted(DELAY));
-   }
-   
+    }
    
+    @Test(groups = {"slow"})
+    public void testRepairForInvoicing() throws AccountApiException, EntitlementUserApiException {
+        log.info("Starting testRepairForInvoicing");
+
+        Account account = accountUserApi.createAccount(getAccountData(1), null, null, context);
+        UUID accountId = account.getId();
+        assertNotNull(account);
+
+        DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+        SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "someBundle", context);
+        assertNotNull(bundle);
+
+        String productName = "Shotgun";
+        BillingPeriod term = BillingPeriod.MONTHLY;
+        String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+        entitlementUserApi.createSubscription(bundle.getId(),
+                new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context);
+
+        busHandler.reset();
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        assertTrue(busHandler.isCompleted(DELAY));
+
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId);
+        assertEquals(invoices.size(), 1);
+
+        // TODO: Jeff implement repair
+    }
+
     @Test(groups = "slow", enabled = false)
     public void testWithRecreatePlan() throws Exception {
 
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
index d4e1685..e18b73d 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
@@ -24,6 +24,7 @@ import java.util.UUID;
 
 import javax.annotation.Nullable;
 
+import com.ning.billing.util.tag.ControlTagType;
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -54,8 +55,7 @@ import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.entity.ExtendedEntityBase;
 
-public class SubscriptionData extends ExtendedEntityBase implements
-        Subscription {
+public class SubscriptionData extends ExtendedEntityBase implements Subscription {
 
     private final static Logger log = LoggerFactory.getLogger(SubscriptionData.class);
 
@@ -504,5 +504,4 @@ public class SubscriptionData extends ExtendedEntityBase implements
             previousPriceList = nextPriceList;
         }
     }
-
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
index e9bfe5c..a1241f6 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
@@ -86,7 +86,7 @@ public class TestUserApiAddOn extends TestApiBase {
     }
 
     @Test(enabled=true, groups={"slow"})
-    public void testCancelBPWthAddon() {
+    public void testCancelBPWithAddon() {
         try {
 
             String baseProduct = "Shotgun";
@@ -114,6 +114,7 @@ public class TestUserApiAddOn extends TestApiBase {
             // SET CTD TO CANCEL IN FUTURE
             DateTime now = clock.getUTCNow();
             Duration ctd = getDurationMonth(1);
+            // Why not just use clock.getUTCNow().plusMonths(1) ?
             DateTime newChargedThroughDate = DefaultClock.addDuration(now, ctd);
             billingApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate, context);
             baseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
@@ -126,7 +127,6 @@ public class TestUserApiAddOn extends TestApiBase {
             assertEquals(aoSubscription.getState(), SubscriptionState.ACTIVE);
             assertTrue(aoSubscription.isSubscriptionFutureCancelled());
 
-
             // MOVE AFTER CANCELLATION
             testListener.reset();
             testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
@@ -145,7 +145,7 @@ public class TestUserApiAddOn extends TestApiBase {
 
 
     @Test(enabled=true, groups={"slow"})
-    public void testChangeBPWthAddonNonIncluded() {
+    public void testChangeBPWithAddonNonIncluded() {
         try {
 
             String baseProduct = "Shotgun";
@@ -198,7 +198,7 @@ public class TestUserApiAddOn extends TestApiBase {
     }
 
     @Test(enabled=true, groups={"slow"})
-    public void testChangeBPWthAddonNonAvailable() {
+    public void testChangeBPWithAddonNonAvailable() {
         try {
 
             String baseProduct = "Shotgun";
diff --git a/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java b/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
index 8fd0386..c5350b8 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
@@ -17,8 +17,12 @@
 package com.ning.billing.invoice.glue;
 
 import com.ning.billing.invoice.api.InvoiceNotifier;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatterFactory;
 import com.ning.billing.invoice.notification.EmailInvoiceNotifier;
-import com.ning.billing.util.email.EmailConfig;
+import com.ning.billing.invoice.template.formatters.DefaultInvoiceFormatterFactory;
+import com.ning.billing.util.email.templates.MustacheTemplateEngine;
+import com.ning.billing.util.email.templates.TemplateEngine;
+import com.ning.billing.util.template.translation.TranslatorConfig;
 import org.skife.config.ConfigurationObjectFactory;
 
 import com.google.inject.AbstractModule;
@@ -71,6 +75,10 @@ public class InvoiceModule extends AbstractModule {
     protected void installNotifiers() {
         bind(NextBillingDateNotifier.class).to(DefaultNextBillingDateNotifier.class).asEagerSingleton();
         bind(NextBillingDatePoster.class).to(DefaultNextBillingDatePoster.class).asEagerSingleton();
+        TranslatorConfig config = new ConfigurationObjectFactory(System.getProperties()).build(TranslatorConfig.class);
+        bind(TranslatorConfig.class).toInstance(config);
+        bind(InvoiceFormatterFactory.class).to(DefaultInvoiceFormatterFactory.class).asEagerSingleton();
+        bind(TemplateEngine.class).to(MustacheTemplateEngine.class).asEagerSingleton();
         bind(InvoiceNotifier.class).to(EmailInvoiceNotifier.class).asEagerSingleton();
     }
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/CreditInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/CreditInvoiceItem.java
new file mode 100644
index 0000000..58c4a49
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/CreditInvoiceItem.java
@@ -0,0 +1,82 @@
+/*
+ * 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.invoice.model;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import org.joda.time.DateTime;
+
+import javax.annotation.Nullable;
+import java.math.BigDecimal;
+import java.util.UUID;
+
+public class CreditInvoiceItem extends InvoiceItemBase {
+    public CreditInvoiceItem(UUID invoiceId, UUID accountId, DateTime date, BigDecimal amount, Currency currency) {
+        this(UUID.randomUUID(), invoiceId, accountId, date, amount, currency, null, null);
+    }
+
+    public CreditInvoiceItem(UUID id, UUID invoiceId, UUID accountId, DateTime date, BigDecimal amount, Currency currency,
+                             @Nullable String createdBy, @Nullable DateTime createdDate) {
+        super(id, invoiceId, accountId, null, null, null, null, date, date, amount, currency, createdBy, createdDate);
+    }
+
+    @Override
+    public InvoiceItem asReversingItem() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getDescription() {
+        return "Credit";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        CreditInvoiceItem that = (CreditInvoiceItem) o;
+
+        if (accountId.compareTo(that.accountId) != 0) return false;
+        if (amount.compareTo(that.amount) != 0) return false;
+        if (currency != that.currency) return false;
+        if (startDate.compareTo(that.startDate) != 0) return false;
+        if (endDate.compareTo(that.endDate) != 0) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = accountId.hashCode();
+        result = 31 * result + startDate.hashCode();
+        result = 31 * result + endDate.hashCode();
+        result = 31 * result + amount.hashCode();
+        result = 31 * result + currency.hashCode();
+        return result;
+    }
+
+    @Override
+    public int compareTo(InvoiceItem item) {
+        if (!(item instanceof CreditInvoiceItem)) {
+            return 1;
+        }
+
+        CreditInvoiceItem that = (CreditInvoiceItem) item;
+        return id.compareTo(that.getId());
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
index 627a7be..0e19115 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
@@ -19,8 +19,10 @@ package com.ning.billing.invoice.model;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
@@ -59,9 +61,9 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
     */
     @Override
     public Invoice generateInvoice(final UUID accountId, @Nullable final BillingEventSet events,
-                                   @Nullable final List<Invoice> existingInvoices,
-                                   DateTime targetDate,
-                                   final Currency targetCurrency) throws InvoiceApiException {
+                                         @Nullable final List<Invoice> existingInvoices,
+                                         DateTime targetDate,
+                                         final Currency targetCurrency) throws InvoiceApiException {
         if ((events == null) || (events.size() == 0)) {
             return null;
         }
@@ -91,18 +93,90 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         for (InvoiceItem existingItem : existingItems) {
             if (existingItem instanceof RecurringInvoiceItem) {
                 RecurringInvoiceItem recurringItem = (RecurringInvoiceItem) existingItem;
-                proposedItems.add(recurringItem.asCredit());
+                proposedItems.add(recurringItem.asReversingItem());
             }
         }
 
+        addCreditItems(accountId, proposedItems, existingInvoices, targetCurrency);
+
         if (proposedItems == null || proposedItems.size() == 0) {
             return null;
         } else {
             invoice.addInvoiceItems(proposedItems);
+
             return invoice;
         }
     }
 
+   /*
+    * ensures that the balance of all invoices are zero or positive, adding an adjusting credit item if needed
+    */
+    private void addCreditItems(UUID accountId, List<InvoiceItem> invoiceItems, List<Invoice> invoices, Currency currency) {
+        Map<UUID, BigDecimal> invoiceBalances = new HashMap<UUID, BigDecimal>();
+
+        updateInvoiceBalance(invoiceItems, invoiceBalances);
+
+        // add all existing items and payments
+        for (Invoice invoice : invoices) {
+            updateInvoiceBalance(invoice.getInvoiceItems(), invoiceBalances);
+        }
+
+        for (Invoice invoice : invoices) {
+            UUID invoiceId = invoice.getId();
+            invoiceBalances.put(invoiceId, invoiceBalances.get(invoiceId).subtract(invoice.getAmountPaid()));
+        }
+
+        BigDecimal creditTotal = BigDecimal.ZERO;
+
+        for (UUID invoiceId : invoiceBalances.keySet()) {
+            BigDecimal balance = invoiceBalances.get(invoiceId);
+            if (balance.compareTo(BigDecimal.ZERO) < 0) {
+                creditTotal = creditTotal.add(balance.negate());
+                invoiceItems.add(new CreditInvoiceItem(invoiceId, accountId, clock.getUTCNow(), balance, currency));
+            }
+        }
+
+        if (creditTotal.compareTo(BigDecimal.ZERO) != 0) {
+            // create a single credit item to cover all credits
+            //invoiceItems.add(new CreditInvoiceItem());
+        }
+    }
+
+    private void updateInvoiceBalance(List<InvoiceItem> items, Map<UUID, BigDecimal> invoiceBalances) {
+        for (InvoiceItem item : items) {
+            UUID invoiceId = item.getInvoiceId();
+
+            if (!invoiceBalances.containsKey(invoiceId)) {
+                invoiceBalances.put(invoiceId, BigDecimal.ZERO);
+            }
+
+            invoiceBalances.put(invoiceId, invoiceBalances.get(invoiceId).add(item.getAmount()));
+        }
+    }
+
+    @Override
+    public void distributeItems(List<Invoice> invoices) {
+        Map<UUID, Invoice> invoiceMap = new HashMap<UUID, Invoice>();
+
+        for (Invoice invoice : invoices) {
+            invoiceMap.put(invoice.getId(), invoice);
+        }
+
+        for (final Invoice invoice: invoices) {
+            Iterator<InvoiceItem> itemIterator = invoice.getInvoiceItems().iterator();
+            final UUID invoiceId = invoice.getId();
+
+            while (itemIterator.hasNext()) {
+                InvoiceItem item = itemIterator.next();
+
+                if (!item.getInvoiceId().equals(invoiceId)) {
+                    invoiceMap.get(item.getInvoiceId()).addInvoiceItem(item);
+                    itemIterator.remove();
+                }
+            }
+        }
+    }
+
     private void validateTargetDate(DateTime targetDate) throws InvoiceApiException {
         int maximumNumberOfMonths = config.getNumberOfMonthsInFuture();
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
index 8c2398b..72566df 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
@@ -38,7 +38,7 @@ public class FixedPriceInvoiceItem extends InvoiceItemBase {
     }
 
     @Override
-    public InvoiceItem asCredit() {
+    public InvoiceItem asReversingItem() {
         throw new UnsupportedOperationException();
     }
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
index 01ebf34..f903087 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
@@ -19,6 +19,7 @@ package com.ning.billing.invoice.model;
 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 org.joda.time.DateTime;
 
 import javax.annotation.Nullable;
@@ -26,5 +27,8 @@ import java.util.List;
 import java.util.UUID;
 
 public interface InvoiceGenerator {
-    public Invoice generateInvoice(UUID accountId, @Nullable BillingEventSet events, @Nullable List<Invoice> existingInvoices, DateTime targetDate, Currency targetCurrency) throws InvoiceApiException;
+    public Invoice generateInvoice(UUID accountId, @Nullable BillingEventSet events, @Nullable List<Invoice> existingInvoices,
+                                   DateTime targetDate, Currency targetCurrency) throws InvoiceApiException;
+
+    public void distributeItems(List<Invoice> invoices);
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemBase.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemBase.java
index 175a08e..f659807 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemBase.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemBase.java
@@ -119,7 +119,7 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem 
     }
 
     @Override
-    public abstract InvoiceItem asCredit();
+    public abstract InvoiceItem asReversingItem();
 
     @Override
     public abstract String getDescription();
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
index 683a610..6fd9608 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
@@ -67,7 +67,7 @@ public class RecurringInvoiceItem extends InvoiceItemBase {
     }
 
     @Override
-    public InvoiceItem asCredit() {
+    public InvoiceItem asReversingItem() {
         BigDecimal amountNegated = amount == null ? null : amount.negate();
 
         return new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate,
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
index ae0aa9f..2fad805 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
@@ -38,6 +38,7 @@ import com.ning.billing.invoice.api.InvoicePayment;
 import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.tag.ControlTagType;
 import com.ning.billing.util.template.translation.TranslatorConfig;
 import com.ning.billing.util.tag.Tag;
 import com.ning.billing.util.tag.TagDefinition;
@@ -239,8 +240,13 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
     }
 
     @Override
-    public boolean hasTag(String tagName) {
-        return invoice.hasTag(tagName);
+    public boolean hasTag(TagDefinition tagDefinition) {
+        return invoice.hasTag(tagDefinition);
+    }
+
+    @Override
+    public boolean hasTag(ControlTagType controlTagType) {
+        return invoice.hasTag(controlTagType);
     }
 
     @Override
@@ -254,6 +260,11 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
     }
 
     @Override
+    public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+        invoice.addTagsFromDefinitions(tagDefinitions);
+    }
+
+    @Override
     public void clearTags() {
         invoice.clearTags();
     }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
index 4ca8ccf..cc072d9 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
@@ -55,8 +55,8 @@ public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
     }
 
     @Override
-    public InvoiceItem asCredit() {
-        return item.asCredit();
+    public InvoiceItem asReversingItem() {
+        return item.asReversingItem();
     }
 
     @Override
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
index d5edd6e..bb8313a 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
@@ -653,7 +653,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         invoiceDao.addControlTag(ControlTagType.WRITTEN_OFF, invoice.getId(), context);
 
         Invoice savedInvoice = invoiceDao.getById(invoice.getId());
-        assertTrue(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF.toString()));
+        assertTrue(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF));
     }
 
     @Test
@@ -683,10 +683,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         invoiceDao.addControlTag(ControlTagType.WRITTEN_OFF, invoice.getId(), context);
 
         Invoice savedInvoice = invoiceDao.getById(invoice.getId());
-        assertTrue(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF.toString()));
+        assertTrue(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF));
 
         invoiceDao.removeControlTag(ControlTagType.WRITTEN_OFF, invoice.getId(), context);
         savedInvoice = invoiceDao.getById(invoice.getId());
-        assertFalse(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF.toString()));
+        assertFalse(savedInvoice.hasTag(ControlTagType.WRITTEN_OFF));
     }
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
index 1f446d0..b4d9789 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
@@ -22,11 +22,14 @@ import static org.testng.Assert.assertNull;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
 
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
 
@@ -719,5 +722,116 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         assertEquals(invoice.getTotalAmount(), expectedAmount);
     }
 
+    @Test(groups = {"fast", "invoicing"})
+    public void testAddOnInvoiceGeneration() throws CatalogApiException, InvoiceApiException {
+        DateTime april25 = new DateTime(2012, 4, 25, 0, 0, 0, 0);
+
+        // create a base plan on April 25th
+        UUID accountId = UUID.randomUUID();
+        Subscription baseSubscription = createZombieSubscription();
+
+        Plan basePlan = new MockPlan("base Plan");
+        MockInternationalPrice price5 = new MockInternationalPrice(new DefaultPrice(FIVE, Currency.USD));
+        MockInternationalPrice price10 = new MockInternationalPrice(new DefaultPrice(TEN, Currency.USD));
+        MockInternationalPrice price20 = new MockInternationalPrice(new DefaultPrice(TWENTY, Currency.USD));
+        PlanPhase basePlanEvergreen = new MockPlanPhase(price10, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+
+        BillingEventSet events = new BillingEventSet();
+        events.add(createBillingEvent(baseSubscription.getId(), april25, basePlan, basePlanEvergreen, 25));
+
+        // generate invoice
+        Invoice invoice1 = generator.generateInvoice(accountId, events, null, april25, Currency.USD);
+        assertNotNull(invoice1);
+        assertEquals(invoice1.getNumberOfItems(), 1);
+        assertEquals(invoice1.getTotalAmount().compareTo(TEN), 0);
+
+        List<Invoice> invoices = new ArrayList<Invoice>();
+        invoices.add(invoice1);
+
+        // create 2 add ons on April 28th
+        DateTime april28 = new DateTime(2012, 4, 28, 0, 0, 0, 0);
+        Subscription addOnSubscription1 = createZombieSubscription();
+        Plan addOn1Plan = new MockPlan("add on 1");
+        PlanPhase addOn1PlanPhaseEvergreen = new MockPlanPhase(price5, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+        events.add(createBillingEvent(addOnSubscription1.getId(), april28, addOn1Plan, addOn1PlanPhaseEvergreen, 25));
+
+        Subscription addOnSubscription2 = createZombieSubscription();
+        Plan addOn2Plan = new MockPlan("add on 2");
+        PlanPhase addOn2PlanPhaseEvergreen = new MockPlanPhase(price20, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+        events.add(createBillingEvent(addOnSubscription2.getId(), april28, addOn2Plan, addOn2PlanPhaseEvergreen, 25));
+
+        // generate invoice
+        Invoice invoice2 = generator.generateInvoice(accountId, events, invoices, april28, Currency.USD);
+        invoices.add(invoice2);
+        assertNotNull(invoice2);
+        assertEquals(invoice2.getNumberOfItems(), 2);
+        assertEquals(invoice2.getTotalAmount().compareTo(TWENTY_FIVE.multiply(new BigDecimal("0.9")).setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD)), 0);
+
+        // perform a repair (change base plan; remove one add-on)
+        // event stream should include just two plans
+        BillingEventSet newEvents = new BillingEventSet();
+        Plan basePlan2 = new MockPlan("base plan 2");
+        MockInternationalPrice price13 = new MockInternationalPrice(new DefaultPrice(THIRTEEN, Currency.USD));
+        PlanPhase basePlan2Phase = new MockPlanPhase(price13, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+        newEvents.add(createBillingEvent(baseSubscription.getId(), april25, basePlan2, basePlan2Phase, 25));
+        newEvents.add(createBillingEvent(addOnSubscription1.getId(), april28, addOn1Plan, addOn1PlanPhaseEvergreen, 25));
+
+        // generate invoice
+        DateTime may1 = new DateTime(2012, 5, 1, 0, 0, 0, 0);
+        Invoice invoice3 = generator.generateInvoice(accountId, newEvents, invoices, may1, Currency.USD);
+        assertNotNull(invoice3);
+        assertEquals(invoice3.getNumberOfItems(), 5);
+        // -4.50 -18 - 10 (to correct the previous 2 invoices) + 4.50 + 13
+        assertEquals(invoice3.getTotalAmount().compareTo(FIFTEEN.negate()), 0);
+    }
+
+    @Test
+    public void testRepairForPaidInvoice() throws CatalogApiException, InvoiceApiException {
+        // create an invoice
+        DateTime april25 = new DateTime(2012, 4, 25, 0, 0, 0, 0);
+
+        // create a base plan on April 25th
+        UUID accountId = UUID.randomUUID();
+        Subscription originalSubscription = createZombieSubscription();
+
+        Plan originalPlan = new MockPlan("original plan");
+        MockInternationalPrice price10 = new MockInternationalPrice(new DefaultPrice(TEN, Currency.USD));
+        PlanPhase originalPlanEvergreen = new MockPlanPhase(price10, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+
+        BillingEventSet events = new BillingEventSet();
+        events.add(createBillingEvent(originalSubscription.getId(), april25, originalPlan, originalPlanEvergreen, 25));
+
+        Invoice invoice1 = generator.generateInvoice(accountId, events, null, april25, Currency.USD);
+        List<Invoice> invoices = new ArrayList<Invoice>();
+        invoices.add(invoice1);
+
+        // pay the invoice
+        invoice1.addPayment(new DefaultInvoicePayment(UUID.randomUUID(), invoice1.getId(), april25, TEN, Currency.USD));
+        assertEquals(invoice1.getBalance().compareTo(ZERO), 0);
+
+        // change the plan (i.e. repair) on start date
+        events.clear();
+        Subscription newSubscription = createZombieSubscription();
+        Plan newPlan = new MockPlan("new plan");
+        MockInternationalPrice price5 = new MockInternationalPrice(new DefaultPrice(FIVE, Currency.USD));
+        PlanPhase newPlanEvergreen = new MockPlanPhase(price5, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
+        events.add(createBillingEvent(newSubscription.getId(), april25, newPlan, newPlanEvergreen, 25));
+
+        // generate a new invoice
+        Invoice invoice2 = generator.generateInvoice(accountId, events, invoices, april25, Currency.USD);
+        invoices.add(invoice2);
+
+        // move items to the correct invoice (normally, the dao calls will sort that out)
+        generator.distributeItems(invoices);
+
+        // ensure that the original invoice balance is zero
+
+        assertEquals(invoice1.getBalance().compareTo(ZERO), 0);
+
+        // ensure that the account balance is correct
+        assertEquals(invoice2.getBalance().compareTo(FIVE.negate()), 0);
+
+    }
+
     // TODO: Jeff C -- how do we ensure that an annual add-on is properly aligned *at the end* with the base plan?
 }
\ No newline at end of file
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
index 132ac1c..25be5e2 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
@@ -43,7 +43,7 @@ public abstract class InvoicingTestBase {
     protected static final BigDecimal ONE_AND_A_HALF = new BigDecimal("1.5").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal TWO = new BigDecimal("2.0").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal THREE = new BigDecimal("3.0").setScale(NUMBER_OF_DECIMALS);
-    //protected static final BigDecimal FOUR = new BigDecimal("4.0").setScale(NUMBER_OF_DECIMALS);
+    protected static final BigDecimal FOUR = new BigDecimal("4.0").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal FIVE = new BigDecimal("5.0").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal SIX = new BigDecimal("6.0").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal SEVEN = new BigDecimal("7.0").setScale(NUMBER_OF_DECIMALS);
@@ -60,6 +60,7 @@ public abstract class InvoicingTestBase {
     protected static final BigDecimal TWENTY = new BigDecimal("20.0").setScale(NUMBER_OF_DECIMALS);
 
     protected static final BigDecimal TWENTY_FOUR = new BigDecimal("24.0").setScale(NUMBER_OF_DECIMALS);
+    protected static final BigDecimal TWENTY_FIVE = new BigDecimal("25.0").setScale(NUMBER_OF_DECIMALS);
 
     protected static final BigDecimal TWENTY_EIGHT = new BigDecimal("28.0").setScale(NUMBER_OF_DECIMALS);
     protected static final BigDecimal TWENTY_NINE = new BigDecimal("29.0").setScale(NUMBER_OF_DECIMALS);
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java
index 71231b5..3b35a0c 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/api/BlockingAccountUserApi.java
@@ -29,10 +29,9 @@ import com.ning.billing.account.api.MigrationAccountData;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.glue.RealImplementation;
-import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
 
 public class BlockingAccountUserApi implements AccountUserApi {
-    
     private AccountUserApi userApi;
 
     @Inject
@@ -41,15 +40,15 @@ public class BlockingAccountUserApi implements AccountUserApi {
     }
 
     @Override
-    public Account createAccount(AccountData data, List<CustomField> fields, List<Tag> tags, CallContext context)
+    public Account createAccount(AccountData data, List<CustomField> fields, List<TagDefinition> tagDefinitions, CallContext context)
             throws AccountApiException {
-        return userApi.createAccount(data, fields, tags, context);
+        return userApi.createAccount(data, fields, tagDefinitions, context);
     }
 
     @Override
-    public Account migrateAccount(MigrationAccountData data, List<CustomField> fields, List<Tag> tags,
+    public Account migrateAccount(MigrationAccountData data, List<CustomField> fields, List<TagDefinition> tagDefinitions,
             CallContext context) throws AccountApiException {
-        return userApi.migrateAccount(data, fields, tags, context);
+        return userApi.migrateAccount(data, fields, tagDefinitions, context);
     }
 
     @Override
diff --git a/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueState.java b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueState.java
index 1d76d72..7d84e7c 100644
--- a/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueState.java
+++ b/overdue/src/main/java/com/ning/billing/overdue/config/DefaultOverdueState.java
@@ -58,7 +58,7 @@ public class DefaultOverdueState<T extends Blockable> extends ValidatingConfig<O
 	//Other actions could include
 	// - send email
 	// - trigger payment retry?
-	// - add tags to bundle/account
+	// - add tagStore to bundle/account
 	// - set payment failure email template
 	// - set payment retry interval
 	// - backup payment mechanism?
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java
index b7d2de0..e319117 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java
@@ -45,7 +45,7 @@ public class AuditedCustomFieldDao implements CustomFieldDao {
             while (existingFieldIterator.hasNext()) {
                 CustomField existingField = existingFieldIterator.next();
                 if (field.getName().equals(existingField.getName())) {
-                    // if the tags match, remove from both lists
+                    // if the tagStore match, remove from both lists
                     fieldsToUpdate.add(field);
                     fieldIterator.remove();
                     existingFieldIterator.remove();
diff --git a/util/src/main/java/com/ning/billing/util/entity/ExtendedEntityBase.java b/util/src/main/java/com/ning/billing/util/entity/ExtendedEntityBase.java
index 0017397..2bb03ca 100644
--- a/util/src/main/java/com/ning/billing/util/entity/ExtendedEntityBase.java
+++ b/util/src/main/java/com/ning/billing/util/entity/ExtendedEntityBase.java
@@ -21,6 +21,8 @@ import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.customfield.Customizable;
 import com.ning.billing.util.customfield.DefaultFieldStore;
 import com.ning.billing.util.customfield.FieldStore;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.tag.DefaultControlTag;
 import com.ning.billing.util.tag.DefaultTagStore;
 import com.ning.billing.util.tag.DescriptiveTag;
 import com.ning.billing.util.tag.Tag;
@@ -30,23 +32,24 @@ import com.ning.billing.util.tag.Taggable;
 import org.joda.time.DateTime;
 
 import javax.annotation.Nullable;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
 public abstract class ExtendedEntityBase extends EntityBase implements Customizable, Taggable {
     protected final FieldStore fields;
-    protected final TagStore tags;
+    protected final TagStore tagStore;
 
     public ExtendedEntityBase() {
         super();
         this.fields = DefaultFieldStore.create(getId(), getObjectName());
-        this.tags = new DefaultTagStore(id, getObjectName());
+        this.tagStore = new DefaultTagStore(id, getObjectName());
     }
 
     public ExtendedEntityBase(final UUID id, @Nullable final String createdBy, @Nullable final DateTime createdDate) {
         super(id, createdBy, createdDate);
         this.fields = DefaultFieldStore.create(getId(), getObjectName());
-        this.tags = new DefaultTagStore(id, getObjectName());
+        this.tagStore = new DefaultTagStore(id, getObjectName());
     }
 
     @Override
@@ -78,45 +81,67 @@ public abstract class ExtendedEntityBase extends EntityBase implements Customiza
 
     @Override
 	public List<Tag> getTagList() {
-		return tags.getEntityList();
+		return tagStore.getEntityList();
 	}
 
 	@Override
-	public boolean hasTag(final String tagName) {
-		return tags.containsTag(tagName);
+	public boolean hasTag(final TagDefinition definition) {
+		return tagStore.containsTagForDefinition(definition);
 	}
 
+    @Override
+    public boolean hasTag(ControlTagType controlTagType) {
+        return tagStore.containsTagForControlTagType(controlTagType);
+    }
+
 	@Override
 	public void addTag(final TagDefinition definition) {
 		Tag tag = new DescriptiveTag(definition);
-		tags.add(tag) ;
+		tagStore.add(tag) ;
 	}
 
+    @Override
+    public void addTags(final List<Tag> tags) {
+        this.tagStore.add(tags);
+    }
+
 	@Override
-	public void addTags(final List<Tag> tags) {
-		if (tags != null) {
-			this.tags.add(tags);
+	public void addTagsFromDefinitions(final List<TagDefinition> tagDefinitions) {
+		if (tagStore != null) {
+            List<Tag> tags = new ArrayList<Tag>();
+            if (tagDefinitions != null) {
+                for (TagDefinition tagDefinition : tagDefinitions) {
+                    try {
+                        ControlTagType controlTagType = ControlTagType.valueOf(tagDefinition.getName());
+                        tags.add(new DefaultControlTag(controlTagType));
+                    } catch (IllegalArgumentException ex) {
+                        tags.add(new DescriptiveTag(tagDefinition));
+                    }
+                }
+            }
+
+			this.tagStore.add(tags);
 		}
 	}
 
 	@Override
 	public void clearTags() {
-		this.tags.clear();
+		this.tagStore.clear();
 	}
 
 	@Override
-	public void removeTag(final TagDefinition definition) {
-		tags.remove(definition.getName());
+	public void removeTag(final TagDefinition tagDefinition) {
+		tagStore.remove(tagDefinition);
 	}
 
 	@Override
 	public boolean generateInvoice() {
-		return tags.generateInvoice();
+		return tagStore.generateInvoice();
 	}
 
 	@Override
 	public boolean processPayment() {
-		return tags.processPayment();
+		return tagStore.processPayment();
 	}
 
     @Override
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java
index 9798250..3b238c0 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java
@@ -52,10 +52,10 @@ public class AuditedTagDao extends AuditedDaoBase implements TagDao {
                                         final List<Tag> tags, final CallContext context) {
         TagSqlDao tagSqlDao = dao.become(TagSqlDao.class);
 
-        // get list of existing tags
+        // get list of existing tagStore
         List<Tag> existingTags = tagSqlDao.load(objectId.toString(), objectType);
 
-        // sort into tags to update (tagsToUpdate), tags to add (tags), and tags to delete (existingTags)
+        // sort into tagStore to update (tagsToUpdate), tagStore to add (tagStore), and tagStore to delete (existingTags)
         Iterator<Tag> tagIterator = tags.iterator();
         while (tagIterator.hasNext()) {
             Tag tag = tagIterator.next();
@@ -64,7 +64,7 @@ public class AuditedTagDao extends AuditedDaoBase implements TagDao {
             while (existingTagIterator.hasNext()) {
                 Tag existingTag = existingTagIterator.next();
                 if (tag.getTagDefinitionName().equals(existingTag.getTagDefinitionName())) {
-                    // if the tags match, remove from both lists
+                    // if the tagStore match, remove from both lists
                     // in the case of tag, this just means the tag remains associated
                     tagIterator.remove();
                     existingTagIterator.remove();
diff --git a/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java b/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java
index eace017..aa9c3af 100644
--- a/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java
+++ b/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java
@@ -32,7 +32,7 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
     @Override
     /***
      * Collates the contents of the TagStore to determine if payments should be processed
-     * @return true is no tags contraindicate payment processing
+     * @return true if no tags contraindicate payment processing
      */
     public boolean processPayment() {
         for (Tag tag : entities.values()) {
@@ -49,7 +49,7 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
 
     /***
      * Collates the contents of the TagStore to determine if invoices should be generated
-     * @return true is no tags contraindicate invoice generation
+     * @return true if no tags contraindicate invoice generation
      */
     @Override
     public boolean generateInvoice() {
@@ -66,18 +66,30 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
     }
 
     @Override
-    public void remove(final String tagName) {
-        entities.remove(entities.get(tagName));
+    public boolean containsTagForDefinition(final TagDefinition tagDefinition) {
+        for (Tag tag : entities.values()) {
+            if (tag.getTagDefinitionName().equals(tagDefinition.getName())) {
+                return true;
+            }
+        }
+
+        return false;
     }
 
     @Override
-    public boolean containsTag(final String tagName) {
+    public boolean containsTagForControlTagType(final ControlTagType controlTagType) {
         for (Tag tag : entities.values()) {
-            if (tag.getTagDefinitionName().equals(tagName)) {
+            if (tag.getTagDefinitionName().equals(controlTagType.toString())) {
                 return true;
             }
         }
 
         return false;
     }
+
+    @Override
+    public Tag remove(TagDefinition tagDefinition) {
+        Tag tag = entities.get(tagDefinition.getName());
+        return (tag == null) ? null : entities.remove(tag);
+    }
 }
\ No newline at end of file
diff --git a/util/src/main/resources/com/ning/billing/util/ddl.sql b/util/src/main/resources/com/ning/billing/util/ddl.sql
index e5e9488..4d91d1c 100644
--- a/util/src/main/resources/com/ning/billing/util/ddl.sql
+++ b/util/src/main/resources/com/ning/billing/util/ddl.sql
@@ -119,5 +119,7 @@ CREATE TABLE audit_log (
     user_token char(36),
     PRIMARY KEY(id)
 ) ENGINE=innodb;
+CREATE INDEX audit_log_fetch_record ON audit_log(table_name, record_id);
+CREATE INDEX audit_log_user_name ON audit_log(changed_by);
 
 
diff --git a/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
index e29023f..0e5041a 100644
--- a/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
+++ b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
@@ -115,7 +115,7 @@ public class TestTagStore {
                 public Void withHandle(Handle handle) throws Exception {
                     handle.createScript("delete from tag_definitions").execute();
                     handle.createScript("delete from tag_definition_history").execute();
-                    handle.createScript("delete from tags").execute();
+                    handle.createScript("delete from tagStore").execute();
                     handle.createScript("delete from tag_history").execute();
                     return null;
                 }
@@ -309,7 +309,7 @@ public class TestTagStore {
         try {
             tagDefinitionDao.deleteAllTagsForDefinition(definitionName, context);
         } catch (TagDefinitionApiException e) {
-            fail("Could not delete tags for tag definition", e);
+            fail("Could not delete tagStore for tag definition", e);
         }
 
         try {
@@ -334,7 +334,7 @@ public class TestTagStore {
         try {
             tagDefinitionDao.deleteAllTagsForDefinition(definitionName, context);
         } catch (TagDefinitionApiException e) {
-            fail("Could not delete tags for tag definition", e);
+            fail("Could not delete tagStore for tag definition", e);
         }
 
         try {