Beispiel #1
0
 /**
  * Method to return the amount applied converted to the currency of payment
  *
  * @param paymentApplicationId the payment application id
  * @return appliedAmount the applied amount as BigDecimal
  */
 public static BigDecimal getPaymentAppliedAmount(
     Delegator delegator, String paymentApplicationId) {
   GenericValue paymentApplication = null;
   BigDecimal appliedAmount = BigDecimal.ZERO;
   try {
     paymentApplication =
         delegator.findByPrimaryKey(
             "PaymentApplication", UtilMisc.toMap("paymentApplicationId", paymentApplicationId));
     appliedAmount = paymentApplication.getBigDecimal("amountApplied");
     if (paymentApplication.get("paymentId") != null) {
       GenericValue payment = paymentApplication.getRelatedOne("Payment");
       if (paymentApplication.get("invoiceId") != null
           && payment.get("actualCurrencyAmount") != null
           && payment.get("actualCurrencyUomId") != null) {
         GenericValue invoice = paymentApplication.getRelatedOne("Invoice");
         if (payment.getString("actualCurrencyUomId").equals(invoice.getString("currencyUomId"))) {
           appliedAmount =
               appliedAmount
                   .multiply(payment.getBigDecimal("amount"))
                   .divide(payment.getBigDecimal("actualCurrencyAmount"), new MathContext(100));
         }
       }
     }
   } catch (GenericEntityException e) {
     Debug.logError(e, "Problem getting Payment", module);
   }
   return appliedAmount;
 }
Beispiel #2
0
  /*
   * Returns the value of a given ShipmentPackageContent record.  Calculated by working out the total value (from the OrderItems) of all ItemIssuances
   * for the ShipmentItem then dividing that by the total quantity issued for the same to get an average item value then multiplying that by the package
   * content quantity.
   * Note: No rounding of the calculation is performed so you will need to round it to the accuracy that you require
   */
  public static BigDecimal getShipmentPackageContentValue(GenericValue shipmentPackageContent) {
    BigDecimal quantity = shipmentPackageContent.getBigDecimal("quantity");

    BigDecimal value = new BigDecimal("0");

    // lookup the issuance to find the order
    List<GenericValue> issuances = null;
    try {
      GenericValue shipmentItem = shipmentPackageContent.getRelatedOne("ShipmentItem");
      issuances = shipmentItem.getRelated("ItemIssuance");
    } catch (GenericEntityException e) {
      Debug.logError(e, module);
    }

    BigDecimal totalIssued = BigDecimal.ZERO;
    BigDecimal totalValue = BigDecimal.ZERO;
    if (UtilValidate.isNotEmpty(issuances)) {
      for (GenericValue issuance : issuances) {
        // we only need one
        BigDecimal issuanceQuantity = issuance.getBigDecimal("quantity");
        BigDecimal issuanceCancelQuantity = issuance.getBigDecimal("cancelQuantity");
        if (issuanceCancelQuantity != null) {
          issuanceQuantity = issuanceQuantity.subtract(issuanceCancelQuantity);
        }
        // get the order item
        GenericValue orderItem = null;
        try {
          orderItem = issuance.getRelatedOne("OrderItem");
        } catch (GenericEntityException e) {
          Debug.logError(e, module);
        }

        if (orderItem != null) {
          // get the value per unit - (base price * amount)
          BigDecimal selectedAmount = orderItem.getBigDecimal("selectedAmount");
          if (selectedAmount == null || selectedAmount.compareTo(BigDecimal.ZERO) <= 0) {
            selectedAmount = BigDecimal.ONE;
          }

          BigDecimal unitPrice = orderItem.getBigDecimal("unitPrice");
          BigDecimal itemValue = unitPrice.multiply(selectedAmount);

          // total value for package (per unit * quantity)
          totalIssued = totalIssued.add(issuanceQuantity);
          totalValue = totalValue.add(itemValue.multiply(issuanceQuantity));
        }
      }
    }
    // take the average value of the issuances and multiply it by the shipment package content
    // quantity
    value = totalValue.divide(totalIssued, 10, BigDecimal.ROUND_HALF_EVEN).multiply(quantity);
    return value;
  }
Beispiel #3
0
 public static BigDecimal getPaymentNotApplied(GenericValue payment, Boolean actual) {
   if (actual.equals(Boolean.TRUE)
       && UtilValidate.isNotEmpty(payment.getBigDecimal("actualCurrencyAmount"))) {
     return payment
         .getBigDecimal("actualCurrencyAmount")
         .subtract(getPaymentApplied(payment, actual))
         .setScale(decimals, rounding);
   }
   return payment
       .getBigDecimal("amount")
       .subtract(getPaymentApplied(payment))
       .setScale(decimals, rounding);
 }
Beispiel #4
0
 /**
  * Method to return the total amount of a payment which is applied to a payment
  *
  * @param payment GenericValue object of the Payment
  * @param actual false for currency of the payment, true for the actual currency
  * @return the applied total as BigDecimal in the currency of the payment
  */
 public static BigDecimal getPaymentApplied(GenericValue payment, Boolean actual) {
   BigDecimal paymentApplied = BigDecimal.ZERO;
   List<GenericValue> paymentApplications = null;
   try {
     List<EntityExpr> cond =
         UtilMisc.toList(
             EntityCondition.makeCondition(
                 "paymentId", EntityOperator.EQUALS, payment.getString("paymentId")),
             EntityCondition.makeCondition(
                 "toPaymentId", EntityOperator.EQUALS, payment.getString("paymentId")));
     EntityCondition partyCond = EntityCondition.makeCondition(cond, EntityOperator.OR);
     paymentApplications =
         payment
             .getDelegator()
             .findList(
                 "PaymentApplication",
                 partyCond,
                 null,
                 UtilMisc.toList("invoiceId", "billingAccountId"),
                 null,
                 false);
     if (UtilValidate.isNotEmpty(paymentApplications)) {
       for (GenericValue paymentApplication : paymentApplications) {
         BigDecimal amountApplied = paymentApplication.getBigDecimal("amountApplied");
         // check currency invoice and if different convert amount applied for display
         if (actual.equals(Boolean.FALSE)
             && paymentApplication.get("invoiceId") != null
             && payment.get("actualCurrencyAmount") != null
             && payment.get("actualCurrencyUomId") != null) {
           GenericValue invoice = paymentApplication.getRelatedOne("Invoice");
           if (payment
               .getString("actualCurrencyUomId")
               .equals(invoice.getString("currencyUomId"))) {
             amountApplied =
                 amountApplied
                     .multiply(payment.getBigDecimal("amount"))
                     .divide(payment.getBigDecimal("actualCurrencyAmount"), new MathContext(100));
           }
         }
         paymentApplied = paymentApplied.add(amountApplied).setScale(decimals, rounding);
       }
     }
   } catch (GenericEntityException e) {
     Debug.logError(e, "Trouble getting entities", module);
   }
   return paymentApplied;
 }
Beispiel #5
0
 public static BigDecimal getPaymentNotApplied(GenericValue payment) {
   if (payment != null) {
     return payment
         .getBigDecimal("amount")
         .subtract(getPaymentApplied(payment))
         .setScale(decimals, rounding);
   }
   return BigDecimal.ZERO;
 }
Beispiel #6
0
  /**
   * Returns the total from a list of Payment entities
   *
   * @param payments List of Payment GenericValue items
   * @return total payments as BigDecimal
   */
  public static BigDecimal getPaymentsTotal(List<GenericValue> payments) {
    if (payments == null) {
      throw new IllegalArgumentException("Payment list cannot be null");
    }

    BigDecimal paymentsTotal = BigDecimal.ZERO;
    for (GenericValue payment : payments) {
      paymentsTotal =
          paymentsTotal.add(payment.getBigDecimal("amount")).setScale(decimals, rounding);
    }
    return paymentsTotal;
  }
  public static Map<String, Object> finAccountReleaseAuth(
      DispatchContext dctx, Map<String, Object> context) {
    LocalDispatcher dispatcher = dctx.getDispatcher();
    GenericValue userLogin = (GenericValue) context.get("userLogin");
    GenericValue paymentPref = (GenericValue) context.get("orderPaymentPreference");
    Locale locale = (Locale) context.get("locale");

    String err =
        UtilProperties.getMessage(resourceError, "AccountingFinAccountCannotBeExpired", locale);
    try {

      // expire the related financial authorization transaction
      GenericValue authTransaction = PaymentGatewayServices.getAuthTransaction(paymentPref);
      if (authTransaction == null) {
        return ServiceUtil.returnError(
            err
                + UtilProperties.getMessage(
                    resourceError, "AccountingFinAccountCannotFindAuthorization", locale));
      }

      Map<String, Object> input =
          UtilMisc.toMap(
              "userLogin", userLogin, "finAccountAuthId", authTransaction.get("referenceNum"));
      Map<String, Object> serviceResults = dispatcher.runSync("expireFinAccountAuth", input);

      Map<String, Object> result = ServiceUtil.returnSuccess();
      result.put("releaseRefNum", authTransaction.getString("referenceNum"));
      result.put("releaseAmount", authTransaction.getBigDecimal("amount"));
      result.put("releaseResult", Boolean.TRUE);

      // if there's an error, don't release
      if (ServiceUtil.isError(serviceResults)) {
        return ServiceUtil.returnError(err + ServiceUtil.getErrorMessage(serviceResults));
      }

      return result;
    } catch (GenericServiceException e) {
      Debug.logError(e, e.getMessage(), module);
      return ServiceUtil.returnError(err + e.getMessage());
    }
  }
Beispiel #8
0
  public static BigDecimal getPaymentNotApplied(
      Delegator delegator, String paymentId, Boolean actual) {
    if (delegator == null) {
      throw new IllegalArgumentException("Null delegator is not allowed in this method");
    }

    GenericValue payment = null;
    try {
      payment = delegator.findByPrimaryKey("Payment", UtilMisc.toMap("paymentId", paymentId));
    } catch (GenericEntityException e) {
      Debug.logError(e, "Problem getting Payment", module);
    }

    if (payment == null) {
      throw new IllegalArgumentException("The paymentId passed does not match an existing payment");
    }
    return payment
        .getBigDecimal("amount")
        .subtract(getPaymentApplied(delegator, paymentId, actual))
        .setScale(decimals, rounding);
  }
  public static String addListToCart(
      Delegator delegator,
      LocalDispatcher dispatcher,
      ShoppingCart cart,
      String prodCatalogId,
      String shoppingListId,
      boolean includeChild,
      boolean setAsListItem,
      boolean append)
      throws java.lang.IllegalArgumentException {
    String errMsg = null;

    // no list; no add
    if (shoppingListId == null) {
      errMsg =
          UtilProperties.getMessage(
              resource_error, "shoppinglistevents.choose_shopping_list", cart.getLocale());
      throw new IllegalArgumentException(errMsg);
    }

    // get the shopping list
    GenericValue shoppingList = null;
    List<GenericValue> shoppingListItems = null;
    try {
      shoppingList =
          EntityQuery.use(delegator)
              .from("ShoppingList")
              .where("shoppingListId", shoppingListId)
              .queryOne();
      if (shoppingList == null) {
        errMsg =
            UtilProperties.getMessage(
                resource_error,
                "shoppinglistevents.error_getting_shopping_list_and_items",
                cart.getLocale());
        throw new IllegalArgumentException(errMsg);
      }

      shoppingListItems = shoppingList.getRelated("ShoppingListItem", null, null, false);
      if (shoppingListItems == null) {
        shoppingListItems = FastList.newInstance();
      }

      // include all items of child lists if flagged to do so
      if (includeChild) {
        List<GenericValue> childShoppingLists =
            shoppingList.getRelated("ChildShoppingList", null, null, false);
        for (GenericValue v : childShoppingLists) {
          List<GenericValue> items = v.getRelated("ShoppingListItem", null, null, false);
          shoppingListItems.addAll(items);
        }
      }

    } catch (GenericEntityException e) {
      Debug.logError(e, "Problems getting ShoppingList and ShoppingListItem records", module);
      errMsg =
          UtilProperties.getMessage(
              resource_error,
              "shoppinglistevents.error_getting_shopping_list_and_items",
              cart.getLocale());
      throw new IllegalArgumentException(errMsg);
    }

    // no items; not an error; just mention that nothing was added
    if (UtilValidate.isEmpty(shoppingListItems)) {
      errMsg =
          UtilProperties.getMessage(
              resource_error, "shoppinglistevents.no_items_added", cart.getLocale());
      return errMsg;
    }

    // check if we are to clear the cart first
    if (!append) {
      cart.clear();
      // Prevent the system from creating a new shopping list every time the cart is restored for
      // anonymous user.
      cart.setAutoSaveListId(shoppingListId);
    }

    // get the survey info for all the items
    Map<String, List<String>> shoppingListSurveyInfo = getItemSurveyInfos(shoppingListItems);

    // add the items
    StringBuilder eventMessage = new StringBuilder();
    for (GenericValue shoppingListItem : shoppingListItems) {
      String productId = shoppingListItem.getString("productId");
      BigDecimal quantity = shoppingListItem.getBigDecimal("quantity");
      Timestamp reservStart = shoppingListItem.getTimestamp("reservStart");
      BigDecimal reservLength = shoppingListItem.getBigDecimal("reservLength");
      BigDecimal reservPersons = shoppingListItem.getBigDecimal("reservPersons");
      //    String accommodationMapId = shoppingListItem.getString("accommodationMapId");
      //    String accommodationSpotId = shoppingListItem.getString("accommodationSpotId");
      String configId = shoppingListItem.getString("configId");
      try {
        String listId = shoppingListItem.getString("shoppingListId");
        String itemId = shoppingListItem.getString("shoppingListItemSeqId");

        Map<String, Object> attributes = FastMap.newInstance();
        // list items are noted in the shopping cart
        if (setAsListItem) {
          attributes.put("shoppingListId", listId);
          attributes.put("shoppingListItemSeqId", itemId);
        }

        // check if we have existing survey responses to append
        if (shoppingListSurveyInfo.containsKey(listId + "." + itemId)
            && UtilValidate.isNotEmpty(shoppingListSurveyInfo.get(listId + "." + itemId))) {
          attributes.put("surveyResponses", shoppingListSurveyInfo.get(listId + "." + itemId));
        }

        ProductConfigWrapper configWrapper = null;
        if (UtilValidate.isNotEmpty(configId)) {
          configWrapper =
              ProductConfigWorker.loadProductConfigWrapper(
                  delegator,
                  dispatcher,
                  configId,
                  productId,
                  cart.getProductStoreId(),
                  prodCatalogId,
                  cart.getWebSiteId(),
                  cart.getCurrency(),
                  cart.getLocale(),
                  cart.getAutoUserLogin());
        }
        // TODO: add code to check for survey response requirement

        // i cannot get the addOrDecrease function to accept a null reservStart field: i get a null
        // pointer exception a null constant works....
        if (reservStart == null) {
          cart.addOrIncreaseItem(
              productId,
              null,
              quantity,
              null,
              null,
              null,
              null,
              null,
              null,
              attributes,
              prodCatalogId,
              configWrapper,
              null,
              null,
              null,
              dispatcher);
        } else {
          cart.addOrIncreaseItem(
              productId,
              null,
              quantity,
              reservStart,
              reservLength,
              reservPersons,
              null,
              null,
              null,
              null,
              null,
              attributes,
              prodCatalogId,
              configWrapper,
              null,
              null,
              null,
              dispatcher);
        }
        Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("productId", productId);
        errMsg =
            UtilProperties.getMessage(
                resource_error,
                "shoppinglistevents.added_product_to_cart",
                messageMap,
                cart.getLocale());
        eventMessage.append(errMsg).append("\n");
      } catch (CartItemModifyException e) {
        Debug.logWarning(
            e,
            UtilProperties.getMessage(
                resource_error, "OrderProblemsAddingItemFromListToCart", cart.getLocale()));
        Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("productId", productId);
        errMsg =
            UtilProperties.getMessage(
                resource_error,
                "shoppinglistevents.problem_adding_product_to_cart",
                messageMap,
                cart.getLocale());
        eventMessage.append(errMsg).append("\n");
      } catch (ItemNotFoundException e) {
        Debug.logWarning(
            e, UtilProperties.getMessage(resource_error, "OrderProductNotFound", cart.getLocale()));
        Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("productId", productId);
        errMsg =
            UtilProperties.getMessage(
                resource_error,
                "shoppinglistevents.problem_adding_product_to_cart",
                messageMap,
                cart.getLocale());
        eventMessage.append(errMsg).append("\n");
      }
    }

    if (eventMessage.length() > 0) {
      return eventMessage.toString();
    }

    // all done
    return ""; // no message to return; will simply reply as success
  }
Beispiel #10
0
  // Note we're not doing a lot of error checking here as this method is really only used
  // to confirm the order with PayPal, the subsequent authorizations will handle any errors
  // that may occur.
  public static Map<String, Object> doExpressCheckout(
      DispatchContext dctx, Map<String, Object> context) {
    LocalDispatcher dispatcher = dctx.getDispatcher();
    Delegator delegator = dctx.getDelegator();
    GenericValue userLogin = (GenericValue) context.get("userLogin");
    GenericValue paymentPref = (GenericValue) context.get("orderPaymentPreference");
    OrderReadHelper orh = new OrderReadHelper(delegator, paymentPref.getString("orderId"));
    Locale locale = (Locale) context.get("locale");

    GenericValue payPalPaymentSetting = getPaymentMethodGatewayPayPal(dctx, context, null);
    GenericValue payPalPaymentMethod = null;
    try {
      payPalPaymentMethod = paymentPref.getRelatedOne("PaymentMethod", false);
      payPalPaymentMethod = payPalPaymentMethod.getRelatedOne("PayPalPaymentMethod", false);
    } catch (GenericEntityException e) {
      Debug.logError(e, module);
      return ServiceUtil.returnError(e.getMessage());
    }
    BigDecimal processAmount = paymentPref.getBigDecimal("maxAmount");

    NVPEncoder encoder = new NVPEncoder();
    encoder.add("METHOD", "DoExpressCheckoutPayment");
    encoder.add("TOKEN", payPalPaymentMethod.getString("expressCheckoutToken"));
    encoder.add("PAYMENTACTION", "Order");
    encoder.add("PAYERID", payPalPaymentMethod.getString("payerId"));
    // set the amount
    encoder.add("AMT", processAmount.setScale(2).toPlainString());
    encoder.add("CURRENCYCODE", orh.getCurrency());
    BigDecimal grandTotal = orh.getOrderGrandTotal();
    BigDecimal shippingTotal = orh.getShippingTotal().setScale(2, BigDecimal.ROUND_HALF_UP);
    BigDecimal taxTotal = orh.getTaxTotal().setScale(2, BigDecimal.ROUND_HALF_UP);
    BigDecimal subTotal =
        grandTotal.subtract(shippingTotal).subtract(taxTotal).setScale(2, BigDecimal.ROUND_HALF_UP);
    encoder.add("ITEMAMT", subTotal.toPlainString());
    encoder.add("SHIPPINGAMT", shippingTotal.toPlainString());
    encoder.add("TAXAMT", taxTotal.toPlainString());

    NVPDecoder decoder = null;
    try {
      decoder = sendNVPRequest(payPalPaymentSetting, encoder);
    } catch (PayPalException e) {
      Debug.logError(e, module);
      return ServiceUtil.returnError(e.getMessage());
    }
    if (decoder == null) {
      return ServiceUtil.returnError(
          UtilProperties.getMessage(resource, "AccountingPayPalUnknownError", locale));
    }

    Map<String, String> errorMessages = getErrorMessageMap(decoder);
    if (UtilValidate.isNotEmpty(errorMessages)) {
      if (errorMessages.containsKey("10417")) {
        // "The transaction cannot complete successfully,  Instruct the customer to use an
        // alternative payment method"
        // I've only encountered this once and there's no indication of the cause so the temporary
        // solution is to try again
        boolean retry = context.get("_RETRY_") == null || (Boolean) context.get("_RETRY_");
        if (retry) {
          context.put("_RETRY_", false);
          return PayPalServices.doExpressCheckout(dctx, context);
        }
      }
      return ServiceUtil.returnError(UtilMisc.toList(errorMessages.values()));
    }

    Map<String, Object> inMap = FastMap.newInstance();
    inMap.put("userLogin", userLogin);
    inMap.put("paymentMethodId", payPalPaymentMethod.get("paymentMethodId"));
    inMap.put("transactionId", decoder.get("TRANSACTIONID"));

    Map<String, Object> outMap = null;
    try {
      outMap = dispatcher.runSync("updatePayPalPaymentMethod", inMap);
    } catch (GenericServiceException e) {
      Debug.logError(e, module);
      return ServiceUtil.returnError(e.getMessage());
    }
    if (ServiceUtil.isError(outMap)) {
      Debug.logError(ServiceUtil.getErrorMessage(outMap), module);
      return outMap;
    }
    return ServiceUtil.returnSuccess();
  }
  // auto-replenish service (deposit)
  public static Map<String, Object> finAccountReplenish(
      DispatchContext dctx, Map<String, Object> context) {
    LocalDispatcher dispatcher = dctx.getDispatcher();
    Delegator delegator = dctx.getDelegator();
    Locale locale = (Locale) context.get("locale");

    GenericValue userLogin = (GenericValue) context.get("userLogin");
    String productStoreId = (String) context.get("productStoreId");
    String finAccountId = (String) context.get("finAccountId");

    // lookup the FinAccount
    GenericValue finAccount;
    try {
      finAccount =
          EntityQuery.use(delegator)
              .from("FinAccount")
              .where("finAccountId", finAccountId)
              .queryOne();
    } catch (GenericEntityException e) {
      Debug.logError(e, module);
      return ServiceUtil.returnError(e.getMessage());
    }
    if (finAccount == null) {
      return ServiceUtil.returnError(
          UtilProperties.getMessage(
              resourceError,
              "AccountingFinAccountNotFound",
              UtilMisc.toMap("finAccountId", finAccountId),
              locale));
    }
    String currency = finAccount.getString("currencyUomId");
    String statusId = finAccount.getString("statusId");

    // look up the type -- determine auto-replenish is active
    GenericValue finAccountType;
    try {
      finAccountType = finAccount.getRelatedOne("FinAccountType", false);
    } catch (GenericEntityException e) {
      Debug.logError(e, module);
      return ServiceUtil.returnError(e.getMessage());
    }
    String replenishEnumId = finAccountType.getString("replenishEnumId");
    if (!"FARP_AUTOMATIC".equals(replenishEnumId)) {
      // type does not support auto-replenish
      return ServiceUtil.returnSuccess();
    }

    // attempt to lookup the product store from a previous deposit
    if (productStoreId == null) {
      productStoreId = getLastProductStoreId(delegator, finAccountId);
      if (productStoreId == null) {
        return ServiceUtil.returnError(
            UtilProperties.getMessage(
                resourceError, "AccountingFinAccountCannotBeReplenish", locale));
      }
    }

    // get the product store settings
    GenericValue finAccountSettings;
    Map<String, Object> psfasFindMap =
        UtilMisc.<String, Object>toMap(
            "productStoreId",
            productStoreId,
            "finAccountTypeId",
            finAccount.getString("finAccountTypeId"));
    try {
      finAccountSettings =
          EntityQuery.use(delegator)
              .from("ProductStoreFinActSetting")
              .where(psfasFindMap)
              .cache()
              .queryOne();
    } catch (GenericEntityException e) {
      Debug.logError(e, module);
      return ServiceUtil.returnError(e.getMessage());
    }
    if (finAccountSettings == null) {
      Debug.logWarning(
          "finAccountReplenish Warning: not replenishing FinAccount ["
              + finAccountId
              + "] because no ProductStoreFinActSetting record found for: "
              + psfasFindMap,
          module);
      // no settings; don't replenish
      return ServiceUtil.returnSuccess();
    }

    BigDecimal replenishThreshold = finAccountSettings.getBigDecimal("replenishThreshold");
    if (replenishThreshold == null) {
      Debug.logWarning(
          "finAccountReplenish Warning: not replenishing FinAccount ["
              + finAccountId
              + "] because ProductStoreFinActSetting.replenishThreshold field was null for: "
              + psfasFindMap,
          module);
      return ServiceUtil.returnSuccess();
    }

    BigDecimal replenishLevel = finAccount.getBigDecimal("replenishLevel");
    if (replenishLevel == null || replenishLevel.compareTo(BigDecimal.ZERO) == 0) {
      Debug.logWarning(
          "finAccountReplenish Warning: not replenishing FinAccount ["
              + finAccountId
              + "] because FinAccount.replenishLevel field was null or 0",
          module);
      // no replenish level set; this account goes not support auto-replenish
      return ServiceUtil.returnSuccess();
    }

    // get the current balance
    BigDecimal balance = finAccount.getBigDecimal("actualBalance");

    // see if we are within the threshold for replenishment
    if (balance.compareTo(replenishThreshold) > -1) {
      Debug.logInfo(
          "finAccountReplenish Info: Not replenishing FinAccount ["
              + finAccountId
              + "] because balance ["
              + balance
              + "] is greater than the replenishThreshold ["
              + replenishThreshold
              + "]",
          module);
      // not ready
      return ServiceUtil.returnSuccess();
    }

    // configure rollback service to set status to Negative Pending Replenishment
    if ("FNACT_NEGPENDREPL".equals(statusId)) {
      try {
        Map<String, Object> rollbackCtx =
            UtilMisc.toMap(
                "userLogin",
                userLogin,
                "finAccountId",
                finAccountId,
                "statusId",
                "FNACT_NEGPENDREPL");
        dispatcher.addRollbackService("updateFinAccount", rollbackCtx, true);
      } catch (GenericServiceException e) {
        Debug.logError(e, module);
        return ServiceUtil.returnError(e.getMessage());
      }
    }

    String replenishMethod = finAccountSettings.getString("replenishMethodEnumId");
    BigDecimal depositAmount;
    if (replenishMethod == null || "FARP_TOP_OFF".equals(replenishMethod)) {
      // the deposit is level - balance (500 - (-10) = 510 || 500 - (10) = 490)
      depositAmount = replenishLevel.subtract(balance);
    } else if ("FARP_REPLENISH_LEVEL".equals(replenishMethod)) {
      // the deposit is replenish-level itself
      depositAmount = replenishLevel;
    } else {
      return ServiceUtil.returnError(
          UtilProperties.getMessage(
              resourceError, "AccountingFinAccountUnknownReplenishMethod", locale));
    }

    // get the owner party
    String ownerPartyId = finAccount.getString("ownerPartyId");
    if (ownerPartyId == null) {
      // no owner cannot replenish; (not fatal, just not supported by this account)
      Debug.logWarning(
          "finAccountReplenish Warning: No owner attached to financial account ["
              + finAccountId
              + "] cannot auto-replenish",
          module);
      return ServiceUtil.returnSuccess();
    }

    // get the payment method to use to replenish
    String paymentMethodId = finAccount.getString("replenishPaymentId");
    if (paymentMethodId == null) {
      Debug.logWarning(
          "finAccountReplenish Warning: No payment method (replenishPaymentId) attached to financial account ["
              + finAccountId
              + "] cannot auto-replenish",
          module);
      return ServiceUtil.returnError(
          UtilProperties.getMessage(
              resourceError,
              "AccountingFinAccountNoPaymentMethodAssociatedWithReplenishAccount",
              locale));
    }

    GenericValue paymentMethod;
    try {
      paymentMethod =
          EntityQuery.use(delegator)
              .from("PaymentMethod")
              .where("paymentMethodId", paymentMethodId)
              .queryOne();
    } catch (GenericEntityException e) {
      Debug.logError(e, module);
      return ServiceUtil.returnError(e.getMessage());
    }
    if (paymentMethod == null) {
      // no payment methods on file; cannot replenish
      Debug.logWarning(
          "finAccountReplenish Warning: No payment method found for ID ["
              + paymentMethodId
              + "] for party ["
              + ownerPartyId
              + "] cannot auto-replenish",
          module);
      return ServiceUtil.returnError(
          UtilProperties.getMessage(
              resourceError,
              "AccountingFinAccountNoPaymentMethodAssociatedWithReplenishAccount",
              locale));
    }

    // hit the payment method for the amount to replenish
    Map<String, BigDecimal> orderItemMap =
        UtilMisc.toMap("Auto-Replenishment FA #" + finAccountId, depositAmount);
    Map<String, Object> replOrderCtx = new HashMap<String, Object>();
    replOrderCtx.put("productStoreId", productStoreId);
    replOrderCtx.put("paymentMethodId", paymentMethod.getString("paymentMethodId"));
    replOrderCtx.put("currency", currency);
    replOrderCtx.put("partyId", ownerPartyId);
    replOrderCtx.put("itemMap", orderItemMap);
    replOrderCtx.put("userLogin", userLogin);
    Map<String, Object> replResp;
    try {
      replResp = dispatcher.runSync("createSimpleNonProductSalesOrder", replOrderCtx);
    } catch (GenericServiceException e) {
      Debug.logError(e, module);
      return ServiceUtil.returnError(e.getMessage());
    }
    if (ServiceUtil.isError(replResp)) {
      return replResp;
    }
    String orderId = (String) replResp.get("orderId");

    // create the deposit
    Map<String, Object> depositCtx = new HashMap<String, Object>();
    depositCtx.put("productStoreId", productStoreId);
    depositCtx.put("finAccountId", finAccountId);
    depositCtx.put("currency", currency);
    depositCtx.put("partyId", ownerPartyId);
    depositCtx.put("orderId", orderId);
    depositCtx.put("orderItemSeqId", "00001"); // always one item on a replish order
    depositCtx.put("amount", depositAmount);
    depositCtx.put("reasonEnumId", "FATR_REPLENISH");
    depositCtx.put("userLogin", userLogin);
    try {
      Map<String, Object> depositResp = dispatcher.runSync("finAccountDeposit", depositCtx);
      if (ServiceUtil.isError(depositResp)) {
        return depositResp;
      }
    } catch (GenericServiceException e) {
      Debug.logError(e, module);
      return ServiceUtil.returnError(e.getMessage());
    }

    // say we are in good standing again
    if ("FNACT_NEGPENDREPL".equals(statusId)) {
      try {
        Map<String, Object> ufaResp =
            dispatcher.runSync(
                "updateFinAccount",
                UtilMisc.<String, Object>toMap(
                    "finAccountId",
                    finAccountId,
                    "statusId",
                    "FNACT_ACTIVE",
                    "userLogin",
                    userLogin));
        if (ServiceUtil.isError(ufaResp)) {
          return ufaResp;
        }
      } catch (GenericServiceException e) {
        Debug.logError(e, module);
        return ServiceUtil.returnError(e.getMessage());
      }
    }

    return ServiceUtil.returnSuccess();
  }
  // base deposit service
  public static Map<String, Object> finAccountDeposit(
      DispatchContext dctx, Map<String, Object> context) {
    LocalDispatcher dispatcher = dctx.getDispatcher();
    Delegator delegator = dctx.getDelegator();
    Locale locale = (Locale) context.get("locale");

    GenericValue userLogin = (GenericValue) context.get("userLogin");
    String productStoreId = (String) context.get("productStoreId");
    String finAccountId = (String) context.get("finAccountId");
    String orderItemSeqId = (String) context.get("orderItemSeqId");
    String reasonEnumId = (String) context.get("reasonEnumId");
    String orderId = (String) context.get("orderId");
    Boolean isRefund = (Boolean) context.get("isRefund");
    BigDecimal amount = (BigDecimal) context.get("amount");

    final String DEPOSIT = isRefund == null || !isRefund ? "DEPOSIT" : "ADJUSTMENT";

    String partyId = (String) context.get("partyId");
    if (UtilValidate.isEmpty(partyId)) {
      partyId = "_NA_";
    }
    String currencyUom = (String) context.get("currency");
    if (UtilValidate.isEmpty(currencyUom)) {
      currencyUom =
          EntityUtilProperties.getPropertyValue(
              "general.properties", "currency.uom.id.default", "USD", delegator);
    }

    GenericValue finAccount;
    try {
      finAccount =
          EntityQuery.use(delegator)
              .from("FinAccount")
              .where("finAccountId", finAccountId)
              .queryOne();
    } catch (GenericEntityException e) {
      Debug.logError(e, module);
      return ServiceUtil.returnError(
          UtilProperties.getMessage(
              resourceError,
              "AccountingFinAccountNotFound",
              UtilMisc.toMap("finAccountId", finAccountId),
              locale));
    }

    // verify we have a financial account
    if (finAccount == null) {
      return ServiceUtil.returnError(
          UtilProperties.getMessage(
              resourceError,
              "AccountingFinAccountNotFound",
              UtilMisc.toMap("finAccountId", ""),
              locale));
    }

    // make sure the fin account itself has not expired
    if ((finAccount.getTimestamp("thruDate") != null)
        && (finAccount.getTimestamp("thruDate").before(UtilDateTime.nowTimestamp()))) {
      return ServiceUtil.returnError(
          UtilProperties.getMessage(
              resourceError,
              "AccountingFinAccountExpired",
              UtilMisc.toMap("thruDate", finAccount.getTimestamp("thruDate")),
              locale));
    }
    Debug.logInfo("Deposit into financial account #" + finAccountId + " [" + amount + "]", module);

    // get the previous balance
    BigDecimal previousBalance = finAccount.getBigDecimal("actualBalance");
    if (previousBalance == null) {
      previousBalance = FinAccountHelper.ZERO;
    }

    // create the transaction
    BigDecimal actualBalance;
    String refNum;
    try {
      refNum =
          FinAccountPaymentServices.createFinAcctPaymentTransaction(
              delegator,
              dispatcher,
              userLogin,
              amount,
              productStoreId,
              partyId,
              orderId,
              orderItemSeqId,
              currencyUom,
              DEPOSIT,
              finAccountId,
              reasonEnumId);
      finAccount.refresh();
      actualBalance = finAccount.getBigDecimal("actualBalance");
    } catch (GeneralException e) {
      Debug.logError(e, module);
      return ServiceUtil.returnError(e.getMessage());
    }

    // make sure balance is not null
    if (actualBalance == null) {
      actualBalance = FinAccountHelper.ZERO;
    } else {
      if (actualBalance.compareTo(BigDecimal.ZERO) < 0) {
        // balance went below zero, set negative pending replenishment status so that no more auths
        // or captures will go through until it is replenished
        try {
          Map<String, Object> rollbackCtx =
              UtilMisc.toMap(
                  "userLogin",
                  userLogin,
                  "finAccountId",
                  finAccountId,
                  "statusId",
                  "FNACT_NEGPENDREPL");
          dispatcher.addRollbackService("updateFinAccount", rollbackCtx, true);
        } catch (GenericServiceException e) {
          Debug.logError(e, module);
          return ServiceUtil.returnError(e.getMessage());
        }
      }
    }

    Map<String, Object> result = ServiceUtil.returnSuccess();
    result.put("previousBalance", previousBalance);
    result.put("balance", actualBalance);
    result.put("amount", amount);
    result.put("processResult", Boolean.TRUE);
    result.put("referenceNum", refNum);
    return result;
  }
  // base account transaction services
  public static Map<String, Object> finAccountWithdraw(
      DispatchContext dctx, Map<String, Object> context) {
    LocalDispatcher dispatcher = dctx.getDispatcher();
    Delegator delegator = dctx.getDelegator();
    Locale locale = (Locale) context.get("locale");

    GenericValue userLogin = (GenericValue) context.get("userLogin");
    String productStoreId = (String) context.get("productStoreId");
    String finAccountId = (String) context.get("finAccountId");
    String orderItemSeqId = (String) context.get("orderItemSeqId");
    String reasonEnumId = (String) context.get("reasonEnumId");
    String orderId = (String) context.get("orderId");
    Boolean requireBalance = (Boolean) context.get("requireBalance");
    BigDecimal amount = (BigDecimal) context.get("amount");
    if (requireBalance == null) requireBalance = Boolean.TRUE;

    final String WITHDRAWAL = "WITHDRAWAL";

    String partyId = (String) context.get("partyId");
    if (UtilValidate.isEmpty(partyId)) {
      partyId = "_NA_";
    }
    String currencyUom = (String) context.get("currency");
    if (UtilValidate.isEmpty(currencyUom)) {
      currencyUom =
          EntityUtilProperties.getPropertyValue(
              "general.properties", "currency.uom.id.default", "USD", delegator);
    }

    // validate the amount
    if (amount.compareTo(BigDecimal.ZERO) < 0) {
      return ServiceUtil.returnError(
          UtilProperties.getMessage(resourceError, "AccountingFinAccountMustBePositive", locale));
    }

    GenericValue finAccount;
    try {
      finAccount =
          EntityQuery.use(delegator)
              .from("FinAccount")
              .where("finAccountId", finAccountId)
              .queryOne();
    } catch (GenericEntityException e) {
      Debug.logError(e, module);
      return ServiceUtil.returnError(e.getMessage());
    }

    // verify we have a financial account
    if (finAccount == null) {
      return ServiceUtil.returnError(
          UtilProperties.getMessage(
              resourceError,
              "AccountingFinAccountNotFound",
              UtilMisc.toMap("finAccountId", ""),
              locale));
    }

    // make sure the fin account itself has not expired
    if ((finAccount.getTimestamp("thruDate") != null)
        && (finAccount.getTimestamp("thruDate").before(UtilDateTime.nowTimestamp()))) {
      return ServiceUtil.returnError(
          UtilProperties.getMessage(
              resourceError,
              "AccountingFinAccountExpired",
              UtilMisc.toMap("thruDate", finAccount.getTimestamp("thruDate")),
              locale));
    }

    // check the actual balance (excluding authorized amounts) and create the transaction if it is
    // sufficient
    BigDecimal previousBalance = finAccount.getBigDecimal("actualBalance");
    if (previousBalance == null) {
      previousBalance = FinAccountHelper.ZERO;
    }

    BigDecimal balance;
    String refNum;
    Boolean procResult;
    if (requireBalance && previousBalance.compareTo(amount) < 0) {
      procResult = Boolean.FALSE;
      balance = previousBalance;
      refNum = "N/A";
    } else {
      try {
        refNum =
            FinAccountPaymentServices.createFinAcctPaymentTransaction(
                delegator,
                dispatcher,
                userLogin,
                amount,
                productStoreId,
                partyId,
                orderId,
                orderItemSeqId,
                currencyUom,
                WITHDRAWAL,
                finAccountId,
                reasonEnumId);
        finAccount.refresh();
        balance = finAccount.getBigDecimal("actualBalance");
        procResult = Boolean.TRUE;
      } catch (GeneralException e) {
        Debug.logError(e, module);
        return ServiceUtil.returnError(e.getMessage());
      }
    }

    // make sure balance is not null
    if (balance == null) {
      balance = FinAccountHelper.ZERO;
    }

    Map<String, Object> result = ServiceUtil.returnSuccess();
    result.put("previousBalance", previousBalance);
    result.put("balance", balance);
    result.put("amount", amount);
    result.put("processResult", procResult);
    result.put("referenceNum", refNum);
    return result;
  }
  // base payment integration services
  public static Map<String, Object> finAccountPreAuth(
      DispatchContext dctx, Map<String, Object> context) {
    LocalDispatcher dispatcher = dctx.getDispatcher();
    Delegator delegator = dctx.getDelegator();
    GenericValue userLogin = (GenericValue) context.get("userLogin");
    Locale locale = (Locale) context.get("locale");
    GenericValue paymentPref = (GenericValue) context.get("orderPaymentPreference");
    String finAccountCode = (String) context.get("finAccountCode");
    String finAccountPin = (String) context.get("finAccountPin");
    String finAccountId = (String) context.get("finAccountId");
    String orderId = (String) context.get("orderId");
    BigDecimal amount = (BigDecimal) context.get("processAmount");

    // check for an existing auth trans and cancel it
    GenericValue authTrans = PaymentGatewayServices.getAuthTransaction(paymentPref);
    if (authTrans != null) {
      Map<String, Object> input =
          UtilMisc.toMap("userLogin", userLogin, "finAccountAuthId", authTrans.get("referenceNum"));
      try {
        dispatcher.runSync("expireFinAccountAuth", input);
      } catch (GenericServiceException e) {
        Debug.logError(e, module);
        return ServiceUtil.returnError(e.getMessage());
      }
    }
    if (finAccountId == null && paymentPref != null) {
      finAccountId = paymentPref.getString("finAccountId");
    }

    // obtain the order information
    OrderReadHelper orh = new OrderReadHelper(delegator, orderId);

    // NOTE DEJ20070808: this means that we want store related settings for where the item is being
    // purchased,
    // NOT where the account was setup; should this be changed to use settings from the store where
    // the account was setup?
    String productStoreId = orh.getProductStoreId();

    // TODO, NOTE DEJ20070808: why is this setup this way anyway? for the allowAuthToNegative
    // wouldn't that be better setup
    // on the FinAccount and not on the ProductStoreFinActSetting? maybe an override on the
    // FinAccount would be good...

    // get the financial account
    GenericValue finAccount;
    if (finAccountId != null) {
      try {
        finAccount =
            EntityQuery.use(delegator)
                .from("FinAccount")
                .where("finAccountId", finAccountId)
                .queryOne();
      } catch (GenericEntityException e) {
        Debug.logError(e, module);
        return ServiceUtil.returnError(e.getMessage());
      }
    } else {
      if (finAccountCode != null) {
        try {
          finAccount = FinAccountHelper.getFinAccountFromCode(finAccountCode, delegator);
        } catch (GenericEntityException e) {
          Debug.logError(e, module);
          return ServiceUtil.returnError(
              UtilProperties.getMessage(
                  resourceError, "AccountingFinAccountCannotLocateItFromAccountCode", locale));
        }
      } else {
        return ServiceUtil.returnError(
            UtilProperties.getMessage(
                resourceError, "AccountingFinAccountIdAndFinAccountCodeAreNull", locale));
      }
    }
    if (finAccount == null) {
      return ServiceUtil.returnError(
          UtilProperties.getMessage(resourceError, "AccountingFinAccountIdInvalid", locale));
    }

    String finAccountTypeId = finAccount.getString("finAccountTypeId");
    finAccountId = finAccount.getString("finAccountId");
    String statusId = finAccount.getString("statusId");

    try {
      // fin the store requires a pin number; validate the PIN with the code
      Map<String, Object> findProductStoreFinActSettingMap =
          UtilMisc.<String, Object>toMap(
              "productStoreId", productStoreId, "finAccountTypeId", finAccountTypeId);
      GenericValue finAccountSettings =
          EntityQuery.use(delegator)
              .from("ProductStoreFinActSetting")
              .where(findProductStoreFinActSettingMap)
              .cache()
              .queryOne();

      if (finAccountSettings == null) {
        Debug.logWarning(
            "In finAccountPreAuth could not find ProductStoreFinActSetting record, values searched by: "
                + findProductStoreFinActSettingMap,
            module);
      }
      if (Debug.verboseOn())
        Debug.logVerbose("In finAccountPreAuth finAccountSettings=" + finAccountSettings, module);

      BigDecimal minBalance = FinAccountHelper.ZERO;
      String allowAuthToNegative = "N";

      if (finAccountSettings != null) {
        allowAuthToNegative = finAccountSettings.getString("allowAuthToNegative");
        minBalance = finAccountSettings.getBigDecimal("minBalance");
        if (minBalance == null) {
          minBalance = FinAccountHelper.ZERO;
        }

        // validate the PIN if the store requires it
        if ("Y".equals(finAccountSettings.getString("requirePinCode"))) {
          if (!FinAccountHelper.validatePin(delegator, finAccountCode, finAccountPin)) {
            Map<String, Object> result = ServiceUtil.returnSuccess();
            result.put(
                "authMessage",
                UtilProperties.getMessage(
                    resourceError, "AccountingFinAccountPinCodeCombinatorNotFound", locale));
            result.put("authResult", Boolean.FALSE);
            result.put("processAmount", amount);
            result.put("authFlag", "0");
            result.put("authCode", "A");
            result.put("authRefNum", "0");
            Debug.logWarning("Unable to auth FinAccount: " + result, module);
            return result;
          }
        }
      }

      // check for expiration date
      if ((finAccount.getTimestamp("thruDate") != null)
          && (finAccount.getTimestamp("thruDate").before(UtilDateTime.nowTimestamp()))) {
        Map<String, Object> result = ServiceUtil.returnSuccess();
        result.put(
            "authMessage",
            UtilProperties.getMessage(
                resourceError,
                "AccountingFinAccountExpired",
                UtilMisc.toMap("thruDate", finAccount.getTimestamp("thruDate")),
                locale));
        result.put("authResult", Boolean.FALSE);
        result.put("processAmount", amount);
        result.put("authFlag", "0");
        result.put("authCode", "A");
        result.put("authRefNum", "0");
        Debug.logWarning("Unable to auth FinAccount: " + result, module);
        return result;
      }

      // check for account being in bad standing somehow
      if ("FNACT_NEGPENDREPL".equals(statusId)
          || "FNACT_MANFROZEN".equals(statusId)
          || "FNACT_CANCELLED".equals(statusId)) {
        // refresh the finaccount
        finAccount.refresh();
        statusId = finAccount.getString("statusId");

        if ("FNACT_NEGPENDREPL".equals(statusId)
            || "FNACT_MANFROZEN".equals(statusId)
            || "FNACT_CANCELLED".equals(statusId)) {
          Map<String, Object> result = ServiceUtil.returnSuccess();
          if ("FNACT_NEGPENDREPL".equals(statusId)) {
            result.put(
                "authMessage",
                UtilProperties.getMessage(resourceError, "AccountingFinAccountNegative", locale));
          } else if ("FNACT_MANFROZEN".equals(statusId)) {
            result.put(
                "authMessage",
                UtilProperties.getMessage(resourceError, "AccountingFinAccountFrozen", locale));
          } else if ("FNACT_CANCELLED".equals(statusId)) {
            result.put(
                "authMessage",
                UtilProperties.getMessage(resourceError, "AccountingFinAccountCancelled", locale));
          }
          result.put("authResult", Boolean.FALSE);
          result.put("processAmount", amount);
          result.put("authFlag", "0");
          result.put("authCode", "A");
          result.put("authRefNum", "0");
          Debug.logWarning("Unable to auth FinAccount: " + result, module);
          return result;
        }
      }

      // check the amount to authorize against the available balance of fin account, which includes
      // active authorizations as well as transactions
      BigDecimal availableBalance = finAccount.getBigDecimal("availableBalance");
      if (availableBalance == null) {
        availableBalance = FinAccountHelper.ZERO;
      } else {
        BigDecimal availableBalanceOriginal = availableBalance;
        availableBalance =
            availableBalance.setScale(FinAccountHelper.decimals, FinAccountHelper.rounding);
        if (availableBalance.compareTo(availableBalanceOriginal) != 0) {
          Debug.logWarning(
              "In finAccountPreAuth for finAccountId ["
                  + finAccountId
                  + "] availableBalance ["
                  + availableBalanceOriginal
                  + "] was different after rounding ["
                  + availableBalance
                  + "]; it should never have made it into the database this way, so check whatever put it there.",
              module);
        }
      }

      Map<String, Object> result = ServiceUtil.returnSuccess();
      String authMessage = null;
      Boolean processResult;
      String refNum;

      // make sure to round and scale it to the same as availableBalance
      amount = amount.setScale(FinAccountHelper.decimals, FinAccountHelper.rounding);

      Debug.logInfo(
          "Allow auth to negative: "
              + allowAuthToNegative
              + " :: available: "
              + availableBalance
              + " comp: "
              + minBalance
              + " = "
              + availableBalance.compareTo(minBalance)
              + " :: req: "
              + amount,
          module);
      // check the available balance to see if we can auth this tx
      if (("Y".equals(allowAuthToNegative) && availableBalance.compareTo(minBalance) > -1)
          || (availableBalance.compareTo(amount) > -1)) {
        Timestamp thruDate;

        if (finAccountSettings != null && finAccountSettings.getLong("authValidDays") != null) {
          thruDate =
              UtilDateTime.getDayEnd(
                  UtilDateTime.nowTimestamp(), finAccountSettings.getLong("authValidDays"));
        } else {
          thruDate =
              UtilDateTime.getDayEnd(
                  UtilDateTime.nowTimestamp(), Long.valueOf(30)); // default 30 days for an auth
        }

        Map<String, Object> tmpResult =
            dispatcher.runSync(
                "createFinAccountAuth",
                UtilMisc.<String, Object>toMap(
                    "finAccountId",
                    finAccountId,
                    "amount",
                    amount,
                    "thruDate",
                    thruDate,
                    "userLogin",
                    userLogin));

        if (ServiceUtil.isError(tmpResult)) {
          return tmpResult;
        }
        refNum = (String) tmpResult.get("finAccountAuthId");
        processResult = Boolean.TRUE;

        // refresh the account
        finAccount.refresh();
      } else {
        Debug.logWarning(
            "Attempted to authorize ["
                + amount
                + "] against a balance of only ["
                + availableBalance
                + "] for finAccountId ["
                + finAccountId
                + "]",
            module);
        refNum = "0"; // a refNum is always required from authorization
        authMessage = "Insufficient funds";
        processResult = Boolean.FALSE;
      }

      result.put("processAmount", amount);
      result.put("authMessage", authMessage);
      result.put("authResult", processResult);
      result.put("processAmount", amount);
      result.put("authFlag", "1");
      result.put("authCode", "A");
      result.put("authRefNum", refNum);
      Debug.logInfo("FinAccont Auth: " + result, module);

      return result;
    } catch (GenericEntityException ex) {
      Debug.logError(ex, "Cannot authorize financial account", module);
      return ServiceUtil.returnError(
          UtilProperties.getMessage(
              resourceError,
              "AccountingFinAccountCannotBeAuthorized",
              UtilMisc.toMap("errorString", ex.getMessage()),
              locale));
    } catch (GenericServiceException ex) {
      Debug.logError(ex, "Cannot authorize financial account", module);
      return ServiceUtil.returnError(
          UtilProperties.getMessage(
              resourceError,
              "AccountingFinAccountCannotBeAuthorized",
              UtilMisc.toMap("errorString", ex.getMessage()),
              locale));
    }
  }
  public static Map<String, Object> rateProductTaxCalcForDisplay(
      DispatchContext dctx, Map<String, ? extends Object> context) {
    Delegator delegator = dctx.getDelegator();
    String productStoreId = (String) context.get("productStoreId");
    String billToPartyId = (String) context.get("billToPartyId");
    String productId = (String) context.get("productId");
    BigDecimal quantity = (BigDecimal) context.get("quantity");
    BigDecimal basePrice = (BigDecimal) context.get("basePrice");
    BigDecimal shippingPrice = (BigDecimal) context.get("shippingPrice");
    Locale locale = (Locale) context.get("locale");

    if (quantity == null) quantity = ONE_BASE;
    BigDecimal amount = basePrice.multiply(quantity);

    BigDecimal taxTotal = ZERO_BASE;
    BigDecimal taxPercentage = ZERO_BASE;
    BigDecimal priceWithTax = basePrice;
    if (shippingPrice != null) priceWithTax = priceWithTax.add(shippingPrice);

    try {
      GenericValue product =
          delegator.findByPrimaryKeyCache("Product", UtilMisc.toMap("productId", productId));
      GenericValue productStore =
          delegator.findByPrimaryKeyCache(
              "ProductStore", UtilMisc.toMap("productStoreId", productStoreId));
      if (productStore == null) {
        throw new IllegalArgumentException(
            "Could not find ProductStore with ID [" + productStoreId + "] for tax calculation");
      }

      if ("Y".equals(productStore.getString("showPricesWithVatTax"))) {
        Set<GenericValue> taxAuthoritySet = FastSet.newInstance();
        if (productStore.get("vatTaxAuthPartyId") == null) {
          List<GenericValue> taxAuthorityRawList =
              delegator.findList(
                  "TaxAuthority",
                  EntityCondition.makeCondition(
                      "taxAuthGeoId", EntityOperator.EQUALS, productStore.get("vatTaxAuthGeoId")),
                  null,
                  null,
                  null,
                  true);
          taxAuthoritySet.addAll(taxAuthorityRawList);
        } else {
          GenericValue taxAuthority =
              delegator.findByPrimaryKeyCache(
                  "TaxAuthority",
                  UtilMisc.toMap(
                      "taxAuthGeoId",
                      productStore.get("vatTaxAuthGeoId"),
                      "taxAuthPartyId",
                      productStore.get("vatTaxAuthPartyId")));
          taxAuthoritySet.add(taxAuthority);
        }

        if (taxAuthoritySet.size() == 0) {
          throw new IllegalArgumentException(
              "Could not find any Tax Authories for store with ID ["
                  + productStoreId
                  + "] for tax calculation; the store settings may need to be corrected.");
        }

        List<GenericValue> taxAdustmentList =
            getTaxAdjustments(
                delegator,
                product,
                productStore,
                null,
                billToPartyId,
                taxAuthoritySet,
                basePrice,
                quantity,
                amount,
                shippingPrice,
                ZERO_BASE);
        if (taxAdustmentList.size() == 0) {
          // this is something that happens every so often for different products and such, so don't
          // blow up on it...
          Debug.logWarning(
              "Could not find any Tax Authories Rate Rules for store with ID ["
                  + productStoreId
                  + "], productId ["
                  + productId
                  + "], basePrice ["
                  + basePrice
                  + "], amount ["
                  + amount
                  + "], for tax calculation; the store settings may need to be corrected.",
              module);
        }

        // add up amounts from adjustments (amount OR exemptAmount, sourcePercentage)
        for (GenericValue taxAdjustment : taxAdustmentList) {
          if ("SALES_TAX".equals(taxAdjustment.getString("orderAdjustmentTypeId"))) {
            taxPercentage = taxPercentage.add(taxAdjustment.getBigDecimal("sourcePercentage"));
            BigDecimal adjAmount = taxAdjustment.getBigDecimal("amount");
            taxTotal = taxTotal.add(adjAmount);
            priceWithTax =
                priceWithTax.add(
                    adjAmount.divide(quantity, salestaxCalcDecimals, salestaxRounding));
            Debug.logInfo(
                "For productId ["
                    + productId
                    + "] added ["
                    + adjAmount.divide(quantity, salestaxCalcDecimals, salestaxRounding)
                    + "] of tax to price for geoId ["
                    + taxAdjustment.getString("taxAuthGeoId")
                    + "], new price is ["
                    + priceWithTax
                    + "]",
                module);
          }
        }
      }
    } catch (GenericEntityException e) {
      Debug.logError(e, "Data error getting tax settings: " + e.toString(), module);
      return ServiceUtil.returnError(
          UtilProperties.getMessage(
              resource,
              "AccountingTaxSettingError",
              UtilMisc.toMap("errorString", e.toString()),
              locale));
    }

    // round to 2 decimal places for display/etc
    taxTotal = taxTotal.setScale(salestaxFinalDecimals, salestaxRounding);
    priceWithTax = priceWithTax.setScale(salestaxFinalDecimals, salestaxRounding);

    Map<String, Object> result = ServiceUtil.returnSuccess();
    result.put("taxTotal", taxTotal);
    result.put("taxPercentage", taxPercentage);
    result.put("priceWithTax", priceWithTax);
    return result;
  }
  private static List<GenericValue> getTaxAdjustments(
      Delegator delegator,
      GenericValue product,
      GenericValue productStore,
      String payToPartyId,
      String billToPartyId,
      Set<GenericValue> taxAuthoritySet,
      BigDecimal itemPrice,
      BigDecimal itemQuantity,
      BigDecimal itemAmount,
      BigDecimal shippingAmount,
      BigDecimal orderPromotionsAmount) {
    Timestamp nowTimestamp = UtilDateTime.nowTimestamp();
    List<GenericValue> adjustments = FastList.newInstance();

    if (payToPartyId == null) {
      if (productStore != null) {
        payToPartyId = productStore.getString("payToPartyId");
      }
    }

    // store expr
    EntityCondition storeCond = null;
    if (productStore != null) {
      storeCond =
          EntityCondition.makeCondition(
              EntityCondition.makeCondition(
                  "productStoreId", EntityOperator.EQUALS, productStore.get("productStoreId")),
              EntityOperator.OR,
              EntityCondition.makeCondition("productStoreId", EntityOperator.EQUALS, null));
    } else {
      storeCond = EntityCondition.makeCondition("productStoreId", EntityOperator.EQUALS, null);
    }

    // build the TaxAuthority expressions (taxAuthGeoId, taxAuthPartyId)
    List<EntityCondition> taxAuthCondOrList = FastList.newInstance();
    // start with the _NA_ TaxAuthority...
    taxAuthCondOrList.add(
        EntityCondition.makeCondition(
            EntityCondition.makeCondition("taxAuthPartyId", EntityOperator.EQUALS, "_NA_"),
            EntityOperator.AND,
            EntityCondition.makeCondition("taxAuthGeoId", EntityOperator.EQUALS, "_NA_")));

    for (GenericValue taxAuthority : taxAuthoritySet) {
      EntityCondition taxAuthCond =
          EntityCondition.makeCondition(
              EntityCondition.makeCondition(
                  "taxAuthPartyId",
                  EntityOperator.EQUALS,
                  taxAuthority.getString("taxAuthPartyId")),
              EntityOperator.AND,
              EntityCondition.makeCondition(
                  "taxAuthGeoId", EntityOperator.EQUALS, taxAuthority.getString("taxAuthGeoId")));
      taxAuthCondOrList.add(taxAuthCond);
    }
    EntityCondition taxAuthoritiesCond =
        EntityCondition.makeCondition(taxAuthCondOrList, EntityOperator.OR);

    try {
      EntityCondition productCategoryCond = null;
      if (product != null) {
        // find the tax categories associated with the product and filter by those, with an IN
        // clause or some such
        // if this product is variant, find the virtual product id and consider also the categories
        // of the virtual
        // question: get all categories, or just a special type? for now let's do all categories...
        String virtualProductId = null;
        if ("Y".equals(product.getString("isVariant"))) {
          virtualProductId = ProductWorker.getVariantVirtualId(product);
        }
        Set<String> productCategoryIdSet = FastSet.newInstance();
        EntityCondition productIdCond = null;
        if (virtualProductId != null) {
          productIdCond =
              EntityCondition.makeCondition(
                  EntityCondition.makeCondition(
                      "productId", EntityOperator.EQUALS, product.getString("productId")),
                  EntityOperator.OR,
                  EntityCondition.makeCondition(
                      "productId", EntityOperator.EQUALS, virtualProductId));

        } else {
          productIdCond =
              EntityCondition.makeCondition(
                  "productId", EntityOperator.EQUALS, product.getString("productId"));
        }
        List<GenericValue> pcmList =
            delegator.findList(
                "ProductCategoryMember",
                productIdCond,
                UtilMisc.toSet("productCategoryId", "fromDate", "thruDate"),
                null,
                null,
                true);
        pcmList = EntityUtil.filterByDate(pcmList, true);
        for (GenericValue pcm : pcmList) {
          productCategoryIdSet.add(pcm.getString("productCategoryId"));
        }

        if (productCategoryIdSet.size() == 0) {
          productCategoryCond =
              EntityCondition.makeCondition("productCategoryId", EntityOperator.EQUALS, null);
        } else {
          productCategoryCond =
              EntityCondition.makeCondition(
                  EntityCondition.makeCondition("productCategoryId", EntityOperator.EQUALS, null),
                  EntityOperator.OR,
                  EntityCondition.makeCondition(
                      "productCategoryId", EntityOperator.IN, productCategoryIdSet));
        }
      } else {
        productCategoryCond =
            EntityCondition.makeCondition("productCategoryId", EntityOperator.EQUALS, null);
      }

      // FIXME handles shipping and promo tax. Simple solution, see
      // https://issues.apache.org/jira/browse/OFBIZ-4160 for a better one
      if (product == null && shippingAmount != null) {
        EntityCondition taxShippingCond =
            EntityCondition.makeCondition(
                EntityCondition.makeCondition("taxShipping", EntityOperator.EQUALS, null),
                EntityOperator.OR,
                EntityCondition.makeCondition("taxShipping", EntityOperator.EQUALS, "Y"));

        if (productCategoryCond != null) {
          productCategoryCond =
              EntityCondition.makeCondition(
                  productCategoryCond, EntityOperator.OR, taxShippingCond);
        }
      }

      if (product == null && orderPromotionsAmount != null) {
        EntityCondition taxOrderPromotionsCond =
            EntityCondition.makeCondition(
                EntityCondition.makeCondition("taxPromotions", EntityOperator.EQUALS, null),
                EntityOperator.OR,
                EntityCondition.makeCondition("taxPromotions", EntityOperator.EQUALS, "Y"));

        if (productCategoryCond != null) {
          productCategoryCond =
              EntityCondition.makeCondition(
                  productCategoryCond, EntityOperator.OR, taxOrderPromotionsCond);
        }
      }

      // build the main condition clause
      List<EntityCondition> mainExprs =
          UtilMisc.toList(storeCond, taxAuthoritiesCond, productCategoryCond);
      mainExprs.add(
          EntityCondition.makeCondition(
              EntityCondition.makeCondition("minItemPrice", EntityOperator.EQUALS, null),
              EntityOperator.OR,
              EntityCondition.makeCondition(
                  "minItemPrice", EntityOperator.LESS_THAN_EQUAL_TO, itemPrice)));
      mainExprs.add(
          EntityCondition.makeCondition(
              EntityCondition.makeCondition("minPurchase", EntityOperator.EQUALS, null),
              EntityOperator.OR,
              EntityCondition.makeCondition(
                  "minPurchase", EntityOperator.LESS_THAN_EQUAL_TO, itemAmount)));
      EntityCondition mainCondition = EntityCondition.makeCondition(mainExprs, EntityOperator.AND);

      // create the orderby clause
      List<String> orderList = UtilMisc.<String>toList("minItemPrice", "minPurchase", "fromDate");

      // finally ready... do the rate query
      List<GenericValue> lookupList =
          delegator.findList(
              "TaxAuthorityRateProduct", mainCondition, null, orderList, null, false);
      List<GenericValue> filteredList = EntityUtil.filterByDate(lookupList, true);

      if (filteredList.size() == 0) {
        Debug.logWarning(
            "In TaxAuthority Product Rate no records were found for condition:"
                + mainCondition.toString(),
            module);
        return adjustments;
      }

      // find the right entry(s) based on purchase amount
      for (GenericValue taxAuthorityRateProduct : filteredList) {
        BigDecimal taxRate =
            taxAuthorityRateProduct.get("taxPercentage") != null
                ? taxAuthorityRateProduct.getBigDecimal("taxPercentage")
                : ZERO_BASE;
        BigDecimal taxable = ZERO_BASE;

        if (product != null
            && (product.get("taxable") == null
                || (product.get("taxable") != null
                    && product.getBoolean("taxable").booleanValue()))) {
          taxable = taxable.add(itemAmount);
        }
        if (shippingAmount != null
            && taxAuthorityRateProduct != null
            && (taxAuthorityRateProduct.get("taxShipping") == null
                || (taxAuthorityRateProduct.get("taxShipping") != null
                    && taxAuthorityRateProduct.getBoolean("taxShipping").booleanValue()))) {
          taxable = taxable.add(shippingAmount);
        }
        if (orderPromotionsAmount != null
            && taxAuthorityRateProduct != null
            && (taxAuthorityRateProduct.get("taxPromotions") == null
                || (taxAuthorityRateProduct.get("taxPromotions") != null
                    && taxAuthorityRateProduct.getBoolean("taxPromotions").booleanValue()))) {
          taxable = taxable.add(orderPromotionsAmount);
        }

        if (taxable.compareTo(BigDecimal.ZERO) == 0) {
          // this should make it less confusing if the taxable flag on the product is not Y/true,
          // and there is no shipping and such
          continue;
        }

        // taxRate is in percentage, so needs to be divided by 100
        BigDecimal taxAmount =
            (taxable.multiply(taxRate))
                .divide(PERCENT_SCALE, salestaxCalcDecimals, salestaxRounding);

        String taxAuthGeoId = taxAuthorityRateProduct.getString("taxAuthGeoId");
        String taxAuthPartyId = taxAuthorityRateProduct.getString("taxAuthPartyId");

        // get glAccountId from TaxAuthorityGlAccount entity using the payToPartyId as the
        // organizationPartyId
        GenericValue taxAuthorityGlAccount =
            delegator.findByPrimaryKey(
                "TaxAuthorityGlAccount",
                UtilMisc.toMap(
                    "taxAuthPartyId",
                    taxAuthPartyId,
                    "taxAuthGeoId",
                    taxAuthGeoId,
                    "organizationPartyId",
                    payToPartyId));
        String taxAuthGlAccountId = null;
        if (taxAuthorityGlAccount != null) {
          taxAuthGlAccountId = taxAuthorityGlAccount.getString("glAccountId");
        } else {
          // TODO: what to do if no TaxAuthorityGlAccount found? Use some default, or is that done
          // elsewhere later on?
        }

        GenericValue productPrice = null;
        if (product != null && taxAuthPartyId != null && taxAuthGeoId != null) {
          // find a ProductPrice for the productId and taxAuth* valxues, and see if it has a
          // priceWithTax value
          Map<String, String> priceFindMap =
              UtilMisc.toMap(
                  "productId",
                  product.getString("productId"),
                  "taxAuthPartyId",
                  taxAuthPartyId,
                  "taxAuthGeoId",
                  taxAuthGeoId,
                  "productPricePurposeId",
                  "PURCHASE");
          List<GenericValue> productPriceList =
              delegator.findByAnd("ProductPrice", priceFindMap, UtilMisc.toList("-fromDate"));
          productPriceList = EntityUtil.filterByDate(productPriceList, true);
          productPrice =
              (productPriceList != null && productPriceList.size() > 0)
                  ? productPriceList.get(0)
                  : null;
          // Debug.logInfo("=================== productId=" + product.getString("productId"),
          // module);
          // Debug.logInfo("=================== productPrice=" + productPrice, module);

        }

        GenericValue taxAdjValue = delegator.makeValue("OrderAdjustment");

        if (productPrice != null && "Y".equals(productPrice.getString("taxInPrice"))) {
          // tax is in the price already, so we want the adjustment to be a VAT_TAX adjustment to be
          // subtracted instead of a SALES_TAX adjustment to be added
          taxAdjValue.set("orderAdjustmentTypeId", "VAT_TAX");

          // the amount will be different because we want to figure out how much of the price was
          // tax, and not how much tax needs to be added
          // the formula is: taxAmount = priceWithTax - (priceWithTax/(1+taxPercentage/100))
          BigDecimal taxAmountIncluded =
              itemAmount.subtract(
                  itemAmount.divide(
                      BigDecimal.ONE.add(
                          taxRate.divide(PERCENT_SCALE, 4, BigDecimal.ROUND_HALF_UP)),
                      3,
                      BigDecimal.ROUND_HALF_UP));
          taxAdjValue.set("amountAlreadyIncluded", taxAmountIncluded);
          taxAdjValue.set("amount", BigDecimal.ZERO);
        } else {
          taxAdjValue.set("orderAdjustmentTypeId", "SALES_TAX");
          taxAdjValue.set("amount", taxAmount);
        }

        taxAdjValue.set("sourcePercentage", taxRate);
        taxAdjValue.set(
            "taxAuthorityRateSeqId", taxAuthorityRateProduct.getString("taxAuthorityRateSeqId"));
        // the primary Geo should be the main jurisdiction that the tax is for, and the secondary
        // would just be to define a parent or wrapping jurisdiction of the primary
        taxAdjValue.set("primaryGeoId", taxAuthGeoId);
        taxAdjValue.set("comments", taxAuthorityRateProduct.getString("description"));
        if (taxAuthPartyId != null) taxAdjValue.set("taxAuthPartyId", taxAuthPartyId);
        if (taxAuthGlAccountId != null) taxAdjValue.set("overrideGlAccountId", taxAuthGlAccountId);
        if (taxAuthGeoId != null) taxAdjValue.set("taxAuthGeoId", taxAuthGeoId);

        // check to see if this party has a tax ID for this, and if the party is tax exempt in the
        // primary (most-local) jurisdiction
        if (UtilValidate.isNotEmpty(billToPartyId) && UtilValidate.isNotEmpty(taxAuthGeoId)) {
          // see if partyId is a member of any groups, if so honor their tax exemptions
          // look for PartyRelationship with partyRelationshipTypeId=GROUP_ROLLUP, the partyIdTo is
          // the group member, so the partyIdFrom is the groupPartyId
          Set<String> billToPartyIdSet = FastSet.newInstance();
          billToPartyIdSet.add(billToPartyId);
          List<GenericValue> partyRelationshipList =
              EntityUtil.filterByDate(
                  delegator.findByAndCache(
                      "PartyRelationship",
                      UtilMisc.toMap(
                          "partyIdTo", billToPartyId, "partyRelationshipTypeId", "GROUP_ROLLUP")),
                  true);
          for (GenericValue partyRelationship : partyRelationshipList) {
            billToPartyIdSet.add(partyRelationship.getString("partyIdFrom"));
          }
          handlePartyTaxExempt(
              taxAdjValue,
              billToPartyIdSet,
              taxAuthGeoId,
              taxAuthPartyId,
              taxAmount,
              nowTimestamp,
              delegator);
        } else {
          Debug.logInfo(
              "NOTE: A tax calculation was done without a billToPartyId or taxAuthGeoId, so no tax exemptions or tax IDs considered; billToPartyId=["
                  + billToPartyId
                  + "] taxAuthGeoId=["
                  + taxAuthGeoId
                  + "]",
              module);
        }

        adjustments.add(taxAdjValue);

        if (productPrice != null
            && itemQuantity != null
            && productPrice.getBigDecimal("priceWithTax") != null
            && !"Y".equals(productPrice.getString("taxInPrice"))) {
          BigDecimal priceWithTax = productPrice.getBigDecimal("priceWithTax");
          BigDecimal price = productPrice.getBigDecimal("price");
          BigDecimal baseSubtotal = price.multiply(itemQuantity);
          BigDecimal baseTaxAmount =
              (baseSubtotal.multiply(taxRate))
                  .divide(PERCENT_SCALE, salestaxCalcDecimals, salestaxRounding);
          // Debug.logInfo("=================== priceWithTax=" + priceWithTax, module);
          // Debug.logInfo("=================== enteredTotalPriceWithTax=" +
          // enteredTotalPriceWithTax, module);
          // Debug.logInfo("=================== calcedTotalPriceWithTax=" + calcedTotalPriceWithTax,
          // module);

          // tax is not already in price so we want to add it in, but this is a VAT situation so
          // adjust to make it as accurate as possible

          // for VAT taxes if the calculated total item price plus calculated taxes is different
          // from what would be
          // expected based on the original entered price with taxes (if the price was entered this
          // way), then create
          // an adjustment that corrects for the difference, and this correction will be effectively
          // subtracted from the
          // price and not from the tax (the tax is meant to be calculated based on Tax Authority
          // rules and so should
          // not be shorted)

          // TODO (don't think this is needed, but just to keep it in mind): get this to work with
          // multiple VAT tax authorities instead of just one (right now will get incorrect totals
          // if there are multiple taxes included in the price)
          // TODO add constraint to ProductPrice lookup by any productStoreGroupId associated with
          // the current productStore

          BigDecimal enteredTotalPriceWithTax = priceWithTax.multiply(itemQuantity);
          BigDecimal calcedTotalPriceWithTax = (baseSubtotal).add(baseTaxAmount);
          if (!enteredTotalPriceWithTax.equals(calcedTotalPriceWithTax)) {
            // if the calced amount is higher than the entered amount we want the value to be
            // negative
            //     to get it down to match the entered amount
            // so, subtract the calced amount from the entered amount (ie: correction = entered -
            // calced)
            BigDecimal correctionAmount =
                enteredTotalPriceWithTax.subtract(calcedTotalPriceWithTax);
            // Debug.logInfo("=================== correctionAmount=" + correctionAmount, module);

            GenericValue correctionAdjValue = delegator.makeValue("OrderAdjustment");
            correctionAdjValue.set(
                "taxAuthorityRateSeqId",
                taxAuthorityRateProduct.getString("taxAuthorityRateSeqId"));
            correctionAdjValue.set("amount", correctionAmount);
            // don't set this, causes a doubling of the tax rate because calling code adds up all
            // tax rates: correctionAdjValue.set("sourcePercentage", taxRate);
            correctionAdjValue.set("orderAdjustmentTypeId", "VAT_PRICE_CORRECT");
            // the primary Geo should be the main jurisdiction that the tax is for, and the
            // secondary would just be to define a parent or wrapping jurisdiction of the primary
            correctionAdjValue.set("primaryGeoId", taxAuthGeoId);
            correctionAdjValue.set("comments", taxAuthorityRateProduct.getString("description"));
            if (taxAuthPartyId != null) correctionAdjValue.set("taxAuthPartyId", taxAuthPartyId);
            if (taxAuthGlAccountId != null)
              correctionAdjValue.set("overrideGlAccountId", taxAuthGlAccountId);
            if (taxAuthGeoId != null) correctionAdjValue.set("taxAuthGeoId", taxAuthGeoId);
            adjustments.add(correctionAdjValue);
          }
        }
      }
    } catch (GenericEntityException e) {
      Debug.logError(e, "Problems looking up tax rates", module);
      return FastList.newInstance();
    }

    return adjustments;
  }