killbill-aplcache

osgi: initial import of Analytics bundle Fast tests pass,

3/29/2013 2:31:21 PM

Changes

Details

diff --git a/osgi-bundles/bundles/analytics/pom.xml b/osgi-bundles/bundles/analytics/pom.xml
new file mode 100644
index 0000000..6b2da9c
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/pom.xml
@@ -0,0 +1,242 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2010-2013 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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.ning.billing</groupId>
+        <artifactId>killbill-osgi-bundles</artifactId>
+        <version>0.1.62-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <artifactId>killbill-osgi-bundles-analytics</artifactId>
+    <name>Killbill billing platform: OSGI Analytics bundle</name>
+    <packaging>bundle</packaging>
+    <dependencies>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject</groupId>
+            <artifactId>guice</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-osgi-bundles-lib-killbill</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.antlr</groupId>
+            <artifactId>stringtemplate</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jdbi</groupId>
+            <artifactId>jdbi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-account</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-catalog</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-catalog</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-entitlement</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-entitlement</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-junction</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-junction</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-invoice</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-payment</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.jayway.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-mxj</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-mxj-db-files</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>jar</goal>
+                            <goal>test-jar</goal>
+                        </goals>
+                        <configuration>
+                            <archive>
+                                <!-- use the manifest file created by the bundle plugin -->
+                                <!--<useDefaultManifestFile>true</useDefaultManifestFile>-->
+                                <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+                                <!-- bundle plugin already generated the maven descriptor -->
+                                <addMavenDescriptor>false</addMavenDescriptor>
+                            </archive>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-Activator>com.ning.billing.osgi.bundles.analytics.AnalyticsActivator</Bundle-Activator>
+                        <Export-Package/>
+                        <Private-Package>com.ning.billing.osgi.bundles.analytics.*</Private-Package>
+                        <!-- Optional resolution because exported by the Felix system bundle -->
+                        <Import-Package>*;resolution:=optional</Import-Package>
+                    </instructions>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>process-classes</phase>
+                        <goals>
+                            <goal>manifest</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>assemble-killbill-osgi-bundles-analytics</id>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <phase>package</phase>
+                        <configuration>
+                            <createSourcesJar>true</createSourcesJar>
+                            <shadedArtifactAttached>false</shadedArtifactAttached>
+                            <shadedClassifierName>jar-with-dependencies</shadedClassifierName>
+                            <filters>
+                                <filter>
+                                    <artifact>${project.groupId}:${project.artifactId}</artifact>
+                                </filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsActivator.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsActivator.java
new file mode 100644
index 0000000..1510ba8
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsActivator.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import org.osgi.framework.BundleContext;
+import org.skife.config.SimplePropertyConfigSource;
+
+import com.ning.billing.osgi.bundles.analytics.setup.AnalyticsModule;
+import com.ning.killbill.osgi.libs.killbill.KillbillActivatorBase;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillEventDispatcher.OSGIKillbillEventHandler;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+public class AnalyticsActivator extends KillbillActivatorBase {
+
+    private OSGIKillbillEventHandler analyticsListener;
+
+    @Override
+    public void start(final BundleContext context) throws Exception {
+        super.start(context);
+
+        final SimplePropertyConfigSource configSource = new SimplePropertyConfigSource(System.getProperties());
+        final Injector injector = Guice.createInjector(new AnalyticsModule(configSource));
+        analyticsListener = injector.getInstance(AnalyticsListener.class);
+    }
+
+    @Override
+    public OSGIKillbillEventHandler getOSGIKillbillEventHandler() {
+        return analyticsListener;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsListener.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsListener.java
new file mode 100644
index 0000000..a62c243
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsListener.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import java.util.concurrent.Callable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.beatrix.bus.api.ExtBusEvent;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.events.AccountChangeInternalEvent;
+import com.ning.billing.util.events.AccountCreationInternalEvent;
+import com.ning.billing.util.events.BusInternalEvent;
+import com.ning.billing.util.events.ControlTagCreationInternalEvent;
+import com.ning.billing.util.events.ControlTagDefinitionCreationInternalEvent;
+import com.ning.billing.util.events.ControlTagDefinitionDeletionInternalEvent;
+import com.ning.billing.util.events.ControlTagDeletionInternalEvent;
+import com.ning.billing.util.events.EffectiveSubscriptionInternalEvent;
+import com.ning.billing.util.events.InvoiceAdjustmentInternalEvent;
+import com.ning.billing.util.events.InvoiceCreationInternalEvent;
+import com.ning.billing.util.events.NullInvoiceInternalEvent;
+import com.ning.billing.util.events.OverdueChangeInternalEvent;
+import com.ning.billing.util.events.PaymentErrorInternalEvent;
+import com.ning.billing.util.events.PaymentInfoInternalEvent;
+import com.ning.billing.util.events.RepairEntitlementInternalEvent;
+import com.ning.billing.util.events.RequestedSubscriptionInternalEvent;
+import com.ning.billing.util.events.UserTagCreationInternalEvent;
+import com.ning.billing.util.events.UserTagDefinitionCreationInternalEvent;
+import com.ning.billing.util.events.UserTagDefinitionDeletionInternalEvent;
+import com.ning.billing.util.events.UserTagDeletionInternalEvent;
+import com.ning.billing.util.globallocker.GlobalLock;
+import com.ning.billing.util.globallocker.GlobalLocker;
+import com.ning.billing.util.globallocker.GlobalLocker.LockerType;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillEventDispatcher.OSGIKillbillEventHandler;
+
+import com.google.inject.Inject;
+
+public class AnalyticsListener implements OSGIKillbillEventHandler {
+
+    private static final Logger log = LoggerFactory.getLogger(AnalyticsListener.class);
+    private static final int NB_LOCK_TRY = 5;
+
+    private final BusinessSubscriptionTransitionDao bstDao;
+    private final BusinessAccountDao bacDao;
+    private final BusinessInvoiceDao invoiceDao;
+    private final BusinessOverdueStatusDao bosDao;
+    private final BusinessInvoicePaymentDao bipDao;
+    private final BusinessTagDao tagDao;
+    private final GlobalLocker locker;
+    private final InternalCallContextFactory internalCallContextFactory;
+
+    @Inject
+    public AnalyticsListener(final BusinessSubscriptionTransitionDao bstDao,
+                             final BusinessAccountDao bacDao,
+                             final BusinessInvoiceDao invoiceDao,
+                             final BusinessOverdueStatusDao bosDao,
+                             final BusinessInvoicePaymentDao bipDao,
+                             final BusinessTagDao tagDao,
+                             final GlobalLocker locker,
+                             final InternalCallContextFactory internalCallContextFactory) {
+        this.bstDao = bstDao;
+        this.bacDao = bacDao;
+        this.invoiceDao = invoiceDao;
+        this.bosDao = bosDao;
+        this.bipDao = bipDao;
+        this.tagDao = tagDao;
+        this.locker = locker;
+        this.internalCallContextFactory = internalCallContextFactory;
+    }
+
+    @Override
+    public void handleKillbillEvent(final ExtBusEvent killbillEvent) {
+        // TODO
+    }
+
+    private void handleEffectiveSubscriptionTransitionChange(final EffectiveSubscriptionInternalEvent eventEffective) {
+        updateWithAccountLock(eventEffective.getAccountRecordId(), new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                // The event is used as a trigger to rebuild all transitions for this bundle
+                bstDao.rebuildTransitionsForBundle(eventEffective.getBundleId(), createCallContext(eventEffective));
+                return null;
+            }
+        });
+    }
+
+    private void handleRequestedSubscriptionTransitionChange(final RequestedSubscriptionInternalEvent eventRequested) {
+        updateWithAccountLock(eventRequested.getAccountRecordId(), new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                // The event is used as a trigger to rebuild all transitions for this bundle
+                bstDao.rebuildTransitionsForBundle(eventRequested.getBundleId(), createCallContext(eventRequested));
+                return null;
+            }
+        });
+    }
+
+    private void handleRepairEntitlement(final RepairEntitlementInternalEvent event) {
+        updateWithAccountLock(event.getAccountRecordId(), new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                // In case of repair, just rebuild all transitions
+                bstDao.rebuildTransitionsForBundle(event.getBundleId(), createCallContext(event));
+                return null;
+            }
+        });
+    }
+
+    private void handleAccountCreation(final AccountCreationInternalEvent event) {
+        updateWithAccountLock(event.getAccountRecordId(), new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                bacDao.accountUpdated(event.getId(), createCallContext(event));
+                return null;
+            }
+        });
+    }
+
+    private void handleAccountChange(final AccountChangeInternalEvent event) {
+        if (!event.hasChanges()) {
+            return;
+        }
+
+        updateWithAccountLock(event.getAccountRecordId(), new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                bacDao.accountUpdated(event.getAccountId(), createCallContext(event));
+                return null;
+            }
+        });
+    }
+
+    private void handleInvoiceCreation(final InvoiceCreationInternalEvent event) {
+        updateWithAccountLock(event.getAccountRecordId(), new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                // The event is used as a trigger to rebuild all invoices and invoice items for this account
+                invoiceDao.rebuildInvoicesForAccount(event.getAccountId(), createCallContext(event));
+                return null;
+            }
+        });
+    }
+
+    private void handleNullInvoice(final NullInvoiceInternalEvent event) {
+        // Ignored for now
+    }
+
+    private void handleInvoiceAdjustment(final InvoiceAdjustmentInternalEvent event) {
+        updateWithAccountLock(event.getAccountRecordId(), new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                // The event is used as a trigger to rebuild all invoices and invoice items for this account
+                invoiceDao.rebuildInvoicesForAccount(event.getAccountId(), createCallContext(event));
+                return null;
+            }
+        });
+    }
+
+    private void handlePaymentInfo(final PaymentInfoInternalEvent paymentInfo) {
+        updateWithAccountLock(paymentInfo.getAccountRecordId(), new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                bipDao.invoicePaymentPosted(paymentInfo.getAccountId(),
+                                            paymentInfo.getPaymentId(),
+                                            paymentInfo.getStatus().toString(),
+                                            createCallContext(paymentInfo));
+                return null;
+            }
+        });
+    }
+
+    private void handlePaymentError(final PaymentErrorInternalEvent paymentError) {
+        updateWithAccountLock(paymentError.getAccountRecordId(), new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                bipDao.invoicePaymentPosted(paymentError.getAccountId(),
+                                            paymentError.getPaymentId(),
+                                            paymentError.getMessage(),
+                                            createCallContext(paymentError));
+                return null;
+            }
+        });
+    }
+
+    private void handleOverdueChange(final OverdueChangeInternalEvent changeEvent) {
+        updateWithAccountLock(changeEvent.getAccountRecordId(), new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                bosDao.overdueStatusChanged(changeEvent.getOverdueObjectType(), changeEvent.getOverdueObjectId(), createCallContext(changeEvent));
+                return null;
+            }
+        });
+    }
+
+    private void handleControlTagCreation(final ControlTagCreationInternalEvent event) {
+        updateWithAccountLock(event.getAccountRecordId(), new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                tagDao.tagAdded(event.getObjectType(), event.getObjectId(), event.getTagDefinition().getName(), createCallContext(event));
+                return null;
+            }
+        });
+    }
+
+    private void handleControlTagDeletion(final ControlTagDeletionInternalEvent event) {
+        updateWithAccountLock(event.getAccountRecordId(), new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                tagDao.tagRemoved(event.getObjectType(), event.getObjectId(), event.getTagDefinition().getName(), createCallContext(event));
+                return null;
+            }
+        });
+    }
+
+    private void handleUserTagCreation(final UserTagCreationInternalEvent event) {
+        updateWithAccountLock(event.getAccountRecordId(), new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                tagDao.tagAdded(event.getObjectType(), event.getObjectId(), event.getTagDefinition().getName(), createCallContext(event));
+                return null;
+            }
+        });
+    }
+
+    private void handleUserTagDeletion(final UserTagDeletionInternalEvent event) {
+        updateWithAccountLock(event.getAccountRecordId(), new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                tagDao.tagRemoved(event.getObjectType(), event.getObjectId(), event.getTagDefinition().getName(), createCallContext(event));
+                return null;
+            }
+        });
+    }
+
+    private void handleControlTagDefinitionCreation(final ControlTagDefinitionCreationInternalEvent event) {
+        // Ignored for now
+    }
+
+    private void handleControlTagDefinitionDeletion(final ControlTagDefinitionDeletionInternalEvent event) {
+        // Ignored for now
+    }
+
+    private void handleUserTagDefinitionCreation(final UserTagDefinitionCreationInternalEvent event) {
+        // Ignored for now
+    }
+
+    private void handleUserTagDefinitionDeletion(final UserTagDefinitionDeletionInternalEvent event) {
+        // Ignored for now
+    }
+
+    private InternalCallContext createCallContext(final BusInternalEvent event) {
+        return internalCallContextFactory.createInternalCallContext(event.getTenantRecordId(), event.getAccountRecordId(),
+                                                                    "AnalyticsService", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
+    }
+
+    private <T> T updateWithAccountLock(final Long accountRecordId, final Callable<T> task) {
+        GlobalLock lock = null;
+        try {
+            final String lockKey = accountRecordId == null ? "0" : accountRecordId.toString();
+            lock = locker.lockWithNumberOfTries(LockerType.ACCOUNT_FOR_ANALYTICS, lockKey, NB_LOCK_TRY);
+            return task.call();
+        } catch (Exception e) {
+            log.warn("Exception while refreshing analytics tables", e);
+        } finally {
+            if (lock != null) {
+                lock.release();
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultAnalyticsService.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultAnalyticsService.java
new file mode 100644
index 0000000..0e0b043
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultAnalyticsService.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.api;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.analytics.api.AnalyticsService;
+import com.ning.billing.lifecycle.LifecycleHandlerType;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsListener;
+import com.ning.billing.util.svcsapi.bus.InternalBus;
+
+import com.google.inject.Inject;
+
+public class DefaultAnalyticsService implements AnalyticsService {
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultAnalyticsService.class);
+
+    private static final String ANALYTICS_SERVICE = "analytics-service";
+
+    private final AnalyticsListener listener;
+    private final InternalBus eventBus;
+
+    @Inject
+    public DefaultAnalyticsService(final AnalyticsListener listener, final InternalBus eventBus) {
+        this.listener = listener;
+        this.eventBus = eventBus;
+    }
+
+    @Override
+    public String getName() {
+        return ANALYTICS_SERVICE;
+    }
+
+    @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.REGISTER_EVENTS)
+    public void registerForNotifications() {
+        try {
+            eventBus.register(listener);
+        } catch (InternalBus.EventBusException e) {
+            log.error("Unable to register to the EventBus!", e);
+        }
+    }
+
+    @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.UNREGISTER_EVENTS)
+    public void unregisterForNotifications() {
+        try {
+            eventBus.unregister(listener);
+        } catch (InternalBus.EventBusException e) {
+            throw new RuntimeException("Unable to unregister to the EventBus!", e);
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessAccount.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessAccount.java
new file mode 100644
index 0000000..be0e938
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessAccount.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.api;
+
+import java.math.BigDecimal;
+
+import org.joda.time.LocalDate;
+
+import com.ning.billing.analytics.api.BusinessAccount;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountModelDao;
+import com.ning.billing.util.entity.EntityBase;
+
+public class DefaultBusinessAccount extends EntityBase implements BusinessAccount {
+
+    private final String externalKey;
+    private final String name;
+    private final String currency;
+    private final BigDecimal balance;
+    private final LocalDate lastInvoiceDate;
+    private final BigDecimal totalInvoiceBalance;
+    private final String lastPaymentStatus;
+    private final String defaultPaymentMethodType;
+    private final String defaultCreditCardType;
+    private final String defaultBillingAddressCountry;
+
+    public DefaultBusinessAccount(final BusinessAccountModelDao businessAccountModelDao) {
+        this.externalKey = businessAccountModelDao.getKey();
+        this.name = businessAccountModelDao.getName();
+        this.currency = businessAccountModelDao.getCurrency();
+        this.balance = businessAccountModelDao.getBalance();
+        this.lastInvoiceDate = businessAccountModelDao.getLastInvoiceDate();
+        this.totalInvoiceBalance = businessAccountModelDao.getTotalInvoiceBalance();
+        this.lastPaymentStatus = businessAccountModelDao.getLastPaymentStatus();
+        this.defaultPaymentMethodType = businessAccountModelDao.getPaymentMethod();
+        this.defaultCreditCardType = businessAccountModelDao.getCreditCardType();
+        this.defaultBillingAddressCountry = businessAccountModelDao.getBillingAddressCountry();
+    }
+
+    @Override
+    public String getExternalKey() {
+        return externalKey;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String getCurrency() {
+        return currency;
+    }
+
+    @Override
+    public BigDecimal getBalance() {
+        return balance;
+    }
+
+    @Override
+    public LocalDate getLastInvoiceDate() {
+        return lastInvoiceDate;
+    }
+
+    @Override
+    public BigDecimal getTotalInvoiceBalance() {
+        return totalInvoiceBalance;
+    }
+
+    @Override
+    public String getLastPaymentStatus() {
+        return lastPaymentStatus;
+    }
+
+    @Override
+    public String getDefaultPaymentMethodType() {
+        return defaultPaymentMethodType;
+    }
+
+    @Override
+    public String getDefaultCreditCardType() {
+        return defaultCreditCardType;
+    }
+
+    @Override
+    public String getDefaultBillingAddressCountry() {
+        return defaultBillingAddressCountry;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("DefaultBusinessAccount");
+        sb.append("{externalKey='").append(externalKey).append('\'');
+        sb.append(", name='").append(name).append('\'');
+        sb.append(", currency='").append(currency).append('\'');
+        sb.append(", balance=").append(balance);
+        sb.append(", lastInvoiceDate=").append(lastInvoiceDate);
+        sb.append(", totalInvoiceBalance=").append(totalInvoiceBalance);
+        sb.append(", lastPaymentStatus='").append(lastPaymentStatus).append('\'');
+        sb.append(", defaultPaymentMethodType='").append(defaultPaymentMethodType).append('\'');
+        sb.append(", defaultCreditCardType='").append(defaultCreditCardType).append('\'');
+        sb.append(", defaultBillingAddressCountry='").append(defaultBillingAddressCountry).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultBusinessAccount that = (DefaultBusinessAccount) o;
+
+        if (balance != null ? !balance.equals(that.balance) : that.balance != null) {
+            return false;
+        }
+        if (currency != null ? !currency.equals(that.currency) : that.currency != null) {
+            return false;
+        }
+        if (defaultBillingAddressCountry != null ? !defaultBillingAddressCountry.equals(that.defaultBillingAddressCountry) : that.defaultBillingAddressCountry != null) {
+            return false;
+        }
+        if (defaultCreditCardType != null ? !defaultCreditCardType.equals(that.defaultCreditCardType) : that.defaultCreditCardType != null) {
+            return false;
+        }
+        if (defaultPaymentMethodType != null ? !defaultPaymentMethodType.equals(that.defaultPaymentMethodType) : that.defaultPaymentMethodType != null) {
+            return false;
+        }
+        if (externalKey != null ? !externalKey.equals(that.externalKey) : that.externalKey != null) {
+            return false;
+        }
+        if (lastInvoiceDate != null ? !lastInvoiceDate.equals(that.lastInvoiceDate) : that.lastInvoiceDate != null) {
+            return false;
+        }
+        if (lastPaymentStatus != null ? !lastPaymentStatus.equals(that.lastPaymentStatus) : that.lastPaymentStatus != null) {
+            return false;
+        }
+        if (name != null ? !name.equals(that.name) : that.name != null) {
+            return false;
+        }
+        if (totalInvoiceBalance != null ? !totalInvoiceBalance.equals(that.totalInvoiceBalance) : that.totalInvoiceBalance != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = externalKey != null ? externalKey.hashCode() : 0;
+        result = 31 * result + (name != null ? name.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        result = 31 * result + (balance != null ? balance.hashCode() : 0);
+        result = 31 * result + (lastInvoiceDate != null ? lastInvoiceDate.hashCode() : 0);
+        result = 31 * result + (totalInvoiceBalance != null ? totalInvoiceBalance.hashCode() : 0);
+        result = 31 * result + (lastPaymentStatus != null ? lastPaymentStatus.hashCode() : 0);
+        result = 31 * result + (defaultPaymentMethodType != null ? defaultPaymentMethodType.hashCode() : 0);
+        result = 31 * result + (defaultCreditCardType != null ? defaultCreditCardType.hashCode() : 0);
+        result = 31 * result + (defaultBillingAddressCountry != null ? defaultBillingAddressCountry.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessField.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessField.java
new file mode 100644
index 0000000..d74d409
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessField.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.api;
+
+import com.ning.billing.ObjectType;
+import com.ning.billing.analytics.api.BusinessField;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountFieldModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessFieldModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceFieldModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoicePaymentFieldModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionFieldModelDao;
+import com.ning.billing.util.entity.EntityBase;
+
+public class DefaultBusinessField extends EntityBase implements BusinessField {
+
+    private final ObjectType objectType;
+    private final String name;
+    private final String value;
+
+    DefaultBusinessField(final ObjectType objectType, final BusinessFieldModelDao businessFieldModelDao) {
+        super(businessFieldModelDao.getId());
+        this.objectType = objectType;
+        this.name = businessFieldModelDao.getName();
+        this.value = businessFieldModelDao.getValue();
+    }
+
+    public DefaultBusinessField(final BusinessAccountFieldModelDao businessAccountFieldModelDao) {
+        this(ObjectType.ACCOUNT, businessAccountFieldModelDao);
+    }
+
+    public DefaultBusinessField(final BusinessInvoiceFieldModelDao businessInvoiceFieldModelDao) {
+        this(ObjectType.INVOICE, businessInvoiceFieldModelDao);
+    }
+
+    public DefaultBusinessField(final BusinessInvoicePaymentFieldModelDao businessInvoicePaymentFieldModelDao) {
+        this(ObjectType.PAYMENT, businessInvoicePaymentFieldModelDao);
+    }
+
+    public DefaultBusinessField(final BusinessSubscriptionTransitionFieldModelDao businessSubscriptionTransitionFieldModelDao) {
+        this(ObjectType.BUNDLE, businessSubscriptionTransitionFieldModelDao);
+    }
+
+    @Override
+    public ObjectType getObjectType() {
+        return objectType;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String getValue() {
+        return value;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("DefaultBusinessField");
+        sb.append("{objectType=").append(objectType);
+        sb.append(", name='").append(name).append('\'');
+        sb.append(", value='").append(value).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultBusinessField that = (DefaultBusinessField) o;
+
+        if (name != null ? !name.equals(that.name) : that.name != null) {
+            return false;
+        }
+        if (objectType != that.objectType) {
+            return false;
+        }
+        if (value != null ? !value.equals(that.value) : that.value != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = objectType != null ? objectType.hashCode() : 0;
+        result = 31 * result + (name != null ? name.hashCode() : 0);
+        result = 31 * result + (value != null ? value.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessInvoice.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessInvoice.java
new file mode 100644
index 0000000..0c32410
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessInvoice.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.api;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+
+import com.ning.billing.analytics.api.BusinessInvoice;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceItemModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceModelDao;
+import com.ning.billing.util.entity.EntityBase;
+
+public class DefaultBusinessInvoice extends EntityBase implements BusinessInvoice {
+
+    private final UUID invoiceId;
+    private final Integer invoiceNumber;
+    private final UUID accountId;
+    private final String accountKey;
+    private final LocalDate invoiceDate;
+    private final LocalDate targetDate;
+    private final Currency currency;
+    private final BigDecimal balance;
+    private final BigDecimal amountPaid;
+    private final BigDecimal amountCharged;
+    private final BigDecimal amountCredited;
+    private final List<BusinessInvoiceItem> invoiceItems;
+
+    public DefaultBusinessInvoice(final BusinessInvoiceModelDao businessInvoiceModelDao,
+                                  final Collection<BusinessInvoiceItemModelDao> businessInvoiceItemModelDaos) {
+        super(businessInvoiceModelDao.getInvoiceId(), businessInvoiceModelDao.getCreatedDate(), businessInvoiceModelDao.getUpdatedDate());
+        this.accountId = businessInvoiceModelDao.getAccountId();
+        this.accountKey = businessInvoiceModelDao.getAccountKey();
+        this.amountCharged = businessInvoiceModelDao.getAmountCharged();
+        this.amountCredited = businessInvoiceModelDao.getAmountCredited();
+        this.amountPaid = businessInvoiceModelDao.getAmountPaid();
+        this.balance = businessInvoiceModelDao.getBalance();
+        this.currency = businessInvoiceModelDao.getCurrency();
+        this.invoiceDate = businessInvoiceModelDao.getInvoiceDate();
+        this.invoiceId = businessInvoiceModelDao.getInvoiceId();
+        this.invoiceNumber = businessInvoiceModelDao.getInvoiceNumber();
+        this.targetDate = businessInvoiceModelDao.getTargetDate();
+        this.invoiceItems = toInvoiceItems(businessInvoiceItemModelDaos);
+    }
+
+    private List<BusinessInvoiceItem> toInvoiceItems(final Collection<BusinessInvoiceItemModelDao> businessInvoiceItemModelDaos) {
+        final List<BusinessInvoiceItem> businessInvoiceItems = new ArrayList<BusinessInvoiceItem>();
+        for (final BusinessInvoiceItemModelDao businessInvoiceItemModelDao : businessInvoiceItemModelDaos) {
+            businessInvoiceItems.add(new BusinessInvoiceItem() {
+                @Override
+                public UUID getItemId() {
+                    return businessInvoiceItemModelDao.getItemId();
+                }
+
+                @Override
+                public UUID getInvoiceId() {
+                    return businessInvoiceItemModelDao.getInvoiceId();
+                }
+
+                @Override
+                public String getItemType() {
+                    return businessInvoiceItemModelDao.getItemType();
+                }
+
+                @Override
+                public String getExternalKey() {
+                    return businessInvoiceItemModelDao.getExternalKey();
+                }
+
+                @Override
+                public String getProductName() {
+                    return businessInvoiceItemModelDao.getProductName();
+                }
+
+                @Override
+                public String getProductType() {
+                    return businessInvoiceItemModelDao.getProductType();
+                }
+
+                @Override
+                public String getProductCategory() {
+                    return businessInvoiceItemModelDao.getProductCategory();
+                }
+
+                @Override
+                public String getSlug() {
+                    return businessInvoiceItemModelDao.getSlug();
+                }
+
+                @Override
+                public String getPhase() {
+                    return businessInvoiceItemModelDao.getPhase();
+                }
+
+                @Override
+                public String getBillingPeriod() {
+                    return businessInvoiceItemModelDao.getBillingPeriod();
+                }
+
+                @Override
+                public LocalDate getStartDate() {
+                    return businessInvoiceItemModelDao.getStartDate();
+                }
+
+                @Override
+                public LocalDate getEndDate() {
+                    return businessInvoiceItemModelDao.getEndDate();
+                }
+
+                @Override
+                public BigDecimal getAmount() {
+                    return businessInvoiceItemModelDao.getAmount();
+                }
+
+                @Override
+                public Currency getCurrency() {
+                    return businessInvoiceItemModelDao.getCurrency();
+                }
+
+                @Override
+                public UUID getLinkedItemId() {
+                    return businessInvoiceItemModelDao.getLinkedItemId();
+                }
+            });
+        }
+
+        return businessInvoiceItems;
+    }
+
+    @Override
+    public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    @Override
+    public Integer getInvoiceNumber() {
+        return invoiceNumber;
+    }
+
+    @Override
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    @Override
+    public String getAccountKey() {
+        return accountKey;
+    }
+
+    @Override
+    public LocalDate getInvoiceDate() {
+        return invoiceDate;
+    }
+
+    @Override
+    public LocalDate getTargetDate() {
+        return targetDate;
+    }
+
+    @Override
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    @Override
+    public BigDecimal getBalance() {
+        return balance;
+    }
+
+    @Override
+    public BigDecimal getAmountPaid() {
+        return amountPaid;
+    }
+
+    @Override
+    public BigDecimal getAmountCharged() {
+        return amountCharged;
+    }
+
+    @Override
+    public BigDecimal getAmountCredited() {
+        return amountCredited;
+    }
+
+    @Override
+    public List<BusinessInvoiceItem> getInvoiceItems() {
+        return invoiceItems;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("DefaultBusinessInvoice");
+        sb.append("{invoiceId=").append(invoiceId);
+        sb.append(", invoiceNumber=").append(invoiceNumber);
+        sb.append(", accountId=").append(accountId);
+        sb.append(", accountKey='").append(accountKey).append('\'');
+        sb.append(", invoiceDate=").append(invoiceDate);
+        sb.append(", targetDate=").append(targetDate);
+        sb.append(", currency=").append(currency);
+        sb.append(", balance=").append(balance);
+        sb.append(", amountPaid=").append(amountPaid);
+        sb.append(", amountCharged=").append(amountCharged);
+        sb.append(", amountCredited=").append(amountCredited);
+        sb.append(", invoiceItemsSize=").append(invoiceItems.size());
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultBusinessInvoice that = (DefaultBusinessInvoice) o;
+
+        if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
+            return false;
+        }
+        if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
+            return false;
+        }
+        if (amountCharged != null ? !amountCharged.equals(that.amountCharged) : that.amountCharged != null) {
+            return false;
+        }
+        if (amountCredited != null ? !amountCredited.equals(that.amountCredited) : that.amountCredited != null) {
+            return false;
+        }
+        if (amountPaid != null ? !amountPaid.equals(that.amountPaid) : that.amountPaid != null) {
+            return false;
+        }
+        if (balance != null ? !balance.equals(that.balance) : that.balance != null) {
+            return false;
+        }
+        if (currency != that.currency) {
+            return false;
+        }
+        if (invoiceDate != null ? !invoiceDate.equals(that.invoiceDate) : that.invoiceDate != null) {
+            return false;
+        }
+        if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
+            return false;
+        }
+        if (invoiceItems != null ? invoiceItems.size() != that.invoiceItems.size() : that.invoiceItems != null) {
+            return false;
+        }
+        if (invoiceNumber != null ? !invoiceNumber.equals(that.invoiceNumber) : that.invoiceNumber != null) {
+            return false;
+        }
+        if (targetDate != null ? !targetDate.equals(that.targetDate) : that.targetDate != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = invoiceId != null ? invoiceId.hashCode() : 0;
+        result = 31 * result + (invoiceNumber != null ? invoiceNumber.hashCode() : 0);
+        result = 31 * result + (accountId != null ? accountId.hashCode() : 0);
+        result = 31 * result + (accountKey != null ? accountKey.hashCode() : 0);
+        result = 31 * result + (invoiceDate != null ? invoiceDate.hashCode() : 0);
+        result = 31 * result + (targetDate != null ? targetDate.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        result = 31 * result + (balance != null ? balance.hashCode() : 0);
+        result = 31 * result + (amountPaid != null ? amountPaid.hashCode() : 0);
+        result = 31 * result + (amountCharged != null ? amountCharged.hashCode() : 0);
+        result = 31 * result + (amountCredited != null ? amountCredited.hashCode() : 0);
+        result = 31 * result + (invoiceItems != null ? invoiceItems.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessInvoicePayment.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessInvoicePayment.java
new file mode 100644
index 0000000..ed76e89
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessInvoicePayment.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.api;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.analytics.api.BusinessInvoicePayment;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoicePaymentModelDao;
+import com.ning.billing.util.entity.EntityBase;
+
+public class DefaultBusinessInvoicePayment extends EntityBase implements BusinessInvoicePayment {
+
+    private final UUID paymentId;
+    private final String extFirstPaymentRefId;
+    private final String extSecondPaymentRefId;
+    private final String accountKey;
+    private final UUID invoiceId;
+    private final DateTime effectiveDate;
+    private final BigDecimal amount;
+    private final Currency currency;
+    private final String paymentError;
+    private final String processingStatus;
+    private final BigDecimal requestedAmount;
+    private final String pluginName;
+    private final String paymentType;
+    private final String paymentMethod;
+    private final String cardType;
+    private final String cardCountry;
+    private final String invoicePaymentType;
+    private final UUID linkedInvoicePaymentId;
+
+    public DefaultBusinessInvoicePayment(final BusinessInvoicePaymentModelDao businessInvoicePaymentModelDao) {
+        this.paymentId = businessInvoicePaymentModelDao.getPaymentId();
+        this.extFirstPaymentRefId = businessInvoicePaymentModelDao.getExtFirstPaymentRefId();
+        this.extSecondPaymentRefId = businessInvoicePaymentModelDao.getExtSecondPaymentRefId();
+        this.accountKey = businessInvoicePaymentModelDao.getAccountKey();
+        this.invoiceId = businessInvoicePaymentModelDao.getInvoiceId();
+        this.effectiveDate = businessInvoicePaymentModelDao.getEffectiveDate();
+        this.amount = businessInvoicePaymentModelDao.getAmount();
+        this.currency = businessInvoicePaymentModelDao.getCurrency();
+        this.paymentError = businessInvoicePaymentModelDao.getPaymentError();
+        this.processingStatus = businessInvoicePaymentModelDao.getProcessingStatus();
+        this.requestedAmount = businessInvoicePaymentModelDao.getRequestedAmount();
+        this.pluginName = businessInvoicePaymentModelDao.getPluginName();
+        this.paymentType = businessInvoicePaymentModelDao.getPaymentType();
+        this.paymentMethod = businessInvoicePaymentModelDao.getPaymentMethod();
+        this.cardType = businessInvoicePaymentModelDao.getCardType();
+        this.cardCountry = businessInvoicePaymentModelDao.getCardCountry();
+        this.invoicePaymentType = businessInvoicePaymentModelDao.getInvoicePaymentType();
+        this.linkedInvoicePaymentId = businessInvoicePaymentModelDao.getLinkedInvoicePaymentId();
+    }
+
+    @Override
+    public UUID getPaymentId() {
+        return paymentId;
+    }
+
+    @Override
+    public String getExtFirstPaymentRefId() {
+        return extFirstPaymentRefId;
+    }
+
+    @Override
+    public String getExtSecondPaymentRefId() {
+        return extSecondPaymentRefId;
+    }
+
+    @Override
+    public String getAccountKey() {
+        return accountKey;
+    }
+
+    @Override
+    public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    @Override
+    public DateTime getEffectiveDate() {
+        return effectiveDate;
+    }
+
+    @Override
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    @Override
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    @Override
+    public String getPaymentError() {
+        return paymentError;
+    }
+
+    @Override
+    public String getProcessingStatus() {
+        return processingStatus;
+    }
+
+    @Override
+    public BigDecimal getRequestedAmount() {
+        return requestedAmount;
+    }
+
+    @Override
+    public String getPluginName() {
+        return pluginName;
+    }
+
+    @Override
+    public String getPaymentType() {
+        return paymentType;
+    }
+
+    @Override
+    public String getPaymentMethod() {
+        return paymentMethod;
+    }
+
+    @Override
+    public String getCardType() {
+        return cardType;
+    }
+
+    @Override
+    public String getCardCountry() {
+        return cardCountry;
+    }
+
+    @Override
+    public String getInvoicePaymentType() {
+        return invoicePaymentType;
+    }
+
+    @Override
+    public UUID getLinkedInvoicePaymentId() {
+        return linkedInvoicePaymentId;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("DefaultBusinessInvoicePayment");
+        sb.append("{paymentId=").append(paymentId);
+        sb.append(", extFirstPaymentRefId='").append(extFirstPaymentRefId).append('\'');
+        sb.append(", extSecondPaymentRefId='").append(extSecondPaymentRefId).append('\'');
+        sb.append(", accountKey='").append(accountKey).append('\'');
+        sb.append(", invoiceId=").append(invoiceId);
+        sb.append(", effectiveDate=").append(effectiveDate);
+        sb.append(", amount=").append(amount);
+        sb.append(", currency=").append(currency);
+        sb.append(", paymentError='").append(paymentError).append('\'');
+        sb.append(", processingStatus='").append(processingStatus).append('\'');
+        sb.append(", requestedAmount=").append(requestedAmount);
+        sb.append(", pluginName='").append(pluginName).append('\'');
+        sb.append(", paymentType='").append(paymentType).append('\'');
+        sb.append(", paymentMethod='").append(paymentMethod).append('\'');
+        sb.append(", cardType='").append(cardType).append('\'');
+        sb.append(", cardCountry='").append(cardCountry).append('\'');
+        sb.append(", invoicePaymentType='").append(invoicePaymentType).append('\'');
+        sb.append(", linkedInvoicePaymentId=").append(linkedInvoicePaymentId);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultBusinessInvoicePayment that = (DefaultBusinessInvoicePayment) o;
+
+        if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
+            return false;
+        }
+        if (amount != null ? !amount.equals(that.amount) : that.amount != null) {
+            return false;
+        }
+        if (cardCountry != null ? !cardCountry.equals(that.cardCountry) : that.cardCountry != null) {
+            return false;
+        }
+        if (cardType != null ? !cardType.equals(that.cardType) : that.cardType != null) {
+            return false;
+        }
+        if (currency != that.currency) {
+            return false;
+        }
+        if (effectiveDate != null ? !effectiveDate.equals(that.effectiveDate) : that.effectiveDate != null) {
+            return false;
+        }
+        if (extFirstPaymentRefId != null ? !extFirstPaymentRefId.equals(that.extFirstPaymentRefId) : that.extFirstPaymentRefId != null) {
+            return false;
+        }
+        if (extSecondPaymentRefId != null ? !extSecondPaymentRefId.equals(that.extSecondPaymentRefId) : that.extSecondPaymentRefId != null) {
+            return false;
+        }
+        if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
+            return false;
+        }
+        if (invoicePaymentType != null ? !invoicePaymentType.equals(that.invoicePaymentType) : that.invoicePaymentType != null) {
+            return false;
+        }
+        if (linkedInvoicePaymentId != null ? !linkedInvoicePaymentId.equals(that.linkedInvoicePaymentId) : that.linkedInvoicePaymentId != null) {
+            return false;
+        }
+        if (paymentError != null ? !paymentError.equals(that.paymentError) : that.paymentError != null) {
+            return false;
+        }
+        if (paymentId != null ? !paymentId.equals(that.paymentId) : that.paymentId != null) {
+            return false;
+        }
+        if (paymentMethod != null ? !paymentMethod.equals(that.paymentMethod) : that.paymentMethod != null) {
+            return false;
+        }
+        if (paymentType != null ? !paymentType.equals(that.paymentType) : that.paymentType != null) {
+            return false;
+        }
+        if (pluginName != null ? !pluginName.equals(that.pluginName) : that.pluginName != null) {
+            return false;
+        }
+        if (processingStatus != null ? !processingStatus.equals(that.processingStatus) : that.processingStatus != null) {
+            return false;
+        }
+        if (requestedAmount != null ? !requestedAmount.equals(that.requestedAmount) : that.requestedAmount != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = paymentId != null ? paymentId.hashCode() : 0;
+        result = 31 * result + (extFirstPaymentRefId != null ? extFirstPaymentRefId.hashCode() : 0);
+        result = 31 * result + (extSecondPaymentRefId != null ? extSecondPaymentRefId.hashCode() : 0);
+        result = 31 * result + (accountKey != null ? accountKey.hashCode() : 0);
+        result = 31 * result + (invoiceId != null ? invoiceId.hashCode() : 0);
+        result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
+        result = 31 * result + (amount != null ? amount.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        result = 31 * result + (paymentError != null ? paymentError.hashCode() : 0);
+        result = 31 * result + (processingStatus != null ? processingStatus.hashCode() : 0);
+        result = 31 * result + (requestedAmount != null ? requestedAmount.hashCode() : 0);
+        result = 31 * result + (pluginName != null ? pluginName.hashCode() : 0);
+        result = 31 * result + (paymentType != null ? paymentType.hashCode() : 0);
+        result = 31 * result + (paymentMethod != null ? paymentMethod.hashCode() : 0);
+        result = 31 * result + (cardType != null ? cardType.hashCode() : 0);
+        result = 31 * result + (cardCountry != null ? cardCountry.hashCode() : 0);
+        result = 31 * result + (invoicePaymentType != null ? invoicePaymentType.hashCode() : 0);
+        result = 31 * result + (linkedInvoicePaymentId != null ? linkedInvoicePaymentId.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessOverdueStatus.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessOverdueStatus.java
new file mode 100644
index 0000000..e96ce47
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessOverdueStatus.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.api;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.ObjectType;
+import com.ning.billing.analytics.api.BusinessOverdueStatus;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessOverdueStatusModelDao;
+import com.ning.billing.util.entity.EntityBase;
+
+public class DefaultBusinessOverdueStatus extends EntityBase implements BusinessOverdueStatus {
+
+    private final ObjectType objectType;
+    private final String accountKey;
+    private final String status;
+    private final DateTime startDate;
+    private final DateTime endDate;
+
+    public DefaultBusinessOverdueStatus(final BusinessOverdueStatusModelDao businessOverdueStatusModelDao) {
+        // TODO
+        super(businessOverdueStatusModelDao.getBundleId());
+        this.objectType = ObjectType.BUNDLE;
+
+        this.accountKey = businessOverdueStatusModelDao.getAccountKey();
+        this.status = businessOverdueStatusModelDao.getStatus();
+        this.startDate = businessOverdueStatusModelDao.getStartDate();
+        this.endDate = businessOverdueStatusModelDao.getEndDate();
+    }
+
+    @Override
+    public ObjectType getObjectType() {
+        return objectType;
+    }
+
+    @Override
+    public String getAccountKey() {
+        return accountKey;
+    }
+
+    @Override
+    public String getStatus() {
+        return status;
+    }
+
+    @Override
+    public DateTime getStartDate() {
+        return startDate;
+    }
+
+    @Override
+    public DateTime getEndDate() {
+        return endDate;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("DefaultBusinessOverdueStatus");
+        sb.append("{objectType=").append(objectType);
+        sb.append(", id='").append(id).append('\'');
+        sb.append(", accountKey='").append(accountKey).append('\'');
+        sb.append(", status='").append(status).append('\'');
+        sb.append(", startDate=").append(startDate);
+        sb.append(", endDate=").append(endDate);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultBusinessOverdueStatus that = (DefaultBusinessOverdueStatus) o;
+
+        if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
+            return false;
+        }
+        if (endDate != null ? !endDate.equals(that.endDate) : that.endDate != null) {
+            return false;
+        }
+        if (objectType != that.objectType) {
+            return false;
+        }
+        if (startDate != null ? !startDate.equals(that.startDate) : that.startDate != null) {
+            return false;
+        }
+        if (status != null ? !status.equals(that.status) : that.status != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = objectType != null ? objectType.hashCode() : 0;
+        result = 31 * result + (accountKey != null ? accountKey.hashCode() : 0);
+        result = 31 * result + (status != null ? status.hashCode() : 0);
+        result = 31 * result + (startDate != null ? startDate.hashCode() : 0);
+        result = 31 * result + (endDate != null ? endDate.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessSnapshot.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessSnapshot.java
new file mode 100644
index 0000000..807209d
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessSnapshot.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.api;
+
+import java.util.Collection;
+
+import com.ning.billing.analytics.api.BusinessAccount;
+import com.ning.billing.analytics.api.BusinessField;
+import com.ning.billing.analytics.api.BusinessInvoice;
+import com.ning.billing.analytics.api.BusinessInvoicePayment;
+import com.ning.billing.analytics.api.BusinessOverdueStatus;
+import com.ning.billing.analytics.api.BusinessSnapshot;
+import com.ning.billing.analytics.api.BusinessSubscriptionTransition;
+import com.ning.billing.analytics.api.BusinessTag;
+
+public class DefaultBusinessSnapshot implements BusinessSnapshot {
+
+    private final BusinessAccount businessAccount;
+    private final Collection<BusinessSubscriptionTransition> businessSubscriptionTransitions;
+    private final Collection<BusinessInvoice> businessInvoices;
+    private final Collection<BusinessInvoicePayment> businessInvoicePayments;
+    private final Collection<BusinessOverdueStatus> businessOverdueStatuses;
+    private final Collection<BusinessTag> businessTags;
+    private final Collection<BusinessField> businessFields;
+
+    public DefaultBusinessSnapshot(final BusinessAccount businessAccount,
+                                   final Collection<BusinessSubscriptionTransition> businessSubscriptionTransitions,
+                                   final Collection<BusinessInvoice> businessInvoices,
+                                   final Collection<BusinessInvoicePayment> businessInvoicePayments,
+                                   final Collection<BusinessOverdueStatus> businessOverdueStatuses,
+                                   final Collection<BusinessTag> businessTags,
+                                   final Collection<BusinessField> businessFields) {
+        this.businessAccount = businessAccount;
+        this.businessSubscriptionTransitions = businessSubscriptionTransitions;
+        this.businessInvoices = businessInvoices;
+        this.businessInvoicePayments = businessInvoicePayments;
+        this.businessOverdueStatuses = businessOverdueStatuses;
+        this.businessTags = businessTags;
+        this.businessFields = businessFields;
+    }
+
+    @Override
+    public BusinessAccount getBusinessAccount() {
+        return businessAccount;
+    }
+
+    @Override
+    public Collection<BusinessSubscriptionTransition> getBusinessSubscriptionTransitions() {
+        return businessSubscriptionTransitions;
+    }
+
+    @Override
+    public Collection<BusinessInvoice> getBusinessInvoices() {
+        return businessInvoices;
+    }
+
+    @Override
+    public Collection<BusinessInvoicePayment> getBusinessInvoicePayments() {
+        return businessInvoicePayments;
+    }
+
+    @Override
+    public Collection<BusinessOverdueStatus> getBusinessOverdueStatuses() {
+        return businessOverdueStatuses;
+    }
+
+    @Override
+    public Collection<BusinessTag> getBusinessTags() {
+        return businessTags;
+    }
+
+    @Override
+    public Collection<BusinessField> getBusinessFields() {
+        return businessFields;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("DefaultBusinessSnapshot");
+        sb.append("{businessAccount=").append(businessAccount);
+        sb.append(", businessSubscriptionTransitions=").append(businessSubscriptionTransitions);
+        sb.append(", businessInvoices=").append(businessInvoices);
+        sb.append(", businessInvoicePayments=").append(businessInvoicePayments);
+        sb.append(", businessOverdueStatuses=").append(businessOverdueStatuses);
+        sb.append(", businessTags=").append(businessTags);
+        sb.append(", businessFields=").append(businessFields);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultBusinessSnapshot that = (DefaultBusinessSnapshot) o;
+
+        if (businessAccount != null ? !businessAccount.equals(that.businessAccount) : that.businessAccount != null) {
+            return false;
+        }
+        if (businessFields != null ? !businessFields.equals(that.businessFields) : that.businessFields != null) {
+            return false;
+        }
+        if (businessInvoicePayments != null ? !businessInvoicePayments.equals(that.businessInvoicePayments) : that.businessInvoicePayments != null) {
+            return false;
+        }
+        if (businessInvoices != null ? !businessInvoices.equals(that.businessInvoices) : that.businessInvoices != null) {
+            return false;
+        }
+        if (businessOverdueStatuses != null ? !businessOverdueStatuses.equals(that.businessOverdueStatuses) : that.businessOverdueStatuses != null) {
+            return false;
+        }
+        if (businessSubscriptionTransitions != null ? !businessSubscriptionTransitions.equals(that.businessSubscriptionTransitions) : that.businessSubscriptionTransitions != null) {
+            return false;
+        }
+        if (businessTags != null ? !businessTags.equals(that.businessTags) : that.businessTags != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = businessAccount != null ? businessAccount.hashCode() : 0;
+        result = 31 * result + (businessSubscriptionTransitions != null ? businessSubscriptionTransitions.hashCode() : 0);
+        result = 31 * result + (businessInvoices != null ? businessInvoices.hashCode() : 0);
+        result = 31 * result + (businessInvoicePayments != null ? businessInvoicePayments.hashCode() : 0);
+        result = 31 * result + (businessOverdueStatuses != null ? businessOverdueStatuses.hashCode() : 0);
+        result = 31 * result + (businessTags != null ? businessTags.hashCode() : 0);
+        result = 31 * result + (businessFields != null ? businessFields.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessSubscriptionTransition.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessSubscriptionTransition.java
new file mode 100644
index 0000000..48ffed4
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessSubscriptionTransition.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.api;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.analytics.api.BusinessSubscriptionTransition;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionModelDao;
+import com.ning.billing.util.entity.EntityBase;
+
+public class DefaultBusinessSubscriptionTransition extends EntityBase implements BusinessSubscriptionTransition {
+
+    private final long totalOrdering;
+    private final UUID bundleId;
+    private final String externalKey;
+    private final UUID accountId;
+    private final String accountKey;
+    private final UUID subscriptionId;
+
+    private final DateTime requestedTimestamp;
+    private final String eventType;
+    private final String category;
+
+    private final String prevProductName;
+    private final String prevProductType;
+    private final String prevProductCategory;
+    private final String prevSlug;
+    private final String prevPhase;
+    private final String prevBillingPeriod;
+    private final BigDecimal prevPrice;
+    private final String prevPriceList;
+    private final BigDecimal prevMrr;
+    private final String prevCurrency;
+    private final DateTime prevStartDate;
+    private final String prevState;
+
+    private final String nextProductName;
+    private final String nextProductType;
+    private final String nextProductCategory;
+    private final String nextSlug;
+    private final String nextPhase;
+    private final String nextBillingPeriod;
+    private final BigDecimal nextPrice;
+    private final String nextPriceList;
+    private final BigDecimal nextMrr;
+    private final String nextCurrency;
+    private final DateTime nextStartDate;
+    private final String nextState;
+
+    public DefaultBusinessSubscriptionTransition(final BusinessSubscriptionTransitionModelDao bstModelDao) {
+        this.totalOrdering = bstModelDao.getTotalOrdering();
+        this.bundleId = bstModelDao.getBundleId();
+        this.externalKey = bstModelDao.getExternalKey();
+        this.accountId = bstModelDao.getAccountId();
+        this.accountKey = bstModelDao.getAccountKey();
+        this.subscriptionId = bstModelDao.getSubscriptionId();
+
+        this.requestedTimestamp = bstModelDao.getRequestedTimestamp();
+        this.eventType = bstModelDao.getEvent().getEventType().toString();
+        if (bstModelDao.getEvent().getCategory() != null) {
+            this.category = bstModelDao.getEvent().getCategory().toString();
+        } else {
+            this.category = null;
+        }
+
+        if (bstModelDao.getPreviousSubscription() != null) {
+            this.prevProductName = bstModelDao.getPreviousSubscription().getProductName();
+            this.prevProductType = bstModelDao.getPreviousSubscription().getProductType();
+            this.prevProductCategory = bstModelDao.getPreviousSubscription().getProductCategory().toString();
+            this.prevSlug = bstModelDao.getPreviousSubscription().getSlug();
+            this.prevPhase = bstModelDao.getPreviousSubscription().getPhase();
+            this.prevBillingPeriod = bstModelDao.getPreviousSubscription().getBillingPeriod();
+            this.prevPrice = bstModelDao.getPreviousSubscription().getPrice();
+            this.prevPriceList = bstModelDao.getPreviousSubscription().getPriceList();
+            this.prevMrr = bstModelDao.getPreviousSubscription().getMrr();
+            this.prevCurrency = bstModelDao.getPreviousSubscription().getCurrency();
+            this.prevStartDate = bstModelDao.getPreviousSubscription().getStartDate();
+            this.prevState = bstModelDao.getPreviousSubscription().getState().toString();
+        } else {
+            this.prevProductName = null;
+            this.prevProductType = null;
+            this.prevProductCategory = null;
+            this.prevSlug = null;
+            this.prevPhase = null;
+            this.prevBillingPeriod = null;
+            this.prevPrice = null;
+            this.prevPriceList = null;
+            this.prevMrr = null;
+            this.prevCurrency = null;
+            this.prevStartDate = null;
+            this.prevState = null;
+        }
+
+        if (bstModelDao.getNextSubscription() != null) {
+            this.nextProductName = bstModelDao.getNextSubscription().getProductName();
+            this.nextProductType = bstModelDao.getNextSubscription().getProductType();
+            this.nextProductCategory = bstModelDao.getNextSubscription().getProductCategory().toString();
+            this.nextSlug = bstModelDao.getNextSubscription().getSlug();
+            this.nextPhase = bstModelDao.getNextSubscription().getPhase();
+            this.nextBillingPeriod = bstModelDao.getNextSubscription().getBillingPeriod();
+            this.nextPrice = bstModelDao.getNextSubscription().getPrice();
+            this.nextPriceList = bstModelDao.getNextSubscription().getPriceList();
+            this.nextMrr = bstModelDao.getNextSubscription().getMrr();
+            this.nextCurrency = bstModelDao.getNextSubscription().getCurrency();
+            this.nextStartDate = bstModelDao.getNextSubscription().getStartDate();
+            this.nextState = bstModelDao.getNextSubscription().getState().toString();
+        } else {
+            this.nextProductName = null;
+            this.nextProductType = null;
+            this.nextProductCategory = null;
+            this.nextSlug = null;
+            this.nextPhase = null;
+            this.nextBillingPeriod = null;
+            this.nextPrice = null;
+            this.nextPriceList = null;
+            this.nextMrr = null;
+            this.nextCurrency = null;
+            this.nextStartDate = null;
+            this.nextState = null;
+        }
+    }
+
+    @Override
+    public long getTotalOrdering() {
+        return totalOrdering;
+    }
+
+    @Override
+    public UUID getBundleId() {
+        return bundleId;
+    }
+
+    @Override
+    public String getExternalKey() {
+        return externalKey;
+    }
+
+    @Override
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    @Override
+    public String getAccountKey() {
+        return accountKey;
+    }
+
+    @Override
+    public UUID getSubscriptionId() {
+        return subscriptionId;
+    }
+
+    @Override
+    public DateTime getRequestedTimestamp() {
+        return requestedTimestamp;
+    }
+
+    @Override
+    public String getEventType() {
+        return eventType;
+    }
+
+    @Override
+    public String getCategory() {
+        return category;
+    }
+
+    @Override
+    public String getPrevProductName() {
+        return prevProductName;
+    }
+
+    @Override
+    public String getPrevProductType() {
+        return prevProductType;
+    }
+
+    @Override
+    public String getPrevProductCategory() {
+        return prevProductCategory;
+    }
+
+    @Override
+    public String getPrevSlug() {
+        return prevSlug;
+    }
+
+    @Override
+    public String getPrevPhase() {
+        return prevPhase;
+    }
+
+    @Override
+    public String getPrevBillingPeriod() {
+        return prevBillingPeriod;
+    }
+
+    @Override
+    public BigDecimal getPrevPrice() {
+        return prevPrice;
+    }
+
+    @Override
+    public String getPrevPriceList() {
+        return prevPriceList;
+    }
+
+    @Override
+    public BigDecimal getPrevMrr() {
+        return prevMrr;
+    }
+
+    @Override
+    public String getPrevCurrency() {
+        return prevCurrency;
+    }
+
+    @Override
+    public DateTime getPrevStartDate() {
+        return prevStartDate;
+    }
+
+    @Override
+    public String getPrevState() {
+        return prevState;
+    }
+
+    @Override
+    public String getNextProductName() {
+        return nextProductName;
+    }
+
+    @Override
+    public String getNextProductType() {
+        return nextProductType;
+    }
+
+    @Override
+    public String getNextProductCategory() {
+        return nextProductCategory;
+    }
+
+    @Override
+    public String getNextSlug() {
+        return nextSlug;
+    }
+
+    @Override
+    public String getNextPhase() {
+        return nextPhase;
+    }
+
+    @Override
+    public String getNextBillingPeriod() {
+        return nextBillingPeriod;
+    }
+
+    @Override
+    public BigDecimal getNextPrice() {
+        return nextPrice;
+    }
+
+    @Override
+    public String getNextPriceList() {
+        return nextPriceList;
+    }
+
+    @Override
+    public BigDecimal getNextMrr() {
+        return nextMrr;
+    }
+
+    @Override
+    public String getNextCurrency() {
+        return nextCurrency;
+    }
+
+    @Override
+    public DateTime getNextStartDate() {
+        return nextStartDate;
+    }
+
+    @Override
+    public String getNextState() {
+        return nextState;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("DefaultBusinessSubscriptionTransition");
+        sb.append("{totalOrdering=").append(totalOrdering);
+        sb.append(", bundleId=").append(bundleId);
+        sb.append(", externalKey='").append(externalKey).append('\'');
+        sb.append(", accountId=").append(accountId);
+        sb.append(", accountKey='").append(accountKey).append('\'');
+        sb.append(", subscriptionId=").append(subscriptionId);
+        sb.append(", requestedTimestamp=").append(requestedTimestamp);
+        sb.append(", eventType='").append(eventType).append('\'');
+        sb.append(", category='").append(category).append('\'');
+        sb.append(", prevProductName='").append(prevProductName).append('\'');
+        sb.append(", prevProductType='").append(prevProductType).append('\'');
+        sb.append(", prevProductCategory='").append(prevProductCategory).append('\'');
+        sb.append(", prevSlug='").append(prevSlug).append('\'');
+        sb.append(", prevPhase='").append(prevPhase).append('\'');
+        sb.append(", prevBillingPeriod='").append(prevBillingPeriod).append('\'');
+        sb.append(", prevPrice=").append(prevPrice);
+        sb.append(", prevPriceList='").append(prevPriceList).append('\'');
+        sb.append(", prevMrr=").append(prevMrr);
+        sb.append(", prevCurrency='").append(prevCurrency).append('\'');
+        sb.append(", prevStartDate=").append(prevStartDate);
+        sb.append(", prevState='").append(prevState).append('\'');
+        sb.append(", nextProductName='").append(nextProductName).append('\'');
+        sb.append(", nextProductType='").append(nextProductType).append('\'');
+        sb.append(", nextProductCategory='").append(nextProductCategory).append('\'');
+        sb.append(", nextSlug='").append(nextSlug).append('\'');
+        sb.append(", nextPhase='").append(nextPhase).append('\'');
+        sb.append(", nextBillingPeriod='").append(nextBillingPeriod).append('\'');
+        sb.append(", nextPrice=").append(nextPrice);
+        sb.append(", nextPriceList='").append(nextPriceList).append('\'');
+        sb.append(", nextMrr=").append(nextMrr);
+        sb.append(", nextCurrency='").append(nextCurrency).append('\'');
+        sb.append(", nextStartDate=").append(nextStartDate);
+        sb.append(", nextState='").append(nextState).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultBusinessSubscriptionTransition that = (DefaultBusinessSubscriptionTransition) o;
+
+        if (totalOrdering != that.totalOrdering) {
+            return false;
+        }
+        if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
+            return false;
+        }
+        if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
+            return false;
+        }
+        if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null) {
+            return false;
+        }
+        if (category != null ? !category.equals(that.category) : that.category != null) {
+            return false;
+        }
+        if (eventType != null ? !eventType.equals(that.eventType) : that.eventType != null) {
+            return false;
+        }
+        if (externalKey != null ? !externalKey.equals(that.externalKey) : that.externalKey != null) {
+            return false;
+        }
+        if (nextBillingPeriod != null ? !nextBillingPeriod.equals(that.nextBillingPeriod) : that.nextBillingPeriod != null) {
+            return false;
+        }
+        if (nextCurrency != null ? !nextCurrency.equals(that.nextCurrency) : that.nextCurrency != null) {
+            return false;
+        }
+        if (nextMrr != null ? !nextMrr.equals(that.nextMrr) : that.nextMrr != null) {
+            return false;
+        }
+        if (nextPhase != null ? !nextPhase.equals(that.nextPhase) : that.nextPhase != null) {
+            return false;
+        }
+        if (nextPrice != null ? !nextPrice.equals(that.nextPrice) : that.nextPrice != null) {
+            return false;
+        }
+        if (nextPriceList != null ? !nextPriceList.equals(that.nextPriceList) : that.nextPriceList != null) {
+            return false;
+        }
+        if (nextProductCategory != null ? !nextProductCategory.equals(that.nextProductCategory) : that.nextProductCategory != null) {
+            return false;
+        }
+        if (nextProductName != null ? !nextProductName.equals(that.nextProductName) : that.nextProductName != null) {
+            return false;
+        }
+        if (nextProductType != null ? !nextProductType.equals(that.nextProductType) : that.nextProductType != null) {
+            return false;
+        }
+        if (nextSlug != null ? !nextSlug.equals(that.nextSlug) : that.nextSlug != null) {
+            return false;
+        }
+        if (nextStartDate != null ? !nextStartDate.equals(that.nextStartDate) : that.nextStartDate != null) {
+            return false;
+        }
+        if (nextState != null ? !nextState.equals(that.nextState) : that.nextState != null) {
+            return false;
+        }
+        if (prevBillingPeriod != null ? !prevBillingPeriod.equals(that.prevBillingPeriod) : that.prevBillingPeriod != null) {
+            return false;
+        }
+        if (prevCurrency != null ? !prevCurrency.equals(that.prevCurrency) : that.prevCurrency != null) {
+            return false;
+        }
+        if (prevMrr != null ? !prevMrr.equals(that.prevMrr) : that.prevMrr != null) {
+            return false;
+        }
+        if (prevPhase != null ? !prevPhase.equals(that.prevPhase) : that.prevPhase != null) {
+            return false;
+        }
+        if (prevPrice != null ? !prevPrice.equals(that.prevPrice) : that.prevPrice != null) {
+            return false;
+        }
+        if (prevPriceList != null ? !prevPriceList.equals(that.prevPriceList) : that.prevPriceList != null) {
+            return false;
+        }
+        if (prevProductCategory != null ? !prevProductCategory.equals(that.prevProductCategory) : that.prevProductCategory != null) {
+            return false;
+        }
+        if (prevProductName != null ? !prevProductName.equals(that.prevProductName) : that.prevProductName != null) {
+            return false;
+        }
+        if (prevProductType != null ? !prevProductType.equals(that.prevProductType) : that.prevProductType != null) {
+            return false;
+        }
+        if (prevSlug != null ? !prevSlug.equals(that.prevSlug) : that.prevSlug != null) {
+            return false;
+        }
+        if (prevStartDate != null ? !prevStartDate.equals(that.prevStartDate) : that.prevStartDate != null) {
+            return false;
+        }
+        if (prevState != null ? !prevState.equals(that.prevState) : that.prevState != null) {
+            return false;
+        }
+        if (requestedTimestamp != null ? !requestedTimestamp.equals(that.requestedTimestamp) : that.requestedTimestamp != null) {
+            return false;
+        }
+        if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = (int) (totalOrdering ^ (totalOrdering >>> 32));
+        result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
+        result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
+        result = 31 * result + (accountId != null ? accountId.hashCode() : 0);
+        result = 31 * result + (accountKey != null ? accountKey.hashCode() : 0);
+        result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
+        result = 31 * result + (requestedTimestamp != null ? requestedTimestamp.hashCode() : 0);
+        result = 31 * result + (eventType != null ? eventType.hashCode() : 0);
+        result = 31 * result + (category != null ? category.hashCode() : 0);
+        result = 31 * result + (prevProductName != null ? prevProductName.hashCode() : 0);
+        result = 31 * result + (prevProductType != null ? prevProductType.hashCode() : 0);
+        result = 31 * result + (prevProductCategory != null ? prevProductCategory.hashCode() : 0);
+        result = 31 * result + (prevSlug != null ? prevSlug.hashCode() : 0);
+        result = 31 * result + (prevPhase != null ? prevPhase.hashCode() : 0);
+        result = 31 * result + (prevBillingPeriod != null ? prevBillingPeriod.hashCode() : 0);
+        result = 31 * result + (prevPrice != null ? prevPrice.hashCode() : 0);
+        result = 31 * result + (prevPriceList != null ? prevPriceList.hashCode() : 0);
+        result = 31 * result + (prevMrr != null ? prevMrr.hashCode() : 0);
+        result = 31 * result + (prevCurrency != null ? prevCurrency.hashCode() : 0);
+        result = 31 * result + (prevStartDate != null ? prevStartDate.hashCode() : 0);
+        result = 31 * result + (prevState != null ? prevState.hashCode() : 0);
+        result = 31 * result + (nextProductName != null ? nextProductName.hashCode() : 0);
+        result = 31 * result + (nextProductType != null ? nextProductType.hashCode() : 0);
+        result = 31 * result + (nextProductCategory != null ? nextProductCategory.hashCode() : 0);
+        result = 31 * result + (nextSlug != null ? nextSlug.hashCode() : 0);
+        result = 31 * result + (nextPhase != null ? nextPhase.hashCode() : 0);
+        result = 31 * result + (nextBillingPeriod != null ? nextBillingPeriod.hashCode() : 0);
+        result = 31 * result + (nextPrice != null ? nextPrice.hashCode() : 0);
+        result = 31 * result + (nextPriceList != null ? nextPriceList.hashCode() : 0);
+        result = 31 * result + (nextMrr != null ? nextMrr.hashCode() : 0);
+        result = 31 * result + (nextCurrency != null ? nextCurrency.hashCode() : 0);
+        result = 31 * result + (nextStartDate != null ? nextStartDate.hashCode() : 0);
+        result = 31 * result + (nextState != null ? nextState.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessTag.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessTag.java
new file mode 100644
index 0000000..d2c5ab8
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultBusinessTag.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.api;
+
+import com.ning.billing.ObjectType;
+import com.ning.billing.analytics.api.BusinessTag;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountTagModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoicePaymentTagModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceTagModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionTagModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessTagModelDao;
+import com.ning.billing.util.entity.EntityBase;
+
+public class DefaultBusinessTag extends EntityBase implements BusinessTag {
+
+    private final ObjectType objectType;
+    private final String name;
+
+    DefaultBusinessTag(final ObjectType objectType, final BusinessTagModelDao businessTagModelDao) {
+        super(businessTagModelDao.getId());
+        this.objectType = objectType;
+        this.name = businessTagModelDao.getName();
+    }
+
+    public DefaultBusinessTag(final BusinessAccountTagModelDao businessAccountTagModelDao) {
+        this(ObjectType.ACCOUNT, businessAccountTagModelDao);
+    }
+
+    public DefaultBusinessTag(final BusinessInvoiceTagModelDao businessInvoiceTagModelDao) {
+        this(ObjectType.INVOICE, businessInvoiceTagModelDao);
+    }
+
+    public DefaultBusinessTag(final BusinessInvoicePaymentTagModelDao businessInvoicePaymentTagModelDao) {
+        this(ObjectType.PAYMENT, businessInvoicePaymentTagModelDao);
+    }
+
+    public DefaultBusinessTag(final BusinessSubscriptionTransitionTagModelDao businessSubscriptionTransitionTagModelDao) {
+        this(ObjectType.BUNDLE, businessSubscriptionTransitionTagModelDao);
+    }
+
+    @Override
+    public ObjectType getObjectType() {
+        return objectType;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("DefaultBusinessTag");
+        sb.append("{objectType=").append(objectType);
+        sb.append(", name='").append(name).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultBusinessTag that = (DefaultBusinessTag) o;
+
+        if (name != null ? !name.equals(that.name) : that.name != null) {
+            return false;
+        }
+        if (objectType != that.objectType) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = objectType != null ? objectType.hashCode() : 0;
+        result = 31 * result + (name != null ? name.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultTimeSeriesData.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultTimeSeriesData.java
new file mode 100644
index 0000000..6b98f05
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/DefaultTimeSeriesData.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.api;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.joda.time.LocalDate;
+
+import com.ning.billing.analytics.api.TimeSeriesData;
+import com.ning.billing.osgi.bundles.analytics.dao.TimeSeriesTuple;
+
+public class DefaultTimeSeriesData implements TimeSeriesData {
+
+    private final List<LocalDate> dates;
+    private final List<Double> values;
+
+    public DefaultTimeSeriesData(final List<TimeSeriesTuple> dataOverTime) {
+        // We assume dataOverTime is sorted by time
+        dates = new LinkedList<LocalDate>();
+        values = new LinkedList<Double>();
+        for (final TimeSeriesTuple data : dataOverTime) {
+            dates.add(data.getLocalDate());
+            values.add(data.getValue());
+        }
+    }
+
+    @Override
+    public List<LocalDate> getDates() {
+        return dates;
+    }
+
+    @Override
+    public List<Double> getValues() {
+        return values;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("DefaultTimeSeriesData");
+        sb.append("{dates=").append(dates);
+        sb.append(", values=").append(values);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultTimeSeriesData that = (DefaultTimeSeriesData) o;
+
+        if (dates != null ? !dates.equals(that.dates) : that.dates != null) {
+            return false;
+        }
+        if (values != null ? !values.equals(that.values) : that.values != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = dates != null ? dates.hashCode() : 0;
+        result = 31 * result + (values != null ? values.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/sanity/DefaultAnalyticsSanityApi.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/sanity/DefaultAnalyticsSanityApi.java
new file mode 100644
index 0000000..f48e9bd
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/sanity/DefaultAnalyticsSanityApi.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.api.sanity;
+
+import java.util.Collection;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import com.ning.billing.analytics.api.sanity.AnalyticsSanityApi;
+import com.ning.billing.osgi.bundles.analytics.dao.AnalyticsSanityDao;
+import com.ning.billing.util.callcontext.InternalCallContextFactory;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.TenantContext;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class DefaultAnalyticsSanityApi implements AnalyticsSanityApi {
+
+    private final AnalyticsSanityDao dao;
+    private final InternalCallContextFactory internalCallContextFactory;
+
+    @Inject
+    public DefaultAnalyticsSanityApi(final AnalyticsSanityDao dao,
+                                     final InternalCallContextFactory internalCallContextFactory) {
+        this.dao = dao;
+        this.internalCallContextFactory = internalCallContextFactory;
+    }
+
+    @Override
+    public Collection<UUID> checkAnalyticsInSyncWithEntitlement(final TenantContext context) {
+        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
+        return dao.checkBstMatchesSubscriptionEvents(internalTenantContext);
+    }
+
+    @Override
+    public Collection<UUID> checkAnalyticsInSyncWithInvoice(final TenantContext context) {
+        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
+        return dao.checkBiiMatchesInvoiceItems(internalTenantContext);
+    }
+
+    @Override
+    public Collection<UUID> checkAnalyticsInSyncWithPayment(final TenantContext context) {
+        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
+        final Collection<UUID> check1 = dao.checkBipMatchesInvoicePayments(internalTenantContext);
+        final Collection<UUID> check2 = dao.checkBinAmountPaidMatchesInvoicePayments(internalTenantContext);
+        final Collection<UUID> check3 = dao.checkBinAmountChargedMatchesInvoicePayments(internalTenantContext);
+
+        return ImmutableSet.<UUID>copyOf(Iterables.<UUID>concat(check1, check2, check3));
+    }
+
+    @Override
+    public Collection<UUID> checkAnalyticsInSyncWithTag(final TenantContext context) {
+        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
+        return dao.checkBacTagsMatchesTags(internalTenantContext);
+    }
+
+    @Override
+    public Collection<UUID> checkAnalyticsConsistency(final TenantContext context) {
+        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
+        final Collection<UUID> check1 = dao.checkBinBiiBalanceConsistency(internalTenantContext);
+        final Collection<UUID> check2 = dao.checkBinBiiAmountCreditedConsistency(internalTenantContext);
+        final Collection<UUID> check3 = dao.checkBacBinBiiConsistency(internalTenantContext);
+
+        return ImmutableSet.<UUID>copyOf(Iterables.<UUID>concat(check1, check2, check3));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/user/DefaultAnalyticsUserApi.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/user/DefaultAnalyticsUserApi.java
new file mode 100644
index 0000000..0364521
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/user/DefaultAnalyticsUserApi.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.api.user;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.ObjectType;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.analytics.api.BusinessAccount;
+import com.ning.billing.analytics.api.BusinessField;
+import com.ning.billing.analytics.api.BusinessInvoice;
+import com.ning.billing.analytics.api.BusinessInvoicePayment;
+import com.ning.billing.analytics.api.BusinessOverdueStatus;
+import com.ning.billing.analytics.api.BusinessSnapshot;
+import com.ning.billing.analytics.api.BusinessSubscriptionTransition;
+import com.ning.billing.analytics.api.BusinessTag;
+import com.ning.billing.analytics.api.TimeSeriesData;
+import com.ning.billing.analytics.api.user.AnalyticsUserApi;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.api.Blockable.Type;
+import com.ning.billing.osgi.bundles.analytics.BusinessAccountDao;
+import com.ning.billing.osgi.bundles.analytics.BusinessInvoiceDao;
+import com.ning.billing.osgi.bundles.analytics.BusinessInvoicePaymentDao;
+import com.ning.billing.osgi.bundles.analytics.BusinessOverdueStatusDao;
+import com.ning.billing.osgi.bundles.analytics.BusinessSubscriptionTransitionDao;
+import com.ning.billing.osgi.bundles.analytics.BusinessTagDao;
+import com.ning.billing.osgi.bundles.analytics.api.DefaultBusinessAccount;
+import com.ning.billing.osgi.bundles.analytics.api.DefaultBusinessInvoice;
+import com.ning.billing.osgi.bundles.analytics.api.DefaultBusinessInvoicePayment;
+import com.ning.billing.osgi.bundles.analytics.api.DefaultBusinessOverdueStatus;
+import com.ning.billing.osgi.bundles.analytics.api.DefaultBusinessSnapshot;
+import com.ning.billing.osgi.bundles.analytics.api.DefaultBusinessSubscriptionTransition;
+import com.ning.billing.osgi.bundles.analytics.api.DefaultBusinessTag;
+import com.ning.billing.osgi.bundles.analytics.dao.AnalyticsDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountTagModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoicePaymentModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessOverdueStatusModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionModelDao;
+import com.ning.billing.payment.api.Payment;
+import com.ning.billing.payment.api.PaymentApiException;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalCallContextFactory;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.billing.util.svcapi.entitlement.EntitlementInternalApi;
+import com.ning.billing.util.svcapi.payment.PaymentInternalApi;
+import com.ning.billing.util.svcapi.tag.TagInternalApi;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+
+public class DefaultAnalyticsUserApi implements AnalyticsUserApi {
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultAnalyticsUserApi.class);
+
+    private final AnalyticsDao analyticsDao;
+    private final BusinessSubscriptionTransitionDao bstDao;
+    private final BusinessAccountDao bacDao;
+    private final BusinessInvoiceDao invoiceDao;
+    private final BusinessOverdueStatusDao bosDao;
+    private final BusinessInvoicePaymentDao bipDao;
+    private final BusinessTagDao tagDao;
+    private final EntitlementInternalApi entitlementInternalApi;
+    private final PaymentInternalApi paymentApi;
+    private final TagInternalApi tagInternalApi;
+    private final InternalCallContextFactory internalCallContextFactory;
+
+    @Inject
+    public DefaultAnalyticsUserApi(final AnalyticsDao analyticsDao,
+                                   final BusinessSubscriptionTransitionDao bstDao,
+                                   final BusinessAccountDao bacDao,
+                                   final BusinessInvoiceDao invoiceDao,
+                                   final BusinessOverdueStatusDao bosDao,
+                                   final BusinessInvoicePaymentDao bipDao,
+                                   final BusinessTagDao tagDao,
+                                   final EntitlementInternalApi entitlementInternalApi,
+                                   final PaymentInternalApi paymentApi,
+                                   final TagInternalApi tagInternalApi,
+                                   final InternalCallContextFactory internalCallContextFactory) {
+        this.analyticsDao = analyticsDao;
+        this.bstDao = bstDao;
+        this.bacDao = bacDao;
+        this.invoiceDao = invoiceDao;
+        this.bosDao = bosDao;
+        this.bipDao = bipDao;
+        this.tagDao = tagDao;
+        this.entitlementInternalApi = entitlementInternalApi;
+        this.paymentApi = paymentApi;
+        this.tagInternalApi = tagInternalApi;
+        this.internalCallContextFactory = internalCallContextFactory;
+    }
+
+    @Override
+    public BusinessSnapshot getBusinessSnapshot(final Account account, final TenantContext context) {
+        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
+
+        // Find account
+        final BusinessAccount businessAccount = getAccountByKey(account.getExternalKey(), context);
+
+        // Find all transitions for all bundles for that account, and associated overdue statuses
+        final List<SubscriptionBundle> bundles = entitlementInternalApi.getBundlesForAccount(account.getId(), internalTenantContext);
+        final Collection<BusinessSubscriptionTransition> businessSubscriptionTransitions = new ArrayList<BusinessSubscriptionTransition>();
+        final Collection<BusinessOverdueStatus> businessOverdueStatuses = new ArrayList<BusinessOverdueStatus>();
+        for (final SubscriptionBundle bundle : bundles) {
+            businessSubscriptionTransitions.addAll(getTransitionsForBundle(bundle.getExternalKey(), context));
+            businessOverdueStatuses.addAll(getOverdueStatusesForBundle(bundle.getExternalKey(), context));
+        }
+
+        // Find all invoices for that account
+        final Collection<BusinessInvoice> businessInvoices = getInvoicesForAccount(account.getExternalKey(), context);
+
+        // Find all payments for that account
+        final Collection<BusinessInvoicePayment> businessInvoicePayments = getInvoicePaymentsForAccount(account.getExternalKey(), context);
+
+        // Find all tags for that account
+        // TODO add other tag types
+        final Collection<BusinessTag> businessTags = getTagsForAccount(account.getExternalKey(), context);
+
+        // TODO find custom fields
+        final Collection<BusinessField> businessFields = ImmutableList.<BusinessField>of();
+
+        return new DefaultBusinessSnapshot(businessAccount, businessSubscriptionTransitions, businessInvoices, businessInvoicePayments,
+                                           businessOverdueStatuses, businessTags, businessFields);
+    }
+
+    @Override
+    public BusinessAccount getAccountByKey(final String accountKey, final TenantContext context) {
+        final BusinessAccountModelDao accountByKey = analyticsDao.getAccountByKey(accountKey, internalCallContextFactory.createInternalTenantContext(context));
+        if (accountByKey == null) {
+            return null;
+        } else {
+            return new DefaultBusinessAccount(accountByKey);
+        }
+    }
+
+    @Override
+    public List<BusinessSubscriptionTransition> getTransitionsForBundle(final String externalKey, final TenantContext context) {
+        final List<BusinessSubscriptionTransitionModelDao> transitionsByKey = analyticsDao.getTransitionsByKey(externalKey, internalCallContextFactory.createInternalTenantContext(context));
+        return ImmutableList.<BusinessSubscriptionTransition>copyOf(Collections2.transform(transitionsByKey, new Function<BusinessSubscriptionTransitionModelDao, BusinessSubscriptionTransition>() {
+            @Override
+            public BusinessSubscriptionTransition apply(@Nullable final BusinessSubscriptionTransitionModelDao input) {
+                return new DefaultBusinessSubscriptionTransition(input);
+            }
+        }));
+    }
+
+    @Override
+    public List<BusinessInvoice> getInvoicesForAccount(final String accountKey, final TenantContext context) {
+        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
+        final List<BusinessInvoiceModelDao> invoicesByKey = analyticsDao.getInvoicesByKey(accountKey, internalTenantContext);
+        return ImmutableList.<BusinessInvoice>copyOf(Collections2.transform(invoicesByKey, new Function<BusinessInvoiceModelDao, BusinessInvoice>() {
+            @Override
+            public BusinessInvoice apply(@Nullable final BusinessInvoiceModelDao input) {
+                return new DefaultBusinessInvoice(input, input == null ? null : analyticsDao.getInvoiceItemsForInvoice(input.getInvoiceId().toString(), internalTenantContext));
+            }
+        }));
+    }
+
+    @Override
+    public List<BusinessInvoicePayment> getInvoicePaymentsForAccount(final String accountKey, final TenantContext context) {
+        final List<BusinessInvoicePaymentModelDao> invoicePaymentsForAccountByKey = analyticsDao.getInvoicePaymentsForAccountByKey(accountKey, internalCallContextFactory.createInternalTenantContext(context));
+        return ImmutableList.<BusinessInvoicePayment>copyOf(Collections2.transform(invoicePaymentsForAccountByKey, new Function<BusinessInvoicePaymentModelDao, BusinessInvoicePayment>() {
+            @Override
+            public BusinessInvoicePayment apply(@Nullable final BusinessInvoicePaymentModelDao input) {
+                return new DefaultBusinessInvoicePayment(input);
+            }
+        }));
+    }
+
+    @Override
+    public List<BusinessOverdueStatus> getOverdueStatusesForBundle(final String externalKey, final TenantContext context) {
+        final List<BusinessOverdueStatusModelDao> overdueStatusesForBundleByKey = analyticsDao.getOverdueStatusesForBundleByKey(externalKey, internalCallContextFactory.createInternalTenantContext(context));
+        return ImmutableList.<BusinessOverdueStatus>copyOf(Collections2.transform(overdueStatusesForBundleByKey, new Function<BusinessOverdueStatusModelDao, BusinessOverdueStatus>() {
+            @Override
+            public BusinessOverdueStatus apply(@Nullable final BusinessOverdueStatusModelDao input) {
+                return new DefaultBusinessOverdueStatus(input);
+            }
+        }));
+    }
+
+    @Override
+    public List<BusinessTag> getTagsForAccount(final String accountKey, final TenantContext context) {
+        final List<BusinessAccountTagModelDao> tagsForAccount = analyticsDao.getTagsForAccount(accountKey, internalCallContextFactory.createInternalTenantContext(context));
+        return ImmutableList.<BusinessTag>copyOf(Collections2.transform(tagsForAccount, new Function<BusinessAccountTagModelDao, BusinessTag>() {
+            @Override
+            public BusinessTag apply(@Nullable final BusinessAccountTagModelDao input) {
+                return new DefaultBusinessTag(input);
+            }
+        }));
+    }
+
+    @Override
+    public TimeSeriesData getAccountsCreatedOverTime(final TenantContext context) {
+        return analyticsDao.getAccountsCreatedOverTime(internalCallContextFactory.createInternalTenantContext(context));
+    }
+
+    @Override
+    public TimeSeriesData getSubscriptionsCreatedOverTime(final String productType, final String slug, final TenantContext context) {
+        return analyticsDao.getSubscriptionsCreatedOverTime(productType, slug, internalCallContextFactory.createInternalTenantContext(context));
+    }
+
+    @Override
+    public void rebuildAnalyticsForAccount(final Account account, final CallContext context) {
+        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), context);
+
+        // Update the BAC row
+        bacDao.accountUpdated(account.getId(), internalCallContext);
+
+        // Update BST for all bundles
+        final Set<UUID> bundleIds = updateBST(account, internalCallContext);
+
+        // Update BIN and BII for all invoices
+        invoiceDao.rebuildInvoicesForAccount(account.getId(), internalCallContext);
+
+        // Update BIP for all invoices
+        try {
+            updateBIP(account, internalCallContext);
+        } catch (PaymentApiException e) {
+            // Log and ignore
+            log.warn(e.toString());
+        }
+
+        // Update BOS for all bundles (only blockable supported today)
+        // TODO: support other blockables
+        for (final UUID bundleId : bundleIds) {
+            bosDao.overdueStatusChanged(Type.SUBSCRIPTION_BUNDLE, bundleId, internalCallContext);
+        }
+
+        // Update bac_tags
+        // TODO: refresh all tags
+        updateTags(account, internalCallContext);
+    }
+
+    private Set<UUID> updateBST(final Account account, final InternalCallContext internalCallContext) {
+        // Find the current state of bundles in entitlement
+        final Collection<UUID> entitlementBundlesId = Collections2.transform(entitlementInternalApi.getBundlesForAccount(account.getId(), internalCallContext),
+                                                                             new Function<SubscriptionBundle, UUID>() {
+                                                                                 @Override
+                                                                                 public UUID apply(@Nullable final SubscriptionBundle input) {
+                                                                                     if (input == null) {
+                                                                                         return null;
+                                                                                     } else {
+                                                                                         return input.getId();
+                                                                                     }
+                                                                                 }
+                                                                             });
+
+        // Find the current state of bundles in analytics
+        final Collection<UUID> analyticsBundlesId = Collections2.transform(analyticsDao.getTransitionsForAccount(account.getExternalKey(), internalCallContext),
+                                                                           new Function<BusinessSubscriptionTransitionModelDao, UUID>() {
+                                                                               @Override
+                                                                               public UUID apply(@Nullable final BusinessSubscriptionTransitionModelDao input) {
+                                                                                   if (input == null) {
+                                                                                       return null;
+                                                                                   } else {
+                                                                                       return input.getBundleId();
+                                                                                   }
+                                                                               }
+                                                                           });
+
+        // Update BST for all bundles found
+        final Set<UUID> bundlesId = new HashSet<UUID>();
+        bundlesId.addAll(entitlementBundlesId);
+        bundlesId.addAll(analyticsBundlesId);
+        for (final UUID bundleId : bundlesId) {
+            bstDao.rebuildTransitionsForBundle(bundleId, internalCallContext);
+        }
+
+        return bundlesId;
+    }
+
+    private void updateBIP(final Account account, final InternalCallContext internalCallContext) throws PaymentApiException {
+        final List<Payment> accountPayments = paymentApi.getAccountPayments(account.getId(), internalCallContext);
+        final Map<UUID, Payment> payments = new HashMap<UUID, Payment>();
+        for (final Payment payment : accountPayments) {
+            payments.put(payment.getId(), payment);
+        }
+
+        // Find the current state of payments in payment
+        final Collection<UUID> paymentPaymentsId = Collections2.transform(accountPayments,
+                                                                          new Function<Payment, UUID>() {
+                                                                              @Override
+                                                                              public UUID apply(@Nullable final Payment input) {
+                                                                                  if (input == null) {
+                                                                                      return null;
+                                                                                  } else {
+                                                                                      return input.getId();
+                                                                                  }
+                                                                              }
+                                                                          });
+
+        // Find the current state of payments in analytics
+        final Collection<UUID> analyticsPaymentsId = Collections2.transform(analyticsDao.getInvoicePaymentsForAccountByKey(account.getExternalKey(), internalCallContext),
+                                                                            new Function<BusinessInvoicePaymentModelDao, UUID>() {
+                                                                                @Override
+                                                                                public UUID apply(@Nullable final BusinessInvoicePaymentModelDao input) {
+                                                                                    if (input == null) {
+                                                                                        return null;
+                                                                                    } else {
+                                                                                        return input.getPaymentId();
+                                                                                    }
+                                                                                }
+                                                                            });
+
+        // Update BIP for all payments found
+        final Set<UUID> paymentsId = new HashSet<UUID>();
+        paymentsId.addAll(paymentPaymentsId);
+        paymentsId.addAll(analyticsPaymentsId);
+        for (final UUID paymentId : paymentsId) {
+            final Payment paymentInfo = payments.get(paymentId);
+            bipDao.invoicePaymentPosted(paymentInfo.getAccountId(),
+                                        paymentInfo.getId(),
+                                        paymentInfo.getPaymentStatus().toString(),
+                                        internalCallContext);
+        }
+    }
+
+    private void updateTags(final Account account, final InternalCallContext internalCallContext) {
+        // Find the current state of tags from util
+        final List<TagDefinition> tagDefinitions = tagInternalApi.getTagDefinitions(internalCallContext);
+        final Collection<String> utilTags = Collections2.transform(tagInternalApi.getTags(account.getId(), ObjectType.ACCOUNT, internalCallContext),
+                                                                   new Function<Tag, String>() {
+                                                                       @Override
+                                                                       public String apply(@Nullable final Tag input) {
+                                                                           if (input == null) {
+                                                                               return "";
+                                                                           }
+
+                                                                           for (final TagDefinition tagDefinition : tagDefinitions) {
+                                                                               if (tagDefinition.getId().equals(input.getTagDefinitionId())) {
+                                                                                   return tagDefinition.getName();
+                                                                               }
+                                                                           }
+                                                                           return "";
+                                                                       }
+                                                                   });
+
+        // Find the current state of tags in analytics
+        final Collection<String> analyticsTags = Collections2.transform(analyticsDao.getTagsForAccount(account.getExternalKey(), internalCallContext),
+                                                                        new Function<BusinessAccountTagModelDao, String>() {
+                                                                            @Override
+                                                                            public String apply(@Nullable final BusinessAccountTagModelDao input) {
+                                                                                if (input == null) {
+                                                                                    return null;
+                                                                                } else {
+                                                                                    return input.getName();
+                                                                                }
+                                                                            }
+                                                                        });
+
+        // Remove non existing tags
+        for (final String tag : Sets.difference(new HashSet<String>(analyticsTags), new HashSet<String>(utilTags))) {
+            tagDao.tagRemoved(ObjectType.ACCOUNT, account.getId(), tag, internalCallContext);
+        }
+
+        // Add missing ones
+        for (final String tag : Sets.difference(new HashSet<String>(utilTags), new HashSet<String>(analyticsTags))) {
+            tagDao.tagAdded(ObjectType.ACCOUNT, account.getId(), tag, internalCallContext);
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/BusinessAccountDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/BusinessAccountDao.java
new file mode 100644
index 0000000..0bce215
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/BusinessAccountDao.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountSqlDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountModelDao;
+import com.ning.billing.payment.api.Payment;
+import com.ning.billing.payment.api.PaymentApiException;
+import com.ning.billing.payment.api.PaymentMethod;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.svcapi.account.AccountInternalApi;
+import com.ning.billing.util.svcapi.invoice.InvoiceInternalApi;
+import com.ning.billing.util.svcapi.payment.PaymentInternalApi;
+
+import com.google.inject.Inject;
+
+public class BusinessAccountDao {
+
+    private static final Logger log = LoggerFactory.getLogger(BusinessAccountDao.class);
+
+    private final BusinessAccountSqlDao sqlDao;
+    private final AccountInternalApi accountApi;
+    private final InvoiceInternalApi invoiceApi;
+    private final PaymentInternalApi paymentApi;
+
+    @Inject
+    public BusinessAccountDao(final BusinessAccountSqlDao sqlDao, final AccountInternalApi accountApi,
+                              final InvoiceInternalApi invoiceApi, final PaymentInternalApi paymentApi) {
+        this.sqlDao = sqlDao;
+        this.accountApi = accountApi;
+        this.invoiceApi = invoiceApi;
+        this.paymentApi = paymentApi;
+    }
+
+    public void accountUpdated(final UUID accountId, final InternalCallContext context) {
+        final Account account;
+        try {
+            account = accountApi.getAccountById(accountId, context);
+        } catch (AccountApiException e) {
+            log.warn("Error encountered creating BusinessAccountModelDao", e);
+            return;
+        }
+
+        final BusinessAccountModelDao bac = createBusinessAccountFromAccount(account, context);
+        sqlDao.inTransaction(new Transaction<Void, BusinessAccountSqlDao>() {
+            @Override
+            public Void inTransaction(final BusinessAccountSqlDao transactional, final TransactionStatus status) throws Exception {
+                updateAccountInTransaction(bac, transactional, context);
+                return null;
+            }
+        });
+    }
+
+    // Called also from BusinessInvoiceDao and BusinessInvoicePaymentDao.
+    // Note: computing the BusinessAccountModelDao object is fairly expensive, hence should be done outside of the transaction
+    public void updateAccountInTransaction(final BusinessAccountModelDao bac, final BusinessAccountSqlDao transactional, final InternalCallContext context) {
+        log.info("ACCOUNT UPDATE " + bac);
+        transactional.deleteAccount(bac.getAccountId().toString(), context);
+        // Note! There is a window of doom here since we use read committed transactional level by default
+        transactional.createAccount(bac, context);
+    }
+
+    public BusinessAccountModelDao createBusinessAccountFromAccount(final Account account, final InternalTenantContext context) {
+        final BusinessAccountModelDao bac = new BusinessAccountModelDao(account);
+
+        try {
+            LocalDate lastInvoiceDate = bac.getLastInvoiceDate();
+            BigDecimal totalInvoiceBalance = bac.getTotalInvoiceBalance();
+            String lastPaymentStatus = bac.getLastPaymentStatus();
+            String paymentMethodType = bac.getPaymentMethod();
+            String creditCardType = bac.getCreditCardType();
+            String billingAddressCountry = bac.getBillingAddressCountry();
+
+            // Retrieve invoices information
+            final Collection<Invoice> invoices = invoiceApi.getInvoicesByAccountId(account.getId(), context);
+            if (invoices != null && invoices.size() > 0) {
+                for (final Invoice invoice : invoices) {
+                    totalInvoiceBalance = totalInvoiceBalance.add(invoice.getBalance());
+
+                    if (lastInvoiceDate == null || invoice.getInvoiceDate().isAfter(lastInvoiceDate)) {
+                        lastInvoiceDate = invoice.getInvoiceDate();
+                    }
+                }
+
+                // Retrieve payments information for these invoices
+                DateTime lastPaymentDate = null;
+
+                final List<Payment> payments = paymentApi.getAccountPayments(account.getId(), context);
+                if (payments != null) {
+                    for (final Payment cur : payments) {
+                        // Use the last payment method/type/country as the default one for the account
+                        if (lastPaymentDate == null || cur.getEffectiveDate().isAfter(lastPaymentDate)) {
+                            lastPaymentDate = cur.getEffectiveDate();
+                            lastPaymentStatus = cur.getPaymentStatus().toString();
+                        }
+                    }
+                }
+            }
+
+            // Retrieve payment methods
+            for (final PaymentMethod paymentMethod : paymentApi.getPaymentMethods(account, context)) {
+                if (paymentMethod.getId().equals(account.getPaymentMethodId()) && paymentMethod.getPluginDetail() != null) {
+                    paymentMethodType = PaymentMethodUtils.getPaymentMethodType(paymentMethod.getPluginDetail());
+                    creditCardType = PaymentMethodUtils.getCardType(paymentMethod.getPluginDetail());
+                    billingAddressCountry = PaymentMethodUtils.getCardCountry(paymentMethod.getPluginDetail());
+                    break;
+                }
+            }
+
+            bac.setLastPaymentStatus(lastPaymentStatus);
+            bac.setPaymentMethod(paymentMethodType);
+            bac.setCreditCardType(creditCardType);
+            bac.setBillingAddressCountry(billingAddressCountry);
+            bac.setLastInvoiceDate(lastInvoiceDate);
+            bac.setTotalInvoiceBalance(totalInvoiceBalance);
+
+            bac.setBalance(invoiceApi.getAccountBalance(account.getId(), context));
+        } catch (PaymentApiException ex) {
+            log.error(String.format("Failed to handle account update for account %s", account.getId()), ex);
+        }
+
+        return bac;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/BusinessInvoiceDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/BusinessInvoiceDao.java
new file mode 100644
index 0000000..efbb85e
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/BusinessInvoiceDao.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceItemSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceSqlDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceItemModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceModelDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.svcapi.account.AccountInternalApi;
+import com.ning.billing.util.svcapi.entitlement.EntitlementInternalApi;
+import com.ning.billing.util.svcapi.invoice.InvoiceInternalApi;
+
+import com.google.common.annotations.VisibleForTesting;
+
+public class BusinessInvoiceDao {
+
+    private static final Logger log = LoggerFactory.getLogger(BusinessInvoiceDao.class);
+
+    private final AccountInternalApi accountApi;
+    private final EntitlementInternalApi entitlementApi;
+    private final InvoiceInternalApi invoiceApi;
+    private final BusinessAccountDao businessAccountDao;
+    private final BusinessInvoiceSqlDao sqlDao;
+    private final CatalogService catalogService;
+
+    @Inject
+    public BusinessInvoiceDao(final AccountInternalApi accountApi,
+                              final EntitlementInternalApi entitlementApi,
+                              final InvoiceInternalApi invoiceApi,
+                              final BusinessAccountDao businessAccountDao,
+                              final BusinessInvoiceSqlDao sqlDao,
+                              final CatalogService catalogService) {
+        this.accountApi = accountApi;
+        this.entitlementApi = entitlementApi;
+        this.invoiceApi = invoiceApi;
+        this.businessAccountDao = businessAccountDao;
+        this.sqlDao = sqlDao;
+        this.catalogService = catalogService;
+    }
+
+    public void rebuildInvoicesForAccount(final UUID accountId, final InternalCallContext context) {
+        // Lookup the associated account
+        final Account account;
+        try {
+            account = accountApi.getAccountById(accountId, context);
+        } catch (AccountApiException e) {
+            log.warn("Ignoring invoice update for account id {} (account does not exist)", accountId);
+            return;
+        }
+
+        // Lookup the invoices for that account
+        final Collection<Invoice> invoices = invoiceApi.getInvoicesByAccountId(account.getId(), context);
+
+        // Create the business invoice and associated business invoice items
+        final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemModelDao>> businessInvoices = new HashMap<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemModelDao>>();
+        for (final Invoice invoice : invoices) {
+            final BusinessInvoiceModelDao businessInvoice = new BusinessInvoiceModelDao(account.getExternalKey(), invoice);
+
+            final List<BusinessInvoiceItemModelDao> businessInvoiceItems = new ArrayList<BusinessInvoiceItemModelDao>();
+            for (final InvoiceItem invoiceItem : invoice.getInvoiceItems()) {
+                final BusinessInvoiceItemModelDao businessInvoiceItem = createBusinessInvoiceItem(invoiceItem, context);
+                if (businessInvoiceItem != null) {
+                    businessInvoiceItems.add(businessInvoiceItem);
+                }
+            }
+
+            businessInvoices.put(businessInvoice, businessInvoiceItems);
+        }
+
+        // Update the account record
+        final BusinessAccountModelDao bac = businessAccountDao.createBusinessAccountFromAccount(account, context);
+
+        // Delete and recreate invoice and invoice items in the transaction
+        sqlDao.inTransaction(new Transaction<Void, BusinessInvoiceSqlDao>() {
+            @Override
+            public Void inTransaction(final BusinessInvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
+                rebuildInvoicesForAccountInTransaction(account, businessInvoices, transactional, context);
+
+                // Update balance, last invoice date and total invoice balance in BAC
+                final BusinessAccountSqlDao accountSqlDao = transactional.become(BusinessAccountSqlDao.class);
+                businessAccountDao.updateAccountInTransaction(bac, accountSqlDao, context);
+                return null;
+            }
+        });
+    }
+
+    // Used by BIP Recorder
+    public void rebuildInvoiceInTransaction(final String accountKey, final Invoice invoice,
+                                            final BusinessInvoiceSqlDao transactional, final InternalCallContext context) {
+        // Delete the invoice
+        transactional.deleteInvoice(invoice.getId().toString(), context);
+
+        // Re-create it - this will update the various amounts
+        transactional.createInvoice(new BusinessInvoiceModelDao(accountKey, invoice), context);
+    }
+
+    private void rebuildInvoicesForAccountInTransaction(final Account account, final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemModelDao>> businessInvoices,
+                                                        final BusinessInvoiceSqlDao transactional, final InternalCallContext context) {
+        log.info("Started rebuilding invoices for account id {}", account.getId());
+        deleteInvoicesAndInvoiceItemsForAccountInTransaction(transactional, account.getId(), context);
+
+        for (final BusinessInvoiceModelDao businessInvoice : businessInvoices.keySet()) {
+            createInvoiceInTransaction(transactional, businessInvoice, businessInvoices.get(businessInvoice), context);
+        }
+
+        log.info("Finished rebuilding invoices for account id {}", account.getId());
+    }
+
+    private void deleteInvoicesAndInvoiceItemsForAccountInTransaction(final BusinessInvoiceSqlDao transactional,
+                                                                      final UUID accountId, final InternalCallContext context) {
+        // We don't use on cascade delete here as we don't want the database layer to be generic - hence we have
+        // to delete the invoice items manually.
+        // Note: invoice items should go first (see query)
+        final BusinessInvoiceItemSqlDao invoiceItemSqlDao = transactional.become(BusinessInvoiceItemSqlDao.class);
+        log.info("Deleting invoice items for account {}", accountId);
+        invoiceItemSqlDao.deleteInvoiceItemsForAccount(accountId.toString(), context);
+
+        log.info("Deleting invoices for account {}", accountId);
+        transactional.deleteInvoicesForAccount(accountId.toString(), context);
+    }
+
+    private void createInvoiceInTransaction(final BusinessInvoiceSqlDao transactional, final BusinessInvoiceModelDao invoice,
+                                            final Iterable<BusinessInvoiceItemModelDao> invoiceItems, final InternalCallContext context) {
+        // Create the invoice
+        log.info("Adding invoice {}", invoice);
+        transactional.createInvoice(invoice, context);
+
+        // Add associated invoice items
+        final BusinessInvoiceItemSqlDao invoiceItemSqlDao = transactional.become(BusinessInvoiceItemSqlDao.class);
+        for (final BusinessInvoiceItemModelDao invoiceItem : invoiceItems) {
+            log.info("Adding invoice item {}", invoiceItem);
+            invoiceItemSqlDao.createInvoiceItem(invoiceItem, context);
+        }
+    }
+
+    @VisibleForTesting
+    BusinessInvoiceItemModelDao createBusinessInvoiceItem(final InvoiceItem invoiceItem, final InternalTenantContext context) {
+        String externalKey = null;
+        Plan plan = null;
+        PlanPhase planPhase = null;
+
+        // Subscription and bundle could be null for e.g. credits or adjustments
+        if (invoiceItem.getBundleId() != null) {
+            try {
+                final SubscriptionBundle bundle = entitlementApi.getBundleFromId(invoiceItem.getBundleId(), context);
+                externalKey = bundle.getExternalKey();
+            } catch (EntitlementUserApiException e) {
+                log.warn("Ignoring subscription fields for invoice item {} for bundle {} (bundle does not exist)",
+                         invoiceItem.getId().toString(),
+                         invoiceItem.getBundleId().toString());
+            }
+        }
+
+        if (invoiceItem.getPlanName() != null) {
+            try {
+                plan = catalogService.getFullCatalog().findPlan(invoiceItem.getPlanName(), invoiceItem.getStartDate().toDateTimeAtStartOfDay());
+            } catch (CatalogApiException e) {
+                log.warn("Unable to retrieve plan for invoice item {}", invoiceItem.getId());
+            }
+        }
+
+        if (invoiceItem.getSubscriptionId() != null && invoiceItem.getPhaseName() != null) {
+            final Subscription subscription;
+            try {
+                subscription = entitlementApi.getSubscriptionFromId(invoiceItem.getSubscriptionId(), context);
+                planPhase = catalogService.getFullCatalog().findPhase(invoiceItem.getPhaseName(), invoiceItem.getStartDate().toDateTimeAtStartOfDay(), subscription.getStartDate());
+            } catch (EntitlementUserApiException e) {
+                log.warn("Ignoring subscription fields for invoice item {} for subscription {} (subscription does not exist)",
+                         invoiceItem.getId().toString(),
+                         invoiceItem.getSubscriptionId().toString());
+            } catch (CatalogApiException e) {
+                log.warn("Unable to retrieve phase for invoice item {}", invoiceItem.getId());
+            }
+        }
+
+        return new BusinessInvoiceItemModelDao(externalKey, invoiceItem, plan, planPhase);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/BusinessInvoicePaymentDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/BusinessInvoicePaymentDao.java
new file mode 100644
index 0000000..b39ad51
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/BusinessInvoicePaymentDao.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoicePaymentSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceSqlDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoicePaymentModelDao;
+import com.ning.billing.payment.api.Payment;
+import com.ning.billing.payment.api.PaymentApiException;
+import com.ning.billing.payment.api.PaymentMethod;
+import com.ning.billing.payment.api.PaymentMethodPlugin;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.svcapi.account.AccountInternalApi;
+import com.ning.billing.util.svcapi.invoice.InvoiceInternalApi;
+import com.ning.billing.util.svcapi.payment.PaymentInternalApi;
+
+public class BusinessInvoicePaymentDao {
+
+    private static final Logger log = LoggerFactory.getLogger(BusinessInvoicePaymentDao.class);
+
+    private final BusinessInvoicePaymentSqlDao invoicePaymentSqlDao;
+    private final AccountInternalApi accountApi;
+    private final InvoiceInternalApi invoiceApi;
+    private final PaymentInternalApi paymentApi;
+    private final Clock clock;
+    private final BusinessInvoiceDao invoiceDao;
+    private final BusinessAccountDao accountDao;
+
+    @Inject
+    public BusinessInvoicePaymentDao(final BusinessInvoicePaymentSqlDao invoicePaymentSqlDao, final AccountInternalApi accountApi,
+                                     final InvoiceInternalApi invoiceApi, final PaymentInternalApi paymentApi,
+                                     final Clock clock, final BusinessInvoiceDao invoiceDao, final BusinessAccountDao accountDao) {
+        this.invoicePaymentSqlDao = invoicePaymentSqlDao;
+        this.accountApi = accountApi;
+        this.invoiceApi = invoiceApi;
+        this.paymentApi = paymentApi;
+        this.clock = clock;
+        this.invoiceDao = invoiceDao;
+        this.accountDao = accountDao;
+    }
+
+    public void invoicePaymentPosted(final UUID accountId, @Nullable final UUID paymentId, final String message, final InternalCallContext context) {
+        // Payment attempt with no default payment method. Ignore.
+        if (paymentId == null) {
+            return;
+        }
+
+        final Account account;
+        try {
+            account = accountApi.getAccountById(accountId, context);
+        } catch (AccountApiException e) {
+            log.warn("Ignoring payment {}: account {} does not exist", paymentId, accountId);
+            return;
+        }
+
+        final Payment payment;
+        try {
+            payment = paymentApi.getPayment(paymentId, context);
+        } catch (PaymentApiException e) {
+            log.warn("Ignoring payment {}: payment does not exist", paymentId);
+            return;
+        }
+
+        PaymentMethod paymentMethod = null;
+        try {
+            paymentMethod = paymentApi.getPaymentMethodById(payment.getPaymentMethodId(), context);
+        } catch (PaymentApiException e) {
+            log.info("For payment {}: payment method {} does not exist", paymentId, payment.getPaymentMethodId());
+        }
+
+        Invoice invoice = null;
+        InvoicePayment invoicePayment = null;
+        try {
+            invoicePayment = invoiceApi.getInvoicePaymentForAttempt(paymentId, context);
+            if (invoicePayment != null) {
+                invoice = invoiceApi.getInvoiceById(invoicePayment.getInvoiceId(), context);
+            }
+        } catch (InvoiceApiException e) {
+            log.warn("Unable to find invoice {} for payment {}",
+                     invoicePayment != null ? invoicePayment.getInvoiceId() : "unknown", paymentId);
+        }
+
+        createPayment(account, invoice, invoicePayment, payment, paymentMethod, message, context);
+    }
+
+    private void createPayment(final Account account, @Nullable final Invoice invoice, @Nullable final InvoicePayment invoicePayment, final Payment payment,
+                               @Nullable final PaymentMethod paymentMethod,
+                               final String message, final InternalCallContext context) {
+        // paymentMethod may be null if the payment method has been deleted
+        final String cardCountry;
+        final String cardType;
+        final String paymentMethodString;
+        if (paymentMethod != null) {
+            final PaymentMethodPlugin pluginDetail = paymentMethod.getPluginDetail();
+            cardCountry = PaymentMethodUtils.getCardCountry(pluginDetail);
+            cardType = PaymentMethodUtils.getCardType(pluginDetail);
+            paymentMethodString = PaymentMethodUtils.getPaymentMethodType(pluginDetail);
+        } else {
+            cardCountry = null;
+            cardType = null;
+            paymentMethodString = null;
+        }
+
+        // invoicePayment may be null on payment failures
+        final String invoicePaymentType;
+        final UUID linkedInvoicePaymentId;
+        final DateTime createdDate;
+        final DateTime updatedDate;
+        if (invoicePayment != null) {
+            invoicePaymentType = invoicePayment.getType().toString();
+            linkedInvoicePaymentId = invoicePayment.getLinkedInvoicePaymentId();
+            createdDate = invoicePayment.getCreatedDate();
+            updatedDate = invoicePayment.getUpdatedDate();
+        } else {
+            invoicePaymentType = null;
+            linkedInvoicePaymentId = null;
+            // TODO PIERRE
+            createdDate = clock.getUTCNow();
+            updatedDate = createdDate;
+        }
+
+        final BusinessInvoicePaymentModelDao businessInvoicePayment = new BusinessInvoicePaymentModelDao(
+                account.getExternalKey(),
+                payment.getAmount(),
+                cardCountry,
+                cardType,
+                createdDate,
+                payment.getCurrency(),
+                payment.getEffectiveDate(),
+                payment.getInvoiceId(),
+                message,
+                payment.getId(),
+                paymentMethodString,
+                "Electronic",
+                paymentMethod == null ? null : paymentMethod.getPluginName(),
+                payment.getPaymentStatus().toString(),
+                payment.getAmount(),
+                updatedDate,
+                invoicePaymentType,
+                linkedInvoicePaymentId);
+
+        // Update the account record
+        final BusinessAccountModelDao bac = accountDao.createBusinessAccountFromAccount(account, context);
+
+        // Make sure to limit the scope of the transaction to avoid InnoDB deadlocks
+        invoicePaymentSqlDao.inTransaction(new Transaction<Void, BusinessInvoicePaymentSqlDao>() {
+            @Override
+            public Void inTransaction(final BusinessInvoicePaymentSqlDao transactional, final TransactionStatus status) throws Exception {
+                // Delete the existing payment if it exists - this is to make the call idempotent
+                transactional.deleteInvoicePayment(payment.getId().toString(), context);
+
+                // Create the bip record
+                transactional.createInvoicePayment(businessInvoicePayment, context);
+
+                if (invoice != null) {
+                    // Update bin to get the latest invoice balance
+                    final BusinessInvoiceSqlDao invoiceSqlDao = transactional.become(BusinessInvoiceSqlDao.class);
+                    invoiceDao.rebuildInvoiceInTransaction(account.getExternalKey(), invoice, invoiceSqlDao, context);
+                }
+
+                // Update bac to get the latest account balance, total invoice balance, etc.
+                final BusinessAccountSqlDao accountSqlDao = transactional.become(BusinessAccountSqlDao.class);
+                accountDao.updateAccountInTransaction(bac, accountSqlDao, context);
+
+                log.info("Added payment {}", businessInvoicePayment);
+                return null;
+            }
+        });
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/BusinessOverdueStatusDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/BusinessOverdueStatusDao.java
new file mode 100644
index 0000000..16a8303
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/BusinessOverdueStatusDao.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import java.util.List;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.api.Blockable;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessOverdueStatusSqlDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessOverdueStatusModelDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.svcapi.account.AccountInternalApi;
+import com.ning.billing.util.svcapi.entitlement.EntitlementInternalApi;
+import com.ning.billing.util.svcapi.junction.BlockingInternalApi;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+public class BusinessOverdueStatusDao {
+
+    private static final Logger log = LoggerFactory.getLogger(BusinessOverdueStatusDao.class);
+
+    private final BusinessOverdueStatusSqlDao overdueStatusSqlDao;
+
+    private final AccountInternalApi accountApi;
+    private final EntitlementInternalApi entitlementApi;
+    private final BlockingInternalApi blockingApi;
+
+    @Inject
+    public BusinessOverdueStatusDao(final BusinessOverdueStatusSqlDao overdueStatusSqlDao, final AccountInternalApi accountApi,
+                                    final EntitlementInternalApi entitlementApi, final BlockingInternalApi blockingApi) {
+
+        this.overdueStatusSqlDao = overdueStatusSqlDao;
+        this.accountApi = accountApi;
+        this.entitlementApi = entitlementApi;
+        this.blockingApi = blockingApi;
+    }
+
+    public void overdueStatusChanged(final Blockable.Type objectType, final UUID objectId, final InternalCallContext context) {
+        if (Blockable.Type.SUBSCRIPTION_BUNDLE.equals(objectType)) {
+            overdueStatusChangedForBundle(objectId, context);
+        } else {
+            log.info("Ignoring overdue status change for object id {} (type {})", objectId.toString(), objectType.toString());
+        }
+    }
+
+    private void overdueStatusChangedForBundle(final UUID bundleId, final InternalCallContext context) {
+        final SubscriptionBundle bundle;
+        try {
+            bundle = entitlementApi.getBundleFromId(bundleId, context);
+        } catch (EntitlementUserApiException e) {
+            log.warn("Ignoring update for bundle {}: bundle does not exist", bundleId);
+            return;
+        }
+
+        final Account account;
+        try {
+            account = accountApi.getAccountById(bundle.getAccountId(), context);
+        } catch (AccountApiException e) {
+            log.warn("Ignoring update for bundle {}: account {} does not exist", bundleId, bundle.getAccountId());
+            return;
+        }
+
+        final String accountKey = account.getExternalKey();
+        final String externalKey = bundle.getExternalKey();
+
+        overdueStatusSqlDao.inTransaction(new Transaction<Void, BusinessOverdueStatusSqlDao>() {
+            @Override
+            public Void inTransaction(final BusinessOverdueStatusSqlDao transactional, final TransactionStatus status) throws Exception {
+                log.info("Started rebuilding overdue statuses for bundle id {}", bundleId);
+                transactional.deleteOverdueStatusesForBundle(bundleId.toString(), context);
+                final List<BlockingState> blockingHistory = blockingApi.getBlockingHistory(bundleId, context);
+                if (blockingHistory != null && blockingHistory.size() > 0) {
+                    final List<BlockingState> overdueStates = ImmutableList.<BlockingState>copyOf(blockingHistory);
+                    final List<BlockingState> overdueStatesReversed = Lists.reverse(overdueStates);
+
+                    DateTime previousStartDate = null;
+                    for (final BlockingState state : overdueStatesReversed) {
+                        final BusinessOverdueStatusModelDao overdueStatus = new BusinessOverdueStatusModelDao(accountKey, bundleId, previousStartDate,
+                                                                                                              externalKey, state.getTimestamp(), state.getStateName());
+                        log.info("Adding overdue state {}", overdueStatus);
+                        overdueStatusSqlDao.createOverdueStatus(overdueStatus, context);
+
+                        previousStartDate = state.getTimestamp();
+                    }
+                }
+
+                log.info("Finished rebuilding overdue statuses for bundle id {}", bundleId);
+                return null;
+            }
+        });
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/BusinessSubscriptionTransitionDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/BusinessSubscriptionTransitionDao.java
new file mode 100644
index 0000000..cdf110f
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/BusinessSubscriptionTransitionDao.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionSqlDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscription;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionEvent;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionModelDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.events.EffectiveSubscriptionInternalEvent;
+import com.ning.billing.util.events.SubscriptionInternalEvent;
+import com.ning.billing.util.svcapi.account.AccountInternalApi;
+import com.ning.billing.util.svcapi.entitlement.EntitlementInternalApi;
+
+import com.google.inject.Inject;
+
+public class BusinessSubscriptionTransitionDao {
+
+    private static final Logger log = LoggerFactory.getLogger(BusinessSubscriptionTransitionDao.class);
+
+    private final BusinessSubscriptionTransitionSqlDao sqlDao;
+    private final EntitlementInternalApi entitlementApi;
+    private final AccountInternalApi accountApi;
+    private final CatalogService catalogService;
+
+    @Inject
+    public BusinessSubscriptionTransitionDao(final BusinessSubscriptionTransitionSqlDao sqlDao,
+                                             final CatalogService catalogService,
+                                             final EntitlementInternalApi entitlementApi,
+                                             final AccountInternalApi accountApi) {
+        this.sqlDao = sqlDao;
+        this.catalogService = catalogService;
+        this.entitlementApi = entitlementApi;
+        this.accountApi = accountApi;
+    }
+
+    public void rebuildTransitionsForBundle(final UUID bundleId, final InternalCallContext context) {
+        final SubscriptionBundle bundle;
+        try {
+            bundle = entitlementApi.getBundleFromId(bundleId, context);
+        } catch (EntitlementUserApiException e) {
+            log.warn("Ignoring update for bundle {}: bundle does not exist", bundleId);
+            return;
+        }
+
+        final Account account;
+        try {
+            account = accountApi.getAccountById(bundle.getAccountId(), context);
+        } catch (AccountApiException e) {
+            log.warn("Ignoring update for bundle {}: account {} does not exist", bundleId, bundle.getAccountId());
+            return;
+        }
+
+        final List<Subscription> subscriptions = entitlementApi.getSubscriptionsForBundle(bundleId, context);
+
+        final Currency currency = account.getCurrency();
+
+        sqlDao.inTransaction(new Transaction<Void, BusinessSubscriptionTransitionSqlDao>() {
+            @Override
+            public Void inTransaction(final BusinessSubscriptionTransitionSqlDao transactional, final TransactionStatus status) throws Exception {
+                log.info("Started rebuilding transitions for bundle id {}", bundleId);
+                transactional.deleteTransitionsForBundle(bundleId.toString(), context);
+
+                final ArrayList<BusinessSubscriptionTransitionModelDao> transitions = new ArrayList<BusinessSubscriptionTransitionModelDao>();
+                for (final Subscription subscription : subscriptions) {
+                    // TODO remove API call from within transaction, although this is NOT a real issue as this call wil not hit the DB
+                    for (final EffectiveSubscriptionInternalEvent event : entitlementApi.getAllTransitions(subscription, context)) {
+                        final BusinessSubscriptionEvent businessEvent = getBusinessSubscriptionFromEvent(event);
+                        if (businessEvent == null) {
+                            continue;
+                        }
+
+                        final BusinessSubscription prevSubscription = createPreviousBusinessSubscription(event, businessEvent, transitions, currency);
+                        final BusinessSubscription nextSubscription = createNextBusinessSubscription(event, businessEvent, currency);
+                        final BusinessSubscriptionTransitionModelDao transition = new BusinessSubscriptionTransitionModelDao(
+                                event.getTotalOrdering(),
+                                bundleId,
+                                bundle.getExternalKey(),
+                                bundle.getAccountId(),
+                                account.getExternalKey(),
+                                subscription.getId(),
+                                event.getRequestedTransitionTime(),
+                                businessEvent,
+                                prevSubscription,
+                                nextSubscription
+                        );
+
+                        transactional.createTransition(transition, context);
+                        transitions.add(transition);
+                        log.info("Adding transition {}", transition);
+
+                        // We need to manually add the system cancel event
+                        if (SubscriptionTransitionType.CANCEL.equals(event.getTransitionType())) {
+                            final BusinessSubscriptionTransitionModelDao systemCancelTransition = new BusinessSubscriptionTransitionModelDao(
+                                    event.getTotalOrdering(),
+                                    bundleId,
+                                    bundle.getExternalKey(),
+                                    bundle.getAccountId(),
+                                    account.getExternalKey(),
+                                    subscription.getId(),
+                                    // Note! The system cancel event requested time is the effective time when the subscription
+                                    // is cancelled, which is the effective time of the cancel event
+                                    event.getEffectiveTransitionTime(),
+                                    new BusinessSubscriptionEvent(BusinessSubscriptionEvent.EventType.SYSTEM_CANCEL, businessEvent.getCategory()),
+                                    prevSubscription,
+                                    nextSubscription
+                            );
+                            transactional.createTransition(systemCancelTransition, context);
+                            transitions.add(systemCancelTransition);
+                            log.info("Adding transition {}", systemCancelTransition);
+                        }
+                    }
+                }
+
+                log.info("Finished rebuilding transitions for bundle id {}", bundleId);
+                return null;
+            }
+        });
+    }
+
+    private BusinessSubscriptionEvent getBusinessSubscriptionFromEvent(final SubscriptionInternalEvent event) throws AccountApiException, EntitlementUserApiException {
+        switch (event.getTransitionType()) {
+            // A subscription enters either through migration or as newly created subscription
+            case MIGRATE_ENTITLEMENT:
+                return subscriptionMigrated(event);
+            case CREATE:
+                return subscriptionCreated(event);
+            case RE_CREATE:
+                return subscriptionRecreated(event);
+            case TRANSFER:
+                return subscriptionTransfered(event);
+            case CANCEL:
+                return subscriptionCancelled(event);
+            case CHANGE:
+                return subscriptionChanged(event);
+            case PHASE:
+                return subscriptionPhaseChanged(event);
+            // TODO - should we really ignore these?
+            case MIGRATE_BILLING:
+            case UNCANCEL:
+                return null;
+            default:
+                log.warn("Unexpected event type " + event.getTransitionType());
+                return null;
+        }
+    }
+
+    private BusinessSubscriptionEvent subscriptionMigrated(final SubscriptionInternalEvent created) throws AccountApiException, EntitlementUserApiException {
+        return BusinessSubscriptionEvent.subscriptionMigrated(created.getNextPlan(), catalogService.getFullCatalog(), created.getEffectiveTransitionTime(), created.getSubscriptionStartDate());
+    }
+
+    private BusinessSubscriptionEvent subscriptionCreated(final SubscriptionInternalEvent created) throws AccountApiException, EntitlementUserApiException {
+        return BusinessSubscriptionEvent.subscriptionCreated(created.getNextPlan(), catalogService.getFullCatalog(), created.getEffectiveTransitionTime(), created.getSubscriptionStartDate());
+    }
+
+    private BusinessSubscriptionEvent subscriptionRecreated(final SubscriptionInternalEvent recreated) throws AccountApiException, EntitlementUserApiException {
+        return BusinessSubscriptionEvent.subscriptionRecreated(recreated.getNextPlan(), catalogService.getFullCatalog(), recreated.getEffectiveTransitionTime(), recreated.getSubscriptionStartDate());
+    }
+
+    private BusinessSubscriptionEvent subscriptionTransfered(final SubscriptionInternalEvent transfered) throws AccountApiException, EntitlementUserApiException {
+        return BusinessSubscriptionEvent.subscriptionTransfered(transfered.getNextPlan(), catalogService.getFullCatalog(), transfered.getEffectiveTransitionTime(), transfered.getSubscriptionStartDate());
+    }
+
+    private BusinessSubscriptionEvent subscriptionCancelled(final SubscriptionInternalEvent cancelled) throws AccountApiException, EntitlementUserApiException {
+        // cancelled.getNextPlan() is null here - need to look at the previous one to create the correct event name
+        return BusinessSubscriptionEvent.subscriptionCancelled(cancelled.getPreviousPlan(), catalogService.getFullCatalog(), cancelled.getEffectiveTransitionTime(), cancelled.getSubscriptionStartDate());
+    }
+
+    private BusinessSubscriptionEvent subscriptionChanged(final SubscriptionInternalEvent changed) throws AccountApiException, EntitlementUserApiException {
+        return BusinessSubscriptionEvent.subscriptionChanged(changed.getNextPlan(), catalogService.getFullCatalog(), changed.getEffectiveTransitionTime(), changed.getSubscriptionStartDate());
+    }
+
+    private BusinessSubscriptionEvent subscriptionPhaseChanged(final SubscriptionInternalEvent phaseChanged) throws AccountApiException, EntitlementUserApiException {
+        return BusinessSubscriptionEvent.subscriptionPhaseChanged(phaseChanged.getNextPlan(), phaseChanged.getNextState(), catalogService.getFullCatalog(), phaseChanged.getEffectiveTransitionTime(), phaseChanged.getSubscriptionStartDate());
+    }
+
+    private BusinessSubscription createNextBusinessSubscription(final EffectiveSubscriptionInternalEvent event, final BusinessSubscriptionEvent businessEvent, final Currency currency) {
+        final BusinessSubscription nextSubscription;
+        if (BusinessSubscriptionEvent.EventType.CANCEL.equals(businessEvent.getEventType()) ||
+            BusinessSubscriptionEvent.EventType.SYSTEM_CANCEL.equals(businessEvent.getEventType())) {
+            nextSubscription = null;
+        } else {
+            nextSubscription = new BusinessSubscription(event.getNextPriceList(), event.getNextPlan(), event.getNextPhase(),
+                                                        currency, event.getEffectiveTransitionTime(), event.getNextState(),
+                                                        catalogService.getFullCatalog());
+        }
+
+        return nextSubscription;
+    }
+
+    private BusinessSubscription createPreviousBusinessSubscription(final EffectiveSubscriptionInternalEvent event,
+                                                                    final BusinessSubscriptionEvent businessEvent,
+                                                                    final ArrayList<BusinessSubscriptionTransitionModelDao> transitions,
+                                                                    final Currency currency) {
+        if (BusinessSubscriptionEvent.EventType.MIGRATE.equals(businessEvent.getEventType()) ||
+            BusinessSubscriptionEvent.EventType.ADD.equals(businessEvent.getEventType()) ||
+            BusinessSubscriptionEvent.EventType.RE_ADD.equals(businessEvent.getEventType()) ||
+            BusinessSubscriptionEvent.EventType.TRANSFER.equals(businessEvent.getEventType())) {
+            return null;
+        }
+
+        final BusinessSubscriptionTransitionModelDao prevTransition = getPreviousBusinessSubscriptionTransitionForEvent(event, transitions);
+        return new BusinessSubscription(event.getPreviousPriceList(), event.getPreviousPlan(), event.getPreviousPhase(),
+                                        currency, prevTransition.getNextSubscription().getStartDate(), event.getPreviousState(),
+                                        catalogService.getFullCatalog());
+    }
+
+    private BusinessSubscriptionTransitionModelDao getPreviousBusinessSubscriptionTransitionForEvent(final EffectiveSubscriptionInternalEvent event,
+                                                                                                     final ArrayList<BusinessSubscriptionTransitionModelDao> transitions) {
+        BusinessSubscriptionTransitionModelDao transition = null;
+        for (final BusinessSubscriptionTransitionModelDao candidate : transitions) {
+            final BusinessSubscription nextSubscription = candidate.getNextSubscription();
+            if (nextSubscription == null || !nextSubscription.getStartDate().isBefore(event.getEffectiveTransitionTime())) {
+                continue;
+            }
+
+            if (candidate.getSubscriptionId().equals(event.getSubscriptionId())) {
+                transition = candidate;
+            }
+        }
+
+        if (transition == null) {
+            log.error("Unable to retrieve the previous transition - THIS SHOULD NEVER HAPPEN");
+            // Fall back to the latest one?
+            transition = transitions.get(transitions.size() - 1);
+        }
+
+        return transition;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/BusinessTagDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/BusinessTagDao.java
new file mode 100644
index 0000000..66bccd4
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/BusinessTagDao.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.ObjectType;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountTagSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoicePaymentTagSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceTagSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionTagSqlDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.svcapi.account.AccountInternalApi;
+import com.ning.billing.util.svcapi.entitlement.EntitlementInternalApi;
+
+public class BusinessTagDao {
+
+    private static final Logger log = LoggerFactory.getLogger(BusinessTagDao.class);
+
+    private final BusinessAccountTagSqlDao accountTagSqlDao;
+    private final BusinessInvoiceTagSqlDao invoiceTagSqlDao;
+    private final BusinessInvoicePaymentTagSqlDao invoicePaymentTagSqlDao;
+    private final BusinessSubscriptionTransitionTagSqlDao subscriptionTransitionTagSqlDao;
+    private final AccountInternalApi accountApi;
+    private final EntitlementInternalApi entitlementApi;
+
+    @Inject
+    public BusinessTagDao(final BusinessAccountTagSqlDao accountTagSqlDao,
+                          final BusinessInvoicePaymentTagSqlDao invoicePaymentTagSqlDao,
+                          final BusinessInvoiceTagSqlDao invoiceTagSqlDao,
+                          final BusinessSubscriptionTransitionTagSqlDao subscriptionTransitionTagSqlDao,
+                          final AccountInternalApi accountApi,
+                          final EntitlementInternalApi entitlementApi) {
+        this.accountTagSqlDao = accountTagSqlDao;
+        this.invoicePaymentTagSqlDao = invoicePaymentTagSqlDao;
+        this.invoiceTagSqlDao = invoiceTagSqlDao;
+        this.subscriptionTransitionTagSqlDao = subscriptionTransitionTagSqlDao;
+        this.accountApi = accountApi;
+        this.entitlementApi = entitlementApi;
+    }
+
+    public void tagAdded(final ObjectType objectType, final UUID objectId, final String name, final InternalCallContext context) {
+        if (objectType.equals(ObjectType.ACCOUNT)) {
+            tagAddedForAccount(objectId, name, context);
+        } else if (objectType.equals(ObjectType.BUNDLE)) {
+            tagAddedForBundle(objectId, name, context);
+        } else if (objectType.equals(ObjectType.INVOICE)) {
+            tagAddedForInvoice(objectId, name, context);
+        } else if (objectType.equals(ObjectType.PAYMENT)) {
+            tagAddedForPayment(objectId, name, context);
+        } else {
+            log.info("Ignoring tag addition of {} for object id {} (type {})", new Object[]{name, objectId.toString(), objectType.toString()});
+        }
+    }
+
+    public void tagRemoved(final ObjectType objectType, final UUID objectId, final String name, final InternalCallContext context) {
+        if (objectType.equals(ObjectType.ACCOUNT)) {
+            tagRemovedForAccount(objectId, name, context);
+        } else if (objectType.equals(ObjectType.BUNDLE)) {
+            tagRemovedForBundle(objectId, name, context);
+        } else if (objectType.equals(ObjectType.INVOICE)) {
+            tagRemovedForInvoice(objectId, name, context);
+        } else if (objectType.equals(ObjectType.PAYMENT)) {
+            tagRemovedForPayment(objectId, name, context);
+        } else {
+            log.info("Ignoring tag removal of {} for object id {} (type {})", new Object[]{name, objectId.toString(), objectType.toString()});
+        }
+    }
+
+    private void tagAddedForAccount(final UUID accountId, final String name, final InternalCallContext context) {
+        final Account account;
+        try {
+            account = accountApi.getAccountById(accountId, context);
+        } catch (AccountApiException e) {
+            log.warn("Ignoring tag addition of {} for account id {} (account does not exist)", name, accountId.toString());
+            return;
+        }
+
+        final String accountKey = account.getExternalKey();
+        accountTagSqlDao.addTag(accountId.toString(), accountKey, name, context);
+    }
+
+    private void tagRemovedForAccount(final UUID accountId, final String name, final InternalCallContext context) {
+        accountTagSqlDao.removeTag(accountId.toString(), name, context);
+    }
+
+    private void tagAddedForBundle(final UUID bundleId, final String name, final InternalCallContext context) {
+        final SubscriptionBundle bundle;
+        try {
+            bundle = entitlementApi.getBundleFromId(bundleId, context);
+        } catch (EntitlementUserApiException e) {
+            log.warn("Ignoring tag addition of {} for bundle id {} (bundle does not exist)", name, bundleId.toString());
+            return;
+        }
+
+        final Account account;
+        try {
+            account = accountApi.getAccountById(bundle.getAccountId(), context);
+        } catch (AccountApiException e) {
+            log.warn("Ignoring tag addition of {} for bundle id {} and account id {} (account does not exist)", new Object[]{name, bundleId.toString(), bundle.getAccountId()});
+            return;
+        }
+
+        /*
+         * Note: we store tags associated to bundles, not to subscriptions.
+         */
+        final String accountKey = account.getExternalKey();
+        final String externalKey = bundle.getExternalKey();
+        subscriptionTransitionTagSqlDao.addTag(accountKey, bundleId.toString(), externalKey, name, context);
+    }
+
+    private void tagRemovedForBundle(final UUID bundleId, final String name, final InternalCallContext context) {
+        subscriptionTransitionTagSqlDao.removeTag(bundleId.toString(), name, context);
+    }
+
+    private void tagAddedForInvoice(final UUID objectId, final String name, final InternalCallContext context) {
+        invoiceTagSqlDao.addTag(objectId.toString(), name, context);
+    }
+
+    private void tagRemovedForInvoice(final UUID objectId, final String name, final InternalCallContext context) {
+        invoiceTagSqlDao.removeTag(objectId.toString(), name, context);
+    }
+
+    private void tagAddedForPayment(final UUID objectId, final String name, final InternalCallContext context) {
+        invoicePaymentTagSqlDao.addTag(objectId.toString(), name, context);
+    }
+
+    private void tagRemovedForPayment(final UUID objectId, final String name, final InternalCallContext context) {
+        invoicePaymentTagSqlDao.removeTag(objectId.toString(), name, context);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/AnalyticsDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/AnalyticsDao.java
new file mode 100644
index 0000000..0541d46
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/AnalyticsDao.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+
+import com.ning.billing.analytics.api.TimeSeriesData;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountTagModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceItemModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoicePaymentModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessOverdueStatusModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionModelDao;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+
+public interface AnalyticsDao {
+
+    TimeSeriesData getAccountsCreatedOverTime(InternalTenantContext context);
+
+    TimeSeriesData getSubscriptionsCreatedOverTime(String productType, String slug, InternalTenantContext context);
+
+    BusinessAccountModelDao getAccountByKey(String accountKey, InternalTenantContext context);
+
+    List<BusinessSubscriptionTransitionModelDao> getTransitionsByKey(String externalKey, InternalTenantContext context);
+
+    List<BusinessSubscriptionTransitionModelDao> getTransitionsForAccount(String accountKey, InternalTenantContext context);
+
+    List<BusinessInvoiceModelDao> getInvoicesByKey(String accountKey, InternalTenantContext context);
+
+    List<BusinessInvoiceItemModelDao> getInvoiceItemsForInvoice(String invoiceId, InternalTenantContext context);
+
+    List<BusinessInvoicePaymentModelDao> getInvoicePaymentsForAccountByKey(String accountKey, InternalTenantContext context);
+
+    List<BusinessOverdueStatusModelDao> getOverdueStatusesForBundleByKey(String externalKey, InternalTenantContext context);
+
+    List<BusinessAccountTagModelDao> getTagsForAccount(String accountKey, InternalTenantContext context);
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/AnalyticsSanityDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/AnalyticsSanityDao.java
new file mode 100644
index 0000000..8d5e177
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/AnalyticsSanityDao.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.Collection;
+import java.util.UUID;
+
+import com.ning.billing.util.callcontext.InternalTenantContext;
+
+public interface AnalyticsSanityDao {
+
+    public Collection<UUID> checkBstMatchesSubscriptionEvents(InternalTenantContext context);
+
+    public Collection<UUID> checkBiiMatchesInvoiceItems(InternalTenantContext context);
+
+    public Collection<UUID> checkBipMatchesInvoicePayments(InternalTenantContext context);
+
+    public Collection<UUID> checkBinAmountPaidMatchesInvoicePayments(InternalTenantContext context);
+
+    public Collection<UUID> checkBinAmountChargedMatchesInvoicePayments(InternalTenantContext context);
+
+    public Collection<UUID> checkBinBiiBalanceConsistency(InternalTenantContext context);
+
+    public Collection<UUID> checkBinBiiAmountCreditedConsistency(InternalTenantContext context);
+
+    public Collection<UUID> checkBacBinBiiConsistency(InternalTenantContext context);
+
+    public Collection<UUID> checkBacTagsMatchesTags(InternalTenantContext context);
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/AnalyticsSanitySqlDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/AnalyticsSanitySqlDao.java
new file mode 100644
index 0000000..dcc1b12
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/AnalyticsSanitySqlDao.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.Collection;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.InternalTenantContextBinder;
+import com.ning.billing.util.dao.UuidMapper;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(UuidMapper.class)
+public interface AnalyticsSanitySqlDao extends Transactional<AnalyticsSanitySqlDao>, Transmogrifier {
+
+    @SqlQuery
+    public Collection<UUID> checkBstMatchesSubscriptionEvents(@InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    public Collection<UUID> checkBiiMatchesInvoiceItems(@InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    public Collection<UUID> checkBipMatchesInvoicePayments(@InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    public Collection<UUID> checkBinAmountPaidMatchesInvoicePayments(@InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    public Collection<UUID> checkBinAmountChargedMatchesInvoicePayments(@InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    public Collection<UUID> checkBinBiiBalanceConsistency(@InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    public Collection<UUID> checkBinBiiAmountCreditedConsistency(@InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    public Collection<UUID> checkBacBinBiiConsistency(@InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    public Collection<UUID> checkBacTagsMatchesTags(@InternalTenantContextBinder final InternalTenantContext context);
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountBinder.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountBinder.java
new file mode 100644
index 0000000..d0356d1
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountBinder.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.sql.Types;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountModelDao;
+
+@BindingAnnotation(BusinessAccountBinder.BacBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface BusinessAccountBinder {
+
+    public static class BacBinderFactory implements BinderFactory {
+
+        public Binder build(final Annotation annotation) {
+            return new Binder<BusinessAccountBinder, BusinessAccountModelDao>() {
+                public void bind(final SQLStatement q, final BusinessAccountBinder bind, final BusinessAccountModelDao account) {
+                    final DateTime dateTimeNow = new DateTime(DateTimeZone.UTC);
+
+                    if (account.getCreatedDate() != null) {
+                        q.bind("created_date", account.getCreatedDate().getMillis());
+                    } else {
+                        q.bind("created_date", dateTimeNow.getMillis());
+                    }
+
+                    if (account.getUpdatedDate() != null) {
+                        q.bind("updated_date", account.getUpdatedDate().getMillis());
+                    } else {
+                        q.bind("updated_date", dateTimeNow.getMillis());
+                    }
+
+                    q.bind("account_id", account.getAccountId().toString());
+                    q.bind("account_key", account.getKey());
+                    q.bind("balance", account.getRoundedBalance());
+                    q.bind("name", account.getName());
+                    if (account.getLastInvoiceDate() != null) {
+                        q.bind("last_invoice_date", account.getLastInvoiceDate().toDate());
+                    } else {
+                        q.bindNull("last_invoice_date", Types.DATE);
+                    }
+                    q.bind("total_invoice_balance", account.getRoundedTotalInvoiceBalance());
+                    q.bind("last_payment_status", account.getLastPaymentStatus());
+                    q.bind("payment_method", account.getPaymentMethod());
+                    q.bind("credit_card_type", account.getCreditCardType());
+                    q.bind("billing_address_country", account.getBillingAddressCountry());
+                    q.bind("currency", account.getCurrency());
+                }
+            };
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountFieldMapper.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountFieldMapper.java
new file mode 100644
index 0000000..d59636c
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountFieldMapper.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountFieldModelDao;
+
+public class BusinessAccountFieldMapper implements ResultSetMapper<BusinessAccountFieldModelDao> {
+
+    @Override
+    public BusinessAccountFieldModelDao map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
+        final UUID accountId = UUID.fromString(r.getString(1));
+        final String accountKey = r.getString(2);
+        final String name = r.getString(3);
+        final String value = r.getString(4);
+        return new BusinessAccountFieldModelDao(accountId, accountKey, name, value);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountFieldSqlDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountFieldSqlDao.java
new file mode 100644
index 0000000..c2a0b9d
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountFieldSqlDao.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountFieldModelDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.InternalTenantContextBinder;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(BusinessAccountFieldMapper.class)
+public interface BusinessAccountFieldSqlDao {
+
+    @SqlQuery
+    List<BusinessAccountFieldModelDao> getFieldsForAccountByKey(@Bind("account_key") final String accountKey,
+                                                                @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlUpdate
+    int addField(@Bind("account_id") final String accountId,
+                 @Bind("account_key") final String accountKey,
+                 @Bind("name") final String name,
+                 @Bind("value") final String value,
+                 @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    int removeField(@Bind("account_id") final String accountId,
+                    @Bind("name") final String name,
+                    @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    void test(@InternalTenantContextBinder final InternalTenantContext context);
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountMapper.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountMapper.java
new file mode 100644
index 0000000..d0b1b9a
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountMapper.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountModelDao;
+import com.ning.billing.util.dao.MapperBase;
+
+public class BusinessAccountMapper extends MapperBase implements ResultSetMapper<BusinessAccountModelDao> {
+
+    @Override
+    public BusinessAccountModelDao map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
+        return new BusinessAccountModelDao(
+                UUID.fromString(r.getString(1)),
+                r.getString(2),
+                r.getString(6),
+                BigDecimal.valueOf(r.getDouble(5)),
+                getDate(r, "last_invoice_date"),
+                BigDecimal.valueOf(r.getDouble(8)),
+                r.getString(9),
+                r.getString(10),
+                r.getString(11),
+                r.getString(12),
+                r.getString(13),
+                new DateTime(r.getLong(3), DateTimeZone.UTC),
+                new DateTime(r.getLong(4), DateTimeZone.UTC)
+        );
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountSqlDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountSqlDao.java
new file mode 100644
index 0000000..b17cc54
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountSqlDao.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountModelDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.InternalTenantContextBinder;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper({BusinessAccountMapper.class, TimeSeriesTupleMapper.class})
+public interface BusinessAccountSqlDao extends Transactional<BusinessAccountSqlDao>, Transmogrifier {
+
+    @SqlQuery
+    List<TimeSeriesTuple> getAccountsCreatedOverTime(@InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    BusinessAccountModelDao getAccount(@Bind("account_id") final String accountId,
+                                       @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    BusinessAccountModelDao getAccountByKey(@Bind("account_key") String accountKey,
+                                            @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlUpdate
+    int createAccount(@BusinessAccountBinder final BusinessAccountModelDao account,
+                      @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    int saveAccount(@BusinessAccountBinder final BusinessAccountModelDao account,
+                    @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    int deleteAccount(@Bind("account_id") final String accountId,
+                      @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    void test(@InternalTenantContextBinder final InternalTenantContext context);
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountTagMapper.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountTagMapper.java
new file mode 100644
index 0000000..e698c69
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountTagMapper.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountTagModelDao;
+
+public class BusinessAccountTagMapper implements ResultSetMapper<BusinessAccountTagModelDao> {
+
+    @Override
+    public BusinessAccountTagModelDao map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
+        final UUID accountId = UUID.fromString(r.getString(1));
+        final String accountKey = r.getString(2);
+        final String name = r.getString(3);
+        return new BusinessAccountTagModelDao(accountId, accountKey, name);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountTagSqlDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountTagSqlDao.java
new file mode 100644
index 0000000..68dce2a
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountTagSqlDao.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountTagModelDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.InternalTenantContextBinder;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(BusinessAccountTagMapper.class)
+public interface BusinessAccountTagSqlDao {
+
+    @SqlQuery
+    List<BusinessAccountTagModelDao> getTagsForAccountByKey(@Bind("account_key") final String accountKey,
+                                                            @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlUpdate
+    int addTag(@Bind("account_id") final String accountId,
+               @Bind("account_key") final String accountKey,
+               @Bind("name") final String name,
+               @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    int removeTag(@Bind("account_id") final String accountId,
+                  @Bind("name") final String name,
+                  @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    void test(@InternalTenantContextBinder final InternalTenantContext context);
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceBinder.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceBinder.java
new file mode 100644
index 0000000..4c258f5
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceBinder.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.sql.Types;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceModelDao;
+import com.ning.billing.osgi.bundles.analytics.utils.Rounder;
+
+@BindingAnnotation(BusinessInvoiceBinder.BinBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface BusinessInvoiceBinder {
+
+    public static class BinBinderFactory implements BinderFactory {
+
+        public Binder build(final Annotation annotation) {
+            return new Binder<BusinessInvoiceBinder, BusinessInvoiceModelDao>() {
+                public void bind(final SQLStatement q, final BusinessInvoiceBinder bind, final BusinessInvoiceModelDao invoice) {
+                    q.bind("invoice_id", invoice.getInvoiceId().toString());
+
+                    if (invoice.getInvoiceNumber() != null) {
+                        q.bind("invoice_number", invoice.getInvoiceNumber());
+                    } else {
+                        q.bindNull("invoice_number", Types.BIGINT);
+                    }
+
+                    final DateTime dateTimeNow = new DateTime(DateTimeZone.UTC);
+                    if (invoice.getCreatedDate() != null) {
+                        q.bind("created_date", invoice.getCreatedDate().getMillis());
+                    } else {
+                        q.bind("created_date", dateTimeNow.getMillis());
+                    }
+
+                    if (invoice.getUpdatedDate() != null) {
+                        q.bind("updated_date", invoice.getUpdatedDate().getMillis());
+                    } else {
+                        q.bind("updated_date", dateTimeNow.getMillis());
+                    }
+
+                    q.bind("account_id", invoice.getAccountId().toString());
+                    q.bind("account_key", invoice.getAccountKey());
+
+                    if (invoice.getInvoiceDate() != null) {
+                        q.bind("invoice_date", invoice.getInvoiceDate().toDate());
+                    } else {
+                        q.bindNull("invoice_date", Types.DATE);
+                    }
+
+                    if (invoice.getTargetDate() != null) {
+                        q.bind("target_date", invoice.getTargetDate().toDate());
+                    } else {
+                        q.bindNull("target_date", Types.DATE);
+                    }
+
+                    q.bind("currency", invoice.getCurrency().toString());
+                    q.bind("balance", Rounder.round(invoice.getBalance()));
+                    q.bind("amount_paid", Rounder.round(invoice.getAmountPaid()));
+                    q.bind("amount_charged", Rounder.round(invoice.getAmountCharged()));
+                    q.bind("amount_credited", Rounder.round(invoice.getAmountCredited()));
+                }
+            };
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceFieldMapper.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceFieldMapper.java
new file mode 100644
index 0000000..edcf683
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceFieldMapper.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceFieldModelDao;
+
+public class BusinessInvoiceFieldMapper implements ResultSetMapper<BusinessInvoiceFieldModelDao> {
+
+    @Override
+    public BusinessInvoiceFieldModelDao map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
+        return new BusinessInvoiceFieldModelDao(UUID.fromString(r.getString(1)), r.getString(2), r.getString(3));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceFieldSqlDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceFieldSqlDao.java
new file mode 100644
index 0000000..1796169
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceFieldSqlDao.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceFieldModelDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.InternalTenantContextBinder;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(BusinessInvoiceFieldMapper.class)
+public interface BusinessInvoiceFieldSqlDao {
+
+    @SqlQuery
+    List<BusinessInvoiceFieldModelDao> getFieldsForInvoice(@Bind("invoice_id") final String invoiceId,
+                                                           @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlUpdate
+    int addField(@Bind("invoice_id") final String invoiceId,
+                 @Bind("name") final String name,
+                 @Bind("value") final String value,
+                 @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    int removeField(@Bind("invoice_id") final String invoiceId,
+                    @Bind("name") final String name,
+                    @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    void test(@InternalTenantContextBinder final InternalTenantContext context);
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceItemBinder.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceItemBinder.java
new file mode 100644
index 0000000..8563d8b
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceItemBinder.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.sql.Types;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceItemModelDao;
+import com.ning.billing.osgi.bundles.analytics.utils.Rounder;
+
+@BindingAnnotation(BusinessInvoiceItemBinder.BiiBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface BusinessInvoiceItemBinder {
+
+    public static class BiiBinderFactory implements BinderFactory {
+
+        public Binder build(final Annotation annotation) {
+            return new Binder<BusinessInvoiceItemBinder, BusinessInvoiceItemModelDao>() {
+                public void bind(final SQLStatement q, final BusinessInvoiceItemBinder bind, final BusinessInvoiceItemModelDao invoiceItem) {
+                    q.bind("item_id", invoiceItem.getItemId().toString());
+                    if (invoiceItem.getLinkedItemId() != null) {
+                        q.bind("linked_item_id", invoiceItem.getLinkedItemId().toString());
+                    } else {
+                        q.bindNull("linked_item_id", Types.VARCHAR);
+                    }
+
+                    final DateTime dateTimeNow = new DateTime(DateTimeZone.UTC);
+                    if (invoiceItem.getCreatedDate() != null) {
+                        q.bind("created_date", invoiceItem.getCreatedDate().getMillis());
+                    } else {
+                        q.bind("created_date", dateTimeNow.getMillis());
+                    }
+
+                    if (invoiceItem.getUpdatedDate() != null) {
+                        q.bind("updated_date", invoiceItem.getUpdatedDate().getMillis());
+                    } else {
+                        q.bind("updated_date", dateTimeNow.getMillis());
+                    }
+
+                    q.bind("invoice_id", invoiceItem.getInvoiceId().toString());
+                    q.bind("item_type", invoiceItem.getItemType());
+                    q.bind("external_key", invoiceItem.getExternalKey());
+                    q.bind("product_name", invoiceItem.getProductName());
+                    q.bind("product_type", invoiceItem.getProductType());
+                    q.bind("product_category", invoiceItem.getProductCategory());
+                    q.bind("slug", invoiceItem.getSlug());
+                    q.bind("phase", invoiceItem.getPhase());
+                    q.bind("billing_period", invoiceItem.getBillingPeriod());
+
+                    if (invoiceItem.getStartDate() != null) {
+                        q.bind("start_date", invoiceItem.getStartDate().toDate());
+                    } else {
+                        q.bindNull("start_date", Types.DATE);
+                    }
+
+                    if (invoiceItem.getEndDate() != null) {
+                        q.bind("end_date", invoiceItem.getEndDate().toDate());
+                    } else {
+                        q.bindNull("end_date", Types.DATE);
+                    }
+
+                    q.bind("amount", Rounder.round(invoiceItem.getAmount()));
+                    q.bind("currency", invoiceItem.getCurrency().toString());
+                }
+            };
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceItemMapper.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceItemMapper.java
new file mode 100644
index 0000000..c6b25f0
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceItemMapper.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceItemModelDao;
+import com.ning.billing.util.dao.MapperBase;
+
+public class BusinessInvoiceItemMapper extends MapperBase implements ResultSetMapper<BusinessInvoiceItemModelDao> {
+
+    @Override
+    public BusinessInvoiceItemModelDao map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
+        final UUID itemId = getUUID(r, "item_id");
+        final UUID linkedItemId = getUUID(r, "linked_item_id");
+        final DateTime createdDate = new DateTime(r.getLong("created_date"), DateTimeZone.UTC);
+        final DateTime updatedDate = new DateTime(r.getLong("updated_date"), DateTimeZone.UTC);
+        final UUID invoiceId = getUUID(r, "invoice_id");
+        final String itemType = r.getString("item_type");
+        final String externalKey = r.getString("external_key");
+        final String productName = r.getString("product_name");
+        final String productType = r.getString("product_type");
+        final String productCategory = r.getString("product_category");
+        final String slug = r.getString("slug");
+        final String phase = r.getString("phase");
+        final String billingPeriod = r.getString("billing_period");
+        final LocalDate startDate = getDate(r, "start_date");
+        final LocalDate endDate = getDate(r, "end_date");
+        final BigDecimal amount = BigDecimal.valueOf(r.getDouble("amount"));
+        final Currency currency = Currency.valueOf(r.getString("currency"));
+
+        return new BusinessInvoiceItemModelDao(amount, billingPeriod, createdDate, currency, endDate, externalKey, invoiceId,
+                                               itemId, linkedItemId, itemType, phase, productCategory, productName, productType, slug,
+                                               startDate, updatedDate);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceItemSqlDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceItemSqlDao.java
new file mode 100644
index 0000000..fbf750f
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceItemSqlDao.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceItemModelDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.InternalTenantContextBinder;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(BusinessInvoiceItemMapper.class)
+public interface BusinessInvoiceItemSqlDao extends Transactional<BusinessInvoiceItemSqlDao>, Transmogrifier {
+
+    @SqlQuery
+    BusinessInvoiceItemModelDao getInvoiceItem(@Bind("item_id") final String itemId,
+                                               @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    List<BusinessInvoiceItemModelDao> getInvoiceItemsForInvoice(@Bind("invoice_id") final String invoiceId,
+                                                                @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    List<BusinessInvoiceItemModelDao> getInvoiceItemsForBundleByKey(@Bind("external_key") final String externalKey,
+                                                                    @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlUpdate
+    int createInvoiceItem(@BusinessInvoiceItemBinder final BusinessInvoiceItemModelDao invoiceItem,
+                          @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    int deleteInvoiceItem(@Bind("item_id") final String itemId,
+                          @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    void deleteInvoiceItemsForAccount(@Bind("account_id") final String accountId,
+                                      @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    void test(@InternalTenantContextBinder final InternalTenantContext context);
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceMapper.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceMapper.java
new file mode 100644
index 0000000..58a8b83
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceMapper.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceModelDao;
+import com.ning.billing.util.dao.MapperBase;
+
+public class BusinessInvoiceMapper extends MapperBase implements ResultSetMapper<BusinessInvoiceModelDao> {
+
+    @Override
+    public BusinessInvoiceModelDao map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
+        final UUID invoiceId = UUID.fromString(r.getString(1));
+        final Integer invoiceNumber = r.getInt(2);
+        final DateTime createdDate = new DateTime(r.getLong(3), DateTimeZone.UTC);
+        final DateTime updatedDate = new DateTime(r.getLong(4), DateTimeZone.UTC);
+        final UUID accountId = UUID.fromString(r.getString(5));
+        final String accountKey = r.getString(6);
+        final LocalDate invoiceDate = getDate(r, "invoice_date");
+        final LocalDate targetDate = getDate(r, "target_date");
+        final Currency currency = Currency.valueOf(r.getString(9));
+        final BigDecimal balance = BigDecimal.valueOf(r.getDouble(10));
+        final BigDecimal amountPaid = BigDecimal.valueOf(r.getDouble(11));
+        final BigDecimal amountCharged = BigDecimal.valueOf(r.getDouble(12));
+        final BigDecimal amountCredited = BigDecimal.valueOf(r.getDouble(13));
+
+        return new BusinessInvoiceModelDao(accountId, accountKey, amountCharged, amountCredited, amountPaid, balance, createdDate, currency,
+                                           invoiceDate, invoiceId, invoiceNumber, targetDate, updatedDate);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentBinder.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentBinder.java
new file mode 100644
index 0000000..08b3693
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentBinder.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.sql.Types;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoicePaymentModelDao;
+import com.ning.billing.osgi.bundles.analytics.utils.Rounder;
+
+@BindingAnnotation(BusinessInvoicePaymentBinder.BipBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface BusinessInvoicePaymentBinder {
+
+    public static class BipBinderFactory implements BinderFactory {
+
+        @Override
+        public Binder build(final Annotation annotation) {
+            return new Binder<BusinessInvoicePaymentBinder, BusinessInvoicePaymentModelDao>() {
+                @Override
+                public void bind(final SQLStatement q, final BusinessInvoicePaymentBinder bind, final BusinessInvoicePaymentModelDao invoicePayment) {
+                    q.bind("payment_id", invoicePayment.getPaymentId().toString());
+
+                    final DateTime dateTimeNow = new DateTime(DateTimeZone.UTC);
+                    if (invoicePayment.getCreatedDate() != null) {
+                        q.bind("created_date", invoicePayment.getCreatedDate().getMillis());
+                    } else {
+                        q.bind("created_date", dateTimeNow.getMillis());
+                    }
+
+                    if (invoicePayment.getUpdatedDate() != null) {
+                        q.bind("updated_date", invoicePayment.getUpdatedDate().getMillis());
+                    } else {
+                        q.bind("updated_date", dateTimeNow.getMillis());
+                    }
+
+                    q.bind("ext_first_payment_ref_id", invoicePayment.getExtFirstPaymentRefId());
+                    q.bind("ext_second_payment_ref_id", invoicePayment.getExtSecondPaymentRefId());
+                    q.bind("account_key", invoicePayment.getAccountKey());
+                    q.bind("invoice_id", invoicePayment.getInvoiceId().toString());
+
+                    if (invoicePayment.getEffectiveDate() != null) {
+                        q.bind("effective_date", invoicePayment.getEffectiveDate().getMillis());
+                    } else {
+                        q.bindNull("effective_date", Types.BIGINT);
+                    }
+
+                    q.bind("amount", Rounder.round(invoicePayment.getAmount()));
+                    q.bind("currency", invoicePayment.getCurrency().toString());
+                    q.bind("payment_error", invoicePayment.getPaymentError());
+                    q.bind("processing_status", invoicePayment.getProcessingStatus());
+                    q.bind("requested_amount", Rounder.round(invoicePayment.getRequestedAmount()));
+                    q.bind("plugin_name", invoicePayment.getPluginName());
+                    q.bind("payment_type", invoicePayment.getPaymentType());
+                    q.bind("payment_method", invoicePayment.getPaymentMethod());
+                    q.bind("card_type", invoicePayment.getCardType());
+                    q.bind("card_country", invoicePayment.getCardCountry());
+                    q.bind("invoice_payment_type", invoicePayment.getInvoicePaymentType());
+
+                    final UUID linkedInvoicePaymentId = invoicePayment.getLinkedInvoicePaymentId();
+                    if (linkedInvoicePaymentId != null) {
+                        q.bind("linked_invoice_payment_id", linkedInvoicePaymentId.toString());
+                    } else {
+                        q.bindNull("linked_invoice_payment_id", Types.VARCHAR);
+                    }
+                }
+            };
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentFieldMapper.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentFieldMapper.java
new file mode 100644
index 0000000..d948982
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentFieldMapper.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoicePaymentFieldModelDao;
+
+public class BusinessInvoicePaymentFieldMapper implements ResultSetMapper<BusinessInvoicePaymentFieldModelDao> {
+
+    @Override
+    public BusinessInvoicePaymentFieldModelDao map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
+        return new BusinessInvoicePaymentFieldModelDao(UUID.fromString(r.getString(1)), r.getString(2), r.getString(3));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentFieldSqlDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentFieldSqlDao.java
new file mode 100644
index 0000000..67d1c07
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentFieldSqlDao.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoicePaymentFieldModelDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.InternalTenantContextBinder;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(BusinessInvoicePaymentFieldMapper.class)
+public interface BusinessInvoicePaymentFieldSqlDao {
+
+    @SqlQuery
+    List<BusinessInvoicePaymentFieldModelDao> getFieldsForInvoicePayment(@Bind("payment_id") final String paymentId,
+                                                                         @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlUpdate
+    int addField(@Bind("payment_id") final String paymentId,
+                 @Bind("name") final String name,
+                 @Bind("value") final String value,
+                 @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    int removeField(@Bind("payment_id") final String paymentId,
+                    @Bind("name") final String name,
+                    @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    void test(@InternalTenantContextBinder final InternalTenantContext context);
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentMapper.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentMapper.java
new file mode 100644
index 0000000..c033499
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentMapper.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoicePaymentModelDao;
+
+public class BusinessInvoicePaymentMapper implements ResultSetMapper<BusinessInvoicePaymentModelDao> {
+
+    @Override
+    public BusinessInvoicePaymentModelDao map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
+        final UUID paymentId = UUID.fromString(r.getString(1));
+        final DateTime createdDate = new DateTime(r.getLong(2), DateTimeZone.UTC);
+        final DateTime updatedDate = new DateTime(r.getLong(3), DateTimeZone.UTC);
+        final String extFirstPaymentRefId = r.getString(4);
+        final String extSecondPaymentRefId = r.getString(5);
+        final String accountKey = r.getString(6);
+        final UUID invoiceId = UUID.fromString(r.getString(7));
+        final DateTime effectiveDate = new DateTime(r.getLong(8), DateTimeZone.UTC);
+        final BigDecimal amount = BigDecimal.valueOf(r.getDouble(9));
+        final Currency currency = Currency.valueOf(r.getString(10));
+        final String paymentError = r.getString(11);
+        final String processingStatus = r.getString(12);
+        final BigDecimal requestedAmount = BigDecimal.valueOf(r.getDouble(13));
+        final String pluginName = r.getString(14);
+        final String paymentType = r.getString(15);
+        final String paymentMethod = r.getString(16);
+        final String cardType = r.getString(17);
+        final String cardCountry = r.getString(18);
+        final String invoicePaymentType = r.getString(19);
+        final String linkedInvoicePaymentIdString = r.getString(20);
+
+        final UUID linkedInvoicePaymentId;
+        if (linkedInvoicePaymentIdString != null) {
+            linkedInvoicePaymentId = UUID.fromString(linkedInvoicePaymentIdString);
+        } else {
+            linkedInvoicePaymentId = null;
+        }
+
+        return new BusinessInvoicePaymentModelDao(accountKey, amount, cardCountry, cardType, createdDate, currency,
+                                                  effectiveDate, invoiceId, paymentError, paymentId, paymentMethod, paymentType,
+                                                  pluginName, processingStatus, requestedAmount, updatedDate, invoicePaymentType,
+                                                  linkedInvoicePaymentId);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentSqlDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentSqlDao.java
new file mode 100644
index 0000000..ea7256a
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentSqlDao.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoicePaymentModelDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.InternalTenantContextBinder;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(BusinessInvoicePaymentMapper.class)
+public interface BusinessInvoicePaymentSqlDao extends Transactional<BusinessInvoicePaymentSqlDao>, Transmogrifier {
+
+    @SqlQuery
+    BusinessInvoicePaymentModelDao getInvoicePayment(@Bind("payment_id") final String paymentId,
+                                                     @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    List<BusinessInvoicePaymentModelDao> getInvoicePaymentsForAccountByKey(@Bind("account_key") final String accountKey,
+                                                                           @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlUpdate
+    int createInvoicePayment(@BusinessInvoicePaymentBinder final BusinessInvoicePaymentModelDao payment,
+                             @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    int deleteInvoicePayment(@Bind("payment_id") final String paymentId,
+                             @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    void test(@InternalTenantContextBinder final InternalTenantContext context);
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentTagMapper.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentTagMapper.java
new file mode 100644
index 0000000..12df373
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentTagMapper.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoicePaymentTagModelDao;
+
+public class BusinessInvoicePaymentTagMapper implements ResultSetMapper<BusinessInvoicePaymentTagModelDao> {
+
+    @Override
+    public BusinessInvoicePaymentTagModelDao map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
+        return new BusinessInvoicePaymentTagModelDao(UUID.fromString(r.getString(1)), r.getString(2));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentTagSqlDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentTagSqlDao.java
new file mode 100644
index 0000000..2961a18
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentTagSqlDao.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoicePaymentTagModelDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.InternalTenantContextBinder;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(BusinessInvoicePaymentTagMapper.class)
+public interface BusinessInvoicePaymentTagSqlDao {
+
+    @SqlQuery
+    List<BusinessInvoicePaymentTagModelDao> getTagsForInvoicePayment(@Bind("payment_id") final String paymentId,
+                                                                     @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlUpdate
+    int addTag(@Bind("payment_id") final String paymentId,
+               @Bind("name") final String name,
+               @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    int removeTag(@Bind("payment_id") final String paymentId,
+                  @Bind("name") final String name,
+                  @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    void test(@InternalTenantContextBinder final InternalTenantContext context);
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceSqlDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceSqlDao.java
new file mode 100644
index 0000000..083ffed
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceSqlDao.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceModelDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.InternalTenantContextBinder;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(BusinessInvoiceMapper.class)
+public interface BusinessInvoiceSqlDao extends Transactional<BusinessInvoiceSqlDao>, Transmogrifier {
+
+    @SqlQuery
+    BusinessInvoiceModelDao getInvoice(@Bind("invoice_id") final String invoiceId,
+                                       @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    List<BusinessInvoiceModelDao> getInvoicesForAccount(@Bind("account_id") final String accountId,
+                                                        @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    List<BusinessInvoiceModelDao> getInvoicesForAccountByKey(@Bind("account_key") final String accountKey,
+                                                             @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlUpdate
+    int createInvoice(@BusinessInvoiceBinder final BusinessInvoiceModelDao invoice,
+                      @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    int deleteInvoice(@Bind("invoice_id") final String invoiceId,
+                      @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    void deleteInvoicesForAccount(@Bind("account_id") final String accountId,
+                                  @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    void test(@InternalTenantContextBinder final InternalTenantContext context);
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceTagMapper.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceTagMapper.java
new file mode 100644
index 0000000..6a050e7
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceTagMapper.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceTagModelDao;
+
+public class BusinessInvoiceTagMapper implements ResultSetMapper<BusinessInvoiceTagModelDao> {
+
+    @Override
+    public BusinessInvoiceTagModelDao map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
+        return new BusinessInvoiceTagModelDao(UUID.fromString(r.getString(1)), r.getString(2));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceTagSqlDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceTagSqlDao.java
new file mode 100644
index 0000000..73cc1b6
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceTagSqlDao.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceTagModelDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.InternalTenantContextBinder;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(BusinessInvoiceTagMapper.class)
+public interface BusinessInvoiceTagSqlDao {
+
+    @SqlQuery
+    List<BusinessInvoiceTagModelDao> getTagsForInvoice(@Bind("invoice_id") final String invoiceId,
+                                                       @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlUpdate
+    int addTag(@Bind("invoice_id") final String invoiceId,
+               @Bind("name") final String name,
+               @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    int removeTag(@Bind("invoice_id") final String invoiceId,
+                  @Bind("name") final String name,
+                  @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    void test(@InternalTenantContextBinder final InternalTenantContext context);
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusBinder.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusBinder.java
new file mode 100644
index 0000000..ba5ff4b
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusBinder.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.sql.Types;
+
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessOverdueStatusModelDao;
+
+@BindingAnnotation(BusinessOverdueStatusBinder.BosBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface BusinessOverdueStatusBinder {
+
+    public static class BosBinderFactory implements BinderFactory {
+
+        public Binder build(final Annotation annotation) {
+            return new Binder<BusinessOverdueStatusBinder, BusinessOverdueStatusModelDao>() {
+                public void bind(final SQLStatement q, final BusinessOverdueStatusBinder bind, final BusinessOverdueStatusModelDao overdueStatus) {
+                    q.bind("account_key", overdueStatus.getAccountKey());
+                    q.bind("bundle_id", overdueStatus.getBundleId().toString());
+                    q.bind("external_key", overdueStatus.getExternalKey());
+                    q.bind("status", overdueStatus.getStatus());
+
+                    if (overdueStatus.getStartDate() != null) {
+                        q.bind("start_date", overdueStatus.getStartDate().getMillis());
+                    } else {
+                        q.bindNull("start_date", Types.BIGINT);
+                    }
+
+                    if (overdueStatus.getEndDate() != null) {
+                        q.bind("end_date", overdueStatus.getEndDate().getMillis());
+                    } else {
+                        q.bindNull("end_date", Types.BIGINT);
+                    }
+                }
+            };
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusMapper.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusMapper.java
new file mode 100644
index 0000000..83f4fac
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusMapper.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessOverdueStatusModelDao;
+
+public class BusinessOverdueStatusMapper implements ResultSetMapper<BusinessOverdueStatusModelDao> {
+
+    @Override
+    public BusinessOverdueStatusModelDao map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
+        final UUID bundleId = UUID.fromString(r.getString(1));
+        final String externalKey = r.getString(2);
+        final String accountKey = r.getString(3);
+        final String status = r.getString(4);
+        final DateTime startDate = new DateTime(r.getLong(5), DateTimeZone.UTC);
+        final DateTime endDate = new DateTime(r.getLong(6), DateTimeZone.UTC);
+
+        return new BusinessOverdueStatusModelDao(accountKey, bundleId, endDate, externalKey, startDate, status);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusSqlDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusSqlDao.java
new file mode 100644
index 0000000..b3da2a5
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusSqlDao.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessOverdueStatusModelDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.InternalTenantContextBinder;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(BusinessOverdueStatusMapper.class)
+public interface BusinessOverdueStatusSqlDao extends Transactional<BusinessOverdueStatusSqlDao>, Transmogrifier {
+
+    @SqlQuery
+    List<BusinessOverdueStatusModelDao> getOverdueStatusesForBundleByKey(@Bind("external_key") final String externalKey,
+                                                                         @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlUpdate
+    int createOverdueStatus(@BusinessOverdueStatusBinder final BusinessOverdueStatusModelDao status,
+                            @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    void deleteOverdueStatusesForBundle(@Bind("bundle_id") final String bundleId,
+                                        @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    void test(@InternalTenantContextBinder final InternalTenantContext context);
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSqlProvider.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSqlProvider.java
new file mode 100644
index 0000000..242c522
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSqlProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import org.skife.jdbi.v2.IDBI;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class BusinessSqlProvider<T> implements Provider<T> {
+
+    @Inject
+    private IDBI dbi;
+
+    private final Class clazz;
+
+    public BusinessSqlProvider(final Class<T> clazz) {
+        this.clazz = clazz;
+    }
+
+    @Override
+    public T get() {
+        return (T) dbi.onDemand(clazz);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionBinder.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionBinder.java
new file mode 100644
index 0000000..f37fa31
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionBinder.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.sql.Types;
+
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscription;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionModelDao;
+
+@BindingAnnotation(BusinessSubscriptionTransitionBinder.BstBinderFactory.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface BusinessSubscriptionTransitionBinder {
+
+    public static class BstBinderFactory implements BinderFactory {
+
+        public Binder build(final Annotation annotation) {
+            return new Binder<BusinessSubscriptionTransitionBinder, BusinessSubscriptionTransitionModelDao>() {
+                public void bind(final SQLStatement q, final BusinessSubscriptionTransitionBinder bind, final BusinessSubscriptionTransitionModelDao arg) {
+                    q.bind("total_ordering", arg.getTotalOrdering());
+                    q.bind("bundle_id", arg.getBundleId().toString());
+                    q.bind("external_key", arg.getExternalKey());
+                    q.bind("account_id", arg.getAccountId().toString());
+                    q.bind("account_key", arg.getAccountKey());
+                    q.bind("subscription_id", arg.getSubscriptionId().toString());
+                    q.bind("requested_timestamp", arg.getRequestedTimestamp().getMillis());
+                    q.bind("event", arg.getEvent().toString());
+
+                    final BusinessSubscription previousSubscription = arg.getPreviousSubscription();
+                    if (previousSubscription == null) {
+                        q.bindNull("prev_product_name", Types.VARCHAR);
+                        q.bindNull("prev_product_type", Types.VARCHAR);
+                        q.bindNull("prev_product_category", Types.VARCHAR);
+                        q.bindNull("prev_slug", Types.VARCHAR);
+                        q.bindNull("prev_phase", Types.VARCHAR);
+                        q.bindNull("prev_billing_period", Types.VARCHAR);
+                        q.bindNull("prev_price", Types.NUMERIC);
+                        q.bindNull("prev_price_list", Types.VARCHAR);
+                        q.bindNull("prev_mrr", Types.NUMERIC);
+                        q.bindNull("prev_currency", Types.VARCHAR);
+                        q.bindNull("prev_start_date", Types.BIGINT);
+                        q.bindNull("prev_state", Types.VARCHAR);
+                    } else {
+                        q.bind("prev_product_name", previousSubscription.getProductName());
+                        q.bind("prev_product_type", previousSubscription.getProductType());
+                        if (previousSubscription.getProductCategory() == null) {
+                            q.bindNull("prev_product_category", Types.VARCHAR);
+                        } else {
+                            q.bind("prev_product_category", previousSubscription.getProductCategory().toString());
+                        }
+                        q.bind("prev_slug", previousSubscription.getSlug());
+                        q.bind("prev_phase", previousSubscription.getPhase());
+                        q.bind("prev_billing_period", previousSubscription.getBillingPeriod());
+                        q.bind("prev_price", previousSubscription.getRoundedPrice());
+                        q.bind("prev_price_list", previousSubscription.getPriceList());
+                        q.bind("prev_mrr", previousSubscription.getRoundedMrr());
+                        q.bind("prev_currency", previousSubscription.getCurrency());
+                        if (previousSubscription.getStartDate() == null) {
+                            q.bindNull("prev_start_date", Types.BIGINT);
+                        } else {
+                            q.bind("prev_start_date", previousSubscription.getStartDate().getMillis());
+                        }
+                        if (previousSubscription.getState() == null) {
+                            q.bindNull("prev_state", Types.VARCHAR);
+                        } else {
+                            q.bind("prev_state", previousSubscription.getState().toString());
+                        }
+                    }
+
+                    final BusinessSubscription nextSubscription = arg.getNextSubscription();
+                    if (nextSubscription == null) {
+                        q.bindNull("next_product_name", Types.VARCHAR);
+                        q.bindNull("next_product_type", Types.VARCHAR);
+                        q.bindNull("next_product_category", Types.VARCHAR);
+                        q.bindNull("next_slug", Types.VARCHAR);
+                        q.bindNull("next_phase", Types.VARCHAR);
+                        q.bindNull("next_billing_period", Types.VARCHAR);
+                        q.bindNull("next_price", Types.NUMERIC);
+                        q.bindNull("next_price_list", Types.VARCHAR);
+                        q.bindNull("next_mrr", Types.NUMERIC);
+                        q.bindNull("next_currency", Types.VARCHAR);
+                        q.bindNull("next_start_date", Types.BIGINT);
+                        q.bindNull("next_state", Types.VARCHAR);
+                    } else {
+                        q.bind("next_product_name", nextSubscription.getProductName());
+                        q.bind("next_product_type", nextSubscription.getProductType());
+                        if (nextSubscription.getProductCategory() == null) {
+                            q.bindNull("next_product_category", Types.VARCHAR);
+                        } else {
+                            q.bind("next_product_category", nextSubscription.getProductCategory().toString());
+                        }
+                        q.bind("next_slug", nextSubscription.getSlug());
+                        q.bind("next_phase", nextSubscription.getPhase());
+                        q.bind("next_billing_period", nextSubscription.getBillingPeriod());
+                        q.bind("next_price", nextSubscription.getRoundedPrice());
+                        q.bind("next_price_list", nextSubscription.getPriceList());
+                        q.bind("next_mrr", nextSubscription.getRoundedMrr());
+                        q.bind("next_currency", nextSubscription.getCurrency());
+                        if (nextSubscription.getStartDate() == null) {
+                            q.bindNull("next_start_date", Types.BIGINT);
+                        } else {
+                            q.bind("next_start_date", nextSubscription.getStartDate().getMillis());
+                        }
+                        if (nextSubscription.getState() == null) {
+                            q.bindNull("next_state", Types.VARCHAR);
+                        } else {
+                            q.bind("next_state", nextSubscription.getState().toString());
+                        }
+                    }
+                }
+            };
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionFieldMapper.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionFieldMapper.java
new file mode 100644
index 0000000..7699bc6
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionFieldMapper.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionFieldModelDao;
+
+public class BusinessSubscriptionTransitionFieldMapper implements ResultSetMapper<BusinessSubscriptionTransitionFieldModelDao> {
+
+    @Override
+    public BusinessSubscriptionTransitionFieldModelDao map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
+        final UUID bundleId = UUID.fromString(r.getString(1));
+        final String externalKey = r.getString(2);
+        final String accountKey = r.getString(3);
+        final String name = r.getString(4);
+        final String value = r.getString(5);
+        return new BusinessSubscriptionTransitionFieldModelDao(accountKey, bundleId, externalKey, name, value);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionFieldSqlDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionFieldSqlDao.java
new file mode 100644
index 0000000..3f1ac43
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionFieldSqlDao.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionFieldModelDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.InternalTenantContextBinder;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(BusinessSubscriptionTransitionFieldMapper.class)
+public interface BusinessSubscriptionTransitionFieldSqlDao {
+
+    @SqlQuery
+    List<BusinessSubscriptionTransitionFieldModelDao> getFieldsForBusinessSubscriptionTransitionByKey(@Bind("external_key") final String externalKey,
+                                                                                                      @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlUpdate
+    int addField(@Bind("account_key") final String accountKey,
+                 @Bind("bundle_id") final String bundleId,
+                 @Bind("external_key") final String externalKey,
+                 @Bind("name") final String name,
+                 @Bind("value") final String value,
+                 @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    int removeField(@Bind("bundle_id") final String bundleId,
+                    @Bind("name") final String name,
+                    @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    void test(@InternalTenantContextBinder final InternalTenantContext context);
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionMapper.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionMapper.java
new file mode 100644
index 0000000..f70c85e
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionMapper.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscription;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionEvent;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionModelDao;
+
+import static com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+
+public class BusinessSubscriptionTransitionMapper implements ResultSetMapper<BusinessSubscriptionTransitionModelDao> {
+
+    @Override
+    public BusinessSubscriptionTransitionModelDao map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
+        BusinessSubscription prev = new BusinessSubscription(
+                r.getString(9), // productName
+                r.getString(10), // productType
+                r.getString(11) == null ? null : ProductCategory.valueOf(r.getString(11)), // productCategory
+                r.getString(12), // slug
+                r.getString(13),  // phase
+                r.getString(14),  // billing period
+                BigDecimal.valueOf(r.getDouble(15)), // price
+                r.getString(16), // priceList
+                BigDecimal.valueOf(r.getDouble(17)), // mrr
+                r.getString(18), // currency
+                r.getLong(19) == 0 ? null : new DateTime(r.getLong(19), DateTimeZone.UTC), // startDate
+                r.getString(20) == null ? null : SubscriptionState.valueOf(r.getString(20)) // state
+        );
+
+        // Avoid creating a dummy subscriptions with all null fields
+        if (prev.getProductName() == null && prev.getSlug() == null) {
+            prev = null;
+        }
+
+        BusinessSubscription next = new BusinessSubscription(
+                r.getString(21), // productName
+                r.getString(22), // productType
+                r.getString(23) == null ? null : ProductCategory.valueOf(r.getString(23)), // productCategory
+                r.getString(24), // slug8
+                r.getString(25),  // phase
+                r.getString(26),  // billing period
+                BigDecimal.valueOf(r.getDouble(27)), // price
+                r.getString(28), // priceList
+                BigDecimal.valueOf(r.getDouble(29)), // mrr
+                r.getString(30), // currency
+                r.getLong(31) == 0 ? null : new DateTime(r.getLong(31), DateTimeZone.UTC), // startDate
+                r.getString(32) == null ? null : SubscriptionState.valueOf(r.getString(32)) // state
+        );
+
+        // Avoid creating a dummy subscriptions with all null fields
+        if (next.getProductName() == null && next.getSlug() == null) {
+            next = null;
+        }
+
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.valueOf(r.getString(8));
+
+        return new BusinessSubscriptionTransitionModelDao(
+                r.getLong(1),
+                UUID.fromString(r.getString(2)),
+                r.getString(3),
+                UUID.fromString(r.getString(4)),
+                r.getString(5),
+                UUID.fromString(r.getString(6)),
+                new DateTime(r.getLong(7), DateTimeZone.UTC),
+                event,
+                prev,
+                next
+        );
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionSqlDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionSqlDao.java
new file mode 100644
index 0000000..7dc48f4
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionSqlDao.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionModelDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.InternalTenantContextBinder;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper({BusinessSubscriptionTransitionMapper.class, TimeSeriesTupleMapper.class})
+public interface BusinessSubscriptionTransitionSqlDao extends Transactional<BusinessSubscriptionTransitionSqlDao> {
+
+    @SqlQuery
+    List<TimeSeriesTuple> getSubscriptionsCreatedOverTime(@Bind("product_type") final String productType,
+                                                          @Bind("slug") final String slug,
+                                                          @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    List<BusinessSubscriptionTransitionModelDao> getTransitionsByKey(@Bind("external_key") final String externalKey,
+                                                                     @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    List<BusinessSubscriptionTransitionModelDao> getTransitionForSubscription(@Bind("subscription_id") final String subscriptionId,
+                                                                              @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    List<BusinessSubscriptionTransitionModelDao> getTransitionsForAccount(@Bind("account_key") final String accountKey,
+                                                                          @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlUpdate
+    int createTransition(@BusinessSubscriptionTransitionBinder final BusinessSubscriptionTransitionModelDao transition,
+                         @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    void deleteTransitionsForBundle(@Bind("bundle_id") final String bundleId,
+                                    @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    void test(@InternalTenantContextBinder final InternalTenantContext context);
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionTagMapper.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionTagMapper.java
new file mode 100644
index 0000000..8b6d9b5
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionTagMapper.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionTagModelDao;
+
+public class BusinessSubscriptionTransitionTagMapper implements ResultSetMapper<BusinessSubscriptionTransitionTagModelDao> {
+
+    @Override
+    public BusinessSubscriptionTransitionTagModelDao map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
+        final UUID bundleId = UUID.fromString(r.getString(1));
+        final String externalKey = r.getString(2);
+        final String accountKey = r.getString(3);
+        final String name = r.getString(4);
+        return new BusinessSubscriptionTransitionTagModelDao(accountKey, bundleId, externalKey, name);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionTagSqlDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionTagSqlDao.java
new file mode 100644
index 0000000..4d42b39
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionTagSqlDao.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionTagModelDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.InternalTenantContextBinder;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(BusinessSubscriptionTransitionTagMapper.class)
+public interface BusinessSubscriptionTransitionTagSqlDao {
+
+    @SqlQuery
+    List<BusinessSubscriptionTransitionTagModelDao> getTagsForBusinessSubscriptionTransitionByKey(@Bind("external_key") final String externalKey,
+                                                                                                  @InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlUpdate
+    int addTag(@Bind("account_key") final String accountKey,
+               @Bind("bundle_id") final String bundleId,
+               @Bind("external_key") final String externalKey,
+               @Bind("name") final String name,
+               @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    int removeTag(@Bind("bundle_id") final String bundleId,
+                  @Bind("name") final String name,
+                  @InternalTenantContextBinder final InternalCallContext context);
+
+    @SqlUpdate
+    void test(@InternalTenantContextBinder final InternalTenantContext context);
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/DefaultAnalyticsDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/DefaultAnalyticsDao.java
new file mode 100644
index 0000000..b710f85
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/DefaultAnalyticsDao.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import com.ning.billing.analytics.api.TimeSeriesData;
+import com.ning.billing.osgi.bundles.analytics.api.DefaultTimeSeriesData;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountTagModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceItemModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoicePaymentModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessOverdueStatusModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionModelDao;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+
+public class DefaultAnalyticsDao implements AnalyticsDao {
+
+    private final BusinessAccountSqlDao accountSqlDao;
+    private final BusinessSubscriptionTransitionSqlDao subscriptionTransitionSqlDao;
+    private final BusinessInvoiceSqlDao invoiceSqlDao;
+    private final BusinessInvoiceItemSqlDao invoiceItemSqlDao;
+    private final BusinessAccountTagSqlDao accountTagSqlDao;
+    private final BusinessOverdueStatusSqlDao overdueStatusSqlDao;
+    private final BusinessInvoicePaymentSqlDao invoicePaymentSqlDao;
+
+    @Inject
+    public DefaultAnalyticsDao(final BusinessAccountSqlDao accountSqlDao,
+                               final BusinessSubscriptionTransitionSqlDao subscriptionTransitionSqlDao,
+                               final BusinessInvoiceSqlDao invoiceSqlDao,
+                               final BusinessInvoiceItemSqlDao invoiceItemSqlDao,
+                               final BusinessAccountTagSqlDao accountTagSqlDao,
+                               final BusinessOverdueStatusSqlDao overdueStatusSqlDao,
+                               final BusinessInvoicePaymentSqlDao invoicePaymentSqlDao) {
+        this.accountSqlDao = accountSqlDao;
+        this.subscriptionTransitionSqlDao = subscriptionTransitionSqlDao;
+        this.invoiceSqlDao = invoiceSqlDao;
+        this.invoiceItemSqlDao = invoiceItemSqlDao;
+        this.accountTagSqlDao = accountTagSqlDao;
+        this.overdueStatusSqlDao = overdueStatusSqlDao;
+        this.invoicePaymentSqlDao = invoicePaymentSqlDao;
+    }
+
+    @Override
+    public TimeSeriesData getAccountsCreatedOverTime(final InternalTenantContext context) {
+        return new DefaultTimeSeriesData(accountSqlDao.getAccountsCreatedOverTime(context));
+    }
+
+    @Override
+    public TimeSeriesData getSubscriptionsCreatedOverTime(final String productType, final String slug, final InternalTenantContext context) {
+        return new DefaultTimeSeriesData(subscriptionTransitionSqlDao.getSubscriptionsCreatedOverTime(productType, slug, context));
+    }
+
+    @Override
+    public BusinessAccountModelDao getAccountByKey(final String accountKey, final InternalTenantContext context) {
+        return accountSqlDao.getAccountByKey(accountKey, context);
+    }
+
+    @Override
+    public List<BusinessSubscriptionTransitionModelDao> getTransitionsByKey(final String externalKey, final InternalTenantContext context) {
+        return subscriptionTransitionSqlDao.getTransitionsByKey(externalKey, context);
+    }
+
+    @Override
+    public List<BusinessSubscriptionTransitionModelDao> getTransitionsForAccount(final String accountKey, final InternalTenantContext context) {
+        return subscriptionTransitionSqlDao.getTransitionsForAccount(accountKey, context);
+    }
+
+    @Override
+    public List<BusinessInvoiceModelDao> getInvoicesByKey(final String accountKey, final InternalTenantContext context) {
+        return invoiceSqlDao.getInvoicesForAccountByKey(accountKey, context);
+    }
+
+    @Override
+    public List<BusinessAccountTagModelDao> getTagsForAccount(final String accountKey, final InternalTenantContext context) {
+        return accountTagSqlDao.getTagsForAccountByKey(accountKey, context);
+    }
+
+    @Override
+    public List<BusinessInvoiceItemModelDao> getInvoiceItemsForInvoice(final String invoiceId, final InternalTenantContext context) {
+        return invoiceItemSqlDao.getInvoiceItemsForInvoice(invoiceId, context);
+    }
+
+    @Override
+    public List<BusinessOverdueStatusModelDao> getOverdueStatusesForBundleByKey(final String externalKey, final InternalTenantContext context) {
+        return overdueStatusSqlDao.getOverdueStatusesForBundleByKey(externalKey, context);
+    }
+
+    @Override
+    public List<BusinessInvoicePaymentModelDao> getInvoicePaymentsForAccountByKey(final String accountKey, final InternalTenantContext context) {
+        return invoicePaymentSqlDao.getInvoicePaymentsForAccountByKey(accountKey, context);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/DefaultAnalyticsSanityDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/DefaultAnalyticsSanityDao.java
new file mode 100644
index 0000000..6068276
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/DefaultAnalyticsSanityDao.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.Collection;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import org.skife.jdbi.v2.IDBI;
+
+import com.ning.billing.util.callcontext.InternalTenantContext;
+
+public class DefaultAnalyticsSanityDao implements AnalyticsSanityDao {
+
+    private final AnalyticsSanitySqlDao sqlDao;
+
+    @Inject
+    public DefaultAnalyticsSanityDao(final IDBI dbi) {
+        sqlDao = dbi.onDemand(AnalyticsSanitySqlDao.class);
+    }
+
+    @Override
+    public Collection<UUID> checkBstMatchesSubscriptionEvents(final InternalTenantContext context) {
+        return sqlDao.checkBstMatchesSubscriptionEvents(context);
+    }
+
+    @Override
+    public Collection<UUID> checkBiiMatchesInvoiceItems(final InternalTenantContext context) {
+        return sqlDao.checkBiiMatchesInvoiceItems(context);
+    }
+
+    @Override
+    public Collection<UUID> checkBipMatchesInvoicePayments(final InternalTenantContext context) {
+        return sqlDao.checkBipMatchesInvoicePayments(context);
+    }
+
+    @Override
+    public Collection<UUID> checkBinAmountPaidMatchesInvoicePayments(final InternalTenantContext context) {
+        return sqlDao.checkBinAmountPaidMatchesInvoicePayments(context);
+    }
+
+    @Override
+    public Collection<UUID> checkBinAmountChargedMatchesInvoicePayments(final InternalTenantContext context) {
+        return sqlDao.checkBinAmountChargedMatchesInvoicePayments(context);
+    }
+
+    @Override
+    public Collection<UUID> checkBinBiiBalanceConsistency(final InternalTenantContext context) {
+        return sqlDao.checkBinBiiBalanceConsistency(context);
+    }
+
+    @Override
+    public Collection<UUID> checkBinBiiAmountCreditedConsistency(final InternalTenantContext context) {
+        return sqlDao.checkBinBiiAmountCreditedConsistency(context);
+    }
+
+    @Override
+    public Collection<UUID> checkBacBinBiiConsistency(final InternalTenantContext context) {
+        return sqlDao.checkBacBinBiiConsistency(context);
+    }
+
+    @Override
+    public Collection<UUID> checkBacTagsMatchesTags(final InternalTenantContext context) {
+        return sqlDao.checkBacTagsMatchesTags(context);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/TimeSeriesTuple.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/TimeSeriesTuple.java
new file mode 100644
index 0000000..1ad845a
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/TimeSeriesTuple.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import org.joda.time.LocalDate;
+
+public class TimeSeriesTuple {
+
+    private final LocalDate localDate;
+    private final Double value;
+
+    public TimeSeriesTuple(final LocalDate localDate, final Double value) {
+        this.localDate = localDate;
+        this.value = value;
+    }
+
+    public LocalDate getLocalDate() {
+        return localDate;
+    }
+
+    public Double getValue() {
+        return value;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("TimeSeriesTuple");
+        sb.append("{localDate=").append(localDate);
+        sb.append(", value=").append(value);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final TimeSeriesTuple that = (TimeSeriesTuple) o;
+
+        if (localDate != null ? !localDate.equals(that.localDate) : that.localDate != null) {
+            return false;
+        }
+        if (value != null ? !value.equals(that.value) : that.value != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = localDate != null ? localDate.hashCode() : 0;
+        result = 31 * result + (value != null ? value.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/TimeSeriesTupleMapper.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/TimeSeriesTupleMapper.java
new file mode 100644
index 0000000..b24ffe2
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/TimeSeriesTupleMapper.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.joda.time.LocalDate;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+public class TimeSeriesTupleMapper implements ResultSetMapper<TimeSeriesTuple> {
+
+    @Override
+    public TimeSeriesTuple map(final int index, final ResultSet r, final StatementContext ctx) throws SQLException {
+        return new TimeSeriesTuple(new LocalDate(r.getDate(1)), r.getDouble(2));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessAccountFieldModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessAccountFieldModelDao.java
new file mode 100644
index 0000000..5384aff
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessAccountFieldModelDao.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+public class BusinessAccountFieldModelDao extends BusinessFieldModelDao {
+
+    private final UUID accountId;
+    private final String accountKey;
+
+    public BusinessAccountFieldModelDao(final UUID accountId, final String accountKey, final String name, final String value) {
+        super(accountId, name, value);
+        this.accountId = accountId;
+        this.accountKey = accountKey;
+    }
+
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    public String getAccountKey() {
+        return accountKey;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("BusinessAccountFieldModelDao");
+        sb.append("{accountId='").append(accountId).append('\'');
+        sb.append(", accountKey='").append(accountKey).append('\'');
+        sb.append(", name='").append(getName()).append('\'');
+        sb.append(", value='").append(getValue()).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final BusinessAccountFieldModelDao that = (BusinessAccountFieldModelDao) o;
+
+        if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
+            return false;
+        }
+        if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
+            return false;
+        }
+        if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
+            return false;
+        }
+        if (getValue() != null ? !getValue().equals(that.getValue()) : that.getValue() != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = accountId != null ? accountId.hashCode() : 0;
+        result = 31 * result + (accountKey != null ? accountKey.hashCode() : 0);
+        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+        result = 31 * result + (getValue() != null ? getValue().hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessAccountModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessAccountModelDao.java
new file mode 100644
index 0000000..3536f29
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessAccountModelDao.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.osgi.bundles.analytics.utils.Rounder;
+import com.ning.billing.util.entity.EntityBase;
+
+import com.google.common.base.Objects;
+
+public class BusinessAccountModelDao extends EntityBase {
+
+    private final UUID accountId;
+    private String key;
+    private String name;
+    private BigDecimal balance;
+    private LocalDate lastInvoiceDate;
+    private BigDecimal totalInvoiceBalance;
+    private String lastPaymentStatus;
+    private String paymentMethod;
+    private String creditCardType;
+    private String billingAddressCountry;
+    private String currency;
+
+    public BusinessAccountModelDao(final UUID accountId, final String key, final String name, final BigDecimal balance,
+                                   final LocalDate lastInvoiceDate, final BigDecimal totalInvoiceBalance, final String lastPaymentStatus,
+                                   final String paymentMethod, final String creditCardType, final String billingAddressCountry,
+                                   final String currency, final DateTime createdDt, final DateTime updatedDt) {
+        super(accountId, createdDt, updatedDt);
+        this.accountId = accountId;
+        this.key = key;
+        this.balance = balance;
+        this.billingAddressCountry = billingAddressCountry;
+        this.creditCardType = creditCardType;
+        this.lastInvoiceDate = lastInvoiceDate;
+        this.lastPaymentStatus = lastPaymentStatus;
+        this.name = name;
+        this.paymentMethod = paymentMethod;
+        this.totalInvoiceBalance = totalInvoiceBalance;
+        this.currency = currency;
+    }
+
+    public BusinessAccountModelDao(final Account account) {
+        super(account.getId(), account.getCreatedDate(), account.getUpdatedDate());
+        this.accountId = account.getId();
+        this.name = account.getName();
+        this.key = account.getExternalKey();
+        if (account.getCurrency() != null) {
+            this.currency = account.getCurrency().toString();
+        }
+    }
+
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(final String key) {
+        this.key = key;
+    }
+
+    public BigDecimal getBalance() {
+        return Objects.firstNonNull(balance, BigDecimal.ZERO);
+    }
+
+    public Double getRoundedBalance() {
+        return Rounder.round(balance);
+    }
+
+    public void setBalance(final BigDecimal balance) {
+        this.balance = balance;
+    }
+
+    public String getBillingAddressCountry() {
+        return billingAddressCountry;
+    }
+
+    public void setBillingAddressCountry(final String billingAddressCountry) {
+        this.billingAddressCountry = billingAddressCountry;
+    }
+
+    public String getCreditCardType() {
+        return creditCardType;
+    }
+
+    public void setCreditCardType(final String creditCardType) {
+        this.creditCardType = creditCardType;
+    }
+
+    public String getCurrency() {
+        return currency;
+    }
+
+    public void setCurrency(final String currency) {
+        this.currency = currency;
+    }
+
+    public LocalDate getLastInvoiceDate() {
+        return lastInvoiceDate;
+    }
+
+    public void setLastInvoiceDate(final LocalDate lastInvoiceDate) {
+        this.lastInvoiceDate = lastInvoiceDate;
+    }
+
+    public String getLastPaymentStatus() {
+        return lastPaymentStatus;
+    }
+
+    public void setLastPaymentStatus(final String lastPaymentStatus) {
+        this.lastPaymentStatus = lastPaymentStatus;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    public String getPaymentMethod() {
+        return paymentMethod;
+    }
+
+    public void setPaymentMethod(final String paymentMethod) {
+        this.paymentMethod = paymentMethod;
+    }
+
+    public BigDecimal getTotalInvoiceBalance() {
+        return Objects.firstNonNull(totalInvoiceBalance, BigDecimal.ZERO);
+    }
+
+    public Double getRoundedTotalInvoiceBalance() {
+        return Rounder.round(totalInvoiceBalance);
+    }
+
+    public void setTotalInvoiceBalance(final BigDecimal totalInvoiceBalance) {
+        this.totalInvoiceBalance = totalInvoiceBalance;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("BusinessAccountModelDao");
+        sb.append("{balance=").append(balance);
+        sb.append(", createdDate=").append(createdDate);
+        sb.append(", updatedDate=").append(updatedDate);
+        sb.append(", accountId='").append(accountId).append('\'');
+        sb.append(", key='").append(key).append('\'');
+        sb.append(", name='").append(name).append('\'');
+        sb.append(", lastInvoiceDate=").append(lastInvoiceDate);
+        sb.append(", totalInvoiceBalance=").append(totalInvoiceBalance);
+        sb.append(", lastPaymentStatus='").append(lastPaymentStatus).append('\'');
+        sb.append(", paymentMethod='").append(paymentMethod).append('\'');
+        sb.append(", creditCardType='").append(creditCardType).append('\'');
+        sb.append(", billingAddressCountry='").append(billingAddressCountry).append('\'');
+        sb.append(", currency='").append(currency).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final BusinessAccountModelDao that = (BusinessAccountModelDao) o;
+
+        if (balance == null ? that.balance != null : balance.compareTo(that.balance) != 0) {
+            return false;
+        }
+        if (billingAddressCountry != null ? !billingAddressCountry.equals(that.billingAddressCountry) : that.billingAddressCountry != null) {
+            return false;
+        }
+        if (createdDate != null ? !createdDate.equals(that.createdDate) : that.createdDate != null) {
+            return false;
+        }
+        if (creditCardType != null ? !creditCardType.equals(that.creditCardType) : that.creditCardType != null) {
+            return false;
+        }
+        if (currency != null ? !currency.equals(that.currency) : that.currency != null) {
+            return false;
+        }
+        if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
+            return false;
+        }
+        if (key != null ? !key.equals(that.key) : that.key != null) {
+            return false;
+        }
+        if (lastInvoiceDate != null ? lastInvoiceDate.compareTo(that.lastInvoiceDate) != 0 : that.lastInvoiceDate != null) {
+            return false;
+        }
+        if (lastPaymentStatus != null ? !lastPaymentStatus.equals(that.lastPaymentStatus) : that.lastPaymentStatus != null) {
+            return false;
+        }
+        if (name != null ? !name.equals(that.name) : that.name != null) {
+            return false;
+        }
+        if (paymentMethod != null ? !paymentMethod.equals(that.paymentMethod) : that.paymentMethod != null) {
+            return false;
+        }
+        if (totalInvoiceBalance == null ? that.totalInvoiceBalance != null : totalInvoiceBalance.compareTo(that.totalInvoiceBalance) != 0) {
+            return false;
+        }
+        if (updatedDate != null ? updatedDate.compareTo(that.updatedDate) != 0 : that.updatedDate != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = createdDate != null ? createdDate.hashCode() : 0;
+        result = 31 * result + (updatedDate != null ? updatedDate.hashCode() : 0);
+        result = 31 * result + (accountId != null ? accountId.hashCode() : 0);
+        result = 31 * result + (key != null ? key.hashCode() : 0);
+        result = 31 * result + (name != null ? name.hashCode() : 0);
+        result = 31 * result + (balance != null ? balance.hashCode() : 0);
+        result = 31 * result + (lastInvoiceDate != null ? lastInvoiceDate.hashCode() : 0);
+        result = 31 * result + (totalInvoiceBalance != null ? totalInvoiceBalance.hashCode() : 0);
+        result = 31 * result + (lastPaymentStatus != null ? lastPaymentStatus.hashCode() : 0);
+        result = 31 * result + (paymentMethod != null ? paymentMethod.hashCode() : 0);
+        result = 31 * result + (creditCardType != null ? creditCardType.hashCode() : 0);
+        result = 31 * result + (billingAddressCountry != null ? billingAddressCountry.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessAccountTagModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessAccountTagModelDao.java
new file mode 100644
index 0000000..8b419d7
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessAccountTagModelDao.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+public class BusinessAccountTagModelDao extends BusinessTagModelDao {
+
+    private final UUID accountId;
+    private final String accountKey;
+
+    public BusinessAccountTagModelDao(final UUID accountId, final String accountKey, final String name) {
+        super(name);
+        this.accountId = accountId;
+        this.accountKey = accountKey;
+    }
+
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    public String getAccountKey() {
+        return accountKey;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("BusinessAccountTagModelDao");
+        sb.append("{accountId='").append(accountId).append('\'');
+        sb.append(", accountKey='").append(accountKey).append('\'');
+        sb.append(", name='").append(getName()).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final BusinessAccountTagModelDao that = (BusinessAccountTagModelDao) o;
+
+        if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
+            return false;
+        }
+        if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
+            return false;
+        }
+        if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = accountId != null ? accountId.hashCode() : 0;
+        result = 31 * result + (accountKey != null ? accountKey.hashCode() : 0);
+        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessFieldModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessFieldModelDao.java
new file mode 100644
index 0000000..ed0293a
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessFieldModelDao.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+import com.ning.billing.util.entity.EntityBase;
+
+public abstract class BusinessFieldModelDao extends EntityBase {
+
+    private final String name;
+    private final String value;
+
+    public BusinessFieldModelDao(final UUID id, final String name, final String value) {
+        super(id);
+        this.name = name;
+        this.value = value;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoiceFieldModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoiceFieldModelDao.java
new file mode 100644
index 0000000..5bdaeab
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoiceFieldModelDao.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+public class BusinessInvoiceFieldModelDao extends BusinessFieldModelDao {
+
+    private final UUID invoiceId;
+
+    public BusinessInvoiceFieldModelDao(final UUID invoiceId, final String name, final String value) {
+        super(invoiceId, name, value);
+        this.invoiceId = invoiceId;
+    }
+
+    public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("BusinessInvoiceFieldModelDao");
+        sb.append("{invoiceId='").append(invoiceId).append('\'');
+        sb.append(", name='").append(getName()).append('\'');
+        sb.append(", value='").append(getValue()).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final BusinessInvoiceFieldModelDao that = (BusinessInvoiceFieldModelDao) o;
+
+        if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
+            return false;
+        }
+        if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
+            return false;
+        }
+        if (getValue() != null ? !getValue().equals(that.getValue()) : that.getValue() != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = invoiceId != null ? invoiceId.hashCode() : 0;
+        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+        result = 31 * result + (getValue() != null ? getValue().hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoiceItemModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoiceItemModelDao.java
new file mode 100644
index 0000000..53f58e6
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoiceItemModelDao.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+
+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.invoice.api.InvoiceItem;
+import com.ning.billing.osgi.bundles.analytics.utils.Rounder;
+import com.ning.billing.util.entity.EntityBase;
+
+public class BusinessInvoiceItemModelDao extends EntityBase {
+
+    private final UUID itemId;
+    private final UUID invoiceId;
+    private final String itemType;
+    private final String externalKey;
+    private final String productName;
+    private final String productType;
+    private final String productCategory;
+    private final String slug;
+    private final String phase;
+    private final String billingPeriod;
+    private final LocalDate startDate;
+    private final LocalDate endDate;
+    private final BigDecimal amount;
+    private final Currency currency;
+    private final UUID linkedItemId;
+
+    public BusinessInvoiceItemModelDao(final BigDecimal amount, @Nullable final String billingPeriod, final DateTime createdDate,
+                                       final Currency currency, final LocalDate endDate, final String externalKey,
+                                       final UUID invoiceId, final UUID itemId, @Nullable final UUID linkedItemId, final String itemType,
+                                       @Nullable final String phase, @Nullable final String productCategory, @Nullable final String productName,
+                                       @Nullable final String productType, @Nullable final String slug, final LocalDate startDate, final DateTime updatedDate) {
+        super(itemId, createdDate, updatedDate);
+        this.amount = amount;
+        this.billingPeriod = billingPeriod;
+        this.currency = currency;
+        this.endDate = endDate;
+        this.externalKey = externalKey;
+        this.invoiceId = invoiceId;
+        this.itemId = itemId;
+        this.linkedItemId = linkedItemId;
+        this.itemType = itemType;
+        this.phase = phase;
+        this.productCategory = productCategory;
+        this.productName = productName;
+        this.productType = productType;
+        this.slug = slug;
+        this.startDate = startDate;
+    }
+
+    public BusinessInvoiceItemModelDao(@Nullable final String externalKey, final InvoiceItem invoiceItem, @Nullable final Plan plan, @Nullable final PlanPhase planPhase) {
+        this(invoiceItem.getAmount(), planPhase != null ? planPhase.getBillingPeriod().toString() : null, invoiceItem.getCreatedDate(), invoiceItem.getCurrency(),
+             /* Populate end date for fixed items for convenience (null in invoice_items table) */
+             (invoiceItem.getEndDate() == null && planPhase != null) ? invoiceItem.getStartDate().plus(planPhase.getDuration().toJodaPeriod()) : invoiceItem.getEndDate(),
+             externalKey, invoiceItem.getInvoiceId(), invoiceItem.getId(), invoiceItem.getLinkedItemId(), invoiceItem.getInvoiceItemType().toString(),
+             planPhase != null ? planPhase.getPhaseType().toString() : null, plan != null ? plan.getProduct().getCategory().toString() : null,
+             plan != null ? plan.getProduct().getName() : null, plan != null ? plan.getProduct().getCatalogName() : null,
+             planPhase != null ? planPhase.getName() : null, invoiceItem.getStartDate(), invoiceItem.getUpdatedDate());
+    }
+
+    public UUID getItemId() {
+        return itemId;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public String getBillingPeriod() {
+        return billingPeriod;
+    }
+
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    public LocalDate getEndDate() {
+        return endDate;
+    }
+
+    public String getExternalKey() {
+        return externalKey;
+    }
+
+    public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    public String getItemType() {
+        return itemType;
+    }
+
+    public UUID getLinkedItemId() {
+        return linkedItemId;
+    }
+
+    public String getPhase() {
+        return phase;
+    }
+
+    public String getProductCategory() {
+        return productCategory;
+    }
+
+    public String getProductName() {
+        return productName;
+    }
+
+    public String getProductType() {
+        return productType;
+    }
+
+    public String getSlug() {
+        return slug;
+    }
+
+    public LocalDate getStartDate() {
+        return startDate;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("BusinessInvoiceItemModelDao");
+        sb.append("{amount=").append(amount);
+        sb.append(", itemId=").append(itemId);
+        sb.append(", createdDate=").append(createdDate);
+        sb.append(", updatedDate=").append(updatedDate);
+        sb.append(", invoiceId=").append(invoiceId);
+        sb.append(", itemType='").append(itemType).append('\'');
+        sb.append(", externalKey='").append(externalKey).append('\'');
+        sb.append(", productName='").append(productName).append('\'');
+        sb.append(", productType='").append(productType).append('\'');
+        sb.append(", productCategory='").append(productCategory).append('\'');
+        sb.append(", slug='").append(slug).append('\'');
+        sb.append(", phase='").append(phase).append('\'');
+        sb.append(", billingPeriod='").append(billingPeriod).append('\'');
+        sb.append(", startDate=").append(startDate);
+        sb.append(", endDate=").append(endDate);
+        sb.append(", currency=").append(currency);
+        sb.append(", linkedItemId=").append(linkedItemId);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final BusinessInvoiceItemModelDao that = (BusinessInvoiceItemModelDao) o;
+
+        if (amount != null ? Rounder.round(amount) != (Rounder.round(that.amount)) : that.amount != null) {
+            return false;
+        }
+        if (billingPeriod != null ? !billingPeriod.equals(that.billingPeriod) : that.billingPeriod != null) {
+            return false;
+        }
+        if (createdDate != null ? !createdDate.equals(that.createdDate) : that.createdDate != null) {
+            return false;
+        }
+        if (currency != that.currency) {
+            return false;
+        }
+        if (endDate != null ? !endDate.equals(that.endDate) : that.endDate != null) {
+            return false;
+        }
+        if (externalKey != null ? !externalKey.equals(that.externalKey) : that.externalKey != null) {
+            return false;
+        }
+        if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
+            return false;
+        }
+        if (itemId != null ? !itemId.equals(that.itemId) : that.itemId != null) {
+            return false;
+        }
+        if (itemType != null ? !itemType.equals(that.itemType) : that.itemType != null) {
+            return false;
+        }
+        if (linkedItemId != null ? !linkedItemId.equals(that.linkedItemId) : that.linkedItemId != null) {
+            return false;
+        }
+        if (phase != null ? !phase.equals(that.phase) : that.phase != null) {
+            return false;
+        }
+        if (productCategory != null ? !productCategory.equals(that.productCategory) : that.productCategory != null) {
+            return false;
+        }
+        if (productName != null ? !productName.equals(that.productName) : that.productName != null) {
+            return false;
+        }
+        if (productType != null ? !productType.equals(that.productType) : that.productType != null) {
+            return false;
+        }
+        if (slug != null ? !slug.equals(that.slug) : that.slug != null) {
+            return false;
+        }
+        if (startDate != null ? !startDate.equals(that.startDate) : that.startDate != null) {
+            return false;
+        }
+        if (updatedDate != null ? !updatedDate.equals(that.updatedDate) : that.updatedDate != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = itemId != null ? itemId.hashCode() : 0;
+        result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0);
+        result = 31 * result + (updatedDate != null ? updatedDate.hashCode() : 0);
+        result = 31 * result + (invoiceId != null ? invoiceId.hashCode() : 0);
+        result = 31 * result + (itemType != null ? itemType.hashCode() : 0);
+        result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
+        result = 31 * result + (productName != null ? productName.hashCode() : 0);
+        result = 31 * result + (productType != null ? productType.hashCode() : 0);
+        result = 31 * result + (productCategory != null ? productCategory.hashCode() : 0);
+        result = 31 * result + (slug != null ? slug.hashCode() : 0);
+        result = 31 * result + (phase != null ? phase.hashCode() : 0);
+        result = 31 * result + (billingPeriod != null ? billingPeriod.hashCode() : 0);
+        result = 31 * result + (startDate != null ? startDate.hashCode() : 0);
+        result = 31 * result + (endDate != null ? endDate.hashCode() : 0);
+        result = 31 * result + (amount != null ? amount.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        result = 31 * result + (linkedItemId != null ? linkedItemId.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoiceModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoiceModelDao.java
new file mode 100644
index 0000000..4515283
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoiceModelDao.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.osgi.bundles.analytics.utils.Rounder;
+import com.ning.billing.util.entity.EntityBase;
+
+public class BusinessInvoiceModelDao extends EntityBase {
+
+    private final UUID invoiceId;
+    private final Integer invoiceNumber;
+    private final UUID accountId;
+    private final String accountKey;
+    private final LocalDate invoiceDate;
+    private final LocalDate targetDate;
+    private final Currency currency;
+    private final BigDecimal balance;
+    private final BigDecimal amountPaid;
+    private final BigDecimal amountCharged;
+    private final BigDecimal amountCredited;
+
+    public BusinessInvoiceModelDao(final UUID accountId, final String accountKey, final BigDecimal amountCharged, final BigDecimal amountCredited,
+                                   final BigDecimal amountPaid, final BigDecimal balance, final DateTime createdDate,
+                                   final Currency currency, final LocalDate invoiceDate, final UUID invoiceId, final Integer invoiceNumber,
+                                   final LocalDate targetDate, final DateTime updatedDate) {
+        super(invoiceId, createdDate, updatedDate);
+        this.accountId = accountId;
+        this.accountKey = accountKey;
+        this.amountCharged = amountCharged;
+        this.amountCredited = amountCredited;
+        this.amountPaid = amountPaid;
+        this.balance = balance;
+        this.currency = currency;
+        this.invoiceDate = invoiceDate;
+        this.invoiceId = invoiceId;
+        this.invoiceNumber = invoiceNumber;
+        this.targetDate = targetDate;
+    }
+
+    public BusinessInvoiceModelDao(final String accountKey, final Invoice invoice) {
+        this(invoice.getAccountId(), accountKey, invoice.getChargedAmount(), invoice.getCreditAdjAmount(), invoice.getPaidAmount(), invoice.getBalance(),
+             invoice.getCreatedDate(), invoice.getCurrency(), invoice.getInvoiceDate(), invoice.getId(), invoice.getInvoiceNumber(), invoice.getTargetDate(),
+             invoice.getUpdatedDate());
+    }
+
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    public String getAccountKey() {
+        return accountKey;
+    }
+
+    public BigDecimal getAmountCharged() {
+        return amountCharged;
+    }
+
+    public BigDecimal getAmountCredited() {
+        return amountCredited;
+    }
+
+    public BigDecimal getAmountPaid() {
+        return amountPaid;
+    }
+
+    public BigDecimal getBalance() {
+        return balance;
+    }
+
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    public LocalDate getInvoiceDate() {
+        return invoiceDate;
+    }
+
+    public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    public Integer getInvoiceNumber() {
+        return invoiceNumber;
+    }
+
+    public LocalDate getTargetDate() {
+        return targetDate;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("BusinessInvoiceModelDao");
+        sb.append("{accountId=").append(accountId);
+        sb.append(", invoiceId=").append(invoiceId);
+        sb.append(", invoiceNumber=").append(invoiceNumber);
+        sb.append(", createdDate=").append(createdDate);
+        sb.append(", updatedDate=").append(updatedDate);
+        sb.append(", accountKey='").append(accountKey).append('\'');
+        sb.append(", invoiceDate=").append(invoiceDate);
+        sb.append(", targetDate=").append(targetDate);
+        sb.append(", currency=").append(currency);
+        sb.append(", balance=").append(balance);
+        sb.append(", amountPaid=").append(amountPaid);
+        sb.append(", amountCharged=").append(amountCharged);
+        sb.append(", amountCredited=").append(amountCredited);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final BusinessInvoiceModelDao that = (BusinessInvoiceModelDao) o;
+
+        if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
+            return false;
+        }
+        if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
+            return false;
+        }
+        if (amountCharged != null ? Rounder.round(amountCharged) != Rounder.round(that.amountCharged) : that.amountCharged != null) {
+            return false;
+        }
+        if (amountCredited != null ? Rounder.round(amountCredited) != Rounder.round(that.amountCredited) : that.amountCredited != null) {
+            return false;
+        }
+        if (amountPaid != null ? Rounder.round(amountPaid) != Rounder.round(that.amountPaid) : that.amountPaid != null) {
+            return false;
+        }
+        if (balance != null ? Rounder.round(balance) != Rounder.round(that.balance) : that.balance != null) {
+            return false;
+        }
+        if (createdDate != null ? createdDate.compareTo(that.createdDate) != 0 : that.createdDate != null) {
+            return false;
+        }
+        if (currency != that.currency) {
+            return false;
+        }
+        if (invoiceDate != null ? invoiceDate.compareTo(that.invoiceDate) != 0 : that.invoiceDate != null) {
+            return false;
+        }
+        if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
+            return false;
+        }
+        if (invoiceNumber != null ? !invoiceNumber.equals(that.invoiceNumber) : that.invoiceNumber != null) {
+            return false;
+        }
+        if (targetDate != null ? targetDate.compareTo(that.targetDate) != 0 : that.targetDate != null) {
+            return false;
+        }
+        if (updatedDate != null ? updatedDate.compareTo(that.updatedDate) != 0 : that.updatedDate != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = invoiceId != null ? invoiceId.hashCode() : 0;
+        result = 31 * result + (invoiceNumber != null ? invoiceNumber.hashCode() : 0);
+        result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0);
+        result = 31 * result + (updatedDate != null ? updatedDate.hashCode() : 0);
+        result = 31 * result + (accountId != null ? accountId.hashCode() : 0);
+        result = 31 * result + (accountKey != null ? accountKey.hashCode() : 0);
+        result = 31 * result + (invoiceDate != null ? invoiceDate.hashCode() : 0);
+        result = 31 * result + (targetDate != null ? targetDate.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        result = 31 * result + (balance != null ? balance.hashCode() : 0);
+        result = 31 * result + (amountPaid != null ? amountPaid.hashCode() : 0);
+        result = 31 * result + (amountCharged != null ? amountCharged.hashCode() : 0);
+        result = 31 * result + (amountCredited != null ? amountCredited.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoicePaymentFieldModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoicePaymentFieldModelDao.java
new file mode 100644
index 0000000..d051bf0
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoicePaymentFieldModelDao.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+public class BusinessInvoicePaymentFieldModelDao extends BusinessFieldModelDao {
+
+    private final UUID paymentId;
+
+    public BusinessInvoicePaymentFieldModelDao(final UUID paymentId, final String name, final String value) {
+        super(paymentId, name, value);
+        this.paymentId = paymentId;
+    }
+
+    public UUID getPaymentId() {
+        return paymentId;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("BusinessPaymentField");
+        sb.append("{paymentId='").append(paymentId).append('\'');
+        sb.append(", name='").append(getName()).append('\'');
+        sb.append(", value='").append(getValue()).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final BusinessInvoicePaymentFieldModelDao that = (BusinessInvoicePaymentFieldModelDao) o;
+
+        if (paymentId != null ? !paymentId.equals(that.paymentId) : that.paymentId != null) {
+            return false;
+        }
+        if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
+            return false;
+        }
+        if (getValue() != null ? !getValue().equals(that.getValue()) : that.getValue() != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = paymentId != null ? paymentId.hashCode() : 0;
+        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+        result = 31 * result + (getValue() != null ? getValue().hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoicePaymentModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoicePaymentModelDao.java
new file mode 100644
index 0000000..e04d1d6
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoicePaymentModelDao.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.osgi.bundles.analytics.utils.Rounder;
+import com.ning.billing.util.entity.EntityBase;
+
+public class BusinessInvoicePaymentModelDao extends EntityBase {
+
+    private final UUID paymentId;
+    private final String extFirstPaymentRefId;
+    private final String extSecondPaymentRefId;
+    private final String accountKey;
+    private final UUID invoiceId;
+    private final DateTime effectiveDate;
+    private final BigDecimal amount;
+    private final Currency currency;
+    private final String paymentError;
+    private final String processingStatus;
+    private final BigDecimal requestedAmount;
+    private final String pluginName;
+    private final String paymentType;
+    private final String paymentMethod;
+    private final String cardType;
+    private final String cardCountry;
+    private final String invoicePaymentType;
+    private final UUID linkedInvoicePaymentId;
+
+    public BusinessInvoicePaymentModelDao(final String accountKey, final BigDecimal amount,
+                                          final String cardCountry, final String cardType, final DateTime createdDate,
+                                          final Currency currency, final DateTime effectiveDate, final UUID invoiceId,
+                                          final String paymentError, final UUID paymentId, final String paymentMethod,
+                                          final String paymentType, @Nullable final String pluginName, final String processingStatus,
+                                          final BigDecimal requestedAmount, final DateTime updatedDate, @Nullable final String invoicePaymentType,
+                                          @Nullable final UUID linkedInvoicePaymentId) {
+        super(paymentId, createdDate, updatedDate);
+        this.accountKey = accountKey;
+        this.amount = amount;
+        // TODO For backward compatibility
+        this.extFirstPaymentRefId = null;
+        this.extSecondPaymentRefId = null;
+        this.cardCountry = cardCountry;
+        this.cardType = cardType;
+        this.currency = currency;
+        this.effectiveDate = effectiveDate;
+        this.invoiceId = invoiceId;
+        this.paymentError = paymentError;
+        this.paymentId = paymentId;
+        this.paymentMethod = paymentMethod;
+        this.paymentType = paymentType;
+        this.pluginName = pluginName;
+        this.processingStatus = processingStatus;
+        this.requestedAmount = requestedAmount;
+        this.invoicePaymentType = invoicePaymentType;
+        this.linkedInvoicePaymentId = linkedInvoicePaymentId;
+    }
+
+    public String getExtFirstPaymentRefId() {
+        return extFirstPaymentRefId;
+    }
+
+    public String getExtSecondPaymentRefId() {
+        return extSecondPaymentRefId;
+    }
+
+    public UUID getPaymentId() {
+        return paymentId;
+    }
+
+    public String getAccountKey() {
+        return accountKey;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public String getCardCountry() {
+        return cardCountry;
+    }
+
+    public String getCardType() {
+        return cardType;
+    }
+
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    public DateTime getEffectiveDate() {
+        return effectiveDate;
+    }
+
+    public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    public String getPaymentError() {
+        return paymentError;
+    }
+
+    public String getPaymentMethod() {
+        return paymentMethod;
+    }
+
+    public String getPaymentType() {
+        return paymentType;
+    }
+
+    public String getPluginName() {
+        return pluginName;
+    }
+
+    public String getProcessingStatus() {
+        return processingStatus;
+    }
+
+    public BigDecimal getRequestedAmount() {
+        return requestedAmount;
+    }
+
+    public String getInvoicePaymentType() {
+        return invoicePaymentType;
+    }
+
+    public UUID getLinkedInvoicePaymentId() {
+        return linkedInvoicePaymentId;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("BusinessInvoicePaymentModelDao");
+        sb.append("{accountKey='").append(accountKey).append('\'');
+        sb.append(", paymentId=").append(paymentId);
+        sb.append(", createdDate=").append(createdDate);
+        sb.append(", extFirstPaymentRefId=").append(extFirstPaymentRefId);
+        sb.append(", updatedDate=").append(updatedDate);
+        sb.append(", invoiceId=").append(invoiceId);
+        sb.append(", effectiveDate=").append(effectiveDate);
+        sb.append(", amount=").append(amount);
+        sb.append(", currency=").append(currency);
+        sb.append(", paymentError='").append(paymentError).append('\'');
+        sb.append(", processingStatus='").append(processingStatus).append('\'');
+        sb.append(", requestedAmount=").append(requestedAmount);
+        sb.append(", pluginName='").append(pluginName).append('\'');
+        sb.append(", paymentType='").append(paymentType).append('\'');
+        sb.append(", paymentMethod='").append(paymentMethod).append('\'');
+        sb.append(", cardType='").append(cardType).append('\'');
+        sb.append(", cardCountry='").append(cardCountry).append('\'');
+        sb.append(", invoicePaymentType='").append(invoicePaymentType).append('\'');
+        sb.append(", linkedInvoicePaymentId='").append(linkedInvoicePaymentId).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final BusinessInvoicePaymentModelDao that = (BusinessInvoicePaymentModelDao) o;
+
+        if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
+            return false;
+        }
+        if (amount != null ? Rounder.round(amount) != Rounder.round(that.amount) : that.amount != null) {
+            return false;
+        }
+        if (extFirstPaymentRefId != null ? !extFirstPaymentRefId.equals(that.extFirstPaymentRefId) : that.extFirstPaymentRefId != null) {
+            return false;
+        }
+        if (cardCountry != null ? !cardCountry.equals(that.cardCountry) : that.cardCountry != null) {
+            return false;
+        }
+        if (cardType != null ? !cardType.equals(that.cardType) : that.cardType != null) {
+            return false;
+        }
+        if (createdDate != null ? !createdDate.equals(that.createdDate) : that.createdDate != null) {
+            return false;
+        }
+        if (currency != that.currency) {
+            return false;
+        }
+        if (effectiveDate != null ? !effectiveDate.equals(that.effectiveDate) : that.effectiveDate != null) {
+            return false;
+        }
+        if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
+            return false;
+        }
+        if (paymentError != null ? !paymentError.equals(that.paymentError) : that.paymentError != null) {
+            return false;
+        }
+        if (paymentId != null ? !paymentId.equals(that.paymentId) : that.paymentId != null) {
+            return false;
+        }
+        if (paymentMethod != null ? !paymentMethod.equals(that.paymentMethod) : that.paymentMethod != null) {
+            return false;
+        }
+        if (paymentType != null ? !paymentType.equals(that.paymentType) : that.paymentType != null) {
+            return false;
+        }
+        if (pluginName != null ? !pluginName.equals(that.pluginName) : that.pluginName != null) {
+            return false;
+        }
+        if (processingStatus != null ? !processingStatus.equals(that.processingStatus) : that.processingStatus != null) {
+            return false;
+        }
+        if (requestedAmount != null ? Rounder.round(requestedAmount) != Rounder.round(that.requestedAmount) : that.requestedAmount != null) {
+            return false;
+        }
+        if (updatedDate != null ? !updatedDate.equals(that.updatedDate) : that.updatedDate != null) {
+            return false;
+        }
+        if (invoicePaymentType != null ? !invoicePaymentType.equals(that.invoicePaymentType) : that.invoicePaymentType != null) {
+            return false;
+        }
+        if (linkedInvoicePaymentId != null ? !linkedInvoicePaymentId.equals(that.linkedInvoicePaymentId) : that.linkedInvoicePaymentId != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = paymentId != null ? paymentId.hashCode() : 0;
+        result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0);
+        result = 31 * result + (extFirstPaymentRefId != null ? extFirstPaymentRefId.hashCode() : 0);
+        result = 31 * result + (updatedDate != null ? updatedDate.hashCode() : 0);
+        result = 31 * result + (accountKey != null ? accountKey.hashCode() : 0);
+        result = 31 * result + (invoiceId != null ? invoiceId.hashCode() : 0);
+        result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
+        result = 31 * result + (amount != null ? amount.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        result = 31 * result + (paymentError != null ? paymentError.hashCode() : 0);
+        result = 31 * result + (processingStatus != null ? processingStatus.hashCode() : 0);
+        result = 31 * result + (requestedAmount != null ? requestedAmount.hashCode() : 0);
+        result = 31 * result + (pluginName != null ? pluginName.hashCode() : 0);
+        result = 31 * result + (paymentType != null ? paymentType.hashCode() : 0);
+        result = 31 * result + (paymentMethod != null ? paymentMethod.hashCode() : 0);
+        result = 31 * result + (cardType != null ? cardType.hashCode() : 0);
+        result = 31 * result + (cardCountry != null ? cardCountry.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoicePaymentTagModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoicePaymentTagModelDao.java
new file mode 100644
index 0000000..92606cf
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoicePaymentTagModelDao.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+public class BusinessInvoicePaymentTagModelDao extends BusinessTagModelDao {
+
+    private final UUID paymentId;
+
+    public BusinessInvoicePaymentTagModelDao(final UUID paymentId, final String name) {
+        super(name);
+        this.paymentId = paymentId;
+    }
+
+    public UUID getPaymentId() {
+        return paymentId;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("BusinessPaymentTag");
+        sb.append("{paymentId='").append(paymentId).append('\'');
+        sb.append(", name='").append(getName()).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final BusinessInvoicePaymentTagModelDao that = (BusinessInvoicePaymentTagModelDao) o;
+
+        if (paymentId != null ? !paymentId.equals(that.paymentId) : that.paymentId != null) {
+            return false;
+        }
+        if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = paymentId != null ? paymentId.hashCode() : 0;
+        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoiceTagModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoiceTagModelDao.java
new file mode 100644
index 0000000..2740d37
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessInvoiceTagModelDao.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+public class BusinessInvoiceTagModelDao extends BusinessTagModelDao {
+
+    private final UUID invoiceId;
+
+    public BusinessInvoiceTagModelDao(final UUID invoiceId, final String name) {
+        super(name);
+        this.invoiceId = invoiceId;
+    }
+
+    public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("BusinessInvoiceTagModelDao");
+        sb.append("{paymentId='").append(invoiceId).append('\'');
+        sb.append(", name='").append(getName()).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final BusinessInvoiceTagModelDao that = (BusinessInvoiceTagModelDao) o;
+
+        if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
+            return false;
+        }
+        if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = invoiceId != null ? invoiceId.hashCode() : 0;
+        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessOverdueStatusModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessOverdueStatusModelDao.java
new file mode 100644
index 0000000..13540b1
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessOverdueStatusModelDao.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.util.entity.EntityBase;
+
+public class BusinessOverdueStatusModelDao extends EntityBase {
+
+    private final String accountKey;
+    private final UUID bundleId;
+    private final String externalKey;
+    private final String status;
+    private final DateTime startDate;
+    private final DateTime endDate;
+
+    public BusinessOverdueStatusModelDao(final String accountKey, final UUID bundleId, @Nullable final DateTime endDate,
+                                         final String externalKey, final DateTime startDate, final String status) {
+        this.accountKey = accountKey;
+        this.bundleId = bundleId;
+        this.endDate = endDate;
+        this.externalKey = externalKey;
+        this.startDate = startDate;
+        this.status = status;
+    }
+
+    public String getAccountKey() {
+        return accountKey;
+    }
+
+    public UUID getBundleId() {
+        return bundleId;
+    }
+
+    public DateTime getEndDate() {
+        return endDate;
+    }
+
+    public String getExternalKey() {
+        return externalKey;
+    }
+
+    public DateTime getStartDate() {
+        return startDate;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("BusinessOverdueStatusModelDao");
+        sb.append("{accountKey=").append(accountKey);
+        sb.append(", bundleId='").append(bundleId).append('\'');
+        sb.append(", endDate='").append(endDate).append('\'');
+        sb.append(", externalKey='").append(externalKey).append('\'');
+        sb.append(", status='").append(status).append('\'');
+        sb.append(", startDate=").append(startDate);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final BusinessOverdueStatusModelDao that = (BusinessOverdueStatusModelDao) o;
+
+        if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
+            return false;
+        }
+        if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null) {
+            return false;
+        }
+        if (endDate != null ? !endDate.equals(that.endDate) : that.endDate != null) {
+            return false;
+        }
+        if (externalKey != null ? !externalKey.equals(that.externalKey) : that.externalKey != null) {
+            return false;
+        }
+        if (startDate != null ? !startDate.equals(that.startDate) : that.startDate != null) {
+            return false;
+        }
+        if (status != null ? !status.equals(that.status) : that.status != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = accountKey != null ? accountKey.hashCode() : 0;
+        result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
+        result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
+        result = 31 * result + (status != null ? status.hashCode() : 0);
+        result = 31 * result + (startDate != null ? startDate.hashCode() : 0);
+        result = 31 * result + (endDate != null ? endDate.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessSubscription.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessSubscription.java
new file mode 100644
index 0000000..5bebb96
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessSubscription.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogApiException;
+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.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.osgi.bundles.analytics.utils.Rounder;
+
+import static com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+
+/**
+ * Describe a subscription for Analytics purposes
+ */
+public class BusinessSubscription {
+
+    private static final Logger log = LoggerFactory.getLogger(BusinessSubscription.class);
+
+    private static final Currency USD = Currency.valueOf("USD");
+
+    private final String productName;
+    private final String productType;
+    private final ProductCategory productCategory;
+    private final String slug;
+    private final String phase;
+    private final String billingPeriod;
+    private final BigDecimal price;
+    private final String priceList;
+    private final BigDecimal mrr;
+    private final String currency;
+    private final DateTime startDate;
+    private final SubscriptionState state;
+
+    public BusinessSubscription(final String productName, final String productType, final ProductCategory productCategory,
+                                final String slug, final String phase, final String billingPeriod, final BigDecimal price,
+                                final String priceList, final BigDecimal mrr, final String currency, final DateTime startDate, final SubscriptionState state) {
+        this.productName = productName;
+        this.productType = productType;
+        this.productCategory = productCategory;
+        this.slug = slug;
+        this.phase = phase;
+        this.billingPeriod = billingPeriod;
+        this.price = price;
+        this.priceList = priceList;
+        this.mrr = mrr;
+        this.currency = currency;
+        this.startDate = startDate;
+        this.state = state;
+    }
+
+    /**
+     * For unit tests only.
+     * <p/>
+     * You can't really use this constructor in real life because the start date is likely not the one you want (you likely
+     * want the phase start date).
+     *
+     * @param subscription Subscription to use as a model
+     * @param currency     ACCOUNT currency
+     * @param catalog      Catalog to use
+     */
+    BusinessSubscription(final Subscription subscription, final Currency currency, final Catalog catalog) {
+        this(subscription.getCurrentPriceList() == null ? null : subscription.getCurrentPriceList().getName(),
+             subscription.getCurrentPlan().getName(), subscription.getCurrentPhase().getName(), currency,
+             subscription.getStartDate(), subscription.getState(), catalog);
+    }
+
+    public BusinessSubscription(final String priceList, final String currentPlan, final String currentPhase, final Currency currency,
+                                final DateTime startDate, final SubscriptionState state, final Catalog catalog) {
+        Plan thePlan = null;
+        PlanPhase thePhase = null;
+        try {
+            thePlan = (currentPlan != null) ? catalog.findPlan(currentPlan, new DateTime(), startDate) : null;
+            thePhase = (currentPhase != null) ? catalog.findPhase(currentPhase, new DateTime(), startDate) : null;
+        } catch (CatalogApiException e) {
+            log.error("Failed to retrieve Plan from catalog for plan {}, phase {}", currentPlan, currentPhase);
+        }
+
+        this.priceList = priceList;
+
+        // Record plan information
+        if (currentPlan != null && thePlan != null && thePlan.getProduct() != null) {
+            final Product product = thePlan.getProduct();
+            productName = product.getName();
+            productCategory = product.getCategory();
+            // TODO - we should keep the product type
+            productType = product.getCatalogName();
+        } else {
+            productName = null;
+            productCategory = null;
+            productType = null;
+        }
+
+        // Record phase information
+        if (currentPhase != null && thePhase != null) {
+            slug = thePhase.getName();
+
+            if (thePhase.getPhaseType() != null) {
+                phase = thePhase.getPhaseType().toString();
+            } else {
+                phase = null;
+            }
+
+            if (thePhase.getBillingPeriod() != null) {
+                billingPeriod = thePhase.getBillingPeriod().toString();
+            } else {
+                billingPeriod = null;
+            }
+
+            if (thePhase.getRecurringPrice() != null) {
+                //TODO check if this is the right way to handle exception
+                BigDecimal tmpPrice;
+                try {
+                    tmpPrice = thePhase.getRecurringPrice().getPrice(USD);
+                } catch (CatalogApiException e) {
+                    tmpPrice = new BigDecimal(0);
+                }
+                price = tmpPrice;
+                mrr = getMrrFromBillingPeriod(thePhase.getBillingPeriod(), price);
+            } else {
+                price = BigDecimal.ZERO;
+                mrr = BigDecimal.ZERO;
+            }
+        } else {
+            slug = null;
+            phase = null;
+            billingPeriod = null;
+            price = BigDecimal.ZERO;
+            mrr = BigDecimal.ZERO;
+        }
+
+        if (currency != null) {
+            this.currency = currency.toString();
+        } else {
+            this.currency = null;
+        }
+
+        this.startDate = startDate;
+        this.state = state;
+    }
+
+    public BusinessSubscription(final String priceList, final Plan currentPlan, final PlanPhase currentPhase, final Currency currency,
+                                final DateTime startDate, final SubscriptionState state) {
+        this.priceList = priceList;
+
+        // Record plan information
+        if (currentPlan != null && currentPlan.getProduct() != null) {
+            final Product product = currentPlan.getProduct();
+            productName = product.getName();
+            productCategory = product.getCategory();
+            // TODO - we should keep the product type
+            productType = product.getCatalogName();
+        } else {
+            productName = null;
+            productCategory = null;
+            productType = null;
+        }
+
+        // Record phase information
+        if (currentPhase != null) {
+            slug = currentPhase.getName();
+
+            if (currentPhase.getPhaseType() != null) {
+                phase = currentPhase.getPhaseType().toString();
+            } else {
+                phase = null;
+            }
+
+            if (currentPhase.getBillingPeriod() != null) {
+                billingPeriod = currentPhase.getBillingPeriod().toString();
+            } else {
+                billingPeriod = null;
+            }
+
+            if (currentPhase.getRecurringPrice() != null) {
+                //TODO check if this is the right way to handle exception
+                BigDecimal tmpPrice;
+                try {
+                    tmpPrice = currentPhase.getRecurringPrice().getPrice(USD);
+                } catch (CatalogApiException e) {
+                    tmpPrice = new BigDecimal(0);
+                }
+                price = tmpPrice;
+                mrr = getMrrFromBillingPeriod(currentPhase.getBillingPeriod(), price);
+            } else {
+                price = BigDecimal.ZERO;
+                mrr = BigDecimal.ZERO;
+            }
+        } else {
+            slug = null;
+            phase = null;
+            billingPeriod = null;
+            price = BigDecimal.ZERO;
+            mrr = BigDecimal.ZERO;
+        }
+
+        if (currency != null) {
+            this.currency = currency.toString();
+        } else {
+            this.currency = null;
+        }
+
+        this.startDate = startDate;
+        this.state = state;
+    }
+
+    public String getBillingPeriod() {
+        return billingPeriod;
+    }
+
+    public String getCurrency() {
+        return currency;
+    }
+
+    public BigDecimal getMrr() {
+        return mrr;
+    }
+
+    public double getRoundedMrr() {
+        return Rounder.round(mrr);
+    }
+
+    public String getPhase() {
+        return phase;
+    }
+
+    public BigDecimal getPrice() {
+        return price;
+    }
+
+    public String getPriceList() {
+        return priceList;
+    }
+
+    public double getRoundedPrice() {
+        return Rounder.round(price);
+    }
+
+    public ProductCategory getProductCategory() {
+        return productCategory;
+    }
+
+    public String getProductName() {
+        return productName;
+    }
+
+    public String getProductType() {
+        return productType;
+    }
+
+    public String getSlug() {
+        return slug;
+    }
+
+    public DateTime getStartDate() {
+        return startDate;
+    }
+
+    public SubscriptionState getState() {
+        return state;
+    }
+
+    static BigDecimal getMrrFromBillingPeriod(final BillingPeriod period, final BigDecimal price) {
+        if (period == null || period.getNumberOfMonths() == 0) {
+            return BigDecimal.ZERO;
+        }
+
+        return price.divide(BigDecimal.valueOf(period.getNumberOfMonths()), Rounder.SCALE, RoundingMode.HALF_UP);
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("BusinessSubscription");
+        sb.append("{billingPeriod='").append(billingPeriod).append('\'');
+        sb.append(", productName='").append(productName).append('\'');
+        sb.append(", productType='").append(productType).append('\'');
+        sb.append(", productCategory=").append(productCategory);
+        sb.append(", slug='").append(slug).append('\'');
+        sb.append(", phase='").append(phase).append('\'');
+        sb.append(", price=").append(price);
+        sb.append(", priceList=").append(priceList);
+        sb.append(", mrr=").append(mrr);
+        sb.append(", currency='").append(currency).append('\'');
+        sb.append(", startDate=").append(startDate);
+        sb.append(", state=").append(state);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final BusinessSubscription that = (BusinessSubscription) o;
+
+        if (billingPeriod != null ? !billingPeriod.equals(that.billingPeriod) : that.billingPeriod != null) {
+            return false;
+        }
+        if (currency != null ? !currency.equals(that.currency) : that.currency != null) {
+            return false;
+        }
+        if (mrr != null ? !(Rounder.round(mrr) == Rounder.round(that.mrr)) : that.mrr != null) {
+            return false;
+        }
+        if (phase != null ? !phase.equals(that.phase) : that.phase != null) {
+            return false;
+        }
+        if (price != null ? !(Rounder.round(price) == Rounder.round(that.price)) : that.price != null) {
+            return false;
+        }
+        if (priceList != null ? !priceList.equals(that.priceList) : that.priceList != null) {
+            return false;
+        }
+        if (productCategory != null ? !productCategory.equals(that.productCategory) : that.productCategory != null) {
+            return false;
+        }
+        if (productName != null ? !productName.equals(that.productName) : that.productName != null) {
+            return false;
+        }
+        if (productType != null ? !productType.equals(that.productType) : that.productType != null) {
+            return false;
+        }
+        if (slug != null ? !slug.equals(that.slug) : that.slug != null) {
+            return false;
+        }
+        if (startDate != null ? !startDate.equals(that.startDate) : that.startDate != null) {
+            return false;
+        }
+        if (state != that.state) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = productName != null ? productName.hashCode() : 0;
+        result = 31 * result + (productType != null ? productType.hashCode() : 0);
+        result = 31 * result + (productCategory != null ? productCategory.hashCode() : 0);
+        result = 31 * result + (slug != null ? slug.hashCode() : 0);
+        result = 31 * result + (phase != null ? phase.hashCode() : 0);
+        result = 31 * result + (price != null ? price.hashCode() : 0);
+        result = 31 * result + (priceList != null ? priceList.hashCode() : 0);
+        result = 31 * result + (mrr != null ? mrr.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        result = 31 * result + (startDate != null ? startDate.hashCode() : 0);
+        result = 31 * result + (state != null ? state.hashCode() : 0);
+        result = 31 * result + (billingPeriod != null ? billingPeriod.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessSubscriptionEvent.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessSubscriptionEvent.java
new file mode 100644
index 0000000..5d4d4c5
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessSubscriptionEvent.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
+
+import static com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+
+/**
+ * Describe an event associated with a transition between two BusinessSubscription
+ */
+public class BusinessSubscriptionEvent {
+
+    private static final Logger log = LoggerFactory.getLogger(BusinessSubscriptionEvent.class);
+
+    private static final String MISC = "MISC";
+
+    public enum EventType {
+        MIGRATE,
+        ADD,
+        CANCEL,
+        RE_ADD,
+        TRANSFER,
+        CHANGE,
+        SYSTEM_CANCEL,
+        SYSTEM_CHANGE
+    }
+
+    private final EventType eventType;
+    private final ProductCategory category;
+
+    public static BusinessSubscriptionEvent valueOf(final String eventString) {
+        for (final EventType possibleEventType : EventType.values()) {
+            if (!eventString.startsWith(possibleEventType.toString().toUpperCase())) {
+                continue;
+            }
+
+            final String categoryString = eventString.substring(possibleEventType.toString().length() + 1, eventString.length());
+
+            if (categoryString.equals(MISC)) {
+                return new BusinessSubscriptionEvent(possibleEventType, null);
+            } else {
+                return new BusinessSubscriptionEvent(possibleEventType, ProductCategory.valueOf(categoryString));
+            }
+        }
+
+        throw new IllegalArgumentException("Unable to parse event string: " + eventString);
+    }
+
+    // Public for internal reasons
+    public BusinessSubscriptionEvent(final EventType eventType, final ProductCategory category) {
+        this.eventType = eventType;
+        this.category = category;
+    }
+
+    public ProductCategory getCategory() {
+        return category;
+    }
+
+    public EventType getEventType() {
+        return eventType;
+    }
+
+    public static BusinessSubscriptionEvent subscriptionMigrated(final String plan, final Catalog catalog, final DateTime eventTime, final DateTime subscriptionCreationDate) {
+        return eventFromType(EventType.MIGRATE, plan, catalog, eventTime, subscriptionCreationDate);
+    }
+
+    public static BusinessSubscriptionEvent subscriptionCreated(final String plan, final Catalog catalog, final DateTime eventTime, final DateTime subscriptionCreationDate) {
+        return eventFromType(EventType.ADD, plan, catalog, eventTime, subscriptionCreationDate);
+    }
+
+    public static BusinessSubscriptionEvent subscriptionCancelled(final String plan, final Catalog catalog, final DateTime eventTime, final DateTime subscriptionCreationDate) {
+        return eventFromType(EventType.CANCEL, plan, catalog, eventTime, subscriptionCreationDate);
+    }
+
+    public static BusinessSubscriptionEvent subscriptionChanged(final String plan, final Catalog catalog, final DateTime eventTime, final DateTime subscriptionCreationDate) {
+        return eventFromType(EventType.CHANGE, plan, catalog, eventTime, subscriptionCreationDate);
+    }
+
+    public static BusinessSubscriptionEvent subscriptionRecreated(final String plan, final Catalog catalog, final DateTime eventTime, final DateTime subscriptionCreationDate) {
+        return eventFromType(EventType.RE_ADD, plan, catalog, eventTime, subscriptionCreationDate);
+    }
+
+    public static BusinessSubscriptionEvent subscriptionTransfered(final String plan, final Catalog catalog, final DateTime eventTime, final DateTime subscriptionCreationDate) {
+        return eventFromType(EventType.TRANSFER, plan, catalog, eventTime, subscriptionCreationDate);
+    }
+
+    public static BusinessSubscriptionEvent subscriptionPhaseChanged(final String plan, final SubscriptionState state, final Catalog catalog, final DateTime eventTime, final DateTime subscriptionCreationDate) {
+        if (state != null && state.equals(SubscriptionState.CANCELLED)) {
+            return eventFromType(EventType.SYSTEM_CANCEL, plan, catalog, eventTime, subscriptionCreationDate);
+        } else {
+            return eventFromType(EventType.SYSTEM_CHANGE, plan, catalog, eventTime, subscriptionCreationDate);
+        }
+    }
+
+    private static BusinessSubscriptionEvent eventFromType(final EventType eventType, final String plan, final Catalog catalog, final DateTime eventTime, final DateTime subscriptionCreationDate) {
+        Plan thePlan = null;
+        try {
+            thePlan = catalog.findPlan(plan, eventTime, subscriptionCreationDate);
+        } catch (CatalogApiException e) {
+            log.error(String.format("Failed to retrieve PLan from catalog for %s", plan));
+
+        }
+        final ProductCategory category = getTypeFromSubscription(thePlan);
+        return new BusinessSubscriptionEvent(eventType, category);
+    }
+
+    private static ProductCategory getTypeFromSubscription(final Plan plan) {
+
+        if (plan != null && plan.getProduct() != null) {
+            final Product product = plan.getProduct();
+            if (product.getCatalogName() != null && product.getCategory() != null) {
+                return product.getCategory();
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return eventType.toString() + "_" + (category == null ? MISC : category.toString().toUpperCase());
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final BusinessSubscriptionEvent that = (BusinessSubscriptionEvent) o;
+
+        if (category != that.category) {
+            return false;
+        }
+        if (eventType != null ? !eventType.equals(that.eventType) : that.eventType != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = eventType != null ? eventType.hashCode() : 0;
+        result = 31 * result + (category != null ? category.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessSubscriptionTransitionFieldModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessSubscriptionTransitionFieldModelDao.java
new file mode 100644
index 0000000..5ed5162
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessSubscriptionTransitionFieldModelDao.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+public class BusinessSubscriptionTransitionFieldModelDao extends BusinessFieldModelDao {
+
+    private final String accountKey;
+    private final UUID bundleId;
+    private final String externalKey;
+
+    public BusinessSubscriptionTransitionFieldModelDao(final String accountKey, final UUID bundleId, final String externalKey,
+                                                       final String name, final String value) {
+        super(bundleId, name, value);
+        this.accountKey = accountKey;
+        this.bundleId = bundleId;
+        this.externalKey = externalKey;
+    }
+
+    public String getAccountKey() {
+        return accountKey;
+    }
+
+    public UUID getBundleId() {
+        return bundleId;
+    }
+
+    public String getExternalKey() {
+        return externalKey;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("BusinessSubscriptionTransitionFieldModelDao");
+        sb.append("{accountKey='").append(accountKey).append('\'');
+        sb.append(", bundleId='").append(bundleId).append('\'');
+        sb.append(", externalKey='").append(externalKey).append('\'');
+        sb.append(", name='").append(getName()).append('\'');
+        sb.append(", value='").append(getValue()).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final BusinessSubscriptionTransitionFieldModelDao that = (BusinessSubscriptionTransitionFieldModelDao) o;
+
+        if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
+            return false;
+        }
+        if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null) {
+            return false;
+        }
+        if (externalKey != null ? !externalKey.equals(that.externalKey) : that.externalKey != null) {
+            return false;
+        }
+        if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
+            return false;
+        }
+        if (getValue() != null ? !getValue().equals(that.getValue()) : that.getValue() != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = accountKey != null ? accountKey.hashCode() : 0;
+        result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
+        result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
+        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+        result = 31 * result + (getValue() != null ? getValue().hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessSubscriptionTransitionModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessSubscriptionTransitionModelDao.java
new file mode 100644
index 0000000..8ca0eb8
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessSubscriptionTransitionModelDao.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.util.entity.EntityBase;
+
+/**
+ * Describe a state change between two BusinessSubscription
+ */
+public class BusinessSubscriptionTransitionModelDao extends EntityBase {
+
+    private final long totalOrdering;
+    private final UUID bundleId;
+    private final String externalKey;
+    private final UUID accountId;
+    private final String accountKey;
+    private final UUID subscriptionId;
+    private final DateTime requestedTimestamp;
+    private final BusinessSubscriptionEvent event;
+    private final BusinessSubscription previousSubscription;
+    private final BusinessSubscription nextSubscription;
+
+    public BusinessSubscriptionTransitionModelDao(final Long totalOrdering, final UUID bundleId, final String externalKey,
+                                                  final UUID accountId, final String accountKey, final UUID subscriptionId,
+                                                  final DateTime requestedTimestamp, final BusinessSubscriptionEvent event,
+                                                  final BusinessSubscription previousSubscription, final BusinessSubscription nextSubscription) {
+        if (totalOrdering == null) {
+            throw new IllegalArgumentException("A transition must have a total ordering");
+        }
+        if (bundleId == null) {
+            throw new IllegalArgumentException("A transition must have a bundle id");
+        }
+        if (externalKey == null) {
+            throw new IllegalArgumentException("A transition must have an external key");
+        }
+        if (accountId == null) {
+            throw new IllegalArgumentException("A transition must have an account key");
+        }
+        if (subscriptionId == null) {
+            throw new IllegalArgumentException("A transition must have a subscription id");
+        }
+        if (accountKey == null) {
+            throw new IllegalArgumentException("A transition must have an account key");
+        }
+        if (requestedTimestamp == null) {
+            throw new IllegalArgumentException("A transition must have a requested timestamp");
+        }
+        if (event == null) {
+            throw new IllegalArgumentException("No event specified");
+        }
+
+        this.totalOrdering = totalOrdering;
+        this.bundleId = bundleId;
+        this.externalKey = externalKey;
+        this.accountId = accountId;
+        this.accountKey = accountKey;
+        this.subscriptionId = subscriptionId;
+        this.requestedTimestamp = requestedTimestamp;
+        this.event = event;
+        this.previousSubscription = previousSubscription;
+        this.nextSubscription = nextSubscription;
+    }
+
+    public long getTotalOrdering() {
+        return totalOrdering;
+    }
+
+    public BusinessSubscriptionEvent getEvent() {
+        return event;
+    }
+
+    public UUID getBundleId() {
+        return bundleId;
+    }
+
+    public String getExternalKey() {
+        return externalKey;
+    }
+
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    public String getAccountKey() {
+        return accountKey;
+    }
+
+    public BusinessSubscription getNextSubscription() {
+        return nextSubscription;
+    }
+
+    public BusinessSubscription getPreviousSubscription() {
+        return previousSubscription;
+    }
+
+    public UUID getSubscriptionId() {
+        return subscriptionId;
+    }
+
+    public DateTime getRequestedTimestamp() {
+        return requestedTimestamp;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("BusinessSubscriptionTransitionModelDao");
+        sb.append("{accountId='").append(accountId).append('\'');
+        sb.append(", accountKey=").append(accountKey);
+        sb.append(", totalOrdering=").append(totalOrdering);
+        sb.append(", bundleId='").append(bundleId).append('\'');
+        sb.append(", externalKey='").append(externalKey).append('\'');
+        sb.append(", subscriptionId='").append(subscriptionId).append('\'');
+        sb.append(", requestedTimestamp=").append(requestedTimestamp);
+        sb.append(", event=").append(event);
+        sb.append(", previousSubscription=").append(previousSubscription);
+        sb.append(", nextSubscription=").append(nextSubscription);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final BusinessSubscriptionTransitionModelDao that = (BusinessSubscriptionTransitionModelDao) o;
+
+        return totalOrdering == that.totalOrdering && isDuplicateOf(that);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = (int) (totalOrdering ^ (totalOrdering >>> 32));
+        result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
+        result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
+        result = 31 * result + (accountId != null ? accountId.hashCode() : 0);
+        result = 31 * result + (accountKey != null ? accountKey.hashCode() : 0);
+        result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
+        result = 31 * result + (requestedTimestamp != null ? requestedTimestamp.hashCode() : 0);
+        result = 31 * result + (event != null ? event.hashCode() : 0);
+        result = 31 * result + (previousSubscription != null ? previousSubscription.hashCode() : 0);
+        result = 31 * result + (nextSubscription != null ? nextSubscription.hashCode() : 0);
+        return result;
+    }
+
+    public boolean isDuplicateOf(final BusinessSubscriptionTransitionModelDao that) {
+        if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
+            return false;
+        }
+        if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
+            return false;
+        }
+        if (event != null ? !event.equals(that.event) : that.event != null) {
+            return false;
+        }
+        if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null) {
+            return false;
+        }
+        if (externalKey != null ? !externalKey.equals(that.externalKey) : that.externalKey != null) {
+            return false;
+        }
+        if (nextSubscription != null ? !nextSubscription.equals(that.nextSubscription) : that.nextSubscription != null) {
+            return false;
+        }
+        if (previousSubscription != null ? !previousSubscription.equals(that.previousSubscription) : that.previousSubscription != null) {
+            return false;
+        }
+        if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null) {
+            return false;
+        }
+        if (requestedTimestamp != null ? !requestedTimestamp.equals(that.requestedTimestamp) : that.requestedTimestamp != null) {
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessSubscriptionTransitionTagModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessSubscriptionTransitionTagModelDao.java
new file mode 100644
index 0000000..2031b9f
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessSubscriptionTransitionTagModelDao.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+public class BusinessSubscriptionTransitionTagModelDao extends BusinessTagModelDao {
+
+    private final String accountKey;
+    private final UUID bundleId;
+    private final String externalKey;
+
+    public BusinessSubscriptionTransitionTagModelDao(final String accountKey, final UUID bundleId, final String externalKey, final String name) {
+        super(name);
+        this.accountKey = accountKey;
+        this.bundleId = bundleId;
+        this.externalKey = externalKey;
+    }
+
+    public String getAccountKey() {
+        return accountKey;
+    }
+
+    public UUID getBundleId() {
+        return bundleId;
+    }
+
+    public String getExternalKey() {
+        return externalKey;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("BusinessSubscriptionTransitionTagModelDao");
+        sb.append("{accountKey='").append(accountKey).append('\'');
+        sb.append(", bundleId='").append(bundleId).append('\'');
+        sb.append(", externalKey='").append(externalKey).append('\'');
+        sb.append(", name='").append(getName()).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final BusinessSubscriptionTransitionTagModelDao that = (BusinessSubscriptionTransitionTagModelDao) o;
+
+        if (accountKey != null ? !accountKey.equals(that.accountKey) : that.accountKey != null) {
+            return false;
+        }
+        if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null) {
+            return false;
+        }
+        if (externalKey != null ? !externalKey.equals(that.externalKey) : that.externalKey != null) {
+            return false;
+        }
+        if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = accountKey != null ? accountKey.hashCode() : 0;
+        result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
+        result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
+        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+        return result;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessTagModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessTagModelDao.java
new file mode 100644
index 0000000..9809b57
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/model/BusinessTagModelDao.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import com.ning.billing.util.entity.EntityBase;
+
+public abstract class BusinessTagModelDao extends EntityBase {
+
+    private final String name;
+
+    public BusinessTagModelDao(final String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/PaymentMethodUtils.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/PaymentMethodUtils.java
new file mode 100644
index 0000000..23fb706
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/PaymentMethodUtils.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import javax.annotation.Nullable;
+
+import com.ning.billing.payment.api.PaymentMethodPlugin;
+
+import com.google.common.annotations.VisibleForTesting;
+
+// TODO - make it generic
+public class PaymentMethodUtils {
+
+    @VisibleForTesting
+    static final String COUNTRY_KEY = "country";
+    @VisibleForTesting
+    static final String CARD_TYPE_KEY = "cardType";
+    @VisibleForTesting
+    static final String TYPE_KEY = "type";
+
+    private PaymentMethodUtils() {}
+
+    public static String getCardCountry(@Nullable final PaymentMethodPlugin pluginDetail) {
+        if (pluginDetail == null) {
+            return null;
+        }
+
+        return pluginDetail.getValueString(COUNTRY_KEY);
+    }
+
+    public static String getCardType(@Nullable final PaymentMethodPlugin pluginDetail) {
+        if (pluginDetail == null) {
+            return null;
+        }
+
+        return pluginDetail.getValueString(CARD_TYPE_KEY);
+    }
+
+    public static String getPaymentMethodType(@Nullable final PaymentMethodPlugin pluginDetail) {
+        if (pluginDetail == null) {
+            return null;
+        }
+
+        return pluginDetail.getValueString(TYPE_KEY);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/setup/AnalyticsModule.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/setup/AnalyticsModule.java
new file mode 100644
index 0000000..a2fa2c0
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/setup/AnalyticsModule.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.setup;
+
+import org.skife.config.ConfigSource;
+
+import com.ning.billing.analytics.api.AnalyticsService;
+import com.ning.billing.analytics.api.sanity.AnalyticsSanityApi;
+import com.ning.billing.analytics.api.user.AnalyticsUserApi;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsListener;
+import com.ning.billing.osgi.bundles.analytics.BusinessAccountDao;
+import com.ning.billing.osgi.bundles.analytics.BusinessSubscriptionTransitionDao;
+import com.ning.billing.osgi.bundles.analytics.BusinessTagDao;
+import com.ning.billing.osgi.bundles.analytics.api.DefaultAnalyticsService;
+import com.ning.billing.osgi.bundles.analytics.api.sanity.DefaultAnalyticsSanityApi;
+import com.ning.billing.osgi.bundles.analytics.api.user.DefaultAnalyticsUserApi;
+import com.ning.billing.osgi.bundles.analytics.dao.AnalyticsDao;
+import com.ning.billing.osgi.bundles.analytics.dao.AnalyticsSanityDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountFieldSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountTagSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceFieldSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceItemSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoicePaymentFieldSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoicePaymentSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoicePaymentTagSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceTagSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessOverdueStatusSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessSqlProvider;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionFieldSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionTagSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.DefaultAnalyticsDao;
+import com.ning.billing.osgi.bundles.analytics.dao.DefaultAnalyticsSanityDao;
+
+import com.google.inject.AbstractModule;
+
+public class AnalyticsModule extends AbstractModule {
+
+    protected final ConfigSource configSource;
+
+    public AnalyticsModule(final ConfigSource configSource) {
+        this.configSource = configSource;
+    }
+
+    @Override
+    protected void configure() {
+        installAnalyticsUserApi();
+        installAnalyticsSanityApi();
+
+        installAnalyticsDao();
+        installAnalyticsSqlDao();
+
+        bind(AnalyticsListener.class).asEagerSingleton();
+        bind(AnalyticsService.class).to(DefaultAnalyticsService.class).asEagerSingleton();
+    }
+
+    protected void installAnalyticsUserApi() {
+        bind(DefaultAnalyticsUserApi.class).asEagerSingleton();
+        bind(AnalyticsUserApi.class).to(DefaultAnalyticsUserApi.class).asEagerSingleton();
+    }
+
+    protected void installAnalyticsSanityApi() {
+        bind(AnalyticsSanityApi.class).to(DefaultAnalyticsSanityApi.class).asEagerSingleton();
+    }
+
+    protected void installAnalyticsDao() {
+        bind(AnalyticsDao.class).to(DefaultAnalyticsDao.class).asEagerSingleton();
+        bind(AnalyticsSanityDao.class).to(DefaultAnalyticsSanityDao.class).asEagerSingleton();
+        bind(BusinessSubscriptionTransitionDao.class).asEagerSingleton();
+        bind(BusinessAccountDao.class).asEagerSingleton();
+        bind(BusinessTagDao.class).asEagerSingleton();
+    }
+
+    protected void installAnalyticsSqlDao() {
+        bind(BusinessAccountSqlDao.class).toProvider(new BusinessSqlProvider<BusinessAccountSqlDao>(BusinessAccountSqlDao.class));
+        bind(BusinessAccountTagSqlDao.class).toProvider(new BusinessSqlProvider<BusinessAccountTagSqlDao>(BusinessAccountTagSqlDao.class));
+        bind(BusinessAccountFieldSqlDao.class).toProvider(new BusinessSqlProvider<BusinessAccountFieldSqlDao>(BusinessAccountFieldSqlDao.class));
+        bind(BusinessInvoiceFieldSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoiceFieldSqlDao>(BusinessInvoiceFieldSqlDao.class));
+        bind(BusinessInvoiceItemSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoiceItemSqlDao>(BusinessInvoiceItemSqlDao.class));
+        bind(BusinessInvoicePaymentFieldSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoicePaymentFieldSqlDao>(BusinessInvoicePaymentFieldSqlDao.class));
+        bind(BusinessInvoicePaymentSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoicePaymentSqlDao>(BusinessInvoicePaymentSqlDao.class));
+        bind(BusinessInvoicePaymentTagSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoicePaymentTagSqlDao>(BusinessInvoicePaymentTagSqlDao.class));
+        bind(BusinessInvoiceSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoiceSqlDao>(BusinessInvoiceSqlDao.class));
+        bind(BusinessInvoiceTagSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoiceTagSqlDao>(BusinessInvoiceTagSqlDao.class));
+        bind(BusinessOverdueStatusSqlDao.class).toProvider(new BusinessSqlProvider<BusinessOverdueStatusSqlDao>(BusinessOverdueStatusSqlDao.class));
+        bind(BusinessSubscriptionTransitionFieldSqlDao.class).toProvider(new BusinessSqlProvider<BusinessSubscriptionTransitionFieldSqlDao>(BusinessSubscriptionTransitionFieldSqlDao.class));
+        bind(BusinessSubscriptionTransitionSqlDao.class).toProvider(new BusinessSqlProvider<BusinessSubscriptionTransitionSqlDao>(BusinessSubscriptionTransitionSqlDao.class));
+        bind(BusinessSubscriptionTransitionTagSqlDao.class).toProvider(new BusinessSqlProvider<BusinessSubscriptionTransitionTagSqlDao>(BusinessSubscriptionTransitionTagSqlDao.class));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/utils/Rounder.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/utils/Rounder.java
new file mode 100644
index 0000000..fc32b85
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/utils/Rounder.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.utils;
+
+import java.math.BigDecimal;
+
+public class Rounder {
+
+    public static final int SCALE = 4;
+
+    // Static only
+    private Rounder() {
+    }
+
+    public static double round(final BigDecimal decimal) {
+        if (decimal == null) {
+            return 0;
+        } else {
+            return decimal.setScale(SCALE, BigDecimal.ROUND_HALF_UP).doubleValue();
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/AnalyticsSanitySqlDao.sql.stg b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/AnalyticsSanitySqlDao.sql.stg
new file mode 100644
index 0000000..4040115
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/AnalyticsSanitySqlDao.sql.stg
@@ -0,0 +1,301 @@
+group AnalyticsSanitySqlDao;
+
+CHECK_TENANT(prefix) ::= "<prefix>tenant_record_id = :tenantRecordId"
+AND_CHECK_TENANT(prefix) ::= "AND <CHECK_TENANT(prefix)>"
+
+checkBstMatchesSubscriptionEvents() ::= <<
+select distinct
+  account_id
+from (
+    select
+      account_id
+    , sum(per_event_check) account_check_left
+    , count(*) account_check_right
+    from (
+        select
+          account_id
+        , account_key_check and app_id and date_type and slug per_event_check
+        from (
+            select
+              q.account_key
+            , q.account_id
+            , b_account_key = account_key account_key_check
+            , b_app_id = app_id app_id
+            , case
+                when b_event like 'CANCEL_%' then b_req_dt = req_dt
+                when b_event like 'SYSTEM_CANCEL_%' then b_req_dt = eff_dt
+                else b_req_dt = req_dt  and b_eff_dt = eff_dt
+              end date_type
+            , coalesce(b_slug = slug, 1) slug
+            from (
+                select
+                  bst.total_ordering record_id
+                , bst.account_key b_account_key
+                , bst.external_key b_app_id
+                , bst.event b_event
+                , bst.next_slug b_slug
+                , from_unixtime(bst.requested_timestamp / 1000) b_req_dt
+                , from_unixtime(bst.next_start_date / 1000) b_eff_dt
+                , a.external_key account_key
+                , a.id account_id
+                , b.external_key app_id
+                , s.id
+                , e.event_type
+                , e.user_type
+                , e.phase_name slug
+                , e.effective_date eff_dt
+                , e.requested_date req_dt
+                , from_unixtime(coalesce(bst.next_start_date, bst.requested_timestamp) / 1000) b_dt
+                from subscription_events e
+                join subscriptions s on e.subscription_id = s.id
+                join bundles b on s.bundle_id = b.id
+                join accounts a on b.account_id = a.id
+                join bst on bst.total_ordering = e.record_id
+                where
+                    e.is_active = 1
+                and e.user_type != 'MIGRATE_BILLING'
+                <AND_CHECK_TENANT("e.")>
+                <AND_CHECK_TENANT("s.")>
+                <AND_CHECK_TENANT("b.")>
+                <AND_CHECK_TENANT("a.")>
+                <AND_CHECK_TENANT("bst.")>
+                order by e.record_id asc
+            ) q
+        ) p
+    ) r group by (account_id)
+) s
+where account_check_left != account_check_right
+;
+>>
+
+checkBiiMatchesInvoiceItems() ::= <<
+select distinct
+  account_id
+from (
+    select
+      id
+    , account_id
+    , start_date_check and end_date_check and amount_check and currency_check and linked_item_id_check and slug_check per_item_check
+    from (
+        select
+          ii.id
+        , ii.account_id
+        , ii.start_date = bii.start_date start_date_check
+        , coalesce(ii.end_date, bii.end_date) = bii.end_date end_date_check
+        , ii.amount = bii.amount amount_check
+        , ii.currency = bii.currency currency_check
+        , coalesce(ii.linked_item_id = bii.linked_item_id, 1) linked_item_id_check
+        , ii.phase_name = bii.slug slug_check
+        from invoice_items ii
+        join bii on ii.id = bii.item_id
+        where <CHECK_TENANT("ii.")>
+        <AND_CHECK_TENANT("bii.")>
+    ) p
+) q where !per_item_check
+;
+>>
+
+checkBipMatchesInvoicePayments() ::= <<
+select distinct
+  account_id
+from (
+    select
+      payment_id
+    , account_id
+    , amount_check and currency_check and payment_type_check and linked_invoice_payment_id_check and invoice_id_check total_check
+    from (
+        select
+          bip.payment_id
+        , a.id account_id
+        , bip.amount = ip.amount amount_check
+        , bip.currency = ip.currency currency_check
+        , bip.invoice_payment_type = ip.type payment_type_check
+        , bip.linked_invoice_payment_id = ip.linked_invoice_payment_id linked_invoice_payment_id_check
+        , bip.invoice_id = ip.invoice_id invoice_id_check
+        from bip
+        join invoice_payments ip on bip.payment_id = ip.id
+        join accounts a on a.record_id = ip.account_record_id
+        where <CHECK_TENANT("bip.")>
+        <AND_CHECK_TENANT("ip.")>
+        <AND_CHECK_TENANT("a.")>
+    ) p
+) q where !total_check
+;
+>>
+
+checkBinAmountPaidMatchesInvoicePayments() ::= <<
+select distinct
+  account_id
+from (
+    select
+      bin.invoice_id
+    , bin.account_id
+    , sum(ip.amount) = amount_paid amount_paid_check
+    from bin
+    join invoice_payments ip on bin.invoice_id = ip.invoice_id
+    where <CHECK_TENANT("bin.")>
+    <AND_CHECK_TENANT("ip.")>
+    group by ip.invoice_id, bin.account_id
+) p where !amount_paid_check
+;
+>>
+
+checkBinAmountChargedMatchesInvoicePayments() ::= <<
+select distinct
+  account_id
+from (
+    select
+      bin.invoice_id
+    , bin.account_id
+    , sum(ip.amount) = amount_charged amount_charged_check
+    from bin
+    join invoice_payments ip on bin.invoice_id = ip.invoice_id
+    where <CHECK_TENANT("bin.")>
+    <AND_CHECK_TENANT("ip.")>
+    group by ip.invoice_id, bin.account_id
+) p where !amount_charged_check
+;
+>>
+
+checkBinBiiBalanceConsistency() ::= <<
+select distinct
+  account_id
+from (
+    select
+      invoice_id
+    , account_id
+    , balance = amount_charged + total_adj_amount + total_cba - amount_paid balance_check
+    from (
+        select
+          bin.invoice_id
+        , bin.account_id
+        , bin.amount_paid
+        , bin.amount_charged
+        , bin.amount_credited
+        , bin.balance
+        , coalesce(total_adj_amount, 0) total_adj_amount
+        , coalesce(total_cba, 0) total_cba
+        from bin
+        left join (
+          select
+            bii.invoice_id
+          , sum(amount) total_adj_amount
+          from bii
+          join bin on bin.invoice_id = bii.invoice_id
+          where bii.item_type in ('CREDIT_ADJ', 'REFUND_ADJ', 'ITEM_ADJ')
+          <AND_CHECK_TENANT("bii.")>
+          <AND_CHECK_TENANT("bin.")>
+          group by (bii.invoice_id)
+        ) p on bin.invoice_id = p.invoice_id
+        left join (
+          select
+            q.invoice_id
+          , total_cba
+          from (
+              select
+                bii.invoice_id
+              , sum(amount) total_cba
+              from bii
+              join bin on bin.invoice_id = bii.invoice_id
+              where bii.item_type in ('CBA_ADJ')
+              <AND_CHECK_TENANT("bii.")>
+              <AND_CHECK_TENANT("bin.")>
+              group by (bii.invoice_id)
+          ) q
+        ) r on r.invoice_id = bin.invoice_id
+        where <CHECK_TENANT("bin.")>
+    ) s
+) t where !balance_check
+;
+>>
+
+checkBinBiiAmountCreditedConsistency() ::= <<
+select distinct
+  account_id
+from (
+    select
+      bii.invoice_id
+    , bin.account_id
+    , sum(amount) = bin.amount_credited credit_check
+    from bii
+    join bin on bin.invoice_id = bii.invoice_id
+    where bii.item_type in ('CREDIT_ADJ')
+    <AND_CHECK_TENANT("bii.")>
+    <AND_CHECK_TENANT("bin.")>
+    group by bii.invoice_id, bin.account_id
+) p where !credit_check
+;
+>>
+
+checkBacBinBiiConsistency() ::= <<
+select distinct
+  account_id
+from (
+    select
+      account_id
+    , total_invoice_balance_check and total_account_balance_check bac_check
+    from (
+        select
+          account_id
+        , total_invoice_balance_on_account = total_invoice_balance total_invoice_balance_check
+        , total_account_balance = total_invoice_balance_on_account - account_cba total_account_balance_check
+        from (
+            select
+              bac.account_id
+            , bac.total_invoice_balance total_invoice_balance_on_account
+            , sum(bin.balance) total_invoice_balance
+            , bac.balance total_account_balance
+            , coalesce(account_cba, 0) account_cba
+            from bac
+            -- some might not have cba items
+            left join (
+                select
+                  bin.account_id
+                , sum(bii.amount) account_cba
+                from bac
+                join bin on bin.account_id = bac.account_id
+                join bii on bii.invoice_id = bin.invoice_id
+                where bii.item_type = 'CBA_ADJ'
+                <AND_CHECK_TENANT("bac.")>
+                <AND_CHECK_TENANT("bin.")>
+                <AND_CHECK_TENANT("bii.")>
+                group by (bin.account_id)
+            ) p on bac.account_id = p.account_id
+            left join bin on bin.account_id = bac.account_id
+            where <CHECK_TENANT("bac.")>
+            <AND_CHECK_TENANT("bin.")>
+            group by bac.account_id, account_cba
+        ) q
+    ) r
+) s where !bac_check
+;
+>>
+
+checkBacTagsMatchesTags() ::= <<
+select distinct
+  account_id
+from (
+    select
+      account_id
+    , b_tag_name = tag_name tag_name_check
+    from (
+        select
+          bt.account_id account_id
+        , bt.name b_tag_name
+        , case
+          when t.tag_definition_id = '00000000-0000-0000-0000-000000000001' then 'AUTO_PAY_OFF'
+          when t.tag_definition_id = '00000000-0000-0000-0000-000000000002' then 'AUTO_INVOICING_OFF'
+          when t.tag_definition_id = '00000000-0000-0000-0000-000000000003' then 'OVERDUE_ENFORCEMENT_OFF'
+          when t.tag_definition_id = '00000000-0000-0000-0000-000000000003' then 'WRITTEN_OFF'
+          else tdef.name
+        end tag_name
+        from bac_tags bt
+        join tags t on t.object_id = bt.account_id
+        left join tag_definitions tdef on t.tag_definition_id = tdef.id
+        where t.object_type  = 'account'
+        <AND_CHECK_TENANT("bt.")>
+        <AND_CHECK_TENANT("t.")>
+        <AND_CHECK_TENANT("tdef.")>
+    ) p
+) q where ! tag_name_check;
+>>
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountFieldSqlDao.sql.stg b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountFieldSqlDao.sql.stg
new file mode 100644
index 0000000..3715ed9
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountFieldSqlDao.sql.stg
@@ -0,0 +1,42 @@
+group BusinessAccountField;
+
+CHECK_TENANT() ::= "tenant_record_id = :tenantRecordId"
+AND_CHECK_TENANT() ::= "AND <CHECK_TENANT()>"
+
+getFieldsForAccountByKey(account_key) ::=<<
+select
+  account_id
+, account_key
+, name
+, value
+from bac_fields
+where account_key = :account_key
+<AND_CHECK_TENANT()>
+;
+>>
+
+addField(account_id, account_key, name, value) ::=<<
+insert into bac_fields (
+  account_id
+, account_key
+, name
+, value
+, account_record_id
+, tenant_record_id
+) values (
+  :account_id
+, :account_key
+, :name
+, :value
+, :accountRecordId
+, :tenantRecordId
+);
+>>
+
+removeField(account_id, name) ::= <<
+delete from bac_fields where account_id = :account_id and name = :name <AND_CHECK_TENANT()>;
+>>
+
+test() ::= <<
+select 1 from bac_fields where <CHECK_TENANT()> limit 1;
+>>
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountSqlDao.sql.stg b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountSqlDao.sql.stg
new file mode 100644
index 0000000..819a5c9
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountSqlDao.sql.stg
@@ -0,0 +1,123 @@
+group BusinessAccount;
+
+CHECK_TENANT() ::= "tenant_record_id = :tenantRecordId"
+AND_CHECK_TENANT() ::= "AND <CHECK_TENANT()>"
+
+getAccountsCreatedOverTime() ::= <<
+  select
+    date(from_unixtime(created_date / 1000)) day
+    -- TODO: use account_record_id, once populated
+  , count(record_id) count
+  from bac
+  where <CHECK_TENANT()>
+  group by 1
+  order by 1
+  ;
+>>
+
+getAccount(account_id) ::= <<
+  select
+    account_id
+  , account_key
+  , created_date
+  , updated_date
+  , balance
+  , name
+  , last_invoice_date
+  , total_invoice_balance
+  , last_payment_status
+  , payment_method
+  , credit_card_type
+  , billing_address_country
+  , currency
+  , tenant_record_id
+  from bac
+  where account_id=:account_id
+  <AND_CHECK_TENANT()>
+  limit 1
+  ;
+>>
+
+getAccountByKey(account_key) ::= <<
+  select
+    account_id
+  , account_key
+  , created_date
+  , updated_date
+  , balance
+  , name
+  , last_invoice_date
+  , total_invoice_balance
+  , last_payment_status
+  , payment_method
+  , credit_card_type
+  , billing_address_country
+  , currency
+  , tenant_record_id
+  from bac
+  where account_key=:account_key
+  <AND_CHECK_TENANT()>
+  limit 1
+  ;
+>>
+
+createAccount() ::= <<
+  insert into bac(
+    account_id
+  , account_key
+  , created_date
+  , updated_date
+  , balance
+  , name
+  , last_invoice_date
+  , total_invoice_balance
+  , last_payment_status
+  , payment_method
+  , credit_card_type
+  , billing_address_country
+  , currency
+  , account_record_id
+  , tenant_record_id
+  ) values (
+    :account_id
+  , :account_key
+  , :created_date
+  , :updated_date
+  , :balance
+  , :name
+  , :last_invoice_date
+  , :total_invoice_balance
+  , :last_payment_status
+  , :payment_method
+  , :credit_card_type
+  , :billing_address_country
+  , :currency
+  , :accountRecordId
+  , :tenantRecordId
+  );
+>>
+
+saveAccount() ::= <<
+  update bac set
+    updated_date=:updated_date
+  , balance=:balance
+  , name=:name
+  , last_invoice_date=:last_invoice_date
+  , total_invoice_balance=:total_invoice_balance
+  , last_payment_status=:last_payment_status
+  , payment_method=:payment_method
+  , credit_card_type=:credit_card_type
+  , billing_address_country=:billing_address_country
+  , currency=:currency
+  where account_id=:account_id
+  <AND_CHECK_TENANT()>
+  ;
+>>
+
+deleteAccount(account_id) ::= <<
+delete from bac where account_id = :account_id <AND_CHECK_TENANT()>;
+>>
+
+test() ::= <<
+select 1 from bac where <CHECK_TENANT()> limit 1;
+>>
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountTagSqlDao.sql.stg b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountTagSqlDao.sql.stg
new file mode 100644
index 0000000..5c4d142
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountTagSqlDao.sql.stg
@@ -0,0 +1,39 @@
+group BusinessAccountTag;
+
+CHECK_TENANT() ::= "tenant_record_id = :tenantRecordId"
+AND_CHECK_TENANT() ::= "AND <CHECK_TENANT()>"
+
+getTagsForAccountByKey(account_key) ::=<<
+select
+  account_id
+, account_key
+, name
+from bac_tags
+where account_key = :account_key
+<AND_CHECK_TENANT()>
+;
+>>
+
+addTag(account_id, account_key, name) ::=<<
+insert into bac_tags (
+  account_id
+, account_key
+, name
+, account_record_id
+, tenant_record_id
+) values (
+  :account_id
+, :account_key
+, :name
+, :accountRecordId
+, :tenantRecordId
+);
+>>
+
+removeTag(account_id, name) ::= <<
+delete from bac_tags where account_id = :account_id and name = :name <AND_CHECK_TENANT()>;
+>>
+
+test() ::= <<
+select 1 from bac_tags where <CHECK_TENANT()> limit 1;
+>>
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceFieldSqlDao.sql.stg b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceFieldSqlDao.sql.stg
new file mode 100644
index 0000000..63e1ce0
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceFieldSqlDao.sql.stg
@@ -0,0 +1,39 @@
+group BusinessInvoiceField;
+
+CHECK_TENANT() ::= "tenant_record_id = :tenantRecordId"
+AND_CHECK_TENANT() ::= "AND <CHECK_TENANT()>"
+
+getFieldsForInvoice(invoice_id) ::=<<
+select
+  invoice_id
+, name
+, value
+from bin_fields
+where invoice_id = :invoice_id
+<AND_CHECK_TENANT()>
+;
+>>
+
+addField(invoice_id, name, value) ::=<<
+insert into bin_fields (
+  invoice_id
+, name
+, value
+, account_record_id
+, tenant_record_id
+) values (
+  :invoice_id
+, :name
+, :value
+, :accountRecordId
+, :tenantRecordId
+);
+>>
+
+removeField(invoice_id, name, value) ::= <<
+delete from bin_fields where invoice_id = :invoice_id and name = :name <AND_CHECK_TENANT()>;
+>>
+
+test() ::= <<
+select 1 from bin_tags where <CHECK_TENANT()> limit 1;
+>>
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceItemSqlDao.sql.stg b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceItemSqlDao.sql.stg
new file mode 100644
index 0000000..c15d21c
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceItemSqlDao.sql.stg
@@ -0,0 +1,141 @@
+group BusinessInvoiceItem;
+
+CHECK_TENANT(prefix) ::= "<prefix>tenant_record_id = :tenantRecordId"
+AND_CHECK_TENANT(prefix) ::= "AND <CHECK_TENANT(prefix)>"
+
+getInvoiceItem(item_id) ::= <<
+select
+  item_id
+, created_date
+, updated_date
+, invoice_id
+, item_type
+, external_key
+, product_name
+, product_type
+, product_category
+, slug
+, phase
+, billing_period
+, start_date
+, end_date
+, amount
+, currency
+, linked_item_id
+, tenant_record_id
+from bii
+where item_id = :item_id
+<AND_CHECK_TENANT()>
+limit 1
+;
+>>
+
+getInvoiceItemsForInvoice(invoice_id) ::= <<
+select
+  item_id
+, created_date
+, updated_date
+, invoice_id
+, item_type
+, external_key
+, product_name
+, product_type
+, product_category
+, slug
+, phase
+, billing_period
+, start_date
+, end_date
+, amount
+, currency
+, linked_item_id
+, tenant_record_id
+from bii
+where invoice_id = :invoice_id
+<AND_CHECK_TENANT()>
+order by created_date asc
+;
+>>
+
+getInvoiceItemsForBundleByKey(external_key) ::= <<
+select
+  item_id
+, created_date
+, updated_date
+, invoice_id
+, item_type
+, external_key
+, product_name
+, product_type
+, product_category
+, slug
+, phase
+, billing_period
+, start_date
+, end_date
+, amount
+, currency
+, linked_item_id
+, tenant_record_id
+from bii
+where external_key = :external_key
+<AND_CHECK_TENANT()>
+order by created_date asc
+;
+>>
+
+createInvoiceItem() ::= <<
+insert into bii (
+  item_id
+, created_date
+, updated_date
+, invoice_id
+, item_type
+, external_key
+, product_name
+, product_type
+, product_category
+, slug
+, phase
+, billing_period
+, start_date
+, end_date
+, amount
+, currency
+, linked_item_id
+, account_record_id
+, tenant_record_id
+) values (
+  :item_id
+, :created_date
+, :updated_date
+, :invoice_id
+, :item_type
+, :external_key
+, :product_name
+, :product_type
+, :product_category
+, :slug
+, :phase
+, :billing_period
+, :start_date
+, :end_date
+, :amount
+, :currency
+, :linked_item_id
+, :accountRecordId
+, :tenantRecordId
+);
+>>
+
+deleteInvoiceItem(item_id) ::= <<
+delete from bii where item_id = :item_id <AND_CHECK_TENANT()>;
+>>
+
+deleteInvoiceItemsForAccount(account_id) ::= <<
+delete from bii where bii.invoice_id in (select invoice_id from bin where bin.account_id = :account_id <AND_CHECK_TENANT("bin.")> for update) <AND_CHECK_TENANT("bii.")>;
+>>
+
+test() ::= <<
+select 1 from bii where <CHECK_TENANT()> limit 1;
+>>
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentFieldSqlDao.sql.stg b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentFieldSqlDao.sql.stg
new file mode 100644
index 0000000..9bee903
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentFieldSqlDao.sql.stg
@@ -0,0 +1,39 @@
+group BusinessInvoicePaymentField;
+
+CHECK_TENANT() ::= "tenant_record_id = :tenantRecordId"
+AND_CHECK_TENANT() ::= "AND <CHECK_TENANT()>"
+
+getFieldsForInvoicePayment(payment_id) ::=<<
+select
+  payment_id
+, name
+, value
+from bip_fields
+where payment_id = :payment_id
+<AND_CHECK_TENANT()>
+;
+>>
+
+addField(payment_id, name, value) ::=<<
+insert into bip_fields (
+  payment_id
+, name
+, value
+, account_record_id
+, tenant_record_id
+) values (
+  :payment_id
+, :name
+, :value
+, :accountRecordId
+, :tenantRecordId
+);
+>>
+
+removeField(payment_id, name) ::= <<
+delete from bip_fields where payment_id = :payment_id and name = :name <AND_CHECK_TENANT()>;
+>>
+
+test() ::= <<
+select 1 from bip_fields where <CHECK_TENANT()> limit 1;
+>>
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentSqlDao.sql.stg b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentSqlDao.sql.stg
new file mode 100644
index 0000000..e358c09
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentSqlDao.sql.stg
@@ -0,0 +1,122 @@
+group BusinessInvoicePayment;
+
+CHECK_TENANT() ::= "tenant_record_id = :tenantRecordId"
+AND_CHECK_TENANT() ::= "AND <CHECK_TENANT()>"
+
+getInvoicePayment(payment_id) ::= <<
+select
+  payment_id
+, created_date
+, updated_date
+, ext_first_payment_ref_id
+, ext_second_payment_ref_id
+, account_key
+, invoice_id
+, effective_date
+, amount
+, currency
+, payment_error
+, processing_status
+, requested_amount
+, plugin_name
+, payment_type
+, payment_method
+, card_type
+, card_country
+, invoice_payment_type
+, linked_invoice_payment_id
+, tenant_record_id
+from bip
+where payment_id = :payment_id
+<AND_CHECK_TENANT()>
+limit 1
+;
+>>
+
+getInvoicePaymentsForAccountByKey(account_key) ::= <<
+select
+  payment_id
+, created_date
+, updated_date
+, ext_first_payment_ref_id
+, ext_second_payment_ref_id
+, account_key
+, invoice_id
+, effective_date
+, amount
+, currency
+, payment_error
+, processing_status
+, requested_amount
+, plugin_name
+, payment_type
+, payment_method
+, card_type
+, card_country
+, invoice_payment_type
+, linked_invoice_payment_id
+, tenant_record_id
+from bip
+where account_key = :account_key
+<AND_CHECK_TENANT()>
+order by created_date asc
+;
+>>
+
+createInvoicePayment() ::= <<
+insert into bip (
+  payment_id
+, created_date
+, updated_date
+, ext_first_payment_ref_id
+, ext_second_payment_ref_id
+, account_key
+, invoice_id
+, effective_date
+, amount
+, currency
+, payment_error
+, processing_status
+, requested_amount
+, plugin_name
+, payment_type
+, payment_method
+, card_type
+, card_country
+, invoice_payment_type
+, linked_invoice_payment_id
+, account_record_id
+, tenant_record_id
+) values (
+  :payment_id
+, :created_date
+, :updated_date
+, :ext_first_payment_ref_id
+, :ext_second_payment_ref_id
+, :account_key
+, :invoice_id
+, :effective_date
+, :amount
+, :currency
+, :payment_error
+, :processing_status
+, :requested_amount
+, :plugin_name
+, :payment_type
+, :payment_method
+, :card_type
+, :card_country
+, :invoice_payment_type
+, :linked_invoice_payment_id
+, :accountRecordId
+, :tenantRecordId
+);
+>>
+
+deleteInvoicePayment(payment_id) ::= <<
+delete from bip where payment_id = :payment_id <AND_CHECK_TENANT()>
+>>
+
+test() ::= <<
+select 1 from bip where <CHECK_TENANT()> limit 1;
+>>
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentTagSqlDao.sql.stg b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentTagSqlDao.sql.stg
new file mode 100644
index 0000000..541a809
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentTagSqlDao.sql.stg
@@ -0,0 +1,37 @@
+group BusinessInvoicePaymentTag;
+
+CHECK_TENANT() ::= "tenant_record_id = :tenantRecordId"
+AND_CHECK_TENANT() ::= "AND <CHECK_TENANT()>"
+
+getTagsForInvoicePayment(payment_id) ::=<<
+select
+  payment_id
+, name
+, tenant_record_id
+from bip_tags
+where payment_id = :payment_id
+<AND_CHECK_TENANT()>
+;
+>>
+
+addTag(payment_id, name) ::=<<
+insert into bip_tags (
+  payment_id
+, name
+, account_record_id
+, tenant_record_id
+) values (
+  :payment_id
+, :name
+, :accountRecordId
+, :tenantRecordId
+);
+>>
+
+removeTag(payment_id, name) ::= <<
+delete from bip_tags where payment_id = :payment_id and name = :name <AND_CHECK_TENANT()>;
+>>
+
+test() ::= <<
+select 1 from bip_tags where <CHECK_TENANT()> limit 1;
+>>
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceSqlDao.sql.stg b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceSqlDao.sql.stg
new file mode 100644
index 0000000..2d19030
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceSqlDao.sql.stg
@@ -0,0 +1,138 @@
+group BusinessInvoice;
+
+CHECK_TENANT() ::= "tenant_record_id = :tenantRecordId"
+AND_CHECK_TENANT() ::= "AND <CHECK_TENANT()>"
+
+getInvoice(invoice_id) ::= <<
+select
+  invoice_id
+, invoice_number
+, created_date
+, updated_date
+, account_id
+, account_key
+, invoice_date
+, target_date
+, currency
+, balance
+, amount_paid
+, amount_charged
+, amount_credited
+, tenant_record_id
+from bin
+where invoice_id = :invoice_id
+<AND_CHECK_TENANT()>
+limit 1
+;
+>>
+
+getInvoicesForAccount(account_id) ::= <<
+select
+  invoice_id
+, invoice_number
+, created_date
+, updated_date
+, account_id
+, account_key
+, invoice_date
+, target_date
+, currency
+, balance
+, amount_paid
+, amount_charged
+, amount_credited
+, tenant_record_id
+from bin
+where account_id = :account_id
+<AND_CHECK_TENANT()>
+order by created_date asc
+;
+>>
+
+getInvoicesForAccountByKey(account_key) ::= <<
+select
+  invoice_id
+, invoice_number
+, created_date
+, updated_date
+, account_id
+, account_key
+, invoice_date
+, target_date
+, currency
+, balance
+, amount_paid
+, amount_charged
+, amount_credited
+, tenant_record_id
+from bin
+where account_key = :account_key
+<AND_CHECK_TENANT()>
+order by created_date asc
+;
+>>
+
+createInvoice() ::= <<
+insert into bin (
+  invoice_id
+, invoice_number
+, created_date
+, updated_date
+, account_id
+, account_key
+, invoice_date
+, target_date
+, currency
+, balance
+, amount_paid
+, amount_charged
+, amount_credited
+, account_record_id
+, tenant_record_id
+) values (
+  :invoice_id
+, :invoice_number
+, :created_date
+, :updated_date
+, :account_id
+, :account_key
+, :invoice_date
+, :target_date
+, :currency
+, :balance
+, :amount_paid
+, :amount_charged
+, :amount_credited
+, :accountRecordId
+, :tenantRecordId
+);
+>>
+
+updateInvoice() ::= <<
+update bin set
+  updated_date = :updated_date
+, invoice_number = :invoice_number
+, account_key = :account_key
+, invoice_date = :invoice_date
+, target_date = :target_date
+, currency = :currency
+, balance = :balance
+, amount_paid = :amount_paid
+, amount_charged = :amount_charged
+, amount_credited = :amount_credited
+where invoice_id = :invoice_id
+<AND_CHECK_TENANT()>
+;
+>>
+
+deleteInvoice(invoice_id) ::= <<
+delete from bin where invoice_id = :invoice_id <AND_CHECK_TENANT()>;
+>>
+
+deleteInvoicesForAccount(account_id) ::= <<
+delete from bin where account_id = :account_id <AND_CHECK_TENANT()>;
+>>
+
+test() ::= <<
+select 1 from bin where <CHECK_TENANT()> limit 1;
+>>
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceTagSqlDao.sql.stg b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceTagSqlDao.sql.stg
new file mode 100644
index 0000000..b2f5c0b
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceTagSqlDao.sql.stg
@@ -0,0 +1,37 @@
+group BusinessInvoiceTag;
+
+CHECK_TENANT() ::= "tenant_record_id = :tenantRecordId"
+AND_CHECK_TENANT() ::= "AND <CHECK_TENANT()>"
+
+getTagsForInvoice(invoice_id) ::=<<
+select
+  invoice_id
+, name
+, tenant_record_id
+from bin_tags
+where invoice_id = :invoice_id
+<AND_CHECK_TENANT()>
+;
+>>
+
+addTag(invoice_id, name) ::=<<
+insert into bin_tags (
+  invoice_id
+, name
+, account_record_id
+, tenant_record_id
+) values (
+  :invoice_id
+, :name
+, :accountRecordId
+, :tenantRecordId
+);
+>>
+
+removeTag(invoice_id, name) ::= <<
+delete from bin_tags where invoice_id = :invoice_id and name = :name <AND_CHECK_TENANT()>;
+>>
+
+test() ::= <<
+select 1 from bin_tags where <CHECK_TENANT()> limit 1;
+>>
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusSqlDao.sql.stg b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusSqlDao.sql.stg
new file mode 100644
index 0000000..59eb814
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusSqlDao.sql.stg
@@ -0,0 +1,50 @@
+group BusinessOverdueStatus;
+
+CHECK_TENANT() ::= "tenant_record_id = :tenantRecordId"
+AND_CHECK_TENANT() ::= "AND <CHECK_TENANT()>"
+
+getOverdueStatusesForBundleByKey(external_key) ::= <<
+select
+  bundle_id
+, external_key
+, account_key
+, status
+, start_date
+, end_date
+, tenant_record_id
+from bos
+where external_key = :external_key
+<AND_CHECK_TENANT()>
+order by start_date asc
+;
+>>
+
+createOverdueStatus() ::= <<
+insert into bos (
+  bundle_id
+, external_key
+, account_key
+, status
+, start_date
+, end_date
+, account_record_id
+, tenant_record_id
+) values (
+  :bundle_id
+, :external_key
+, :account_key
+, :status
+, :start_date
+, :end_date
+, :accountRecordId
+, :tenantRecordId
+);
+>>
+
+deleteOverdueStatusesForBundle(bundle_id) ::= <<
+delete from bos where bundle_id = :bundle_id <AND_CHECK_TENANT()>;
+>>
+
+test() ::= <<
+select 1 from bos where <CHECK_TENANT()> limit 1;
+>>
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionFieldSqlDao.sql.stg b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionFieldSqlDao.sql.stg
new file mode 100644
index 0000000..3528475
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionFieldSqlDao.sql.stg
@@ -0,0 +1,46 @@
+group BusinessSubscriptionTransitionField;
+
+CHECK_TENANT() ::= "tenant_record_id = :tenantRecordId"
+AND_CHECK_TENANT() ::= "AND <CHECK_TENANT()>"
+
+getFieldsForBusinessSubscriptionTransitionByKey(external_key) ::=<<
+select
+  bundle_id
+, external_key
+, account_key
+, name
+, value
+, tenant_record_id
+from bst_fields
+where external_key = :external_key
+<AND_CHECK_TENANT()>
+;
+>>
+
+addField(bundle_id, external_key, name, value) ::=<<
+insert into bst_fields (
+  bundle_id
+, external_key
+, account_key
+, name
+, value
+, account_record_id
+, tenant_record_id
+) values (
+  :bundle_id
+, :external_key
+, :account_key
+, :name
+, :value
+, :accountRecordId
+, :tenantRecordId
+);
+>>
+
+removeField(bundle_id, name) ::= <<
+delete from bst_fields where bundle_id = :bundle_id and name = :name <AND_CHECK_TENANT()>;
+>>
+
+test() ::= <<
+select 1 from bst_fields where <CHECK_TENANT()> limit 1;
+>>
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionSqlDao.sql.stg b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionSqlDao.sql.stg
new file mode 100644
index 0000000..d88006e
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionSqlDao.sql.stg
@@ -0,0 +1,229 @@
+group BusinessSubscriptionTransition;
+
+CHECK_TENANT() ::= "tenant_record_id = :tenantRecordId"
+AND_CHECK_TENANT() ::= "AND <CHECK_TENANT()>"
+
+getSubscriptionsCreatedOverTime(product_type, slug) ::= <<
+  select
+    date(from_unixtime(requested_timestamp / 1000)) day
+  , count(record_id) count
+  from bst
+  where event in ('ADD_ADD_ON', 'ADD_BASE', 'ADD_STANDALONE')
+  and next_product_type = :product_type
+  and next_slug = :slug
+  <AND_CHECK_TENANT()>
+  group by 1
+  order by 1
+  ;
+>>
+
+getTransitionsByKey(external_key) ::= <<
+  select
+    total_ordering
+  , bundle_id
+  , external_key
+  , account_id
+  , account_key
+  , subscription_id
+  , requested_timestamp
+  , event
+  , prev_product_name
+  , prev_product_type
+  , prev_product_category
+  , prev_slug
+  , prev_phase
+  , prev_billing_period
+  , prev_price
+  , prev_price_list
+  , prev_mrr
+  , prev_currency
+  , prev_start_date
+  , prev_state
+  , next_product_name
+  , next_product_type
+  , next_product_category
+  , next_slug
+  , next_phase
+  , next_billing_period
+  , next_price
+  , next_price_list
+  , next_mrr
+  , next_currency
+  , next_start_date
+  , next_state
+  , tenant_record_id
+  from bst
+  where external_key=:external_key
+  <AND_CHECK_TENANT()>
+  order by requested_timestamp asc
+  ;
+>>
+
+getTransitionForSubscription(subscription_id) ::= <<
+  select
+    total_ordering
+  , bundle_id
+  , external_key
+  , account_id
+  , account_key
+  , subscription_id
+  , requested_timestamp
+  , event
+  , prev_product_name
+  , prev_product_type
+  , prev_product_category
+  , prev_slug
+  , prev_phase
+  , prev_billing_period
+  , prev_price
+  , prev_price_list
+  , prev_mrr
+  , prev_currency
+  , prev_start_date
+  , prev_state
+  , next_product_name
+  , next_product_type
+  , next_product_category
+  , next_slug
+  , next_phase
+  , next_billing_period
+  , next_price
+  , next_price_list
+  , next_mrr
+  , next_currency
+  , next_start_date
+  , next_state
+  , tenant_record_id
+  from bst
+  where subscription_id = :subscription_id
+  <AND_CHECK_TENANT()>
+  order by requested_timestamp asc
+  ;
+>>
+
+getTransitionsForAccount(account_id) ::= <<
+  select
+    total_ordering
+  , bundle_id
+  , external_key
+  , account_id
+  , account_key
+  , subscription_id
+  , requested_timestamp
+  , event
+  , prev_product_name
+  , prev_product_type
+  , prev_product_category
+  , prev_slug
+  , prev_phase
+  , prev_billing_period
+  , prev_price
+  , prev_price_list
+  , prev_mrr
+  , prev_currency
+  , prev_start_date
+  , prev_state
+  , next_product_name
+  , next_product_type
+  , next_product_category
+  , next_slug
+  , next_phase
+  , next_billing_period
+  , next_price
+  , next_price_list
+  , next_mrr
+  , next_currency
+  , next_start_date
+  , next_state
+  , tenant_record_id
+  from bst
+  where account_key = :account_key
+  <AND_CHECK_TENANT()>
+  order by requested_timestamp asc
+  ;
+>>
+
+createTransition() ::= <<
+  insert into bst(
+    total_ordering
+  , bundle_id
+  , external_key
+  , account_id
+  , account_key
+  , subscription_id
+  , requested_timestamp
+  , event
+  , prev_product_name
+  , prev_product_type
+  , prev_product_category
+  , prev_slug
+  , prev_phase
+  , prev_billing_period
+  , prev_price
+  , prev_price_list
+  , prev_mrr
+  , prev_currency
+  , prev_start_date
+  , prev_state
+  , next_product_name
+  , next_product_type
+  , next_product_category
+  , next_slug
+  , next_phase
+  , next_billing_period
+  , next_price
+  , next_price_list
+  , next_mrr
+  , next_currency
+  , next_start_date
+  , next_state
+  , account_record_id
+  , tenant_record_id
+  ) values (
+    :total_ordering
+  , :bundle_id
+  , :external_key
+  , :account_id
+  , :account_key
+  , :subscription_id
+  , :requested_timestamp
+  , :event
+  , :prev_product_name
+  , :prev_product_type
+  , :prev_product_category
+  , :prev_slug
+  , :prev_phase
+  , :prev_billing_period
+  , :prev_price
+  , :prev_price_list
+  , :prev_mrr
+  , :prev_currency
+  , :prev_start_date
+  , :prev_state
+  , :next_product_name
+  , :next_product_type
+  , :next_product_category
+  , :next_slug
+  , :next_phase
+  , :next_billing_period
+  , :next_price
+  , :next_price_list
+  , :next_mrr
+  , :next_currency
+  , :next_start_date
+  , :next_state
+  , :accountRecordId
+  , :tenantRecordId
+  );
+>>
+
+deleteTransitionsForBundle(bundle_id) ::= <<
+  delete from bst
+  where bundle_id=:bundle_id
+  <AND_CHECK_TENANT()>
+  ;
+>>
+
+test() ::= <<
+select 1 from bst where <CHECK_TENANT()> limit 1;
+>>
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionTagSqlDao.sql.stg b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionTagSqlDao.sql.stg
new file mode 100644
index 0000000..bd89b0d
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionTagSqlDao.sql.stg
@@ -0,0 +1,43 @@
+group BusinessSubscriptionTransitionTag;
+
+CHECK_TENANT() ::= "tenant_record_id = :tenantRecordId"
+AND_CHECK_TENANT() ::= "AND <CHECK_TENANT()>"
+
+getTagsForBusinessSubscriptionTransitionByKey(external_key) ::=<<
+select
+  bundle_id
+, external_key
+, account_key
+, name
+, tenant_record_id
+from bst_tags
+where external_key = :external_key
+<AND_CHECK_TENANT()>
+;
+>>
+
+addTag(bundle_id, external_key, name) ::=<<
+insert into bst_tags (
+  bundle_id
+, external_key
+, account_key
+, name
+, account_record_id
+, tenant_record_id
+) values (
+  :bundle_id
+, :external_key
+, :account_key
+, :name
+, :accountRecordId
+, :tenantRecordId
+);
+>>
+
+removeTag(bundle_id, name) ::= <<
+delete from bst_tags where bundle_id = :bundle_id and name = :name <AND_CHECK_TENANT()>;
+>>
+
+test() ::= <<
+select 1 from bst_tags where <CHECK_TENANT()> limit 1;
+>>
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/ddl.sql b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/ddl.sql
new file mode 100644
index 0000000..7c93a84
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/ddl.sql
@@ -0,0 +1,259 @@
+/*! SET storage_engine=INNODB */;
+
+drop table if exists bst;
+create table bst (
+  record_id int(11) unsigned not null auto_increment
+, total_ordering bigint default 0
+, bundle_id char(36) not null
+, account_id char(36) not null
+, external_key varchar(50) not null comment 'Bundle external key'
+, account_key varchar(50) not null comment 'Account external key'
+, subscription_id char(36) not null
+, requested_timestamp bigint not null
+, event varchar(50) not null
+, prev_product_name varchar(50) default null
+, prev_product_type varchar(50) default null
+, prev_product_category varchar(50) default null
+, prev_slug varchar(50) default null
+, prev_phase varchar(50) default null
+, prev_billing_period varchar(50) default null
+, prev_price numeric(10, 4) default 0
+, prev_price_list varchar(50) default null
+, prev_mrr numeric(10, 4) default 0
+, prev_currency varchar(50) default null
+, prev_start_date bigint default null
+, prev_state varchar(50) default null
+, next_product_name varchar(50) default null
+, next_product_type varchar(50) default null
+, next_product_category varchar(50) default null
+, next_slug varchar(50) default null
+, next_phase varchar(50) default null
+, next_billing_period varchar(50) default null
+, next_price numeric(10, 4) default 0
+, next_price_list varchar(50) default null
+, next_mrr numeric(10, 4) default 0
+, next_currency varchar(50) default null
+, next_start_date bigint default null
+, next_state varchar(50) default null
+, account_record_id int(11) unsigned default null
+, tenant_record_id int(11) unsigned default null
+, primary key(record_id)
+);
+create index bst_key_index on bst (external_key, requested_timestamp asc);
+create index bst_tenant_account_record_id on bst(tenant_record_id, account_record_id);
+
+drop table if exists bac;
+create table bac (
+  record_id int(11) unsigned not null auto_increment
+, account_id char(36) not null
+, account_key varchar(50) not null
+, name varchar(100) not null
+, created_date bigint not null
+, updated_date bigint not null
+, balance numeric(10, 4) default 0
+, last_invoice_date date default null
+, total_invoice_balance numeric(10, 4) default 0
+, last_payment_status varchar(255) default null
+, payment_method varchar(50) default null
+, credit_card_type varchar(50) default null
+, billing_address_country varchar(50) default null
+, currency char(50) default null
+, account_record_id int(11) unsigned default null
+, tenant_record_id int(11) unsigned default null
+, primary key(record_id)
+);
+create unique index bac_key_index on bac (account_key);
+create index bac_tenant_account_record_id on bac(tenant_record_id, account_record_id);
+
+drop table if exists bin;
+create table bin (
+  record_id int(11) unsigned not null auto_increment
+, invoice_id char(36) not null
+, invoice_number bigint default null
+, created_date bigint not null
+, updated_date bigint not null
+, account_id char(36) not null
+, account_key varchar(50) not null
+, invoice_date date not null
+, target_date date not null
+, currency char(50) not null
+, balance numeric(10, 4) default 0 comment 'amount_charged - amount_paid - amount_credited'
+, amount_paid numeric(10, 4) default 0 comment 'Sums of the successful payments made for this invoice minus the refunds associated with this invoice'
+, amount_charged numeric(10, 4) default 0 comment 'Sums of the invoice items amount'
+, amount_credited numeric(10, 4) default 0 comment 'Sums of the credit items'
+, account_record_id int(11) unsigned default null
+, tenant_record_id int(11) unsigned default null
+, primary key(record_id)
+);
+create unique index bin_key_index on bin (invoice_id);
+create index bin_tenant_account_record_id on bin(tenant_record_id, account_record_id);
+
+drop table if exists bii;
+create table bii (
+  record_id int(11) unsigned not null auto_increment
+, item_id char(36) not null
+, created_date bigint not null
+, updated_date bigint not null
+, invoice_id char(36) not null
+, item_type char(50) not null comment 'e.g. FIXED or RECURRING'
+, external_key varchar(50) default null comment 'Bundle external key (could be null for certain items)'
+, product_name varchar(50) default null
+, product_type varchar(50) default null
+, product_category varchar(50) default null
+, slug varchar(50) default null
+, phase varchar(50) default null
+, billing_period varchar(50) default null
+, start_date date default null
+, end_date date default null
+, amount numeric(10, 4) default 0
+, currency char(50) default null
+, linked_item_id char(36) default null
+, account_record_id int(11) unsigned default null
+, tenant_record_id int(11) unsigned default null
+, primary key(record_id)
+);
+create unique index bii_key_index on bii (item_id);
+create index bii_tenant_account_record_id on bii(tenant_record_id, account_record_id);
+
+drop table if exists bip;
+create table bip (
+  record_id int(11) unsigned not null auto_increment
+, payment_id char(36) not null
+, created_date bigint not null
+, updated_date bigint not null
+, ext_first_payment_ref_id varchar(255) default null
+, ext_second_payment_ref_id varchar(255) default null
+, account_key varchar(50) not null comment 'Account external key'
+, invoice_id char(36) not null
+, effective_date bigint default null
+, amount numeric(10, 4) default 0
+, currency char(50) default null
+, payment_error varchar(255) default null
+, processing_status varchar(50) default null
+, requested_amount numeric(10, 4) default 0
+, plugin_name varchar(50) default null
+, payment_type varchar(50) default null
+, payment_method varchar(50) default null
+, card_type varchar(50) default null
+, card_country varchar(50) default null
+, invoice_payment_type varchar(50) default null
+, linked_invoice_payment_id char(36) default null
+, account_record_id int(11) unsigned default null
+, tenant_record_id int(11) unsigned default null
+, primary key(record_id)
+);
+create unique index bip_key_index on bip (payment_id);
+create index bip_tenant_account_record_id on bip(tenant_record_id, account_record_id);
+
+drop table if exists bos;
+create table bos (
+  record_id int(11) unsigned not null auto_increment
+, bundle_id char(36) not null
+, external_key varchar(50) not null comment 'Bundle external key'
+, account_key varchar(50) not null comment 'Account external key'
+, status varchar(50) not null
+, start_date bigint default null
+, end_date bigint default null
+, account_record_id int(11) unsigned default null
+, tenant_record_id int(11) unsigned default null
+, primary key(record_id)
+);
+create index bos_tenant_account_record_id on bos(tenant_record_id, account_record_id);
+
+drop table if exists bac_tags;
+create table bac_tags (
+  record_id int(11) unsigned not null auto_increment
+, account_id char(36) not null
+, account_key varchar(50) not null comment 'Account external key'
+, name varchar(50) not null
+, account_record_id int(11) unsigned default null
+, tenant_record_id int(11) unsigned default null
+, primary key(record_id)
+);
+create index bac_tags_tenant_account_record_id on bac_tags(tenant_record_id, account_record_id);
+
+drop table if exists bac_fields;
+create table bac_fields (
+  record_id int(11) unsigned not null auto_increment
+, account_id char(36) not null
+, account_key varchar(50) not null comment 'Account external key'
+, name varchar(50) not null
+, value varchar(255) default null
+, account_record_id int(11) unsigned default null
+, tenant_record_id int(11) unsigned default null
+, primary key(record_id)
+);
+create index bac_fields_tenant_account_record_id on bac_fields(tenant_record_id, account_record_id);
+
+drop table if exists bst_tags;
+create table bst_tags (
+  record_id int(11) unsigned not null auto_increment
+, bundle_id char(36) not null
+, external_key varchar(50) not null comment 'Bundle external key'
+, account_key varchar(50) not null comment 'Account external key'
+, name varchar(50) not null
+, account_record_id int(11) unsigned default null
+, tenant_record_id int(11) unsigned default null
+, primary key(record_id)
+);
+create index bst_tags_tenant_account_record_id on bst_tags(tenant_record_id, account_record_id);
+
+drop table if exists bst_fields;
+create table bst_fields (
+  record_id int(11) unsigned not null auto_increment
+, bundle_id char(36) not null
+, external_key varchar(50) not null comment 'Bundle external key'
+, account_key varchar(50) not null comment 'Account external key'
+, name varchar(50) not null
+, value varchar(255) default null
+, account_record_id int(11) unsigned default null
+, tenant_record_id int(11) unsigned default null
+, primary key(record_id)
+);
+create index bst_fields_tenant_account_record_id on bst_fields(tenant_record_id, account_record_id);
+
+drop table if exists bin_tags;
+create table bin_tags (
+  record_id int(11) unsigned not null auto_increment
+, invoice_id char(36) not null
+, name varchar(50) not null
+, account_record_id int(11) unsigned default null
+, tenant_record_id int(11) unsigned default null
+, primary key(record_id)
+);
+create index bin_tags_tenant_account_record_id on bin_tags(tenant_record_id, account_record_id);
+
+drop table if exists bin_fields;
+create table bin_fields (
+  record_id int(11) unsigned not null auto_increment
+, invoice_id char(36) not null
+, name varchar(50) not null
+, value varchar(255) default null
+, account_record_id int(11) unsigned default null
+, tenant_record_id int(11) unsigned default null
+, primary key(record_id)
+);
+create index bin_fields_tenant_account_record_id on bin_fields(tenant_record_id, account_record_id);
+
+drop table if exists bip_tags;
+create table bip_tags (
+  record_id int(11) unsigned not null auto_increment
+, payment_id char(36) not null
+, name varchar(50) not null
+, account_record_id int(11) unsigned default null
+, tenant_record_id int(11) unsigned default null
+, primary key(record_id)
+);
+create index bip_tags_tenant_account_record_id on bip_tags(tenant_record_id, account_record_id);
+
+drop table if exists bip_fields;
+create table bip_fields (
+  record_id int(11) unsigned not null auto_increment
+, payment_id char(36) not null
+, name varchar(50) not null
+, value varchar(255) default null
+, account_record_id int(11) unsigned default null
+, tenant_record_id int(11) unsigned default null
+, primary key(record_id)
+);
+create index bip_fields_tenant_account_record_id on bip_fields(tenant_record_id, account_record_id);
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/AnalyticsTestSuiteNoDB.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/AnalyticsTestSuiteNoDB.java
new file mode 100644
index 0000000..e096376
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/AnalyticsTestSuiteNoDB.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+
+import com.ning.billing.GuicyKillbillTestSuiteNoDB;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.analytics.api.user.AnalyticsUserApi;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.osgi.bundles.analytics.api.DefaultAnalyticsService;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountFieldSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountTagSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceFieldSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceItemSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoicePaymentFieldSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoicePaymentSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoicePaymentTagSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceTagSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessOverdueStatusSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionFieldSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionTagSqlDao;
+import com.ning.billing.osgi.bundles.analytics.glue.TestAnalyticsModuleNoDB;
+import com.ning.billing.payment.dao.PaymentDao;
+import com.ning.billing.util.glue.RealImplementation;
+import com.ning.billing.util.svcapi.account.AccountInternalApi;
+import com.ning.billing.util.svcapi.entitlement.EntitlementInternalApi;
+import com.ning.billing.util.svcapi.invoice.InvoiceInternalApi;
+import com.ning.billing.util.svcsapi.bus.InternalBus;
+
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+
+public abstract class AnalyticsTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
+
+    @Inject
+    @RealImplementation
+    protected AccountUserApi accountApi;
+    @Inject
+    protected AccountInternalApi accountInternalApi;
+    @Inject
+    protected AnalyticsUserApi analyticsUserApi;
+    @Inject
+    protected CatalogService catalogService;
+    @Inject
+    @RealImplementation
+    protected EntitlementUserApi entitlementApi;
+    @Inject
+    protected EntitlementInternalApi entitlementInternalApi;
+    @Inject
+    protected InvoiceUserApi invoiceApi;
+    @Inject
+    protected InvoiceDao realInvoiceDao;
+    @Inject
+    protected InvoiceInternalApi invoiceInternalApi;
+    @Inject
+    protected PaymentDao paymentDao;
+    @Inject
+    protected DefaultAnalyticsService service;
+    @Inject
+    protected InternalBus bus;
+    @Inject
+    protected BusinessAccountDao accountDao;
+    @Inject
+    protected BusinessAccountSqlDao accountSqlDao;
+    @Inject
+    protected BusinessAccountFieldSqlDao accountFieldSqlDao;
+    @Inject
+    protected BusinessAccountTagSqlDao accountTagSqlDao;
+    @Inject
+    protected BusinessInvoiceFieldSqlDao invoiceFieldSqlDao;
+    @Inject
+    protected BusinessInvoiceItemSqlDao invoiceItemSqlDao;
+    @Inject
+    protected BusinessInvoicePaymentFieldSqlDao invoicePaymentFieldSqlDao;
+    @Inject
+    protected BusinessInvoicePaymentSqlDao invoicePaymentSqlDao;
+    @Inject
+    protected BusinessInvoicePaymentTagSqlDao invoicePaymentTagSqlDao;
+    @Inject
+    protected BusinessInvoiceDao invoiceDao;
+    @Inject
+    protected BusinessInvoiceSqlDao invoiceSqlDao;
+    @Inject
+    protected BusinessInvoiceTagSqlDao invoiceTagSqlDao;
+    @Inject
+    protected BusinessOverdueStatusDao overdueStatusDao;
+    @Inject
+    protected BusinessOverdueStatusSqlDao overdueStatusSqlDao;
+    @Inject
+    protected BusinessSubscriptionTransitionFieldSqlDao subscriptionTransitionFieldSqlDao;
+    @Inject
+    protected BusinessSubscriptionTransitionTagSqlDao subscriptionTransitionTagSqlDao;
+    @Inject
+    protected BusinessSubscriptionTransitionDao subscriptionTransitionDao;
+    @Inject
+    protected BusinessSubscriptionTransitionSqlDao subscriptionTransitionSqlDao;
+    @Inject
+    protected BusinessTagDao tagDao;
+
+    @BeforeClass(groups = "fast")
+    protected void beforeClass() throws Exception {
+        final Injector injector = Guice.createInjector(new TestAnalyticsModuleNoDB(configSource));
+        injector.injectMembers(this);
+    }
+
+    @BeforeMethod(groups = "fast")
+    public void beforeMethod() throws Exception {
+        bus.start();
+    }
+
+    @AfterMethod(groups = "fast")
+    public void afterMethod() throws Exception {
+        bus.stop();
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/AnalyticsTestSuiteWithEmbeddedDB.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/AnalyticsTestSuiteWithEmbeddedDB.java
new file mode 100644
index 0000000..ae239d4
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/AnalyticsTestSuiteWithEmbeddedDB.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+
+import com.ning.billing.GuicyKillbillTestSuiteWithEmbeddedDB;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.analytics.api.AnalyticsService;
+import com.ning.billing.analytics.api.user.AnalyticsUserApi;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.osgi.bundles.analytics.api.DefaultAnalyticsService;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountFieldSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountTagSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceFieldSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceItemSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoicePaymentFieldSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoicePaymentSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoicePaymentTagSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceTagSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessOverdueStatusSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionFieldSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionTagSqlDao;
+import com.ning.billing.osgi.bundles.analytics.glue.TestAnalyticsModuleWithEmbeddedDB;
+import com.ning.billing.payment.dao.PaymentDao;
+import com.ning.billing.util.glue.RealImplementation;
+import com.ning.billing.util.svcapi.account.AccountInternalApi;
+import com.ning.billing.util.svcapi.entitlement.EntitlementInternalApi;
+import com.ning.billing.util.svcapi.invoice.InvoiceInternalApi;
+import com.ning.billing.util.svcsapi.bus.InternalBus;
+
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+
+public abstract class AnalyticsTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWithEmbeddedDB {
+
+    @Inject
+    @RealImplementation
+    protected AccountUserApi accountApi;
+    @Inject
+    protected AccountInternalApi accountInternalApi;
+    @Inject
+    protected AnalyticsUserApi analyticsUserApi;
+    @Inject
+    protected AnalyticsService analyticsService;
+    @Inject
+    protected CatalogService catalogService;
+    @Inject
+    @RealImplementation
+    protected EntitlementUserApi entitlementApi;
+    @Inject
+    protected EntitlementInternalApi entitlementInternalApi;
+    @Inject
+    protected InvoiceUserApi invoiceApi;
+    @Inject
+    protected InvoiceDao realInvoiceDao;
+    @Inject
+    protected InvoiceInternalApi invoiceInternalApi;
+    @Inject
+    protected PaymentDao paymentDao;
+    @Inject
+    protected DefaultAnalyticsService service;
+    @Inject
+    protected InternalBus bus;
+    @Inject
+    protected BusinessAccountDao accountDao;
+    @Inject
+    protected BusinessAccountSqlDao accountSqlDao;
+    @Inject
+    protected BusinessAccountFieldSqlDao accountFieldSqlDao;
+    @Inject
+    protected BusinessAccountTagSqlDao accountTagSqlDao;
+    @Inject
+    protected BusinessInvoiceFieldSqlDao invoiceFieldSqlDao;
+    @Inject
+    protected BusinessInvoiceItemSqlDao invoiceItemSqlDao;
+    @Inject
+    protected BusinessInvoicePaymentFieldSqlDao invoicePaymentFieldSqlDao;
+    @Inject
+    protected BusinessInvoicePaymentSqlDao invoicePaymentSqlDao;
+    @Inject
+    protected BusinessInvoicePaymentTagSqlDao invoicePaymentTagSqlDao;
+    @Inject
+    protected BusinessInvoiceDao invoiceDao;
+    @Inject
+    protected BusinessInvoiceSqlDao invoiceSqlDao;
+    @Inject
+    protected BusinessInvoiceTagSqlDao invoiceTagSqlDao;
+    @Inject
+    protected BusinessOverdueStatusDao overdueStatusDao;
+    @Inject
+    protected BusinessOverdueStatusSqlDao overdueStatusSqlDao;
+    @Inject
+    protected BusinessSubscriptionTransitionFieldSqlDao subscriptionTransitionFieldSqlDao;
+    @Inject
+    protected BusinessSubscriptionTransitionTagSqlDao subscriptionTransitionTagSqlDao;
+    @Inject
+    protected BusinessSubscriptionTransitionDao subscriptionTransitionDao;
+    @Inject
+    protected BusinessSubscriptionTransitionSqlDao subscriptionTransitionSqlDao;
+    @Inject
+    protected BusinessTagDao tagDao;
+
+    @BeforeClass(groups = "slow")
+    protected void beforeClass() throws Exception {
+        final Injector injector = Guice.createInjector(new TestAnalyticsModuleWithEmbeddedDB(configSource));
+        injector.injectMembers(this);
+    }
+
+    @BeforeMethod(groups = "slow")
+    public void beforeMethod() throws Exception {
+        super.beforeMethod();
+        bus.start();
+        restartAnalyticsService();
+    }
+
+    @AfterMethod(groups = "slow")
+    public void afterMethod() throws Exception {
+        bus.stop();
+        stopAnalyticsService();
+    }
+
+    private void restartAnalyticsService() throws Exception {
+        ((DefaultAnalyticsService) analyticsService).registerForNotifications();
+    }
+
+    private void stopAnalyticsService() throws Exception {
+        ((DefaultAnalyticsService) analyticsService).unregisterForNotifications();
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestAnalyticsService.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestAnalyticsService.java
new file mode 100644
index 0000000..2810f3e
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestAnalyticsService.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.api;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+
+import org.joda.time.DateTime;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
+import com.ning.billing.account.dao.AccountModelDao;
+import com.ning.billing.catalog.MockCatalog;
+import com.ning.billing.catalog.MockPriceList;
+import com.ning.billing.catalog.api.Currency;
+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.PriceList;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.user.DefaultEffectiveSubscriptionEvent;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.entitlement.events.user.ApiEventType;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.user.DefaultInvoiceCreationEvent;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.mock.MockAccountBuilder;
+import com.ning.billing.mock.MockPlan;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteWithEmbeddedDB;
+import com.ning.billing.osgi.bundles.analytics.MockDuration;
+import com.ning.billing.osgi.bundles.analytics.MockPhase;
+import com.ning.billing.osgi.bundles.analytics.MockProduct;
+import com.ning.billing.payment.api.DefaultPaymentInfoEvent;
+import com.ning.billing.payment.api.PaymentMethod;
+import com.ning.billing.payment.api.PaymentStatus;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.events.AccountCreationInternalEvent;
+import com.ning.billing.util.events.EffectiveSubscriptionInternalEvent;
+import com.ning.billing.util.events.InvoiceCreationInternalEvent;
+import com.ning.billing.util.events.PaymentInfoInternalEvent;
+
+import com.google.common.collect.ImmutableList;
+
+import static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.testng.Assert.fail;
+
+public class TestAnalyticsService extends AnalyticsTestSuiteWithEmbeddedDB {
+
+    final Product product = new MockProduct("platinum", "subscription", ProductCategory.BASE);
+    final Plan plan = new MockPlan("platinum-monthly", product);
+    final PlanPhase phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
+
+    private static final Long TOTAL_ORDERING = 11L;
+    private final String ACCOUNT_KEY = UUID.randomUUID().toString();
+    private final String BUNDLE_KEY = UUID.randomUUID().toString();
+    private static final Currency ACCOUNT_CURRENCY = Currency.EUR;
+    private static final BigDecimal INVOICE_AMOUNT = BigDecimal.valueOf(1243.11);
+    private final UUID bundleId = UUID.randomUUID();
+    private final UUID subscriptionId = UUID.randomUUID();
+
+    private EffectiveSubscriptionInternalEvent transition;
+
+    private AccountCreationInternalEvent accountCreationNotification;
+    private InvoiceCreationInternalEvent invoiceCreationNotification;
+    private PaymentInfoInternalEvent paymentInfoNotification;
+
+    @BeforeMethod(groups = "slow")
+    public void beforeMethod() throws Exception {
+        super.beforeMethod();
+        Mockito.when(catalogService.getFullCatalog()).thenReturn(new MockCatalog());
+
+        final PaymentMethod paymentMethod = Mockito.mock(PaymentMethod.class);
+        final UUID paymentMethodId = UUID.randomUUID();
+        Mockito.when(paymentMethod.getId()).thenReturn(paymentMethodId);
+        final Account account = new MockAccountBuilder(UUID.randomUUID())
+                .externalKey(ACCOUNT_KEY)
+                .currency(ACCOUNT_CURRENCY)
+                .paymentMethodId(paymentMethodId)
+                .build();
+        Mockito.when(accountInternalApi.getAccountById(Mockito.eq(account.getId()), Mockito.<InternalCallContext>any())).thenReturn(account);
+
+        try {
+            // Create events for the bus and expected results
+            createSubscriptionTransitionEvent(account);
+            createAccountCreationEvent(account);
+            createInvoiceAndPaymentCreationEvents(account);
+        } catch (Throwable t) {
+            fail("Initializing accounts failed.", t);
+        }
+    }
+
+    private void createSubscriptionTransitionEvent(final Account account) throws EntitlementUserApiException {
+        final DateTime effectiveTransitionTime = clock.getUTCNow();
+        final DateTime requestedTransitionTime = clock.getUTCNow();
+        final PriceList priceList = new MockPriceList().setName("something");
+
+        final SubscriptionBundle bundle = Mockito.mock(SubscriptionBundle.class);
+        Mockito.when(bundle.getId()).thenReturn(bundleId);
+        Mockito.when(bundle.getAccountId()).thenReturn(account.getId());
+        Mockito.when(bundle.getExternalKey()).thenReturn(BUNDLE_KEY);
+        Mockito.when(entitlementInternalApi.getBundleFromId(Mockito.eq(bundleId), Mockito.<InternalCallContext>any())).thenReturn(bundle);
+
+        final Subscription subscription = Mockito.mock(Subscription.class);
+        Mockito.when(subscription.getId()).thenReturn(subscriptionId);
+        Mockito.when(subscription.getBundleId()).thenReturn(bundleId);
+        Mockito.when(entitlementInternalApi.getSubscriptionFromId(Mockito.eq(subscriptionId), Mockito.<InternalCallContext>any())).thenReturn(subscription);
+        Mockito.when(entitlementInternalApi.getSubscriptionsForBundle(Mockito.eq(bundleId), Mockito.<InternalCallContext>any())).thenReturn(ImmutableList.<Subscription>of(subscription));
+
+        final EffectiveSubscriptionInternalEvent event = Mockito.mock(EffectiveSubscriptionInternalEvent.class);
+        Mockito.when(event.getEffectiveTransitionTime()).thenReturn(effectiveTransitionTime);
+        Mockito.when(event.getRequestedTransitionTime()).thenReturn(requestedTransitionTime);
+        Mockito.when(event.getTransitionType()).thenReturn(SubscriptionTransitionType.CREATE);
+        Mockito.when(entitlementInternalApi.getAllTransitions(Mockito.eq(subscription), Mockito.<InternalCallContext>any())).thenReturn(ImmutableList.<EffectiveSubscriptionInternalEvent>of(event));
+
+        // Create a subscription transition event
+        transition = new DefaultEffectiveSubscriptionEvent(new SubscriptionTransitionData(
+                UUID.randomUUID(),
+                subscriptionId,
+                bundleId,
+                EntitlementEvent.EventType.API_USER,
+                ApiEventType.CREATE,
+                requestedTransitionTime,
+                effectiveTransitionTime,
+                null,
+                null,
+                null,
+                null,
+                Subscription.SubscriptionState.ACTIVE,
+                plan,
+                phase,
+                priceList,
+                TOTAL_ORDERING,
+                null,
+                true), null, null, 1L, 1L);
+    }
+
+    private void createAccountCreationEvent(final Account account) {
+        accountCreationNotification = new DefaultAccountCreationEvent(new AccountModelDao(account.getId(), account), null, 1L, 1L);
+    }
+
+    private void createInvoiceAndPaymentCreationEvents(final Account account) {
+        final DefaultInvoice invoice = new DefaultInvoice(account.getId(), clock.getUTCToday(), clock.getUTCToday(), ACCOUNT_CURRENCY);
+        final FixedPriceInvoiceItem invoiceItem = new FixedPriceInvoiceItem(invoice.getId(), account.getId(), bundleId, subscriptionId, "somePlan", "somePhase", clock.getUTCToday(),
+                                                                            INVOICE_AMOUNT, ACCOUNT_CURRENCY);
+        invoice.addInvoiceItem(invoiceItem);
+        Mockito.when(invoiceInternalApi.getInvoicesByAccountId(Mockito.eq(account.getId()), Mockito.<InternalCallContext>any())).thenReturn(ImmutableList.<Invoice>of(invoice));
+
+        // It doesn't really matter what the events contain - the listener will go back to the db
+        invoiceCreationNotification = new DefaultInvoiceCreationEvent(invoice.getId(), account.getId(),
+                                                                      INVOICE_AMOUNT, ACCOUNT_CURRENCY, null, 1L, 1L);
+
+        paymentInfoNotification = new DefaultPaymentInfoEvent(account.getId(), invoice.getId(), null, INVOICE_AMOUNT, -1,
+                                                              PaymentStatus.UNKNOWN, null, clock.getUTCNow(), 1L, 1L);
+    }
+
+    @Test(groups = "slow")
+    public void testRegisterForNotifications() throws Exception {
+        // Make sure the service has been instantiated
+        Assert.assertEquals(service.getName(), "analytics-service");
+
+        Assert.assertNull(accountSqlDao.getAccountByKey(ACCOUNT_KEY, internalCallContext));
+
+        // Send events and wait for the async part...
+        bus.post(accountCreationNotification, internalCallContext);
+        waitALittle(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                return (accountSqlDao.getAccountByKey(ACCOUNT_KEY, internalCallContext) != null);
+            }
+        });
+
+        // Test subscriptions integration - this is just to exercise the code. It's hard to test the actual subscriptions
+        // as we would need to mock a bunch of APIs (see integration tests in Beatrix instead)
+        bus.post(transition, internalCallContext);
+        waitALittle(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                return (subscriptionTransitionSqlDao.getTransitionsForAccount(ACCOUNT_KEY, internalCallContext).size() == 1);
+            }
+        });
+
+        // Test invoice integration - the account creation notification has triggered a BAC update
+        bus.post(invoiceCreationNotification, internalCallContext);
+        waitALittle(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                // Test invoice integration - the account creation notification has triggered a BAC update
+                return (accountSqlDao.getAccountByKey(ACCOUNT_KEY, internalCallContext).getTotalInvoiceBalance().compareTo(INVOICE_AMOUNT) == 0);
+            }
+        });
+
+        // Test payment integration - the fields have already been populated, just make sure the code is exercised
+        // It's hard to test the actual payments fields though in bac, since we should mock the plugin
+        bus.post(paymentInfoNotification, internalCallContext);
+    }
+
+    private void waitALittle(final Callable<Boolean> callable) {
+        try {
+            await().atMost(5, SECONDS).until(callable);
+        } catch (Exception e) {
+            Assert.fail("Exception in TestAnalyticsService", e);
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/user/TestDefaultAnalyticsUserApi.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/user/TestDefaultAnalyticsUserApi.java
new file mode 100644
index 0000000..c7cb17d
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/user/TestDefaultAnalyticsUserApi.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.api.user;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.analytics.api.TimeSeriesData;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.Currency;
+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;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.mock.MockPlan;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteWithEmbeddedDB;
+import com.ning.billing.osgi.bundles.analytics.MockDuration;
+import com.ning.billing.osgi.bundles.analytics.MockPhase;
+import com.ning.billing.osgi.bundles.analytics.MockProduct;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscription;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionEvent;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionModelDao;
+
+public class TestDefaultAnalyticsUserApi extends AnalyticsTestSuiteWithEmbeddedDB {
+
+    @Test(groups = "mysql")
+    public void testAccountsCreatedOverTime() throws Exception {
+        final BusinessAccountModelDao account = new BusinessAccountModelDao(UUID.randomUUID(), UUID.randomUUID().toString(), UUID.randomUUID().toString(), BigDecimal.ONE, clock.getUTCToday(),
+                                                                            BigDecimal.TEN, "ERROR_NOT_ENOUGH_FUNDS", "CreditCard", "Visa", "FRANCE", "USD", clock.getUTCNow(), clock.getUTCNow());
+        accountSqlDao.createAccount(account, internalCallContext);
+
+        final TimeSeriesData data = analyticsUserApi.getAccountsCreatedOverTime(callContext);
+        Assert.assertEquals(data.getDates().size(), 1);
+        Assert.assertEquals(data.getDates().get(0), clock.getUTCToday());
+        Assert.assertEquals(data.getValues().size(), 1);
+        Assert.assertEquals(data.getValues().get(0), (double) 1);
+    }
+
+    @Test(groups = "mysql")
+    public void testSubscriptionsCreatedOverTime() throws Exception {
+        final String productType = "subscription";
+        final Product product = new MockProduct("platinum", productType, ProductCategory.BASE);
+        final Plan plan = new MockPlan("platinum-monthly", product);
+        final PlanPhase phase = new MockPhase(PhaseType.TRIAL, plan, MockDuration.UNLIMITED(), 25.95);
+        final Catalog catalog = Mockito.mock(Catalog.class);
+        Mockito.when(catalog.findPlan(Mockito.anyString(), Mockito.<DateTime>any(), Mockito.<DateTime>any())).thenReturn(plan);
+        Mockito.when(catalog.findPhase(Mockito.anyString(), Mockito.<DateTime>any(), Mockito.<DateTime>any())).thenReturn(phase);
+        final BusinessSubscriptionTransitionModelDao transition = new BusinessSubscriptionTransitionModelDao(
+                3L,
+                UUID.randomUUID(),
+                UUID.randomUUID().toString(),
+                UUID.randomUUID(),
+                UUID.randomUUID().toString(),
+                UUID.randomUUID(),
+                clock.getUTCNow(),
+                BusinessSubscriptionEvent.subscriptionCreated(plan.getName(), catalog, clock.getUTCNow(), clock.getUTCNow()),
+                null,
+                new BusinessSubscription("DEFAULT", plan.getName(), phase.getName(), Currency.USD, clock.getUTCNow(), Subscription.SubscriptionState.ACTIVE, catalog)
+        );
+        subscriptionTransitionSqlDao.createTransition(transition, internalCallContext);
+
+        final TimeSeriesData notFoundData = analyticsUserApi.getSubscriptionsCreatedOverTime(productType, UUID.randomUUID().toString(), callContext);
+        Assert.assertEquals(notFoundData.getDates().size(), 0);
+        Assert.assertEquals(notFoundData.getValues().size(), 0);
+
+        final TimeSeriesData data = analyticsUserApi.getSubscriptionsCreatedOverTime(productType, phase.getName(), callContext);
+        Assert.assertEquals(data.getDates().size(), 1);
+        Assert.assertEquals(data.getDates().get(0), clock.getUTCToday());
+        Assert.assertEquals(data.getValues().size(), 1);
+        Assert.assertEquals(data.getValues().get(0), (double) 1);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestAnalyticsDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestAnalyticsDao.java
new file mode 100644
index 0000000..07aa037
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestAnalyticsDao.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+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.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.Currency;
+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;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.mock.MockPlan;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteWithEmbeddedDB;
+import com.ning.billing.osgi.bundles.analytics.MockDuration;
+import com.ning.billing.osgi.bundles.analytics.MockPhase;
+import com.ning.billing.osgi.bundles.analytics.MockProduct;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountModelDao;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscription;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionEvent;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionModelDao;
+import com.ning.billing.osgi.bundles.analytics.utils.Rounder;
+
+public class TestAnalyticsDao extends AnalyticsTestSuiteWithEmbeddedDB {
+
+    private static final Long TOTAL_ORDERING = 1L;
+    private static final UUID BUNDLE_ID = UUID.randomUUID();
+    private static final String EXTERNAL_KEY = "23456";
+    private static final UUID ACCOUNT_ID = UUID.randomUUID();
+    private static final String ACCOUNT_KEY = "pierre-143343-vcc";
+    private static final String CURRENCY = UUID.randomUUID().toString();
+
+    private final Product product = new MockProduct("platinium", "subscription", ProductCategory.BASE);
+    private final Plan plan = new MockPlan("platinum-monthly", product);
+    private final PlanPhase phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
+
+    private BusinessSubscriptionTransitionModelDao transition;
+    private BusinessAccountModelDao account;
+
+    private final Catalog catalog = Mockito.mock(Catalog.class);
+
+    @BeforeClass(groups = "slow")
+    public void beforeClass() throws Exception {
+        super.beforeClass();
+        Mockito.when(catalog.findPlan(Mockito.anyString(), Mockito.<DateTime>any())).thenReturn(plan);
+        Mockito.when(catalog.findPlan(Mockito.anyString(), Mockito.<DateTime>any(), Mockito.<DateTime>any())).thenReturn(plan);
+        Mockito.when(catalog.findPhase(Mockito.anyString(), Mockito.<DateTime>any(), Mockito.<DateTime>any())).thenReturn(phase);
+        Mockito.when(catalogService.getFullCatalog()).thenReturn(catalog);
+
+        setupBusinessSubscriptionTransition();
+        setupBusinessAccount();
+    }
+
+    private void setupBusinessSubscriptionTransition() {
+        final DateTime requestedTimestamp = clock.getUTCNow();
+        final BusinessSubscription prevSubscription = new BusinessSubscription(null, plan.getName(), phase.getName(), Currency.USD, clock.getUTCNow(), Subscription.SubscriptionState.ACTIVE, catalog);
+        final BusinessSubscription nextSubscription = new BusinessSubscription(null, plan.getName(), phase.getName(), Currency.USD, clock.getUTCNow(), Subscription.SubscriptionState.CANCELLED, catalog);
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionCancelled(plan.getName(), catalog, requestedTimestamp, requestedTimestamp);
+
+        transition = new BusinessSubscriptionTransitionModelDao(TOTAL_ORDERING, BUNDLE_ID, EXTERNAL_KEY, ACCOUNT_ID, ACCOUNT_KEY,
+                                                                UUID.randomUUID(), requestedTimestamp, event, prevSubscription, nextSubscription);
+    }
+
+    private void setupBusinessAccount() {
+        account = new BusinessAccountModelDao(UUID.randomUUID(), ACCOUNT_KEY, UUID.randomUUID().toString(), BigDecimal.ONE, clock.getUTCToday(),
+                                              BigDecimal.TEN, "ERROR_NOT_ENOUGH_FUNDS", "CreditCard", "Visa", "FRANCE", CURRENCY, clock.getUTCNow(), clock.getUTCNow());
+    }
+
+    @Test(groups = "slow")
+    public void testTransitionsWithNullPrevSubscription() {
+        final BusinessSubscriptionTransitionModelDao transitionWithNullPrev = new BusinessSubscriptionTransitionModelDao(
+                transition.getTotalOrdering(),
+                transition.getBundleId(),
+                transition.getExternalKey(),
+                transition.getAccountId(),
+                transition.getAccountKey(),
+                transition.getSubscriptionId(),
+                transition.getRequestedTimestamp(),
+                transition.getEvent(),
+                null,
+                transition.getNextSubscription()
+        );
+        subscriptionTransitionSqlDao.createTransition(transitionWithNullPrev, internalCallContext);
+
+        final List<BusinessSubscriptionTransitionModelDao> transitions = subscriptionTransitionSqlDao.getTransitionsByKey(EXTERNAL_KEY, internalCallContext);
+        Assert.assertEquals(transitions.size(), 1);
+        Assert.assertEquals(transitions.get(0), transitionWithNullPrev);
+    }
+
+    @Test(groups = "slow")
+    public void testTransitionsWithNullNextSubscription() {
+        final BusinessSubscriptionTransitionModelDao transitionWithNullNext = new BusinessSubscriptionTransitionModelDao(
+                transition.getTotalOrdering(),
+                transition.getBundleId(),
+                transition.getExternalKey(),
+                transition.getAccountId(),
+                transition.getAccountKey(),
+                transition.getSubscriptionId(),
+                transition.getRequestedTimestamp(),
+                transition.getEvent(),
+                transition.getPreviousSubscription(),
+                null
+        );
+        subscriptionTransitionSqlDao.createTransition(transitionWithNullNext, internalCallContext);
+
+        final List<BusinessSubscriptionTransitionModelDao> transitions = subscriptionTransitionSqlDao.getTransitionsByKey(EXTERNAL_KEY, internalCallContext);
+        Assert.assertEquals(transitions.size(), 1);
+        Assert.assertEquals(transitions.get(0), transitionWithNullNext);
+    }
+
+    @Test(groups = "slow")
+    public void testTransitionsWithNullFieldsInSubscription() {
+        final BusinessSubscription subscriptionWithNullFields = new BusinessSubscription(null, plan.getName(), phase.getName(), Currency.USD, null, null, catalog);
+        final BusinessSubscriptionTransitionModelDao transitionWithNullFields = new BusinessSubscriptionTransitionModelDao(
+                transition.getTotalOrdering(),
+                transition.getBundleId(),
+                transition.getExternalKey(),
+                transition.getAccountId(),
+                transition.getAccountKey(),
+                transition.getSubscriptionId(),
+                transition.getRequestedTimestamp(),
+                transition.getEvent(),
+                subscriptionWithNullFields,
+                subscriptionWithNullFields
+        );
+        subscriptionTransitionSqlDao.createTransition(transitionWithNullFields, internalCallContext);
+
+        final List<BusinessSubscriptionTransitionModelDao> transitions = subscriptionTransitionSqlDao.getTransitionsByKey(EXTERNAL_KEY, internalCallContext);
+        Assert.assertEquals(transitions.size(), 1);
+        Assert.assertEquals(transitions.get(0), transitionWithNullFields);
+    }
+
+    @Test(groups = "slow")
+    public void testTransitionsWithNullPlanAndPhase() throws Exception {
+        final BusinessSubscription subscriptionWithNullPlanAndPhase = new BusinessSubscription(null, null, null, Currency.USD, null, null, catalog);
+        final BusinessSubscriptionTransitionModelDao transitionWithNullPlanAndPhase = new BusinessSubscriptionTransitionModelDao(
+                transition.getTotalOrdering(),
+                transition.getBundleId(),
+                transition.getExternalKey(),
+                transition.getAccountId(),
+                transition.getAccountKey(),
+                transition.getSubscriptionId(),
+                transition.getRequestedTimestamp(),
+                transition.getEvent(),
+                subscriptionWithNullPlanAndPhase,
+                subscriptionWithNullPlanAndPhase
+        );
+        subscriptionTransitionSqlDao.createTransition(transitionWithNullPlanAndPhase, internalCallContext);
+
+        final List<BusinessSubscriptionTransitionModelDao> transitions = subscriptionTransitionSqlDao.getTransitionsByKey(EXTERNAL_KEY, internalCallContext);
+        Assert.assertEquals(transitions.size(), 1);
+        Assert.assertEquals(transitions.get(0).getExternalKey(), transition.getExternalKey());
+        Assert.assertEquals(transitions.get(0).getRequestedTimestamp(), transition.getRequestedTimestamp());
+        Assert.assertEquals(transitions.get(0).getEvent(), transition.getEvent());
+        Assert.assertNull(transitions.get(0).getPreviousSubscription());
+        Assert.assertNull(transitions.get(0).getNextSubscription());
+    }
+
+    @Test(groups = "slow")
+    public void testTransitionsWithNullPlan() throws Exception {
+        final BusinessSubscription subscriptionWithNullPlan = new BusinessSubscription(null, null, phase.getName(), Currency.USD, null, null, catalog);
+        final BusinessSubscriptionTransitionModelDao transitionWithNullPlan = new BusinessSubscriptionTransitionModelDao(
+                transition.getTotalOrdering(),
+                transition.getBundleId(),
+                transition.getExternalKey(),
+                transition.getAccountId(),
+                transition.getAccountKey(),
+                transition.getSubscriptionId(),
+                transition.getRequestedTimestamp(),
+                transition.getEvent(),
+                subscriptionWithNullPlan,
+                subscriptionWithNullPlan
+        );
+        subscriptionTransitionSqlDao.createTransition(transitionWithNullPlan, internalCallContext);
+
+        final List<BusinessSubscriptionTransitionModelDao> transitions = subscriptionTransitionSqlDao.getTransitionsByKey(EXTERNAL_KEY, internalCallContext);
+        Assert.assertEquals(transitions.size(), 1);
+        // Null Plan but Phase - we don't turn the subscription into a null
+        Assert.assertEquals(transitions.get(0), transitionWithNullPlan);
+    }
+
+    @Test(groups = "slow")
+    public void testTransitionsWithNullPhase() throws Exception {
+        final BusinessSubscription subscriptionWithNullPhase = new BusinessSubscription(null, plan.getName(), null, Currency.USD, null, null, catalog);
+        final BusinessSubscriptionTransitionModelDao transitionWithNullPhase = new BusinessSubscriptionTransitionModelDao(
+                transition.getTotalOrdering(),
+                transition.getBundleId(),
+                transition.getExternalKey(),
+                transition.getAccountId(),
+                transition.getAccountKey(),
+                transition.getSubscriptionId(),
+                transition.getRequestedTimestamp(),
+                transition.getEvent(),
+                subscriptionWithNullPhase,
+                subscriptionWithNullPhase
+        );
+        subscriptionTransitionSqlDao.createTransition(transitionWithNullPhase, internalCallContext);
+
+        final List<BusinessSubscriptionTransitionModelDao> transitions = subscriptionTransitionSqlDao.getTransitionsByKey(EXTERNAL_KEY, internalCallContext);
+        Assert.assertEquals(transitions.size(), 1);
+        Assert.assertEquals(transitions.get(0).getExternalKey(), transition.getExternalKey());
+        Assert.assertEquals(transitions.get(0).getRequestedTimestamp(), transition.getRequestedTimestamp());
+        Assert.assertEquals(transitions.get(0).getEvent(), transition.getEvent());
+
+        // Null Phase but Plan - we don't turn the subscription into a null, however price and mrr are both set to 0 (not null)
+        final BusinessSubscription blankSubscription = new BusinessSubscription(null, plan.getName(), new MockPhase(null, null, null, 0.0).getName(), Currency.USD, null, null, catalog);
+        Assert.assertEquals(transitions.get(0).getPreviousSubscription(), blankSubscription);
+        Assert.assertEquals(transitions.get(0).getNextSubscription(), blankSubscription);
+    }
+
+    @Test(groups = "slow")
+    public void testCreateAndRetrieveTransitions() {
+        subscriptionTransitionSqlDao.createTransition(transition, internalCallContext);
+
+        final List<BusinessSubscriptionTransitionModelDao> transitions = subscriptionTransitionSqlDao.getTransitionsByKey(EXTERNAL_KEY, internalCallContext);
+        Assert.assertEquals(transitions.size(), 1);
+        Assert.assertEquals(transitions.get(0), transition);
+
+        Assert.assertEquals(subscriptionTransitionSqlDao.getTransitionsByKey("Doesn't exist", internalCallContext).size(), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testCreateSaveAndRetrieveAccounts() {
+        // Create and retrieve an account
+        accountSqlDao.createAccount(account, internalCallContext);
+        final BusinessAccountModelDao foundAccount = accountSqlDao.getAccountByKey(ACCOUNT_KEY, internalCallContext);
+        Assert.assertEquals(foundAccount.getCreatedDate().getMillis(), account.getCreatedDate().getMillis());
+        Assert.assertEquals(foundAccount.getUpdatedDate().getMillis(), account.getUpdatedDate().getMillis());
+        Assert.assertTrue(foundAccount.equals(account));
+
+        // Try to update the account
+        account.setBalance(BigDecimal.TEN);
+        account.setPaymentMethod("PayPal");
+        account.setCurrency("CAD");
+        accountSqlDao.saveAccount(account, internalCallContext);
+        // Verify the save worked as expected
+        account = accountSqlDao.getAccountByKey(ACCOUNT_KEY, internalCallContext);
+        Assert.assertEquals(Rounder.round(BigDecimal.TEN), account.getRoundedBalance());
+        Assert.assertEquals("PayPal", account.getPaymentMethod());
+        Assert.assertEquals("CAD", account.getCurrency());
+
+        // ACCOUNT not found
+        Assert.assertNull(accountSqlDao.getAccountByKey("Doesn't exist", internalCallContext));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessAccountFieldSqlDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessAccountFieldSqlDao.java
new file mode 100644
index 0000000..c664b30
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessAccountFieldSqlDao.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteWithEmbeddedDB;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountFieldModelDao;
+
+public class TestBusinessAccountFieldSqlDao extends AnalyticsTestSuiteWithEmbeddedDB {
+
+    @Test(groups = "slow")
+    public void testCRUD() throws Exception {
+        final UUID accountId = UUID.randomUUID();
+        final String accountKey = UUID.randomUUID().toString();
+        final String name = UUID.randomUUID().toString().substring(0, 30);
+        final String value = UUID.randomUUID().toString();
+
+        // Verify initial state
+        Assert.assertEquals(accountFieldSqlDao.getFieldsForAccountByKey(accountKey, internalCallContext).size(), 0);
+        Assert.assertEquals(accountFieldSqlDao.removeField(accountId.toString(), name, internalCallContext), 0);
+
+        // Add an entry
+        Assert.assertEquals(accountFieldSqlDao.addField(accountId.toString(), accountKey, name, value, internalCallContext), 1);
+        final List<BusinessAccountFieldModelDao> fieldsForAccount = accountFieldSqlDao.getFieldsForAccountByKey(accountKey, internalCallContext);
+        Assert.assertEquals(fieldsForAccount.size(), 1);
+
+        // Retrieve it
+        final BusinessAccountFieldModelDao accountField = fieldsForAccount.get(0);
+        Assert.assertEquals(accountField.getAccountId(), accountId);
+        Assert.assertEquals(accountField.getAccountKey(), accountKey);
+        Assert.assertEquals(accountField.getName(), name);
+        Assert.assertEquals(accountField.getValue(), value);
+
+        // Delete it
+        Assert.assertEquals(accountFieldSqlDao.removeField(accountId.toString(), name, internalCallContext), 1);
+        Assert.assertEquals(accountFieldSqlDao.getFieldsForAccountByKey(accountKey, internalCallContext).size(), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testSegmentation() throws Exception {
+        final UUID accountId1 = UUID.randomUUID();
+        final String accountKey1 = UUID.randomUUID().toString();
+        final String name1 = UUID.randomUUID().toString().substring(0, 30);
+        final UUID accountId2 = UUID.randomUUID();
+        final String accountKey2 = UUID.randomUUID().toString();
+        final String name2 = UUID.randomUUID().toString().substring(0, 30);
+
+        // Add a field to both accounts
+        Assert.assertEquals(accountFieldSqlDao.addField(accountId1.toString(), accountKey1, name1, UUID.randomUUID().toString(), internalCallContext), 1);
+        Assert.assertEquals(accountFieldSqlDao.addField(accountId2.toString(), accountKey2, name2, UUID.randomUUID().toString(), internalCallContext), 1);
+
+        Assert.assertEquals(accountFieldSqlDao.getFieldsForAccountByKey(accountKey1, internalCallContext).size(), 1);
+        Assert.assertEquals(accountFieldSqlDao.getFieldsForAccountByKey(accountKey2, internalCallContext).size(), 1);
+
+        // Remove the field for the first account
+        Assert.assertEquals(accountFieldSqlDao.removeField(accountId1.toString(), name1, internalCallContext), 1);
+
+        Assert.assertEquals(accountFieldSqlDao.getFieldsForAccountByKey(accountKey1, internalCallContext).size(), 0);
+        Assert.assertEquals(accountFieldSqlDao.getFieldsForAccountByKey(accountKey2, internalCallContext).size(), 1);
+    }
+
+    @Test(groups = "slow")
+    public void testHealthCheck() throws Exception {
+        // HealthCheck test to make sure MySQL is setup properly
+        try {
+            accountFieldSqlDao.test(internalCallContext);
+        } catch (Throwable t) {
+            Assert.fail(t.toString());
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessAccountTagSqlDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessAccountTagSqlDao.java
new file mode 100644
index 0000000..597adcc
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessAccountTagSqlDao.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteWithEmbeddedDB;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessAccountTagModelDao;
+
+public class TestBusinessAccountTagSqlDao extends AnalyticsTestSuiteWithEmbeddedDB {
+
+    @Test(groups = "slow")
+    public void testCRUD() throws Exception {
+        final UUID accountId = UUID.randomUUID();
+        final String accountKey = UUID.randomUUID().toString();
+        final String name = UUID.randomUUID().toString().substring(0, 20);
+
+        // Verify initial state
+        Assert.assertEquals(accountTagSqlDao.getTagsForAccountByKey(accountKey, internalCallContext).size(), 0);
+        Assert.assertEquals(accountTagSqlDao.removeTag(accountId.toString(), name, internalCallContext), 0);
+
+        // Add an entry
+        Assert.assertEquals(accountTagSqlDao.addTag(accountId.toString(), accountKey, name, internalCallContext), 1);
+        final List<BusinessAccountTagModelDao> tagsForAccount = accountTagSqlDao.getTagsForAccountByKey(accountKey, internalCallContext);
+        Assert.assertEquals(tagsForAccount.size(), 1);
+
+        // Retrieve it
+        final BusinessAccountTagModelDao accountTag = tagsForAccount.get(0);
+        Assert.assertEquals(accountTag.getAccountId(), accountId);
+        Assert.assertEquals(accountTag.getAccountKey(), accountKey);
+        Assert.assertEquals(accountTag.getName(), name);
+
+        // Delete it
+        Assert.assertEquals(accountTagSqlDao.removeTag(accountId.toString(), name, internalCallContext), 1);
+        Assert.assertEquals(accountTagSqlDao.getTagsForAccountByKey(accountKey, internalCallContext).size(), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testSegmentation() throws Exception {
+        final UUID accountId1 = UUID.randomUUID();
+        final String accountKey1 = UUID.randomUUID().toString();
+        final String name1 = UUID.randomUUID().toString().substring(0, 20);
+        final UUID accountId2 = UUID.randomUUID();
+        final String accountKey2 = UUID.randomUUID().toString();
+        final String name2 = UUID.randomUUID().toString().substring(0, 20);
+
+        // Add a tag to both accounts
+        Assert.assertEquals(accountTagSqlDao.addTag(accountId1.toString(), accountKey1, name1, internalCallContext), 1);
+        Assert.assertEquals(accountTagSqlDao.addTag(accountId2.toString(), accountKey2, name2, internalCallContext), 1);
+
+        Assert.assertEquals(accountTagSqlDao.getTagsForAccountByKey(accountKey1, internalCallContext).size(), 1);
+        Assert.assertEquals(accountTagSqlDao.getTagsForAccountByKey(accountKey2, internalCallContext).size(), 1);
+
+        // Remove the tag for the first account
+        Assert.assertEquals(accountTagSqlDao.removeTag(accountId1.toString(), name1, internalCallContext), 1);
+
+        Assert.assertEquals(accountTagSqlDao.getTagsForAccountByKey(accountKey1, internalCallContext).size(), 0);
+        Assert.assertEquals(accountTagSqlDao.getTagsForAccountByKey(accountKey2, internalCallContext).size(), 1);
+    }
+
+    @Test(groups = "slow")
+    public void testHealthCheck() throws Exception {
+        // HealthCheck test to make sure MySQL is setup properly
+        try {
+            accountTagSqlDao.test(internalCallContext);
+        } catch (Throwable t) {
+            Assert.fail(t.toString());
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceFieldSqlDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceFieldSqlDao.java
new file mode 100644
index 0000000..234a7c9
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceFieldSqlDao.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteWithEmbeddedDB;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceFieldModelDao;
+
+public class TestBusinessInvoiceFieldSqlDao extends AnalyticsTestSuiteWithEmbeddedDB {
+
+    @Test(groups = "slow")
+    public void testCRUD() throws Exception {
+        final String invoiceId = UUID.randomUUID().toString();
+        final String name = UUID.randomUUID().toString().substring(0, 30);
+        final String value = UUID.randomUUID().toString();
+
+        // Verify initial state
+        Assert.assertEquals(invoiceFieldSqlDao.getFieldsForInvoice(invoiceId, internalCallContext).size(), 0);
+        Assert.assertEquals(invoiceFieldSqlDao.removeField(invoiceId, name, internalCallContext), 0);
+
+        // Add an entry
+        Assert.assertEquals(invoiceFieldSqlDao.addField(invoiceId, name, value, internalCallContext), 1);
+        final List<BusinessInvoiceFieldModelDao> fieldsForInvoice = invoiceFieldSqlDao.getFieldsForInvoice(invoiceId, internalCallContext);
+        Assert.assertEquals(fieldsForInvoice.size(), 1);
+
+        // Retrieve it
+        final BusinessInvoiceFieldModelDao invoiceField = fieldsForInvoice.get(0);
+        Assert.assertEquals(invoiceField.getInvoiceId().toString(), invoiceId);
+        Assert.assertEquals(invoiceField.getName(), name);
+        Assert.assertEquals(invoiceField.getValue(), value);
+
+        // Delete it
+        Assert.assertEquals(invoiceFieldSqlDao.removeField(invoiceId, name, internalCallContext), 1);
+        Assert.assertEquals(invoiceFieldSqlDao.getFieldsForInvoice(invoiceId, internalCallContext).size(), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testSegmentation() throws Exception {
+        final String invoiceId1 = UUID.randomUUID().toString();
+        final String name1 = UUID.randomUUID().toString().substring(0, 30);
+        final String invoiceId2 = UUID.randomUUID().toString();
+        final String name2 = UUID.randomUUID().toString().substring(0, 30);
+
+        // Add a field to both invoices
+        Assert.assertEquals(invoiceFieldSqlDao.addField(invoiceId1, name1, UUID.randomUUID().toString(), internalCallContext), 1);
+        Assert.assertEquals(invoiceFieldSqlDao.addField(invoiceId2, name2, UUID.randomUUID().toString(), internalCallContext), 1);
+
+        Assert.assertEquals(invoiceFieldSqlDao.getFieldsForInvoice(invoiceId1, internalCallContext).size(), 1);
+        Assert.assertEquals(invoiceFieldSqlDao.getFieldsForInvoice(invoiceId2, internalCallContext).size(), 1);
+
+        // Remove the field for the first invoice
+        Assert.assertEquals(invoiceFieldSqlDao.removeField(invoiceId1, name1, internalCallContext), 1);
+
+        Assert.assertEquals(invoiceFieldSqlDao.getFieldsForInvoice(invoiceId1, internalCallContext).size(), 0);
+        Assert.assertEquals(invoiceFieldSqlDao.getFieldsForInvoice(invoiceId2, internalCallContext).size(), 1);
+    }
+
+    @Test(groups = "slow")
+    public void testHealthCheck() throws Exception {
+        // HealthCheck test to make sure MySQL is setup properly
+        try {
+            invoiceFieldSqlDao.test(internalCallContext);
+        } catch (Throwable t) {
+            Assert.fail(t.toString());
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceItemSqlDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceItemSqlDao.java
new file mode 100644
index 0000000..d6997fb
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceItemSqlDao.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+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.osgi.bundles.analytics.AnalyticsTestSuiteWithEmbeddedDB;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceItemModelDao;
+
+public class TestBusinessInvoiceItemSqlDao extends AnalyticsTestSuiteWithEmbeddedDB {
+
+    @Test(groups = "slow")
+    public void testCRUD() throws Exception {
+        final UUID invoiceId = UUID.randomUUID();
+        final String externalKey = UUID.randomUUID().toString();
+        final BusinessInvoiceItemModelDao invoiceItem = createInvoiceItem(invoiceId, externalKey);
+
+        // Verify initial state
+        Assert.assertNull(invoiceItemSqlDao.getInvoiceItem(invoiceItem.getItemId().toString(), internalCallContext));
+        Assert.assertEquals(invoiceItemSqlDao.deleteInvoiceItem(invoiceItem.getItemId().toString(), internalCallContext), 0);
+
+        // Add the invoice item
+        Assert.assertEquals(invoiceItemSqlDao.createInvoiceItem(invoiceItem, internalCallContext), 1);
+
+        // Retrieve it
+        Assert.assertEquals(invoiceItemSqlDao.getInvoiceItem(invoiceItem.getItemId().toString(), internalCallContext), invoiceItem);
+        Assert.assertEquals(invoiceItemSqlDao.getInvoiceItemsForBundleByKey(invoiceItem.getExternalKey(), internalCallContext).size(), 1);
+        Assert.assertEquals(invoiceItemSqlDao.getInvoiceItemsForBundleByKey(invoiceItem.getExternalKey(), internalCallContext).get(0), invoiceItem);
+        Assert.assertEquals(invoiceItemSqlDao.getInvoiceItemsForInvoice(invoiceItem.getInvoiceId().toString(), internalCallContext).size(), 1);
+        Assert.assertEquals(invoiceItemSqlDao.getInvoiceItemsForInvoice(invoiceItem.getInvoiceId().toString(), internalCallContext).get(0), invoiceItem);
+
+        // Delete it
+        Assert.assertEquals(invoiceItemSqlDao.deleteInvoiceItem(invoiceItem.getItemId().toString(), internalCallContext), 1);
+        Assert.assertNull(invoiceItemSqlDao.getInvoiceItem(invoiceItem.getItemId().toString(), internalCallContext));
+        Assert.assertEquals(invoiceItemSqlDao.getInvoiceItemsForBundleByKey(invoiceItem.getExternalKey(), internalCallContext).size(), 0);
+        Assert.assertEquals(invoiceItemSqlDao.getInvoiceItemsForInvoice(invoiceItem.getInvoiceId().toString(), internalCallContext).size(), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testSegmentation() throws Exception {
+        final UUID invoiceId1 = UUID.randomUUID();
+        final String externalKey1 = UUID.randomUUID().toString();
+        final BusinessInvoiceItemModelDao invoiceItem1 = createInvoiceItem(invoiceId1, externalKey1);
+        final UUID invoiceId2 = UUID.randomUUID();
+        final String externalKey2 = UUID.randomUUID().toString();
+        final BusinessInvoiceItemModelDao invoiceItem2 = createInvoiceItem(invoiceId2, externalKey2);
+
+        // Create both invoice items
+        Assert.assertEquals(invoiceItemSqlDao.createInvoiceItem(invoiceItem1, internalCallContext), 1);
+        Assert.assertEquals(invoiceItemSqlDao.createInvoiceItem(invoiceItem2, internalCallContext), 1);
+
+        Assert.assertEquals(invoiceItemSqlDao.getInvoiceItemsForBundleByKey(externalKey1, internalCallContext).size(), 1);
+        Assert.assertEquals(invoiceItemSqlDao.getInvoiceItemsForBundleByKey(externalKey2, internalCallContext).size(), 1);
+        Assert.assertEquals(invoiceItemSqlDao.getInvoiceItemsForInvoice(invoiceId1.toString(), internalCallContext).size(), 1);
+        Assert.assertEquals(invoiceItemSqlDao.getInvoiceItemsForInvoice(invoiceId2.toString(), internalCallContext).size(), 1);
+
+        // Remove the first invoice item
+        Assert.assertEquals(invoiceItemSqlDao.deleteInvoiceItem(invoiceItem1.getItemId().toString(), internalCallContext), 1);
+
+        Assert.assertEquals(invoiceItemSqlDao.getInvoiceItemsForBundleByKey(externalKey1, internalCallContext).size(), 0);
+        Assert.assertEquals(invoiceItemSqlDao.getInvoiceItemsForBundleByKey(externalKey2, internalCallContext).size(), 1);
+        Assert.assertEquals(invoiceItemSqlDao.getInvoiceItemsForInvoice(invoiceId1.toString(), internalCallContext).size(), 0);
+        Assert.assertEquals(invoiceItemSqlDao.getInvoiceItemsForInvoice(invoiceId2.toString(), internalCallContext).size(), 1);
+    }
+
+    @Test(groups = "slow")
+    public void testHealthCheck() throws Exception {
+        // HealthCheck test to make sure MySQL is setup properly
+        try {
+            invoiceItemSqlDao.test(internalCallContext);
+        } catch (Throwable t) {
+            Assert.fail(t.toString());
+        }
+    }
+
+    private BusinessInvoiceItemModelDao createInvoiceItem(final UUID invoiceId, final String externalKey) {
+        final BigDecimal amount = BigDecimal.TEN;
+        final String billingPeriod = UUID.randomUUID().toString().substring(0, 20);
+        final DateTime createdDate = clock.getUTCNow();
+        final Currency currency = Currency.AUD;
+        final LocalDate endDate = clock.getUTCToday();
+        final UUID itemId = UUID.randomUUID();
+        final UUID linkedItemId = UUID.randomUUID();
+        final String itemType = UUID.randomUUID().toString().substring(0, 20);
+        final String phase = UUID.randomUUID().toString().substring(0, 20);
+        final String productCategory = UUID.randomUUID().toString().substring(0, 20);
+        final String productName = UUID.randomUUID().toString().substring(0, 20);
+        final String productType = UUID.randomUUID().toString().substring(0, 20);
+        final String slug = UUID.randomUUID().toString();
+        final LocalDate startDate = clock.getUTCToday();
+        final DateTime updatedDate = clock.getUTCNow();
+
+        return new BusinessInvoiceItemModelDao(amount, billingPeriod, createdDate, currency, endDate, externalKey, invoiceId, itemId,
+                                               linkedItemId, itemType, phase, productCategory, productName, productType, slug, startDate, updatedDate);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoicePaymentFieldSqlDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoicePaymentFieldSqlDao.java
new file mode 100644
index 0000000..0c4b38d
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoicePaymentFieldSqlDao.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteWithEmbeddedDB;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoicePaymentFieldModelDao;
+
+public class TestBusinessInvoicePaymentFieldSqlDao extends AnalyticsTestSuiteWithEmbeddedDB {
+
+    @Test(groups = "slow")
+    public void testCRUD() throws Exception {
+        final String paymentId = UUID.randomUUID().toString();
+        final String name = UUID.randomUUID().toString().substring(0, 30);
+        final String value = UUID.randomUUID().toString();
+
+        // Verify initial state
+        Assert.assertEquals(invoicePaymentFieldSqlDao.getFieldsForInvoicePayment(paymentId, internalCallContext).size(), 0);
+        Assert.assertEquals(invoicePaymentFieldSqlDao.removeField(paymentId, name, internalCallContext), 0);
+
+        // Add an entry
+        Assert.assertEquals(invoicePaymentFieldSqlDao.addField(paymentId, name, value, internalCallContext), 1);
+        final List<BusinessInvoicePaymentFieldModelDao> fieldsForInvoicePayment = invoicePaymentFieldSqlDao.getFieldsForInvoicePayment(paymentId, internalCallContext);
+        Assert.assertEquals(fieldsForInvoicePayment.size(), 1);
+
+        // Retrieve it
+        final BusinessInvoicePaymentFieldModelDao invoicePaymentField = fieldsForInvoicePayment.get(0);
+        Assert.assertEquals(invoicePaymentField.getPaymentId().toString(), paymentId);
+        Assert.assertEquals(invoicePaymentField.getName(), name);
+        Assert.assertEquals(invoicePaymentField.getValue(), value);
+
+        // Delete it
+        Assert.assertEquals(invoicePaymentFieldSqlDao.removeField(paymentId, name, internalCallContext), 1);
+        Assert.assertEquals(invoicePaymentFieldSqlDao.getFieldsForInvoicePayment(paymentId, internalCallContext).size(), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testSegmentation() throws Exception {
+        final String paymentId1 = UUID.randomUUID().toString();
+        final String name1 = UUID.randomUUID().toString().substring(0, 30);
+        final String paymentId2 = UUID.randomUUID().toString();
+        final String name2 = UUID.randomUUID().toString().substring(0, 30);
+
+        // Add a field to both invoice payments
+        Assert.assertEquals(invoicePaymentFieldSqlDao.addField(paymentId1, name1, UUID.randomUUID().toString(), internalCallContext), 1);
+        Assert.assertEquals(invoicePaymentFieldSqlDao.addField(paymentId2, name2, UUID.randomUUID().toString(), internalCallContext), 1);
+
+        Assert.assertEquals(invoicePaymentFieldSqlDao.getFieldsForInvoicePayment(paymentId1, internalCallContext).size(), 1);
+        Assert.assertEquals(invoicePaymentFieldSqlDao.getFieldsForInvoicePayment(paymentId2, internalCallContext).size(), 1);
+
+        // Remove the field for the first invoice payment
+        Assert.assertEquals(invoicePaymentFieldSqlDao.removeField(paymentId1, name1, internalCallContext), 1);
+
+        Assert.assertEquals(invoicePaymentFieldSqlDao.getFieldsForInvoicePayment(paymentId1, internalCallContext).size(), 0);
+        Assert.assertEquals(invoicePaymentFieldSqlDao.getFieldsForInvoicePayment(paymentId2, internalCallContext).size(), 1);
+    }
+
+    @Test(groups = "slow")
+    public void testHealthCheck() throws Exception {
+        // HealthCheck test to make sure MySQL is setup properly
+        try {
+            invoicePaymentFieldSqlDao.test(internalCallContext);
+        } catch (Throwable t) {
+            Assert.fail(t.toString());
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoicePaymentSqlDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoicePaymentSqlDao.java
new file mode 100644
index 0000000..c8445c4
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoicePaymentSqlDao.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteWithEmbeddedDB;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoicePaymentModelDao;
+
+public class TestBusinessInvoicePaymentSqlDao extends AnalyticsTestSuiteWithEmbeddedDB {
+
+    @Test(groups = "slow")
+    public void testCRUD() throws Exception {
+        final String accountKey = UUID.randomUUID().toString();
+        final BusinessInvoicePaymentModelDao invoicePayment = createInvoicePayment(accountKey);
+
+        // Verify initial state
+        Assert.assertNull(invoicePaymentSqlDao.getInvoicePayment(invoicePayment.getPaymentId().toString(), internalCallContext));
+        Assert.assertEquals(invoicePaymentSqlDao.getInvoicePaymentsForAccountByKey(invoicePayment.getAccountKey(), internalCallContext).size(), 0);
+
+        // Add the invoice payment
+        Assert.assertEquals(invoicePaymentSqlDao.createInvoicePayment(invoicePayment, internalCallContext), 1);
+
+        // Retrieve it
+        Assert.assertEquals(invoicePaymentSqlDao.getInvoicePayment(invoicePayment.getPaymentId().toString(), internalCallContext), invoicePayment);
+        Assert.assertEquals(invoicePaymentSqlDao.getInvoicePaymentsForAccountByKey(invoicePayment.getAccountKey(), internalCallContext).size(), 1);
+        Assert.assertEquals(invoicePaymentSqlDao.getInvoicePaymentsForAccountByKey(invoicePayment.getAccountKey(), internalCallContext).get(0), invoicePayment);
+
+        // Delete it
+        Assert.assertEquals(invoicePaymentSqlDao.deleteInvoicePayment(invoicePayment.getPaymentId().toString(), internalCallContext), 1);
+        Assert.assertNull(invoicePaymentSqlDao.getInvoicePayment(invoicePayment.getPaymentId().toString(), internalCallContext));
+        Assert.assertEquals(invoicePaymentSqlDao.getInvoicePaymentsForAccountByKey(invoicePayment.getAccountKey(), internalCallContext).size(), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testSegmentation() throws Exception {
+        final String accountKey1 = UUID.randomUUID().toString();
+        final BusinessInvoicePaymentModelDao invoicePayment1 = createInvoicePayment(accountKey1);
+        final String accountKey2 = UUID.randomUUID().toString();
+        final BusinessInvoicePaymentModelDao invoicePayment2 = createInvoicePayment(accountKey2);
+
+        // Create both invoice payments
+        Assert.assertEquals(invoicePaymentSqlDao.createInvoicePayment(invoicePayment1, internalCallContext), 1);
+        Assert.assertEquals(invoicePaymentSqlDao.createInvoicePayment(invoicePayment2, internalCallContext), 1);
+
+        Assert.assertEquals(invoicePaymentSqlDao.getInvoicePayment(invoicePayment1.getPaymentId().toString(), internalCallContext), invoicePayment1);
+        Assert.assertEquals(invoicePaymentSqlDao.getInvoicePayment(invoicePayment2.getPaymentId().toString(), internalCallContext), invoicePayment2);
+        Assert.assertEquals(invoicePaymentSqlDao.getInvoicePaymentsForAccountByKey(invoicePayment1.getAccountKey(), internalCallContext).size(), 1);
+        Assert.assertEquals(invoicePaymentSqlDao.getInvoicePaymentsForAccountByKey(invoicePayment2.getAccountKey(), internalCallContext).size(), 1);
+
+        // Remove the first invoice payment
+        Assert.assertEquals(invoicePaymentSqlDao.deleteInvoicePayment(invoicePayment1.getPaymentId().toString(), internalCallContext), 1);
+
+        Assert.assertNull(invoicePaymentSqlDao.getInvoicePayment(invoicePayment1.getPaymentId().toString(), internalCallContext));
+        Assert.assertEquals(invoicePaymentSqlDao.getInvoicePayment(invoicePayment2.getPaymentId().toString(), internalCallContext), invoicePayment2);
+        Assert.assertEquals(invoicePaymentSqlDao.getInvoicePaymentsForAccountByKey(invoicePayment1.getAccountKey(), internalCallContext).size(), 0);
+        Assert.assertEquals(invoicePaymentSqlDao.getInvoicePaymentsForAccountByKey(invoicePayment2.getAccountKey(), internalCallContext).size(), 1);
+    }
+
+    @Test(groups = "slow")
+    public void testHealthCheck() throws Exception {
+        // HealthCheck test to make sure MySQL is setup properly
+        try {
+            invoicePaymentSqlDao.test(internalCallContext);
+        } catch (Throwable t) {
+            Assert.fail(t.toString());
+        }
+    }
+
+    private BusinessInvoicePaymentModelDao createInvoicePayment(final String accountKey) {
+        final BigDecimal amount = BigDecimal.ONE;
+        final String cardCountry = UUID.randomUUID().toString().substring(0, 20);
+        final String cardType = UUID.randomUUID().toString().substring(0, 20);
+        final DateTime createdDate = new DateTime(DateTimeZone.UTC);
+        final Currency currency = Currency.BRL;
+        final DateTime effectiveDate = new DateTime(DateTimeZone.UTC);
+        final UUID invoiceId = UUID.randomUUID();
+        final String paymentError = UUID.randomUUID().toString();
+        final UUID paymentId = UUID.randomUUID();
+        final String paymentMethod = UUID.randomUUID().toString().substring(0, 20);
+        final String paymentType = UUID.randomUUID().toString().substring(0, 20);
+        final String pluginName = UUID.randomUUID().toString().substring(0, 20);
+        final String processingStatus = UUID.randomUUID().toString();
+        final BigDecimal requestedAmount = BigDecimal.ZERO;
+        final DateTime updatedDate = new DateTime(DateTimeZone.UTC);
+        final String invoicePaymentType = UUID.randomUUID().toString().substring(0, 10);
+        final UUID linkedInvoicePaymentId = UUID.randomUUID();
+
+        return new BusinessInvoicePaymentModelDao(accountKey, amount,
+                                                  cardCountry, cardType, createdDate,
+                                                  currency, effectiveDate, invoiceId,
+                                                  paymentError, paymentId, paymentMethod,
+                                                  paymentType, pluginName, processingStatus,
+                                                  requestedAmount, updatedDate, invoicePaymentType,
+                                                  linkedInvoicePaymentId);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoicePaymentTagSqlDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoicePaymentTagSqlDao.java
new file mode 100644
index 0000000..6ef7ab5
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoicePaymentTagSqlDao.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteWithEmbeddedDB;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoicePaymentTagModelDao;
+
+public class TestBusinessInvoicePaymentTagSqlDao extends AnalyticsTestSuiteWithEmbeddedDB {
+
+    @Test(groups = "slow")
+    public void testCRUD() throws Exception {
+        final String paymentId = UUID.randomUUID().toString();
+        final String name = UUID.randomUUID().toString().substring(0, 20);
+
+        // Verify initial state
+        Assert.assertEquals(invoicePaymentTagSqlDao.getTagsForInvoicePayment(paymentId, internalCallContext).size(), 0);
+        Assert.assertEquals(invoicePaymentTagSqlDao.removeTag(paymentId, name, internalCallContext), 0);
+
+        // Add an entry
+        Assert.assertEquals(invoicePaymentTagSqlDao.addTag(paymentId, name, internalCallContext), 1);
+        final List<BusinessInvoicePaymentTagModelDao> tagsForInvoicePayment = invoicePaymentTagSqlDao.getTagsForInvoicePayment(paymentId, internalCallContext);
+        Assert.assertEquals(tagsForInvoicePayment.size(), 1);
+
+        // Retrieve it
+        final BusinessInvoicePaymentTagModelDao invoicePaymentTag = tagsForInvoicePayment.get(0);
+        Assert.assertEquals(invoicePaymentTag.getPaymentId().toString(), paymentId);
+        Assert.assertEquals(invoicePaymentTag.getName(), name);
+
+        // Delete it
+        Assert.assertEquals(invoicePaymentTagSqlDao.removeTag(paymentId, name, internalCallContext), 1);
+        Assert.assertEquals(invoicePaymentTagSqlDao.getTagsForInvoicePayment(paymentId, internalCallContext).size(), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testSegmentation() throws Exception {
+        final String paymentId1 = UUID.randomUUID().toString();
+        final String name1 = UUID.randomUUID().toString().substring(0, 20);
+        final String paymentId2 = UUID.randomUUID().toString();
+        final String name2 = UUID.randomUUID().toString().substring(0, 20);
+
+        // Add a tag to both invoice payments
+        Assert.assertEquals(invoicePaymentTagSqlDao.addTag(paymentId1, name1, internalCallContext), 1);
+        Assert.assertEquals(invoicePaymentTagSqlDao.addTag(paymentId2, name2, internalCallContext), 1);
+
+        Assert.assertEquals(invoicePaymentTagSqlDao.getTagsForInvoicePayment(paymentId1, internalCallContext).size(), 1);
+        Assert.assertEquals(invoicePaymentTagSqlDao.getTagsForInvoicePayment(paymentId2, internalCallContext).size(), 1);
+
+        // Remove the tag for the first invoice payment
+        Assert.assertEquals(invoicePaymentTagSqlDao.removeTag(paymentId1, name1, internalCallContext), 1);
+
+        Assert.assertEquals(invoicePaymentTagSqlDao.getTagsForInvoicePayment(paymentId1, internalCallContext).size(), 0);
+        Assert.assertEquals(invoicePaymentTagSqlDao.getTagsForInvoicePayment(paymentId2, internalCallContext).size(), 1);
+    }
+
+    @Test(groups = "slow")
+    public void testHealthCheck() throws Exception {
+        // HealthCheck test to make sure MySQL is setup properly
+        try {
+            invoicePaymentTagSqlDao.test(internalCallContext);
+        } catch (Throwable t) {
+            Assert.fail(t.toString());
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceSqlDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceSqlDao.java
new file mode 100644
index 0000000..61a7b44
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceSqlDao.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+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.osgi.bundles.analytics.AnalyticsTestSuiteWithEmbeddedDB;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceModelDao;
+
+public class TestBusinessInvoiceSqlDao extends AnalyticsTestSuiteWithEmbeddedDB {
+
+    @Test(groups = "slow")
+    public void testCRUD() throws Exception {
+        final UUID invoiceId = UUID.randomUUID();
+        final UUID accountId = UUID.randomUUID();
+        final String accountKey = UUID.randomUUID().toString();
+        final BusinessInvoiceModelDao invoice = createInvoice(accountId, invoiceId, accountKey);
+
+        // Verify initial state
+        Assert.assertNull(invoiceSqlDao.getInvoice(invoice.getInvoiceId().toString(), internalCallContext));
+        Assert.assertEquals(invoiceSqlDao.deleteInvoice(invoice.getInvoiceId().toString(), internalCallContext), 0);
+
+        // Add the invoice
+        Assert.assertEquals(invoiceSqlDao.createInvoice(invoice, internalCallContext), 1);
+
+        // Retrieve it
+        Assert.assertEquals(invoiceSqlDao.getInvoice(invoice.getInvoiceId().toString(), internalCallContext), invoice);
+        Assert.assertEquals(invoiceSqlDao.getInvoicesForAccount(invoice.getAccountId().toString(), internalCallContext).size(), 1);
+        Assert.assertEquals(invoiceSqlDao.getInvoicesForAccount(invoice.getAccountId().toString(), internalCallContext).get(0), invoice);
+
+        // Delete it
+        Assert.assertEquals(invoiceSqlDao.deleteInvoice(invoice.getInvoiceId().toString(), internalCallContext), 1);
+        Assert.assertNull(invoiceSqlDao.getInvoice(invoice.getInvoiceId().toString(), internalCallContext));
+        Assert.assertEquals(invoiceSqlDao.getInvoicesForAccount(invoice.getAccountId().toString(), internalCallContext).size(), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testSegmentation() throws Exception {
+        final UUID invoiceId1 = UUID.randomUUID();
+        final UUID accountId1 = UUID.randomUUID();
+        final String accountKey1 = UUID.randomUUID().toString();
+        final BusinessInvoiceModelDao invoice1 = createInvoice(invoiceId1, accountId1, accountKey1);
+        final UUID invoiceId2 = UUID.randomUUID();
+        final UUID accountId2 = UUID.randomUUID();
+        final String accountKey2 = UUID.randomUUID().toString();
+        final BusinessInvoiceModelDao invoice2 = createInvoice(invoiceId2, accountId2, accountKey2);
+
+        // Create both invoices
+        Assert.assertEquals(invoiceSqlDao.createInvoice(invoice1, internalCallContext), 1);
+        Assert.assertEquals(invoiceSqlDao.createInvoice(invoice2, internalCallContext), 1);
+
+        Assert.assertEquals(invoiceSqlDao.getInvoicesForAccount(accountId1.toString(), internalCallContext).size(), 1);
+        Assert.assertEquals(invoiceSqlDao.getInvoicesForAccount(accountId2.toString(), internalCallContext).size(), 1);
+
+        // Remove the first invoice
+        Assert.assertEquals(invoiceSqlDao.deleteInvoice(invoice1.getInvoiceId().toString(), internalCallContext), 1);
+
+        Assert.assertEquals(invoiceSqlDao.getInvoicesForAccount(accountId1.toString(), internalCallContext).size(), 0);
+        Assert.assertEquals(invoiceSqlDao.getInvoicesForAccount(accountId2.toString(), internalCallContext).size(), 1);
+    }
+
+    @Test(groups = "slow")
+    public void testHealthCheck() throws Exception {
+        // HealthCheck test to make sure MySQL is setup properly
+        try {
+            invoiceSqlDao.test(internalCallContext);
+        } catch (Throwable t) {
+            Assert.fail(t.toString());
+        }
+    }
+
+    private BusinessInvoiceModelDao createInvoice(final UUID invoiceId, final UUID accountId, final String accountKey) {
+        final BigDecimal amountCharged = BigDecimal.ZERO;
+        final BigDecimal amountCredited = BigDecimal.ONE;
+        final BigDecimal amountPaid = BigDecimal.TEN;
+        final BigDecimal balance = BigDecimal.valueOf(123L);
+        final DateTime createdDate = clock.getUTCNow();
+        final Currency currency = Currency.MXN;
+        final LocalDate invoiceDate = clock.getUTCToday();
+        final LocalDate targetDate = clock.getUTCToday();
+        final DateTime updatedDate = clock.getUTCNow();
+
+        return new BusinessInvoiceModelDao(accountId, accountKey, amountCharged, amountCredited, amountPaid, balance,
+                                           createdDate, currency, invoiceDate, invoiceId, 12, targetDate, updatedDate);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceTagSqlDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceTagSqlDao.java
new file mode 100644
index 0000000..06118ba
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceTagSqlDao.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteWithEmbeddedDB;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceTagModelDao;
+
+public class TestBusinessInvoiceTagSqlDao extends AnalyticsTestSuiteWithEmbeddedDB {
+
+    @Test(groups = "slow")
+    public void testCRUD() throws Exception {
+        final String invoiceId = UUID.randomUUID().toString();
+        final String name = UUID.randomUUID().toString().substring(0, 20);
+
+        // Verify initial state
+        Assert.assertEquals(invoiceTagSqlDao.getTagsForInvoice(invoiceId, internalCallContext).size(), 0);
+        Assert.assertEquals(invoiceTagSqlDao.removeTag(invoiceId, name, internalCallContext), 0);
+
+        // Add an entry
+        Assert.assertEquals(invoiceTagSqlDao.addTag(invoiceId, name, internalCallContext), 1);
+        final List<BusinessInvoiceTagModelDao> tagsForInvoice = invoiceTagSqlDao.getTagsForInvoice(invoiceId, internalCallContext);
+        Assert.assertEquals(tagsForInvoice.size(), 1);
+
+        // Retrieve it
+        final BusinessInvoiceTagModelDao invoiceTag = tagsForInvoice.get(0);
+        Assert.assertEquals(invoiceTag.getInvoiceId().toString(), invoiceId);
+        Assert.assertEquals(invoiceTag.getName(), name);
+
+        // Delete it
+        Assert.assertEquals(invoiceTagSqlDao.removeTag(invoiceId, name, internalCallContext), 1);
+        Assert.assertEquals(invoiceTagSqlDao.getTagsForInvoice(invoiceId, internalCallContext).size(), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testSegmentation() throws Exception {
+        final String invoiceId1 = UUID.randomUUID().toString();
+        final String name1 = UUID.randomUUID().toString().substring(0, 20);
+        final String invoiceId2 = UUID.randomUUID().toString();
+        final String name2 = UUID.randomUUID().toString().substring(0, 20);
+
+        // Add a tag to both invoices
+        Assert.assertEquals(invoiceTagSqlDao.addTag(invoiceId1, name1, internalCallContext), 1);
+        Assert.assertEquals(invoiceTagSqlDao.addTag(invoiceId2, name2, internalCallContext), 1);
+
+        Assert.assertEquals(invoiceTagSqlDao.getTagsForInvoice(invoiceId1, internalCallContext).size(), 1);
+        Assert.assertEquals(invoiceTagSqlDao.getTagsForInvoice(invoiceId2, internalCallContext).size(), 1);
+
+        // Remove the tag for the first invoice
+        Assert.assertEquals(invoiceTagSqlDao.removeTag(invoiceId1, name1, internalCallContext), 1);
+
+        Assert.assertEquals(invoiceTagSqlDao.getTagsForInvoice(invoiceId1, internalCallContext).size(), 0);
+        Assert.assertEquals(invoiceTagSqlDao.getTagsForInvoice(invoiceId2, internalCallContext).size(), 1);
+    }
+
+    @Test(groups = "slow")
+    public void testHealthCheck() throws Exception {
+        // HealthCheck test to make sure MySQL is setup properly
+        try {
+            invoiceTagSqlDao.test(internalCallContext);
+        } catch (Throwable t) {
+            Assert.fail(t.toString());
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessOverdueStatusSqlDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessOverdueStatusSqlDao.java
new file mode 100644
index 0000000..880fc49
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessOverdueStatusSqlDao.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteWithEmbeddedDB;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessOverdueStatusModelDao;
+
+public class TestBusinessOverdueStatusSqlDao extends AnalyticsTestSuiteWithEmbeddedDB {
+
+    @Test(groups = "slow")
+    public void testCreate() throws Exception {
+        final String accountKey = UUID.randomUUID().toString();
+        final UUID bundleId = UUID.randomUUID();
+        final String externalKey = UUID.randomUUID().toString();
+        final BusinessOverdueStatusModelDao firstOverdueStatus = createOverdueStatus(accountKey, bundleId, externalKey);
+
+        // Verify initial state
+        Assert.assertEquals(overdueStatusSqlDao.getOverdueStatusesForBundleByKey(externalKey, internalCallContext).size(), 0);
+
+        // Add the overdue status
+        Assert.assertEquals(overdueStatusSqlDao.createOverdueStatus(firstOverdueStatus, internalCallContext), 1);
+
+        // Retrieve it
+        Assert.assertEquals(overdueStatusSqlDao.getOverdueStatusesForBundleByKey(externalKey, internalCallContext).size(), 1);
+        Assert.assertEquals(overdueStatusSqlDao.getOverdueStatusesForBundleByKey(externalKey, internalCallContext).get(0), firstOverdueStatus);
+
+        // Add a second one
+        final BusinessOverdueStatusModelDao secondOverdueStatus = createOverdueStatus(accountKey, bundleId, externalKey);
+        Assert.assertEquals(overdueStatusSqlDao.createOverdueStatus(secondOverdueStatus, internalCallContext), 1);
+
+        // Retrieve both
+        Assert.assertEquals(overdueStatusSqlDao.getOverdueStatusesForBundleByKey(externalKey, internalCallContext).size(), 2);
+        Assert.assertEquals(overdueStatusSqlDao.getOverdueStatusesForBundleByKey(externalKey, internalCallContext).get(0), firstOverdueStatus);
+        Assert.assertEquals(overdueStatusSqlDao.getOverdueStatusesForBundleByKey(externalKey, internalCallContext).get(1), secondOverdueStatus);
+    }
+
+    @Test(groups = "slow")
+    public void testHealthCheck() throws Exception {
+        // HealthCheck test to make sure MySQL is setup properly
+        try {
+            overdueStatusSqlDao.test(internalCallContext);
+        } catch (Throwable t) {
+            Assert.fail(t.toString());
+        }
+    }
+
+    private BusinessOverdueStatusModelDao createOverdueStatus(final String accountKey, final UUID bundleId, final String externalKey) {
+        final DateTime endDate = new DateTime(DateTimeZone.UTC);
+        final DateTime startDate = new DateTime(DateTimeZone.UTC);
+        final String status = UUID.randomUUID().toString();
+
+        return new BusinessOverdueStatusModelDao(accountKey, bundleId, endDate, externalKey, startDate, status);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessSubscriptionTransitionFieldSqlDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessSubscriptionTransitionFieldSqlDao.java
new file mode 100644
index 0000000..c6e0618
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessSubscriptionTransitionFieldSqlDao.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteWithEmbeddedDB;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionFieldModelDao;
+
+public class TestBusinessSubscriptionTransitionFieldSqlDao extends AnalyticsTestSuiteWithEmbeddedDB {
+
+    @Test(groups = "slow")
+    public void testCRUD() throws Exception {
+        final String accountKey = UUID.randomUUID().toString();
+        final UUID bundleId = UUID.randomUUID();
+        final String externalKey = UUID.randomUUID().toString();
+        final String name = UUID.randomUUID().toString().substring(0, 30);
+        final String value = UUID.randomUUID().toString();
+
+        // Verify initial state
+        Assert.assertEquals(subscriptionTransitionFieldSqlDao.getFieldsForBusinessSubscriptionTransitionByKey(externalKey, internalCallContext).size(), 0);
+        Assert.assertEquals(subscriptionTransitionFieldSqlDao.removeField(bundleId.toString(), name, internalCallContext), 0);
+
+        // Add an entry
+        Assert.assertEquals(subscriptionTransitionFieldSqlDao.addField(accountKey, bundleId.toString(), externalKey, name, value, internalCallContext), 1);
+        final List<BusinessSubscriptionTransitionFieldModelDao> fieldsForBusinessSubscriptionTransition = subscriptionTransitionFieldSqlDao.getFieldsForBusinessSubscriptionTransitionByKey(externalKey, internalCallContext);
+        Assert.assertEquals(fieldsForBusinessSubscriptionTransition.size(), 1);
+
+        // Retrieve it
+        final BusinessSubscriptionTransitionFieldModelDao subscriptionTransitionField = fieldsForBusinessSubscriptionTransition.get(0);
+        Assert.assertEquals(subscriptionTransitionField.getBundleId(), bundleId);
+        Assert.assertEquals(subscriptionTransitionField.getExternalKey(), externalKey);
+        Assert.assertEquals(subscriptionTransitionField.getName(), name);
+        Assert.assertEquals(subscriptionTransitionField.getValue(), value);
+
+        // Delete it
+        Assert.assertEquals(subscriptionTransitionFieldSqlDao.removeField(bundleId.toString(), name, internalCallContext), 1);
+        Assert.assertEquals(subscriptionTransitionFieldSqlDao.getFieldsForBusinessSubscriptionTransitionByKey(externalKey, internalCallContext).size(), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testSegmentation() throws Exception {
+        final String accountKey = UUID.randomUUID().toString();
+        final UUID bundleId1 = UUID.randomUUID();
+        final String externalKey1 = UUID.randomUUID().toString();
+        final String name1 = UUID.randomUUID().toString().substring(0, 30);
+        final UUID bundleId2 = UUID.randomUUID();
+        final String externalKey2 = UUID.randomUUID().toString();
+        final String name2 = UUID.randomUUID().toString().substring(0, 30);
+
+        // Add a field to both transitions
+        Assert.assertEquals(subscriptionTransitionFieldSqlDao.addField(accountKey, bundleId1.toString(), externalKey1, name1, UUID.randomUUID().toString(), internalCallContext), 1);
+        Assert.assertEquals(subscriptionTransitionFieldSqlDao.addField(accountKey, bundleId2.toString(), externalKey2, name2, UUID.randomUUID().toString(), internalCallContext), 1);
+
+        Assert.assertEquals(subscriptionTransitionFieldSqlDao.getFieldsForBusinessSubscriptionTransitionByKey(externalKey1, internalCallContext).size(), 1);
+        Assert.assertEquals(subscriptionTransitionFieldSqlDao.getFieldsForBusinessSubscriptionTransitionByKey(externalKey2, internalCallContext).size(), 1);
+
+        // Remove the field for the first transition
+        Assert.assertEquals(subscriptionTransitionFieldSqlDao.removeField(bundleId1.toString(), name1, internalCallContext), 1);
+
+        Assert.assertEquals(subscriptionTransitionFieldSqlDao.getFieldsForBusinessSubscriptionTransitionByKey(externalKey1, internalCallContext).size(), 0);
+        Assert.assertEquals(subscriptionTransitionFieldSqlDao.getFieldsForBusinessSubscriptionTransitionByKey(externalKey2, internalCallContext).size(), 1);
+    }
+
+    @Test(groups = "slow")
+    public void testHealthCheck() throws Exception {
+        // HealthCheck test to make sure MySQL is setup properly
+        try {
+            subscriptionTransitionFieldSqlDao.test(internalCallContext);
+        } catch (Throwable t) {
+            Assert.fail(t.toString());
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessSubscriptionTransitionTagSqlDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessSubscriptionTransitionTagSqlDao.java
new file mode 100644
index 0000000..9681b10
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessSubscriptionTransitionTagSqlDao.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.dao;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteWithEmbeddedDB;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionTagModelDao;
+
+public class TestBusinessSubscriptionTransitionTagSqlDao extends AnalyticsTestSuiteWithEmbeddedDB {
+
+    @Test(groups = "slow")
+    public void testCRUD() throws Exception {
+        final String accountKey = UUID.randomUUID().toString();
+        final UUID bundleId = UUID.randomUUID();
+        final String externalKey = UUID.randomUUID().toString();
+        final String name = UUID.randomUUID().toString().substring(0, 20);
+
+        // Verify initial state
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.getTagsForBusinessSubscriptionTransitionByKey(externalKey, internalCallContext).size(), 0);
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.removeTag(bundleId.toString(), name, internalCallContext), 0);
+
+        // Add an entry
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.addTag(accountKey, bundleId.toString(), externalKey, name, internalCallContext), 1);
+        final List<BusinessSubscriptionTransitionTagModelDao> tagsForBusinessSubscriptionTransition = subscriptionTransitionTagSqlDao.getTagsForBusinessSubscriptionTransitionByKey(externalKey, internalCallContext);
+        Assert.assertEquals(tagsForBusinessSubscriptionTransition.size(), 1);
+
+        // Retrieve it
+        final BusinessSubscriptionTransitionTagModelDao subscriptionTransitionTag = tagsForBusinessSubscriptionTransition.get(0);
+        Assert.assertEquals(subscriptionTransitionTag.getBundleId(), bundleId);
+        Assert.assertEquals(subscriptionTransitionTag.getExternalKey(), externalKey);
+        Assert.assertEquals(subscriptionTransitionTag.getName(), name);
+
+        // Delete it
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.removeTag(bundleId.toString(), name, internalCallContext), 1);
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.getTagsForBusinessSubscriptionTransitionByKey(externalKey, internalCallContext).size(), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testSegmentation() throws Exception {
+        final String accountKey = UUID.randomUUID().toString();
+        final UUID bundleId1 = UUID.randomUUID();
+        final String externalKey1 = UUID.randomUUID().toString();
+        final String name1 = UUID.randomUUID().toString().substring(0, 20);
+        final UUID bundleId2 = UUID.randomUUID();
+        final String externalKey2 = UUID.randomUUID().toString();
+        final String name2 = UUID.randomUUID().toString().substring(0, 20);
+
+        // Add a tag to both transitions
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.addTag(accountKey, bundleId1.toString(), externalKey1, name1, internalCallContext), 1);
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.addTag(accountKey, bundleId2.toString(), externalKey2, name2, internalCallContext), 1);
+
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.getTagsForBusinessSubscriptionTransitionByKey(externalKey1, internalCallContext).size(), 1);
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.getTagsForBusinessSubscriptionTransitionByKey(externalKey2, internalCallContext).size(), 1);
+
+        // Remove the tag for the first transition
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.removeTag(bundleId1.toString(), name1, internalCallContext), 1);
+
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.getTagsForBusinessSubscriptionTransitionByKey(externalKey1, internalCallContext).size(), 0);
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.getTagsForBusinessSubscriptionTransitionByKey(externalKey2, internalCallContext).size(), 1);
+    }
+
+    @Test(groups = "slow")
+    public void testHealthCheck() throws Exception {
+        // HealthCheck test to make sure MySQL is setup properly
+        try {
+            subscriptionTransitionTagSqlDao.test(internalCallContext);
+        } catch (Throwable t) {
+            Assert.fail(t.toString());
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/glue/TestAnalyticsModule.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/glue/TestAnalyticsModule.java
new file mode 100644
index 0000000..e15543a
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/glue/TestAnalyticsModule.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.glue;
+
+import org.mockito.Mockito;
+import org.skife.config.ConfigSource;
+
+import com.ning.billing.catalog.MockCatalogModule;
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.mock.glue.MockAccountModule;
+import com.ning.billing.mock.glue.MockEntitlementModule;
+import com.ning.billing.mock.glue.MockGlobalLockerModule;
+import com.ning.billing.mock.glue.MockInvoiceModule;
+import com.ning.billing.mock.glue.MockJunctionModule;
+import com.ning.billing.mock.glue.MockOverdueModule;
+import com.ning.billing.mock.glue.MockPaymentModule;
+import com.ning.billing.osgi.bundles.analytics.setup.AnalyticsModule;
+import com.ning.billing.payment.dao.PaymentDao;
+import com.ning.billing.util.glue.AuditModule;
+import com.ning.billing.util.glue.CacheModule;
+import com.ning.billing.util.glue.CallContextModule;
+import com.ning.billing.util.glue.CustomFieldModule;
+import com.ning.billing.util.glue.NotificationQueueModule;
+import com.ning.billing.util.glue.TagStoreModule;
+
+public class TestAnalyticsModule extends AnalyticsModule {
+
+    public TestAnalyticsModule(final ConfigSource configSource) {
+        super(configSource);
+    }
+
+    @Override
+    protected void configure() {
+        super.configure();
+
+        install(new AuditModule());
+        install(new CacheModule(configSource));
+        install(new CallContextModule());
+        install(new CustomFieldModule());
+        install(new MockAccountModule());
+        install(new MockCatalogModule());
+        install(new MockEntitlementModule());
+        install(new MockInvoiceModule());
+        install(new MockJunctionModule());
+        install(new MockOverdueModule());
+        install(new MockPaymentModule());
+        install(new MockGlobalLockerModule());
+        install(new NotificationQueueModule(configSource));
+        install(new TagStoreModule());
+
+        bind(InvoiceDao.class).toInstance(Mockito.mock(InvoiceDao.class));
+        bind(PaymentDao.class).toInstance(Mockito.mock(PaymentDao.class));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/glue/TestAnalyticsModuleNoDB.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/glue/TestAnalyticsModuleNoDB.java
new file mode 100644
index 0000000..12757fd
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/glue/TestAnalyticsModuleNoDB.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.glue;
+
+import org.mockito.Mockito;
+import org.skife.config.ConfigSource;
+
+import com.ning.billing.GuicyKillbillTestNoDBModule;
+import com.ning.billing.mock.glue.MockNonEntityDaoModule;
+import com.ning.billing.osgi.bundles.analytics.MockBusinessSubscriptionTransitionSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountFieldSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountTagSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceFieldSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceItemSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoicePaymentFieldSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoicePaymentSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoicePaymentTagSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceTagSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessOverdueStatusSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionFieldSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionTagSqlDao;
+import com.ning.billing.util.bus.InMemoryBusModule;
+
+public class TestAnalyticsModuleNoDB extends TestAnalyticsModule {
+
+    public TestAnalyticsModuleNoDB(final ConfigSource configSource) {
+        super(configSource);
+    }
+
+    @Override
+    protected void installAnalyticsSqlDao() {
+        bind(BusinessSubscriptionTransitionSqlDao.class).to(MockBusinessSubscriptionTransitionSqlDao.class).asEagerSingleton();
+
+        bind(BusinessAccountSqlDao.class).toInstance(Mockito.mock(BusinessAccountSqlDao.class));
+        bind(BusinessAccountTagSqlDao.class).toInstance(Mockito.mock(BusinessAccountTagSqlDao.class));
+        bind(BusinessAccountFieldSqlDao.class).toInstance(Mockito.mock(BusinessAccountFieldSqlDao.class));
+        bind(BusinessInvoiceFieldSqlDao.class).toInstance(Mockito.mock(BusinessInvoiceFieldSqlDao.class));
+        bind(BusinessInvoiceItemSqlDao.class).toInstance(Mockito.mock(BusinessInvoiceItemSqlDao.class));
+        bind(BusinessInvoicePaymentFieldSqlDao.class).toInstance(Mockito.mock(BusinessInvoicePaymentFieldSqlDao.class));
+        bind(BusinessInvoicePaymentSqlDao.class).toInstance(Mockito.mock(BusinessInvoicePaymentSqlDao.class));
+        bind(BusinessInvoicePaymentTagSqlDao.class).toInstance(Mockito.mock(BusinessInvoicePaymentTagSqlDao.class));
+        bind(BusinessInvoiceSqlDao.class).toInstance(Mockito.mock(BusinessInvoiceSqlDao.class));
+        bind(BusinessInvoiceTagSqlDao.class).toInstance(Mockito.mock(BusinessInvoiceTagSqlDao.class));
+        bind(BusinessOverdueStatusSqlDao.class).toInstance(Mockito.mock(BusinessOverdueStatusSqlDao.class));
+        bind(BusinessSubscriptionTransitionFieldSqlDao.class).toInstance(Mockito.mock(BusinessSubscriptionTransitionFieldSqlDao.class));
+        bind(BusinessSubscriptionTransitionTagSqlDao.class).toInstance(Mockito.mock(BusinessSubscriptionTransitionTagSqlDao.class));
+    }
+
+    @Override
+    public void configure() {
+        super.configure();
+
+        install(new GuicyKillbillTestNoDBModule());
+        install(new MockNonEntityDaoModule());
+        install(new InMemoryBusModule(configSource));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/glue/TestAnalyticsModuleWithEmbeddedDB.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/glue/TestAnalyticsModuleWithEmbeddedDB.java
new file mode 100644
index 0000000..23b0dc1
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/glue/TestAnalyticsModuleWithEmbeddedDB.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.glue;
+
+import org.skife.config.ConfigSource;
+
+import com.ning.billing.GuicyKillbillTestWithEmbeddedDBModule;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.NonEntityDaoModule;
+
+public class TestAnalyticsModuleWithEmbeddedDB extends TestAnalyticsModule {
+
+    public TestAnalyticsModuleWithEmbeddedDB(final ConfigSource configSource) {
+        super(configSource);
+    }
+
+    @Override
+    public void configure() {
+        super.configure();
+
+        install(new GuicyKillbillTestWithEmbeddedDBModule());
+        install(new NonEntityDaoModule());
+        install(new BusModule(configSource));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/MockBusinessSubscriptionTransitionSqlDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/MockBusinessSubscriptionTransitionSqlDao.java
new file mode 100644
index 0000000..f9ee7bc
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/MockBusinessSubscriptionTransitionSqlDao.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionIsolationLevel;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.testng.Assert;
+
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionBinder;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionSqlDao;
+import com.ning.billing.osgi.bundles.analytics.dao.TimeSeriesTuple;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionModelDao;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.InternalTenantContextBinder;
+
+import com.google.common.collect.ImmutableList;
+
+public class MockBusinessSubscriptionTransitionSqlDao implements BusinessSubscriptionTransitionSqlDao {
+
+    private final Map<String, List<BusinessSubscriptionTransitionModelDao>> content = new HashMap<String, List<BusinessSubscriptionTransitionModelDao>>();
+    private final Map<String, String> keyForBundleId = new HashMap<String, String>();
+
+    @Override
+    public List<TimeSeriesTuple> getSubscriptionsCreatedOverTime(@Bind("product_type") final String productType,
+                                                                 @Bind("slug") final String slug,
+                                                                 @InternalTenantContextBinder final InternalTenantContext context) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public List<BusinessSubscriptionTransitionModelDao> getTransitionsByKey(@Bind("event_key") final String key,
+                                                                            @InternalTenantContextBinder final InternalTenantContext context) {
+        return content.get(key);
+    }
+
+    @Override
+    public List<BusinessSubscriptionTransitionModelDao> getTransitionForSubscription(@Bind("subscription_id") final String subscriptionId,
+                                                                                     @InternalTenantContextBinder final InternalTenantContext context) {
+        return ImmutableList.<BusinessSubscriptionTransitionModelDao>of();
+    }
+
+    @Override
+    public List<BusinessSubscriptionTransitionModelDao> getTransitionsForAccount(@Bind("account_id") final String accountId, @InternalTenantContextBinder final InternalTenantContext context) {
+        return ImmutableList.<BusinessSubscriptionTransitionModelDao>of();
+    }
+
+    @Override
+    public int createTransition(@BusinessSubscriptionTransitionBinder final BusinessSubscriptionTransitionModelDao transition,
+                                @InternalTenantContextBinder final InternalCallContext context) {
+        if (content.get(transition.getExternalKey()) == null) {
+            content.put(transition.getExternalKey(), new ArrayList<BusinessSubscriptionTransitionModelDao>());
+        }
+        content.get(transition.getExternalKey()).add(transition);
+        keyForBundleId.put(transition.getBundleId().toString(), transition.getExternalKey());
+        return 1;
+    }
+
+    @Override
+    public void deleteTransitionsForBundle(@Bind("bundle_id") final String bundleId,
+                                           @InternalTenantContextBinder final InternalCallContext context) {
+        content.put(keyForBundleId.get(bundleId), new ArrayList<BusinessSubscriptionTransitionModelDao>());
+    }
+
+    @Override
+    public void test(@InternalTenantContextBinder final InternalTenantContext context) {
+    }
+
+    @Override
+    public void begin() {
+    }
+
+    @Override
+    public void commit() {
+    }
+
+    @Override
+    public void rollback() {
+    }
+
+    @Override
+    public void checkpoint(final String name) {
+    }
+
+    @Override
+    public void release(final String name) {
+    }
+
+    @Override
+    public void rollback(final String name) {
+    }
+
+    @Override
+    public <ReturnType> ReturnType inTransaction(final Transaction<ReturnType, BusinessSubscriptionTransitionSqlDao> func) {
+        try {
+            return func.inTransaction(this, null);
+        } catch (Exception e) {
+            Assert.fail(e.toString());
+            return null;
+        }
+    }
+
+    @Override
+    public <ReturnType> ReturnType inTransaction(final TransactionIsolationLevel isolation, final Transaction<ReturnType, BusinessSubscriptionTransitionSqlDao> func) {
+        return inTransaction(func);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/MockDuration.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/MockDuration.java
new file mode 100644
index 0000000..d98fd8a
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/MockDuration.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import org.joda.time.DateTime;
+import org.joda.time.Period;
+
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.TimeUnit;
+
+public class MockDuration {
+
+    public static Duration UNLIMITED() {
+        return new Duration() {
+            @Override
+            public TimeUnit getUnit() {
+                return TimeUnit.UNLIMITED;
+            }
+
+            @Override
+            public int getNumber() {
+                return 1;
+            }
+
+            @Override
+            public DateTime addToDateTime(final DateTime dateTime) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public Period toJodaPeriod() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/MockPhase.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/MockPhase.java
new file mode 100644
index 0000000..cfd5d45
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/MockPhase.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import java.math.BigDecimal;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.catalog.api.Limit;
+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.Price;
+
+public class MockPhase implements PlanPhase {
+
+    private final PhaseType cohort;
+    private final Plan plan;
+    private final Duration duration;
+    private final double price;
+
+    public MockPhase(final PhaseType cohort, final Plan plan, final Duration duration, final double price) {
+        this.cohort = cohort;
+        this.plan = plan;
+        this.duration = duration;
+        this.price = price;
+    }
+
+    @Override
+    public InternationalPrice getRecurringPrice() {
+        return new InternationalPrice() {
+            @Override
+            public Price[] getPrices() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public BigDecimal getPrice(final Currency currency) {
+                return BigDecimal.valueOf(price);
+            }
+
+            @Override
+            public boolean isZero() {
+                return price == 0.0;
+            }
+
+        };
+    }
+
+    @Override
+    public InternationalPrice getFixedPrice() {
+        return new InternationalPrice() {
+            @Override
+            public Price[] getPrices() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public BigDecimal getPrice(final Currency currency) {
+                return BigDecimal.valueOf(price);
+            }
+
+            @Override
+            public boolean isZero() {
+                return price == 0.0;
+            }
+        };
+    }
+
+    @Override
+    public BillingPeriod getBillingPeriod() {
+        return null;
+    }
+
+    @Override
+    public String getName() {
+        if (plan == null) {
+            return null;
+        } else {
+            return plan.getName() + "-" + cohort;
+        }
+    }
+
+    @Override
+    public Plan getPlan() {
+        return plan;
+    }
+
+    @Override
+    public Duration getDuration() {
+        return duration;
+    }
+
+    @Override
+    public PhaseType getPhaseType() {
+        return cohort;
+    }
+
+    @Override
+    public Limit[] getLimits() {
+        return new Limit[0];
+    }
+
+    @Override
+    public boolean compliesWithLimits(String unit, double value) {
+        return false;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/MockProduct.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/MockProduct.java
new file mode 100644
index 0000000..7999311
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/MockProduct.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import com.ning.billing.catalog.api.Limit;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
+
+public class MockProduct implements Product {
+
+    private final String name;
+    private final String type;
+    private final ProductCategory category;
+
+    public MockProduct(final String name, final String type, final ProductCategory category) {
+        this.name = name;
+        this.type = type;
+        this.category = category;
+    }
+
+    @Override
+    public String getCatalogName() {
+        return type;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public ProductCategory getCategory() {
+        return category;
+    }
+
+    @Override
+    public Product[] getAvailable() {
+        return null;
+    }
+
+    @Override
+    public Product[] getIncluded() {
+        return null;
+    }
+
+    @Override
+    public boolean isRetired() {
+        return false;
+    }
+
+    @Override
+    public Limit[] getLimits() {
+        return new Limit[0];
+    }
+
+    @Override
+    public boolean compliesWithLimits(String unit, double value) {
+        return false;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessAccount.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessAccount.java
new file mode 100644
index 0000000..1e99805
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessAccount.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+
+public class TestBusinessAccount extends AnalyticsTestSuiteNoDB {
+
+    private BusinessAccountModelDao account;
+
+    @BeforeMethod(groups = "fast")
+    public void beforeMethod() throws Exception {
+        super.beforeMethod();
+        account = new BusinessAccountModelDao(UUID.randomUUID(), "pierre", UUID.randomUUID().toString(), BigDecimal.ONE, clock.getUTCToday(),
+                                              BigDecimal.TEN, "ERROR_NOT_ENOUGH_FUNDS", "CreditCard", "Visa", "", UUID.randomUUID().toString(),
+                                              clock.getUTCNow(), clock.getUTCNow());
+    }
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        Assert.assertSame(account, account);
+        Assert.assertEquals(account, account);
+        Assert.assertTrue(account.equals(account));
+
+        final BusinessAccountModelDao otherAccount = new BusinessAccountModelDao(UUID.randomUUID(), "pierre cardin", UUID.randomUUID().toString(),
+                                                                                 BigDecimal.ONE, clock.getUTCToday(),
+                                                                                 BigDecimal.TEN, "ERROR_NOT_ENOUGH_FUNDS", "CreditCard", "Visa",
+                                                                                 "", UUID.randomUUID().toString(), clock.getUTCNow(), clock.getUTCNow());
+        Assert.assertFalse(account.equals(otherAccount));
+    }
+
+    @Test(groups = "fast")
+    public void testDefaultBigDecimalValues() throws Exception {
+        final Account account = Mockito.mock(Account.class);
+        final BusinessAccountModelDao bac = new BusinessAccountModelDao(account);
+        Assert.assertEquals(bac.getBalance(), BigDecimal.ZERO);
+        Assert.assertEquals(bac.getTotalInvoiceBalance(), BigDecimal.ZERO);
+
+        bac.setBalance(BigDecimal.ONE);
+        bac.setTotalInvoiceBalance(BigDecimal.TEN);
+        Assert.assertEquals(bac.getBalance(), BigDecimal.ONE);
+        Assert.assertEquals(bac.getTotalInvoiceBalance(), BigDecimal.TEN);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessAccountField.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessAccountField.java
new file mode 100644
index 0000000..5fd381a
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessAccountField.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+
+public class TestBusinessAccountField extends AnalyticsTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        final UUID accountId = UUID.randomUUID();
+        final String accountKey = UUID.randomUUID().toString();
+        final String name = UUID.randomUUID().toString();
+        final String value = UUID.randomUUID().toString();
+        final BusinessAccountFieldModelDao accountField = new BusinessAccountFieldModelDao(accountId,
+                                                                                           accountKey,
+                                                                                           name,
+                                                                                           value);
+        Assert.assertSame(accountField, accountField);
+        Assert.assertEquals(accountField, accountField);
+        Assert.assertTrue(accountField.equals(accountField));
+        Assert.assertEquals(accountField.getAccountId(), accountId);
+        Assert.assertEquals(accountField.getAccountKey(), accountKey);
+        Assert.assertEquals(accountField.getName(), name);
+        Assert.assertEquals(accountField.getValue(), value);
+
+        final BusinessAccountFieldModelDao otherAccountField = new BusinessAccountFieldModelDao(UUID.randomUUID(),
+                                                                                                UUID.randomUUID().toString(),
+                                                                                                UUID.randomUUID().toString(),
+                                                                                                UUID.randomUUID().toString());
+        Assert.assertFalse(accountField.equals(otherAccountField));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessAccountTag.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessAccountTag.java
new file mode 100644
index 0000000..2691132
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessAccountTag.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+
+public class TestBusinessAccountTag extends AnalyticsTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        final UUID accountId = UUID.randomUUID();
+        final String accountKey = UUID.randomUUID().toString();
+        final String name = UUID.randomUUID().toString();
+        final BusinessAccountTagModelDao accountTag = new BusinessAccountTagModelDao(accountId, accountKey, name);
+        Assert.assertSame(accountTag, accountTag);
+        Assert.assertEquals(accountTag, accountTag);
+        Assert.assertTrue(accountTag.equals(accountTag));
+        Assert.assertEquals(accountTag.getAccountId(), accountId);
+        Assert.assertEquals(accountTag.getAccountKey(), accountKey);
+        Assert.assertEquals(accountTag.getName(), name);
+
+        final BusinessAccountTagModelDao otherAccountTag = new BusinessAccountTagModelDao(UUID.randomUUID(), UUID.randomUUID().toString(), UUID.randomUUID().toString());
+        Assert.assertFalse(accountTag.equals(otherAccountTag));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoice.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoice.java
new file mode 100644
index 0000000..c2cb8c7
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoice.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+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.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+
+public class TestBusinessInvoice extends AnalyticsTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        final UUID accountId = UUID.randomUUID();
+        final String accountKey = UUID.randomUUID().toString();
+        final BigDecimal amountCharged = BigDecimal.ZERO;
+        final BigDecimal amountCredited = BigDecimal.ONE;
+        final BigDecimal amountPaid = BigDecimal.TEN;
+        final BigDecimal balance = BigDecimal.valueOf(123L);
+        final DateTime createdDate = clock.getUTCNow();
+        final Currency currency = Currency.MXN;
+        final LocalDate invoiceDate = clock.getUTCToday();
+        final UUID invoiceId = UUID.randomUUID();
+        final Integer invoiceNumber = 15;
+        final LocalDate targetDate = clock.getUTCToday();
+        final DateTime updatedDate = clock.getUTCNow();
+        final BusinessInvoiceModelDao invoice = new BusinessInvoiceModelDao(accountId, accountKey, amountCharged, amountCredited, amountPaid, balance,
+                                                                            createdDate, currency, invoiceDate, invoiceId, invoiceNumber, targetDate, updatedDate);
+        Assert.assertSame(invoice, invoice);
+        Assert.assertEquals(invoice, invoice);
+        Assert.assertTrue(invoice.equals(invoice));
+        Assert.assertEquals(invoice.getAccountId(), accountId);
+        Assert.assertEquals(invoice.getAccountKey(), accountKey);
+        Assert.assertEquals(invoice.getAmountCharged(), amountCharged);
+        Assert.assertEquals(invoice.getAmountCredited(), amountCredited);
+        Assert.assertEquals(invoice.getAmountPaid(), amountPaid);
+        Assert.assertEquals(invoice.getBalance(), balance);
+        Assert.assertEquals(invoice.getCreatedDate(), createdDate);
+        Assert.assertEquals(invoice.getCurrency(), currency);
+        Assert.assertEquals(invoice.getInvoiceDate(), invoiceDate);
+        Assert.assertEquals(invoice.getInvoiceId(), invoiceId);
+        Assert.assertEquals(invoice.getInvoiceNumber(), invoiceNumber);
+        Assert.assertEquals(invoice.getTargetDate(), targetDate);
+        Assert.assertEquals(invoice.getUpdatedDate(), updatedDate);
+
+        final BusinessInvoiceModelDao otherInvoice = new BusinessInvoiceModelDao(null, null, null, null, null, null, createdDate, null,
+                                                                                 null, invoiceId, 0, null, null);
+        Assert.assertFalse(invoice.equals(otherInvoice));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoiceField.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoiceField.java
new file mode 100644
index 0000000..6287f82
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoiceField.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+
+public class TestBusinessInvoiceField extends AnalyticsTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        final UUID invoiceId = UUID.randomUUID();
+        final String name = UUID.randomUUID().toString();
+        final String value = UUID.randomUUID().toString();
+        final BusinessInvoiceFieldModelDao invoiceField = new BusinessInvoiceFieldModelDao(invoiceId,
+                                                                                           name,
+                                                                                           value);
+        Assert.assertSame(invoiceField, invoiceField);
+        Assert.assertEquals(invoiceField, invoiceField);
+        Assert.assertTrue(invoiceField.equals(invoiceField));
+        Assert.assertEquals(invoiceField.getInvoiceId(), invoiceId);
+        Assert.assertEquals(invoiceField.getName(), name);
+        Assert.assertEquals(invoiceField.getValue(), value);
+
+        final BusinessInvoiceFieldModelDao otherInvoiceField = new BusinessInvoiceFieldModelDao(UUID.randomUUID(),
+                                                                                                UUID.randomUUID().toString(),
+                                                                                                UUID.randomUUID().toString());
+        Assert.assertFalse(invoiceField.equals(otherInvoiceField));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoiceItem.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoiceItem.java
new file mode 100644
index 0000000..5c7663e
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoiceItem.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+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.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+
+public class TestBusinessInvoiceItem extends AnalyticsTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        final BigDecimal amount = BigDecimal.TEN;
+        final String billingPeriod = UUID.randomUUID().toString();
+        final DateTime createdDate = clock.getUTCNow();
+        final Currency currency = Currency.AUD;
+        final LocalDate endDate = clock.getUTCToday();
+        final String externalKey = UUID.randomUUID().toString();
+        final UUID invoiceId = UUID.randomUUID();
+        final UUID itemId = UUID.randomUUID();
+        final UUID linkedItemId = UUID.randomUUID();
+        final String itemType = UUID.randomUUID().toString();
+        final String phase = UUID.randomUUID().toString();
+        final String productCategory = UUID.randomUUID().toString();
+        final String productName = UUID.randomUUID().toString();
+        final String productType = UUID.randomUUID().toString();
+        final String slug = UUID.randomUUID().toString();
+        final LocalDate startDate = clock.getUTCToday();
+        final DateTime updatedDate = clock.getUTCNow();
+        final BusinessInvoiceItemModelDao invoiceItem = new BusinessInvoiceItemModelDao(amount, billingPeriod, createdDate, currency,
+                                                                                        endDate, externalKey, invoiceId, itemId, linkedItemId,
+                                                                                        itemType, phase, productCategory, productName, productType,
+                                                                                        slug, startDate, updatedDate);
+        Assert.assertSame(invoiceItem, invoiceItem);
+        Assert.assertEquals(invoiceItem, invoiceItem);
+        Assert.assertTrue(invoiceItem.equals(invoiceItem));
+        Assert.assertEquals(invoiceItem.getAmount(), amount);
+        Assert.assertEquals(invoiceItem.getBillingPeriod(), billingPeriod);
+        Assert.assertEquals(invoiceItem.getCreatedDate(), createdDate);
+        Assert.assertEquals(invoiceItem.getCurrency(), currency);
+        Assert.assertEquals(invoiceItem.getEndDate(), endDate);
+        Assert.assertEquals(invoiceItem.getExternalKey(), externalKey);
+        Assert.assertEquals(invoiceItem.getInvoiceId(), invoiceId);
+        Assert.assertEquals(invoiceItem.getItemId(), itemId);
+        Assert.assertEquals(invoiceItem.getItemType(), itemType);
+        Assert.assertEquals(invoiceItem.getLinkedItemId(), linkedItemId);
+        Assert.assertEquals(invoiceItem.getPhase(), phase);
+        Assert.assertEquals(invoiceItem.getProductCategory(), productCategory);
+        Assert.assertEquals(invoiceItem.getProductName(), productName);
+        Assert.assertEquals(invoiceItem.getProductType(), productType);
+        Assert.assertEquals(invoiceItem.getSlug(), slug);
+        Assert.assertEquals(invoiceItem.getStartDate(), startDate);
+        Assert.assertEquals(invoiceItem.getUpdatedDate(), updatedDate);
+
+        final BusinessInvoiceItemModelDao otherInvoiceItem = new BusinessInvoiceItemModelDao(null, null, createdDate, null, null, null, null, itemId,
+                                                                                             linkedItemId, null, null, null, null, null, null, null, null);
+        Assert.assertFalse(invoiceItem.equals(otherInvoiceItem));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoicePayment.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoicePayment.java
new file mode 100644
index 0000000..b609d54
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoicePayment.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+
+public class TestBusinessInvoicePayment extends AnalyticsTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        final String accountKey = UUID.randomUUID().toString();
+        final BigDecimal amount = BigDecimal.ONE;
+        final String cardCountry = UUID.randomUUID().toString();
+        final String cardType = UUID.randomUUID().toString();
+        final DateTime createdDate = new DateTime(DateTimeZone.UTC);
+        final Currency currency = Currency.BRL;
+        final DateTime effectiveDate = new DateTime(DateTimeZone.UTC);
+        final UUID invoiceId = UUID.randomUUID();
+        final String paymentError = UUID.randomUUID().toString();
+        final UUID paymentId = UUID.randomUUID();
+        final String paymentMethod = UUID.randomUUID().toString();
+        final String paymentType = UUID.randomUUID().toString();
+        final String pluginName = UUID.randomUUID().toString();
+        final String processingStatus = UUID.randomUUID().toString();
+        final BigDecimal requestedAmount = BigDecimal.ZERO;
+        final DateTime updatedDate = new DateTime(DateTimeZone.UTC);
+        final String invoicePaymentType = UUID.randomUUID().toString();
+        final UUID linkedInvoicePaymentId = UUID.randomUUID();
+        final BusinessInvoicePaymentModelDao invoicePayment = new BusinessInvoicePaymentModelDao(accountKey, amount,
+                                                                                                 cardCountry, cardType, createdDate,
+                                                                                                 currency, effectiveDate, invoiceId,
+                                                                                                 paymentError, paymentId, paymentMethod,
+                                                                                                 paymentType, pluginName, processingStatus,
+                                                                                                 requestedAmount, updatedDate, invoicePaymentType,
+                                                                                                 linkedInvoicePaymentId);
+        Assert.assertSame(invoicePayment, invoicePayment);
+        Assert.assertEquals(invoicePayment, invoicePayment);
+        Assert.assertTrue(invoicePayment.equals(invoicePayment));
+        Assert.assertEquals(invoicePayment.getAccountKey(), accountKey);
+        Assert.assertEquals(invoicePayment.getAmount(), amount);
+        Assert.assertEquals(invoicePayment.getCardCountry(), cardCountry);
+        Assert.assertEquals(invoicePayment.getCardType(), cardType);
+        Assert.assertEquals(invoicePayment.getCreatedDate(), createdDate);
+        Assert.assertEquals(invoicePayment.getCurrency(), currency);
+        Assert.assertEquals(invoicePayment.getEffectiveDate(), effectiveDate);
+        Assert.assertEquals(invoicePayment.getInvoiceId(), invoiceId);
+        Assert.assertEquals(invoicePayment.getPaymentError(), paymentError);
+        Assert.assertEquals(invoicePayment.getPaymentId(), paymentId);
+        Assert.assertEquals(invoicePayment.getPaymentMethod(), paymentMethod);
+        Assert.assertEquals(invoicePayment.getPaymentType(), paymentType);
+        Assert.assertEquals(invoicePayment.getPluginName(), pluginName);
+        Assert.assertEquals(invoicePayment.getProcessingStatus(), processingStatus);
+        Assert.assertEquals(invoicePayment.getRequestedAmount(), requestedAmount);
+        Assert.assertEquals(invoicePayment.getUpdatedDate(), updatedDate);
+        Assert.assertEquals(invoicePayment.getInvoicePaymentType(), invoicePaymentType);
+        Assert.assertEquals(invoicePayment.getLinkedInvoicePaymentId(), linkedInvoicePaymentId);
+
+        final BusinessInvoicePaymentModelDao otherInvoicePayment = new BusinessInvoicePaymentModelDao(null, null, null, null, createdDate,
+                                                                                                      null, null, null, null, paymentId, null,
+                                                                                                      null, null, null, null, null, null, null);
+        Assert.assertFalse(invoicePayment.equals(otherInvoicePayment));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoicePaymentField.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoicePaymentField.java
new file mode 100644
index 0000000..5db7895
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoicePaymentField.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+
+public class TestBusinessInvoicePaymentField extends AnalyticsTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        final UUID paymentId = UUID.randomUUID();
+        final String name = UUID.randomUUID().toString();
+        final String value = UUID.randomUUID().toString();
+        final BusinessInvoicePaymentFieldModelDao invoiceField = new BusinessInvoicePaymentFieldModelDao(paymentId,
+                                                                                                         name,
+                                                                                                         value);
+        Assert.assertSame(invoiceField, invoiceField);
+        Assert.assertEquals(invoiceField, invoiceField);
+        Assert.assertTrue(invoiceField.equals(invoiceField));
+        Assert.assertEquals(invoiceField.getPaymentId(), paymentId);
+        Assert.assertEquals(invoiceField.getName(), name);
+        Assert.assertEquals(invoiceField.getValue(), value);
+
+        final BusinessInvoicePaymentFieldModelDao otherInvoicePaymentField = new BusinessInvoicePaymentFieldModelDao(UUID.randomUUID(),
+                                                                                                                     UUID.randomUUID().toString(),
+                                                                                                                     UUID.randomUUID().toString());
+        Assert.assertFalse(invoiceField.equals(otherInvoicePaymentField));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoicePaymentTag.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoicePaymentTag.java
new file mode 100644
index 0000000..9336ffe
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoicePaymentTag.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+
+public class TestBusinessInvoicePaymentTag extends AnalyticsTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        final UUID paymentId = UUID.randomUUID();
+        final String name = UUID.randomUUID().toString();
+        final BusinessInvoicePaymentTagModelDao invoicePaymentTag = new BusinessInvoicePaymentTagModelDao(paymentId, name);
+        Assert.assertSame(invoicePaymentTag, invoicePaymentTag);
+        Assert.assertEquals(invoicePaymentTag, invoicePaymentTag);
+        Assert.assertTrue(invoicePaymentTag.equals(invoicePaymentTag));
+        Assert.assertEquals(invoicePaymentTag.getPaymentId(), paymentId);
+        Assert.assertEquals(invoicePaymentTag.getName(), name);
+
+        final BusinessInvoicePaymentTagModelDao otherInvoicePaymentTag = new BusinessInvoicePaymentTagModelDao(UUID.randomUUID(),
+                                                                                                               UUID.randomUUID().toString());
+        Assert.assertFalse(invoicePaymentTag.equals(otherInvoicePaymentTag));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoiceTag.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoiceTag.java
new file mode 100644
index 0000000..91fc4d3
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessInvoiceTag.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+
+public class TestBusinessInvoiceTag extends AnalyticsTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        final UUID invoiceId = UUID.randomUUID();
+        final String name = UUID.randomUUID().toString();
+        final BusinessInvoiceTagModelDao invoiceTag = new BusinessInvoiceTagModelDao(invoiceId, name);
+        Assert.assertSame(invoiceTag, invoiceTag);
+        Assert.assertEquals(invoiceTag, invoiceTag);
+        Assert.assertTrue(invoiceTag.equals(invoiceTag));
+        Assert.assertEquals(invoiceTag.getInvoiceId(), invoiceId);
+        Assert.assertEquals(invoiceTag.getName(), name);
+
+        final BusinessInvoiceTagModelDao otherInvoiceTag = new BusinessInvoiceTagModelDao(UUID.randomUUID(), UUID.randomUUID().toString());
+        Assert.assertFalse(invoiceTag.equals(otherInvoiceTag));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessOverdueStatus.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessOverdueStatus.java
new file mode 100644
index 0000000..c813681
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessOverdueStatus.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+
+public class TestBusinessOverdueStatus extends AnalyticsTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        final String accountKey = UUID.randomUUID().toString();
+        final UUID bundleId = UUID.randomUUID();
+        final DateTime endDate = new DateTime(DateTimeZone.UTC);
+        final String externalKey = UUID.randomUUID().toString();
+        final DateTime startDate = new DateTime(DateTimeZone.UTC);
+        final String status = UUID.randomUUID().toString();
+        final BusinessOverdueStatusModelDao overdueStatus = new BusinessOverdueStatusModelDao(accountKey, bundleId, endDate, externalKey, startDate, status);
+        Assert.assertSame(overdueStatus, overdueStatus);
+        Assert.assertEquals(overdueStatus, overdueStatus);
+        Assert.assertTrue(overdueStatus.equals(overdueStatus));
+        Assert.assertEquals(overdueStatus.getAccountKey(), accountKey);
+        Assert.assertEquals(overdueStatus.getBundleId(), bundleId);
+        Assert.assertEquals(overdueStatus.getEndDate(), endDate);
+        Assert.assertEquals(overdueStatus.getExternalKey(), externalKey);
+        Assert.assertEquals(overdueStatus.getStartDate(), startDate);
+        Assert.assertEquals(overdueStatus.getStatus(), status);
+
+        final BusinessOverdueStatusModelDao otherOverdueStatus = new BusinessOverdueStatusModelDao(UUID.randomUUID().toString(),
+                                                                                                   UUID.randomUUID(),
+                                                                                                   new DateTime(DateTimeZone.UTC),
+                                                                                                   UUID.randomUUID().toString(),
+                                                                                                   new DateTime(DateTimeZone.UTC),
+                                                                                                   UUID.randomUUID().toString());
+        Assert.assertFalse(overdueStatus.equals(otherOverdueStatus));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessSubscription.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessSubscription.java
new file mode 100644
index 0000000..869b3d4
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessSubscription.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.math.BigDecimal;
+
+import org.joda.time.DateTime;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Catalog;
+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;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.mock.MockPlan;
+import com.ning.billing.mock.MockSubscription;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+import com.ning.billing.osgi.bundles.analytics.MockDuration;
+import com.ning.billing.osgi.bundles.analytics.MockPhase;
+import com.ning.billing.osgi.bundles.analytics.MockProduct;
+
+import static com.ning.billing.catalog.api.Currency.USD;
+
+public class TestBusinessSubscription extends AnalyticsTestSuiteNoDB {
+
+    final Object[][] catalogMapping = {
+            {BillingPeriod.NO_BILLING_PERIOD, 369.9500, 0.0000},
+            {BillingPeriod.NO_BILLING_PERIOD, 429.9500, 0.0000},
+            {BillingPeriod.NO_BILLING_PERIOD, 999.9500, 0.0000},
+            {BillingPeriod.NO_BILLING_PERIOD, 2300.0000, 0.0000},
+            {BillingPeriod.MONTHLY, 2.9500, 2.9500},
+            {BillingPeriod.MONTHLY, 3.9500, 3.9500},
+            {BillingPeriod.MONTHLY, 6.9500, 6.9500},
+            {BillingPeriod.MONTHLY, 7.0000, 7.0000},
+            {BillingPeriod.MONTHLY, 7.9500, 7.9500},
+            {BillingPeriod.MONTHLY, 9.0000, 9.0000},
+            {BillingPeriod.MONTHLY, 9.9500, 9.9500},
+            {BillingPeriod.MONTHLY, 11.9500, 11.9500},
+            {BillingPeriod.MONTHLY, 12.4500, 12.4500},
+            {BillingPeriod.MONTHLY, 12.9500, 12.9500},
+            {BillingPeriod.MONTHLY, 14.9500, 14.9500},
+            {BillingPeriod.MONTHLY, 15.0000, 15.0000},
+            {BillingPeriod.MONTHLY, 16.9500, 16.9500},
+            {BillingPeriod.MONTHLY, 19.0000, 19.0000},
+            {BillingPeriod.MONTHLY, 19.9500, 19.9500},
+            {BillingPeriod.MONTHLY, 24.9500, 24.9500},
+            {BillingPeriod.MONTHLY, 29.0000, 29.0000},
+            {BillingPeriod.MONTHLY, 29.9500, 29.9500},
+            {BillingPeriod.MONTHLY, 31.0000, 31.0000},
+            {BillingPeriod.MONTHLY, 34.9500, 34.9500},
+            {BillingPeriod.MONTHLY, 39.0000, 39.0000},
+            {BillingPeriod.MONTHLY, 39.9500, 39.9500},
+            {BillingPeriod.MONTHLY, 49.0000, 49.0000},
+            {BillingPeriod.MONTHLY, 49.9500, 49.9500},
+            {BillingPeriod.MONTHLY, 59.9500, 59.9500},
+            {BillingPeriod.MONTHLY, 79.0000, 79.0000},
+            {BillingPeriod.MONTHLY, 99.0000, 99.0000},
+            {BillingPeriod.MONTHLY, 139.0000, 139.0000},
+            {BillingPeriod.MONTHLY, 209.0000, 209.0000},
+            {BillingPeriod.MONTHLY, 229.0000, 229.0000},
+            {BillingPeriod.MONTHLY, 274.5000, 274.5000},
+            {BillingPeriod.MONTHLY, 549.0000, 549.0000},
+            {BillingPeriod.ANNUAL, 18.2900, 1.5242},
+            {BillingPeriod.ANNUAL, 19.9500, 1.6625},
+            {BillingPeriod.ANNUAL, 29.9500, 2.4958},
+            {BillingPeriod.ANNUAL, 49.0000, 4.0833},
+            {BillingPeriod.ANNUAL, 59.0000, 4.9167},
+            {BillingPeriod.ANNUAL, 149.9500, 12.4958},
+            {BillingPeriod.ANNUAL, 159.9500, 13.3292},
+            {BillingPeriod.ANNUAL, 169.9500, 14.1625},
+            {BillingPeriod.ANNUAL, 183.2900, 15.2742},
+            {BillingPeriod.ANNUAL, 199.9500, 16.6625},
+            {BillingPeriod.ANNUAL, 219.9500, 18.3292},
+            {BillingPeriod.ANNUAL, 239.9000, 19.9917},
+            {BillingPeriod.ANNUAL, 249.9500, 20.8292},
+            {BillingPeriod.ANNUAL, 319.0000, 26.5833},
+            {BillingPeriod.ANNUAL, 349.9500, 29.1625},
+            {BillingPeriod.ANNUAL, 399.0000, 33.2500},
+            {BillingPeriod.ANNUAL, 399.9500, 33.3292},
+            {BillingPeriod.ANNUAL, 458.2900, 38.1908},
+            {BillingPeriod.ANNUAL, 499.9500, 41.6625},
+            {BillingPeriod.ANNUAL, 549.9500, 45.8292},
+            {BillingPeriod.ANNUAL, 599.9000, 49.9917},
+            {BillingPeriod.ANNUAL, 599.9500, 49.9958},
+            {BillingPeriod.ANNUAL, 624.9500, 52.0792},
+            {BillingPeriod.ANNUAL, 799.0000, 66.5833},
+            {BillingPeriod.ANNUAL, 999.0000, 83.2500},
+            {BillingPeriod.ANNUAL, 2299.0000, 191.5833},
+            {BillingPeriod.ANNUAL, 5499.0000, 458.2500}};
+
+    private Product product;
+    private Plan plan;
+    private PlanPhase phase;
+    private Subscription isubscription;
+    private BusinessSubscription subscription;
+
+    private final Catalog catalog = Mockito.mock(Catalog.class);
+
+    @Override
+    @BeforeMethod(groups = "fast")
+    public void beforeMethod() throws Exception {
+        super.beforeMethod();
+        product = new MockProduct("platinium", "subscription", ProductCategory.BASE);
+        plan = new MockPlan("platinum-monthly", product);
+        phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
+
+        Mockito.when(catalog.findPlan(Mockito.anyString(), Mockito.<DateTime>any())).thenReturn(plan);
+        Mockito.when(catalog.findPlan(Mockito.anyString(), Mockito.<DateTime>any(), Mockito.<DateTime>any())).thenReturn(plan);
+        Mockito.when(catalog.findPhase(Mockito.anyString(), Mockito.<DateTime>any(), Mockito.<DateTime>any())).thenReturn(phase);
+        Mockito.when(catalogService.getFullCatalog()).thenReturn(catalog);
+
+        isubscription = new MockSubscription(Subscription.SubscriptionState.ACTIVE, plan, phase);
+        subscription = new BusinessSubscription(isubscription, USD, catalog);
+    }
+
+    @Test(groups = "fast")
+    public void testMrrComputation() throws Exception {
+        int i = 0;
+        for (final Object[] object : catalogMapping) {
+            final BillingPeriod billingPeriod = (BillingPeriod) object[0];
+            final double price = (Double) object[1];
+            final double expectedMrr = (Double) object[2];
+
+            final BigDecimal computedMrr = BusinessSubscription.getMrrFromBillingPeriod(billingPeriod, BigDecimal.valueOf(price));
+            Assert.assertEquals(computedMrr.doubleValue(), expectedMrr, "Invalid mrr for product #" + i);
+            i++;
+        }
+    }
+
+    @Test(groups = "fast")
+    public void testConstructor() throws Exception {
+        Assert.assertEquals(subscription.getRoundedMrr(), 0.0);
+        Assert.assertEquals(subscription.getSlug(), phase.getName());
+        Assert.assertEquals(subscription.getPhase(), phase.getPhaseType().toString());
+        Assert.assertEquals(subscription.getBillingPeriod(), phase.getBillingPeriod());
+        Assert.assertEquals(subscription.getPrice(), phase.getRecurringPrice().getPrice(null));
+        Assert.assertEquals(subscription.getProductCategory(), product.getCategory());
+        Assert.assertEquals(subscription.getProductName(), product.getName());
+        Assert.assertEquals(subscription.getProductType(), product.getCatalogName());
+        Assert.assertEquals(subscription.getStartDate(), isubscription.getStartDate());
+    }
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        Assert.assertSame(subscription, subscription);
+        Assert.assertEquals(subscription, subscription);
+        Assert.assertTrue(subscription.equals(subscription));
+
+        final Subscription otherSubscription = new MockSubscription(Subscription.SubscriptionState.CANCELLED, plan, phase);
+        Assert.assertTrue(!subscription.equals(new BusinessSubscription(otherSubscription, USD, catalog)));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessSubscriptionEvent.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessSubscriptionEvent.java
new file mode 100644
index 0000000..1dbf626
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessSubscriptionEvent.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import org.joda.time.DateTime;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Catalog;
+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;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.mock.MockPlan;
+import com.ning.billing.mock.MockSubscription;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+import com.ning.billing.osgi.bundles.analytics.MockDuration;
+import com.ning.billing.osgi.bundles.analytics.MockPhase;
+import com.ning.billing.osgi.bundles.analytics.MockProduct;
+
+public class TestBusinessSubscriptionEvent extends AnalyticsTestSuiteNoDB {
+
+    private Product product;
+    private Plan plan;
+    private PlanPhase phase;
+    private Subscription subscription;
+
+    private final Catalog catalog = Mockito.mock(Catalog.class);
+
+    @Override
+    @BeforeMethod(groups = "fast")
+    public void beforeMethod() throws Exception {
+        super.beforeMethod();
+        product = new MockProduct("platinium", "subscription", ProductCategory.BASE);
+        plan = new MockPlan("platinum-monthly", product);
+        phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
+
+        Mockito.when(catalog.findPlan(Mockito.anyString(), Mockito.<DateTime>any())).thenReturn(plan);
+        Mockito.when(catalog.findPlan(Mockito.anyString(), Mockito.<DateTime>any(), Mockito.<DateTime>any())).thenReturn(plan);
+        Mockito.when(catalog.findPhase(Mockito.anyString(), Mockito.<DateTime>any(), Mockito.<DateTime>any())).thenReturn(phase);
+        Mockito.when(catalogService.getFullCatalog()).thenReturn(catalog);
+
+        subscription = new MockSubscription(Subscription.SubscriptionState.ACTIVE, plan, phase);
+    }
+
+    @Test(groups = "fast")
+    public void testValueOf() throws Exception {
+        BusinessSubscriptionEvent event;
+
+        event = BusinessSubscriptionEvent.valueOf("ADD_ADD_ON");
+        Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.ADD);
+        Assert.assertEquals(event.getCategory(), ProductCategory.ADD_ON);
+
+        event = BusinessSubscriptionEvent.valueOf("CANCEL_BASE");
+        Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.CANCEL);
+        Assert.assertEquals(event.getCategory(), ProductCategory.BASE);
+
+        event = BusinessSubscriptionEvent.valueOf("SYSTEM_CANCEL_ADD_ON");
+        Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.SYSTEM_CANCEL);
+        Assert.assertEquals(event.getCategory(), ProductCategory.ADD_ON);
+    }
+
+    @Test(groups = "fast")
+    public void testFromSubscription() throws Exception {
+        BusinessSubscriptionEvent event;
+
+        final DateTime now = new DateTime();
+
+        event = BusinessSubscriptionEvent.subscriptionCreated(subscription.getCurrentPlan().getName(), catalog, now, now);
+        Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.ADD);
+        Assert.assertEquals(event.getCategory(), product.getCategory());
+        Assert.assertEquals(event.toString(), "ADD_BASE");
+
+        event = BusinessSubscriptionEvent.subscriptionCancelled(subscription.getCurrentPlan().getName(), catalog, now, now);
+        Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.CANCEL);
+        Assert.assertEquals(event.getCategory(), product.getCategory());
+        Assert.assertEquals(event.toString(), "CANCEL_BASE");
+
+        event = BusinessSubscriptionEvent.subscriptionChanged(subscription.getCurrentPlan().getName(), catalog, now, now);
+        Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.CHANGE);
+        Assert.assertEquals(event.getCategory(), product.getCategory());
+        Assert.assertEquals(event.toString(), "CHANGE_BASE");
+
+        event = BusinessSubscriptionEvent.subscriptionPhaseChanged(subscription.getCurrentPlan().getName(), subscription.getState(), catalog, now, now);
+        // The subscription is still active, it's a system change
+        Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.SYSTEM_CHANGE);
+        Assert.assertEquals(event.getCategory(), product.getCategory());
+        Assert.assertEquals(event.toString(), "SYSTEM_CHANGE_BASE");
+
+        subscription = new MockSubscription(Subscription.SubscriptionState.CANCELLED, plan, phase);
+        event = BusinessSubscriptionEvent.subscriptionPhaseChanged(subscription.getCurrentPlan().getName(), subscription.getState(), catalog, now, now);
+        // The subscription is cancelled, it's a system cancellation
+        Assert.assertEquals(event.getEventType(), BusinessSubscriptionEvent.EventType.SYSTEM_CANCEL);
+        Assert.assertEquals(event.getCategory(), product.getCategory());
+        Assert.assertEquals(event.toString(), "SYSTEM_CANCEL_BASE");
+    }
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        final DateTime now = new DateTime();
+        final BusinessSubscriptionEvent event = BusinessSubscriptionEvent.subscriptionChanged(subscription.getCurrentPlan().getName(), catalog, now, now);
+        Assert.assertSame(event, event);
+        Assert.assertEquals(event, event);
+        Assert.assertTrue(event.equals(event));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessSubscriptionTransition.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessSubscriptionTransition.java
new file mode 100644
index 0000000..9bc0d4a
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessSubscriptionTransition.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Catalog;
+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;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.mock.MockPlan;
+import com.ning.billing.mock.MockSubscription;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+import com.ning.billing.osgi.bundles.analytics.MockDuration;
+import com.ning.billing.osgi.bundles.analytics.MockPhase;
+import com.ning.billing.osgi.bundles.analytics.MockProduct;
+
+import static com.ning.billing.catalog.api.Currency.USD;
+
+public class TestBusinessSubscriptionTransition extends AnalyticsTestSuiteNoDB {
+
+    private BusinessSubscription prevSubscription;
+    private BusinessSubscription nextSubscription;
+    private BusinessSubscriptionEvent event;
+    private DateTime requestedTimestamp;
+    private Long totalOrdering;
+    private UUID bundleId;
+    private String externalKey;
+    private UUID accountId;
+    private String accountKey;
+    private UUID subscriptionId;
+    private BusinessSubscriptionTransitionModelDao transition;
+
+    private final Catalog catalog = Mockito.mock(Catalog.class);
+
+    @Override
+    @BeforeMethod(groups = "fast")
+    public void beforeMethod() throws Exception {
+        super.beforeMethod();
+        final Product product = new MockProduct("platinium", "subscription", ProductCategory.BASE);
+        final Plan plan = new MockPlan("platinum-monthly", product);
+        final PlanPhase phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
+        final Subscription prevISubscription = new MockSubscription(Subscription.SubscriptionState.ACTIVE, plan, phase);
+        final Subscription nextISubscription = new MockSubscription(Subscription.SubscriptionState.CANCELLED, plan, phase);
+
+        Mockito.when(catalog.findPlan(Mockito.anyString(), Mockito.<DateTime>any())).thenReturn(plan);
+        Mockito.when(catalog.findPhase(Mockito.anyString(), Mockito.<DateTime>any(), Mockito.<DateTime>any())).thenReturn(phase);
+        Mockito.when(catalogService.getFullCatalog()).thenReturn(catalog);
+
+        final DateTime now = new DateTime();
+
+        prevSubscription = new BusinessSubscription(prevISubscription, USD, catalog);
+        nextSubscription = new BusinessSubscription(nextISubscription, USD, catalog);
+        event = BusinessSubscriptionEvent.subscriptionCancelled(prevISubscription.getCurrentPlan().getName(), catalog, now, now);
+        requestedTimestamp = new DateTime(DateTimeZone.UTC);
+        totalOrdering = 12L;
+        bundleId = UUID.randomUUID();
+        externalKey = "1234";
+        accountId = UUID.randomUUID();
+        accountKey = "pierre-1234";
+        subscriptionId = UUID.randomUUID();
+        transition = new BusinessSubscriptionTransitionModelDao(totalOrdering, bundleId, externalKey, accountId, accountKey,
+                                                                subscriptionId, requestedTimestamp, event, prevSubscription, nextSubscription);
+    }
+
+    @Test(groups = "fast")
+    public void testConstructor() throws Exception {
+        Assert.assertEquals(transition.getEvent(), event);
+        Assert.assertEquals(transition.getPreviousSubscription(), prevSubscription);
+        Assert.assertEquals(transition.getNextSubscription(), nextSubscription);
+        Assert.assertEquals(transition.getRequestedTimestamp(), requestedTimestamp);
+    }
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        Assert.assertSame(transition, transition);
+        Assert.assertEquals(transition, transition);
+        Assert.assertTrue(transition.equals(transition));
+
+        BusinessSubscriptionTransitionModelDao otherTransition;
+
+        otherTransition = new BusinessSubscriptionTransitionModelDao(totalOrdering, bundleId, externalKey, accountId, accountKey,
+                                                                     subscriptionId, new DateTime(), event, prevSubscription, nextSubscription);
+        Assert.assertTrue(!transition.equals(otherTransition));
+
+        otherTransition = new BusinessSubscriptionTransitionModelDao(totalOrdering, bundleId, "12345", accountId, accountKey,
+                                                                     subscriptionId, requestedTimestamp, event, prevSubscription, nextSubscription);
+        Assert.assertTrue(!transition.equals(otherTransition));
+
+        otherTransition = new BusinessSubscriptionTransitionModelDao(totalOrdering, bundleId, externalKey, accountId, accountKey,
+                                                                     subscriptionId, requestedTimestamp, event, prevSubscription, prevSubscription);
+        Assert.assertTrue(!transition.equals(otherTransition));
+
+        otherTransition = new BusinessSubscriptionTransitionModelDao(totalOrdering, bundleId, externalKey, accountId, accountKey,
+                                                                     subscriptionId, requestedTimestamp, event, nextSubscription, nextSubscription);
+        Assert.assertTrue(!transition.equals(otherTransition));
+
+        otherTransition = new BusinessSubscriptionTransitionModelDao(totalOrdering, bundleId, externalKey, accountId, accountKey,
+                                                                     subscriptionId, requestedTimestamp, event, nextSubscription, prevSubscription);
+        Assert.assertTrue(!transition.equals(otherTransition));
+    }
+
+    @Test(groups = "fast")
+    public void testRejectInvalidTransitions() throws Exception {
+        try {
+            new BusinessSubscriptionTransitionModelDao(null, bundleId, externalKey, accountId, accountKey,
+                                                       subscriptionId, requestedTimestamp, event, prevSubscription, nextSubscription);
+            Assert.fail();
+        } catch (IllegalArgumentException e) {
+            Assert.assertTrue(true);
+        }
+
+        try {
+            new BusinessSubscriptionTransitionModelDao(totalOrdering, bundleId, null, accountId, accountKey,
+                                                       subscriptionId, requestedTimestamp, event, prevSubscription, nextSubscription);
+            Assert.fail();
+        } catch (IllegalArgumentException e) {
+            Assert.assertTrue(true);
+        }
+
+        try {
+            new BusinessSubscriptionTransitionModelDao(totalOrdering, bundleId, externalKey, accountId, accountKey,
+                                                       subscriptionId, null, event, prevSubscription, nextSubscription);
+            Assert.fail();
+        } catch (IllegalArgumentException e) {
+            Assert.assertTrue(true);
+        }
+
+        try {
+            new BusinessSubscriptionTransitionModelDao(totalOrdering, bundleId, externalKey, accountId, accountKey,
+                                                       subscriptionId, requestedTimestamp, null, prevSubscription, nextSubscription);
+            Assert.fail();
+        } catch (IllegalArgumentException e) {
+            Assert.assertTrue(true);
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessSubscriptionTransitionField.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessSubscriptionTransitionField.java
new file mode 100644
index 0000000..b016bbf
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessSubscriptionTransitionField.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+
+public class TestBusinessSubscriptionTransitionField extends AnalyticsTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        final String accountKey = UUID.randomUUID().toString();
+        final UUID bundleId = UUID.randomUUID();
+        final String externalKey = UUID.randomUUID().toString();
+        final String name = UUID.randomUUID().toString();
+        final String value = UUID.randomUUID().toString();
+        final BusinessSubscriptionTransitionFieldModelDao subscriptionTransitionField = new BusinessSubscriptionTransitionFieldModelDao(accountKey,
+                                                                                                                                        bundleId,
+                                                                                                                                        externalKey,
+                                                                                                                                        name,
+                                                                                                                                        value);
+        Assert.assertSame(subscriptionTransitionField, subscriptionTransitionField);
+        Assert.assertEquals(subscriptionTransitionField, subscriptionTransitionField);
+        Assert.assertTrue(subscriptionTransitionField.equals(subscriptionTransitionField));
+        Assert.assertEquals(subscriptionTransitionField.getAccountKey(), accountKey);
+        Assert.assertEquals(subscriptionTransitionField.getBundleId(), bundleId);
+        Assert.assertEquals(subscriptionTransitionField.getExternalKey(), externalKey);
+        Assert.assertEquals(subscriptionTransitionField.getName(), name);
+        Assert.assertEquals(subscriptionTransitionField.getValue(), value);
+
+        final BusinessSubscriptionTransitionFieldModelDao otherSubscriptionField = new BusinessSubscriptionTransitionFieldModelDao(UUID.randomUUID().toString(),
+                                                                                                                                   UUID.randomUUID(),
+                                                                                                                                   UUID.randomUUID().toString(),
+                                                                                                                                   UUID.randomUUID().toString(),
+                                                                                                                                   UUID.randomUUID().toString());
+        Assert.assertFalse(subscriptionTransitionField.equals(otherSubscriptionField));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessSubscriptionTransitionTag.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessSubscriptionTransitionTag.java
new file mode 100644
index 0000000..5b7bb79
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/model/TestBusinessSubscriptionTransitionTag.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.model;
+
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+
+public class TestBusinessSubscriptionTransitionTag extends AnalyticsTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        final String accountKey = UUID.randomUUID().toString();
+        final UUID bundleId = UUID.randomUUID();
+        final String externalKey = UUID.randomUUID().toString();
+        final String name = UUID.randomUUID().toString();
+        final BusinessSubscriptionTransitionTagModelDao subscriptionTransitionTag = new BusinessSubscriptionTransitionTagModelDao(accountKey,
+                                                                                                                                  bundleId,
+                                                                                                                                  externalKey,
+                                                                                                                                  name);
+        Assert.assertSame(subscriptionTransitionTag, subscriptionTransitionTag);
+        Assert.assertEquals(subscriptionTransitionTag, subscriptionTransitionTag);
+        Assert.assertTrue(subscriptionTransitionTag.equals(subscriptionTransitionTag));
+        Assert.assertEquals(subscriptionTransitionTag.getAccountKey(), accountKey);
+        Assert.assertEquals(subscriptionTransitionTag.getBundleId(), bundleId);
+        Assert.assertEquals(subscriptionTransitionTag.getExternalKey(), externalKey);
+        Assert.assertEquals(subscriptionTransitionTag.getName(), name);
+
+        final BusinessSubscriptionTransitionTagModelDao otherTransitionTag = new BusinessSubscriptionTransitionTagModelDao(UUID.randomUUID().toString(),
+                                                                                                                           UUID.randomUUID(),
+                                                                                                                           UUID.randomUUID().toString(),
+                                                                                                                           UUID.randomUUID().toString());
+        Assert.assertFalse(subscriptionTransitionTag.equals(otherTransitionTag));
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/TestBusinessInvoiceRecorder.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/TestBusinessInvoiceRecorder.java
new file mode 100644
index 0000000..37c7ebd
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/TestBusinessInvoiceRecorder.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import java.math.BigDecimal;
+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.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessInvoiceItemModelDao;
+
+public class TestBusinessInvoiceRecorder extends AnalyticsTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testShouldBeAbleToHandleNullFieldsInInvoiceItem() throws Exception {
+        final InvoiceItem invoiceItem = Mockito.mock(InvoiceItem.class);
+        Mockito.when(invoiceItem.getAmount()).thenReturn(BigDecimal.TEN);
+        Mockito.when(invoiceItem.getCurrency()).thenReturn(Currency.AUD);
+        Mockito.when(invoiceItem.getEndDate()).thenReturn(new LocalDate(1200, 1, 12));
+        final UUID invoiceId = UUID.randomUUID();
+        Mockito.when(invoiceItem.getInvoiceId()).thenReturn(invoiceId);
+        final UUID id = UUID.randomUUID();
+        Mockito.when(invoiceItem.getId()).thenReturn(id);
+        Mockito.when(invoiceItem.getStartDate()).thenReturn(new LocalDate(1985, 9, 10));
+        Mockito.when(invoiceItem.getInvoiceItemType()).thenReturn(InvoiceItemType.CREDIT_ADJ);
+
+        final BusinessInvoiceItemModelDao bii = invoiceDao.createBusinessInvoiceItem(invoiceItem, internalCallContext);
+        Assert.assertNotNull(bii);
+        Assert.assertEquals(bii.getAmount(), invoiceItem.getAmount());
+        Assert.assertEquals(bii.getCurrency(), invoiceItem.getCurrency());
+        Assert.assertEquals(bii.getEndDate(), invoiceItem.getEndDate());
+        Assert.assertEquals(bii.getInvoiceId(), invoiceItem.getInvoiceId());
+        Assert.assertEquals(bii.getItemId(), invoiceItem.getId());
+        Assert.assertEquals(bii.getStartDate(), invoiceItem.getStartDate());
+        Assert.assertEquals(bii.getItemType(), invoiceItem.getInvoiceItemType().toString());
+        Assert.assertNull(bii.getBillingPeriod());
+        Assert.assertNull(bii.getPhase());
+        Assert.assertNull(bii.getProductCategory());
+        Assert.assertNull(bii.getProductName());
+        Assert.assertNull(bii.getProductType());
+        Assert.assertNull(bii.getSlug());
+        Assert.assertNull(bii.getExternalKey());
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/TestBusinessSubscriptionTransitionRecorder.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/TestBusinessSubscriptionTransitionRecorder.java
new file mode 100644
index 0000000..5330aeb
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/TestBusinessSubscriptionTransitionRecorder.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.osgi.bundles.analytics.model.BusinessSubscriptionTransitionModelDao;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.events.EffectiveSubscriptionInternalEvent;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestBusinessSubscriptionTransitionRecorder extends AnalyticsTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testCreateAddOn() throws Exception {
+        final UUID bundleId = UUID.randomUUID();
+        final UUID externalKey = UUID.randomUUID();
+        final UUID accountId = UUID.randomUUID();
+        final UUID subscriptionId = UUID.randomUUID();
+
+        // Setup the catalog
+        Mockito.when(catalogService.getFullCatalog()).thenReturn(Mockito.mock(Catalog.class));
+
+        // Setup the entitlement API
+        final SubscriptionBundle bundle = Mockito.mock(SubscriptionBundle.class);
+        Mockito.when(bundle.getId()).thenReturn(bundleId);
+        Mockito.when(bundle.getAccountId()).thenReturn(accountId);
+        Mockito.when(bundle.getExternalKey()).thenReturn(externalKey.toString());
+        Mockito.when(entitlementInternalApi.getBundleFromId(Mockito.<UUID>any(), Mockito.<InternalTenantContext>any())).thenReturn(bundle);
+
+        // Setup the account API
+        final Account account = Mockito.mock(Account.class);
+        Mockito.when(account.getExternalKey()).thenReturn(externalKey.toString());
+        Mockito.when(accountInternalApi.getAccountById(Mockito.eq(bundle.getAccountId()), Mockito.<InternalTenantContext>any())).thenReturn(account);
+
+        // Create an new subscription event
+        final EffectiveSubscriptionInternalEvent eventEffective = Mockito.mock(EffectiveSubscriptionInternalEvent.class);
+        Mockito.when(eventEffective.getId()).thenReturn(UUID.randomUUID());
+        Mockito.when(eventEffective.getTransitionType()).thenReturn(SubscriptionTransitionType.CREATE);
+        Mockito.when(eventEffective.getSubscriptionId()).thenReturn(subscriptionId);
+        Mockito.when(eventEffective.getRequestedTransitionTime()).thenReturn(new DateTime(DateTimeZone.UTC));
+        Mockito.when(eventEffective.getNextPlan()).thenReturn(UUID.randomUUID().toString());
+        Mockito.when(eventEffective.getEffectiveTransitionTime()).thenReturn(new DateTime(DateTimeZone.UTC));
+        Mockito.when(eventEffective.getSubscriptionStartDate()).thenReturn(new DateTime(DateTimeZone.UTC));
+
+        final Subscription subscription = Mockito.mock(Subscription.class);
+        Mockito.when(subscription.getId()).thenReturn(subscriptionId);
+        Mockito.when(entitlementInternalApi.getAllTransitions(Mockito.eq(subscription), Mockito.<InternalTenantContext>any())).thenReturn(ImmutableList.<EffectiveSubscriptionInternalEvent>of(eventEffective));
+
+        Mockito.when(entitlementInternalApi.getSubscriptionsForBundle(Mockito.<UUID>any(), Mockito.<InternalTenantContext>any())).thenReturn(ImmutableList.<Subscription>of(subscription));
+
+        subscriptionTransitionDao.rebuildTransitionsForBundle(bundle.getId(), internalCallContext);
+
+        Assert.assertEquals(subscriptionTransitionSqlDao.getTransitionsByKey(externalKey.toString(), internalCallContext).size(), 1);
+        final BusinessSubscriptionTransitionModelDao transition = subscriptionTransitionSqlDao.getTransitionsByKey(externalKey.toString(), internalCallContext).get(0);
+        Assert.assertEquals(transition.getTotalOrdering(), (long) eventEffective.getTotalOrdering());
+        Assert.assertEquals(transition.getAccountKey(), externalKey.toString());
+        // Make sure all the prev_ columns are null
+        Assert.assertNull(transition.getPreviousSubscription());
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/TestBusinessTagRecorder.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/TestBusinessTagRecorder.java
new file mode 100644
index 0000000..ee0e021
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/TestBusinessTagRecorder.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import java.util.UUID;
+
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.ObjectType;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.mock.MockAccountBuilder;
+import com.ning.billing.util.callcontext.InternalCallContext;
+
+public class TestBusinessTagRecorder extends AnalyticsTestSuiteWithEmbeddedDB {
+
+    @Test(groups = "slow")
+    public void testAddAndRemoveTagsForAccount() throws Exception {
+        final String name = UUID.randomUUID().toString().substring(0, 20);
+        final String accountKey = UUID.randomUUID().toString();
+
+        final Account accountData = new MockAccountBuilder(UUID.randomUUID())
+                .externalKey(accountKey)
+                .currency(Currency.MXN)
+                .build();
+        Mockito.when(accountInternalApi.getAccountById(Mockito.eq(accountData.getId()), Mockito.<InternalCallContext>any())).thenReturn(accountData);
+        Mockito.when(accountInternalApi.getAccountByKey(Mockito.eq(accountData.getExternalKey()), Mockito.<InternalCallContext>any())).thenReturn(accountData);
+        final UUID accountId = accountData.getId();
+
+        Assert.assertEquals(accountTagSqlDao.getTagsForAccountByKey(accountKey, internalCallContext).size(), 0);
+        tagDao.tagAdded(ObjectType.ACCOUNT, accountId, name, internalCallContext);
+        Assert.assertEquals(accountTagSqlDao.getTagsForAccountByKey(accountKey, internalCallContext).size(), 1);
+        tagDao.tagRemoved(ObjectType.ACCOUNT, accountId, name, internalCallContext);
+        Assert.assertEquals(accountTagSqlDao.getTagsForAccountByKey(accountKey, internalCallContext).size(), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testAddAndRemoveTagsForBundle() throws Exception {
+        final String name = UUID.randomUUID().toString().substring(0, 20);
+        final String externalKey = UUID.randomUUID().toString();
+
+        final Account accountData = new MockAccountBuilder()
+                .currency(Currency.MXN)
+                .build();
+        Mockito.when(accountInternalApi.getAccountById(Mockito.eq(accountData.getId()), Mockito.<InternalCallContext>any())).thenReturn(accountData);
+        Mockito.when(accountInternalApi.getAccountByKey(Mockito.eq(accountData.getExternalKey()), Mockito.<InternalCallContext>any())).thenReturn(accountData);
+
+        final UUID bundleId = UUID.randomUUID();
+        final SubscriptionBundle bundle = Mockito.mock(SubscriptionBundle.class);
+        Mockito.when(bundle.getId()).thenReturn(bundleId);
+        Mockito.when(bundle.getAccountId()).thenReturn(accountData.getId());
+        Mockito.when(bundle.getExternalKey()).thenReturn(externalKey);
+        Mockito.when(entitlementInternalApi.getBundleFromId(Mockito.eq(bundleId), Mockito.<InternalCallContext>any())).thenReturn(bundle);
+
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.getTagsForBusinessSubscriptionTransitionByKey(externalKey, internalCallContext).size(), 0);
+        tagDao.tagAdded(ObjectType.BUNDLE, bundleId, name, internalCallContext);
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.getTagsForBusinessSubscriptionTransitionByKey(externalKey, internalCallContext).size(), 1);
+        tagDao.tagRemoved(ObjectType.BUNDLE, bundleId, name, internalCallContext);
+        Assert.assertEquals(subscriptionTransitionTagSqlDao.getTagsForBusinessSubscriptionTransitionByKey(externalKey, internalCallContext).size(), 0);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/TestPaymentMethodUtils.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/TestPaymentMethodUtils.java
new file mode 100644
index 0000000..371cbbf
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/TestPaymentMethodUtils.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics;
+
+import java.util.UUID;
+
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.payment.api.PaymentMethodPlugin;
+
+public class TestPaymentMethodUtils extends AnalyticsTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testUnknowns() throws Exception {
+        Assert.assertNull(PaymentMethodUtils.getCardCountry(null));
+        Assert.assertNull(PaymentMethodUtils.getCardType(null));
+        Assert.assertNull(PaymentMethodUtils.getPaymentMethodType(null));
+
+        final PaymentMethodPlugin paymentMethodPlugin = Mockito.mock(PaymentMethodPlugin.class);
+        Assert.assertNull(PaymentMethodUtils.getCardCountry(paymentMethodPlugin));
+        Assert.assertNull(PaymentMethodUtils.getCardType(paymentMethodPlugin));
+        Assert.assertNull(PaymentMethodUtils.getPaymentMethodType(paymentMethodPlugin));
+    }
+
+    @Test(groups = "fast")
+    public void testCardCountry() throws Exception {
+        final String country = UUID.randomUUID().toString();
+        final String cardType = UUID.randomUUID().toString();
+        final String type = UUID.randomUUID().toString();
+
+        final PaymentMethodPlugin paymentMethodPlugin = Mockito.mock(PaymentMethodPlugin.class);
+        Mockito.when(paymentMethodPlugin.getValueString(PaymentMethodUtils.COUNTRY_KEY)).thenReturn(country);
+        Mockito.when(paymentMethodPlugin.getValueString(PaymentMethodUtils.CARD_TYPE_KEY)).thenReturn(cardType);
+        Mockito.when(paymentMethodPlugin.getValueString(PaymentMethodUtils.TYPE_KEY)).thenReturn(type);
+
+        Assert.assertEquals(PaymentMethodUtils.getCardCountry(paymentMethodPlugin), country);
+        Assert.assertEquals(PaymentMethodUtils.getCardType(paymentMethodPlugin), cardType);
+        Assert.assertEquals(PaymentMethodUtils.getPaymentMethodType(paymentMethodPlugin), type);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/utils/TestRounder.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/utils/TestRounder.java
new file mode 100644
index 0000000..3e3cbec
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/utils/TestRounder.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2010-2013 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.osgi.bundles.analytics.utils;
+
+import java.math.BigDecimal;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+
+public class TestRounder extends AnalyticsTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testRound() throws Exception {
+        Assert.assertEquals(Rounder.round(null), 0.0);
+        Assert.assertEquals(Rounder.round(BigDecimal.ZERO), 0.0);
+        Assert.assertEquals(Rounder.round(BigDecimal.ONE), 1.0);
+        Assert.assertEquals(Rounder.round(BigDecimal.TEN), 10.0);
+        Assert.assertEquals(Rounder.round(BigDecimal.valueOf(1.33333)), 1.3333);
+        Assert.assertEquals(Rounder.round(BigDecimal.valueOf(4444.33333)), 4444.3333);
+        Assert.assertEquals(Rounder.round(BigDecimal.valueOf(10.11111)), 10.1111);
+        Assert.assertEquals(Rounder.round(BigDecimal.valueOf(10.11115)), 10.1112);
+        Assert.assertEquals(Rounder.round(BigDecimal.valueOf(10.11116)), 10.1112);
+    }
+}
diff --git a/osgi-bundles/defaultbundles/pom.xml b/osgi-bundles/defaultbundles/pom.xml
index 3f94eec..1e88c52 100644
--- a/osgi-bundles/defaultbundles/pom.xml
+++ b/osgi-bundles/defaultbundles/pom.xml
@@ -29,6 +29,10 @@
     <dependencies>
         <dependency>
             <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-osgi-bundles-analytics</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
             <artifactId>killbill-osgi-bundles-jruby</artifactId>
         </dependency>
         <dependency>