Exemple #1
0
  /**
   * Converts the supplied String into a String suitable for address line matching. Performs the
   * following transformations on the supplied String: - Converts to upper case - Retrieves all
   * records from the AddressMatchMap table and replaces all occurrences of addressMatchMap.mapKey
   * with addressMatchMap.mapValue using upper case matching. - Removes all non-word characters from
   * the String i.e. everything except A-Z, 0-9 and _
   *
   * @param delegator A Delegator instance
   * @param address The address String to convert
   * @return The converted Address
   */
  public static String makeMatchingString(Delegator delegator, String address) {
    if (address == null) {
      return null;
    }

    // upper case the address
    String str = address.trim().toUpperCase();

    // replace mapped words
    List<GenericValue> addressMap = null;
    try {
      addressMap =
          delegator.findList(
              "AddressMatchMap", null, null, UtilMisc.toList("sequenceNum"), null, false);
    } catch (GenericEntityException e) {
      Debug.logError(e, module);
    }

    if (addressMap != null) {
      for (GenericValue v : addressMap) {
        str =
            str.replaceAll(
                v.getString("mapKey").toUpperCase(), v.getString("mapValue").toUpperCase());
      }
    }

    // remove all non-word characters
    return str.replaceAll("\\W", "");
  }
Exemple #2
0
 public static List<String> getAssociatedPartyIdsByRelationshipType(
     Delegator delegator, String partyIdFrom, String partyRelationshipTypeId) {
   List<GenericValue> partyList = FastList.newInstance();
   List<String> partyIds = null;
   try {
     EntityConditionList<EntityExpr> baseExprs =
         EntityCondition.makeCondition(
             UtilMisc.toList(
                 EntityCondition.makeCondition("partyIdFrom", partyIdFrom),
                 EntityCondition.makeCondition(
                     "partyRelationshipTypeId", partyRelationshipTypeId)),
             EntityOperator.AND);
     List<GenericValue> associatedParties =
         delegator.findList("PartyRelationship", baseExprs, null, null, null, true);
     partyList.addAll(associatedParties);
     while (UtilValidate.isNotEmpty(associatedParties)) {
       List<GenericValue> currentAssociatedParties = FastList.newInstance();
       for (GenericValue associatedParty : associatedParties) {
         EntityConditionList<EntityExpr> innerExprs =
             EntityCondition.makeCondition(
                 UtilMisc.toList(
                     EntityCondition.makeCondition(
                         "partyIdFrom", associatedParty.get("partyIdTo")),
                     EntityCondition.makeCondition(
                         "partyRelationshipTypeId", partyRelationshipTypeId)),
                 EntityOperator.AND);
         List<GenericValue> associatedPartiesChilds =
             delegator.findList("PartyRelationship", innerExprs, null, null, null, true);
         if (UtilValidate.isNotEmpty(associatedPartiesChilds)) {
           currentAssociatedParties.addAll(associatedPartiesChilds);
         }
         partyList.add(associatedParty);
       }
       associatedParties = currentAssociatedParties;
     }
     partyIds = EntityUtil.getFieldListFromEntityList(partyList, "partyIdTo", true);
   } catch (GenericEntityException e) {
     Debug.logWarning(e, module);
   }
   return partyIds;
 }
 /**
  * Returns a complete category trail - can be used for exporting proper category trees. This is
  * mostly useful when used in combination with bread-crumbs, for building a faceted index tree, or
  * to export a category tree for migration to another system. Will create the tree from root point
  * to categoryId.
  *
  * <p>This method is not meant to be run on every request. Its best use is to generate the trail
  * every so often and store somewhere (a lucene/solr tree, entities, cache or so).
  *
  * @param dctx The DispatchContext that this service is operating in
  * @param context Map containing the input parameters
  * @return Map organized trail from root point to categoryId.
  */
 public static Map getCategoryTrail(DispatchContext dctx, Map context) {
   String productCategoryId = (String) context.get("productCategoryId");
   Map<String, Object> results = ServiceUtil.returnSuccess();
   Delegator delegator = dctx.getDelegator();
   List<String> trailElements = FastList.newInstance();
   trailElements.add(productCategoryId);
   String parentProductCategoryId = productCategoryId;
   while (UtilValidate.isNotEmpty(parentProductCategoryId)) {
     // find product category rollup
     try {
       List<EntityCondition> rolllupConds = FastList.newInstance();
       rolllupConds.add(
           EntityCondition.makeCondition("productCategoryId", parentProductCategoryId));
       rolllupConds.add(EntityUtil.getFilterByDateExpr());
       List<GenericValue> productCategoryRollups =
           delegator.findList(
               "ProductCategoryRollup",
               EntityCondition.makeCondition(rolllupConds),
               null,
               UtilMisc.toList("sequenceNum"),
               null,
               true);
       if (UtilValidate.isNotEmpty(productCategoryRollups)) {
         // add only categories that belong to the top category to trail
         for (GenericValue productCategoryRollup : productCategoryRollups) {
           String trailCategoryId = productCategoryRollup.getString("parentProductCategoryId");
           parentProductCategoryId = trailCategoryId;
           if (trailElements.contains(trailCategoryId)) {
             break;
           } else {
             trailElements.add(trailCategoryId);
           }
         }
       } else {
         parentProductCategoryId = null;
       }
     } catch (GenericEntityException e) {
       Map<String, String> messageMap =
           UtilMisc.toMap("errMessage", ". Cannot generate trail from product category. ");
       String errMsg =
           UtilProperties.getMessage(
               "CommonUiLabels",
               "CommonDatabaseProblem",
               messageMap,
               (Locale) context.get("locale"));
       Debug.logError(e, errMsg, module);
       return ServiceUtil.returnError(errMsg);
     }
   }
   Collections.reverse(trailElements);
   results.put("trail", trailElements);
   return results;
 }
  public static Map<String, Object> convertOrderIdListToHeaders(
      DispatchContext dctx, Map<String, ? extends Object> context) {
    Delegator delegator = dctx.getDelegator();

    List<GenericValue> orderHeaderList = UtilGenerics.checkList(context.get("orderHeaderList"));
    List<String> orderIdList = UtilGenerics.checkList(context.get("orderIdList"));

    // we don't want to process if there is already a header list
    if (orderHeaderList == null) {
      // convert the ID list to headers
      if (orderIdList != null) {
        List<EntityCondition> conditionList1 = FastList.newInstance();
        List<EntityCondition> conditionList2 = FastList.newInstance();

        // we are only concerned about approved sales orders
        conditionList2.add(
            EntityCondition.makeCondition("statusId", EntityOperator.EQUALS, "ORDER_APPROVED"));
        conditionList2.add(
            EntityCondition.makeCondition("orderTypeId", EntityOperator.EQUALS, "SALES_ORDER"));

        // build the expression list from the IDs
        for (String orderId : orderIdList) {
          conditionList1.add(
              EntityCondition.makeCondition("orderId", EntityOperator.EQUALS, orderId));
        }

        // create the conditions
        EntityCondition idCond = EntityCondition.makeCondition(conditionList1, EntityOperator.OR);
        conditionList2.add(idCond);

        EntityCondition cond = EntityCondition.makeCondition(conditionList2, EntityOperator.AND);

        // run the query
        try {
          orderHeaderList =
              delegator.findList(
                  "OrderHeader", cond, null, UtilMisc.toList("+orderDate"), null, false);
        } catch (GenericEntityException e) {
          Debug.logError(e, module);
          return ServiceUtil.returnError(e.getMessage());
        }
        Debug.logInfo("Recieved orderIdList  - " + orderIdList, module);
        Debug.logInfo("Found orderHeaderList - " + orderHeaderList, module);
      }
    }

    Map<String, Object> result = ServiceUtil.returnSuccess();
    result.put("orderHeaderList", orderHeaderList);
    return result;
  }
  public static void getCategoriesWithNoParent(ServletRequest request, String attributeName) {
    Delegator delegator = (Delegator) request.getAttribute("delegator");
    Collection<GenericValue> results = FastList.newInstance();

    try {
      Collection<GenericValue> allCategories =
          delegator.findList("ProductCategory", null, null, null, null, false);

      for (GenericValue curCat : allCategories) {
        Collection<GenericValue> parentCats =
            curCat.getRelatedCache("CurrentProductCategoryRollup");

        if (parentCats.isEmpty()) results.add(curCat);
      }
    } catch (GenericEntityException e) {
      Debug.logWarning(e, module);
    }
    request.setAttribute(attributeName, results);
  }
Exemple #6
0
 @Override
 public boolean exec(MethodContext methodContext) throws MiniLangException {
   String entityName = entityNameFse.expandString(methodContext.getEnvMap());
   boolean useCache = "true".equals(useCacheFse.expandString(methodContext.getEnvMap()));
   boolean useIterator = "true".equals(useIteratorFse.expandString(methodContext.getEnvMap()));
   List<String> orderByNames = orderByListFma.get(methodContext.getEnvMap());
   Collection<String> fieldsToSelectList = fieldsToSelectListFma.get(methodContext.getEnvMap());
   Delegator delegator = getDelegator(methodContext);
   try {
     EntityCondition whereCond = null;
     Map<String, ? extends Object> fieldMap = mapFma.get(methodContext.getEnvMap());
     if (fieldMap != null) {
       whereCond = EntityCondition.makeCondition(fieldMap);
     }
     if (useIterator) {
       listFma.put(
           methodContext.getEnvMap(),
           delegator.find(
               entityName,
               whereCond,
               null,
               UtilMisc.toSet(fieldsToSelectList),
               orderByNames,
               null));
     } else {
       listFma.put(
           methodContext.getEnvMap(),
           delegator.findList(
               entityName,
               whereCond,
               UtilMisc.toSet(fieldsToSelectList),
               orderByNames,
               null,
               useCache));
     }
   } catch (GenericEntityException e) {
     String errMsg = "Exception thrown while performing entity find: " + e.getMessage();
     Debug.logWarning(e, errMsg, module);
     simpleMethod.addErrorMessage(methodContext, errMsg);
     return false;
   }
   return true;
 }
  private static void getTaxAuthorities(
      Delegator delegator, GenericValue shippingAddress, Set<GenericValue> taxAuthoritySet)
      throws GenericEntityException {
    Map<String, String> geoIdByTypeMap = FastMap.newInstance();
    if (shippingAddress != null) {
      if (UtilValidate.isNotEmpty(shippingAddress.getString("countryGeoId"))) {
        geoIdByTypeMap.put("COUNTRY", shippingAddress.getString("countryGeoId"));
      }
      if (UtilValidate.isNotEmpty(shippingAddress.getString("stateProvinceGeoId"))) {
        geoIdByTypeMap.put("STATE", shippingAddress.getString("stateProvinceGeoId"));
      }
      if (UtilValidate.isNotEmpty(shippingAddress.getString("countyGeoId"))) {
        geoIdByTypeMap.put("COUNTY", shippingAddress.getString("countyGeoId"));
      }
      String postalCodeGeoId =
          ContactMechWorker.getPostalAddressPostalCodeGeoId(shippingAddress, delegator);
      if (UtilValidate.isNotEmpty(postalCodeGeoId)) {
        geoIdByTypeMap.put("POSTAL_CODE", postalCodeGeoId);
      }
    } else {
      Debug.logWarning("shippingAddress was null, adding nothing to taxAuthoritySet", module);
    }

    // Debug.logInfo("Tax calc geoIdByTypeMap before expand:" + geoIdByTypeMap + "; this is for
    // shippingAddress=" + shippingAddress, module);
    // get the most granular, or all available, geoIds and then find parents by GeoAssoc with
    // geoAssocTypeId="REGIONS" and geoIdTo=<granular geoId> and find the GeoAssoc.geoId
    geoIdByTypeMap = GeoWorker.expandGeoRegionDeep(geoIdByTypeMap, delegator);
    // Debug.logInfo("Tax calc geoIdByTypeMap after expand:" + geoIdByTypeMap, module);

    List<GenericValue> taxAuthorityRawList =
        delegator.findList(
            "TaxAuthority",
            EntityCondition.makeCondition(
                "taxAuthGeoId", EntityOperator.IN, geoIdByTypeMap.values()),
            null,
            null,
            null,
            true);
    taxAuthoritySet.addAll(taxAuthorityRawList);
    // Debug.logInfo("Tax calc taxAuthoritySet after expand:" + taxAuthoritySet, module);
  }
Exemple #8
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;
  }
  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);
      }
    }
  }
  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;
  }