@Override
  public synchronized void setBlockingState(
      final BlockingState state, final Clock clock, final InternalCallContext context) {
    if (blockingStates.get(state.getBlockedId()) == null) {
      blockingStates.put(state.getBlockedId(), new ArrayList<BlockingState>());
    }
    blockingStates.get(state.getBlockedId()).add(state);

    if (blockingStatesPerAccountRecordId.get(context.getAccountRecordId()) == null) {
      blockingStatesPerAccountRecordId.put(
          context.getAccountRecordId(), new ArrayList<BlockingState>());
    }
    blockingStatesPerAccountRecordId.get(context.getAccountRecordId()).add(state);
  }
  /**
   * Create an adjustment for a given invoice item. This just creates the object in memory, it
   * doesn't write it to disk.
   *
   * @param invoiceToBeAdjusted the invoice
   * @param invoiceItemId the invoice item id to adjust
   * @param positiveAdjAmount the amount to adjust. Pass null to adjust the full amount of the
   *     original item
   * @param currency the currency of the amount. Pass null to default to the original currency used
   * @param effectiveDate adjustment effective date, in the account timezone
   * @return the adjustment item
   */
  public InvoiceItem createAdjustmentItem(
      final Invoice invoiceToBeAdjusted,
      final UUID invoiceItemId,
      @Nullable final BigDecimal positiveAdjAmount,
      @Nullable final Currency currency,
      final LocalDate effectiveDate,
      final InternalCallContext context)
      throws InvoiceApiException {
    final InvoiceItem invoiceItemToBeAdjusted =
        Iterables.<InvoiceItem>tryFind(
                invoiceToBeAdjusted.getInvoiceItems(),
                new Predicate<InvoiceItem>() {
                  @Override
                  public boolean apply(final InvoiceItem input) {
                    return input.getId().equals(invoiceItemId);
                  }
                })
            .orNull();
    if (invoiceItemToBeAdjusted == null) {
      throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_NOT_FOUND, invoiceItemId);
    }

    // Check the specified currency matches the one of the existing invoice
    final Currency currencyForAdjustment =
        Objects.firstNonNull(currency, invoiceItemToBeAdjusted.getCurrency());
    if (invoiceItemToBeAdjusted.getCurrency() != currencyForAdjustment) {
      throw new InvoiceApiException(
          ErrorCode.CURRENCY_INVALID, currency, invoiceItemToBeAdjusted.getCurrency());
    }

    // Reuse the same logic we have for refund with item adjustment
    final Map<UUID, BigDecimal> input = new HashMap<UUID, BigDecimal>();
    input.put(invoiceItemId, positiveAdjAmount);

    final Map<UUID, BigDecimal> output =
        dao.computeItemAdjustments(invoiceToBeAdjusted.getId().toString(), input, context);

    // If we pass that stage, it means the validation succeeded so we just need to extract resulting
    // amount and negate the result.
    final BigDecimal amountToAdjust = output.get(invoiceItemId).negate();
    // Finally, create the adjustment
    return new ItemAdjInvoiceItem(
        UUIDs.randomUUID(),
        context.getCreatedDate(),
        invoiceItemToBeAdjusted.getInvoiceId(),
        invoiceItemToBeAdjusted.getAccountId(),
        effectiveDate,
        null,
        amountToAdjust,
        currencyForAdjustment,
        invoiceItemToBeAdjusted.getId());
  }
Пример #3
0
  @Override
  protected void postBusEventFromTransaction(
      final AccountModelDao account,
      final AccountModelDao savedAccount,
      final ChangeType changeType,
      final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
      final InternalCallContext context)
      throws BillingExceptionBase {
    // This is only called for the create call (see update below)
    switch (changeType) {
      case INSERT:
        break;
      default:
        return;
    }

    final Long recordId =
        entitySqlDaoWrapperFactory
            .become(AccountSqlDao.class)
            .getRecordId(savedAccount.getId().toString(), context);
    // We need to re-hydrate the callcontext with the account record id
    final InternalCallContext rehydratedContext =
        internalCallContextFactory.createInternalCallContext(recordId, context);
    final AccountCreationInternalEvent creationEvent =
        new DefaultAccountCreationEvent(
            new DefaultAccountData(savedAccount),
            savedAccount.getId(),
            rehydratedContext.getAccountRecordId(),
            rehydratedContext.getTenantRecordId(),
            rehydratedContext.getUserToken());
    try {
      eventBus.postFromTransaction(
          creationEvent, entitySqlDaoWrapperFactory.getHandle().getConnection());
    } catch (final EventBusException e) {
      log.warn("Failed to post account creation event for accountId='{}'", savedAccount.getId(), e);
    }
  }
Пример #4
0
  public RawUsageOptimizerResult getConsumableInArrearUsage(
      final LocalDate firstEventStartDate,
      final LocalDate targetDate,
      final Iterable<InvoiceItem> existingUsageItems,
      final Map<String, Usage> knownUsage,
      final InternalCallContext internalCallContext) {
    final LocalDate targetStartDate =
        config.getMaxRawUsagePreviousPeriod() > 0
            ? getOptimizedRawUsageStartDate(
                firstEventStartDate, targetDate, existingUsageItems, knownUsage)
            : firstEventStartDate;
    log.info(
        "RawUsageOptimizer [accountRecordId = {}]: rawUsageStartDate = {}, (proposed) firstEventStartDate = {}",
        new Object[] {
          internalCallContext.getAccountRecordId(), targetStartDate, firstEventStartDate
        });

    final List<RawUsage> rawUsageData =
        usageApi.getRawUsageForAccount(targetStartDate, targetDate, internalCallContext);
    return new RawUsageOptimizerResult(firstEventStartDate, targetStartDate, rawUsageData);
  }
  @Override
  public SubscriptionBase createSubscription(
      final UUID bundleId,
      final PlanPhaseSpecifier spec,
      final DateTime requestedDateWithMs,
      final InternalCallContext context)
      throws SubscriptionBaseApiException {
    try {
      final String realPriceList =
          (spec.getPriceListName() == null)
              ? PriceListSet.DEFAULT_PRICELIST_NAME
              : spec.getPriceListName();
      final DateTime now = clock.getUTCNow();
      final DateTime requestedDate =
          (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now;
      if (requestedDate.isAfter(now)) {
        throw new SubscriptionBaseApiException(
            ErrorCode.SUB_INVALID_REQUESTED_DATE, now.toString(), requestedDate.toString());
      }
      final DateTime effectiveDate = requestedDate;

      final Catalog catalog = catalogService.getFullCatalog();
      final Plan plan =
          catalog.findPlan(
              spec.getProductName(), spec.getBillingPeriod(), realPriceList, requestedDate);

      final PlanPhase phase = plan.getAllPhases()[0];
      if (phase == null) {
        throw new SubscriptionBaseError(
            String.format(
                "No initial PlanPhase for Product %s, term %s and set %s does not exist in the catalog",
                spec.getProductName(), spec.getBillingPeriod().toString(), realPriceList));
      }

      final SubscriptionBaseBundle bundle = dao.getSubscriptionBundleFromId(bundleId, context);
      if (bundle == null) {
        throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BUNDLE, bundleId);
      }

      DateTime bundleStartDate = null;
      final DefaultSubscriptionBase baseSubscription =
          (DefaultSubscriptionBase) dao.getBaseSubscription(bundleId, context);
      switch (plan.getProduct().getCategory()) {
        case BASE:
          if (baseSubscription != null) {
            if (baseSubscription.getState() == EntitlementState.ACTIVE) {
              throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_BP_EXISTS, bundleId);
            }
          }
          bundleStartDate = requestedDate;
          break;
        case ADD_ON:
          if (baseSubscription == null) {
            throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BP, bundleId);
          }
          if (effectiveDate.isBefore(baseSubscription.getStartDate())) {
            throw new SubscriptionBaseApiException(
                ErrorCode.SUB_INVALID_REQUESTED_DATE,
                effectiveDate.toString(),
                baseSubscription.getStartDate().toString());
          }
          addonUtils.checkAddonCreationRights(baseSubscription, plan);
          bundleStartDate = baseSubscription.getStartDate();
          break;
        case STANDALONE:
          if (baseSubscription != null) {
            throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_BP_EXISTS, bundleId);
          }
          // Not really but we don't care, there is no alignment for STANDALONE subscriptions
          bundleStartDate = requestedDate;
          break;
        default:
          throw new SubscriptionBaseError(
              String.format(
                  "Can't create subscription of type %s",
                  plan.getProduct().getCategory().toString()));
      }

      final UUID tenantId =
          nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT);
      return apiService.createPlan(
          new SubscriptionBuilder()
              .setId(UUID.randomUUID())
              .setBundleId(bundleId)
              .setCategory(plan.getProduct().getCategory())
              .setBundleStartDate(bundleStartDate)
              .setAlignStartDate(effectiveDate),
          plan,
          spec.getPhaseType(),
          realPriceList,
          requestedDate,
          effectiveDate,
          now,
          context.toCallContext(tenantId));
    } catch (CatalogApiException e) {
      throw new SubscriptionBaseApiException(e);
    }
  }