You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by jo...@apache.org on 2010/12/06 09:05:44 UTC

svn commit: r1042542 - in /ofbiz/trunk/applications: accounting/src/org/ofbiz/accounting/tax/ order/data/ order/entitydef/ order/src/org/ofbiz/order/order/ product/entitydef/ product/script/org/ofbiz/product/price/ product/servicedef/

Author: jonesde
Date: Mon Dec  6 08:05:44 2010
New Revision: 1042542

URL: http://svn.apache.org/viewvc?rev=1042542&view=rev
Log:
Implemented alternative way of saving ProductPrices with tax included in the price, and then calculating VAT tax as an exclusive instead of inclusive amount that goes on a new field on OrderAdjustment; also added a method to OrderReadHelper to get tax for display; there are various changes to service descriptions and entity fields to describe these changes

Modified:
    ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java
    ofbiz/trunk/applications/order/data/OrderTypeData.xml
    ofbiz/trunk/applications/order/entitydef/entitymodel.xml
    ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
    ofbiz/trunk/applications/product/entitydef/entitymodel.xml
    ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml
    ofbiz/trunk/applications/product/servicedef/services.xml

Modified: ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java?rev=1042542&r1=1042541&r2=1042542&view=diff
==============================================================================
--- ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java (original)
+++ ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java Mon Dec  6 08:05:44 2010
@@ -391,11 +391,38 @@ public class TaxAuthorityServices {
                     // 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, 
+                            "productPriceTypeId", "DEFAULT_PRICE", "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");
-                taxAdjValue.set("taxAuthorityRateSeqId", taxAuthorityRateProduct.getString("taxAuthorityRateSeqId"));
-                taxAdjValue.set("amount", taxAmount);
+
+                if ("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("orderAdjustmentTypeId", "SALES_TAX");
+                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"));
@@ -421,62 +448,50 @@ public class TaxAuthorityServices {
                 }
 
                 adjustments.add(taxAdjValue);
-                
-                // 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 get this to work with price rules changing the default price (right now only works where itemPrice==defaultPrice
-                // 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
-                
-                //Debug.logInfo("=================== itemQuantity=" + itemQuantity, module);
-                //Debug.logInfo("=================== taxAuthPartyId=" + taxAuthPartyId, module);
-                //Debug.logInfo("=================== taxAuthGeoId=" + taxAuthGeoId, module);
-                if (product != null && itemQuantity != 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, 
-                            "productPriceTypeId", "DEFAULT_PRICE", "productPricePurposeId", "PURCHASE");
-                    List<GenericValue> productPriceList = delegator.findByAnd("ProductPrice", priceFindMap, UtilMisc.toList("-fromDate"));
-                    productPriceList = EntityUtil.filterByDate(productPriceList, true);
-                    GenericValue productPrice = (productPriceList != null && productPriceList.size() > 0) ? productPriceList.get(0): null;
-                    //Debug.logInfo("=================== productId=" + product.getString("productId"), module);
-                    //Debug.logInfo("=================== productPrice=" + productPrice, module);
+
+                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
                     
-                    if (productPrice != null && productPrice.getBigDecimal("priceWithTax") != null) {
-                        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);
-                        BigDecimal enteredTotalPriceWithTax = priceWithTax.multiply(itemQuantity);
-                        BigDecimal calcedTotalPriceWithTax = (baseSubtotal).add(baseTaxAmount);
-                        //Debug.logInfo("=================== priceWithTax=" + priceWithTax, module);
-                        //Debug.logInfo("=================== enteredTotalPriceWithTax=" + enteredTotalPriceWithTax, module);
-                        //Debug.logInfo("=================== calcedTotalPriceWithTax=" + calcedTotalPriceWithTax, module);
+                    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);
                         
-                        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);
-                        }
+                        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);
                     }
                 }
             }

Modified: ofbiz/trunk/applications/order/data/OrderTypeData.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/data/OrderTypeData.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
==============================================================================
--- ofbiz/trunk/applications/order/data/OrderTypeData.xml (original)
+++ ofbiz/trunk/applications/order/data/OrderTypeData.xml Mon Dec  6 08:05:44 2010
@@ -42,6 +42,7 @@ under the License.
     <OrderAdjustmentType description="Fee" hasTable="N" orderAdjustmentTypeId="FEE" parentTypeId=""/>
     <OrderAdjustmentType description="Miscellaneous Charges" hasTable="N" orderAdjustmentTypeId="MISCELLANEOUS_CHARGE" parentTypeId=""/>
     <OrderAdjustmentType description="Sales Tax" hasTable="N" orderAdjustmentTypeId="SALES_TAX" parentTypeId=""/>
+    <OrderAdjustmentType description="VAT Tax (not added to totals)" hasTable="N" orderAdjustmentTypeId="VAT_TAX" parentTypeId=""/>
     <OrderAdjustmentType description="VAT Price Correction" hasTable="N" orderAdjustmentTypeId="VAT_PRICE_CORRECT" parentTypeId=""/>
     <OrderAdjustmentType description="Shipping and Handling" hasTable="N" orderAdjustmentTypeId="SHIPPING_CHARGES" parentTypeId=""/>
     <OrderAdjustmentType description="Surcharge" hasTable="N" orderAdjustmentTypeId="SURCHARGE_ADJUSTMENT" parentTypeId=""/>

Modified: ofbiz/trunk/applications/order/entitydef/entitymodel.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/entitydef/entitymodel.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
==============================================================================
--- ofbiz/trunk/applications/order/entitydef/entitymodel.xml (original)
+++ ofbiz/trunk/applications/order/entitydef/entitymodel.xml Mon Dec  6 08:05:44 2010
@@ -59,6 +59,7 @@ under the License.
       <field name="description" type="description"></field>
       <field name="amount" type="currency-precise"></field>
       <field name="recurringAmount" type="currency-precise"></field>
+      <field name="amountAlreadyIncluded" type="currency-precise"><description>The amount here is already represented in the price, such as VAT taxes.</description></field>
       <field name="productPromoId" type="id"></field>
       <field name="productPromoRuleId" type="id"></field>
       <field name="productPromoActionSeqId" type="id"></field>

Modified: ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java?rev=1042542&r1=1042541&r2=1042542&view=diff
==============================================================================
--- ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java (original)
+++ ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java Mon Dec  6 08:05:44 2010
@@ -2920,4 +2920,62 @@ public class OrderReadHelper {
        result.put("taxGrandTotal", taxGrandTotal);
        return result;
    }
+
+   public static Map<String, Object> getOrderTaxByTaxAuthGeoAndPartyForDisplay(List<GenericValue> orderAdjustmentsOriginal) {
+       BigDecimal taxGrandTotal = BigDecimal.ZERO;
+       List<Map<String, Object>> taxByTaxAuthGeoAndPartyList = FastList.newInstance();
+       List<GenericValue> orderAdjustmentsToUse = FastList.newInstance();
+       if (UtilValidate.isNotEmpty(orderAdjustmentsOriginal)) {
+           // get orderAdjustment where orderAdjustmentTypeId is SALES_TAX.
+           orderAdjustmentsToUse.addAll(EntityUtil.filterByAnd(orderAdjustmentsOriginal, UtilMisc.toMap("orderAdjustmentTypeId", "SALES_TAX")));
+           orderAdjustmentsToUse.addAll(EntityUtil.filterByAnd(orderAdjustmentsOriginal, UtilMisc.toMap("orderAdjustmentTypeId", "VAT_TAX")));
+           orderAdjustmentsToUse = EntityUtil.orderBy(orderAdjustmentsToUse, UtilMisc.toList("taxAuthGeoId","taxAuthPartyId"));
+
+           // get the list of all distinct taxAuthGeoId and taxAuthPartyId. It is for getting the number of taxAuthGeo and taxAuthPartyId in adjustments.
+           List<String> distinctTaxAuthGeoIdList = EntityUtil.getFieldListFromEntityList(orderAdjustmentsToUse, "taxAuthGeoId", true);
+           List<String> distinctTaxAuthPartyIdList = EntityUtil.getFieldListFromEntityList(orderAdjustmentsToUse, "taxAuthPartyId", true);
+
+           // Keep a list of amount that have been added to make sure none are missed (if taxAuth* information is missing)
+           List<GenericValue> processedAdjustments = FastList.newInstance();
+           // For each taxAuthGeoId get and add amount from orderAdjustment
+           for (String taxAuthGeoId : distinctTaxAuthGeoIdList) {
+               for (String taxAuthPartyId : distinctTaxAuthPartyIdList) {
+                   //get all records for orderAdjustments filtered by taxAuthGeoId and taxAurhPartyId
+                   List<GenericValue> orderAdjByTaxAuthGeoAndPartyIds = EntityUtil.filterByAnd(orderAdjustmentsToUse, UtilMisc.toMap("taxAuthGeoId", taxAuthGeoId, "taxAuthPartyId", taxAuthPartyId));
+                   if (UtilValidate.isNotEmpty(orderAdjByTaxAuthGeoAndPartyIds)) {
+                       BigDecimal totalAmount = BigDecimal.ZERO;
+                       //Now for each orderAdjustment record get and add amount.
+                       for (GenericValue orderAdjustment : orderAdjByTaxAuthGeoAndPartyIds) {
+                           BigDecimal amount = orderAdjustment.getBigDecimal("amount");
+                           if (amount != null) {
+                               totalAmount = totalAmount.add(amount);
+                           }
+                           if ("VAT_TAX".equals(orderAdjustment.getString("orderAdjustmentTypeId")) && 
+                                   orderAdjustment.get("amountAlreadyIncluded") != null) {
+                               // this is the only case where the VAT_TAX amountAlreadyIncluded should be added in, and should just be for display and not to calculate the order grandTotal
+                               totalAmount = totalAmount.add(orderAdjustment.getBigDecimal("amountAlreadyIncluded"));
+                           }
+                           totalAmount = totalAmount.setScale(taxCalcScale, taxRounding);
+                           processedAdjustments.add(orderAdjustment);
+                       }
+                       totalAmount = totalAmount.setScale(taxFinalScale, taxRounding);
+                       taxByTaxAuthGeoAndPartyList.add(UtilMisc.<String, Object>toMap("taxAuthPartyId", taxAuthPartyId, "taxAuthGeoId", taxAuthGeoId, "totalAmount", totalAmount));
+                       taxGrandTotal = taxGrandTotal.add(totalAmount);
+                   }
+               }
+           }
+           // Process any adjustments that got missed
+           List<GenericValue> missedAdjustments = FastList.newInstance();
+           missedAdjustments.addAll(orderAdjustmentsToUse);
+           missedAdjustments.removeAll(processedAdjustments);
+           for (GenericValue orderAdjustment : missedAdjustments) {
+               taxGrandTotal = taxGrandTotal.add(orderAdjustment.getBigDecimal("amount").setScale(taxCalcScale, taxRounding));
+           }
+           taxGrandTotal = taxGrandTotal.setScale(taxFinalScale, taxRounding);
+       }
+       Map<String, Object> result = FastMap.newInstance();
+       result.put("taxByTaxAuthGeoAndPartyList", taxByTaxAuthGeoAndPartyList);
+       result.put("taxGrandTotal", taxGrandTotal);
+       return result;
+   }
 }

Modified: ofbiz/trunk/applications/product/entitydef/entitymodel.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/entitydef/entitymodel.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
==============================================================================
--- ofbiz/trunk/applications/product/entitydef/entitymodel.xml (original)
+++ ofbiz/trunk/applications/product/entitydef/entitymodel.xml Mon Dec  6 08:05:44 2010
@@ -2375,11 +2375,13 @@ under the License.
       <field name="price" type="currency-precise"></field>
       <field name="termUomId" type="id"><description>Mainly used for recurring and usage prices to specify a time/freq measure, or a usage unit measure (bits, minutes, etc)</description></field>
       <field name="customPriceCalcService" type="id"><description>Points to a CustomMethod used to specify a service for the calculation of the unit price of the product (NOTE: a better name for this field might be priceCalcCustomMethodId)</description></field>
-      <field name="priceWithTax" type="currency-precise"/>
+      <field name="priceWithoutTax" type="currency-precise"><description>Always without tax if populated, regardless of if price does or does not include tax.</description></field>
+      <field name="priceWithTax" type="currency-precise"><description>Always with tax if populated, regardless of if price does or does not include tax.</description></field>
       <field name="taxAmount" type="currency-precise"/>
       <field name="taxPercentage" type="fixed-point"/>
       <field name="taxAuthPartyId" type="id-ne"/>
       <field name="taxAuthGeoId" type="id-ne"/>
+      <field name="taxInPrice" type="indicator"><description>If Y the price field has tax included for the given taxAuthPartyId/taxAuthGeoId at the taxPercentage.</description></field>
       <field name="createdDate" type="date-time"></field>
       <field name="createdByUserLogin" type="id-vlong"></field>
       <field name="lastModifiedDate" type="date-time"></field>

Modified: ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
==============================================================================
--- ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml (original)
+++ ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml Mon Dec  6 08:05:44 2010
@@ -106,6 +106,7 @@ under the License.
             </condition>
             <then>
                 <set field="parameters.priceWithTax" from-field="parameters.price"/>
+                
                 <!-- if taxPercentage not passed in look it up based on taxAuthGeoId and taxAuthPartyId -->
                 <if-empty field="parameters.taxPercentage">
                     <!-- we only have basic data to constrain by here, so assume that if it is a VAT tax setup it should be pretty simple -->
@@ -127,6 +128,7 @@ under the License.
                     <check-errors/>
                 </if-empty>
                 
+                <!-- in short the formula is: taxAmount = priceWithTax - (priceWithTax/(1+taxPercentage/100)) -->
                 <calculate field="parameters.taxAmount" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
                     <calcop operator="subtract">
                         <calcop operator="get" field="parameters.priceWithTax"/>
@@ -142,12 +144,23 @@ under the License.
                         </calcop>
                     </calcop>
                 </calculate>
-                <calculate field="parameters.price" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
+                
+                <calculate field="parameters.priceWithoutTax" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
                     <calcop operator="subtract">
                         <calcop operator="get" field="parameters.priceWithTax"/>
                         <calcop operator="get" field="parameters.taxAmount"></calcop>
                     </calcop>
                 </calculate>
+                
+                <if-compare field="parameters.taxInPrice" operator="equals" value="Y">
+                    <!-- the price passed in has tax included, and we want to store it with tax included -->
+                    <set field="parameters.price" from-field="parameters.priceWithTax"/>
+                    
+                    <else>
+                        <!-- the price passed in has tax included, but we want to store it without tax included -->
+                        <set field="parameters.price" from-field="parameters.priceWithoutTax"/>
+                    </else>
+                </if-compare>
             </then>
         </if>
     </simple-method>

Modified: ofbiz/trunk/applications/product/servicedef/services.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/servicedef/services.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
==============================================================================
--- ofbiz/trunk/applications/product/servicedef/services.xml (original)
+++ ofbiz/trunk/applications/product/servicedef/services.xml Mon Dec  6 08:05:44 2010
@@ -242,12 +242,15 @@ under the License.
         <description>
             Create an ProductPrice. 
             Price is always stored without tax. 
-            If a taxAuthGeoId and taxAuthPartyId are (or taxAuthCombinedId is) passed in then the price will be considered a price 
-                with tax included and the tax will be removed before storing to the database 
-                (the priceWithTax, taxAmount, and taxPercentage fields will also be populated).
+            If taxAuthGeoId and taxAuthPartyId are (or taxAuthCombinedId is) passed in then the price will be considered a price 
+            with tax included (the priceWithoutTax, priceWithTax, taxAmount, and taxPercentage fields will also be populated).
+            If the taxInPrice field is 'Y' then the price field will be left with the price included (price will be equal to priceWithTax), 
+            otherwise tax will be removed from the passed in price and the price field will be equal to the priceWithoutTax field.
+            If taxAuthGeoId or taxAuthPartyId empty, and taxAuthCombinedId is empty, then the taxInPrice field will be ignored.
         </description>
         <auto-attributes include="pk" mode="IN" optional="false"/>
         <auto-attributes include="nonpk" mode="IN" optional="true">
+            <exclude field-name="priceWithoutTax"/>
             <exclude field-name="priceWithTax"/>
             <exclude field-name="taxAmount"/>
             <exclude field-name="createdDate"/>
@@ -264,6 +267,7 @@ under the License.
         <description>Update an ProductPrice</description>
         <auto-attributes include="pk" mode="IN" optional="false"/>
         <auto-attributes include="nonpk" mode="IN" optional="true">
+            <exclude field-name="priceWithoutTax"/>
             <exclude field-name="priceWithTax"/>
             <exclude field-name="taxAmount"/>
             <exclude field-name="createdDate"/>



Re: svn commit: r1042542 - in /ofbiz/trunk/applications: accounting/src/org/ofbiz/accounting/tax/ order/data/ order/entitydef/ order/src/org/ofbiz/order/order/ product/entitydef/ product/script/org/ofbiz/product/price/ product/servicedef/

Posted by David E Jones <de...@me.com>.
That's pretty much correct. A product can certainly have some prices with tax included and some without. 

Keep in mind though that there are possible configurations that will result in invalid (or undesired) numbers coming out the other end, such as a price in a certain currency without tax included that has a from date later than another price with the same currency and also a valid from/thru date range that has tax included. That could be eliminated by passing down the ProductPrice record that the calculated price was based on (probably the List price), but doing so would require a lot of code changes.

-David


On Dec 15, 2010, at 9:28 PM, Paul Foxworthy wrote:

> 
> Hi,
> 
> Can I check I understand what's going on in lines 394 and onward in the
> getTaxAdjustments method in TaxAuthorityServices.java?
> 
> You can choose a price based on any criteria, and that price is passed in
> the itemPrice parameter to the getTaxAdjustments method. This method looks
> at the default price for the product to determine if the itemPrice is
> tax-inclusive or not. If the default price is tax-inclusive, the actual
> price being charged for the product is assumed to be tax-inclusive as well.
> An adjustment is calculated so that the tax rate being charged for the
> actual price is the same as the tax rate on the default price.
> 
> Have I got that right? If so, should it be possible for a given product to
> have some prices that do include tax and some that don't?
> 
> Thanks
> 
> Paul Foxworthy
> 
> 
> -- 
> View this message in context: http://ofbiz.135035.n4.nabble.com/Re-svn-commit-r1042542-in-ofbiz-trunk-applications-accounting-src-org-ofbiz-accounting-tax-order-dat-tp3074820p3090321.html
> Sent from the OFBiz - Dev mailing list archive at Nabble.com.


Re: svn commit: r1042542 - in /ofbiz/trunk/applications: accounting/src/org/ofbiz/accounting/tax/ order/data/ order/entitydef/ order/src/org/ofbiz/order/order/ product/entitydef/ product/script/org/ofbiz/product/price/ product/servicedef/

Posted by Paul Foxworthy <pa...@cohsoft.com.au>.
Hi,

Can I check I understand what's going on in lines 394 and onward in the
getTaxAdjustments method in TaxAuthorityServices.java?

You can choose a price based on any criteria, and that price is passed in
the itemPrice parameter to the getTaxAdjustments method. This method looks
at the default price for the product to determine if the itemPrice is
tax-inclusive or not. If the default price is tax-inclusive, the actual
price being charged for the product is assumed to be tax-inclusive as well.
An adjustment is calculated so that the tax rate being charged for the
actual price is the same as the tax rate on the default price.

Have I got that right? If so, should it be possible for a given product to
have some prices that do include tax and some that don't?

Thanks

Paul Foxworthy


-- 
View this message in context: http://ofbiz.135035.n4.nabble.com/Re-svn-commit-r1042542-in-ofbiz-trunk-applications-accounting-src-org-ofbiz-accounting-tax-order-dat-tp3074820p3090321.html
Sent from the OFBiz - Dev mailing list archive at Nabble.com.

Re: svn commit: r1042542 - in /ofbiz/trunk/applications: accounting/src/org/ofbiz/accounting/tax/ order/data/ order/entitydef/ order/src/org/ofbiz/order/order/ product/entitydef/ product/script/org/ofbiz/product/price/ product/servicedef/

Posted by Jacques Le Roux <ja...@les7arts.com>.
Great! Thanks David

Jacques

From: "David E Jones" <de...@me.com>
> On Dec 8, 2010, at 2:22 PM, Jacques Le Roux wrote:
>
>> From: "David E Jones" <de...@me.com>
>>> On Dec 7, 2010, at 11:58 PM, Jacques Le Roux wrote:
>>>
>>>> From: "David E Jones" <de...@me.com>
>>>>> On Dec 7, 2010, at 3:39 AM, Jacques Le Roux wrote:
>>>>>
>>>>>> If there are no plans to prevent the use of the new fields for the other types Price Types than Default, I'd like latter to 
>>>>>> add
>>>>>> a small js script to handle it. Of course being able to use it with Price Rules and Promo would be great.
>>>>>
>>>>> Why would we want to prevent using these fields for other price types?
>>>>
>>>> I thought only the Default Price is really using VAT included price. So to prevent users thinking they can use VAT included 
>>>> price
>>>> with other price types.
>>>
>>> Any price could, and should if applicable, have tax authority settings and have VAT tax included.
>>
>> Does this means that Price Rules and Promo work with Gross Prices?
>
> That's actually the main point of doing things this way, ie to properly and accurately support all of these various features that 
> OFBiz does OOTB.
>
> -David
>
> 



Re: svn commit: r1042542 - in /ofbiz/trunk/applications: accounting/src/org/ofbiz/accounting/tax/ order/data/ order/entitydef/ order/src/org/ofbiz/order/order/ product/entitydef/ product/script/org/ofbiz/product/price/ product/servicedef/

Posted by David E Jones <de...@me.com>.
On Dec 8, 2010, at 2:22 PM, Jacques Le Roux wrote:

> From: "David E Jones" <de...@me.com>
>> On Dec 7, 2010, at 11:58 PM, Jacques Le Roux wrote:
>> 
>>> From: "David E Jones" <de...@me.com>
>>>> On Dec 7, 2010, at 3:39 AM, Jacques Le Roux wrote:
>>>> 
>>>>> If there are no plans to prevent the use of the new fields for the other types Price Types than Default, I'd like latter to add
>>>>> a small js script to handle it. Of course being able to use it with Price Rules and Promo would be great.
>>>> 
>>>> Why would we want to prevent using these fields for other price types?
>>> 
>>> I thought only the Default Price is really using VAT included price. So to prevent users thinking they can use VAT included price
>>> with other price types.
>> 
>> Any price could, and should if applicable, have tax authority settings and have VAT tax included.
> 
> Does this means that Price Rules and Promo work with Gross Prices?

That's actually the main point of doing things this way, ie to properly and accurately support all of these various features that OFBiz does OOTB.

-David



Re: svn commit: r1042542 - in /ofbiz/trunk/applications: accounting/src/org/ofbiz/accounting/tax/ order/data/ order/entitydef/ order/src/org/ofbiz/order/order/ product/entitydef/ product/script/org/ofbiz/product/price/ product/servicedef/

Posted by Jacques Le Roux <ja...@les7arts.com>.
From: "David E Jones" <de...@me.com>
> On Dec 7, 2010, at 11:58 PM, Jacques Le Roux wrote:
>
>> From: "David E Jones" <de...@me.com>
>>> On Dec 7, 2010, at 3:39 AM, Jacques Le Roux wrote:
>>>
>>>> If there are no plans to prevent the use of the new fields for the other types Price Types than Default, I'd like latter to add
>>>> a small js script to handle it. Of course being able to use it with Price Rules and Promo would be great.
>>>
>>> Why would we want to prevent using these fields for other price types?
>>
>> I thought only the Default Price is really using VAT included price. So to prevent users thinking they can use VAT included price
>> with other price types.
>
> Any price could, and should if applicable, have tax authority settings and have VAT tax included.

Does this means that Price Rules and Promo work with Gross Prices?

>>>> Also what for is used taxAuthCombinedId? I don't see an use at the UI level.
>>>
>>> With a combined ID you can select the tax authority in a drop-down instead of forcing the user to handle two separate fields
>>> that are not correlated, making the UI significantly more cumbersome and difficult to use.
>>
>> Yes but then I think we need to expose a bit more how to enter a combined ID, I will add a tooltip somewhere in the screen
>
> A user wouldn't generally enter the combined ID manually, that would be a bad UI. The combined ID is just meant to be the ID in a
> drop-down (or perhaps a lookup).

OK, we need to amend the OOTB UI anyway

BTW, thanks for this improvement David, I believe it's really a good base for next step. I mean I really prefer that it's you, who 
already designed and wrote the Tax Auth stuff, who wrote this new part!

Jacques

> -David
>
>
>>>
>>>> From: "David E Jones" <de...@me.com>
>>>>> I commented before that my previous efforts only made calculations more accurate, and those efforts made it clear that the
>>>>> only way to be sure is to calculate things based on prices with tax included.
>>>>>
>>>>> So, now OFBiz has low-level support for prices with tax included and calculating VAT instead of sales tax with adjustments
>>>>> that represent amounts already included in the prices.
>>>>>
>>>>> Note that this does not include any UI changes, and so unless changes are done to allow entry of prices with tax included and
>>>>> to show the VAT_TAX adjustments (specifically the new amountAlreadyIncluded field, which was added so that it wouldn't
>>>>> interfere with the current amount field).
>>>>>
>>>>> -David
>>>>>
>>>>>
>>>>> On Dec 6, 2010, at 8:39 AM, Jacques Le Roux wrote:
>>>>>
>>>>>> Hi David,
>>>>>>
>>>>>> Interesting, what decided you to finally shift your ground?
>>>>>>
>>>>>> Is it still true that only Default Price can be used, and if yes is there any blocking reasons?
>>>>>>
>>>>>> Thanks
>>>>>>
>>>>>> Jacques
>>>>>>
>>>>>> From: <jo...@apache.org>
>>>>>>> Author: jonesde
>>>>>>> Date: Mon Dec  6 08:05:44 2010
>>>>>>> New Revision: 1042542
>>>>>>>
>>>>>>> URL: http://svn.apache.org/viewvc?rev=1042542&view=rev
>>>>>>> Log:
>>>>>>> Implemented alternative way of saving ProductPrices with tax included in the price, and then calculating VAT tax as an
>>>>>>> exclusive instead of inclusive amount that goes on a new field on OrderAdjustment; also added a method to OrderReadHelper to
>>>>>>> get tax for display; there are various changes to service descriptions and entity fields to describe these changes
>>>>>>>
>>>>>>> Modified:
>>>>>>> ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java
>>>>>>> ofbiz/trunk/applications/order/data/OrderTypeData.xml
>>>>>>> ofbiz/trunk/applications/order/entitydef/entitymodel.xml
>>>>>>> ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
>>>>>>> ofbiz/trunk/applications/product/entitydef/entitymodel.xml
>>>>>>> ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml
>>>>>>> ofbiz/trunk/applications/product/servicedef/services.xml
>>>>>>>
>>>>>>> Modified: ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java
>>>>>>> URL:
>>>>>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>>>> ==============================================================================
>>>>>>> --- ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java (original)
>>>>>>> +++ ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java Mon Dec  6 08:05:44 2010
>>>>>>> @@ -391,11 +391,38 @@ public class TaxAuthorityServices {
>>>>>>>                  // 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,
>>>>>>> +                            "productPriceTypeId", "DEFAULT_PRICE", "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");
>>>>>>> -                taxAdjValue.set("taxAuthorityRateSeqId", taxAuthorityRateProduct.getString("taxAuthorityRateSeqId"));
>>>>>>> -                taxAdjValue.set("amount", taxAmount);
>>>>>>> +
>>>>>>> +                if ("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("orderAdjustmentTypeId", "SALES_TAX");
>>>>>>> +                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"));
>>>>>>> @@ -421,62 +448,50 @@ public class TaxAuthorityServices {
>>>>>>>              }
>>>>>>>
>>>>>>>              adjustments.add(taxAdjValue);
>>>>>>> -
>>>>>>> -                // 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 get this to work with price rules changing the default price (right now only works where
>>>>>>> itemPrice==defaultPrice
>>>>>>> -                // 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
>>>>>>> -
>>>>>>> -                //Debug.logInfo("=================== itemQuantity=" + itemQuantity, module);
>>>>>>> -                //Debug.logInfo("=================== taxAuthPartyId=" + taxAuthPartyId, module);
>>>>>>> -                //Debug.logInfo("=================== taxAuthGeoId=" + taxAuthGeoId, module);
>>>>>>> -                if (product != null && itemQuantity != 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,
>>>>>>> -                            "productPriceTypeId", "DEFAULT_PRICE", "productPricePurposeId", "PURCHASE");
>>>>>>> -                    List<GenericValue> productPriceList = delegator.findByAnd("ProductPrice", priceFindMap,
>>>>>>> UtilMisc.toList("-fromDate"));
>>>>>>> -                    productPriceList = EntityUtil.filterByDate(productPriceList, true);
>>>>>>> -                    GenericValue productPrice = (productPriceList != null && productPriceList.size() > 0) ?
>>>>>>> productPriceList.get(0): null;
>>>>>>> -                    //Debug.logInfo("=================== productId=" + product.getString("productId"), module);
>>>>>>> -                    //Debug.logInfo("=================== productPrice=" + productPrice, module);
>>>>>>> +
>>>>>>> +                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
>>>>>>>
>>>>>>> -                    if (productPrice != null && productPrice.getBigDecimal("priceWithTax") != null) {
>>>>>>> -                        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);
>>>>>>> -                        BigDecimal enteredTotalPriceWithTax = priceWithTax.multiply(itemQuantity);
>>>>>>> -                        BigDecimal calcedTotalPriceWithTax = (baseSubtotal).add(baseTaxAmount);
>>>>>>> -                        //Debug.logInfo("=================== priceWithTax=" + priceWithTax, module);
>>>>>>> -                        //Debug.logInfo("=================== enteredTotalPriceWithTax=" + enteredTotalPriceWithTax,
>>>>>>> module);
>>>>>>> -                        //Debug.logInfo("=================== calcedTotalPriceWithTax=" + calcedTotalPriceWithTax, module);
>>>>>>> +                    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);
>>>>>>>
>>>>>>> -                        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);
>>>>>>> -                        }
>>>>>>> +                        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);
>>>>>>>                  }
>>>>>>>              }
>>>>>>>          }
>>>>>>>
>>>>>>> Modified: ofbiz/trunk/applications/order/data/OrderTypeData.xml
>>>>>>> URL:
>>>>>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/data/OrderTypeData.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>>>> ==============================================================================
>>>>>>> --- ofbiz/trunk/applications/order/data/OrderTypeData.xml (original)
>>>>>>> +++ ofbiz/trunk/applications/order/data/OrderTypeData.xml Mon Dec  6 08:05:44 2010
>>>>>>> @@ -42,6 +42,7 @@ under the License.
>>>>>>>  <OrderAdjustmentType description="Fee" hasTable="N" orderAdjustmentTypeId="FEE" parentTypeId=""/>
>>>>>>>  <OrderAdjustmentType description="Miscellaneous Charges" hasTable="N" orderAdjustmentTypeId="MISCELLANEOUS_CHARGE"
>>>>>>> parentTypeId=""/>
>>>>>>>  <OrderAdjustmentType description="Sales Tax" hasTable="N" orderAdjustmentTypeId="SALES_TAX" parentTypeId=""/>
>>>>>>> +    <OrderAdjustmentType description="VAT Tax (not added to totals)" hasTable="N" orderAdjustmentTypeId="VAT_TAX"
>>>>>>> parentTypeId=""/>
>>>>>>>  <OrderAdjustmentType description="VAT Price Correction" hasTable="N" orderAdjustmentTypeId="VAT_PRICE_CORRECT"
>>>>>>> parentTypeId=""/>
>>>>>>>  <OrderAdjustmentType description="Shipping and Handling" hasTable="N" orderAdjustmentTypeId="SHIPPING_CHARGES"
>>>>>>> parentTypeId=""/>
>>>>>>>  <OrderAdjustmentType description="Surcharge" hasTable="N" orderAdjustmentTypeId="SURCHARGE_ADJUSTMENT" parentTypeId=""/>
>>>>>>>
>>>>>>> Modified: ofbiz/trunk/applications/order/entitydef/entitymodel.xml
>>>>>>> URL:
>>>>>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/entitydef/entitymodel.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>>>> ==============================================================================
>>>>>>> --- ofbiz/trunk/applications/order/entitydef/entitymodel.xml (original)
>>>>>>> +++ ofbiz/trunk/applications/order/entitydef/entitymodel.xml Mon Dec  6 08:05:44 2010
>>>>>>> @@ -59,6 +59,7 @@ under the License.
>>>>>>>    <field name="description" type="description"></field>
>>>>>>>    <field name="amount" type="currency-precise"></field>
>>>>>>>    <field name="recurringAmount" type="currency-precise"></field>
>>>>>>> +      <field name="amountAlreadyIncluded" type="currency-precise"><description>The amount here is already represented in
>>>>>>> the price, such as VAT taxes.</description></field>
>>>>>>>    <field name="productPromoId" type="id"></field>
>>>>>>>    <field name="productPromoRuleId" type="id"></field>
>>>>>>>    <field name="productPromoActionSeqId" type="id"></field>
>>>>>>>
>>>>>>> Modified: ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
>>>>>>> URL:
>>>>>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>>>> ==============================================================================
>>>>>>> --- ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java (original)
>>>>>>> +++ ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java Mon Dec  6 08:05:44 2010
>>>>>>> @@ -2920,4 +2920,62 @@ public class OrderReadHelper {
>>>>>>>     result.put("taxGrandTotal", taxGrandTotal);
>>>>>>>     return result;
>>>>>>> }
>>>>>>> +
>>>>>>> +   public static Map<String, Object> getOrderTaxByTaxAuthGeoAndPartyForDisplay(List<GenericValue> orderAdjustmentsOriginal)
>>>>>>> {
>>>>>>> +       BigDecimal taxGrandTotal = BigDecimal.ZERO;
>>>>>>> +       List<Map<String, Object>> taxByTaxAuthGeoAndPartyList = FastList.newInstance();
>>>>>>> +       List<GenericValue> orderAdjustmentsToUse = FastList.newInstance();
>>>>>>> +       if (UtilValidate.isNotEmpty(orderAdjustmentsOriginal)) {
>>>>>>> +           // get orderAdjustment where orderAdjustmentTypeId is SALES_TAX.
>>>>>>> +           orderAdjustmentsToUse.addAll(EntityUtil.filterByAnd(orderAdjustmentsOriginal,
>>>>>>> UtilMisc.toMap("orderAdjustmentTypeId", "SALES_TAX")));
>>>>>>> +           orderAdjustmentsToUse.addAll(EntityUtil.filterByAnd(orderAdjustmentsOriginal,
>>>>>>> UtilMisc.toMap("orderAdjustmentTypeId", "VAT_TAX")));
>>>>>>> +           orderAdjustmentsToUse = EntityUtil.orderBy(orderAdjustmentsToUse,
>>>>>>> UtilMisc.toList("taxAuthGeoId","taxAuthPartyId"));
>>>>>>> +
>>>>>>> +           // get the list of all distinct taxAuthGeoId and taxAuthPartyId. It is for getting the number of taxAuthGeo and
>>>>>>> taxAuthPartyId in adjustments.
>>>>>>> +           List<String> distinctTaxAuthGeoIdList = EntityUtil.getFieldListFromEntityList(orderAdjustmentsToUse,
>>>>>>> "taxAuthGeoId", true);
>>>>>>> +           List<String> distinctTaxAuthPartyIdList = EntityUtil.getFieldListFromEntityList(orderAdjustmentsToUse,
>>>>>>> "taxAuthPartyId", true);
>>>>>>> +
>>>>>>> +           // Keep a list of amount that have been added to make sure none are missed (if taxAuth* information is missing)
>>>>>>> +           List<GenericValue> processedAdjustments = FastList.newInstance();
>>>>>>> +           // For each taxAuthGeoId get and add amount from orderAdjustment
>>>>>>> +           for (String taxAuthGeoId : distinctTaxAuthGeoIdList) {
>>>>>>> +               for (String taxAuthPartyId : distinctTaxAuthPartyIdList) {
>>>>>>> +                   //get all records for orderAdjustments filtered by taxAuthGeoId and taxAurhPartyId
>>>>>>> +                   List<GenericValue> orderAdjByTaxAuthGeoAndPartyIds = EntityUtil.filterByAnd(orderAdjustmentsToUse,
>>>>>>> UtilMisc.toMap("taxAuthGeoId", taxAuthGeoId, "taxAuthPartyId", taxAuthPartyId));
>>>>>>> +                   if (UtilValidate.isNotEmpty(orderAdjByTaxAuthGeoAndPartyIds)) {
>>>>>>> +                       BigDecimal totalAmount = BigDecimal.ZERO;
>>>>>>> +                       //Now for each orderAdjustment record get and add amount.
>>>>>>> +                       for (GenericValue orderAdjustment : orderAdjByTaxAuthGeoAndPartyIds) {
>>>>>>> +                           BigDecimal amount = orderAdjustment.getBigDecimal("amount");
>>>>>>> +                           if (amount != null) {
>>>>>>> +                               totalAmount = totalAmount.add(amount);
>>>>>>> +                           }
>>>>>>> +                           if ("VAT_TAX".equals(orderAdjustment.getString("orderAdjustmentTypeId")) &&
>>>>>>> +                                   orderAdjustment.get("amountAlreadyIncluded") != null) {
>>>>>>> +                               // this is the only case where the VAT_TAX amountAlreadyIncluded should be added in, and
>>>>>>> should just be for display and not to calculate the order grandTotal
>>>>>>> +                               totalAmount = totalAmount.add(orderAdjustment.getBigDecimal("amountAlreadyIncluded"));
>>>>>>> +                           }
>>>>>>> +                           totalAmount = totalAmount.setScale(taxCalcScale, taxRounding);
>>>>>>> +                           processedAdjustments.add(orderAdjustment);
>>>>>>> +                       }
>>>>>>> +                       totalAmount = totalAmount.setScale(taxFinalScale, taxRounding);
>>>>>>> +                       taxByTaxAuthGeoAndPartyList.add(UtilMisc.<String, Object>toMap("taxAuthPartyId", taxAuthPartyId,
>>>>>>> "taxAuthGeoId", taxAuthGeoId, "totalAmount", totalAmount));
>>>>>>> +                       taxGrandTotal = taxGrandTotal.add(totalAmount);
>>>>>>> +                   }
>>>>>>> +               }
>>>>>>> +           }
>>>>>>> +           // Process any adjustments that got missed
>>>>>>> +           List<GenericValue> missedAdjustments = FastList.newInstance();
>>>>>>> +           missedAdjustments.addAll(orderAdjustmentsToUse);
>>>>>>> +           missedAdjustments.removeAll(processedAdjustments);
>>>>>>> +           for (GenericValue orderAdjustment : missedAdjustments) {
>>>>>>> +               taxGrandTotal = taxGrandTotal.add(orderAdjustment.getBigDecimal("amount").setScale(taxCalcScale,
>>>>>>> taxRounding));
>>>>>>> +           }
>>>>>>> +           taxGrandTotal = taxGrandTotal.setScale(taxFinalScale, taxRounding);
>>>>>>> +       }
>>>>>>> +       Map<String, Object> result = FastMap.newInstance();
>>>>>>> +       result.put("taxByTaxAuthGeoAndPartyList", taxByTaxAuthGeoAndPartyList);
>>>>>>> +       result.put("taxGrandTotal", taxGrandTotal);
>>>>>>> +       return result;
>>>>>>> +   }
>>>>>>> }
>>>>>>>
>>>>>>> Modified: ofbiz/trunk/applications/product/entitydef/entitymodel.xml
>>>>>>> URL:
>>>>>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/entitydef/entitymodel.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>>>> ==============================================================================
>>>>>>> --- ofbiz/trunk/applications/product/entitydef/entitymodel.xml (original)
>>>>>>> +++ ofbiz/trunk/applications/product/entitydef/entitymodel.xml Mon Dec  6 08:05:44 2010
>>>>>>> @@ -2375,11 +2375,13 @@ under the License.
>>>>>>>    <field name="price" type="currency-precise"></field>
>>>>>>>    <field name="termUomId" type="id"><description>Mainly used for recurring and usage prices to specify a time/freq measure,
>>>>>>> or a usage unit measure (bits, minutes, etc)</description></field>
>>>>>>>    <field name="customPriceCalcService" type="id"><description>Points to a CustomMethod used to specify a service for the
>>>>>>> calculation of the unit price of the product (NOTE: a better name for this field might be
>>>>>>> priceCalcCustomMethodId)</description></field>
>>>>>>> -      <field name="priceWithTax" type="currency-precise"/>
>>>>>>> +      <field name="priceWithoutTax" type="currency-precise"><description>Always without tax if populated, regardless of if
>>>>>>> price does or does not include tax.</description></field>
>>>>>>> +      <field name="priceWithTax" type="currency-precise"><description>Always with tax if populated, regardless of if price
>>>>>>> does or does not include tax.</description></field>
>>>>>>>    <field name="taxAmount" type="currency-precise"/>
>>>>>>>    <field name="taxPercentage" type="fixed-point"/>
>>>>>>>    <field name="taxAuthPartyId" type="id-ne"/>
>>>>>>>    <field name="taxAuthGeoId" type="id-ne"/>
>>>>>>> +      <field name="taxInPrice" type="indicator"><description>If Y the price field has tax included for the given
>>>>>>> taxAuthPartyId/taxAuthGeoId at the taxPercentage.</description></field>
>>>>>>>    <field name="createdDate" type="date-time"></field>
>>>>>>>    <field name="createdByUserLogin" type="id-vlong"></field>
>>>>>>>    <field name="lastModifiedDate" type="date-time"></field>
>>>>>>>
>>>>>>> Modified: ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml
>>>>>>> URL:
>>>>>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>>>> ==============================================================================
>>>>>>> --- ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml (original)
>>>>>>> +++ ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml Mon Dec  6 08:05:44 2010
>>>>>>> @@ -106,6 +106,7 @@ under the License.
>>>>>>>          </condition>
>>>>>>>          <then>
>>>>>>>              <set field="parameters.priceWithTax" from-field="parameters.price"/>
>>>>>>> +
>>>>>>>              <!-- if taxPercentage not passed in look it up based on taxAuthGeoId and taxAuthPartyId -->
>>>>>>>              <if-empty field="parameters.taxPercentage">
>>>>>>>                  <!-- we only have basic data to constrain by here, so assume that if it is a VAT tax setup it should be
>>>>>>> pretty simple -->
>>>>>>> @@ -127,6 +128,7 @@ under the License.
>>>>>>>                  <check-errors/>
>>>>>>>              </if-empty>
>>>>>>>
>>>>>>> +                <!-- in short the formula is: taxAmount = priceWithTax - (priceWithTax/(1+taxPercentage/100)) -->
>>>>>>>              <calculate field="parameters.taxAmount" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>>>>>>>                  <calcop operator="subtract">
>>>>>>>                      <calcop operator="get" field="parameters.priceWithTax"/>
>>>>>>> @@ -142,12 +144,23 @@ under the License.
>>>>>>>                      </calcop>
>>>>>>>                  </calcop>
>>>>>>>              </calculate>
>>>>>>> -                <calculate field="parameters.price" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>>>>>>> +
>>>>>>> +                <calculate field="parameters.priceWithoutTax" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>>>>>>>                  <calcop operator="subtract">
>>>>>>>                      <calcop operator="get" field="parameters.priceWithTax"/>
>>>>>>>                      <calcop operator="get" field="parameters.taxAmount"></calcop>
>>>>>>>                  </calcop>
>>>>>>>              </calculate>
>>>>>>> +
>>>>>>> +                <if-compare field="parameters.taxInPrice" operator="equals" value="Y">
>>>>>>> +                    <!-- the price passed in has tax included, and we want to store it with tax included -->
>>>>>>> +                    <set field="parameters.price" from-field="parameters.priceWithTax"/>
>>>>>>> +
>>>>>>> +                    <else>
>>>>>>> +                        <!-- the price passed in has tax included, but we want to store it without tax included -->
>>>>>>> +                        <set field="parameters.price" from-field="parameters.priceWithoutTax"/>
>>>>>>> +                    </else>
>>>>>>> +                </if-compare>
>>>>>>>          </then>
>>>>>>>      </if>
>>>>>>>  </simple-method>
>>>>>>>
>>>>>>> Modified: ofbiz/trunk/applications/product/servicedef/services.xml
>>>>>>> URL:
>>>>>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/servicedef/services.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>>>> ==============================================================================
>>>>>>> --- ofbiz/trunk/applications/product/servicedef/services.xml (original)
>>>>>>> +++ ofbiz/trunk/applications/product/servicedef/services.xml Mon Dec  6 08:05:44 2010
>>>>>>> @@ -242,12 +242,15 @@ under the License.
>>>>>>>      <description>
>>>>>>>          Create an ProductPrice.
>>>>>>>          Price is always stored without tax.
>>>>>>> -            If a taxAuthGeoId and taxAuthPartyId are (or taxAuthCombinedId is) passed in then the price will be considered
>>>>>>> a price
>>>>>>> -                with tax included and the tax will be removed before storing to the database
>>>>>>> -                (the priceWithTax, taxAmount, and taxPercentage fields will also be populated).
>>>>>>> +            If taxAuthGeoId and taxAuthPartyId are (or taxAuthCombinedId is) passed in then the price will be considered a
>>>>>>> price
>>>>>>> +            with tax included (the priceWithoutTax, priceWithTax, taxAmount, and taxPercentage fields will also be
>>>>>>> populated).
>>>>>>> +            If the taxInPrice field is 'Y' then the price field will be left with the price included (price will be equal
>>>>>>> to priceWithTax),
>>>>>>> +            otherwise tax will be removed from the passed in price and the price field will be equal to the priceWithoutTax
>>>>>>> field.
>>>>>>> +            If taxAuthGeoId or taxAuthPartyId empty, and taxAuthCombinedId is empty, then the taxInPrice field will be
>>>>>>> ignored.
>>>>>>>      </description>
>>>>>>>      <auto-attributes include="pk" mode="IN" optional="false"/>
>>>>>>>      <auto-attributes include="nonpk" mode="IN" optional="true">
>>>>>>> +            <exclude field-name="priceWithoutTax"/>
>>>>>>>          <exclude field-name="priceWithTax"/>
>>>>>>>          <exclude field-name="taxAmount"/>
>>>>>>>          <exclude field-name="createdDate"/>
>>>>>>> @@ -264,6 +267,7 @@ under the License.
>>>>>>>      <description>Update an ProductPrice</description>
>>>>>>>      <auto-attributes include="pk" mode="IN" optional="false"/>
>>>>>>>      <auto-attributes include="nonpk" mode="IN" optional="true">
>>>>>>> +            <exclude field-name="priceWithoutTax"/>
>>>>>>>          <exclude field-name="priceWithTax"/>
>>>>>>>          <exclude field-name="taxAmount"/>
>>>>>>>          <exclude field-name="createdDate"/>
>>>>>>>
>>>>>>
>>>>>>
>>>>
>>>>
>>
>>
>



Re: svn commit: r1042542 - in /ofbiz/trunk/applications: accounting/src/org/ofbiz/accounting/tax/ order/data/ order/entitydef/ order/src/org/ofbiz/order/order/ product/entitydef/ product/script/org/ofbiz/product/price/ product/servicedef/

Posted by David E Jones <de...@me.com>.
On Dec 7, 2010, at 11:58 PM, Jacques Le Roux wrote:

> From: "David E Jones" <de...@me.com>
>> On Dec 7, 2010, at 3:39 AM, Jacques Le Roux wrote:
>> 
>>> If there are no plans to prevent the use of the new fields for the other types Price Types than Default, I'd like latter to add a small js script to handle it. Of course being able to use it with Price Rules and Promo would be great.
>> 
>> Why would we want to prevent using these fields for other price types?
> 
> I thought only the Default Price is really using VAT included price. So to prevent users thinking they can use VAT included price with other price types.

Any price could, and should if applicable, have tax authority settings and have VAT tax included.

>>> Also what for is used taxAuthCombinedId? I don't see an use at the UI level.
>> 
>> With a combined ID you can select the tax authority in a drop-down instead of forcing the user to handle two separate fields that are not correlated, making the UI significantly more cumbersome and difficult to use.
> 
> Yes but then I think we need to expose a bit more how to enter a combined ID, I will add a tooltip somewhere in the screen

A user wouldn't generally enter the combined ID manually, that would be a bad UI. The combined ID is just meant to be the ID in a drop-down (or perhaps a lookup).

-David


>> 
>>> From: "David E Jones" <de...@me.com>
>>>> I commented before that my previous efforts only made calculations more accurate, and those efforts made it clear that the only way to be sure is to calculate things based on prices with tax included.
>>>> 
>>>> So, now OFBiz has low-level support for prices with tax included and calculating VAT instead of sales tax with adjustments that represent amounts already included in the prices.
>>>> 
>>>> Note that this does not include any UI changes, and so unless changes are done to allow entry of prices with tax included and to show the VAT_TAX adjustments (specifically the new amountAlreadyIncluded field, which was added so that it wouldn't interfere with the current amount field).
>>>> 
>>>> -David
>>>> 
>>>> 
>>>> On Dec 6, 2010, at 8:39 AM, Jacques Le Roux wrote:
>>>> 
>>>>> Hi David,
>>>>> 
>>>>> Interesting, what decided you to finally shift your ground?
>>>>> 
>>>>> Is it still true that only Default Price can be used, and if yes is there any blocking reasons?
>>>>> 
>>>>> Thanks
>>>>> 
>>>>> Jacques
>>>>> 
>>>>> From: <jo...@apache.org>
>>>>>> Author: jonesde
>>>>>> Date: Mon Dec  6 08:05:44 2010
>>>>>> New Revision: 1042542
>>>>>> 
>>>>>> URL: http://svn.apache.org/viewvc?rev=1042542&view=rev
>>>>>> Log:
>>>>>> Implemented alternative way of saving ProductPrices with tax included in the price, and then calculating VAT tax as an exclusive instead of inclusive amount that goes on a new field on OrderAdjustment; also added a method to OrderReadHelper to get tax for display; there are various changes to service descriptions and entity fields to describe these changes
>>>>>> 
>>>>>> Modified:
>>>>>> ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java
>>>>>> ofbiz/trunk/applications/order/data/OrderTypeData.xml
>>>>>> ofbiz/trunk/applications/order/entitydef/entitymodel.xml
>>>>>> ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
>>>>>> ofbiz/trunk/applications/product/entitydef/entitymodel.xml
>>>>>> ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml
>>>>>> ofbiz/trunk/applications/product/servicedef/services.xml
>>>>>> 
>>>>>> Modified: ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java
>>>>>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>>> ==============================================================================
>>>>>> --- ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java (original)
>>>>>> +++ ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java Mon Dec  6 08:05:44 2010
>>>>>> @@ -391,11 +391,38 @@ public class TaxAuthorityServices {
>>>>>>                  // 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,
>>>>>> +                            "productPriceTypeId", "DEFAULT_PRICE", "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");
>>>>>> -                taxAdjValue.set("taxAuthorityRateSeqId", taxAuthorityRateProduct.getString("taxAuthorityRateSeqId"));
>>>>>> -                taxAdjValue.set("amount", taxAmount);
>>>>>> +
>>>>>> +                if ("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("orderAdjustmentTypeId", "SALES_TAX");
>>>>>> +                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"));
>>>>>> @@ -421,62 +448,50 @@ public class TaxAuthorityServices {
>>>>>>              }
>>>>>> 
>>>>>>              adjustments.add(taxAdjValue);
>>>>>> -
>>>>>> -                // 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 get this to work with price rules changing the default price (right now only works where itemPrice==defaultPrice
>>>>>> -                // 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
>>>>>> -
>>>>>> -                //Debug.logInfo("=================== itemQuantity=" + itemQuantity, module);
>>>>>> -                //Debug.logInfo("=================== taxAuthPartyId=" + taxAuthPartyId, module);
>>>>>> -                //Debug.logInfo("=================== taxAuthGeoId=" + taxAuthGeoId, module);
>>>>>> -                if (product != null && itemQuantity != 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,
>>>>>> -                            "productPriceTypeId", "DEFAULT_PRICE", "productPricePurposeId", "PURCHASE");
>>>>>> -                    List<GenericValue> productPriceList = delegator.findByAnd("ProductPrice", priceFindMap, UtilMisc.toList("-fromDate"));
>>>>>> -                    productPriceList = EntityUtil.filterByDate(productPriceList, true);
>>>>>> -                    GenericValue productPrice = (productPriceList != null && productPriceList.size() > 0) ? productPriceList.get(0): null;
>>>>>> -                    //Debug.logInfo("=================== productId=" + product.getString("productId"), module);
>>>>>> -                    //Debug.logInfo("=================== productPrice=" + productPrice, module);
>>>>>> +
>>>>>> +                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
>>>>>> 
>>>>>> -                    if (productPrice != null && productPrice.getBigDecimal("priceWithTax") != null) {
>>>>>> -                        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);
>>>>>> -                        BigDecimal enteredTotalPriceWithTax = priceWithTax.multiply(itemQuantity);
>>>>>> -                        BigDecimal calcedTotalPriceWithTax = (baseSubtotal).add(baseTaxAmount);
>>>>>> -                        //Debug.logInfo("=================== priceWithTax=" + priceWithTax, module);
>>>>>> -                        //Debug.logInfo("=================== enteredTotalPriceWithTax=" + enteredTotalPriceWithTax, module);
>>>>>> -                        //Debug.logInfo("=================== calcedTotalPriceWithTax=" + calcedTotalPriceWithTax, module);
>>>>>> +                    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);
>>>>>> 
>>>>>> -                        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);
>>>>>> -                        }
>>>>>> +                        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);
>>>>>>                  }
>>>>>>              }
>>>>>>          }
>>>>>> 
>>>>>> Modified: ofbiz/trunk/applications/order/data/OrderTypeData.xml
>>>>>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/data/OrderTypeData.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>>> ==============================================================================
>>>>>> --- ofbiz/trunk/applications/order/data/OrderTypeData.xml (original)
>>>>>> +++ ofbiz/trunk/applications/order/data/OrderTypeData.xml Mon Dec  6 08:05:44 2010
>>>>>> @@ -42,6 +42,7 @@ under the License.
>>>>>>  <OrderAdjustmentType description="Fee" hasTable="N" orderAdjustmentTypeId="FEE" parentTypeId=""/>
>>>>>>  <OrderAdjustmentType description="Miscellaneous Charges" hasTable="N" orderAdjustmentTypeId="MISCELLANEOUS_CHARGE" parentTypeId=""/>
>>>>>>  <OrderAdjustmentType description="Sales Tax" hasTable="N" orderAdjustmentTypeId="SALES_TAX" parentTypeId=""/>
>>>>>> +    <OrderAdjustmentType description="VAT Tax (not added to totals)" hasTable="N" orderAdjustmentTypeId="VAT_TAX" parentTypeId=""/>
>>>>>>  <OrderAdjustmentType description="VAT Price Correction" hasTable="N" orderAdjustmentTypeId="VAT_PRICE_CORRECT" parentTypeId=""/>
>>>>>>  <OrderAdjustmentType description="Shipping and Handling" hasTable="N" orderAdjustmentTypeId="SHIPPING_CHARGES" parentTypeId=""/>
>>>>>>  <OrderAdjustmentType description="Surcharge" hasTable="N" orderAdjustmentTypeId="SURCHARGE_ADJUSTMENT" parentTypeId=""/>
>>>>>> 
>>>>>> Modified: ofbiz/trunk/applications/order/entitydef/entitymodel.xml
>>>>>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/entitydef/entitymodel.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>>> ==============================================================================
>>>>>> --- ofbiz/trunk/applications/order/entitydef/entitymodel.xml (original)
>>>>>> +++ ofbiz/trunk/applications/order/entitydef/entitymodel.xml Mon Dec  6 08:05:44 2010
>>>>>> @@ -59,6 +59,7 @@ under the License.
>>>>>>    <field name="description" type="description"></field>
>>>>>>    <field name="amount" type="currency-precise"></field>
>>>>>>    <field name="recurringAmount" type="currency-precise"></field>
>>>>>> +      <field name="amountAlreadyIncluded" type="currency-precise"><description>The amount here is already represented in the price, such as VAT taxes.</description></field>
>>>>>>    <field name="productPromoId" type="id"></field>
>>>>>>    <field name="productPromoRuleId" type="id"></field>
>>>>>>    <field name="productPromoActionSeqId" type="id"></field>
>>>>>> 
>>>>>> Modified: ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
>>>>>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>>> ==============================================================================
>>>>>> --- ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java (original)
>>>>>> +++ ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java Mon Dec  6 08:05:44 2010
>>>>>> @@ -2920,4 +2920,62 @@ public class OrderReadHelper {
>>>>>>     result.put("taxGrandTotal", taxGrandTotal);
>>>>>>     return result;
>>>>>> }
>>>>>> +
>>>>>> +   public static Map<String, Object> getOrderTaxByTaxAuthGeoAndPartyForDisplay(List<GenericValue> orderAdjustmentsOriginal) {
>>>>>> +       BigDecimal taxGrandTotal = BigDecimal.ZERO;
>>>>>> +       List<Map<String, Object>> taxByTaxAuthGeoAndPartyList = FastList.newInstance();
>>>>>> +       List<GenericValue> orderAdjustmentsToUse = FastList.newInstance();
>>>>>> +       if (UtilValidate.isNotEmpty(orderAdjustmentsOriginal)) {
>>>>>> +           // get orderAdjustment where orderAdjustmentTypeId is SALES_TAX.
>>>>>> +           orderAdjustmentsToUse.addAll(EntityUtil.filterByAnd(orderAdjustmentsOriginal, UtilMisc.toMap("orderAdjustmentTypeId", "SALES_TAX")));
>>>>>> +           orderAdjustmentsToUse.addAll(EntityUtil.filterByAnd(orderAdjustmentsOriginal, UtilMisc.toMap("orderAdjustmentTypeId", "VAT_TAX")));
>>>>>> +           orderAdjustmentsToUse = EntityUtil.orderBy(orderAdjustmentsToUse, UtilMisc.toList("taxAuthGeoId","taxAuthPartyId"));
>>>>>> +
>>>>>> +           // get the list of all distinct taxAuthGeoId and taxAuthPartyId. It is for getting the number of taxAuthGeo and taxAuthPartyId in adjustments.
>>>>>> +           List<String> distinctTaxAuthGeoIdList = EntityUtil.getFieldListFromEntityList(orderAdjustmentsToUse, "taxAuthGeoId", true);
>>>>>> +           List<String> distinctTaxAuthPartyIdList = EntityUtil.getFieldListFromEntityList(orderAdjustmentsToUse, "taxAuthPartyId", true);
>>>>>> +
>>>>>> +           // Keep a list of amount that have been added to make sure none are missed (if taxAuth* information is missing)
>>>>>> +           List<GenericValue> processedAdjustments = FastList.newInstance();
>>>>>> +           // For each taxAuthGeoId get and add amount from orderAdjustment
>>>>>> +           for (String taxAuthGeoId : distinctTaxAuthGeoIdList) {
>>>>>> +               for (String taxAuthPartyId : distinctTaxAuthPartyIdList) {
>>>>>> +                   //get all records for orderAdjustments filtered by taxAuthGeoId and taxAurhPartyId
>>>>>> +                   List<GenericValue> orderAdjByTaxAuthGeoAndPartyIds = EntityUtil.filterByAnd(orderAdjustmentsToUse, UtilMisc.toMap("taxAuthGeoId", taxAuthGeoId, "taxAuthPartyId", taxAuthPartyId));
>>>>>> +                   if (UtilValidate.isNotEmpty(orderAdjByTaxAuthGeoAndPartyIds)) {
>>>>>> +                       BigDecimal totalAmount = BigDecimal.ZERO;
>>>>>> +                       //Now for each orderAdjustment record get and add amount.
>>>>>> +                       for (GenericValue orderAdjustment : orderAdjByTaxAuthGeoAndPartyIds) {
>>>>>> +                           BigDecimal amount = orderAdjustment.getBigDecimal("amount");
>>>>>> +                           if (amount != null) {
>>>>>> +                               totalAmount = totalAmount.add(amount);
>>>>>> +                           }
>>>>>> +                           if ("VAT_TAX".equals(orderAdjustment.getString("orderAdjustmentTypeId")) &&
>>>>>> +                                   orderAdjustment.get("amountAlreadyIncluded") != null) {
>>>>>> +                               // this is the only case where the VAT_TAX amountAlreadyIncluded should be added in, and should just be for display and not to calculate the order grandTotal
>>>>>> +                               totalAmount = totalAmount.add(orderAdjustment.getBigDecimal("amountAlreadyIncluded"));
>>>>>> +                           }
>>>>>> +                           totalAmount = totalAmount.setScale(taxCalcScale, taxRounding);
>>>>>> +                           processedAdjustments.add(orderAdjustment);
>>>>>> +                       }
>>>>>> +                       totalAmount = totalAmount.setScale(taxFinalScale, taxRounding);
>>>>>> +                       taxByTaxAuthGeoAndPartyList.add(UtilMisc.<String, Object>toMap("taxAuthPartyId", taxAuthPartyId, "taxAuthGeoId", taxAuthGeoId, "totalAmount", totalAmount));
>>>>>> +                       taxGrandTotal = taxGrandTotal.add(totalAmount);
>>>>>> +                   }
>>>>>> +               }
>>>>>> +           }
>>>>>> +           // Process any adjustments that got missed
>>>>>> +           List<GenericValue> missedAdjustments = FastList.newInstance();
>>>>>> +           missedAdjustments.addAll(orderAdjustmentsToUse);
>>>>>> +           missedAdjustments.removeAll(processedAdjustments);
>>>>>> +           for (GenericValue orderAdjustment : missedAdjustments) {
>>>>>> +               taxGrandTotal = taxGrandTotal.add(orderAdjustment.getBigDecimal("amount").setScale(taxCalcScale, taxRounding));
>>>>>> +           }
>>>>>> +           taxGrandTotal = taxGrandTotal.setScale(taxFinalScale, taxRounding);
>>>>>> +       }
>>>>>> +       Map<String, Object> result = FastMap.newInstance();
>>>>>> +       result.put("taxByTaxAuthGeoAndPartyList", taxByTaxAuthGeoAndPartyList);
>>>>>> +       result.put("taxGrandTotal", taxGrandTotal);
>>>>>> +       return result;
>>>>>> +   }
>>>>>> }
>>>>>> 
>>>>>> Modified: ofbiz/trunk/applications/product/entitydef/entitymodel.xml
>>>>>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/entitydef/entitymodel.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>>> ==============================================================================
>>>>>> --- ofbiz/trunk/applications/product/entitydef/entitymodel.xml (original)
>>>>>> +++ ofbiz/trunk/applications/product/entitydef/entitymodel.xml Mon Dec  6 08:05:44 2010
>>>>>> @@ -2375,11 +2375,13 @@ under the License.
>>>>>>    <field name="price" type="currency-precise"></field>
>>>>>>    <field name="termUomId" type="id"><description>Mainly used for recurring and usage prices to specify a time/freq measure, or a usage unit measure (bits, minutes, etc)</description></field>
>>>>>>    <field name="customPriceCalcService" type="id"><description>Points to a CustomMethod used to specify a service for the calculation of the unit price of the product (NOTE: a better name for this field might be priceCalcCustomMethodId)</description></field>
>>>>>> -      <field name="priceWithTax" type="currency-precise"/>
>>>>>> +      <field name="priceWithoutTax" type="currency-precise"><description>Always without tax if populated, regardless of if price does or does not include tax.</description></field>
>>>>>> +      <field name="priceWithTax" type="currency-precise"><description>Always with tax if populated, regardless of if price does or does not include tax.</description></field>
>>>>>>    <field name="taxAmount" type="currency-precise"/>
>>>>>>    <field name="taxPercentage" type="fixed-point"/>
>>>>>>    <field name="taxAuthPartyId" type="id-ne"/>
>>>>>>    <field name="taxAuthGeoId" type="id-ne"/>
>>>>>> +      <field name="taxInPrice" type="indicator"><description>If Y the price field has tax included for the given taxAuthPartyId/taxAuthGeoId at the taxPercentage.</description></field>
>>>>>>    <field name="createdDate" type="date-time"></field>
>>>>>>    <field name="createdByUserLogin" type="id-vlong"></field>
>>>>>>    <field name="lastModifiedDate" type="date-time"></field>
>>>>>> 
>>>>>> Modified: ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml
>>>>>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>>> ==============================================================================
>>>>>> --- ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml (original)
>>>>>> +++ ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml Mon Dec  6 08:05:44 2010
>>>>>> @@ -106,6 +106,7 @@ under the License.
>>>>>>          </condition>
>>>>>>          <then>
>>>>>>              <set field="parameters.priceWithTax" from-field="parameters.price"/>
>>>>>> +
>>>>>>              <!-- if taxPercentage not passed in look it up based on taxAuthGeoId and taxAuthPartyId -->
>>>>>>              <if-empty field="parameters.taxPercentage">
>>>>>>                  <!-- we only have basic data to constrain by here, so assume that if it is a VAT tax setup it should be pretty simple -->
>>>>>> @@ -127,6 +128,7 @@ under the License.
>>>>>>                  <check-errors/>
>>>>>>              </if-empty>
>>>>>> 
>>>>>> +                <!-- in short the formula is: taxAmount = priceWithTax - (priceWithTax/(1+taxPercentage/100)) -->
>>>>>>              <calculate field="parameters.taxAmount" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>>>>>>                  <calcop operator="subtract">
>>>>>>                      <calcop operator="get" field="parameters.priceWithTax"/>
>>>>>> @@ -142,12 +144,23 @@ under the License.
>>>>>>                      </calcop>
>>>>>>                  </calcop>
>>>>>>              </calculate>
>>>>>> -                <calculate field="parameters.price" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>>>>>> +
>>>>>> +                <calculate field="parameters.priceWithoutTax" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>>>>>>                  <calcop operator="subtract">
>>>>>>                      <calcop operator="get" field="parameters.priceWithTax"/>
>>>>>>                      <calcop operator="get" field="parameters.taxAmount"></calcop>
>>>>>>                  </calcop>
>>>>>>              </calculate>
>>>>>> +
>>>>>> +                <if-compare field="parameters.taxInPrice" operator="equals" value="Y">
>>>>>> +                    <!-- the price passed in has tax included, and we want to store it with tax included -->
>>>>>> +                    <set field="parameters.price" from-field="parameters.priceWithTax"/>
>>>>>> +
>>>>>> +                    <else>
>>>>>> +                        <!-- the price passed in has tax included, but we want to store it without tax included -->
>>>>>> +                        <set field="parameters.price" from-field="parameters.priceWithoutTax"/>
>>>>>> +                    </else>
>>>>>> +                </if-compare>
>>>>>>          </then>
>>>>>>      </if>
>>>>>>  </simple-method>
>>>>>> 
>>>>>> Modified: ofbiz/trunk/applications/product/servicedef/services.xml
>>>>>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/servicedef/services.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>>> ==============================================================================
>>>>>> --- ofbiz/trunk/applications/product/servicedef/services.xml (original)
>>>>>> +++ ofbiz/trunk/applications/product/servicedef/services.xml Mon Dec  6 08:05:44 2010
>>>>>> @@ -242,12 +242,15 @@ under the License.
>>>>>>      <description>
>>>>>>          Create an ProductPrice.
>>>>>>          Price is always stored without tax.
>>>>>> -            If a taxAuthGeoId and taxAuthPartyId are (or taxAuthCombinedId is) passed in then the price will be considered a price
>>>>>> -                with tax included and the tax will be removed before storing to the database
>>>>>> -                (the priceWithTax, taxAmount, and taxPercentage fields will also be populated).
>>>>>> +            If taxAuthGeoId and taxAuthPartyId are (or taxAuthCombinedId is) passed in then the price will be considered a price
>>>>>> +            with tax included (the priceWithoutTax, priceWithTax, taxAmount, and taxPercentage fields will also be populated).
>>>>>> +            If the taxInPrice field is 'Y' then the price field will be left with the price included (price will be equal to priceWithTax),
>>>>>> +            otherwise tax will be removed from the passed in price and the price field will be equal to the priceWithoutTax field.
>>>>>> +            If taxAuthGeoId or taxAuthPartyId empty, and taxAuthCombinedId is empty, then the taxInPrice field will be ignored.
>>>>>>      </description>
>>>>>>      <auto-attributes include="pk" mode="IN" optional="false"/>
>>>>>>      <auto-attributes include="nonpk" mode="IN" optional="true">
>>>>>> +            <exclude field-name="priceWithoutTax"/>
>>>>>>          <exclude field-name="priceWithTax"/>
>>>>>>          <exclude field-name="taxAmount"/>
>>>>>>          <exclude field-name="createdDate"/>
>>>>>> @@ -264,6 +267,7 @@ under the License.
>>>>>>      <description>Update an ProductPrice</description>
>>>>>>      <auto-attributes include="pk" mode="IN" optional="false"/>
>>>>>>      <auto-attributes include="nonpk" mode="IN" optional="true">
>>>>>> +            <exclude field-name="priceWithoutTax"/>
>>>>>>          <exclude field-name="priceWithTax"/>
>>>>>>          <exclude field-name="taxAmount"/>
>>>>>>          <exclude field-name="createdDate"/>
>>>>>> 
>>>>> 
>>>>> 
>>> 
>>> 
> 
> 


Re: svn commit: r1042542 - in /ofbiz/trunk/applications: accounting/src/org/ofbiz/accounting/tax/ order/data/ order/entitydef/ order/src/org/ofbiz/order/order/ product/entitydef/ product/script/org/ofbiz/product/price/ product/servicedef/

Posted by Jacques Le Roux <ja...@les7arts.com>.
From: "David E Jones" <de...@me.com>
> On Dec 7, 2010, at 3:39 AM, Jacques Le Roux wrote:
>
>> If there are no plans to prevent the use of the new fields for the other types Price Types than Default, I'd like latter to add a 
>> small js script to handle it. Of course being able to use it with Price Rules and Promo would be great.
>
> Why would we want to prevent using these fields for other price types?

I thought only the Default Price is really using VAT included price. So to prevent users thinking they can use VAT included price 
with other price types.

>> Also what for is used taxAuthCombinedId? I don't see an use at the UI level.
>
> With a combined ID you can select the tax authority in a drop-down instead of forcing the user to handle two separate fields that 
> are not correlated, making the UI significantly more cumbersome and difficult to use.

Yes but then I think we need to expose a bit more how to enter a combined ID, I will add a tooltip somewhere in the screen

Jacques

>
> -David
>
>
>
>> From: "David E Jones" <de...@me.com>
>>> I commented before that my previous efforts only made calculations more accurate, and those efforts made it clear that the only 
>>> way to be sure is to calculate things based on prices with tax included.
>>>
>>> So, now OFBiz has low-level support for prices with tax included and calculating VAT instead of sales tax with adjustments that 
>>> represent amounts already included in the prices.
>>>
>>> Note that this does not include any UI changes, and so unless changes are done to allow entry of prices with tax included and to 
>>> show the VAT_TAX adjustments (specifically the new amountAlreadyIncluded field, which was added so that it wouldn't interfere 
>>> with the current amount field).
>>>
>>> -David
>>>
>>>
>>> On Dec 6, 2010, at 8:39 AM, Jacques Le Roux wrote:
>>>
>>>> Hi David,
>>>>
>>>> Interesting, what decided you to finally shift your ground?
>>>>
>>>> Is it still true that only Default Price can be used, and if yes is there any blocking reasons?
>>>>
>>>> Thanks
>>>>
>>>> Jacques
>>>>
>>>> From: <jo...@apache.org>
>>>>> Author: jonesde
>>>>> Date: Mon Dec  6 08:05:44 2010
>>>>> New Revision: 1042542
>>>>>
>>>>> URL: http://svn.apache.org/viewvc?rev=1042542&view=rev
>>>>> Log:
>>>>> Implemented alternative way of saving ProductPrices with tax included in the price, and then calculating VAT tax as an 
>>>>> exclusive instead of inclusive amount that goes on a new field on OrderAdjustment; also added a method to OrderReadHelper to 
>>>>> get tax for display; there are various changes to service descriptions and entity fields to describe these changes
>>>>>
>>>>> Modified:
>>>>>  ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java
>>>>>  ofbiz/trunk/applications/order/data/OrderTypeData.xml
>>>>>  ofbiz/trunk/applications/order/entitydef/entitymodel.xml
>>>>>  ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
>>>>>  ofbiz/trunk/applications/product/entitydef/entitymodel.xml
>>>>>  ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml
>>>>>  ofbiz/trunk/applications/product/servicedef/services.xml
>>>>>
>>>>> Modified: ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java
>>>>> URL: 
>>>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>> ==============================================================================
>>>>> --- ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java (original)
>>>>> +++ ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java Mon Dec  6 08:05:44 2010
>>>>> @@ -391,11 +391,38 @@ public class TaxAuthorityServices {
>>>>>                   // 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,
>>>>> +                            "productPriceTypeId", "DEFAULT_PRICE", "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");
>>>>> -                taxAdjValue.set("taxAuthorityRateSeqId", taxAuthorityRateProduct.getString("taxAuthorityRateSeqId"));
>>>>> -                taxAdjValue.set("amount", taxAmount);
>>>>> +
>>>>> +                if ("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("orderAdjustmentTypeId", "SALES_TAX");
>>>>> +                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"));
>>>>> @@ -421,62 +448,50 @@ public class TaxAuthorityServices {
>>>>>               }
>>>>>
>>>>>               adjustments.add(taxAdjValue);
>>>>> -
>>>>> -                // 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 get this to work with price rules changing the default price (right now only works where 
>>>>> itemPrice==defaultPrice
>>>>> -                // 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
>>>>> -
>>>>> -                //Debug.logInfo("=================== itemQuantity=" + itemQuantity, module);
>>>>> -                //Debug.logInfo("=================== taxAuthPartyId=" + taxAuthPartyId, module);
>>>>> -                //Debug.logInfo("=================== taxAuthGeoId=" + taxAuthGeoId, module);
>>>>> -                if (product != null && itemQuantity != 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,
>>>>> -                            "productPriceTypeId", "DEFAULT_PRICE", "productPricePurposeId", "PURCHASE");
>>>>> -                    List<GenericValue> productPriceList = delegator.findByAnd("ProductPrice", priceFindMap, 
>>>>> UtilMisc.toList("-fromDate"));
>>>>> -                    productPriceList = EntityUtil.filterByDate(productPriceList, true);
>>>>> -                    GenericValue productPrice = (productPriceList != null && productPriceList.size() > 0) ? 
>>>>> productPriceList.get(0): null;
>>>>> -                    //Debug.logInfo("=================== productId=" + product.getString("productId"), module);
>>>>> -                    //Debug.logInfo("=================== productPrice=" + productPrice, module);
>>>>> +
>>>>> +                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
>>>>>
>>>>> -                    if (productPrice != null && productPrice.getBigDecimal("priceWithTax") != null) {
>>>>> -                        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);
>>>>> -                        BigDecimal enteredTotalPriceWithTax = priceWithTax.multiply(itemQuantity);
>>>>> -                        BigDecimal calcedTotalPriceWithTax = (baseSubtotal).add(baseTaxAmount);
>>>>> -                        //Debug.logInfo("=================== priceWithTax=" + priceWithTax, module);
>>>>> -                        //Debug.logInfo("=================== enteredTotalPriceWithTax=" + enteredTotalPriceWithTax, module);
>>>>> -                        //Debug.logInfo("=================== calcedTotalPriceWithTax=" + calcedTotalPriceWithTax, module);
>>>>> +                    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);
>>>>>
>>>>> -                        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);
>>>>> -                        }
>>>>> +                        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);
>>>>>                   }
>>>>>               }
>>>>>           }
>>>>>
>>>>> Modified: ofbiz/trunk/applications/order/data/OrderTypeData.xml
>>>>> URL: 
>>>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/data/OrderTypeData.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>> ==============================================================================
>>>>> --- ofbiz/trunk/applications/order/data/OrderTypeData.xml (original)
>>>>> +++ ofbiz/trunk/applications/order/data/OrderTypeData.xml Mon Dec  6 08:05:44 2010
>>>>> @@ -42,6 +42,7 @@ under the License.
>>>>>   <OrderAdjustmentType description="Fee" hasTable="N" orderAdjustmentTypeId="FEE" parentTypeId=""/>
>>>>>   <OrderAdjustmentType description="Miscellaneous Charges" hasTable="N" orderAdjustmentTypeId="MISCELLANEOUS_CHARGE" 
>>>>> parentTypeId=""/>
>>>>>   <OrderAdjustmentType description="Sales Tax" hasTable="N" orderAdjustmentTypeId="SALES_TAX" parentTypeId=""/>
>>>>> +    <OrderAdjustmentType description="VAT Tax (not added to totals)" hasTable="N" orderAdjustmentTypeId="VAT_TAX" 
>>>>> parentTypeId=""/>
>>>>>   <OrderAdjustmentType description="VAT Price Correction" hasTable="N" orderAdjustmentTypeId="VAT_PRICE_CORRECT" 
>>>>> parentTypeId=""/>
>>>>>   <OrderAdjustmentType description="Shipping and Handling" hasTable="N" orderAdjustmentTypeId="SHIPPING_CHARGES" 
>>>>> parentTypeId=""/>
>>>>>   <OrderAdjustmentType description="Surcharge" hasTable="N" orderAdjustmentTypeId="SURCHARGE_ADJUSTMENT" parentTypeId=""/>
>>>>>
>>>>> Modified: ofbiz/trunk/applications/order/entitydef/entitymodel.xml
>>>>> URL: 
>>>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/entitydef/entitymodel.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>> ==============================================================================
>>>>> --- ofbiz/trunk/applications/order/entitydef/entitymodel.xml (original)
>>>>> +++ ofbiz/trunk/applications/order/entitydef/entitymodel.xml Mon Dec  6 08:05:44 2010
>>>>> @@ -59,6 +59,7 @@ under the License.
>>>>>     <field name="description" type="description"></field>
>>>>>     <field name="amount" type="currency-precise"></field>
>>>>>     <field name="recurringAmount" type="currency-precise"></field>
>>>>> +      <field name="amountAlreadyIncluded" type="currency-precise"><description>The amount here is already represented in the 
>>>>> price, such as VAT taxes.</description></field>
>>>>>     <field name="productPromoId" type="id"></field>
>>>>>     <field name="productPromoRuleId" type="id"></field>
>>>>>     <field name="productPromoActionSeqId" type="id"></field>
>>>>>
>>>>> Modified: ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
>>>>> URL: 
>>>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>> ==============================================================================
>>>>> --- ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java (original)
>>>>> +++ ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java Mon Dec  6 08:05:44 2010
>>>>> @@ -2920,4 +2920,62 @@ public class OrderReadHelper {
>>>>>      result.put("taxGrandTotal", taxGrandTotal);
>>>>>      return result;
>>>>>  }
>>>>> +
>>>>> +   public static Map<String, Object> getOrderTaxByTaxAuthGeoAndPartyForDisplay(List<GenericValue> orderAdjustmentsOriginal) {
>>>>> +       BigDecimal taxGrandTotal = BigDecimal.ZERO;
>>>>> +       List<Map<String, Object>> taxByTaxAuthGeoAndPartyList = FastList.newInstance();
>>>>> +       List<GenericValue> orderAdjustmentsToUse = FastList.newInstance();
>>>>> +       if (UtilValidate.isNotEmpty(orderAdjustmentsOriginal)) {
>>>>> +           // get orderAdjustment where orderAdjustmentTypeId is SALES_TAX.
>>>>> +           orderAdjustmentsToUse.addAll(EntityUtil.filterByAnd(orderAdjustmentsOriginal, 
>>>>> UtilMisc.toMap("orderAdjustmentTypeId", "SALES_TAX")));
>>>>> +           orderAdjustmentsToUse.addAll(EntityUtil.filterByAnd(orderAdjustmentsOriginal, 
>>>>> UtilMisc.toMap("orderAdjustmentTypeId", "VAT_TAX")));
>>>>> +           orderAdjustmentsToUse = EntityUtil.orderBy(orderAdjustmentsToUse, 
>>>>> UtilMisc.toList("taxAuthGeoId","taxAuthPartyId"));
>>>>> +
>>>>> +           // get the list of all distinct taxAuthGeoId and taxAuthPartyId. It is for getting the number of taxAuthGeo and 
>>>>> taxAuthPartyId in adjustments.
>>>>> +           List<String> distinctTaxAuthGeoIdList = EntityUtil.getFieldListFromEntityList(orderAdjustmentsToUse, 
>>>>> "taxAuthGeoId", true);
>>>>> +           List<String> distinctTaxAuthPartyIdList = EntityUtil.getFieldListFromEntityList(orderAdjustmentsToUse, 
>>>>> "taxAuthPartyId", true);
>>>>> +
>>>>> +           // Keep a list of amount that have been added to make sure none are missed (if taxAuth* information is missing)
>>>>> +           List<GenericValue> processedAdjustments = FastList.newInstance();
>>>>> +           // For each taxAuthGeoId get and add amount from orderAdjustment
>>>>> +           for (String taxAuthGeoId : distinctTaxAuthGeoIdList) {
>>>>> +               for (String taxAuthPartyId : distinctTaxAuthPartyIdList) {
>>>>> +                   //get all records for orderAdjustments filtered by taxAuthGeoId and taxAurhPartyId
>>>>> +                   List<GenericValue> orderAdjByTaxAuthGeoAndPartyIds = EntityUtil.filterByAnd(orderAdjustmentsToUse, 
>>>>> UtilMisc.toMap("taxAuthGeoId", taxAuthGeoId, "taxAuthPartyId", taxAuthPartyId));
>>>>> +                   if (UtilValidate.isNotEmpty(orderAdjByTaxAuthGeoAndPartyIds)) {
>>>>> +                       BigDecimal totalAmount = BigDecimal.ZERO;
>>>>> +                       //Now for each orderAdjustment record get and add amount.
>>>>> +                       for (GenericValue orderAdjustment : orderAdjByTaxAuthGeoAndPartyIds) {
>>>>> +                           BigDecimal amount = orderAdjustment.getBigDecimal("amount");
>>>>> +                           if (amount != null) {
>>>>> +                               totalAmount = totalAmount.add(amount);
>>>>> +                           }
>>>>> +                           if ("VAT_TAX".equals(orderAdjustment.getString("orderAdjustmentTypeId")) &&
>>>>> +                                   orderAdjustment.get("amountAlreadyIncluded") != null) {
>>>>> +                               // this is the only case where the VAT_TAX amountAlreadyIncluded should be added in, and 
>>>>> should just be for display and not to calculate the order grandTotal
>>>>> +                               totalAmount = totalAmount.add(orderAdjustment.getBigDecimal("amountAlreadyIncluded"));
>>>>> +                           }
>>>>> +                           totalAmount = totalAmount.setScale(taxCalcScale, taxRounding);
>>>>> +                           processedAdjustments.add(orderAdjustment);
>>>>> +                       }
>>>>> +                       totalAmount = totalAmount.setScale(taxFinalScale, taxRounding);
>>>>> +                       taxByTaxAuthGeoAndPartyList.add(UtilMisc.<String, Object>toMap("taxAuthPartyId", taxAuthPartyId, 
>>>>> "taxAuthGeoId", taxAuthGeoId, "totalAmount", totalAmount));
>>>>> +                       taxGrandTotal = taxGrandTotal.add(totalAmount);
>>>>> +                   }
>>>>> +               }
>>>>> +           }
>>>>> +           // Process any adjustments that got missed
>>>>> +           List<GenericValue> missedAdjustments = FastList.newInstance();
>>>>> +           missedAdjustments.addAll(orderAdjustmentsToUse);
>>>>> +           missedAdjustments.removeAll(processedAdjustments);
>>>>> +           for (GenericValue orderAdjustment : missedAdjustments) {
>>>>> +               taxGrandTotal = taxGrandTotal.add(orderAdjustment.getBigDecimal("amount").setScale(taxCalcScale, 
>>>>> taxRounding));
>>>>> +           }
>>>>> +           taxGrandTotal = taxGrandTotal.setScale(taxFinalScale, taxRounding);
>>>>> +       }
>>>>> +       Map<String, Object> result = FastMap.newInstance();
>>>>> +       result.put("taxByTaxAuthGeoAndPartyList", taxByTaxAuthGeoAndPartyList);
>>>>> +       result.put("taxGrandTotal", taxGrandTotal);
>>>>> +       return result;
>>>>> +   }
>>>>> }
>>>>>
>>>>> Modified: ofbiz/trunk/applications/product/entitydef/entitymodel.xml
>>>>> URL: 
>>>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/entitydef/entitymodel.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>> ==============================================================================
>>>>> --- ofbiz/trunk/applications/product/entitydef/entitymodel.xml (original)
>>>>> +++ ofbiz/trunk/applications/product/entitydef/entitymodel.xml Mon Dec  6 08:05:44 2010
>>>>> @@ -2375,11 +2375,13 @@ under the License.
>>>>>     <field name="price" type="currency-precise"></field>
>>>>>     <field name="termUomId" type="id"><description>Mainly used for recurring and usage prices to specify a time/freq measure, 
>>>>> or a usage unit measure (bits, minutes, etc)</description></field>
>>>>>     <field name="customPriceCalcService" type="id"><description>Points to a CustomMethod used to specify a service for the 
>>>>> calculation of the unit price of the product (NOTE: a better name for this field might be 
>>>>> priceCalcCustomMethodId)</description></field>
>>>>> -      <field name="priceWithTax" type="currency-precise"/>
>>>>> +      <field name="priceWithoutTax" type="currency-precise"><description>Always without tax if populated, regardless of if 
>>>>> price does or does not include tax.</description></field>
>>>>> +      <field name="priceWithTax" type="currency-precise"><description>Always with tax if populated, regardless of if price 
>>>>> does or does not include tax.</description></field>
>>>>>     <field name="taxAmount" type="currency-precise"/>
>>>>>     <field name="taxPercentage" type="fixed-point"/>
>>>>>     <field name="taxAuthPartyId" type="id-ne"/>
>>>>>     <field name="taxAuthGeoId" type="id-ne"/>
>>>>> +      <field name="taxInPrice" type="indicator"><description>If Y the price field has tax included for the given 
>>>>> taxAuthPartyId/taxAuthGeoId at the taxPercentage.</description></field>
>>>>>     <field name="createdDate" type="date-time"></field>
>>>>>     <field name="createdByUserLogin" type="id-vlong"></field>
>>>>>     <field name="lastModifiedDate" type="date-time"></field>
>>>>>
>>>>> Modified: ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml
>>>>> URL: 
>>>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>> ==============================================================================
>>>>> --- ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml (original)
>>>>> +++ ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml Mon Dec  6 08:05:44 2010
>>>>> @@ -106,6 +106,7 @@ under the License.
>>>>>           </condition>
>>>>>           <then>
>>>>>               <set field="parameters.priceWithTax" from-field="parameters.price"/>
>>>>> +
>>>>>               <!-- if taxPercentage not passed in look it up based on taxAuthGeoId and taxAuthPartyId -->
>>>>>               <if-empty field="parameters.taxPercentage">
>>>>>                   <!-- we only have basic data to constrain by here, so assume that if it is a VAT tax setup it should be 
>>>>> pretty simple -->
>>>>> @@ -127,6 +128,7 @@ under the License.
>>>>>                   <check-errors/>
>>>>>               </if-empty>
>>>>>
>>>>> +                <!-- in short the formula is: taxAmount = priceWithTax - (priceWithTax/(1+taxPercentage/100)) -->
>>>>>               <calculate field="parameters.taxAmount" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>>>>>                   <calcop operator="subtract">
>>>>>                       <calcop operator="get" field="parameters.priceWithTax"/>
>>>>> @@ -142,12 +144,23 @@ under the License.
>>>>>                       </calcop>
>>>>>                   </calcop>
>>>>>               </calculate>
>>>>> -                <calculate field="parameters.price" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>>>>> +
>>>>> +                <calculate field="parameters.priceWithoutTax" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>>>>>                   <calcop operator="subtract">
>>>>>                       <calcop operator="get" field="parameters.priceWithTax"/>
>>>>>                       <calcop operator="get" field="parameters.taxAmount"></calcop>
>>>>>                   </calcop>
>>>>>               </calculate>
>>>>> +
>>>>> +                <if-compare field="parameters.taxInPrice" operator="equals" value="Y">
>>>>> +                    <!-- the price passed in has tax included, and we want to store it with tax included -->
>>>>> +                    <set field="parameters.price" from-field="parameters.priceWithTax"/>
>>>>> +
>>>>> +                    <else>
>>>>> +                        <!-- the price passed in has tax included, but we want to store it without tax included -->
>>>>> +                        <set field="parameters.price" from-field="parameters.priceWithoutTax"/>
>>>>> +                    </else>
>>>>> +                </if-compare>
>>>>>           </then>
>>>>>       </if>
>>>>>   </simple-method>
>>>>>
>>>>> Modified: ofbiz/trunk/applications/product/servicedef/services.xml
>>>>> URL: 
>>>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/servicedef/services.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>>> ==============================================================================
>>>>> --- ofbiz/trunk/applications/product/servicedef/services.xml (original)
>>>>> +++ ofbiz/trunk/applications/product/servicedef/services.xml Mon Dec  6 08:05:44 2010
>>>>> @@ -242,12 +242,15 @@ under the License.
>>>>>       <description>
>>>>>           Create an ProductPrice.
>>>>>           Price is always stored without tax.
>>>>> -            If a taxAuthGeoId and taxAuthPartyId are (or taxAuthCombinedId is) passed in then the price will be considered a 
>>>>> price
>>>>> -                with tax included and the tax will be removed before storing to the database
>>>>> -                (the priceWithTax, taxAmount, and taxPercentage fields will also be populated).
>>>>> +            If taxAuthGeoId and taxAuthPartyId are (or taxAuthCombinedId is) passed in then the price will be considered a 
>>>>> price
>>>>> +            with tax included (the priceWithoutTax, priceWithTax, taxAmount, and taxPercentage fields will also be 
>>>>> populated).
>>>>> +            If the taxInPrice field is 'Y' then the price field will be left with the price included (price will be equal to 
>>>>> priceWithTax),
>>>>> +            otherwise tax will be removed from the passed in price and the price field will be equal to the priceWithoutTax 
>>>>> field.
>>>>> +            If taxAuthGeoId or taxAuthPartyId empty, and taxAuthCombinedId is empty, then the taxInPrice field will be 
>>>>> ignored.
>>>>>       </description>
>>>>>       <auto-attributes include="pk" mode="IN" optional="false"/>
>>>>>       <auto-attributes include="nonpk" mode="IN" optional="true">
>>>>> +            <exclude field-name="priceWithoutTax"/>
>>>>>           <exclude field-name="priceWithTax"/>
>>>>>           <exclude field-name="taxAmount"/>
>>>>>           <exclude field-name="createdDate"/>
>>>>> @@ -264,6 +267,7 @@ under the License.
>>>>>       <description>Update an ProductPrice</description>
>>>>>       <auto-attributes include="pk" mode="IN" optional="false"/>
>>>>>       <auto-attributes include="nonpk" mode="IN" optional="true">
>>>>> +            <exclude field-name="priceWithoutTax"/>
>>>>>           <exclude field-name="priceWithTax"/>
>>>>>           <exclude field-name="taxAmount"/>
>>>>>           <exclude field-name="createdDate"/>
>>>>>
>>>>
>>>>
>>
>>
> 



Re: svn commit: r1042542 - in /ofbiz/trunk/applications: accounting/src/org/ofbiz/accounting/tax/ order/data/ order/entitydef/ order/src/org/ofbiz/order/order/ product/entitydef/ product/script/org/ofbiz/product/price/ product/servicedef/

Posted by David E Jones <de...@me.com>.
On Dec 7, 2010, at 3:39 AM, Jacques Le Roux wrote:

> If there are no plans to prevent the use of the new fields for the other types Price Types than Default, I'd like latter to add a small js script to handle it. Of course being able to use it with Price Rules and Promo would be great.

Why would we want to prevent using these fields for other price types? 

> Also what for is used taxAuthCombinedId? I don't see an use at the UI level.

With a combined ID you can select the tax authority in a drop-down instead of forcing the user to handle two separate fields that are not correlated, making the UI significantly more cumbersome and difficult to use.

-David



> From: "David E Jones" <de...@me.com>
>> I commented before that my previous efforts only made calculations more accurate, and those efforts made it clear that the only way to be sure is to calculate things based on prices with tax included.
>> 
>> So, now OFBiz has low-level support for prices with tax included and calculating VAT instead of sales tax with adjustments that represent amounts already included in the prices.
>> 
>> Note that this does not include any UI changes, and so unless changes are done to allow entry of prices with tax included and to show the VAT_TAX adjustments (specifically the new amountAlreadyIncluded field, which was added so that it wouldn't interfere with the current amount field).
>> 
>> -David
>> 
>> 
>> On Dec 6, 2010, at 8:39 AM, Jacques Le Roux wrote:
>> 
>>> Hi David,
>>> 
>>> Interesting, what decided you to finally shift your ground?
>>> 
>>> Is it still true that only Default Price can be used, and if yes is there any blocking reasons?
>>> 
>>> Thanks
>>> 
>>> Jacques
>>> 
>>> From: <jo...@apache.org>
>>>> Author: jonesde
>>>> Date: Mon Dec  6 08:05:44 2010
>>>> New Revision: 1042542
>>>> 
>>>> URL: http://svn.apache.org/viewvc?rev=1042542&view=rev
>>>> Log:
>>>> Implemented alternative way of saving ProductPrices with tax included in the price, and then calculating VAT tax as an exclusive instead of inclusive amount that goes on a new field on OrderAdjustment; also added a method to OrderReadHelper to get tax for display; there are various changes to service descriptions and entity fields to describe these changes
>>>> 
>>>> Modified:
>>>>  ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java
>>>>  ofbiz/trunk/applications/order/data/OrderTypeData.xml
>>>>  ofbiz/trunk/applications/order/entitydef/entitymodel.xml
>>>>  ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
>>>>  ofbiz/trunk/applications/product/entitydef/entitymodel.xml
>>>>  ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml
>>>>  ofbiz/trunk/applications/product/servicedef/services.xml
>>>> 
>>>> Modified: ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java
>>>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>> ==============================================================================
>>>> --- ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java (original)
>>>> +++ ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java Mon Dec  6 08:05:44 2010
>>>> @@ -391,11 +391,38 @@ public class TaxAuthorityServices {
>>>>                   // 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,
>>>> +                            "productPriceTypeId", "DEFAULT_PRICE", "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");
>>>> -                taxAdjValue.set("taxAuthorityRateSeqId", taxAuthorityRateProduct.getString("taxAuthorityRateSeqId"));
>>>> -                taxAdjValue.set("amount", taxAmount);
>>>> +
>>>> +                if ("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("orderAdjustmentTypeId", "SALES_TAX");
>>>> +                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"));
>>>> @@ -421,62 +448,50 @@ public class TaxAuthorityServices {
>>>>               }
>>>> 
>>>>               adjustments.add(taxAdjValue);
>>>> -
>>>> -                // 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 get this to work with price rules changing the default price (right now only works where itemPrice==defaultPrice
>>>> -                // 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
>>>> -
>>>> -                //Debug.logInfo("=================== itemQuantity=" + itemQuantity, module);
>>>> -                //Debug.logInfo("=================== taxAuthPartyId=" + taxAuthPartyId, module);
>>>> -                //Debug.logInfo("=================== taxAuthGeoId=" + taxAuthGeoId, module);
>>>> -                if (product != null && itemQuantity != 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,
>>>> -                            "productPriceTypeId", "DEFAULT_PRICE", "productPricePurposeId", "PURCHASE");
>>>> -                    List<GenericValue> productPriceList = delegator.findByAnd("ProductPrice", priceFindMap, UtilMisc.toList("-fromDate"));
>>>> -                    productPriceList = EntityUtil.filterByDate(productPriceList, true);
>>>> -                    GenericValue productPrice = (productPriceList != null && productPriceList.size() > 0) ? productPriceList.get(0): null;
>>>> -                    //Debug.logInfo("=================== productId=" + product.getString("productId"), module);
>>>> -                    //Debug.logInfo("=================== productPrice=" + productPrice, module);
>>>> +
>>>> +                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
>>>> 
>>>> -                    if (productPrice != null && productPrice.getBigDecimal("priceWithTax") != null) {
>>>> -                        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);
>>>> -                        BigDecimal enteredTotalPriceWithTax = priceWithTax.multiply(itemQuantity);
>>>> -                        BigDecimal calcedTotalPriceWithTax = (baseSubtotal).add(baseTaxAmount);
>>>> -                        //Debug.logInfo("=================== priceWithTax=" + priceWithTax, module);
>>>> -                        //Debug.logInfo("=================== enteredTotalPriceWithTax=" + enteredTotalPriceWithTax, module);
>>>> -                        //Debug.logInfo("=================== calcedTotalPriceWithTax=" + calcedTotalPriceWithTax, module);
>>>> +                    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);
>>>> 
>>>> -                        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);
>>>> -                        }
>>>> +                        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);
>>>>                   }
>>>>               }
>>>>           }
>>>> 
>>>> Modified: ofbiz/trunk/applications/order/data/OrderTypeData.xml
>>>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/data/OrderTypeData.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>> ==============================================================================
>>>> --- ofbiz/trunk/applications/order/data/OrderTypeData.xml (original)
>>>> +++ ofbiz/trunk/applications/order/data/OrderTypeData.xml Mon Dec  6 08:05:44 2010
>>>> @@ -42,6 +42,7 @@ under the License.
>>>>   <OrderAdjustmentType description="Fee" hasTable="N" orderAdjustmentTypeId="FEE" parentTypeId=""/>
>>>>   <OrderAdjustmentType description="Miscellaneous Charges" hasTable="N" orderAdjustmentTypeId="MISCELLANEOUS_CHARGE" parentTypeId=""/>
>>>>   <OrderAdjustmentType description="Sales Tax" hasTable="N" orderAdjustmentTypeId="SALES_TAX" parentTypeId=""/>
>>>> +    <OrderAdjustmentType description="VAT Tax (not added to totals)" hasTable="N" orderAdjustmentTypeId="VAT_TAX" parentTypeId=""/>
>>>>   <OrderAdjustmentType description="VAT Price Correction" hasTable="N" orderAdjustmentTypeId="VAT_PRICE_CORRECT" parentTypeId=""/>
>>>>   <OrderAdjustmentType description="Shipping and Handling" hasTable="N" orderAdjustmentTypeId="SHIPPING_CHARGES" parentTypeId=""/>
>>>>   <OrderAdjustmentType description="Surcharge" hasTable="N" orderAdjustmentTypeId="SURCHARGE_ADJUSTMENT" parentTypeId=""/>
>>>> 
>>>> Modified: ofbiz/trunk/applications/order/entitydef/entitymodel.xml
>>>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/entitydef/entitymodel.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>> ==============================================================================
>>>> --- ofbiz/trunk/applications/order/entitydef/entitymodel.xml (original)
>>>> +++ ofbiz/trunk/applications/order/entitydef/entitymodel.xml Mon Dec  6 08:05:44 2010
>>>> @@ -59,6 +59,7 @@ under the License.
>>>>     <field name="description" type="description"></field>
>>>>     <field name="amount" type="currency-precise"></field>
>>>>     <field name="recurringAmount" type="currency-precise"></field>
>>>> +      <field name="amountAlreadyIncluded" type="currency-precise"><description>The amount here is already represented in the price, such as VAT taxes.</description></field>
>>>>     <field name="productPromoId" type="id"></field>
>>>>     <field name="productPromoRuleId" type="id"></field>
>>>>     <field name="productPromoActionSeqId" type="id"></field>
>>>> 
>>>> Modified: ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
>>>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>> ==============================================================================
>>>> --- ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java (original)
>>>> +++ ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java Mon Dec  6 08:05:44 2010
>>>> @@ -2920,4 +2920,62 @@ public class OrderReadHelper {
>>>>      result.put("taxGrandTotal", taxGrandTotal);
>>>>      return result;
>>>>  }
>>>> +
>>>> +   public static Map<String, Object> getOrderTaxByTaxAuthGeoAndPartyForDisplay(List<GenericValue> orderAdjustmentsOriginal) {
>>>> +       BigDecimal taxGrandTotal = BigDecimal.ZERO;
>>>> +       List<Map<String, Object>> taxByTaxAuthGeoAndPartyList = FastList.newInstance();
>>>> +       List<GenericValue> orderAdjustmentsToUse = FastList.newInstance();
>>>> +       if (UtilValidate.isNotEmpty(orderAdjustmentsOriginal)) {
>>>> +           // get orderAdjustment where orderAdjustmentTypeId is SALES_TAX.
>>>> +           orderAdjustmentsToUse.addAll(EntityUtil.filterByAnd(orderAdjustmentsOriginal, UtilMisc.toMap("orderAdjustmentTypeId", "SALES_TAX")));
>>>> +           orderAdjustmentsToUse.addAll(EntityUtil.filterByAnd(orderAdjustmentsOriginal, UtilMisc.toMap("orderAdjustmentTypeId", "VAT_TAX")));
>>>> +           orderAdjustmentsToUse = EntityUtil.orderBy(orderAdjustmentsToUse, UtilMisc.toList("taxAuthGeoId","taxAuthPartyId"));
>>>> +
>>>> +           // get the list of all distinct taxAuthGeoId and taxAuthPartyId. It is for getting the number of taxAuthGeo and taxAuthPartyId in adjustments.
>>>> +           List<String> distinctTaxAuthGeoIdList = EntityUtil.getFieldListFromEntityList(orderAdjustmentsToUse, "taxAuthGeoId", true);
>>>> +           List<String> distinctTaxAuthPartyIdList = EntityUtil.getFieldListFromEntityList(orderAdjustmentsToUse, "taxAuthPartyId", true);
>>>> +
>>>> +           // Keep a list of amount that have been added to make sure none are missed (if taxAuth* information is missing)
>>>> +           List<GenericValue> processedAdjustments = FastList.newInstance();
>>>> +           // For each taxAuthGeoId get and add amount from orderAdjustment
>>>> +           for (String taxAuthGeoId : distinctTaxAuthGeoIdList) {
>>>> +               for (String taxAuthPartyId : distinctTaxAuthPartyIdList) {
>>>> +                   //get all records for orderAdjustments filtered by taxAuthGeoId and taxAurhPartyId
>>>> +                   List<GenericValue> orderAdjByTaxAuthGeoAndPartyIds = EntityUtil.filterByAnd(orderAdjustmentsToUse, UtilMisc.toMap("taxAuthGeoId", taxAuthGeoId, "taxAuthPartyId", taxAuthPartyId));
>>>> +                   if (UtilValidate.isNotEmpty(orderAdjByTaxAuthGeoAndPartyIds)) {
>>>> +                       BigDecimal totalAmount = BigDecimal.ZERO;
>>>> +                       //Now for each orderAdjustment record get and add amount.
>>>> +                       for (GenericValue orderAdjustment : orderAdjByTaxAuthGeoAndPartyIds) {
>>>> +                           BigDecimal amount = orderAdjustment.getBigDecimal("amount");
>>>> +                           if (amount != null) {
>>>> +                               totalAmount = totalAmount.add(amount);
>>>> +                           }
>>>> +                           if ("VAT_TAX".equals(orderAdjustment.getString("orderAdjustmentTypeId")) &&
>>>> +                                   orderAdjustment.get("amountAlreadyIncluded") != null) {
>>>> +                               // this is the only case where the VAT_TAX amountAlreadyIncluded should be added in, and should just be for display and not to calculate the order grandTotal
>>>> +                               totalAmount = totalAmount.add(orderAdjustment.getBigDecimal("amountAlreadyIncluded"));
>>>> +                           }
>>>> +                           totalAmount = totalAmount.setScale(taxCalcScale, taxRounding);
>>>> +                           processedAdjustments.add(orderAdjustment);
>>>> +                       }
>>>> +                       totalAmount = totalAmount.setScale(taxFinalScale, taxRounding);
>>>> +                       taxByTaxAuthGeoAndPartyList.add(UtilMisc.<String, Object>toMap("taxAuthPartyId", taxAuthPartyId, "taxAuthGeoId", taxAuthGeoId, "totalAmount", totalAmount));
>>>> +                       taxGrandTotal = taxGrandTotal.add(totalAmount);
>>>> +                   }
>>>> +               }
>>>> +           }
>>>> +           // Process any adjustments that got missed
>>>> +           List<GenericValue> missedAdjustments = FastList.newInstance();
>>>> +           missedAdjustments.addAll(orderAdjustmentsToUse);
>>>> +           missedAdjustments.removeAll(processedAdjustments);
>>>> +           for (GenericValue orderAdjustment : missedAdjustments) {
>>>> +               taxGrandTotal = taxGrandTotal.add(orderAdjustment.getBigDecimal("amount").setScale(taxCalcScale, taxRounding));
>>>> +           }
>>>> +           taxGrandTotal = taxGrandTotal.setScale(taxFinalScale, taxRounding);
>>>> +       }
>>>> +       Map<String, Object> result = FastMap.newInstance();
>>>> +       result.put("taxByTaxAuthGeoAndPartyList", taxByTaxAuthGeoAndPartyList);
>>>> +       result.put("taxGrandTotal", taxGrandTotal);
>>>> +       return result;
>>>> +   }
>>>> }
>>>> 
>>>> Modified: ofbiz/trunk/applications/product/entitydef/entitymodel.xml
>>>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/entitydef/entitymodel.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>> ==============================================================================
>>>> --- ofbiz/trunk/applications/product/entitydef/entitymodel.xml (original)
>>>> +++ ofbiz/trunk/applications/product/entitydef/entitymodel.xml Mon Dec  6 08:05:44 2010
>>>> @@ -2375,11 +2375,13 @@ under the License.
>>>>     <field name="price" type="currency-precise"></field>
>>>>     <field name="termUomId" type="id"><description>Mainly used for recurring and usage prices to specify a time/freq measure, or a usage unit measure (bits, minutes, etc)</description></field>
>>>>     <field name="customPriceCalcService" type="id"><description>Points to a CustomMethod used to specify a service for the calculation of the unit price of the product (NOTE: a better name for this field might be priceCalcCustomMethodId)</description></field>
>>>> -      <field name="priceWithTax" type="currency-precise"/>
>>>> +      <field name="priceWithoutTax" type="currency-precise"><description>Always without tax if populated, regardless of if price does or does not include tax.</description></field>
>>>> +      <field name="priceWithTax" type="currency-precise"><description>Always with tax if populated, regardless of if price does or does not include tax.</description></field>
>>>>     <field name="taxAmount" type="currency-precise"/>
>>>>     <field name="taxPercentage" type="fixed-point"/>
>>>>     <field name="taxAuthPartyId" type="id-ne"/>
>>>>     <field name="taxAuthGeoId" type="id-ne"/>
>>>> +      <field name="taxInPrice" type="indicator"><description>If Y the price field has tax included for the given taxAuthPartyId/taxAuthGeoId at the taxPercentage.</description></field>
>>>>     <field name="createdDate" type="date-time"></field>
>>>>     <field name="createdByUserLogin" type="id-vlong"></field>
>>>>     <field name="lastModifiedDate" type="date-time"></field>
>>>> 
>>>> Modified: ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml
>>>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>> ==============================================================================
>>>> --- ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml (original)
>>>> +++ ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml Mon Dec  6 08:05:44 2010
>>>> @@ -106,6 +106,7 @@ under the License.
>>>>           </condition>
>>>>           <then>
>>>>               <set field="parameters.priceWithTax" from-field="parameters.price"/>
>>>> +
>>>>               <!-- if taxPercentage not passed in look it up based on taxAuthGeoId and taxAuthPartyId -->
>>>>               <if-empty field="parameters.taxPercentage">
>>>>                   <!-- we only have basic data to constrain by here, so assume that if it is a VAT tax setup it should be pretty simple -->
>>>> @@ -127,6 +128,7 @@ under the License.
>>>>                   <check-errors/>
>>>>               </if-empty>
>>>> 
>>>> +                <!-- in short the formula is: taxAmount = priceWithTax - (priceWithTax/(1+taxPercentage/100)) -->
>>>>               <calculate field="parameters.taxAmount" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>>>>                   <calcop operator="subtract">
>>>>                       <calcop operator="get" field="parameters.priceWithTax"/>
>>>> @@ -142,12 +144,23 @@ under the License.
>>>>                       </calcop>
>>>>                   </calcop>
>>>>               </calculate>
>>>> -                <calculate field="parameters.price" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>>>> +
>>>> +                <calculate field="parameters.priceWithoutTax" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>>>>                   <calcop operator="subtract">
>>>>                       <calcop operator="get" field="parameters.priceWithTax"/>
>>>>                       <calcop operator="get" field="parameters.taxAmount"></calcop>
>>>>                   </calcop>
>>>>               </calculate>
>>>> +
>>>> +                <if-compare field="parameters.taxInPrice" operator="equals" value="Y">
>>>> +                    <!-- the price passed in has tax included, and we want to store it with tax included -->
>>>> +                    <set field="parameters.price" from-field="parameters.priceWithTax"/>
>>>> +
>>>> +                    <else>
>>>> +                        <!-- the price passed in has tax included, but we want to store it without tax included -->
>>>> +                        <set field="parameters.price" from-field="parameters.priceWithoutTax"/>
>>>> +                    </else>
>>>> +                </if-compare>
>>>>           </then>
>>>>       </if>
>>>>   </simple-method>
>>>> 
>>>> Modified: ofbiz/trunk/applications/product/servicedef/services.xml
>>>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/servicedef/services.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>>> ==============================================================================
>>>> --- ofbiz/trunk/applications/product/servicedef/services.xml (original)
>>>> +++ ofbiz/trunk/applications/product/servicedef/services.xml Mon Dec  6 08:05:44 2010
>>>> @@ -242,12 +242,15 @@ under the License.
>>>>       <description>
>>>>           Create an ProductPrice.
>>>>           Price is always stored without tax.
>>>> -            If a taxAuthGeoId and taxAuthPartyId are (or taxAuthCombinedId is) passed in then the price will be considered a price
>>>> -                with tax included and the tax will be removed before storing to the database
>>>> -                (the priceWithTax, taxAmount, and taxPercentage fields will also be populated).
>>>> +            If taxAuthGeoId and taxAuthPartyId are (or taxAuthCombinedId is) passed in then the price will be considered a price
>>>> +            with tax included (the priceWithoutTax, priceWithTax, taxAmount, and taxPercentage fields will also be populated).
>>>> +            If the taxInPrice field is 'Y' then the price field will be left with the price included (price will be equal to priceWithTax),
>>>> +            otherwise tax will be removed from the passed in price and the price field will be equal to the priceWithoutTax field.
>>>> +            If taxAuthGeoId or taxAuthPartyId empty, and taxAuthCombinedId is empty, then the taxInPrice field will be ignored.
>>>>       </description>
>>>>       <auto-attributes include="pk" mode="IN" optional="false"/>
>>>>       <auto-attributes include="nonpk" mode="IN" optional="true">
>>>> +            <exclude field-name="priceWithoutTax"/>
>>>>           <exclude field-name="priceWithTax"/>
>>>>           <exclude field-name="taxAmount"/>
>>>>           <exclude field-name="createdDate"/>
>>>> @@ -264,6 +267,7 @@ under the License.
>>>>       <description>Update an ProductPrice</description>
>>>>       <auto-attributes include="pk" mode="IN" optional="false"/>
>>>>       <auto-attributes include="nonpk" mode="IN" optional="true">
>>>> +            <exclude field-name="priceWithoutTax"/>
>>>>           <exclude field-name="priceWithTax"/>
>>>>           <exclude field-name="taxAmount"/>
>>>>           <exclude field-name="createdDate"/>
>>>> 
>>> 
>>> 
> 
> 


Re: svn commit: r1042542 - in /ofbiz/trunk/applications: accounting/src/org/ofbiz/accounting/tax/ order/data/ order/entitydef/ order/src/org/ofbiz/order/order/ product/entitydef/ product/script/org/ofbiz/product/price/ product/servicedef/

Posted by Jacques Le Roux <ja...@les7arts.com>.
If there are no plans to prevent the use of the new fields for the other types Price Types than Default, I'd like latter to add a 
small js script to handle it. Of course being able to use it with Price Rules and Promo would be great.

Also what for is used taxAuthCombinedId? I don't see an use at the UI level.

Thanks

Jacques


From: "David E Jones" <de...@me.com>
> I commented before that my previous efforts only made calculations more accurate, and those efforts made it clear that the only 
> way to be sure is to calculate things based on prices with tax included.
>
> So, now OFBiz has low-level support for prices with tax included and calculating VAT instead of sales tax with adjustments that 
> represent amounts already included in the prices.
>
> Note that this does not include any UI changes, and so unless changes are done to allow entry of prices with tax included and to 
> show the VAT_TAX adjustments (specifically the new amountAlreadyIncluded field, which was added so that it wouldn't interfere with 
> the current amount field).
>
> -David
>
>
> On Dec 6, 2010, at 8:39 AM, Jacques Le Roux wrote:
>
>> Hi David,
>>
>> Interesting, what decided you to finally shift your ground?
>>
>> Is it still true that only Default Price can be used, and if yes is there any blocking reasons?
>>
>> Thanks
>>
>> Jacques
>>
>> From: <jo...@apache.org>
>>> Author: jonesde
>>> Date: Mon Dec  6 08:05:44 2010
>>> New Revision: 1042542
>>>
>>> URL: http://svn.apache.org/viewvc?rev=1042542&view=rev
>>> Log:
>>> Implemented alternative way of saving ProductPrices with tax included in the price, and then calculating VAT tax as an exclusive 
>>> instead of inclusive amount that goes on a new field on OrderAdjustment; also added a method to OrderReadHelper to get tax for 
>>> display; there are various changes to service descriptions and entity fields to describe these changes
>>>
>>> Modified:
>>>   ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java
>>>   ofbiz/trunk/applications/order/data/OrderTypeData.xml
>>>   ofbiz/trunk/applications/order/entitydef/entitymodel.xml
>>>   ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
>>>   ofbiz/trunk/applications/product/entitydef/entitymodel.xml
>>>   ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml
>>>   ofbiz/trunk/applications/product/servicedef/services.xml
>>>
>>> Modified: ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java
>>> URL: 
>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java?rev=1042542&r1=1042541&r2=1042542&view=diff
>>> ==============================================================================
>>> --- ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java (original)
>>> +++ ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java Mon Dec  6 08:05:44 2010
>>> @@ -391,11 +391,38 @@ public class TaxAuthorityServices {
>>>                    // 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,
>>> +                            "productPriceTypeId", "DEFAULT_PRICE", "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");
>>> -                taxAdjValue.set("taxAuthorityRateSeqId", taxAuthorityRateProduct.getString("taxAuthorityRateSeqId"));
>>> -                taxAdjValue.set("amount", taxAmount);
>>> +
>>> +                if ("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("orderAdjustmentTypeId", "SALES_TAX");
>>> +                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"));
>>> @@ -421,62 +448,50 @@ public class TaxAuthorityServices {
>>>                }
>>>
>>>                adjustments.add(taxAdjValue);
>>> -
>>> -                // 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 get this to work with price rules changing the default price (right now only works where 
>>> itemPrice==defaultPrice
>>> -                // 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
>>> -
>>> -                //Debug.logInfo("=================== itemQuantity=" + itemQuantity, module);
>>> -                //Debug.logInfo("=================== taxAuthPartyId=" + taxAuthPartyId, module);
>>> -                //Debug.logInfo("=================== taxAuthGeoId=" + taxAuthGeoId, module);
>>> -                if (product != null && itemQuantity != 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,
>>> -                            "productPriceTypeId", "DEFAULT_PRICE", "productPricePurposeId", "PURCHASE");
>>> -                    List<GenericValue> productPriceList = delegator.findByAnd("ProductPrice", priceFindMap, 
>>> UtilMisc.toList("-fromDate"));
>>> -                    productPriceList = EntityUtil.filterByDate(productPriceList, true);
>>> -                    GenericValue productPrice = (productPriceList != null && productPriceList.size() > 0) ? 
>>> productPriceList.get(0): null;
>>> -                    //Debug.logInfo("=================== productId=" + product.getString("productId"), module);
>>> -                    //Debug.logInfo("=================== productPrice=" + productPrice, module);
>>> +
>>> +                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
>>>
>>> -                    if (productPrice != null && productPrice.getBigDecimal("priceWithTax") != null) {
>>> -                        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);
>>> -                        BigDecimal enteredTotalPriceWithTax = priceWithTax.multiply(itemQuantity);
>>> -                        BigDecimal calcedTotalPriceWithTax = (baseSubtotal).add(baseTaxAmount);
>>> -                        //Debug.logInfo("=================== priceWithTax=" + priceWithTax, module);
>>> -                        //Debug.logInfo("=================== enteredTotalPriceWithTax=" + enteredTotalPriceWithTax, module);
>>> -                        //Debug.logInfo("=================== calcedTotalPriceWithTax=" + calcedTotalPriceWithTax, module);
>>> +                    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);
>>>
>>> -                        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);
>>> -                        }
>>> +                        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);
>>>                    }
>>>                }
>>>            }
>>>
>>> Modified: ofbiz/trunk/applications/order/data/OrderTypeData.xml
>>> URL: 
>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/data/OrderTypeData.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>> ==============================================================================
>>> --- ofbiz/trunk/applications/order/data/OrderTypeData.xml (original)
>>> +++ ofbiz/trunk/applications/order/data/OrderTypeData.xml Mon Dec  6 08:05:44 2010
>>> @@ -42,6 +42,7 @@ under the License.
>>>    <OrderAdjustmentType description="Fee" hasTable="N" orderAdjustmentTypeId="FEE" parentTypeId=""/>
>>>    <OrderAdjustmentType description="Miscellaneous Charges" hasTable="N" orderAdjustmentTypeId="MISCELLANEOUS_CHARGE" 
>>> parentTypeId=""/>
>>>    <OrderAdjustmentType description="Sales Tax" hasTable="N" orderAdjustmentTypeId="SALES_TAX" parentTypeId=""/>
>>> +    <OrderAdjustmentType description="VAT Tax (not added to totals)" hasTable="N" orderAdjustmentTypeId="VAT_TAX" 
>>> parentTypeId=""/>
>>>    <OrderAdjustmentType description="VAT Price Correction" hasTable="N" orderAdjustmentTypeId="VAT_PRICE_CORRECT" 
>>> parentTypeId=""/>
>>>    <OrderAdjustmentType description="Shipping and Handling" hasTable="N" orderAdjustmentTypeId="SHIPPING_CHARGES" 
>>> parentTypeId=""/>
>>>    <OrderAdjustmentType description="Surcharge" hasTable="N" orderAdjustmentTypeId="SURCHARGE_ADJUSTMENT" parentTypeId=""/>
>>>
>>> Modified: ofbiz/trunk/applications/order/entitydef/entitymodel.xml
>>> URL: 
>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/entitydef/entitymodel.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>> ==============================================================================
>>> --- ofbiz/trunk/applications/order/entitydef/entitymodel.xml (original)
>>> +++ ofbiz/trunk/applications/order/entitydef/entitymodel.xml Mon Dec  6 08:05:44 2010
>>> @@ -59,6 +59,7 @@ under the License.
>>>      <field name="description" type="description"></field>
>>>      <field name="amount" type="currency-precise"></field>
>>>      <field name="recurringAmount" type="currency-precise"></field>
>>> +      <field name="amountAlreadyIncluded" type="currency-precise"><description>The amount here is already represented in the 
>>> price, such as VAT taxes.</description></field>
>>>      <field name="productPromoId" type="id"></field>
>>>      <field name="productPromoRuleId" type="id"></field>
>>>      <field name="productPromoActionSeqId" type="id"></field>
>>>
>>> Modified: ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
>>> URL: 
>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java?rev=1042542&r1=1042541&r2=1042542&view=diff
>>> ==============================================================================
>>> --- ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java (original)
>>> +++ ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java Mon Dec  6 08:05:44 2010
>>> @@ -2920,4 +2920,62 @@ public class OrderReadHelper {
>>>       result.put("taxGrandTotal", taxGrandTotal);
>>>       return result;
>>>   }
>>> +
>>> +   public static Map<String, Object> getOrderTaxByTaxAuthGeoAndPartyForDisplay(List<GenericValue> orderAdjustmentsOriginal) {
>>> +       BigDecimal taxGrandTotal = BigDecimal.ZERO;
>>> +       List<Map<String, Object>> taxByTaxAuthGeoAndPartyList = FastList.newInstance();
>>> +       List<GenericValue> orderAdjustmentsToUse = FastList.newInstance();
>>> +       if (UtilValidate.isNotEmpty(orderAdjustmentsOriginal)) {
>>> +           // get orderAdjustment where orderAdjustmentTypeId is SALES_TAX.
>>> +           orderAdjustmentsToUse.addAll(EntityUtil.filterByAnd(orderAdjustmentsOriginal, 
>>> UtilMisc.toMap("orderAdjustmentTypeId", "SALES_TAX")));
>>> +           orderAdjustmentsToUse.addAll(EntityUtil.filterByAnd(orderAdjustmentsOriginal, 
>>> UtilMisc.toMap("orderAdjustmentTypeId", "VAT_TAX")));
>>> +           orderAdjustmentsToUse = EntityUtil.orderBy(orderAdjustmentsToUse, UtilMisc.toList("taxAuthGeoId","taxAuthPartyId"));
>>> +
>>> +           // get the list of all distinct taxAuthGeoId and taxAuthPartyId. It is for getting the number of taxAuthGeo and 
>>> taxAuthPartyId in adjustments.
>>> +           List<String> distinctTaxAuthGeoIdList = EntityUtil.getFieldListFromEntityList(orderAdjustmentsToUse, "taxAuthGeoId", 
>>> true);
>>> +           List<String> distinctTaxAuthPartyIdList = EntityUtil.getFieldListFromEntityList(orderAdjustmentsToUse, 
>>> "taxAuthPartyId", true);
>>> +
>>> +           // Keep a list of amount that have been added to make sure none are missed (if taxAuth* information is missing)
>>> +           List<GenericValue> processedAdjustments = FastList.newInstance();
>>> +           // For each taxAuthGeoId get and add amount from orderAdjustment
>>> +           for (String taxAuthGeoId : distinctTaxAuthGeoIdList) {
>>> +               for (String taxAuthPartyId : distinctTaxAuthPartyIdList) {
>>> +                   //get all records for orderAdjustments filtered by taxAuthGeoId and taxAurhPartyId
>>> +                   List<GenericValue> orderAdjByTaxAuthGeoAndPartyIds = EntityUtil.filterByAnd(orderAdjustmentsToUse, 
>>> UtilMisc.toMap("taxAuthGeoId", taxAuthGeoId, "taxAuthPartyId", taxAuthPartyId));
>>> +                   if (UtilValidate.isNotEmpty(orderAdjByTaxAuthGeoAndPartyIds)) {
>>> +                       BigDecimal totalAmount = BigDecimal.ZERO;
>>> +                       //Now for each orderAdjustment record get and add amount.
>>> +                       for (GenericValue orderAdjustment : orderAdjByTaxAuthGeoAndPartyIds) {
>>> +                           BigDecimal amount = orderAdjustment.getBigDecimal("amount");
>>> +                           if (amount != null) {
>>> +                               totalAmount = totalAmount.add(amount);
>>> +                           }
>>> +                           if ("VAT_TAX".equals(orderAdjustment.getString("orderAdjustmentTypeId")) &&
>>> +                                   orderAdjustment.get("amountAlreadyIncluded") != null) {
>>> +                               // this is the only case where the VAT_TAX amountAlreadyIncluded should be added in, and should 
>>> just be for display and not to calculate the order grandTotal
>>> +                               totalAmount = totalAmount.add(orderAdjustment.getBigDecimal("amountAlreadyIncluded"));
>>> +                           }
>>> +                           totalAmount = totalAmount.setScale(taxCalcScale, taxRounding);
>>> +                           processedAdjustments.add(orderAdjustment);
>>> +                       }
>>> +                       totalAmount = totalAmount.setScale(taxFinalScale, taxRounding);
>>> +                       taxByTaxAuthGeoAndPartyList.add(UtilMisc.<String, Object>toMap("taxAuthPartyId", taxAuthPartyId, 
>>> "taxAuthGeoId", taxAuthGeoId, "totalAmount", totalAmount));
>>> +                       taxGrandTotal = taxGrandTotal.add(totalAmount);
>>> +                   }
>>> +               }
>>> +           }
>>> +           // Process any adjustments that got missed
>>> +           List<GenericValue> missedAdjustments = FastList.newInstance();
>>> +           missedAdjustments.addAll(orderAdjustmentsToUse);
>>> +           missedAdjustments.removeAll(processedAdjustments);
>>> +           for (GenericValue orderAdjustment : missedAdjustments) {
>>> +               taxGrandTotal = taxGrandTotal.add(orderAdjustment.getBigDecimal("amount").setScale(taxCalcScale, taxRounding));
>>> +           }
>>> +           taxGrandTotal = taxGrandTotal.setScale(taxFinalScale, taxRounding);
>>> +       }
>>> +       Map<String, Object> result = FastMap.newInstance();
>>> +       result.put("taxByTaxAuthGeoAndPartyList", taxByTaxAuthGeoAndPartyList);
>>> +       result.put("taxGrandTotal", taxGrandTotal);
>>> +       return result;
>>> +   }
>>> }
>>>
>>> Modified: ofbiz/trunk/applications/product/entitydef/entitymodel.xml
>>> URL: 
>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/entitydef/entitymodel.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>> ==============================================================================
>>> --- ofbiz/trunk/applications/product/entitydef/entitymodel.xml (original)
>>> +++ ofbiz/trunk/applications/product/entitydef/entitymodel.xml Mon Dec  6 08:05:44 2010
>>> @@ -2375,11 +2375,13 @@ under the License.
>>>      <field name="price" type="currency-precise"></field>
>>>      <field name="termUomId" type="id"><description>Mainly used for recurring and usage prices to specify a time/freq measure, 
>>> or a usage unit measure (bits, minutes, etc)</description></field>
>>>      <field name="customPriceCalcService" type="id"><description>Points to a CustomMethod used to specify a service for the 
>>> calculation of the unit price of the product (NOTE: a better name for this field might be 
>>> priceCalcCustomMethodId)</description></field>
>>> -      <field name="priceWithTax" type="currency-precise"/>
>>> +      <field name="priceWithoutTax" type="currency-precise"><description>Always without tax if populated, regardless of if 
>>> price does or does not include tax.</description></field>
>>> +      <field name="priceWithTax" type="currency-precise"><description>Always with tax if populated, regardless of if price does 
>>> or does not include tax.</description></field>
>>>      <field name="taxAmount" type="currency-precise"/>
>>>      <field name="taxPercentage" type="fixed-point"/>
>>>      <field name="taxAuthPartyId" type="id-ne"/>
>>>      <field name="taxAuthGeoId" type="id-ne"/>
>>> +      <field name="taxInPrice" type="indicator"><description>If Y the price field has tax included for the given 
>>> taxAuthPartyId/taxAuthGeoId at the taxPercentage.</description></field>
>>>      <field name="createdDate" type="date-time"></field>
>>>      <field name="createdByUserLogin" type="id-vlong"></field>
>>>      <field name="lastModifiedDate" type="date-time"></field>
>>>
>>> Modified: ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml
>>> URL: 
>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>> ==============================================================================
>>> --- ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml (original)
>>> +++ ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml Mon Dec  6 08:05:44 2010
>>> @@ -106,6 +106,7 @@ under the License.
>>>            </condition>
>>>            <then>
>>>                <set field="parameters.priceWithTax" from-field="parameters.price"/>
>>> +
>>>                <!-- if taxPercentage not passed in look it up based on taxAuthGeoId and taxAuthPartyId -->
>>>                <if-empty field="parameters.taxPercentage">
>>>                    <!-- we only have basic data to constrain by here, so assume that if it is a VAT tax setup it should be 
>>> pretty simple -->
>>> @@ -127,6 +128,7 @@ under the License.
>>>                    <check-errors/>
>>>                </if-empty>
>>>
>>> +                <!-- in short the formula is: taxAmount = priceWithTax - (priceWithTax/(1+taxPercentage/100)) -->
>>>                <calculate field="parameters.taxAmount" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>>>                    <calcop operator="subtract">
>>>                        <calcop operator="get" field="parameters.priceWithTax"/>
>>> @@ -142,12 +144,23 @@ under the License.
>>>                        </calcop>
>>>                    </calcop>
>>>                </calculate>
>>> -                <calculate field="parameters.price" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>>> +
>>> +                <calculate field="parameters.priceWithoutTax" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>>>                    <calcop operator="subtract">
>>>                        <calcop operator="get" field="parameters.priceWithTax"/>
>>>                        <calcop operator="get" field="parameters.taxAmount"></calcop>
>>>                    </calcop>
>>>                </calculate>
>>> +
>>> +                <if-compare field="parameters.taxInPrice" operator="equals" value="Y">
>>> +                    <!-- the price passed in has tax included, and we want to store it with tax included -->
>>> +                    <set field="parameters.price" from-field="parameters.priceWithTax"/>
>>> +
>>> +                    <else>
>>> +                        <!-- the price passed in has tax included, but we want to store it without tax included -->
>>> +                        <set field="parameters.price" from-field="parameters.priceWithoutTax"/>
>>> +                    </else>
>>> +                </if-compare>
>>>            </then>
>>>        </if>
>>>    </simple-method>
>>>
>>> Modified: ofbiz/trunk/applications/product/servicedef/services.xml
>>> URL: 
>>> http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/servicedef/services.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>>> ==============================================================================
>>> --- ofbiz/trunk/applications/product/servicedef/services.xml (original)
>>> +++ ofbiz/trunk/applications/product/servicedef/services.xml Mon Dec  6 08:05:44 2010
>>> @@ -242,12 +242,15 @@ under the License.
>>>        <description>
>>>            Create an ProductPrice.
>>>            Price is always stored without tax.
>>> -            If a taxAuthGeoId and taxAuthPartyId are (or taxAuthCombinedId is) passed in then the price will be considered a 
>>> price
>>> -                with tax included and the tax will be removed before storing to the database
>>> -                (the priceWithTax, taxAmount, and taxPercentage fields will also be populated).
>>> +            If taxAuthGeoId and taxAuthPartyId are (or taxAuthCombinedId is) passed in then the price will be considered a 
>>> price
>>> +            with tax included (the priceWithoutTax, priceWithTax, taxAmount, and taxPercentage fields will also be populated).
>>> +            If the taxInPrice field is 'Y' then the price field will be left with the price included (price will be equal to 
>>> priceWithTax),
>>> +            otherwise tax will be removed from the passed in price and the price field will be equal to the priceWithoutTax 
>>> field.
>>> +            If taxAuthGeoId or taxAuthPartyId empty, and taxAuthCombinedId is empty, then the taxInPrice field will be ignored.
>>>        </description>
>>>        <auto-attributes include="pk" mode="IN" optional="false"/>
>>>        <auto-attributes include="nonpk" mode="IN" optional="true">
>>> +            <exclude field-name="priceWithoutTax"/>
>>>            <exclude field-name="priceWithTax"/>
>>>            <exclude field-name="taxAmount"/>
>>>            <exclude field-name="createdDate"/>
>>> @@ -264,6 +267,7 @@ under the License.
>>>        <description>Update an ProductPrice</description>
>>>        <auto-attributes include="pk" mode="IN" optional="false"/>
>>>        <auto-attributes include="nonpk" mode="IN" optional="true">
>>> +            <exclude field-name="priceWithoutTax"/>
>>>            <exclude field-name="priceWithTax"/>
>>>            <exclude field-name="taxAmount"/>
>>>            <exclude field-name="createdDate"/>
>>>
>>
>>
> 



Re: svn commit: r1042542 - in /ofbiz/trunk/applications: accounting/src/org/ofbiz/accounting/tax/ order/data/ order/entitydef/ order/src/org/ofbiz/order/order/ product/entitydef/ product/script/org/ofbiz/product/price/ product/servicedef/

Posted by David E Jones <de...@me.com>.
I commented before that my previous efforts only made calculations more accurate, and those efforts made it clear that the only way to be sure is to calculate things based on prices with tax included.

So, now OFBiz has low-level support for prices with tax included and calculating VAT instead of sales tax with adjustments that represent amounts already included in the prices.

Note that this does not include any UI changes, and so unless changes are done to allow entry of prices with tax included and to show the VAT_TAX adjustments (specifically the new amountAlreadyIncluded field, which was added so that it wouldn't interfere with the current amount field).

-David


On Dec 6, 2010, at 8:39 AM, Jacques Le Roux wrote:

> Hi David,
> 
> Interesting, what decided you to finally shift your ground?
> 
> Is it still true that only Default Price can be used, and if yes is there any blocking reasons?
> 
> Thanks
> 
> Jacques
> 
> From: <jo...@apache.org>
>> Author: jonesde
>> Date: Mon Dec  6 08:05:44 2010
>> New Revision: 1042542
>> 
>> URL: http://svn.apache.org/viewvc?rev=1042542&view=rev
>> Log:
>> Implemented alternative way of saving ProductPrices with tax included in the price, and then calculating VAT tax as an exclusive instead of inclusive amount that goes on a new field on OrderAdjustment; also added a method to OrderReadHelper to get tax for display; there are various changes to service descriptions and entity fields to describe these changes
>> 
>> Modified:
>>   ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java
>>   ofbiz/trunk/applications/order/data/OrderTypeData.xml
>>   ofbiz/trunk/applications/order/entitydef/entitymodel.xml
>>   ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
>>   ofbiz/trunk/applications/product/entitydef/entitymodel.xml
>>   ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml
>>   ofbiz/trunk/applications/product/servicedef/services.xml
>> 
>> Modified: ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java
>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java?rev=1042542&r1=1042541&r2=1042542&view=diff
>> ==============================================================================
>> --- ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java (original)
>> +++ ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java Mon Dec  6 08:05:44 2010
>> @@ -391,11 +391,38 @@ public class TaxAuthorityServices {
>>                    // 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,
>> +                            "productPriceTypeId", "DEFAULT_PRICE", "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");
>> -                taxAdjValue.set("taxAuthorityRateSeqId", taxAuthorityRateProduct.getString("taxAuthorityRateSeqId"));
>> -                taxAdjValue.set("amount", taxAmount);
>> +
>> +                if ("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("orderAdjustmentTypeId", "SALES_TAX");
>> +                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"));
>> @@ -421,62 +448,50 @@ public class TaxAuthorityServices {
>>                }
>> 
>>                adjustments.add(taxAdjValue);
>> -
>> -                // 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 get this to work with price rules changing the default price (right now only works where itemPrice==defaultPrice
>> -                // 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
>> -
>> -                //Debug.logInfo("=================== itemQuantity=" + itemQuantity, module);
>> -                //Debug.logInfo("=================== taxAuthPartyId=" + taxAuthPartyId, module);
>> -                //Debug.logInfo("=================== taxAuthGeoId=" + taxAuthGeoId, module);
>> -                if (product != null && itemQuantity != 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,
>> -                            "productPriceTypeId", "DEFAULT_PRICE", "productPricePurposeId", "PURCHASE");
>> -                    List<GenericValue> productPriceList = delegator.findByAnd("ProductPrice", priceFindMap, UtilMisc.toList("-fromDate"));
>> -                    productPriceList = EntityUtil.filterByDate(productPriceList, true);
>> -                    GenericValue productPrice = (productPriceList != null && productPriceList.size() > 0) ? productPriceList.get(0): null;
>> -                    //Debug.logInfo("=================== productId=" + product.getString("productId"), module);
>> -                    //Debug.logInfo("=================== productPrice=" + productPrice, module);
>> +
>> +                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
>> 
>> -                    if (productPrice != null && productPrice.getBigDecimal("priceWithTax") != null) {
>> -                        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);
>> -                        BigDecimal enteredTotalPriceWithTax = priceWithTax.multiply(itemQuantity);
>> -                        BigDecimal calcedTotalPriceWithTax = (baseSubtotal).add(baseTaxAmount);
>> -                        //Debug.logInfo("=================== priceWithTax=" + priceWithTax, module);
>> -                        //Debug.logInfo("=================== enteredTotalPriceWithTax=" + enteredTotalPriceWithTax, module);
>> -                        //Debug.logInfo("=================== calcedTotalPriceWithTax=" + calcedTotalPriceWithTax, module);
>> +                    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);
>> 
>> -                        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);
>> -                        }
>> +                        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);
>>                    }
>>                }
>>            }
>> 
>> Modified: ofbiz/trunk/applications/order/data/OrderTypeData.xml
>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/data/OrderTypeData.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>> ==============================================================================
>> --- ofbiz/trunk/applications/order/data/OrderTypeData.xml (original)
>> +++ ofbiz/trunk/applications/order/data/OrderTypeData.xml Mon Dec  6 08:05:44 2010
>> @@ -42,6 +42,7 @@ under the License.
>>    <OrderAdjustmentType description="Fee" hasTable="N" orderAdjustmentTypeId="FEE" parentTypeId=""/>
>>    <OrderAdjustmentType description="Miscellaneous Charges" hasTable="N" orderAdjustmentTypeId="MISCELLANEOUS_CHARGE" parentTypeId=""/>
>>    <OrderAdjustmentType description="Sales Tax" hasTable="N" orderAdjustmentTypeId="SALES_TAX" parentTypeId=""/>
>> +    <OrderAdjustmentType description="VAT Tax (not added to totals)" hasTable="N" orderAdjustmentTypeId="VAT_TAX" parentTypeId=""/>
>>    <OrderAdjustmentType description="VAT Price Correction" hasTable="N" orderAdjustmentTypeId="VAT_PRICE_CORRECT" parentTypeId=""/>
>>    <OrderAdjustmentType description="Shipping and Handling" hasTable="N" orderAdjustmentTypeId="SHIPPING_CHARGES" parentTypeId=""/>
>>    <OrderAdjustmentType description="Surcharge" hasTable="N" orderAdjustmentTypeId="SURCHARGE_ADJUSTMENT" parentTypeId=""/>
>> 
>> Modified: ofbiz/trunk/applications/order/entitydef/entitymodel.xml
>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/entitydef/entitymodel.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>> ==============================================================================
>> --- ofbiz/trunk/applications/order/entitydef/entitymodel.xml (original)
>> +++ ofbiz/trunk/applications/order/entitydef/entitymodel.xml Mon Dec  6 08:05:44 2010
>> @@ -59,6 +59,7 @@ under the License.
>>      <field name="description" type="description"></field>
>>      <field name="amount" type="currency-precise"></field>
>>      <field name="recurringAmount" type="currency-precise"></field>
>> +      <field name="amountAlreadyIncluded" type="currency-precise"><description>The amount here is already represented in the price, such as VAT taxes.</description></field>
>>      <field name="productPromoId" type="id"></field>
>>      <field name="productPromoRuleId" type="id"></field>
>>      <field name="productPromoActionSeqId" type="id"></field>
>> 
>> Modified: ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java?rev=1042542&r1=1042541&r2=1042542&view=diff
>> ==============================================================================
>> --- ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java (original)
>> +++ ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java Mon Dec  6 08:05:44 2010
>> @@ -2920,4 +2920,62 @@ public class OrderReadHelper {
>>       result.put("taxGrandTotal", taxGrandTotal);
>>       return result;
>>   }
>> +
>> +   public static Map<String, Object> getOrderTaxByTaxAuthGeoAndPartyForDisplay(List<GenericValue> orderAdjustmentsOriginal) {
>> +       BigDecimal taxGrandTotal = BigDecimal.ZERO;
>> +       List<Map<String, Object>> taxByTaxAuthGeoAndPartyList = FastList.newInstance();
>> +       List<GenericValue> orderAdjustmentsToUse = FastList.newInstance();
>> +       if (UtilValidate.isNotEmpty(orderAdjustmentsOriginal)) {
>> +           // get orderAdjustment where orderAdjustmentTypeId is SALES_TAX.
>> +           orderAdjustmentsToUse.addAll(EntityUtil.filterByAnd(orderAdjustmentsOriginal, UtilMisc.toMap("orderAdjustmentTypeId", "SALES_TAX")));
>> +           orderAdjustmentsToUse.addAll(EntityUtil.filterByAnd(orderAdjustmentsOriginal, UtilMisc.toMap("orderAdjustmentTypeId", "VAT_TAX")));
>> +           orderAdjustmentsToUse = EntityUtil.orderBy(orderAdjustmentsToUse, UtilMisc.toList("taxAuthGeoId","taxAuthPartyId"));
>> +
>> +           // get the list of all distinct taxAuthGeoId and taxAuthPartyId. It is for getting the number of taxAuthGeo and taxAuthPartyId in adjustments.
>> +           List<String> distinctTaxAuthGeoIdList = EntityUtil.getFieldListFromEntityList(orderAdjustmentsToUse, "taxAuthGeoId", true);
>> +           List<String> distinctTaxAuthPartyIdList = EntityUtil.getFieldListFromEntityList(orderAdjustmentsToUse, "taxAuthPartyId", true);
>> +
>> +           // Keep a list of amount that have been added to make sure none are missed (if taxAuth* information is missing)
>> +           List<GenericValue> processedAdjustments = FastList.newInstance();
>> +           // For each taxAuthGeoId get and add amount from orderAdjustment
>> +           for (String taxAuthGeoId : distinctTaxAuthGeoIdList) {
>> +               for (String taxAuthPartyId : distinctTaxAuthPartyIdList) {
>> +                   //get all records for orderAdjustments filtered by taxAuthGeoId and taxAurhPartyId
>> +                   List<GenericValue> orderAdjByTaxAuthGeoAndPartyIds = EntityUtil.filterByAnd(orderAdjustmentsToUse, UtilMisc.toMap("taxAuthGeoId", taxAuthGeoId, "taxAuthPartyId", taxAuthPartyId));
>> +                   if (UtilValidate.isNotEmpty(orderAdjByTaxAuthGeoAndPartyIds)) {
>> +                       BigDecimal totalAmount = BigDecimal.ZERO;
>> +                       //Now for each orderAdjustment record get and add amount.
>> +                       for (GenericValue orderAdjustment : orderAdjByTaxAuthGeoAndPartyIds) {
>> +                           BigDecimal amount = orderAdjustment.getBigDecimal("amount");
>> +                           if (amount != null) {
>> +                               totalAmount = totalAmount.add(amount);
>> +                           }
>> +                           if ("VAT_TAX".equals(orderAdjustment.getString("orderAdjustmentTypeId")) &&
>> +                                   orderAdjustment.get("amountAlreadyIncluded") != null) {
>> +                               // this is the only case where the VAT_TAX amountAlreadyIncluded should be added in, and should just be for display and not to calculate the order grandTotal
>> +                               totalAmount = totalAmount.add(orderAdjustment.getBigDecimal("amountAlreadyIncluded"));
>> +                           }
>> +                           totalAmount = totalAmount.setScale(taxCalcScale, taxRounding);
>> +                           processedAdjustments.add(orderAdjustment);
>> +                       }
>> +                       totalAmount = totalAmount.setScale(taxFinalScale, taxRounding);
>> +                       taxByTaxAuthGeoAndPartyList.add(UtilMisc.<String, Object>toMap("taxAuthPartyId", taxAuthPartyId, "taxAuthGeoId", taxAuthGeoId, "totalAmount", totalAmount));
>> +                       taxGrandTotal = taxGrandTotal.add(totalAmount);
>> +                   }
>> +               }
>> +           }
>> +           // Process any adjustments that got missed
>> +           List<GenericValue> missedAdjustments = FastList.newInstance();
>> +           missedAdjustments.addAll(orderAdjustmentsToUse);
>> +           missedAdjustments.removeAll(processedAdjustments);
>> +           for (GenericValue orderAdjustment : missedAdjustments) {
>> +               taxGrandTotal = taxGrandTotal.add(orderAdjustment.getBigDecimal("amount").setScale(taxCalcScale, taxRounding));
>> +           }
>> +           taxGrandTotal = taxGrandTotal.setScale(taxFinalScale, taxRounding);
>> +       }
>> +       Map<String, Object> result = FastMap.newInstance();
>> +       result.put("taxByTaxAuthGeoAndPartyList", taxByTaxAuthGeoAndPartyList);
>> +       result.put("taxGrandTotal", taxGrandTotal);
>> +       return result;
>> +   }
>> }
>> 
>> Modified: ofbiz/trunk/applications/product/entitydef/entitymodel.xml
>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/entitydef/entitymodel.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>> ==============================================================================
>> --- ofbiz/trunk/applications/product/entitydef/entitymodel.xml (original)
>> +++ ofbiz/trunk/applications/product/entitydef/entitymodel.xml Mon Dec  6 08:05:44 2010
>> @@ -2375,11 +2375,13 @@ under the License.
>>      <field name="price" type="currency-precise"></field>
>>      <field name="termUomId" type="id"><description>Mainly used for recurring and usage prices to specify a time/freq measure, or a usage unit measure (bits, minutes, etc)</description></field>
>>      <field name="customPriceCalcService" type="id"><description>Points to a CustomMethod used to specify a service for the calculation of the unit price of the product (NOTE: a better name for this field might be priceCalcCustomMethodId)</description></field>
>> -      <field name="priceWithTax" type="currency-precise"/>
>> +      <field name="priceWithoutTax" type="currency-precise"><description>Always without tax if populated, regardless of if price does or does not include tax.</description></field>
>> +      <field name="priceWithTax" type="currency-precise"><description>Always with tax if populated, regardless of if price does or does not include tax.</description></field>
>>      <field name="taxAmount" type="currency-precise"/>
>>      <field name="taxPercentage" type="fixed-point"/>
>>      <field name="taxAuthPartyId" type="id-ne"/>
>>      <field name="taxAuthGeoId" type="id-ne"/>
>> +      <field name="taxInPrice" type="indicator"><description>If Y the price field has tax included for the given taxAuthPartyId/taxAuthGeoId at the taxPercentage.</description></field>
>>      <field name="createdDate" type="date-time"></field>
>>      <field name="createdByUserLogin" type="id-vlong"></field>
>>      <field name="lastModifiedDate" type="date-time"></field>
>> 
>> Modified: ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml
>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>> ==============================================================================
>> --- ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml (original)
>> +++ ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml Mon Dec  6 08:05:44 2010
>> @@ -106,6 +106,7 @@ under the License.
>>            </condition>
>>            <then>
>>                <set field="parameters.priceWithTax" from-field="parameters.price"/>
>> +
>>                <!-- if taxPercentage not passed in look it up based on taxAuthGeoId and taxAuthPartyId -->
>>                <if-empty field="parameters.taxPercentage">
>>                    <!-- we only have basic data to constrain by here, so assume that if it is a VAT tax setup it should be pretty simple -->
>> @@ -127,6 +128,7 @@ under the License.
>>                    <check-errors/>
>>                </if-empty>
>> 
>> +                <!-- in short the formula is: taxAmount = priceWithTax - (priceWithTax/(1+taxPercentage/100)) -->
>>                <calculate field="parameters.taxAmount" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>>                    <calcop operator="subtract">
>>                        <calcop operator="get" field="parameters.priceWithTax"/>
>> @@ -142,12 +144,23 @@ under the License.
>>                        </calcop>
>>                    </calcop>
>>                </calculate>
>> -                <calculate field="parameters.price" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>> +
>> +                <calculate field="parameters.priceWithoutTax" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>>                    <calcop operator="subtract">
>>                        <calcop operator="get" field="parameters.priceWithTax"/>
>>                        <calcop operator="get" field="parameters.taxAmount"></calcop>
>>                    </calcop>
>>                </calculate>
>> +
>> +                <if-compare field="parameters.taxInPrice" operator="equals" value="Y">
>> +                    <!-- the price passed in has tax included, and we want to store it with tax included -->
>> +                    <set field="parameters.price" from-field="parameters.priceWithTax"/>
>> +
>> +                    <else>
>> +                        <!-- the price passed in has tax included, but we want to store it without tax included -->
>> +                        <set field="parameters.price" from-field="parameters.priceWithoutTax"/>
>> +                    </else>
>> +                </if-compare>
>>            </then>
>>        </if>
>>    </simple-method>
>> 
>> Modified: ofbiz/trunk/applications/product/servicedef/services.xml
>> URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/servicedef/services.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
>> ==============================================================================
>> --- ofbiz/trunk/applications/product/servicedef/services.xml (original)
>> +++ ofbiz/trunk/applications/product/servicedef/services.xml Mon Dec  6 08:05:44 2010
>> @@ -242,12 +242,15 @@ under the License.
>>        <description>
>>            Create an ProductPrice.
>>            Price is always stored without tax.
>> -            If a taxAuthGeoId and taxAuthPartyId are (or taxAuthCombinedId is) passed in then the price will be considered a price
>> -                with tax included and the tax will be removed before storing to the database
>> -                (the priceWithTax, taxAmount, and taxPercentage fields will also be populated).
>> +            If taxAuthGeoId and taxAuthPartyId are (or taxAuthCombinedId is) passed in then the price will be considered a price
>> +            with tax included (the priceWithoutTax, priceWithTax, taxAmount, and taxPercentage fields will also be populated).
>> +            If the taxInPrice field is 'Y' then the price field will be left with the price included (price will be equal to priceWithTax),
>> +            otherwise tax will be removed from the passed in price and the price field will be equal to the priceWithoutTax field.
>> +            If taxAuthGeoId or taxAuthPartyId empty, and taxAuthCombinedId is empty, then the taxInPrice field will be ignored.
>>        </description>
>>        <auto-attributes include="pk" mode="IN" optional="false"/>
>>        <auto-attributes include="nonpk" mode="IN" optional="true">
>> +            <exclude field-name="priceWithoutTax"/>
>>            <exclude field-name="priceWithTax"/>
>>            <exclude field-name="taxAmount"/>
>>            <exclude field-name="createdDate"/>
>> @@ -264,6 +267,7 @@ under the License.
>>        <description>Update an ProductPrice</description>
>>        <auto-attributes include="pk" mode="IN" optional="false"/>
>>        <auto-attributes include="nonpk" mode="IN" optional="true">
>> +            <exclude field-name="priceWithoutTax"/>
>>            <exclude field-name="priceWithTax"/>
>>            <exclude field-name="taxAmount"/>
>>            <exclude field-name="createdDate"/>
>> 
> 
> 


Re: svn commit: r1042542 - in /ofbiz/trunk/applications: accounting/src/org/ofbiz/accounting/tax/ order/data/ order/entitydef/ order/src/org/ofbiz/order/order/ product/entitydef/ product/script/org/ofbiz/product/price/ product/servicedef/

Posted by Jacques Le Roux <ja...@les7arts.com>.
Hi David,

Interesting, what decided you to finally shift your ground?

Is it still true that only Default Price can be used, and if yes is there any blocking reasons?

Thanks

Jacques

From: <jo...@apache.org>
> Author: jonesde
> Date: Mon Dec  6 08:05:44 2010
> New Revision: 1042542
>
> URL: http://svn.apache.org/viewvc?rev=1042542&view=rev
> Log:
> Implemented alternative way of saving ProductPrices with tax included in the price, and then calculating VAT tax as an exclusive 
> instead of inclusive amount that goes on a new field on OrderAdjustment; also added a method to OrderReadHelper to get tax for 
> display; there are various changes to service descriptions and entity fields to describe these changes
>
> Modified:
>    ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java
>    ofbiz/trunk/applications/order/data/OrderTypeData.xml
>    ofbiz/trunk/applications/order/entitydef/entitymodel.xml
>    ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
>    ofbiz/trunk/applications/product/entitydef/entitymodel.xml
>    ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml
>    ofbiz/trunk/applications/product/servicedef/services.xml
>
> Modified: ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java
> URL: 
> http://svn.apache.org/viewvc/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java?rev=1042542&r1=1042541&r2=1042542&view=diff
> ==============================================================================
> --- ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java (original)
> +++ ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/tax/TaxAuthorityServices.java Mon Dec  6 08:05:44 2010
> @@ -391,11 +391,38 @@ public class TaxAuthorityServices {
>                     // 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,
> +                            "productPriceTypeId", "DEFAULT_PRICE", "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");
> -                taxAdjValue.set("taxAuthorityRateSeqId", taxAuthorityRateProduct.getString("taxAuthorityRateSeqId"));
> -                taxAdjValue.set("amount", taxAmount);
> +
> +                if ("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("orderAdjustmentTypeId", "SALES_TAX");
> +                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"));
> @@ -421,62 +448,50 @@ public class TaxAuthorityServices {
>                 }
>
>                 adjustments.add(taxAdjValue);
> -
> -                // 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 get this to work with price rules changing the default price (right now only works where 
> itemPrice==defaultPrice
> -                // 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
> -
> -                //Debug.logInfo("=================== itemQuantity=" + itemQuantity, module);
> -                //Debug.logInfo("=================== taxAuthPartyId=" + taxAuthPartyId, module);
> -                //Debug.logInfo("=================== taxAuthGeoId=" + taxAuthGeoId, module);
> -                if (product != null && itemQuantity != 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,
> -                            "productPriceTypeId", "DEFAULT_PRICE", "productPricePurposeId", "PURCHASE");
> -                    List<GenericValue> productPriceList = delegator.findByAnd("ProductPrice", priceFindMap, 
> UtilMisc.toList("-fromDate"));
> -                    productPriceList = EntityUtil.filterByDate(productPriceList, true);
> -                    GenericValue productPrice = (productPriceList != null && productPriceList.size() > 0) ? 
> productPriceList.get(0): null;
> -                    //Debug.logInfo("=================== productId=" + product.getString("productId"), module);
> -                    //Debug.logInfo("=================== productPrice=" + productPrice, module);
> +
> +                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
>
> -                    if (productPrice != null && productPrice.getBigDecimal("priceWithTax") != null) {
> -                        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);
> -                        BigDecimal enteredTotalPriceWithTax = priceWithTax.multiply(itemQuantity);
> -                        BigDecimal calcedTotalPriceWithTax = (baseSubtotal).add(baseTaxAmount);
> -                        //Debug.logInfo("=================== priceWithTax=" + priceWithTax, module);
> -                        //Debug.logInfo("=================== enteredTotalPriceWithTax=" + enteredTotalPriceWithTax, module);
> -                        //Debug.logInfo("=================== calcedTotalPriceWithTax=" + calcedTotalPriceWithTax, module);
> +                    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);
>
> -                        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);
> -                        }
> +                        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);
>                     }
>                 }
>             }
>
> Modified: ofbiz/trunk/applications/order/data/OrderTypeData.xml
> URL: 
> http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/data/OrderTypeData.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
> ==============================================================================
> --- ofbiz/trunk/applications/order/data/OrderTypeData.xml (original)
> +++ ofbiz/trunk/applications/order/data/OrderTypeData.xml Mon Dec  6 08:05:44 2010
> @@ -42,6 +42,7 @@ under the License.
>     <OrderAdjustmentType description="Fee" hasTable="N" orderAdjustmentTypeId="FEE" parentTypeId=""/>
>     <OrderAdjustmentType description="Miscellaneous Charges" hasTable="N" orderAdjustmentTypeId="MISCELLANEOUS_CHARGE" 
> parentTypeId=""/>
>     <OrderAdjustmentType description="Sales Tax" hasTable="N" orderAdjustmentTypeId="SALES_TAX" parentTypeId=""/>
> +    <OrderAdjustmentType description="VAT Tax (not added to totals)" hasTable="N" orderAdjustmentTypeId="VAT_TAX" 
> parentTypeId=""/>
>     <OrderAdjustmentType description="VAT Price Correction" hasTable="N" orderAdjustmentTypeId="VAT_PRICE_CORRECT" 
> parentTypeId=""/>
>     <OrderAdjustmentType description="Shipping and Handling" hasTable="N" orderAdjustmentTypeId="SHIPPING_CHARGES" 
> parentTypeId=""/>
>     <OrderAdjustmentType description="Surcharge" hasTable="N" orderAdjustmentTypeId="SURCHARGE_ADJUSTMENT" parentTypeId=""/>
>
> Modified: ofbiz/trunk/applications/order/entitydef/entitymodel.xml
> URL: 
> http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/entitydef/entitymodel.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
> ==============================================================================
> --- ofbiz/trunk/applications/order/entitydef/entitymodel.xml (original)
> +++ ofbiz/trunk/applications/order/entitydef/entitymodel.xml Mon Dec  6 08:05:44 2010
> @@ -59,6 +59,7 @@ under the License.
>       <field name="description" type="description"></field>
>       <field name="amount" type="currency-precise"></field>
>       <field name="recurringAmount" type="currency-precise"></field>
> +      <field name="amountAlreadyIncluded" type="currency-precise"><description>The amount here is already represented in the 
> price, such as VAT taxes.</description></field>
>       <field name="productPromoId" type="id"></field>
>       <field name="productPromoRuleId" type="id"></field>
>       <field name="productPromoActionSeqId" type="id"></field>
>
> Modified: ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
> URL: 
> http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java?rev=1042542&r1=1042541&r2=1042542&view=diff
> ==============================================================================
> --- ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java (original)
> +++ ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java Mon Dec  6 08:05:44 2010
> @@ -2920,4 +2920,62 @@ public class OrderReadHelper {
>        result.put("taxGrandTotal", taxGrandTotal);
>        return result;
>    }
> +
> +   public static Map<String, Object> getOrderTaxByTaxAuthGeoAndPartyForDisplay(List<GenericValue> orderAdjustmentsOriginal) {
> +       BigDecimal taxGrandTotal = BigDecimal.ZERO;
> +       List<Map<String, Object>> taxByTaxAuthGeoAndPartyList = FastList.newInstance();
> +       List<GenericValue> orderAdjustmentsToUse = FastList.newInstance();
> +       if (UtilValidate.isNotEmpty(orderAdjustmentsOriginal)) {
> +           // get orderAdjustment where orderAdjustmentTypeId is SALES_TAX.
> +           orderAdjustmentsToUse.addAll(EntityUtil.filterByAnd(orderAdjustmentsOriginal, UtilMisc.toMap("orderAdjustmentTypeId", 
> "SALES_TAX")));
> +           orderAdjustmentsToUse.addAll(EntityUtil.filterByAnd(orderAdjustmentsOriginal, UtilMisc.toMap("orderAdjustmentTypeId", 
> "VAT_TAX")));
> +           orderAdjustmentsToUse = EntityUtil.orderBy(orderAdjustmentsToUse, UtilMisc.toList("taxAuthGeoId","taxAuthPartyId"));
> +
> +           // get the list of all distinct taxAuthGeoId and taxAuthPartyId. It is for getting the number of taxAuthGeo and 
> taxAuthPartyId in adjustments.
> +           List<String> distinctTaxAuthGeoIdList = EntityUtil.getFieldListFromEntityList(orderAdjustmentsToUse, "taxAuthGeoId", 
> true);
> +           List<String> distinctTaxAuthPartyIdList = EntityUtil.getFieldListFromEntityList(orderAdjustmentsToUse, 
> "taxAuthPartyId", true);
> +
> +           // Keep a list of amount that have been added to make sure none are missed (if taxAuth* information is missing)
> +           List<GenericValue> processedAdjustments = FastList.newInstance();
> +           // For each taxAuthGeoId get and add amount from orderAdjustment
> +           for (String taxAuthGeoId : distinctTaxAuthGeoIdList) {
> +               for (String taxAuthPartyId : distinctTaxAuthPartyIdList) {
> +                   //get all records for orderAdjustments filtered by taxAuthGeoId and taxAurhPartyId
> +                   List<GenericValue> orderAdjByTaxAuthGeoAndPartyIds = EntityUtil.filterByAnd(orderAdjustmentsToUse, 
> UtilMisc.toMap("taxAuthGeoId", taxAuthGeoId, "taxAuthPartyId", taxAuthPartyId));
> +                   if (UtilValidate.isNotEmpty(orderAdjByTaxAuthGeoAndPartyIds)) {
> +                       BigDecimal totalAmount = BigDecimal.ZERO;
> +                       //Now for each orderAdjustment record get and add amount.
> +                       for (GenericValue orderAdjustment : orderAdjByTaxAuthGeoAndPartyIds) {
> +                           BigDecimal amount = orderAdjustment.getBigDecimal("amount");
> +                           if (amount != null) {
> +                               totalAmount = totalAmount.add(amount);
> +                           }
> +                           if ("VAT_TAX".equals(orderAdjustment.getString("orderAdjustmentTypeId")) &&
> +                                   orderAdjustment.get("amountAlreadyIncluded") != null) {
> +                               // this is the only case where the VAT_TAX amountAlreadyIncluded should be added in, and should 
> just be for display and not to calculate the order grandTotal
> +                               totalAmount = totalAmount.add(orderAdjustment.getBigDecimal("amountAlreadyIncluded"));
> +                           }
> +                           totalAmount = totalAmount.setScale(taxCalcScale, taxRounding);
> +                           processedAdjustments.add(orderAdjustment);
> +                       }
> +                       totalAmount = totalAmount.setScale(taxFinalScale, taxRounding);
> +                       taxByTaxAuthGeoAndPartyList.add(UtilMisc.<String, Object>toMap("taxAuthPartyId", taxAuthPartyId, 
> "taxAuthGeoId", taxAuthGeoId, "totalAmount", totalAmount));
> +                       taxGrandTotal = taxGrandTotal.add(totalAmount);
> +                   }
> +               }
> +           }
> +           // Process any adjustments that got missed
> +           List<GenericValue> missedAdjustments = FastList.newInstance();
> +           missedAdjustments.addAll(orderAdjustmentsToUse);
> +           missedAdjustments.removeAll(processedAdjustments);
> +           for (GenericValue orderAdjustment : missedAdjustments) {
> +               taxGrandTotal = taxGrandTotal.add(orderAdjustment.getBigDecimal("amount").setScale(taxCalcScale, taxRounding));
> +           }
> +           taxGrandTotal = taxGrandTotal.setScale(taxFinalScale, taxRounding);
> +       }
> +       Map<String, Object> result = FastMap.newInstance();
> +       result.put("taxByTaxAuthGeoAndPartyList", taxByTaxAuthGeoAndPartyList);
> +       result.put("taxGrandTotal", taxGrandTotal);
> +       return result;
> +   }
> }
>
> Modified: ofbiz/trunk/applications/product/entitydef/entitymodel.xml
> URL: 
> http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/entitydef/entitymodel.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
> ==============================================================================
> --- ofbiz/trunk/applications/product/entitydef/entitymodel.xml (original)
> +++ ofbiz/trunk/applications/product/entitydef/entitymodel.xml Mon Dec  6 08:05:44 2010
> @@ -2375,11 +2375,13 @@ under the License.
>       <field name="price" type="currency-precise"></field>
>       <field name="termUomId" type="id"><description>Mainly used for recurring and usage prices to specify a time/freq measure, or 
> a usage unit measure (bits, minutes, etc)</description></field>
>       <field name="customPriceCalcService" type="id"><description>Points to a CustomMethod used to specify a service for the 
> calculation of the unit price of the product (NOTE: a better name for this field might be 
> priceCalcCustomMethodId)</description></field>
> -      <field name="priceWithTax" type="currency-precise"/>
> +      <field name="priceWithoutTax" type="currency-precise"><description>Always without tax if populated, regardless of if price 
> does or does not include tax.</description></field>
> +      <field name="priceWithTax" type="currency-precise"><description>Always with tax if populated, regardless of if price does 
> or does not include tax.</description></field>
>       <field name="taxAmount" type="currency-precise"/>
>       <field name="taxPercentage" type="fixed-point"/>
>       <field name="taxAuthPartyId" type="id-ne"/>
>       <field name="taxAuthGeoId" type="id-ne"/>
> +      <field name="taxInPrice" type="indicator"><description>If Y the price field has tax included for the given 
> taxAuthPartyId/taxAuthGeoId at the taxPercentage.</description></field>
>       <field name="createdDate" type="date-time"></field>
>       <field name="createdByUserLogin" type="id-vlong"></field>
>       <field name="lastModifiedDate" type="date-time"></field>
>
> Modified: ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml
> URL: 
> http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
> ==============================================================================
> --- ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml (original)
> +++ ofbiz/trunk/applications/product/script/org/ofbiz/product/price/PriceServices.xml Mon Dec  6 08:05:44 2010
> @@ -106,6 +106,7 @@ under the License.
>             </condition>
>             <then>
>                 <set field="parameters.priceWithTax" from-field="parameters.price"/>
> +
>                 <!-- if taxPercentage not passed in look it up based on taxAuthGeoId and taxAuthPartyId -->
>                 <if-empty field="parameters.taxPercentage">
>                     <!-- we only have basic data to constrain by here, so assume that if it is a VAT tax setup it should be pretty 
> simple -->
> @@ -127,6 +128,7 @@ under the License.
>                     <check-errors/>
>                 </if-empty>
>
> +                <!-- in short the formula is: taxAmount = priceWithTax - (priceWithTax/(1+taxPercentage/100)) -->
>                 <calculate field="parameters.taxAmount" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>                     <calcop operator="subtract">
>                         <calcop operator="get" field="parameters.priceWithTax"/>
> @@ -142,12 +144,23 @@ under the License.
>                         </calcop>
>                     </calcop>
>                 </calculate>
> -                <calculate field="parameters.price" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
> +
> +                <calculate field="parameters.priceWithoutTax" type="BigDecimal" decimal-scale="3" rounding-mode="HalfUp">
>                     <calcop operator="subtract">
>                         <calcop operator="get" field="parameters.priceWithTax"/>
>                         <calcop operator="get" field="parameters.taxAmount"></calcop>
>                     </calcop>
>                 </calculate>
> +
> +                <if-compare field="parameters.taxInPrice" operator="equals" value="Y">
> +                    <!-- the price passed in has tax included, and we want to store it with tax included -->
> +                    <set field="parameters.price" from-field="parameters.priceWithTax"/>
> +
> +                    <else>
> +                        <!-- the price passed in has tax included, but we want to store it without tax included -->
> +                        <set field="parameters.price" from-field="parameters.priceWithoutTax"/>
> +                    </else>
> +                </if-compare>
>             </then>
>         </if>
>     </simple-method>
>
> Modified: ofbiz/trunk/applications/product/servicedef/services.xml
> URL: 
> http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/servicedef/services.xml?rev=1042542&r1=1042541&r2=1042542&view=diff
> ==============================================================================
> --- ofbiz/trunk/applications/product/servicedef/services.xml (original)
> +++ ofbiz/trunk/applications/product/servicedef/services.xml Mon Dec  6 08:05:44 2010
> @@ -242,12 +242,15 @@ under the License.
>         <description>
>             Create an ProductPrice.
>             Price is always stored without tax.
> -            If a taxAuthGeoId and taxAuthPartyId are (or taxAuthCombinedId is) passed in then the price will be considered a 
> price
> -                with tax included and the tax will be removed before storing to the database
> -                (the priceWithTax, taxAmount, and taxPercentage fields will also be populated).
> +            If taxAuthGeoId and taxAuthPartyId are (or taxAuthCombinedId is) passed in then the price will be considered a price
> +            with tax included (the priceWithoutTax, priceWithTax, taxAmount, and taxPercentage fields will also be populated).
> +            If the taxInPrice field is 'Y' then the price field will be left with the price included (price will be equal to 
> priceWithTax),
> +            otherwise tax will be removed from the passed in price and the price field will be equal to the priceWithoutTax 
> field.
> +            If taxAuthGeoId or taxAuthPartyId empty, and taxAuthCombinedId is empty, then the taxInPrice field will be ignored.
>         </description>
>         <auto-attributes include="pk" mode="IN" optional="false"/>
>         <auto-attributes include="nonpk" mode="IN" optional="true">
> +            <exclude field-name="priceWithoutTax"/>
>             <exclude field-name="priceWithTax"/>
>             <exclude field-name="taxAmount"/>
>             <exclude field-name="createdDate"/>
> @@ -264,6 +267,7 @@ under the License.
>         <description>Update an ProductPrice</description>
>         <auto-attributes include="pk" mode="IN" optional="false"/>
>         <auto-attributes include="nonpk" mode="IN" optional="true">
> +            <exclude field-name="priceWithoutTax"/>
>             <exclude field-name="priceWithTax"/>
>             <exclude field-name="taxAmount"/>
>             <exclude field-name="createdDate"/>
>
>