text:
package org.killbill.billing.invoice;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;

import javax.annotation.Nullable;

import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.account.api.AccountInternalApi;
import org.killbill.billing.account.api.ImmutableAccountData;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.entitlement.api.EntitlementSpecifier;
import org.killbill.billing.entitlement.api.SubscriptionEventType;
import org.killbill.billing.events.BusInternalEvent;
import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
import org.killbill.billing.events.InvoiceNotificationInternalEvent;
import org.killbill.billing.events.RequestedSubscriptionInternalEvent;
import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.FutureAccountNotificationsBuilder;
import org.killbill.billing.invoice.api.DryRunArguments;
import org.killbill.billing.invoice.api.DryRunType;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.invoice.api.InvoiceStatus;
import org.killbill.billing.invoice.api.user.DefaultInvoiceNotificationInternalEvent;
import org.killbill.billing.invoice.api.user.DefaultNullInvoiceEvent;
import org.killbill.billing.invoice.calculator.InvoiceCalculatorUtils;
import org.killbill.billing.invoice.dao.ExistingInvoiceMetadata;
import org.killbill.billing.invoice.dao.InvoiceDao;
import org.killbill.billing.invoice.dao.InvoiceItemModelDao;
import org.killbill.billing.invoice.dao.InvoiceModelDao;
import org.killbill.billing.invoice.dao.InvoiceModelDaoHelper;
import org.killbill.billing.invoice.dao.InvoiceParentChildModelDao;
import org.killbill.billing.invoice.dao.InvoiceTrackingModelDao;
import org.killbill.billing.invoice.generator.InvoiceGenerator;
import org.killbill.billing.invoice.generator.InvoiceWithMetadata;
import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates;
import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates.UsageDef;
import org.killbill.billing.invoice.generator.InvoiceWithMetadata.TrackingRecordId;
import org.killbill.billing.invoice.model.DefaultInvoice;
import org.killbill.billing.invoice.model.InvoiceItemFactory;
import org.killbill.billing.invoice.model.ItemAdjInvoiceItem;
import org.killbill.billing.invoice.model.ParentInvoiceItem;
import org.killbill.billing.invoice.notification.DefaultNextBillingDateNotifier;
import org.killbill.billing.invoice.notification.NextBillingDateNotificationKey;
import org.killbill.billing.invoice.optimizer.InvoiceOptimizer;
import org.killbill.billing.invoice.optimizer.InvoiceOptimizerBase.AccountInvoices;
import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.junction.BillingEventSet;
import org.killbill.billing.junction.BillingInternalApi;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.platform.api.KillbillService.KILLBILL_SERVICES;
import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
import org.killbill.billing.util.UUIDs;
import org.killbill.billing.util.api.TagApiException;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.callcontext.TenantContext;
import org.killbill.billing.util.config.definition.InvoiceConfig;
import org.killbill.billing.util.globallocker.LockerType;
import org.killbill.billing.util.optimizer.BusOptimizer;
import org.killbill.bus.api.PersistentBus.EventBusException;
import org.killbill.clock.Clock;
import org.killbill.commons.locker.GlobalLock;
import org.killbill.commons.locker.GlobalLocker;
import org.killbill.commons.locker.LockFailedException;
import org.killbill.notificationq.api.NotificationEventWithMetadata;
import org.killbill.notificationq.api.NotificationQueue;
import org.killbill.notificationq.api.NotificationQueueService;
import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.google.inject.Inject;

public class InvoiceDispatcher {

    private static final Logger log = LoggerFactory.getLogger(InvoiceDispatcher.class);

    public static final int MAX_NB_ITEMS_TO_PRINT = 20;

    private static final Ordering<DateTime> UPCOMING_NOTIFICATION_DATE_ORDERING = Ordering.natural();
    private static final Joiner JOINER_COMMA = Joiner.on(",");
    private static final TargetDateDryRunArguments TARGET_DATE_DRY_RUN_ARGUMENTS = new TargetDateDryRunArguments();

    private static final String DRY_RUN_CUR_DATE_PROP = "DRY_RUN_CUR_DATE";
    private static final String DRY_RUN_TARGET_DATE_PROP = "DRY_RUN_TARGET_DATE";

    private final InvoiceGenerator generator;
    private final BillingInternalApi billingApi;
    private final AccountInternalApi accountApi;
    private final SubscriptionBaseInternalApi subscriptionApi;
    private final InvoiceDao invoiceDao;
    private final InternalCallContextFactory internalCallContextFactory;
    private final InvoicePluginDispatcher invoicePluginDispatcher;
    private final GlobalLocker locker;
    private final BusOptimizer eventBus;
    private final Clock clock;
    private final NotificationQueueService notificationQueueService;
    private final InvoiceConfig invoiceConfig;
    private final ParkedAccountsManager parkedAccountsManager;
    private final InvoiceOptimizer invoiceOptimizer;

    @Inject
    public InvoiceDispatcher(final InvoiceGenerator generator,
                             final AccountInternalApi accountApi,
                             final BillingInternalApi billingApi,
                             final SubscriptionBaseInternalApi SubscriptionApi,
                             final InvoiceDao invoiceDao,
                             final InternalCallContextFactory internalCallContextFactory,
                             final InvoicePluginDispatcher invoicePluginDispatcher,
                             final GlobalLocker locker,
                             final BusOptimizer eventBus,
                             final NotificationQueueService notificationQueueService,
                             final InvoiceConfig invoiceConfig,
                             final Clock clock,
                             final InvoiceOptimizer invoiceOptimizer,
                             final ParkedAccountsManager parkedAccountsManager) {
        this.generator = generator;
        this.billingApi = billingApi;
        this.subscriptionApi = SubscriptionApi;
        this.accountApi = accountApi;
        this.invoiceDao = invoiceDao;
        this.internalCallContextFactory = internalCallContextFactory;
        this.invoicePluginDispatcher = invoicePluginDispatcher;
        this.locker = locker;
        this.eventBus = eventBus;
        this.clock = clock;
        this.invoiceOptimizer = invoiceOptimizer;
        this.notificationQueueService = notificationQueueService;
        this.invoiceConfig = invoiceConfig;
        this.parkedAccountsManager = parkedAccountsManager;
    }

    public void processSubscriptionStartRequestedDate(final RequestedSubscriptionInternalEvent transition, final InternalCallContext context) {
        final long dryRunNotificationTime = invoiceConfig.getDryRunNotificationSchedule(context).getMillis();
        final boolean isInvoiceNotificationEnabled = dryRunNotificationTime > 0;
        if (!isInvoiceNotificationEnabled) {
            return;
        }

        final UUID accountId;
        try {
            accountId = subscriptionApi.getAccountIdFromSubscriptionId(transition.getSubscriptionId(), context);
        } catch (final SubscriptionBaseApiException e) {
            log.warn("Failed handling SubscriptionBase change.",
                     new InvoiceApiException(ErrorCode.INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID, transition.getSubscriptionId().toString()));
            return;
        }

        GlobalLock lock = null;
        try {
            lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), accountId.toString(), invoiceConfig.getMaxGlobalLockRetries());

            processSubscriptionStartRequestedDateWithLock(accountId, transition, context);
        } catch (final LockFailedException e) {
            log.warn("Failed to process RequestedSubscriptionInternalEvent for accountId='{}'", accountId.toString(), e);
        } finally {
            if (lock != null) {
                lock.release();
            }
        }
    }

    private void processSubscriptionStartRequestedDateWithLock(final UUID accountId, final RequestedSubscriptionInternalEvent transition, final InternalCallContext context) {
        try {
            final BillingEventSet billingEvents = billingApi.getBillingEventsForAccountAndUpdateAccountBCD(accountId, null, context);
            if (billingEvents.isEmpty()) {
                return;
            }

            final FutureAccountNotificationsBuilder notificationsBuilder = new FutureAccountNotificationsBuilder();
            populateNextFutureDryRunNotificationDate(billingEvents, notificationsBuilder, context);

            final ImmutableAccountData account = accountApi.getImmutableAccountDataById(accountId, context);

            commitInvoiceAndSetFutureNotifications(account, notificationsBuilder.build(), context);
        } catch (final SubscriptionBaseApiException e) {
            log.warn("Failed handling SubscriptionBase change.",
                     new InvoiceApiException(ErrorCode.INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID, transition.getSubscriptionId().toString()));
        } catch (final AccountApiException e) {
            log.warn("Failed to retrieve BillingEvents for accountId='{}'", accountId, e);
        } catch (final CatalogApiException e) {
            log.warn("Failed to retrieve BillingEvents for accountId='{}'", accountId, e);
        }
    }

    public void processSubscriptionForInvoiceGeneration(final EffectiveSubscriptionInternalEvent transition,
                                                        final InternalCallContext context) throws InvoiceApiException {
        final UUID subscriptionId = transition.getSubscriptionId();
        final LocalDate targetDate = context.toLocalDate(transition.getEffectiveTransitionTime());
        processSubscriptionForInvoiceGeneration(subscriptionId, targetDate, false, context);
    }

    public void processSubscriptionForInvoiceGeneration(final UUID subscriptionId, final LocalDate targetDate, final boolean isRescheduled, final InternalCallContext context) throws InvoiceApiException {
        processSubscriptionInternal(subscriptionId, targetDate, false, isRescheduled, context);
    }

    public void processSubscriptionForInvoiceNotification(final UUID subscriptionId, final LocalDate targetDate, final InternalCallContext context) throws InvoiceApiException {
        final Invoice dryRunInvoice = processSubscriptionInternal(subscriptionId, targetDate, true, false, context);
        if (dryRunInvoice != null && dryRunInvoice.getBalance().compareTo(BigDecimal.ZERO) > 0) {
            final InvoiceNotificationInternalEvent event = new DefaultInvoiceNotificationInternalEvent(dryRunInvoice.getAccountId(), dryRunInvoice.getBalance(), dryRunInvoice.getCurrency(),
                                                                                context.toUTCDateTime(targetDate), context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
            try {
                eventBus.post(event);
            } catch (final EventBusException e) {
                log.warn("Failed to post event {}", event, e);
            }
        }
    }

    private Invoice processSubscriptionInternal(final UUID subscriptionId, final LocalDate targetDate, final boolean dryRunForNotification, final boolean isRescheduled, final InternalCallContext context) throws InvoiceApiException {
        try {
            if (subscriptionId == null) {
                log.warn("Failed handling SubscriptionBase change.", new InvoiceApiException(ErrorCode.INVOICE_INVALID_TRANSITION));
                return null;
            }
            final UUID accountId = subscriptionApi.getAccountIdFromSubscriptionId(subscriptionId, context);
            final DryRunArguments dryRunArguments = dryRunForNotification ? TARGET_DATE_DRY_RUN_ARGUMENTS : null;

            return processAccountFromNotificationOrBusEvent(accountId, targetDate, dryRunArguments, isRescheduled, context);
        } catch (final SubscriptionBaseApiException e) {
            log.warn("Failed handling SubscriptionBase change.",
                     new InvoiceApiException(ErrorCode.INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID, subscriptionId.toString()));
            return null;
        }
    }

    public Invoice processAccountFromNotificationOrBusEvent(final UUID accountId,
                                                            @Nullable final LocalDate targetDate,
                                                            @Nullable final DryRunArguments dryRunArguments,
                                                            final boolean isRescheduled,
                                                            final InternalCallContext context) throws InvoiceApiException {
        if (!invoiceConfig.isInvoicingSystemEnabled(context)) {
            log.warn("Invoicing system is off, parking accountId='{}'", accountId);
            parkAccount(accountId, context);
            return null;
        }

        return processAccount(false, accountId, targetDate, dryRunArguments, isRescheduled, context);
    }

    public Invoice processAccount(final boolean isApiCall,
                                  final UUID accountId,
                                  @Nullable final LocalDate targetDate,
                                  @Nullable final DryRunArguments dryRunArguments,
                                  final boolean isRescheduled,
                                  final InternalCallContext context) throws InvoiceApiException {
        boolean parkedAccount = false;
        try {
            parkedAccount = parkedAccountsManager.isParked(context);
            if (parkedAccount && !isApiCall) {
                log.warn("Ignoring invoice generation process for accountId='{}', targetDate='{}', account is parked", accountId.toString(), targetDate);
                return null;
            }
        } catch (final TagApiException e) {
            log.warn("Unable to determine parking state for accountId='{}'", accountId);
        }

        GlobalLock lock = null;
        try {
            lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), accountId.toString(), invoiceConfig.getMaxGlobalLockRetries());

            return processAccountWithLock(parkedAccount, accountId, targetDate, dryRunArguments, isRescheduled, context);
        } catch (final LockFailedException e) {
            log.warn("Failed to process invoice for accountId='{}', targetDate='{}'", accountId.toString(), targetDate, e);
        } finally {
            if (lock != null) {
                lock.release();
            }
        }
        return null;
    }

    private Invoice processAccountWithLock(final boolean parkedAccount,
                                           final UUID accountId,
                                           @Nullable final LocalDate inputTargetDateMaybeNull,
                                           @Nullable final DryRunArguments dryRunArguments,
                                           final boolean isRescheduled,
                                           final InternalCallContext context) throws InvoiceApiException {
        final boolean isDryRun = dryRunArguments != null;
        final boolean upcomingInvoiceDryRun = isDryRun && DryRunType.UPCOMING_INVOICE.equals(dryRunArguments.getDryRunType());

        LocalDate inputTargetDate = inputTargetDateMaybeNull;
        // A null inputTargetDate is only allowed in UPCOMING_INVOICE dryRun mode to have the system compute it
        if (inputTargetDate == null && !upcomingInvoiceDryRun) {
            inputTargetDate = context.toLocalDate(clock.getUTCNow());
        }
        Preconditions.checkArgument(inputTargetDate != null || upcomingInvoiceDryRun, "inputTargetDate is required in non dryRun mode");

        try {
            // Make sure to first set the BCD if needed then get the account object (to have the BCD set)
            final BillingEventSet billingEvents = billingApi.getBillingEventsForAccountAndUpdateAccountBCD(accountId, dryRunArguments, context);
            if (billingEvents.isAccountAutoInvoiceOff()) {
                return null;
            }

            // Avoid pulling all invoices when AUTO_INVOICING_OFF is set since we will disable invoicing later
            // (Note that we can't return right away as we send a NullInvoice event)
            final AccountInvoices accountInvoices = billingEvents.isAccountAutoInvoiceOff() ?
                                                    new AccountInvoices() : invoiceOptimizer.getInvoices(context);

            final Invoice invoice;
            if (!isDryRun) {
                final InvoiceWithFutureNotifications invoiceWithFutureNotifications = processAccountWithLockAndInputTargetDate(accountId, inputTargetDate, billingEvents, accountInvoices, false, isRescheduled, Lists.newLinkedList(), context);
                invoice = invoiceWithFutureNotifications != null ? invoiceWithFutureNotifications.getInvoice() : null;
                if (parkedAccount) {
                    try {
                        log.info("Illegal invoicing state fixed for accountId='{}', unparking account", accountId);
                        parkedAccountsManager.unparkAccount(accountId, context);
                    } catch (final TagApiException ignored) {
                        log.warn("Unable to unpark account", ignored);
                    }
                }
            } else /* Dry run use cases */ {
                final NotificationQueue notificationQueue = notificationQueueService.getNotificationQueue(KILLBILL_SERVICES.INVOICE_SERVICE.getServiceName(),
                                                                                DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
                final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotificationsIterable = notificationQueue.getFutureNotificationForSearchKeys(context.getAccountRecordId(), context.getTenantRecordId());
                // Copy the results as retrieving the iterator will issue a query each time. This also makes sure the underlying JDBC connection is closed.
                final List<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = ImmutableList.<NotificationEventWithMetadata<NextBillingDateNotificationKey>>copyOf(futureNotificationsIterable);

                final Map<UUID, DateTime> nextScheduledSubscriptionsEventMap = getNextTransitionsForSubscriptions(billingEvents);

                // List of all existing invoice notifications
                final Set<LocalDate> allCandidateTargetDates = getUpcomingInvoiceCandidateDates(futureNotifications, nextScheduledSubscriptionsEventMap, ImmutableList.<UUID>of(), context);

                if (dryRunArguments.getDryRunType() == DryRunType.UPCOMING_INVOICE) {

                    final Iterable<UUID> filteredSubscriptionIdsForDryRun = getFilteredSubscriptionIdsFor_UPCOMING_INVOICE_DryRun(dryRunArguments, billingEvents);

                    // List of existing invoice notifications associated to the filter set of subscriptionIds
                    final Set<LocalDate> filteredCandidateTargetDates = Iterables.isEmpty(filteredSubscriptionIdsForDryRun) ?
                                                                        allCandidateTargetDates :
                                                                        getUpcomingInvoiceCandidateDates(futureNotifications, nextScheduledSubscriptionsEventMap, filteredSubscriptionIdsForDryRun, context);

                    if (Iterables.isEmpty(filteredSubscriptionIdsForDryRun)) {
                        invoice = processDryRun_UPCOMING_INVOICE_Invoice(accountId, allCandidateTargetDates, billingEvents, accountInvoices, context);
                    } else {
                        invoice = processDryRun_UPCOMING_INVOICE_FILTERING_Invoice(accountId, filteredCandidateTargetDates, allCandidateTargetDates, billingEvents, accountInvoices, context);
                    }
                } else /* DryRunType.TARGET_DATE, SUBSCRIPTION_ACTION */ {
                    invoice = processDryRun_TARGET_DATE_Invoice(accountId, inputTargetDate, allCandidateTargetDates, billingEvents, accountInvoices, context);
                }
            }
            return invoice;
        } catch (final CatalogApiException e) {
            log.warn("Failed to retrieve BillingEvents for accountId='{}', dryRunArguments='{}'", accountId, dryRunArguments, e);
            return null;
        } catch (final AccountApiException e) {
            log.warn("Failed to retrieve BillingEvents for accountId='{}', dryRunArguments='{}'", accountId, dryRunArguments, e);
            return null;
        } catch (final SubscriptionBaseApiException e) {
            log.warn("Failed to retrieve BillingEvents for accountId='{}', dryRunArguments='{}'", accountId, dryRunArguments, e);
            return null;
        } catch (final InvoiceApiException e) {
            if (e.getCode() == ErrorCode.INVOICE_PLUGIN_API_ABORTED.getCode()) {
                return null;
            }

            if (e.getCode() == ErrorCode.UNEXPECTED_ERROR.getCode() && !isDryRun) {
                log.warn("Illegal invoicing state detected for accountId='{}', dryRunArguments='{}', parking account", accountId, dryRunArguments, e);
                parkAccount(accountId, context);
            }
            throw e;
        } catch (final NoSuchNotificationQueue e) {
            throw new InvoiceApiException(ErrorCode.UNEXPECTED_ERROR, "Failed to retrieve future notifications from notificationQ");
        }
    }

    // Return a map of subscriptionId / localDate identifying what is the next upcoming billing transition (PHASE, PAUSE, ..)
    private Map<UUID, DateTime> getNextTransitionsForSubscriptions(final BillingEventSet billingEvents) {

        final DateTime now = clock.getUTCNow();
        final Map<UUID, DateTime> result = new HashMap<UUID, DateTime>();
        for (final BillingEvent evt : billingEvents) {
            final UUID subscriptionId = evt.getSubscriptionId();
            final DateTime evtEffectiveDate = evt.getEffectiveDate();
            if (evtEffectiveDate.compareTo(now) <= 0) {
                continue;
            }
            final DateTime nextUpcomingPerSubscriptionDate = result.get(subscriptionId);
            if (nextUpcomingPerSubscriptionDate == null || nextUpcomingPerSubscriptionDate.compareTo(evtEffectiveDate) > 0) {
                result.put(subscriptionId, evtEffectiveDate);
            }
        }
        return result;
    }

    private Invoice processDryRun_UPCOMING_INVOICE_Invoice(final UUID accountId, final Set<LocalDate> allCandidateTargetDates, final BillingEventSet billingEvents, final AccountInvoices accountInvoices, final InternalCallContext context) throws InvoiceApiException {
        for (final LocalDate curTargetDate : allCandidateTargetDates) {
            final InvoiceWithFutureNotifications invoiceWithFutureNotifications = processAccountWithLockAndInputTargetDate(accountId, curTargetDate, billingEvents, accountInvoices, true, false, Lists.newLinkedList(), context);
            final Invoice invoice = invoiceWithFutureNotifications != null ? invoiceWithFutureNotifications.getInvoice() : null;
            if (invoice != null) {
                return invoice;
            }
        }
        return null;
    }

    private Invoice processDryRun_UPCOMING_INVOICE_FILTERING_Invoice(final UUID accountId, final Set<LocalDate> filteringCandidateTargetDates, final Set<LocalDate> allCandidateTargetDates, final BillingEventSet billingEvents, final AccountInvoices accountInvoices, final InternalCallContext context) throws InvoiceApiException {
        for (final LocalDate curTargetDate : filteringCandidateTargetDates) {
            final Invoice invoice = processDryRun_TARGET_DATE_Invoice(accountId, curTargetDate, allCandidateTargetDates, billingEvents, accountInvoices, context);
            if (invoice != null) {
                return invoice;
            }
        }
        return null;
    }

    private Invoice processDryRun_TARGET_DATE_Invoice(final UUID accountId, final LocalDate targetDate, final Set<LocalDate> allCandidateTargetDates, final BillingEventSet billingEvents, final AccountInvoices accountInvoices, final InternalCallContext context) throws InvoiceApiException {

        LinkedList<PluginProperty> pluginProperties;

        final PriorityQueue<LocalDate> pq = new PriorityQueue<LocalDate>(allCandidateTargetDates);

        // Keeps track of generated invoices as we go through the list
        // The list is an ordered list of items merged from existing notifications and upcoming notifications, each of these the result of a previous invoice being generated.
        final List<Invoice> augmentedExistingInvoices = new ArrayList<Invoice>(accountInvoices.getInvoices());
        Invoice additionalInvoice = null;
        LocalDate prev = null;
        LocalDate cur;
        while ((cur = pq.poll()) != null) {
            // Eat up duplicate if any
            if (prev != null && prev.compareTo(cur) == 0) {
                continue;
            }
            if (cur.compareTo(targetDate) >= 0) {
                break;
            }

            // Loop through each boundary date prior to our given targetDate
            pluginProperties = new LinkedList<PluginProperty>();
            pluginProperties.add(new PluginProperty(DRY_RUN_CUR_DATE_PROP, cur, false));
            pluginProperties.add(new PluginProperty(DRY_RUN_TARGET_DATE_PROP, targetDate, false));

            final InvoiceWithFutureNotifications result = processAccountWithLockAndInputTargetDate(accountId, cur, billingEvents, new AccountInvoices(accountInvoices.getCutoffDate(), augmentedExistingInvoices), true, false, pluginProperties, context);
            additionalInvoice = result != null ? result.getInvoice() : null;
            if (additionalInvoice != null) {
                for (final LocalDate k : result.getNotifications().getNotificationsForTrigger().keySet()) {
                    if (k.compareTo(cur) > 0 && k.compareTo(targetDate) < 0) {
                        pq.add(k);
                    }
                }
                // In order to handle AUTO_INVOICING_REUSE_DRAFT where the invoice is being reused
                // we need to only keep the latest invoice with all the items currently being generated
                // See https://github.com/killbill/killbill/issues/1313
                final UUID additionalInvoiceId = additionalInvoice.getId();
                Iterables.removeIf(augmentedExistingInvoices, new Predicate<Invoice>() {
                    @Override
                    public boolean apply(final Invoice input) {
                        return input.getId().equals(additionalInvoiceId);
                    }
                });
                augmentedExistingInvoices.add(additionalInvoice);
            }
            prev = cur;
        }

        pluginProperties = new LinkedList<PluginProperty>();
        pluginProperties.add(new PluginProperty(DRY_RUN_CUR_DATE_PROP, targetDate, false));
        pluginProperties.add(new PluginProperty(DRY_RUN_TARGET_DATE_PROP, targetDate, false));
        final InvoiceWithFutureNotifications invoiceWithFutureNotifications = processAccountWithLockAndInputTargetDate(accountId, targetDate, billingEvents, new AccountInvoices(accountInvoices.getCutoffDate(), augmentedExistingInvoices), true, false, pluginProperties, context);
        final Invoice targetInvoice = invoiceWithFutureNotifications != null ? invoiceWithFutureNotifications.getInvoice() : null;
        return targetInvoice != null ? targetInvoice : additionalInvoice;
    }

    private void parkAccount(final UUID accountId, final InternalCallContext context) {
        try {
            parkedAccountsManager.parkAccount(accountId, context);
        } catch (final TagApiException ignored) {
            log.warn("Unable to park account", ignored);
        }
    }

    private Iterable<UUID> getFilteredSubscriptionIdsFor_UPCOMING_INVOICE_DryRun(@Nullable final DryRunArguments dryRunArguments, final BillingEventSet billingEvents) {
        if (dryRunArguments == null ||
            !dryRunArguments.getDryRunType().equals(DryRunType.UPCOMING_INVOICE) ||
            (dryRunArguments.getSubscriptionId() == null && dryRunArguments.getBundleId() == null)) {
            return ImmutableList.<UUID>of();
        }

        if (dryRunArguments.getSubscriptionId() != null) {
            return ImmutableList.of(dryRunArguments.getSubscriptionId());
        }

        return Iterables.transform(Iterables.filter(billingEvents, new Predicate<BillingEvent>() {
            @Override
            public boolean apply(final BillingEvent input) {
                return input.getBundleId().equals(dryRunArguments.getBundleId());
            }
        }), new Function<BillingEvent, UUID>() {
            @Override
            public UUID apply(final BillingEvent input) {
                return input.getSubscriptionId();
            }
        });
    }

    private InvoiceWithFutureNotifications processAccountWithLockAndInputTargetDate(final UUID accountId,
                                                                                final LocalDate originalTargetDate,
                                                                                final BillingEventSet billingEvents,
                                                                                final AccountInvoices accountInvoices,
                                                                                final boolean isDryRun,
                                                                                final boolean isRescheduled,
                                                                                final LinkedList<PluginProperty> pluginProperties,
                                                                                final InternalCallContext internalCallContext) throws InvoiceApiException {
        final CallContext callContext = buildCallContext(internalCallContext);

        final ImmutableAccountData account;
        try {
            account = accountApi.getImmutableAccountDataById(accountId, internalCallContext);
        } catch (final AccountApiException e) {
            log.error("Unable to generate invoice for accountId='{}', a future notification has NOT been recorded", accountId, e);
            invoicePluginDispatcher.onFailureCall(originalTargetDate, null, accountInvoices.getInvoices(), isDryRun, isRescheduled, callContext, pluginProperties, internalCallContext);
            return null;
        }

        final DateTime rescheduleDate = invoicePluginDispatcher.priorCall(originalTargetDate, accountInvoices.getInvoices(), isDryRun, isRescheduled, callContext, pluginProperties, internalCallContext);
        if (rescheduleDate != null) {
            if (isDryRun) {
                log.warn("Ignoring rescheduleDate='{}', delayed scheduling is unsupported in dry-run", rescheduleDate);
            } else {
                final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(rescheduleDate, billingEvents, internalCallContext);
                commitInvoiceAndSetFutureNotifications(account, futureAccountNotifications, internalCallContext);
            }
            return null;
        }

        final InvoiceWithMetadata invoiceWithMetadata = generateKillBillInvoice(account, originalTargetDate, billingEvents, accountInvoices, internalCallContext);
        final DefaultInvoice invoice = invoiceWithMetadata.getInvoice();

        // Compute future notifications
        final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(invoiceWithMetadata, billingEvents, internalCallContext);

        // If invoice comes back null, there is nothing new to generate, we can bail early
        if (invoice == null) {
            invoicePluginDispatcher.onSuccessCall(originalTargetDate, null, accountInvoices.getInvoices(), isDryRun, isRescheduled, callContext, pluginProperties, internalCallContext);

            if (isDryRun) {
                log.info("Generated null dryRun invoice for accountId='{}', targetDate='{}'", accountId, originalTargetDate);
            } else {
                log.info("Generated null invoice for accountId='{}', targetDate='{}'", accountId, originalTargetDate);

                final BusInternalEvent event = new DefaultNullInvoiceEvent(accountId, clock.getUTCToday(),
                                                                           internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId(), internalCallContext.getUserToken());

                // Although we have a null invoice, it could be as a result of removing $0 USAGE (config#isUsageZeroAmountDisabled)
                // and so we may still need to set the CTD for such subscriptions.
                setChargedThroughDatesNoExceptions(invoiceWithMetadata.getChargeThroughDates(), internalCallContext);
                commitInvoiceAndSetFutureNotifications(account, futureAccountNotifications, internalCallContext);
                postEvent(event);
            }
            return null;
        }

        final LocalDate actualTargetDate = invoice.getTargetDate();
        boolean success = false;
        try {
            // Generate missing credit (> 0 for generation and < 0 for use) prior we call the plugin(s)
            final InvoiceItem cbaItemPreInvoicePlugins = computeCBAOnExistingInvoice(invoice, internalCallContext);
            if (cbaItemPreInvoicePlugins != null) {
                invoice.addInvoiceItem(cbaItemPreInvoicePlugins);
            }

            //
            // Ask external invoice plugins if additional items (tax, etc) shall be added to the invoice
            //
            final boolean invoiceUpdated = invoicePluginDispatcher.updateOriginalInvoiceWithPluginInvoiceItems(invoice, isDryRun, callContext, pluginProperties, internalCallContext);
            if (invoiceUpdated) {
                // Remove the temporary CBA item as we need to re-compute CBA
                if (cbaItemPreInvoicePlugins != null) {
                    invoice.removeInvoiceItem(cbaItemPreInvoicePlugins);
                }

                // Use credit after we call the plugin (https://github.com/killbill/killbill/issues/637)
                final InvoiceItem cbaItemPostInvoicePlugins = computeCBAOnExistingInvoice(invoice, internalCallContext);
                if (cbaItemPostInvoicePlugins != null) {
                    invoice.addInvoiceItem(cbaItemPostInvoicePlugins);
                }
            }

            if (!isDryRun) {
                // Compute whether this is a new invoice object (or just some adjustments on an existing invoice), and extract invoiceIds for later use
                final Set<UUID> uniqueInvoiceIds = getUniqueInvoiceIds(invoice);
                final boolean isRealInvoiceWithItems = uniqueInvoiceIds.remove(invoice.getId());
                final Set<UUID> adjustedUniqueOtherInvoiceId = uniqueInvoiceIds;

                logInvoiceWithItems(account, invoice, actualTargetDate, adjustedUniqueOtherInvoiceId, isRealInvoiceWithItems);

                // Transformation to Invoice -> InvoiceModelDao
                final InvoiceModelDao invoiceModelDao = new InvoiceModelDao(invoice);
                final List<InvoiceItemModelDao> invoiceItemModelDaos = transformToInvoiceModelDao(invoice.getInvoiceItems());
                invoiceModelDao.addInvoiceItems(invoiceItemModelDaos);

                final Set<InvoiceTrackingModelDao> trackingIds = new HashSet<>();
                for (final TrackingRecordId cur : invoiceWithMetadata.getTrackingIds()) {
                    trackingIds.add(new InvoiceTrackingModelDao(cur.getTrackingId(), cur.getInvoiceId(), cur.getSubscriptionId(), cur.getUnitType(), cur.getRecordDate()));
                }

                // Commit invoice on disk
                final ExistingInvoiceMetadata existingInvoiceMetadata = new ExistingInvoiceMetadata(accountInvoices.getInvoices());
                commitInvoiceAndSetFutureNotifications(account, invoiceModelDao, billingEvents, trackingIds, futureAccountNotifications, existingInvoiceMetadata, internalCallContext);
                setChargedThroughDatesNoExceptions(invoiceWithMetadata.getChargeThroughDates(), internalCallContext);
                success = true;
            }
        } finally {
            // Make sure we always set future notifications in case of errors
            if (!isDryRun && !success) {
                commitInvoiceAndSetFutureNotifications(account, futureAccountNotifications, internalCallContext);
            }

            if (isDryRun || success) {
                final DefaultInvoice refreshedInvoice = isDryRun ? invoice : new DefaultInvoice(invoiceDao.getById(invoice.getId(), internalCallContext));
                invoicePluginDispatcher.onSuccessCall(actualTargetDate, refreshedInvoice, accountInvoices.getInvoices(), isDryRun, isRescheduled, callContext, pluginProperties, internalCallContext);
            } else {
                invoicePluginDispatcher.onFailureCall(actualTargetDate, invoice, accountInvoices.getInvoices(), isDryRun, isRescheduled, callContext, pluginProperties, internalCallContext);
            }
        }

        return new InvoiceWithFutureNotifications(invoice, futureAccountNotifications);
    }

    private InvoiceWithMetadata generateKillBillInvoice(final ImmutableAccountData account, final LocalDate targetDate, final BillingEventSet billingEvents, final AccountInvoices accountInvoices, final InternalCallContext context) throws InvoiceApiException {
        final UUID targetInvoiceId;
        // Filter out DRAFT invoices for computation  of existing items unless Account is in AUTO_INVOICING_REUSE_DRAFT
        if (billingEvents.isAccountAutoInvoiceReuseDraft()) {
            final Invoice existingDraft = Iterables.tryFind(accountInvoices.getInvoices(), new Predicate<Invoice>() {
                @Override
                public boolean apply(final Invoice input) {
                    return input.getStatus() == InvoiceStatus.DRAFT;
                }
            }).orNull();
            targetInvoiceId = existingDraft != null ? existingDraft.getId() : null;
        } else {
            targetInvoiceId = null;
        }

        return generator.generateInvoice(account, billingEvents, accountInvoices, targetInvoiceId, targetDate, account.getCurrency(), context);
    }

    private FutureAccountNotifications createNextFutureNotificationDate(final DateTime rescheduleDate, final BillingEventSet billingEvents, final InternalCallContext context) {
        final FutureAccountNotificationsBuilder notificationsBuilder = new FutureAccountNotificationsBuilder();
        notificationsBuilder.setRescheduled(true);

        final Set<UUID> subscriptionIds = ImmutableSet.<UUID>copyOf(Iterables.<BillingEvent, UUID>transform(billingEvents,
                                                                                new Function<BillingEvent, UUID>() {
                                                                                @Override
                                                                                public UUID apply(final BillingEvent billingEvent) {
                                                                                return billingEvent.getSubscriptionId();
                                                                                }
                                                                                }));
        populateNextFutureNotificationDate(rescheduleDate, subscriptionIds, notificationsBuilder, context);

        // Even though a plugin forced us to reschedule the invoice generation, honor the dry run notifications settings
        populateNextFutureDryRunNotificationDate(billingEvents, notificationsBuilder, context);

        return notificationsBuilder.build();
    }

    private void populateNextFutureNotificationDate(final DateTime notificationDateTime, final Set<UUID> subscriptionIds, final FutureAccountNotificationsBuilder notificationsBuilder, final InternalCallContext context) {
        final LocalDate notificationDate = context.toLocalDate(notificationDateTime);
        notificationsBuilder.setNotificationListForTrigger(ImmutableMap.<LocalDate, Set<UUID>>of(notificationDate, subscriptionIds));
    }

    private FutureAccountNotifications createNextFutureNotificationDate(final InvoiceWithMetadata invoiceWithMetadata, final BillingEventSet billingEvents, final InternalCallContext context) {
        final FutureAccountNotificationsBuilder notificationsBuilder = new FutureAccountNotificationsBuilder();
        populateNextFutureNotificationDate(invoiceWithMetadata, notificationsBuilder);
        populateNextFutureDryRunNotificationDate(billingEvents, notificationsBuilder, context);
        return notificationsBuilder.build();
    }

    private void populateNextFutureNotificationDate(final InvoiceWithMetadata invoiceWithMetadata, final FutureAccountNotificationsBuilder notificationsBuilder) {
        final Map<LocalDate, Set<UUID>> notificationListForTrigger = new HashMap<LocalDate, Set<UUID>>();

        for (final UUID subscriptionId : invoiceWithMetadata.getPerSubscriptionFutureNotificationDates().keySet()) {

            final SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = invoiceWithMetadata.getPerSubscriptionFutureNotificationDates().get(subscriptionId);

            if (subscriptionFutureNotificationDates.getNextRecurringDate() != null) {
                Set<UUID> subscriptionsForDates = notificationListForTrigger.get(subscriptionFutureNotificationDates.getNextRecurringDate());
                if (subscriptionsForDates == null) {
                    subscriptionsForDates = new HashSet<UUID>();
                    notificationListForTrigger.put(subscriptionFutureNotificationDates.getNextRecurringDate(), subscriptionsForDates);
                }
                subscriptionsForDates.add(subscriptionId);
            }

            if (subscriptionFutureNotificationDates.getNextUsageDates() != null) {
                for (final UsageDef usageDef : subscriptionFutureNotificationDates.getNextUsageDates().keySet()) {

                    final LocalDate nextNotificationDateForUsage = subscriptionFutureNotificationDates.getNextUsageDates().get(usageDef);
                    Set<UUID> subscriptionsForDates = notificationListForTrigger.get(nextNotificationDateForUsage);
                    if (subscriptionsForDates == null) {
                        subscriptionsForDates = new HashSet<UUID>();
                        notificationListForTrigger.put(nextNotificationDateForUsage, subscriptionsForDates);
                    }
                    subscriptionsForDates.add(subscriptionId);
                }
            }
        }
        notificationsBuilder.setNotificationListForTrigger(notificationListForTrigger);
    }

    private void populateNextFutureDryRunNotificationDate(final BillingEventSet billingEvents, final FutureAccountNotificationsBuilder notificationsBuilder, final InternalCallContext context) {

        final Map<LocalDate, Set<UUID>> notificationListForTrigger = notificationsBuilder.getNotificationListForTrigger();

        final long dryRunNotificationTime = invoiceConfig.getDryRunNotificationSchedule(context).getMillis();
        final boolean isInvoiceNotificationEnabled = dryRunNotificationTime > 0;

        final Map<LocalDate, Set<UUID>> notificationListForDryRun = isInvoiceNotificationEnabled ? new HashMap<LocalDate, Set<UUID>>() : ImmutableMap.<LocalDate, Set<UUID>>of();
        if (isInvoiceNotificationEnabled) {
            for (final Entry<LocalDate, Set<UUID>> entry : notificationListForTrigger.entrySet()) {
                final LocalDate curDate = entry.getKey();
                final LocalDate curDryRunDate = context.toLocalDate(context.toUTCDateTime(curDate).minus(dryRunNotificationTime));
                Set<UUID> subscriptionsForDryRunDates = notificationListForDryRun.get(curDryRunDate);
                if (subscriptionsForDryRunDates == null) {
                    subscriptionsForDryRunDates = new HashSet<UUID>();
                    notificationListForDryRun.put(curDryRunDate, subscriptionsForDryRunDates);
                }
                subscriptionsForDryRunDates.addAll(entry.getValue());
            }

            final Map<UUID, DateTime> upcomingTransitionsForSubscriptions = getNextTransitionsForSubscriptions(billingEvents);

            for (final Entry<UUID, DateTime> entry : upcomingTransitionsForSubscriptions.entrySet()) {
                final LocalDate curDryRunDate = context.toLocalDate(entry.getValue().minus(dryRunNotificationTime));
                Set<UUID> subscriptionsForDryRunDates = notificationListForDryRun.get(curDryRunDate);
                if (subscriptionsForDryRunDates == null) {
                    subscriptionsForDryRunDates = new HashSet<UUID>();
                    notificationListForDryRun.put(curDryRunDate, subscriptionsForDryRunDates);
                }
                subscriptionsForDryRunDates.add(entry.getKey());
            }
        }
        notificationsBuilder.setNotificationListForDryRun(notificationListForDryRun);
    }

    private List<InvoiceItemModelDao> transformToInvoiceModelDao(final List<InvoiceItem> invoiceItems) {
        return Lists.transform(invoiceItems,
                               new Function<InvoiceItem, InvoiceItemModelDao>() {
                                   @Override
                                   public InvoiceItemModelDao apply(final InvoiceItem input) {
                                       return new InvoiceItemModelDao(input);
                                   }
                               });
    }

    private Set<UUID> getUniqueInvoiceIds(final Invoice invoice) {
        final Set<UUID> uniqueInvoiceIds = new TreeSet<UUID>();
        for (final InvoiceItem invoiceItem : invoice.getInvoiceItems()) {
            uniqueInvoiceIds.add(invoiceItem.getInvoiceId());
        }
        return uniqueInvoiceIds;
    }

    private void logInvoiceWithItems(final ImmutableAccountData account, final Invoice invoice, final LocalDate targetDate, final Set<UUID> adjustedUniqueOtherInvoiceId, final boolean isRealInvoiceWithItems) {
        final StringBuilder tmp = new StringBuilder();
        if (isRealInvoiceWithItems) {
            tmp.append(String.format("Generated invoiceId='%s', numberOfItems='%d', accountId='%s', targetDate='%s':", invoice.getId(), invoice.getNumberOfItems(), account.getId(), targetDate));
        } else {
            final String adjustedInvoices = JOINER_COMMA.join(adjustedUniqueOtherInvoiceId.toArray(new UUID[adjustedUniqueOtherInvoiceId.size()]));
            tmp.append(String.format("Adjusting existing invoiceId='%s', numberOfItems='%d', accountId='%s', targetDate='%s':%n",
                                     adjustedInvoices, invoice.getNumberOfItems(), account.getId(), targetDate));
        }
        int n = 0;
        for (final InvoiceItem item : invoice.getInvoiceItems()) {
            if (n > MAX_NB_ITEMS_TO_PRINT) {
                // https://github.com/killbill/killbill/issues/1337
                tmp.append(String.format("%n\t... and %s more ...", invoice.getNumberOfItems() - n));
                break;
            }
            tmp.append(String.format("%n\t item = %s", item));
            n++;
        }
        log.info(tmp.toString());
    }

    private void commitInvoiceAndSetFutureNotifications(final ImmutableAccountData account,
                                                        final FutureAccountNotifications futureAccountNotifications,
                                                        final InternalCallContext context) {
        commitInvoiceAndSetFutureNotifications(account, null, null, ImmutableSet.of(), futureAccountNotifications, null, context);
    }

    private void commitInvoiceAndSetFutureNotifications(final ImmutableAccountData account,
                                                        @Nullable final InvoiceModelDao invoiceModelDao,
                                                        final BillingEventSet billingEvents,
                                                        final Set<InvoiceTrackingModelDao> trackingIds,
                                                        final FutureAccountNotifications futureAccountNotifications,
                                                        final ExistingInvoiceMetadata existingInvoiceMetadata,
                                                        final InternalCallContext context) {
        final boolean isThereAnyItemsLeft = invoiceModelDao != null && !invoiceModelDao.getInvoiceItems().isEmpty();
        if (isThereAnyItemsLeft) {
            invoiceDao.createInvoice(invoiceModelDao, billingEvents, trackingIds, futureAccountNotifications, existingInvoiceMetadata, context);
        } else {
            invoiceDao.setFutureAccountNotificationsForEmptyInvoice(account.getId(), futureAccountNotifications, context);
        }
    }

    private InvoiceItem computeCBAOnExistingInvoice(final Invoice invoice, final InternalCallContext context) throws InvoiceApiException {
        // Transformation to Invoice -> InvoiceModelDao
        final InvoiceModelDao invoiceModelDao = new InvoiceModelDao(invoice);
        final List<InvoiceItemModelDao> invoiceItemModelDaos = ImmutableList.copyOf(Collections2.transform(invoice.getInvoiceItems(),
                                                                                new Function<InvoiceItem, InvoiceItemModelDao>() {
                                                                                @Override
                                                                                public InvoiceItemModelDao apply(final InvoiceItem input) {
                                                                                return new InvoiceItemModelDao(input);
                                                                                }
                                                                                }));
        invoiceModelDao.addInvoiceItems(invoiceItemModelDaos);
        final InvoiceItemModelDao cbaItem = invoiceDao.doCBAComplexity(invoiceModelDao, context);
        return cbaItem != null ? InvoiceItemFactory.fromModelDao(cbaItem) : null;
    }

    private TenantContext buildTenantContext(final InternalTenantContext context) {
        return internalCallContextFactory.createTenantContext(context);
    }

    private CallContext buildCallContext(final InternalCallContext context) {
        return internalCallContextFactory.createCallContext(context);
    }

    public void setChargedThroughDates(final Invoice invoice, final InternalCallContext context) throws InvoiceApiException {
        final Map<UUID, DateTime> chargeThroughDates = InvoiceWithMetadata.computeChargedThroughDates(invoice, context);
        setChargedThroughDates(chargeThroughDates, context);
    }

    // TODO we should revisit this logic of swallowing the exception here -- especially in a use case where we use a catalog plugin
    private void setChargedThroughDatesNoExceptions(final Map<UUID, DateTime> chargeThroughDates, final InternalCallContext context) {
        try {
            setChargedThroughDates(chargeThroughDates, context);
        } catch (final InvoiceApiException e) {
            log.error(e.getMessage(), e);
        }
    }

    private void setChargedThroughDates(final Map<UUID, DateTime> chargeThroughDates, final InternalCallContext context) throws InvoiceApiException {
        try {
            for (final Entry<UUID, DateTime> entry : chargeThroughDates.entrySet()) {
                if (entry.getKey() != null) {
                    final DateTime chargeThroughDate = entry.getValue();
                    subscriptionApi.setChargedThroughDate(entry.getKey(), chargeThroughDate, context);
                }
            }
        } catch (final SubscriptionBaseApiException e) {
            throw new InvoiceApiException(ErrorCode.UNEXPECTED_ERROR, "Failed to set chargedThroughDates", e);
        }
    }

    private void postEvent(final BusInternalEvent event) {
        try {
            eventBus.post(event);
        } catch (final EventBusException e) {
            log.warn("Failed to post event {}", event, e);
        }
    }

    public static class FutureAccountNotifications {

        private final Map<LocalDate, Set<UUID>> notificationListForTrigger;
        private final Map<LocalDate, Set<UUID>> notificationListForDryRun;
        private final boolean isRescheduled;

        public FutureAccountNotifications() {
            this(ImmutableMap.<LocalDate, Set<UUID>>of(), ImmutableMap.<LocalDate, Set<UUID>>of(), false);
        }

        public FutureAccountNotifications(final Map<LocalDate, Set<UUID>> notificationListForTrigger, final Map<LocalDate, Set<UUID>> notificationListForDryRun, final boolean isRescheduled) {
            this.notificationListForTrigger = notificationListForTrigger;
            this.notificationListForDryRun = notificationListForDryRun;
            this.isRescheduled = isRescheduled;
        }

        public Map<LocalDate, Set<UUID>> getNotificationsForTrigger() {
            return notificationListForTrigger;
        }

        public Map<LocalDate, Set<UUID>> getNotificationsForDryRun() {
            return notificationListForDryRun;
        }

        public boolean isRescheduled() {
            return isRescheduled;
        }

        public static class FutureAccountNotificationsBuilder {

            private Map<LocalDate, Set<UUID>> notificationListForTrigger;
            private Map<LocalDate, Set<UUID>> notificationListForDryRun;
            private boolean isRescheduled = false;

            public FutureAccountNotificationsBuilder() {
            }

            public FutureAccountNotificationsBuilder setNotificationListForTrigger(final Map<LocalDate, Set<UUID>> notificationListForTrigger) {
                this.notificationListForTrigger = notificationListForTrigger;
                return this;
            }

            public FutureAccountNotificationsBuilder setNotificationListForDryRun(final Map<LocalDate, Set<UUID>> notificationListForDryRun) {
                this.notificationListForDryRun = notificationListForDryRun;
                return this;
            }

            public FutureAccountNotificationsBuilder setRescheduled(final boolean rescheduled) {
                isRescheduled = rescheduled;
                return this;
            }

            public Map<LocalDate, Set<UUID>> getNotificationListForTrigger() {
                return MoreObjects.firstNonNull(notificationListForTrigger, ImmutableMap.<LocalDate, Set<UUID>>of());
            }

            public Map<LocalDate, Set<UUID>> getNotificationListForDryRun() {
                return MoreObjects.firstNonNull(notificationListForDryRun, ImmutableMap.<LocalDate, Set<UUID>>of());
            }

            public boolean isRescheduled() {
                return isRescheduled;
            }

            public FutureAccountNotifications build() {
                return new FutureAccountNotifications(getNotificationListForTrigger(), getNotificationListForDryRun(), isRescheduled());
            }
        }
    }

    private Set<LocalDate> getUpcomingInvoiceCandidateDates(final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications,
                                                            final Map<UUID, DateTime> nextScheduledSubscriptionsEventMap,
                                                            final Iterable<UUID> filteredSubscriptionIds,
                                                            final InternalCallContext internalCallContext) {

        final Iterable<DateTime> nextScheduledInvoiceDates = getNextScheduledInvoiceEffectiveDate(futureNotifications, filteredSubscriptionIds);

        final Iterable<DateTime> nextScheduledSubscriptionsEvents;
        if (!Iterables.isEmpty(filteredSubscriptionIds)) {
            final List<DateTime> tmp = new ArrayList<DateTime>();
            for (final Entry<UUID, DateTime> entry : nextScheduledSubscriptionsEventMap.entrySet()) {
                if (Iterables.contains(filteredSubscriptionIds, entry.getKey())) {
                    tmp.add(entry.getValue());
                }
            }
            nextScheduledSubscriptionsEvents = tmp;
        } else {
            nextScheduledSubscriptionsEvents = nextScheduledSubscriptionsEventMap.values();
        }

        return Sets.newTreeSet(Iterables.transform(Iterables.<DateTime>concat(nextScheduledInvoiceDates, nextScheduledSubscriptionsEvents),
                                                   new Function<DateTime, LocalDate>() {
                                                       @Override
                                                       public LocalDate apply(final DateTime input) {
                                                           return internalCallContext.toLocalDate(input);
                                                       }
                                                   }));
    }

    private Iterable<DateTime> getNextScheduledInvoiceEffectiveDate(final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications,
                                                                    final Iterable<UUID> filteredSubscriptionIds) {
        final Collection<DateTime> effectiveDates = new LinkedList<DateTime>();
        for (final NotificationEventWithMetadata<NextBillingDateNotificationKey> input : futureNotifications) {

            // If we don't specify a filter list of subscriptionIds, we look at all events.
            boolean isEventForSubscription = Iterables.isEmpty(filteredSubscriptionIds);
            // If we specify a filter, we keep the date if at least one of the subscriptions from the event list matches one of the subscription from our filter list
            if (!Iterables.isEmpty(filteredSubscriptionIds)) {
                for (final UUID curSubscriptionId : filteredSubscriptionIds) {
                    if (Iterables.contains(input.getEvent().getUuidKeys(), curSubscriptionId)) {
                        isEventForSubscription = true;
                        break;
                    }
                }
            }

            final boolean isEventDryRunForNotifications = input.getEvent().isDryRunForInvoiceNotification() != null ?
                                                          input.getEvent().isDryRunForInvoiceNotification() : false;
            if (isEventForSubscription && !isEventDryRunForNotifications) {
                effectiveDates.add(input.getEffectiveDate());
            }
        }
        return effectiveDates;
    }

    private static final class TargetDateDryRunArguments implements DryRunArguments {

        @Override
        public DryRunType getDryRunType() {
            return DryRunType.TARGET_DATE;
        }

        @Override
        public EntitlementSpecifier getEntitlementSpecifier() {
            return null;
        }

        @Override
        public SubscriptionEventType getAction() {
            return null;
        }

        @Override
        public UUID getSubscriptionId() {
            return null;
        }

        @Override
        public LocalDate getEffectiveDate() {
            return null;
        }

        @Override
        public UUID getBundleId() {
            return null;
        }

        @Override
        public BillingActionPolicy getBillingActionPolicy() {
            return null;
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("TargetDateDryRunArguments{");
            sb.append("dryRunType=").append(DryRunType.TARGET_DATE);
            sb.append('}');
            return sb.toString();
        }
    }

    public void processParentInvoiceForInvoiceGeneration(final Account childAccount, final UUID childInvoiceId, final InternalCallContext context) throws InvoiceApiException {
        GlobalLock lock = null;
        try {
            lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), childAccount.getParentAccountId().toString(), invoiceConfig.getMaxGlobalLockRetries());

            processParentInvoiceForInvoiceGenerationWithLock(childAccount, childInvoiceId, context);
        } catch (final LockFailedException e) {
            log.warn("Failed to process parent invoice for parentAccountId='{}'", childAccount.getParentAccountId().toString(), e);
        } finally {
            if (lock != null) {
                lock.release();
            }
        }
    }

    private void processParentInvoiceForInvoiceGenerationWithLock(final Account childAccount, final UUID childInvoiceId, final InternalCallContext context) throws InvoiceApiException {
        log.info("Processing parent invoice for parentAccountId='{}', childInvoiceId='{}'", childAccount.getParentAccountId(), childInvoiceId);
        final InvoiceModelDao childInvoiceModelDao = invoiceDao.getById(childInvoiceId, context);
        final Invoice childInvoice = new DefaultInvoice(childInvoiceModelDao);

        final Long parentAccountRecordId = internalCallContextFactory.getRecordIdFromObject(childAccount.getParentAccountId(), ObjectType.ACCOUNT, buildTenantContext(context));
        final InternalCallContext parentContext = internalCallContextFactory.createInternalCallContext(parentAccountRecordId, context);

        final BigDecimal childInvoiceAmount = InvoiceCalculatorUtils.computeChildInvoiceAmount(childInvoice.getCurrency(), childInvoice.getInvoiceItems());
        InvoiceModelDao draftParentInvoice = invoiceDao.getParentDraftInvoice(childAccount.getParentAccountId(), parentContext);

        final String description = childAccount.getExternalKey().concat(" summary");
        if (draftParentInvoice != null) {
            for (final InvoiceItemModelDao item : draftParentInvoice.getInvoiceItems()) {
                if ((item.getChildAccountId() != null) && item.getChildAccountId().equals(childInvoice.getAccountId())) {
                    // update child item amount for existing parent invoice item
                    final BigDecimal newChildInvoiceAmount = childInvoiceAmount.add(item.getAmount());
                    log.info("Updating existing itemId='{}', oldAmount='{}', newAmount='{}' on existing DRAFT invoiceId='{}'", item.getId(), item.getAmount(), newChildInvoiceAmount, draftParentInvoice.getId());
                    invoiceDao.updateInvoiceItemAmount(item.getId(), newChildInvoiceAmount, parentContext);
                    return;
                }
            }

            // new item when the parent invoices does not have this child item yet
            final ParentInvoiceItem newParentInvoiceItem = new ParentInvoiceItem(UUID.randomUUID(), context.getCreatedDate(), draftParentInvoice.getId(), childAccount.getParentAccountId(), childAccount.getId(), childInvoiceAmount, childAccount.getCurrency(), description);
            final InvoiceItemModelDao parentInvoiceItem = new InvoiceItemModelDao(newParentInvoiceItem);
            draftParentInvoice.addInvoiceItem(parentInvoiceItem);

            final List<InvoiceModelDao> invoices = new ArrayList<InvoiceModelDao>();
            invoices.add(draftParentInvoice);
            log.info("Adding new itemId='{}', amount='{}' on existing DRAFT invoiceId='{}'", parentInvoiceItem.getId(), childInvoiceAmount, draftParentInvoice.getId());
            invoiceDao.createInvoices(invoices, null, ImmutableSet.of(), parentContext);
        } else {
            if (shouldIgnoreChildInvoice(childInvoice, childInvoiceAmount)) {
                return;
            }

            final LocalDate invoiceDate = context.toLocalDate(context.getCreatedDate());
            draftParentInvoice = new InvoiceModelDao(childAccount.getParentAccountId(), invoiceDate, childAccount.getCurrency(), InvoiceStatus.DRAFT, true);
            final InvoiceItem parentInvoiceItem = new ParentInvoiceItem(UUID.randomUUID(), context.getCreatedDate(), draftParentInvoice.getId(), childAccount.getParentAccountId(), childAccount.getId(), childInvoiceAmount, childAccount.getCurrency(), description);
            draftParentInvoice.addInvoiceItem(new InvoiceItemModelDao(parentInvoiceItem));

            log.info("Adding new itemId='{}', amount='{}' on new DRAFT invoiceId='{}'", parentInvoiceItem.getId(), childInvoiceAmount, draftParentInvoice.getId());
            invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(draftParentInvoice), null, ImmutableSet.of(), parentContext);
        }

        // save parent child invoice relation
        final InvoiceParentChildModelDao invoiceRelation = new InvoiceParentChildModelDao(draftParentInvoice.getId(), childInvoiceId, childAccount.getId());
        invoiceDao.createParentChildInvoiceRelation(invoiceRelation, parentContext);
    }

    private boolean shouldIgnoreChildInvoice(final Invoice childInvoice, final BigDecimal childInvoiceAmount) {

        switch (childInvoiceAmount.compareTo(BigDecimal.ZERO)) {
            case -1:
                // do nothing if child invoice has negative amount because it's a credit and it will be use in next invoice
                return true;
            case 1:
                return false;
            case 0:
                // only ignore if amount == 0 and any item is not FIXED or RECURRING
                for (final InvoiceItem item : childInvoice.getInvoiceItems()) {
                    if (item.getInvoiceItemType().equals(InvoiceItemType.FIXED) || item.getInvoiceItemType().equals(InvoiceItemType.RECURRING)) {
                        return false;
                    }
                }
        }

        return true;
    }

    public void processParentInvoiceForAdjustments(final Account childAccount, final UUID childInvoiceId, final InternalCallContext context) throws InvoiceApiException {
        GlobalLock lock = null;
        try {
            lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), childAccount.getParentAccountId().toString(), invoiceConfig.getMaxGlobalLockRetries());

            processParentInvoiceForAdjustmentsWithLock(childAccount, childInvoiceId, context);
        } catch (final LockFailedException e) {
            log.warn("Failed to process parent invoice for parentAccountId='{}'", childAccount.getParentAccountId().toString(), e);
        } finally {
            if (lock != null) {
                lock.release();
            }
        }
    }

    public void processParentInvoiceForAdjustmentsWithLock(final Account account, final UUID childInvoiceId, final InternalCallContext context) throws InvoiceApiException {
        final InvoiceModelDao childInvoiceModelDao = invoiceDao.getById(childInvoiceId, context);
        final InvoiceModelDao parentInvoiceModelDao = childInvoiceModelDao.getParentInvoice();

        if (parentInvoiceModelDao == null) {
            throw new InvoiceApiException(ErrorCode.INVOICE_MISSING_PARENT_INVOICE, childInvoiceModelDao.getId());
        } else if (InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(parentInvoiceModelDao).compareTo(BigDecimal.ZERO) == 0) {
            // ignore item adjustments for paid invoices.
            return;
        }

        final Long parentAccountRecordId = internalCallContextFactory.getRecordIdFromObject(account.getParentAccountId(), ObjectType.ACCOUNT, buildTenantContext(context));
        final InternalCallContext parentContext = internalCallContextFactory.createInternalCallContext(parentAccountRecordId, context);
        final String description = "Adjustment for account ".concat(account.getExternalKey());

        // find PARENT_SUMMARY invoice item for this child account
        final InvoiceItemModelDao parentSummaryInvoiceItem = Iterables.find(parentInvoiceModelDao.getInvoiceItems(), new Predicate<InvoiceItemModelDao>() {
            @Override
            public boolean apply(final InvoiceItemModelDao input) {
                return input.getType().equals(InvoiceItemType.PARENT_SUMMARY)
                       && input.getChildAccountId().equals(childInvoiceModelDao.getAccountId());
            }
        });

        final Iterable<InvoiceItemModelDao> childAdjustments = Iterables.filter(childInvoiceModelDao.getInvoiceItems(), new Predicate<InvoiceItemModelDao>() {
            @Override
            public boolean apply(final InvoiceItemModelDao input) {
                return input.getType().equals(InvoiceItemType.ITEM_ADJ);
            }
        });

        //  childAdjustments can be empty if event was a result of a CBA_ADJ
        if (Iterables.isEmpty(childAdjustments)) {
            return;
        }

        // find last ITEM_ADJ invoice added in child invoice
        final InvoiceItemModelDao lastChildInvoiceItemAdjustment = Collections.max(Lists.newArrayList(childAdjustments), new Comparator<InvoiceItemModelDao>() {
            @Override
            public int compare(final InvoiceItemModelDao o1, final InvoiceItemModelDao o2) {
                return o1.getCreatedDate().compareTo(o2.getCreatedDate());
            }
        });

        final BigDecimal childInvoiceAdjustmentAmount = lastChildInvoiceItemAdjustment.getAmount();

        if (parentInvoiceModelDao.getStatus().equals(InvoiceStatus.COMMITTED)) {
            final ItemAdjInvoiceItem adj = new ItemAdjInvoiceItem(UUIDs.randomUUID(),
                                                                  lastChildInvoiceItemAdjustment.getCreatedDate(),
                                                                  parentSummaryInvoiceItem.getInvoiceId(),
                                                                  parentSummaryInvoiceItem.getAccountId(),
                                                                  lastChildInvoiceItemAdjustment.getStartDate(),
                                                                  description,
                                                                  childInvoiceAdjustmentAmount,
                                                                  parentInvoiceModelDao.getCurrency(),
                                                                  parentSummaryInvoiceItem.getId(),
                                                                  null);
            parentInvoiceModelDao.addInvoiceItem(new InvoiceItemModelDao(adj));
            invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(parentInvoiceModelDao), null, ImmutableSet.of(), parentContext);
            return;
        }

        // update item amount
        final BigDecimal newParentInvoiceItemAmount = childInvoiceAdjustmentAmount.add(parentSummaryInvoiceItem.getAmount());
        invoiceDao.updateInvoiceItemAmount(parentSummaryInvoiceItem.getId(), newParentInvoiceItemAmount, parentContext);
    }

    private static class InvoiceWithFutureNotifications {

        private final Invoice invoice;
        private final FutureAccountNotifications notifications;

        public InvoiceWithFutureNotifications(final Invoice invoice, final FutureAccountNotifications notifications) {
            this.invoice = invoice;
            this.notifications = notifications;
        }

        public Invoice getInvoice() {
            return invoice;
        }

        public FutureAccountNotifications getNotifications() {
            return notifications;
        }
    }

}

redpajama_set_name: RedPajamaGithub


text:
WATCH! President Elect Donald Trump and Bob Johnson; Van Jones and T.D. Jakes; and Fake News
Civil Rights & Civil Christianity in America, Government Mountain, Media Mountain, Original Intent of the Church, Original Intent of The U.S. Constitution, Religion Mountain, VFNtv Programs, WATCH FULL PROGRAM
"Let's give [Trump] a shot" says Bob Johnson, Founder of B.E.T.
On more than one occasion President Elect Donald Trump has expressed that he is not a politician, nor is he looking for the talents of politicians to fill one of the vacancies in his cabinet. This couldn't be more evident in his recent invitation to Robert "Bob" Johnson, businessman and founder of B.E.T., Black Entertainment Television. The details of the specific position offered to Johnson has yet to be disclosed, yet Johnson was very clear about his support of President Elect Trump.
When discussing the topic of President Elect Trump while on CNBC, Johnson shared, "I'm a Democrat and I say this with all due respect to the Democratic Party and I say this to the Republican Party. This country needs somebody that is going to lead it not somebody who is going to force us to choose sides…" This is such an encouraging exchange, Johnson and President Elect Trump, to see someone voted into office by the people of the nation, and then to see that leader invite people to discuss important matters AND listen to what they share! Johnson also squelched accusations that Trump is a racist when he reportedly pointed out, "he never thought Trump is a racist or anti-African American", as reported by CBN News. See the full dialogue and response from Robert Johnson, and so much more. Also shared in this segment: leadership, Constitution, freedom, and President Richard Nixon. Greg and Steve shared in this segment.
Van Jones & T.D. Jakes "Can You Help Us? Can you Help America?"
Bishop T.D. Jakes has been continually known as a faithful leader in the Body of Christ and continually encouraged those around him as well as those in our nation. Recently, God has additionally blessed him with his own program "The T.D. Jakes Show". Not only has he been outspoken but he has also continually touched on many issues that our nation is facing. When Van Jones, a commentator with CNN, spoke with Jakes, they tackled the topic of many of the crises that we are witnessing.
Jakes brings immediate clarity to the reality by sharing, "Most of those uprisings are often asking for the same thing. I cannot see why we cannot get together."He continues to highlight how many people do not feel that what the "parties" are discussing includes what people are truly deeming important. "What the election showed me is that neither party actually knew their constituency like they thought they did." As valid of a point it was that Jakes made, Jones brings to light where the common disconnect is when we consider a "party". "When you say "party", in your mind, you think voters. But the people who run the party, they think donors. See these are donor owned parties, not voter owned parties. And so now, the voters are rebelling…"
As all the media coverage of the Presidential election comes to a close, the eyes of America can clearly see that the media has no idea how things are going to end up, regardless of how much they apply their own interpretations or desires on the events. Media is scrambling for answers. They are looking for the truth. Now, they are coming to the church, to find the help they are so desperately looking for. Jones points out to Jakes that he has the ability to connect with people, to "bridge" with them, that others don't. "Very few people can bridge like you can bridge. Its common pain but we don't have common purpose." In an air of humor with subtle truth, Jones adds, "Part of the reason I wanted to be on the show was, can you help us? Can you help us? CAN YOU HELP AMERICA?!!" Right now, were seeing the world trying to have a conversation that only the Church can have.
Van Jones has gone through his own journey through life as people have accused him of his ties with Communism and questioning his true motives while he was a special adviser in the Obama Administration. But, as the Church, it is important that we love every person with the Gospel of Jesus Christ, regardless of who they are. It is not for us to point fingers, but to be the carriers of the light that God calls us to be. As media is recognizing that they do not have the answer, it is the Church that must recognize that WE have the answer! See the full conversation about this important truth, and so much more. Also shared in this segment: political parties, division, media, conversation, communism, Valerie Jarrett, pointing fingers, and bridge building. Greg and Steve shared in this segment.
List of "Fake News" Sources? Facebook being told to transform from a Technology company to a Media company? How can one News Source dictate the Decisions of Another News Source?
As we conclude the myriad of media coverage of the Presidential races and debates, and now begin the transition to the next President, the nation is still finding itself responding to the shocking reality that not even the media knew how the elections were going to turn out. Amidst the many wrong projections that were hyped and flaunted, now a list of "fake news" sites are being spread by Los Angeles Times, as is being reported by CBN News. When CBN News sat down with Dan Gainor, the vice president of Business and Culture for the Media Research Center, he expanded on the reality of the "fake news" in the media.
When noticing that sources such as "The Blaze" and "The Independent Journal Review" made the list, Gainor was asked if conservative sources are being targeted. "Well absolutely…the left is furious about the result of the election. They did everything, they threw everything, not just the kitchen sink, they threw the kitchen at Donald Trump and it didn't work." Gainor hits the nail on the head pointing out, "Rather than sit back and do an honest analysis, and say "why did this happen?" and conclude that maybe they were so biased against enduring the campaign…instead, they want to point fingers pretty much anywhere they can go." Gainor points out how conservative media is targeted by other news sources. "If they can ban conservative media from the most popular social media sites: Twitter, Facebook, Instagram, etc., they can shut them down, or even make it difficult, they view that as a win." During the exchange, Gainor points out how Facebook's "legitimacy" has been brought into question.
TechCrunch dives deeper into this very paradigm regarding Facebook's involvement with media. Considering that Facebook first focused on technology and communication, rather than media, it is refusing to be acknowledged as a media company. As TechCrunch.com reports, "Facebook, whether it says it is or it isn't, is a media company. They have an obligation to provide legit information…They should take actions that make their product cleaner and better for people who use Facebook as a news consumption tool." What we are overlooking is the fact that news sources are pointing fingers at other news sources to violate their first amendment right. "Congress shall make no law respecting an establishment of religion, or prohibiting the free exercise thereof; or abridging the freedom of speech, or of the press; or the right of the people peaceably to assemble, and to petition the Government for a redress of grievances." Regardless of who we are, no one can tell or limit one another's ability to speak freely. It is important that we recognize the underlying truths that exist regardless of what may be promulgated in someone's headline. Be encouraged as you see the whole report and encouraged in your ability to speak freely, and so much more. Also shared in this segment: reports, elections, media, news, information, fake, sources, rights, and truth. Greg and Steve shared in this segment.
VFNRP M1O2U3N4T5 R1E2L3I4G5I6O7N8 M1E2D3I4A5 G1O2V3E4R5N6 C1I2V3I4L5 C1H2U3R4C5H6 C1O2N3S4T5I6T7U8T9I0ON V1F2N3P4R5O6G7R8A9M0
Bishop T.D. Jakes, Bob Johnson, Fake News, President Donald Trump
WATCH! Cuba, Fidel Castro, Che Guevara, Communism, and Revival
Radical Islam on Ohio State University; The Women of ISIS; and the First Muslim Congressman being the Next Chair of the Democratic Party? Are we Seeing the Progression of Radical Islam in America?
PROPHECY! President Donald Trump Will Have a Prayer Task Force! Dutch Sheets
200 Drug Overdose Deaths Every Day: Opioid Crisis in America and Trump's Administrations Response
WATCH! 17 Prophetic Words for 2017 By Patricia King part 1

redpajama_set_name: RedPajamaCommonCrawl


text:
Андрій Валерійович Картаполов (; 9 листопада 1963, Веймар, НДР) — російський воєначальник і політик, генерал-полковник ЗС РФ.

Біографія
Син полковника артилерії у відставці Валерія Леонтійовича Картаполова, випускника Михайлівської військової артилерійської академії. Закінчив Московське вище загальновійськове командне училище імені Верховної Ради РРФСР (1985), Військову академію ім. М. В. Фрунзе (1993), Військову академію Генерального штабу Збройних сил РФ (2007). Пройшов шлях від командира взводу до командира мотострілецької дивізії в ГРВН, Західній групі військ, Далекосхідному військовому окрузі. В 2007/08 роках — заступник командувача армії в Сибірському військовому окрузі. В 2008/09 роках — начальник штабу 22-ї гвардійської армії в Московському військовому окрузі. В2009/10 роках — начальник Головного оперативного управління Генерального штабу Збройних сил Російської Федерації. З травня 2010 по січень 2012 року — командувач 58-ю армією Північно-Кавказького, потім Південного військового округів. В 2012/13 роках — заступник командувача військ Південного військового округу. З лютого 2013 року по червень 2014 року — начальник штабу Західного військового округу. З червня 2014 року по 9 листопада 2015 року — начальник Головного оперативного управління і заступник начальника Генерального штабу Збройних сил Російської Федерації. 10 листопада 2015 року призначений командувачем військ Західного військового округу.

З 19 грудня 2016 по березень 2017 року — командувач російського угрупування в Сирії. Під час командування Картаполова після операції за участю російської авіації та Сил спеціальних операцій 2 березня 2017 року Пальміра була вдруге повернута під контроль сирійського уряду.

30 липня 2018 року указом Президента Російської Федерації призначений заступником Міністра оборони Російської Федерації і начальником Головного військово-політичного управління (ГВПУ). Таким чином генерал-полковник Картаполов став дев'ятим заступником Міністра оборони Російської Федерації і очолив відтворене того ж дня ГВПУ. Крім Головного військово-політичного управління, у підпорядкуванні генерал-полковника Картаполова знаходились Департамент культури Міністерства оборони Російської Федерації, Управління Міністерства оборони Російської Федерації щодо роботи зі зверненнями громадян та Військово-геральдична служба Збройних Сил Російської Федерації.

Указом Президента Російської Федерації від 18 листопада 2019 р. № 561 включений в склад Геральдичної ради при Президентові Російської Федерації. Також є членом Колегії Міністерства оборони Російської Федерації і редакційної колегії журналу «Військова думка».

Наприкінці квітня 2021 року подав документи для участі у праймеріз партії «Єдина Росія», в яких переміг. Брав участь у виборах до Державної думи за партійним списком у Москві (шостий номер у списку) та за їх підсумками був обраний депутатом Державної думи VIII скликання. 5 жовтня 2021 року указом Президента Російської Федерації звільнений з посади начальника Головного військово-політичного управління та звільнений з військової служби у зв'язку з переходом на роботу до Державної думи. 12 жовтня 2021 обраний головою комітету Державної думи Федеральних зборів Російської Федерації VIII скликання з оборони.

Сім'я
Дружина - Картаполова Ірина Альбертівна

Донька - Гур'янова Варвара Андріївна

Скандали
16 лютого 2015 року включений Євросоюзом до списку санкцій осіб, у яких в ЄС заморожуються активи і щодо яких запроваджено візові обмеження.

Міжнародні експерти довели, що Картаполов сфальсифікував дані щодо збиття літака Boeing 777, щоб перекласти провину за інцидент на українських військових льотчиків.

23 грудня 2019 року Картаполов організував викрадення співробітника Фонду боротьби з корупцією Руслана Шаведдінова і його переправу у військову частину на архіпелазі Нова Земля. Шаведдінов протягом року служив в армії на Новій Землі під особистим контролем Картаполова. В серпні 2021 року Шаведдінов опублікував розслідування, в якому довів причетність Картаполова і його дочки до корупційних схем.

Санкції
7 вересня 2022 року до Андрія Валерійовича Картаполова введені ванкційні обмеження на території України.

Звання

 Генерал-лейтенант (13 грудня 2012)
 Генерал-полковник (11 червня 2015)

Нагороди

 Орден «За заслуги перед Вітчизною»
 4-го ступеня з мечами
 3-го ступеня
 Орден Олександра Невського
 Орден Мужності
 Орден «За військові заслуги»
 Медаль «70 років Збройних Сил СРСР»
 Медаль «За військову доблесть» 2-го і 1-го ступеня
 Медаль «За старанність під час виконання завдань інженерного забезпечення»
 Медаль «За відзнаку у військовій службі» 3-го, 2-го і 1-го ступеня (20 років)
 Медаль «За повернення Криму»
 Медаль «За бойову співдружність» (МВС РФ)
 Медаль «За участь у контртерористичній операції» (ФСБ РФ)
 Медаль «За відзнаку в навчаннях»
 Пам'ятний знак «150 років Західному військовому округу»
 Почесний знак «За заслуги перед Санкт-Петербургом»
 Почесний знак «За заслуги перед Кронштадтом»
 Орден святого благовірного великого князя Димитрія Донського 1-го ступеня (13 червня 2021) — «за увагу до допомоги у будівництві Головного храму Збройних сил Російської Федерації.»
 Орден Святителя Миколая Чудотворця
 Орден «За заслуги перед Карачаєво-Черкеською Республікою»

Галерея

Посилання

 Генерал-полковник А. В. Картаполов на сайті Міноборони Росії
 Профіль на сайті Державної думи
 Особиста сторінка Картаполова Андрія Валерійовича

Примітки
Учасники інтервенції Росії в Сирію
Депутати Державної думи Російської Федерації
Російські пропагандисти
Фігуранти санкцій проти російської інтервенції в Україну
Фігуранти бази «Миротворець»
Особи, до яких застосовано санкції через російсько-українську війну

Відмічені в українофобії

redpajama_set_name: RedPajamaWikipedia


Looking for the cheapest car insurance in Churdan, Iowa? We can help you compare the best deals. Simply enter your Zip Code in the form above and you will be on your way to finding cheap Churdan, IA auto insurance quotes from the best providers in Iowa.
One of the most important types of insurance that you can have if you are an adult is car insurance. It is legally required for all adult drivers. The type of insurance that you get can differ on each vehicle that you own. It depends upon whether or not you are making payments, or if you own the vehicle outright. Older vehicles will only need some form of liability insurance, whereas brand-new vehicles will need liability, collision, and comprehensive coverage. It can be very expensive if you own multiple vehicles, and if your rates have recently increased, it is definitely time to look for better premiums and coverage from a different provider. Here are a few of the best discount vehicle insurance online tips that you can use as you are searching the web for the best rates available in Greene county.
Buying automobile insurance policy on a restricted spending budget can be tough to do. You want the very best protection, but you can only afford so a lot. In this post, we will supply some useful tips for locating quality car insurance policy protection on a limited spending budget. Go through on to find out much more.
As you decide on vehicle insurance policies, be certain to correctly protect yourself when it comes to liability for property damage and bodily injuries. The appropriate volume of protection in this region can make a big big difference in the event of an accident. Not obtaining adequate protection can leave you with a lawsuit that can be tough to get well from.
The world wide web can be a worthwhile asset when it comes time to renew your automobile insurance plan. You can comparison store to find an agency that provides the coverage you want at the most affordable attainable cost. No agents are required for online transactions so the businesses move some of their personal savings on to you in the type of reduced rates.
One particular of the best methods to fall your auto insurance coverage rates is to present the insurance policy firm that you are a secure, reputable driver. To do this, you need to think about attending a safe-driving training course. These classes are affordable, quick, and you could stop up conserving 1000's of bucks over the lifestyle of your insurance coverage plan.
Purchase a higher high quality "basic safety-rated" motor vehicle to get reduced premium prices. These vehicles are recognized to be the most secure on the marketplace, so insurance companies will give you a bit of additional credit if you are driving 1. Appear for sedans and family automobiles, as they are typically the types with the ideal scores.
When you are buying for vehicle insurance policy for your teenage driver, get estimates for equally incorporating him or her to your insurance and for buying a independent insurance policy policy. In basic it will be less costly to include a new driver to your present insurance policies, but there might be circumstances when it is much less high-priced to purchase a separate plan.
If you can lower your yearly mileage, you can count on a lessen in expense for your vehicle coverage. Insurance coverage companies typically estimate that you will travel around twelve,000 miles per 12 months. If you can reduced this number, or are somebody who does not push that considerably that typically, you may possibly see a reduction. Be certain that you are honest about your miles because the insurance policies company might want proof.
With a lot of insurance policies organizations, teens have to pay out far more for automobile insurance policy. This is since they are regarded to be substantial threat drivers. In get to make auto insurance policy more affordable for youngsters, it might be clever to place them on the very same insurance as a more experienced push, these kinds of as their mom or father.
Do some comprehensive arranging forward when buying for your up coming car. Arranging ahead can save you a whole lot of funds when it arrives time to put it on your car insurance policies plan. Commence seeking at stats and evaluating charges early on. It's often a fantastic thought to plan for the long term.
You need leading high quality automobile insurance, but you might be on a very limited funds. What are you to do? In this post, we have lined some valuable tips that can help. Use them to discover coverage that is leading top quality, pretty priced and that will actually satisfy your insurance policies needs.


text:
Jerry Lee Lewis Emerges After Stroke, Receives Mississippi Country Music Trail Marker
December 19, 2019 by Joe Mirrione
NESBIT, Miss. (Dec. 19, 2019) – Music icon Jerry Lee Lewis received a marker on the Mississippi Country Music Trail Thursday afternoon at The Lewis Ranch, his longtime home in Nesbit, Miss. The event was his first public appearance since a minor stroke in March of 2019 and signals a productive 2020 for the Rock and Roll Hall of Fame member. Watch the presentation here, with an acoustic performance of "Touching Home" by Marty Stuart with Lewis joining in and singing along.
"After pioneering the genres of rock 'n' roll and rockabilly with hits that crossed over to the country music charts, Jerry Lee decided to locate to Nesbit to settle down, and we couldn't be more thrilled to have the music legend call Mississippi home," said Governor Phil Bryant, adding, "it is an honor to dedicate a Mississippi Country Music Trail marker here at his ranch."
(L to R): Front Row: Judith Lewis, Jerry Lee Lewis, Gov. Phil Bryant, Connie Smith
Back Row: Jerry Lee Lewis III, Steve Azar, Marty Stuart
In addition to Gov. Bryant, the marker unveiling ceremony included remarks from GRAMMY® Award-winning musician and Mississippi Country Music Trail Commissioner Marty Stuart, Visit Mississippi Director and Mississippi Country Music Trail Chairman Craig Ray, Lewis' publicist Zach Farnum, DeSoto County Supervisor Michael Lee and Visit Mississippi Tourism Development Bureau Manager Kamel King. Also in attendance was Country Music Hall of Fame member Connie Smith, country artist Steve Azar, The Recording Academy's Jon Hornyak and many more local notables.
A native of Ferriday, LA, Lewis started his musical career in Natchez, Mississippi. Lewis's 1956 rock 'n' roll classics "Whole Lot of Shakin' Going On" and "Great Balls of Fire" both topped the country charts. Starting in 1968, the singer had a string of country music hits including "To Make Love Sweeter for You," "There Must Be More to Love Than This" and "Would You Take Another Chance on Me?" In 1973, the musician established the Lewis Ranch in Nesbit.
The marker is located at The Lewis Ranch, 1595 Malone Rd. in Nesbit. It is the trail's 34th marker. The Lewis Ranch is open for tours by appointment at thelewisranch.com.
Founded in 2010, the Mississippi Country Music Trail recognizes the state's contributions to country music. To learn more about the Mississippi Country Music Trail, visit mscountrymusictrail.org.
Filed Under: Articles & Interviews Tagged With: Jerry Lee Lewis, marty stuart
Things You Didn't Know About The Drifters
Ahmet Ertegun of Atlantic Records may very well be the true founder of The Drifters Ertegun was the driving force behind Atlantic's success with Solomon Burke, Ruth Brown, Otis Redding, Sam and Dave, Percy Sledge, Aretha Franklin and Wilson Pickett. He convinced Crosby, Stills and Nash to join up with Neil Young. He discovered The


Here at Guerrilla we are proud of producing console games with astonishing visuals. To produce the vast amounts of high quality (environment) art for our games, we work together with both in-house artists as well as external art studios. The art varies from the creation of art assets to technical features for the purpose of World Building, that really push the visual fidelity of our games in all aspects.
Collaborate with the Outsourcing Manager to plan feasible scope.
Have a proactive, can-do attitude and are comfortable in a dynamic work environment, embracing changes and accepting revisions if needed to improve the game.
If you think you're up for the challenge, we'd love to hear from you! You can apply by hitting the "apply now" button. Be sure to submit your CV and Cover Letter. For any questions, please contact Wessel Haytink (Studio Recruiter) at recruitment@guerrilla-games.com.


text:
Q: Regular Expression help - string find and replace I'm using Komodo and need to find / replace a huge amount of code.
I need to replace all occurrences of the following, but retain a certain portion of it as shown below:
StartDate

replace with::
<?=$this->Html->image('bm.acp.help.png', array('alt' => 'Help', 'title'=>'THIS IS THE STRING THAT NEEDS TO BE RETAINED'));?>StartDate

Can anyone help me?
Thanks
Kevin

A: perl -e '
$a = qq{StartDate	};
$a =~ s{(.*)<\/th>}{<?=$this->Html->image("bm.acp.help.png", array("alt" => "Help", "title"=>"$1"));?>$2<\\th>};
print "\n\n$a";'

redpajama_set_name: RedPajamaStackExchange


I've taken up running recently, and am still pretty useless. I'm slow, get out of breath quickly and generally find the whole thing exhausting. Not unnaturally, this has the effect of me wanting to stay in with a nice bottle of wine and a good film. Like a normal person.
Except that once a week I get an email from the nice people at parkrun. Parkrun organise a Saturday morning 5k dash round the local park, or in my case the Thames. It is also exhausting, but I've got to know a few people and am getting used to the mud. So the combination of the little reminder, the social, and some small conscience that I should be keeping fit, means I actually turn up on a Saturday morning as often as I can.
It dawns on me that the urge NOT to go running is a great deal greater than the urge to run; by quite a long way. So this little communiqué is doing a good job in reminding me to get out there.
And so it is with all marketing communications. Out of sight is out of mind, particularly if it's something we can put off. It absolutely helps that this email is something I have opted to receive, is relevant to my interests and contains relevant news, but shouldn't all our marketing communications have the same attributes?
It's a challenge for all companies to regularly communicate relevant and interesting material to our target market. And the parkrun email seems to me to be a good example.