public static boolean isProductInCategory(
      Delegator delegator, String productId, String productCategoryId)
      throws GenericEntityException {
    if (productCategoryId == null) return false;
    if (UtilValidate.isEmpty(productId)) return false;

    List<GenericValue> productCategoryMembers =
        EntityUtil.filterByDate(
            delegator.findByAndCache(
                "ProductCategoryMember",
                UtilMisc.toMap("productCategoryId", productCategoryId, "productId", productId)),
            true);
    if (UtilValidate.isEmpty(productCategoryMembers)) {
      // before giving up see if this is a variant product, and if so look up the virtual product
      // and check it...
      GenericValue product =
          delegator.findByPrimaryKeyCache("Product", UtilMisc.toMap("productId", productId));
      List<GenericValue> productAssocs = ProductWorker.getVariantVirtualAssocs(product);
      // this does take into account that a product could be a variant of multiple products, but
      // this shouldn't ever really happen...
      if (productAssocs != null) {
        for (GenericValue productAssoc : productAssocs) {
          if (isProductInCategory(
              delegator, productAssoc.getString("productId"), productCategoryId)) {
            return true;
          }
        }
      }

      return false;
    } else {
      return true;
    }
  }
Beispiel #2
0
 /** Checks if the given party with role is assigned to the user login. */
 public static boolean isAssignedToUserLogin(
     String partyId, String roleTypeId, GenericValue userLogin) throws GenericEntityException {
   Delegator delegator = userLogin.getDelegator();
   String roleTypeIdTo =
       getFirstValidTeamMemberRoleTypeId(userLogin.getString("partyId"), delegator);
   if (roleTypeIdTo == null) {
     return false;
   }
   List<GenericValue> activeRelationships =
       EntityUtil.filterByDate(
           delegator.findByAnd(
               "PartyRelationship",
               UtilMisc.toMap(
                   "partyIdFrom",
                   partyId,
                   "roleTypeIdFrom",
                   roleTypeId,
                   "partyIdTo",
                   userLogin.get("partyId"),
                   "roleTypeIdTo",
                   roleTypeIdTo,
                   "partyRelationshipTypeId",
                   "ASSIGNED_TO")));
   return activeRelationships.size() > 0;
 }
Beispiel #3
0
  public static List<Map<String, GenericValue>> getPartyPaymentMethodValueMaps(
      Delegator delegator, String partyId, Boolean showOld) {
    List<Map<String, GenericValue>> paymentMethodValueMaps = FastList.newInstance();
    try {
      List<GenericValue> paymentMethods =
          delegator.findByAnd("PaymentMethod", UtilMisc.toMap("partyId", partyId));

      if (!showOld) paymentMethods = EntityUtil.filterByDate(paymentMethods, true);

      for (GenericValue paymentMethod : paymentMethods) {
        Map<String, GenericValue> valueMap = FastMap.newInstance();

        paymentMethodValueMaps.add(valueMap);
        valueMap.put("paymentMethod", paymentMethod);
        if ("CREDIT_CARD".equals(paymentMethod.getString("paymentMethodTypeId"))) {
          GenericValue creditCard = paymentMethod.getRelatedOne("CreditCard");
          if (creditCard != null) valueMap.put("creditCard", creditCard);
        } else if ("GIFT_CARD".equals(paymentMethod.getString("paymentMethodTypeId"))) {
          GenericValue giftCard = paymentMethod.getRelatedOne("GiftCard");
          if (giftCard != null) valueMap.put("giftCard", giftCard);
        } else if ("EFT_ACCOUNT".equals(paymentMethod.getString("paymentMethodTypeId"))) {
          GenericValue eftAccount = paymentMethod.getRelatedOne("EftAccount");
          if (eftAccount != null) valueMap.put("eftAccount", eftAccount);
        }
      }
    } catch (GenericEntityException e) {
      Debug.logWarning(e, module);
    }
    return paymentMethodValueMaps;
  }
Beispiel #4
0
  public static GenericValue getPaymentAddress(Delegator delegator, String partyId) {
    List<GenericValue> paymentAddresses = null;
    try {
      paymentAddresses =
          delegator.findByAnd(
              "PartyContactMechPurpose",
              UtilMisc.toMap("partyId", partyId, "contactMechPurposeTypeId", "PAYMENT_LOCATION"),
              UtilMisc.toList("-fromDate"));
      paymentAddresses = EntityUtil.filterByDate(paymentAddresses);
    } catch (GenericEntityException e) {
      Debug.logError(e, "Trouble getting PartyContactMechPurpose entity list", module);
    }

    // get the address for the primary contact mech
    GenericValue purpose = EntityUtil.getFirst(paymentAddresses);
    GenericValue postalAddress = null;
    if (purpose != null) {
      try {
        postalAddress =
            delegator.findByPrimaryKey(
                "PostalAddress",
                UtilMisc.toMap("contactMechId", purpose.getString("contactMechId")));
      } catch (GenericEntityException e) {
        Debug.logError(
            e,
            "Trouble getting PostalAddress record for contactMechId: "
                + purpose.getString("contactMechId"),
            module);
      }
    }

    return postalAddress;
  }
  public static Map<String, Object> reassignContactResponsibleParty(
      DispatchContext dctx, Map<String, Object> context) {
    Delegator delegator = dctx.getDelegator();
    LocalDispatcher dispatcher = dctx.getDispatcher();
    Security security = dctx.getSecurity();
    GenericValue userLogin = (GenericValue) context.get("userLogin");
    Locale locale = UtilCommon.getLocale(context);

    String contactPartyId = (String) context.get("contactPartyId");
    String newPartyId = (String) context.get("newPartyId");

    // ensure reassign permission on this contact
    if (!CrmsfaSecurity.hasPartyRelationSecurity(
        security, "CRMSFA_CONTACT", "_REASSIGN", userLogin, contactPartyId)) {
      return UtilMessage.createAndLogServiceError("CrmErrorPermissionDenied", locale, MODULE);
    }
    try {
      // we need to expire all the active ASSIGNED_TO relationships from the contact party to the
      // new owner party
      List<GenericValue> activeAssignedToRelationships =
          EntityUtil.filterByDate(
              delegator.findByAnd(
                  "PartyRelationship",
                  UtilMisc.toMap(
                      "partyIdFrom",
                      contactPartyId,
                      "roleTypeIdFrom",
                      "CONTACT",
                      "partyIdTo",
                      newPartyId,
                      "partyRelationshipTypeId",
                      "ASSIGNED_TO")));
      PartyHelper.expirePartyRelationships(
          activeAssignedToRelationships, UtilDateTime.nowTimestamp(), dispatcher, userLogin);

      // reassign relationship using a helper method
      boolean result =
          createResponsibleContactRelationshipForParty(
              newPartyId, contactPartyId, userLogin, delegator, dispatcher);
      if (!result) {
        return UtilMessage.createAndLogServiceError("CrmErrorReassignFail", locale, MODULE);
      }
    } catch (GenericServiceException e) {
      return UtilMessage.createAndLogServiceError(e, "CrmErrorReassignFail", locale, MODULE);
    } catch (GenericEntityException e) {
      return UtilMessage.createAndLogServiceError(e, "CrmErrorReassignFail", locale, MODULE);
    }
    return ServiceUtil.returnSuccess();
  }
Beispiel #6
0
 /** Checks if the given party with role is unassigned. */
 public static boolean isUnassigned(Delegator delegator, String partyId, String roleTypeId)
     throws GenericEntityException {
   List<GenericValue> activeRelationships =
       EntityUtil.filterByDate(
           delegator.findByAnd(
               "PartyRelationship",
               UtilMisc.toMap(
                   "partyIdFrom",
                   partyId,
                   "roleTypeIdFrom",
                   roleTypeId,
                   "partyRelationshipTypeId",
                   "ASSIGNED_TO")));
   return activeRelationships.size() == 0;
 }
 private boolean evalRoleMember(GenericValue userLogin) {
   if (nameOrRole == null) {
     Debug.logWarning("Null role type name passed for evaluation", module);
     return false;
   }
   List<GenericValue> partyRoles = null;
   /**
    * (jaz) THIS IS NOT SECURE AT ALL try { partyRoles = delegator.findByAnd("PartyRole",
    * "roleTypeId", nameOrRole, "partyId", userLogin.get("partyId")); } catch
    * (GenericEntityException e) { Debug.logError(e, "Unable to lookup PartyRole records", module);
    * }
    */
   if (UtilValidate.isNotEmpty(partyRoles)) {
     partyRoles = EntityUtil.filterByDate(partyRoles);
     if (UtilValidate.isNotEmpty(partyRoles)) {
       return true;
     }
   }
   return false;
 }
Beispiel #8
0
 public static GenericValue findPartyLatestContactMech(
     String partyId, String contactMechTypeId, Delegator delegator) {
   try {
     List<GenericValue> cmList =
         delegator.findByAnd(
             "PartyAndContactMech",
             UtilMisc.toMap("partyId", partyId, "contactMechTypeId", contactMechTypeId),
             UtilMisc.toList("-fromDate"));
     cmList = EntityUtil.filterByDate(cmList);
     return EntityUtil.getFirst(cmList);
   } catch (GenericEntityException e) {
     Debug.logError(
         e,
         "Error while finding latest ContactMech for party with ID ["
             + partyId
             + "] TYPE ["
             + contactMechTypeId
             + "]: "
             + e.toString(),
         module);
     return null;
   }
 }
  public static List<GenericValue> getRelatedCategoriesRet(
      Delegator delegator,
      String attributeName,
      String parentId,
      boolean limitView,
      boolean excludeEmpty,
      boolean recursive) {
    List<GenericValue> categories = FastList.newInstance();

    if (Debug.verboseOn())
      Debug.logVerbose("[CategoryWorker.getRelatedCategories] ParentID: " + parentId, module);

    List<GenericValue> rollups = null;

    try {
      rollups =
          delegator.findByAndCache(
              "ProductCategoryRollup",
              UtilMisc.toMap("parentProductCategoryId", parentId),
              UtilMisc.toList("sequenceNum"));
      if (limitView) {
        rollups = EntityUtil.filterByDate(rollups, true);
      }
    } catch (GenericEntityException e) {
      Debug.logWarning(e.getMessage(), module);
    }
    if (rollups != null) {
      // Debug.logInfo("Rollup size: " + rollups.size(), module);
      for (GenericValue parent : rollups) {
        // Debug.logInfo("Adding child of: " + parent.getString("parentProductCategoryId"), module);
        GenericValue cv = null;

        try {
          cv = parent.getRelatedOneCache("CurrentProductCategory");
        } catch (GenericEntityException e) {
          Debug.logWarning(e.getMessage(), module);
        }
        if (cv != null) {
          if (excludeEmpty) {
            if (!isCategoryEmpty(cv)) {
              // Debug.logInfo("Child : " + cv.getString("productCategoryId") + " is not empty.",
              // module);
              categories.add(cv);
              if (recursive) {
                categories.addAll(
                    getRelatedCategoriesRet(
                        delegator,
                        attributeName,
                        cv.getString("productCategoryId"),
                        limitView,
                        excludeEmpty,
                        recursive));
              }
            }
          } else {
            categories.add(cv);
            if (recursive) {
              categories.addAll(
                  getRelatedCategoriesRet(
                      delegator,
                      attributeName,
                      cv.getString("productCategoryId"),
                      limitView,
                      excludeEmpty,
                      recursive));
            }
          }
        }
      }
    }
    return categories;
  }
  /**
   * Creates and updates a PartyRelationship creating related PartyRoles if needed. A side of the
   * relationship is checked to maintain history
   *
   * @param ctx The DispatchContext that this service is operating in
   * @param context Map containing the input parameters
   * @return Map with the result of the service, the output parameters
   */
  public static Map<String, Object> createUpdatePartyRelationshipAndRoles(
      DispatchContext ctx, Map<String, ? extends Object> context) {
    Map<String, Object> result = FastMap.newInstance();
    Delegator delegator = ctx.getDelegator();
    LocalDispatcher dispatcher = ctx.getDispatcher();

    try {
      List<GenericValue> partyRelationShipList =
          PartyRelationshipHelper.getActivePartyRelationships(delegator, context);
      if (UtilValidate.isEmpty(
          partyRelationShipList)) { // If already exists and active nothing to do: keep the current
                                    // one
        String partyId = (String) context.get("partyId");
        String partyIdFrom = (String) context.get("partyIdFrom");
        String partyIdTo = (String) context.get("partyIdTo");
        String roleTypeIdFrom = (String) context.get("roleTypeIdFrom");
        String roleTypeIdTo = (String) context.get("roleTypeIdTo");
        String partyRelationshipTypeId = (String) context.get("partyRelationshipTypeId");

        // Before creating the partyRelationShip, create the partyRoles if they don't exist
        GenericValue partyToRole = null;
        partyToRole =
            delegator.findOne(
                "PartyRole",
                UtilMisc.toMap("partyId", partyIdTo, "roleTypeId", roleTypeIdTo),
                false);
        if (partyToRole == null) {
          partyToRole =
              delegator.makeValue(
                  "PartyRole", UtilMisc.toMap("partyId", partyIdTo, "roleTypeId", roleTypeIdTo));
          partyToRole.create();
        }

        GenericValue partyFromRole = null;
        partyFromRole =
            delegator.findOne(
                "PartyRole",
                UtilMisc.toMap("partyId", partyIdFrom, "roleTypeId", roleTypeIdFrom),
                false);
        if (partyFromRole == null) {
          partyFromRole =
              delegator.makeValue(
                  "PartyRole",
                  UtilMisc.toMap("partyId", partyIdFrom, "roleTypeId", roleTypeIdFrom));
          partyFromRole.create();
        }

        // Check if there is already a partyRelationship of that type with another party from the
        // side indicated
        String sideChecked = partyIdFrom.equals(partyId) ? "partyIdFrom" : "partyIdTo";
        partyRelationShipList =
            delegator.findByAnd(
                "PartyRelationship",
                UtilMisc.toMap(
                    sideChecked,
                    partyId,
                    "roleTypeIdFrom",
                    roleTypeIdFrom,
                    "roleTypeIdTo",
                    roleTypeIdTo,
                    "partyRelationshipTypeId",
                    partyRelationshipTypeId));
        // We consider the last one (in time) as sole active (we try to maintain a unique
        // relationship and keep changes history)
        partyRelationShipList = EntityUtil.filterByDate(partyRelationShipList);
        GenericValue oldPartyRelationShip = EntityUtil.getFirst(partyRelationShipList);
        if (UtilValidate.isNotEmpty(oldPartyRelationShip)) {
          oldPartyRelationShip.setFields(
              UtilMisc.toMap("thruDate", UtilDateTime.nowTimestamp())); // Current becomes inactive
          oldPartyRelationShip.store();
        }
        try {
          dispatcher.runSync("createPartyRelationship", context); // Create new one
        } catch (GenericServiceException e) {
          Debug.logWarning(e.getMessage(), module);
          return ServiceUtil.returnError(
              "Could not create party relationship (write failure): " + e.getMessage());
        }
      }
    } catch (GenericEntityException e) {
      Debug.logWarning(e.getMessage(), module);
      return ServiceUtil.returnError(
          "Could not create party relationship (write failure): " + e.getMessage());
    }
    result.put(ModelService.RESPONSE_MESSAGE, ModelService.RESPOND_SUCCESS);
    return result;
  }
Beispiel #11
0
  /**
   * Finds all matching parties based on the values provided. Excludes party records with a statusId
   * of PARTY_DISABLED. Results are ordered by descending PartyContactMech.fromDate. 1. Candidate
   * addresses are found by querying PartyAndPostalAddress using the supplied city and if provided,
   * stateProvinceGeoId, postalCode, postalCodeExt and countryGeoId 2. In-memory address line
   * comparisons are then performed against the supplied address1 and if provided, address2. Address
   * lines are compared after the strings have been converted using {@link
   * #makeMatchingString(Delegator, String)}.
   *
   * @param delegator Delegator instance
   * @param address1 PostalAddress.address1 to match against (Required).
   * @param address2 Optional PostalAddress.address2 to match against.
   * @param city PostalAddress.city value to match against (Required).
   * @param stateProvinceGeoId Optional PostalAddress.stateProvinceGeoId value to match against. If
   *     null or "**" is passed then the value will be ignored during matching. "NA" can be passed
   *     in place of "_NA_".
   * @param postalCode PostalAddress.postalCode value to match against. Cannot be null but can be
   *     skipped by passing a value starting with an "*". If the length of the supplied string is 10
   *     characters and the string contains a "-" then the postal code will be split at the "-" and
   *     the second half will be used as the postalCodeExt.
   * @param postalCodeExt Optional PostalAddress.postalCodeExt value to match against. Will be
   *     overridden if a postalCodeExt value is retrieved from postalCode as described above.
   * @param countryGeoId Optional PostalAddress.countryGeoId value to match against.
   * @param partyTypeId Optional Party.partyTypeId to match against.
   * @return List of PartyAndPostalAddress GenericValue objects that match the supplied criteria.
   * @throws GenericEntityException
   */
  public static List<GenericValue> findMatchingPartyPostalAddress(
      Delegator delegator,
      String address1,
      String address2,
      String city,
      String stateProvinceGeoId,
      String postalCode,
      String postalCodeExt,
      String countryGeoId,
      String partyTypeId)
      throws GenericEntityException {

    if (address1 == null || city == null || postalCode == null) {
      throw new IllegalArgumentException();
    }

    List<EntityCondition> addrExprs = FastList.newInstance();
    if (stateProvinceGeoId != null) {
      if ("**".equals(stateProvinceGeoId)) {
        Debug.logWarning("Illegal state code passed!", module);
      } else if ("NA".equals(stateProvinceGeoId)) {
        addrExprs.add(
            EntityCondition.makeCondition("stateProvinceGeoId", EntityOperator.EQUALS, "_NA_"));
      } else {
        addrExprs.add(
            EntityCondition.makeCondition(
                "stateProvinceGeoId", EntityOperator.EQUALS, stateProvinceGeoId.toUpperCase()));
      }
    }

    if (!postalCode.startsWith("*")) {
      if (postalCode.length() == 10 && postalCode.indexOf("-") != -1) {
        String[] zipSplit = postalCode.split("-", 2);
        postalCode = zipSplit[0];
        postalCodeExt = zipSplit[1];
      }
      addrExprs.add(EntityCondition.makeCondition("postalCode", EntityOperator.EQUALS, postalCode));
    }

    if (postalCodeExt != null) {
      addrExprs.add(
          EntityCondition.makeCondition("postalCodeExt", EntityOperator.EQUALS, postalCodeExt));
    }

    addrExprs.add(
        EntityCondition.makeCondition(
            EntityFunction.UPPER_FIELD("city"), EntityOperator.EQUALS, EntityFunction.UPPER(city)));

    if (countryGeoId != null) {
      addrExprs.add(
          EntityCondition.makeCondition(
              "countryGeoId", EntityOperator.EQUALS, countryGeoId.toUpperCase()));
    }

    // limit to only non-disabled status
    addrExprs.add(
        EntityCondition.makeCondition(
            EntityCondition.makeCondition("statusId", EntityOperator.EQUALS, null),
            EntityOperator.OR,
            EntityCondition.makeCondition("statusId", EntityOperator.NOT_EQUAL, "PARTY_DISABLED")));

    if (partyTypeId != null) {
      addrExprs.add(
          EntityCondition.makeCondition("partyTypeId", EntityOperator.EQUALS, partyTypeId));
    }

    List<String> sort = UtilMisc.toList("-fromDate");
    EntityCondition addrCond = EntityCondition.makeCondition(addrExprs, EntityOperator.AND);
    List<GenericValue> addresses =
        EntityUtil.filterByDate(
            delegator.findList("PartyAndPostalAddress", addrCond, null, sort, null, false));
    // Debug.logInfo("Checking for matching address: " + addrCond.toString() + "[" +
    // addresses.size() + "]", module);

    if (UtilValidate.isEmpty(addresses)) {
      // No address matches, return an empty list
      return addresses;
    }

    List<GenericValue> validFound = FastList.newInstance();
    // check the address line
    for (GenericValue address : addresses) {
      // address 1 field
      String addr1Source = PartyWorker.makeMatchingString(delegator, address1);
      String addr1Target = PartyWorker.makeMatchingString(delegator, address.getString("address1"));

      if (addr1Target != null) {
        Debug.logInfo("Comparing address1 : " + addr1Source + " / " + addr1Target, module);
        if (addr1Target.equals(addr1Source)) {

          // address 2 field
          if (address2 != null) {
            String addr2Source = PartyWorker.makeMatchingString(delegator, address2);
            String addr2Target =
                PartyWorker.makeMatchingString(delegator, address.getString("address2"));
            if (addr2Target != null) {
              Debug.logInfo("Comparing address2 : " + addr2Source + " / " + addr2Target, module);

              if (addr2Source.equals(addr2Target)) {
                Debug.logInfo("Matching address2; adding valid address", module);
                validFound.add(address);
                // validParty.put(address.getString("partyId"), address.getString("contactMechId"));
              }
            }
          } else {
            if (address.get("address2") == null) {
              Debug.logInfo("No address2; adding valid address", module);
              validFound.add(address);
              // validParty.put(address.getString("partyId"), address.getString("contactMechId"));
            }
          }
        }
      }
    }
    return validFound;
  }
Beispiel #12
0
  public static void indexKeywords(GenericValue product, boolean doAll)
      throws GenericEntityException {
    if (product == null) return;
    Timestamp nowTimestamp = UtilDateTime.nowTimestamp();

    if (!doAll) {
      if ("N".equals(product.getString("autoCreateKeywords"))) {
        return;
      }
      if ("Y".equals(product.getString("isVariant"))
          && "true"
              .equals(UtilProperties.getPropertyValue("prodsearch", "index.ignore.variants"))) {
        return;
      }
      Timestamp salesDiscontinuationDate = product.getTimestamp("salesDiscontinuationDate");
      if (salesDiscontinuationDate != null
          && salesDiscontinuationDate.before(nowTimestamp)
          && "true"
              .equals(
                  UtilProperties.getPropertyValue(
                      "prodsearch", "index.ignore.discontinued.sales"))) {
        return;
      }
    }

    Delegator delegator = product.getDelegator();
    if (delegator == null) return;
    String productId = product.getString("productId");

    // get these in advance just once since they will be used many times for the multiple strings to
    // index
    String separators = KeywordSearchUtil.getSeparators();
    String stopWordBagOr = KeywordSearchUtil.getStopWordBagOr();
    String stopWordBagAnd = KeywordSearchUtil.getStopWordBagAnd();
    boolean removeStems = KeywordSearchUtil.getRemoveStems();
    Set<String> stemSet = KeywordSearchUtil.getStemSet();

    Map<String, Long> keywords = new TreeMap<String, Long>();
    List<String> strings = FastList.newInstance();

    int pidWeight = 1;
    try {
      pidWeight =
          Integer.parseInt(
              UtilProperties.getPropertyValue("prodsearch", "index.weight.Product.productId", "0"));
    } catch (Exception e) {
      Debug.logWarning("Could not parse weight number: " + e.toString(), module);
    }
    keywords.put(product.getString("productId").toLowerCase(), Long.valueOf(pidWeight));

    // Product fields - default is 0 if not found in the properties file
    if (!"0"
        .equals(
            UtilProperties.getPropertyValue(
                "prodsearch", "index.weight.Product.productName", "0"))) {
      addWeightedKeywordSourceString(product, "productName", strings);
    }
    if (!"0"
        .equals(
            UtilProperties.getPropertyValue(
                "prodsearch", "index.weight.Product.internalName", "0"))) {
      addWeightedKeywordSourceString(product, "internalName", strings);
    }
    if (!"0"
        .equals(
            UtilProperties.getPropertyValue("prodsearch", "index.weight.Product.brandName", "0"))) {
      addWeightedKeywordSourceString(product, "brandName", strings);
    }
    if (!"0"
        .equals(
            UtilProperties.getPropertyValue(
                "prodsearch", "index.weight.Product.description", "0"))) {
      addWeightedKeywordSourceString(product, "description", strings);
    }
    if (!"0"
        .equals(
            UtilProperties.getPropertyValue(
                "prodsearch", "index.weight.Product.longDescription", "0"))) {
      addWeightedKeywordSourceString(product, "longDescription", strings);
    }

    // ProductFeatureAppl
    if (!"0"
            .equals(
                UtilProperties.getPropertyValue(
                    "prodsearch", "index.weight.ProductFeatureAndAppl.description", "0"))
        || !"0"
            .equals(
                UtilProperties.getPropertyValue(
                    "prodsearch", "index.weight.ProductFeatureAndAppl.abbrev", "0"))
        || !"0"
            .equals(
                UtilProperties.getPropertyValue(
                    "prodsearch", "index.weight.ProductFeatureAndAppl.idCode", "0"))) {
      // get strings from attributes and features
      List<GenericValue> productFeatureAndAppls =
          delegator.findByAnd("ProductFeatureAndAppl", UtilMisc.toMap("productId", productId));
      for (GenericValue productFeatureAndAppl : productFeatureAndAppls) {
        addWeightedKeywordSourceString(productFeatureAndAppl, "description", strings);
        addWeightedKeywordSourceString(productFeatureAndAppl, "abbrev", strings);
        addWeightedKeywordSourceString(productFeatureAndAppl, "idCode", strings);
      }
    }

    // ProductAttribute
    if (!"0"
            .equals(
                UtilProperties.getPropertyValue(
                    "prodsearch", "index.weight.ProductAttribute.attrName", "0"))
        || !"0"
            .equals(
                UtilProperties.getPropertyValue(
                    "prodsearch", "index.weight.ProductAttribute.attrValue", "0"))) {
      List<GenericValue> productAttributes =
          delegator.findByAnd("ProductAttribute", UtilMisc.toMap("productId", productId));
      for (GenericValue productAttribute : productAttributes) {
        addWeightedKeywordSourceString(productAttribute, "attrName", strings);
        addWeightedKeywordSourceString(productAttribute, "attrValue", strings);
      }
    }

    // GoodIdentification
    if (!"0"
        .equals(
            UtilProperties.getPropertyValue(
                "prodsearch", "index.weight.GoodIdentification.idValue", "0"))) {
      List<GenericValue> goodIdentifications =
          delegator.findByAnd("GoodIdentification", UtilMisc.toMap("productId", productId));
      for (GenericValue goodIdentification : goodIdentifications) {
        addWeightedKeywordSourceString(goodIdentification, "idValue", strings);
      }
    }

    // Variant Product IDs
    if ("Y".equals(product.getString("isVirtual"))) {
      if (!"0"
          .equals(
              UtilProperties.getPropertyValue(
                  "prodsearch", "index.weight.Variant.Product.productId", "0"))) {
        List<GenericValue> variantProductAssocs =
            delegator.findByAnd(
                "ProductAssoc",
                UtilMisc.toMap("productId", productId, "productAssocTypeId", "PRODUCT_VARIANT"));
        variantProductAssocs = EntityUtil.filterByDate(variantProductAssocs);
        for (GenericValue variantProductAssoc : variantProductAssocs) {
          int weight = 1;
          try {
            weight =
                Integer.parseInt(
                    UtilProperties.getPropertyValue(
                        "prodsearch", "index.weight.Variant.Product.productId", "0"));
          } catch (Exception e) {
            Debug.logWarning("Could not parse weight number: " + e.toString(), module);
          }
          for (int i = 0; i < weight; i++) {
            strings.add(variantProductAssoc.getString("productIdTo"));
          }
        }
      }
    }

    String productContentTypes =
        UtilProperties.getPropertyValue("prodsearch", "index.include.ProductContentTypes");
    for (String productContentTypeId : productContentTypes.split(",")) {
      int weight = 1;
      try {
        // this is defaulting to a weight of 1 because you specified you wanted to index this type
        weight =
            Integer.parseInt(
                UtilProperties.getPropertyValue(
                    "prodsearch", "index.weight.ProductContent." + productContentTypeId, "1"));
      } catch (Exception e) {
        Debug.logWarning("Could not parse weight number: " + e.toString(), module);
      }

      List<GenericValue> productContentAndInfos =
          delegator.findByAnd(
              "ProductContentAndInfo",
              UtilMisc.toMap("productId", productId, "productContentTypeId", productContentTypeId),
              null);
      for (GenericValue productContentAndInfo : productContentAndInfos) {
        addWeightedDataResourceString(productContentAndInfo, weight, strings, delegator, product);

        List<GenericValue> alternateViews =
            productContentAndInfo.getRelated(
                "ContentAssocDataResourceViewTo",
                UtilMisc.toMap("caContentAssocTypeId", "ALTERNATE_LOCALE"),
                UtilMisc.toList("-caFromDate"));
        alternateViews =
            EntityUtil.filterByDate(
                alternateViews, UtilDateTime.nowTimestamp(), "caFromDate", "caThruDate", true);
        for (GenericValue thisView : alternateViews) {
          addWeightedDataResourceString(thisView, weight, strings, delegator, product);
        }
      }
    }
    if (UtilValidate.isNotEmpty(strings)) {
      for (String str : strings) {
        // call process keywords method here
        KeywordSearchUtil.processKeywordsForIndex(
            str, keywords, separators, stopWordBagAnd, stopWordBagOr, removeStems, stemSet);
      }
    }

    List<GenericValue> toBeStored = FastList.newInstance();
    int keywordMaxLength =
        Integer.parseInt(
            UtilProperties.getPropertyValue("prodsearch", "product.keyword.max.length"));
    for (Map.Entry<String, Long> entry : keywords.entrySet()) {
      if (entry.getKey().length() <= keywordMaxLength) {
        GenericValue productKeyword =
            delegator.makeValue(
                "ProductKeyword",
                UtilMisc.toMap(
                    "productId",
                    product.getString("productId"),
                    "keyword",
                    entry.getKey(),
                    "keywordTypeId",
                    "KWT_KEYWORD",
                    "relevancyWeight",
                    entry.getValue()));
        toBeStored.add(productKeyword);
      }
    }
    if (toBeStored.size() > 0) {
      if (Debug.verboseOn())
        Debug.logVerbose(
            "[KeywordIndex.indexKeywords] Storing "
                + toBeStored.size()
                + " keywords for productId "
                + product.getString("productId"),
            module);

      if ("true"
          .equals(
              UtilProperties.getPropertyValue("prodsearch", "index.delete.on_index", "false"))) {
        // delete all keywords if the properties file says to
        delegator.removeByAnd(
            "ProductKeyword", UtilMisc.toMap("productId", product.getString("productId")));
      }

      delegator.storeAll(toBeStored);
    }
  }
  private static void handlePartyTaxExempt(
      GenericValue adjValue,
      Set<String> billToPartyIdSet,
      String taxAuthGeoId,
      String taxAuthPartyId,
      BigDecimal taxAmount,
      Timestamp nowTimestamp,
      Delegator delegator)
      throws GenericEntityException {
    Debug.logInfo("Checking for tax exemption : " + taxAuthGeoId + " / " + taxAuthPartyId, module);
    List<EntityCondition> ptiConditionList =
        UtilMisc.<EntityCondition>toList(
            EntityCondition.makeCondition("partyId", EntityOperator.IN, billToPartyIdSet),
            EntityCondition.makeCondition("taxAuthGeoId", EntityOperator.EQUALS, taxAuthGeoId),
            EntityCondition.makeCondition("taxAuthPartyId", EntityOperator.EQUALS, taxAuthPartyId));
    ptiConditionList.add(
        EntityCondition.makeCondition("fromDate", EntityOperator.LESS_THAN_EQUAL_TO, nowTimestamp));
    ptiConditionList.add(
        EntityCondition.makeCondition(
            EntityCondition.makeCondition("thruDate", EntityOperator.EQUALS, null),
            EntityOperator.OR,
            EntityCondition.makeCondition("thruDate", EntityOperator.GREATER_THAN, nowTimestamp)));
    EntityCondition ptiCondition =
        EntityCondition.makeCondition(ptiConditionList, EntityOperator.AND);
    // sort by -fromDate to get the newest (largest) first, just in case there is more than one, we
    // only want the most recent valid one, should only be one per jurisdiction...
    List<GenericValue> partyTaxInfos =
        delegator.findList(
            "PartyTaxAuthInfo", ptiCondition, null, UtilMisc.toList("-fromDate"), null, false);

    boolean foundExemption = false;
    if (partyTaxInfos.size() > 0) {
      GenericValue partyTaxInfo = partyTaxInfos.get(0);
      adjValue.set("customerReferenceId", partyTaxInfo.get("partyTaxId"));
      if ("Y".equals(partyTaxInfo.getString("isExempt"))) {
        adjValue.set("amount", BigDecimal.ZERO);
        adjValue.set("exemptAmount", taxAmount);
        foundExemption = true;
      }
    }

    // if no exceptions were found for the current; try the parent
    if (!foundExemption) {
      // try the "parent" TaxAuthority
      List<GenericValue> taxAuthorityAssocList =
          delegator.findByAndCache(
              "TaxAuthorityAssoc",
              UtilMisc.toMap(
                  "toTaxAuthGeoId",
                  taxAuthGeoId,
                  "toTaxAuthPartyId",
                  taxAuthPartyId,
                  "taxAuthorityAssocTypeId",
                  "EXEMPT_INHER"),
              UtilMisc.toList("-fromDate"));
      taxAuthorityAssocList = EntityUtil.filterByDate(taxAuthorityAssocList, true);
      GenericValue taxAuthorityAssoc = EntityUtil.getFirst(taxAuthorityAssocList);
      // Debug.logInfo("Parent assoc to " + taxAuthGeoId + " : " + taxAuthorityAssoc, module);
      if (taxAuthorityAssoc != null) {
        handlePartyTaxExempt(
            adjValue,
            billToPartyIdSet,
            taxAuthorityAssoc.getString("taxAuthGeoId"),
            taxAuthorityAssoc.getString("taxAuthPartyId"),
            taxAmount,
            nowTimestamp,
            delegator);
      }
    }
  }
  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;
  }