private static List getTaxAdjustments()

in applications/accounting/src/main/java/org/apache/ofbiz/accounting/tax/TaxAuthorityServices.java [344:646]


    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, BigDecimal weight) {
        Timestamp nowTimestamp = UtilDateTime.nowTimestamp();
        List<GenericValue> adjustments = new LinkedList<>();
        if (weight == null) {
            weight = BigDecimal.ONE;
        }

        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 = new LinkedList<>();
        // 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;
            productCategoryCond = setProductCategoryCond(delegator, product);

            if (product == null && shippingAmount != null) {
                EntityCondition taxShippingCond = EntityCondition.makeCondition(
                        EntityCondition.makeCondition("taxShipping", EntityOperator.EQUALS, null),
                        EntityOperator.OR,
                        EntityCondition.makeCondition("taxShipping", EntityOperator.EQUALS, "Y"));

                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"));

                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);

            // finally ready... do the rate query
            List<GenericValue> lookupList = EntityQuery.use(delegator).from("TaxAuthorityRateProduct")
                    .where(mainCondition).orderBy("minItemPrice", "minPurchase", "fromDate").filterByDate().queryList();

            if (lookupList.isEmpty()) {
                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 : lookupList) {
                BigDecimal taxRate = taxAuthorityRateProduct.get("taxPercentage") != null ? taxAuthorityRateProduct
                        .getBigDecimal("taxPercentage") : ZERO_BASE;
                taxRate = taxRate.multiply(weight);
                BigDecimal taxable = ZERO_BASE;

                if (product != null && (product.get("taxable") == null || (product.get("taxable") != null && product
                        .getBoolean("taxable")))) {
                    taxable = taxable.add(itemAmount);
                }
                if (shippingAmount != null && (taxAuthorityRateProduct.get("taxShipping") == null
                        || (taxAuthorityRateProduct.get("taxShipping") != null && taxAuthorityRateProduct.getBoolean(
                        "taxShipping")))) {
                    taxable = taxable.add(shippingAmount);
                }
                if (orderPromotionsAmount != null && (taxAuthorityRateProduct.get("taxPromotions") == null
                        || (taxAuthorityRateProduct.get("taxPromotions") != null && taxAuthorityRateProduct.getBoolean(
                        "taxPromotions")))) {
                    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, TAX_SCALE,
                        TAX_ROUNDING);

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

                // get glAccountId from TaxAuthorityGlAccount entity using the payToPartyId as
                // the organizationPartyId
                GenericValue taxAuthorityGlAccount = EntityQuery.use(delegator).from("TaxAuthorityGlAccount")
                        .where("taxAuthPartyId", taxAuthPartyId, "taxAuthGeoId", taxAuthGeoId, "organizationPartyId", payToPartyId).queryOne();
                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?
                    Debug.logVerbose("what to do if no TaxAuthorityGlAccount found?", MODULE);
                }

                GenericValue productPrice = null;
                if (product != null && taxAuthPartyId != null && taxAuthGeoId != null) {
                    // find a ProductPrice for the productId and taxAuth* values, and see if it has
                    // a priceWithTax value
                    productPrice = getProductPrice(delegator, product, productStore, taxAuthGeoId, taxAuthPartyId);
                    if (productPrice == null) {
                        GenericValue virtualProduct = ProductWorker.getParentProduct(product.getString("productId"), delegator);
                        if (virtualProduct != null) {
                            productPrice = getProductPrice(delegator, virtualProduct, productStore, taxAuthGeoId, taxAuthPartyId);
                        }
                    }
                }
                GenericValue taxAdjValue = delegator.makeValue("OrderAdjustment");

                BigDecimal discountedSalesTax = BigDecimal.ZERO;
                taxAdjValue.set("orderAdjustmentTypeId", "SALES_TAX");
                if (productPrice != null && "Y".equals(productPrice.getString("taxInPrice"))
                        && itemQuantity != BigDecimal.ZERO) {
                    // For example product price is 43 with 20% VAT(means product actual price is
                    // 35.83).
                    // itemPrice = 43;
                    // itemQuantity = 3;
                    // taxAmountIncludedInFullPrice = (43-(43/(1+(20/100))))*3 = 21.51
                    taxAdjValue.set("orderAdjustmentTypeId", "VAT_TAX");
                    BigDecimal taxAmountIncludedInFullPrice = itemPrice.subtract(itemPrice.divide(BigDecimal.ONE.add(
                            taxRate.divide(PERCENT_SCALE, 4, RoundingMode.HALF_UP)), 2, RoundingMode.HALF_UP)).multiply(
                                    itemQuantity);
                    // If 1 quantity has 50% discount then itemAmount = 107.5 otherwise 129 (In case
                    // of no discount)
                    // Net price for each item
                    // netItemPrice = itemAmount / quantity = 107.5 / 3 = 35.833333333
                    BigDecimal netItemPrice = itemAmount.divide(itemQuantity, RoundingMode.HALF_UP);
                    // Calculate tax on the discounted price, be sure to round to 2 decimal places
                    // before multiplying by quantity
                    // netTax = (netItemPrice - netItemPrice / (1 + (taxRate/100))) * quantity
                    // netTax = (35.833333333-(35.833333333/(1+(20/100))))*3 = 17.92
                    BigDecimal netTax = netItemPrice.subtract(netItemPrice.divide(BigDecimal.ONE.add(taxRate.divide(
                            PERCENT_SCALE, 4, RoundingMode.HALF_UP)), 2, RoundingMode.HALF_UP)).multiply(itemQuantity);
                    // Subtract net tax from base tax (taxAmountIncludedFullPrice) to get the
                    // negative promotion tax adjustment amount
                    // discountedSalesTax = 17.92 - 21.51 = −3.59 (If no discounted item quantity
                    // then discountedSalesTax will be ZERO)
                    discountedSalesTax = netTax.subtract(taxAmountIncludedInFullPrice);
                    taxAdjValue.set("amountAlreadyIncluded", taxAmountIncludedInFullPrice);
                    taxAdjValue.set("amount", BigDecimal.ZERO);
                } else {
                    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 = new HashSet<>();
                    billToPartyIdSet.add(billToPartyId);
                    List<GenericValue> partyRelationshipList = EntityQuery.use(delegator).from("PartyRelationship")
                            .where("partyIdTo", billToPartyId, "partyRelationshipTypeId", "GROUP_ROLLUP")
                            .cache().filterByDate().queryList();

                    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);
                }
                if (discountedSalesTax.compareTo(BigDecimal.ZERO) < 0) {
                    GenericValue taxAdjValueNegative = delegator.makeValue("OrderAdjustment");
                    taxAdjValueNegative.setFields(taxAdjValue);
                    taxAdjValueNegative.set("amountAlreadyIncluded", discountedSalesTax);
                    adjustments.add(taxAdjValueNegative);
                }
                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,
                            TAX_SCALE, TAX_ROUNDING);

                    // 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 calculated 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 calculated amount from the entered amount (ie: correction =
                        // entered - calculated)
                        BigDecimal correctionAmount = enteredTotalPriceWithTax.subtract(calcedTotalPriceWithTax);

                        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 new LinkedList<>();
        }

        return adjustments;
    }