Author: diveshdutta
Date: Sat Nov  5 11:05:04 2016
New Revision: 1768186

URL: http://svn.apache.org/viewvc?rev=1768186&view=rev
Log:
Improvement: Vat tax should be recorded as separate line items in sales invoice 
when products have vat tax included in its price. Added new Invoice Item type 
for vat tax. This will enable posting taxes in liability account and actual 
product's price in revenue account. So this will eventually fix accounting 
transactions and reports. 

(OFBIZ-7012)

Thanks: Thanks Ankush Upadhyay for your patch and Scott for design discussions

Modified:
    ofbiz/trunk/applications/accounting/data/AccountingTypeData.xml
    
ofbiz/trunk/applications/accounting/src/main/java/org/apache/ofbiz/accounting/invoice/InvoiceServices.java
    
ofbiz/trunk/applications/accounting/src/main/java/org/apache/ofbiz/accounting/tax/TaxAuthorityServices.java

Modified: ofbiz/trunk/applications/accounting/data/AccountingTypeData.xml
URL: 
http://svn.apache.org/viewvc/ofbiz/trunk/applications/accounting/data/AccountingTypeData.xml?rev=1768186&r1=1768185&r2=1768186&view=diff
==============================================================================
--- ofbiz/trunk/applications/accounting/data/AccountingTypeData.xml (original)
+++ ofbiz/trunk/applications/accounting/data/AccountingTypeData.xml Sat Nov  5 
11:05:04 2016
@@ -425,6 +425,7 @@ under the License.
     <InvoiceItemType description="Invoice Item Surcharge(Sales)" hasTable="N" 
invoiceItemTypeId="ITM_SURCHARGE_ADJ" parentTypeId="SINVOICE_ITM_ADJ"/>
     <InvoiceItemType description="Invoice Item Additional Feature(Sales)" 
hasTable="N" invoiceItemTypeId="ITM_ADD_FEATURE" 
parentTypeId="SINVOICE_ITM_ADJ"/>
     <InvoiceItemType description="Invoice Item Warranty(Sales)" hasTable="N" 
invoiceItemTypeId="ITM_WARRANTY_ADJ" parentTypeId="SINVOICE_ITM_ADJ"/>
+    <InvoiceItemType description="Invoice Item VAT Tax(Sales)" hasTable="N" 
invoiceItemTypeId="ITM_VAT_TAX" parentTypeId="SINVOICE_ITM_ADJ"/>
 
     <InvoiceItemType description="Invoice Product Item(Sales)" hasTable="N" 
invoiceItemTypeId="INV_PROD_ITEM" parentTypeId=""/>
     <InvoiceItemType description="Invoice Finished Good Item(Sales)" 
hasTable="N" invoiceItemTypeId="INV_FPROD_ITEM" parentTypeId="INV_PROD_ITEM"/>
@@ -562,7 +563,7 @@ under the License.
     <InvoiceItemTypeMap invoiceTypeId="SALES_INVOICE" 
invoiceItemMapKey="SURCHARGE_ADJUSTMENT" invoiceItemTypeId="ITM_SURCHARGE_ADJ"/>
     <InvoiceItemTypeMap invoiceTypeId="SALES_INVOICE" 
invoiceItemMapKey="ADDITIONAL_FEATURE" invoiceItemTypeId="ITM_ADD_FEATURE"/>
     <InvoiceItemTypeMap invoiceTypeId="SALES_INVOICE" 
invoiceItemMapKey="WARRANTY_ADJUSTMENT" invoiceItemTypeId="ITM_WARRANTY_ADJ"/>
-
+    <InvoiceItemTypeMap invoiceTypeId="SALES_INVOICE" 
invoiceItemMapKey="VAT_TAX" invoiceItemTypeId="ITM_VAT_TAX"/>
     <!-- orderItemTypeId -->
     <InvoiceItemTypeMap invoiceTypeId="PURCHASE_INVOICE" 
invoiceItemMapKey="INVENTORY_ORDER_ITEM" invoiceItemTypeId="PINV_INVPRD_ITEM"/>
     <InvoiceItemTypeMap invoiceTypeId="PURCHASE_INVOICE" 
invoiceItemMapKey="SUPPLIES_ORDER_ITEM" invoiceItemTypeId="PINV_SUPLPRD_ITEM"/>

Modified: 
ofbiz/trunk/applications/accounting/src/main/java/org/apache/ofbiz/accounting/invoice/InvoiceServices.java
URL: 
http://svn.apache.org/viewvc/ofbiz/trunk/applications/accounting/src/main/java/org/apache/ofbiz/accounting/invoice/InvoiceServices.java?rev=1768186&r1=1768185&r2=1768186&view=diff
==============================================================================
--- 
ofbiz/trunk/applications/accounting/src/main/java/org/apache/ofbiz/accounting/invoice/InvoiceServices.java
 (original)
+++ 
ofbiz/trunk/applications/accounting/src/main/java/org/apache/ofbiz/accounting/invoice/InvoiceServices.java
 Sat Nov  5 11:05:04 2016
@@ -423,7 +423,18 @@ public class InvoiceServices {
                     shippingApplies = true;
                 }
 
-                BigDecimal billingAmount = 
orderItem.getBigDecimal("unitPrice").setScale(invoiceTypeDecimals, ROUNDING);
+                BigDecimal billingAmount = BigDecimal.ZERO;
+                GenericValue OrderAdjustment = 
EntityUtil.getFirst(orderItem.getRelated("OrderAdjustment", 
UtilMisc.toMap("orderAdjustmentTypeId", "VAT_TAX"), null, false));
+                /* Apply formula to get actual product price to set amount in 
invoice item
+                    Formula is: productPrice = 
(productPriceWithTax.multiply(100)) / (orderAdj sourcePercentage + 100))
+                    product price = (43*100) / (20+100) = 35.83 (Here product 
price is 43 with VAT)
+                 */
+                if (UtilValidate.isNotEmpty(OrderAdjustment) && 
(OrderAdjustment.getBigDecimal("amount").signum() == 0) && 
UtilValidate.isNotEmpty(OrderAdjustment.getBigDecimal("amountAlreadyIncluded")) 
&& OrderAdjustment.getBigDecimal("amountAlreadyIncluded").signum() != 0) {
+                    BigDecimal sourcePercentageTotal = 
OrderAdjustment.getBigDecimal("sourcePercentage").add(new BigDecimal(100));
+                    billingAmount = 
orderItem.getBigDecimal("unitPrice").divide(sourcePercentageTotal, 100, 
ROUNDING).multiply(new BigDecimal(100)).setScale(invoiceTypeDecimals, ROUNDING);
+                } else {
+                    billingAmount = 
orderItem.getBigDecimal("unitPrice").setScale(invoiceTypeDecimals, ROUNDING);
+                }
 
                 Map<String, Object> createInvoiceItemContext = new 
HashMap<String, Object>();
                 createInvoiceItemContext.put("invoiceId", invoiceId);
@@ -532,6 +543,11 @@ public class InvoiceServices {
 //                    if (adj.get("amount") == null) { TODO check usage with 
webPos. Was: fix a bug coming from POS in case of use of a discount (on item(s) 
or sale, item(s) here) and a cash amount higher than total (hence issuing 
change)
 //                        continue;
 //                    }
+                    // Set adjustment amount as amountAlreadyIncluded to 
continue invoice item creation process
+                    Boolean isTaxIncludedInPrice = 
adj.getString("orderAdjustmentTypeId").equals("VAT_TAX") && 
UtilValidate.isNotEmpty(adj.getBigDecimal("amountAlreadyIncluded")) && 
adj.getBigDecimal("amountAlreadyIncluded").signum() != 0;
+                    if ((adj.getBigDecimal("amount").signum() == 0) && 
isTaxIncludedInPrice) {
+                        adj.set("amount", 
adj.getBigDecimal("amountAlreadyIncluded"));
+                    }
                     // If the absolute invoiced amount >= the abs of the 
adjustment amount, the full amount has already been invoiced, so skip this 
adjustment
                     if 
(adjAlreadyInvoicedAmount.abs().compareTo(adj.getBigDecimal("amount").setScale(invoiceTypeDecimals,
 ROUNDING).abs()) > 0) {
                         continue;
@@ -541,10 +557,52 @@ public class InvoiceServices {
                     BigDecimal amount = ZERO;
                     if (originalOrderItemQuantity.signum() != 0) {
                         if (adj.get("amount") != null) {
-                            // pro-rate the amount
-                            // set decimals = 100 means we don't round this 
intermediate value, which is very important
-                            amount = 
adj.getBigDecimal("amount").divide(originalOrderItemQuantity, 100, ROUNDING);
-                            amount = amount.multiply(billingQuantity);
+                                
if("PROMOTION_ADJUSTMENT".equals(adj.getString("orderAdjustmentTypeId")) && 
adj.get("productPromoId") != null) {
+                                    /* Find negative amountAlreadyIncluded in 
OrderAdjustment to subtract it from discounted amount.
+                                                                          As 
we stored negative sales tax amount in order adjustment for discounted item.
+                                     */
+                                    List<EntityExpr> exprs = 
UtilMisc.toList(EntityCondition.makeCondition("orderId", EntityOperator.EQUALS, 
orderItem.getString("orderId")),
+                                            
EntityCondition.makeCondition("orderItemSeqId", EntityOperator.EQUALS, 
orderItem.getString("orderItemSeqId")),
+                                            
EntityCondition.makeCondition("orderAdjustmentTypeId", EntityOperator.EQUALS, 
"VAT_TAX"),
+                                            
EntityCondition.makeCondition("amountAlreadyIncluded", 
EntityOperator.LESS_THAN, BigDecimal.ZERO));
+                                    EntityCondition andCondition = 
EntityCondition.makeCondition(exprs, EntityOperator.AND);
+                                    GenericValue orderAdjustment =  
EntityUtil.getFirst(delegator.findList("OrderAdjustment", andCondition, null, 
null, null, false));
+                                    if 
(UtilValidate.isNotEmpty(orderAdjustment)) {
+                                        amount = 
adj.getBigDecimal("amount").subtract(orderAdjustment.getBigDecimal("amountAlreadyIncluded")).setScale(100,
 ROUNDING);
+                                    } else {
+                                        amount = adj.getBigDecimal("amount");
+                                    }
+                                } else {
+                                    // pro-rate the amount
+                                    // set decimals = 100 means we don't round 
this intermediate value, which is very important
+                                    if (isTaxIncludedInPrice) {
+                                        BigDecimal priceWithTax = 
originalOrderItem.getBigDecimal("unitPrice");
+                                        // Get tax included in item price
+                                        amount = 
priceWithTax.subtract(billingAmount);
+                                        amount = 
amount.multiply(billingQuantity);
+                                        // get adjustment amount
+                                        /* Get tax amount of other invoice and 
calculate remaining amount need to store in invoice item(Handle case of of 
partial shipment and promotional item)
+                                                                              
to adjust tax amount in invoice item. 
+                                         */
+                                        BigDecimal otherInvoiceTaxAmount = 
BigDecimal.ZERO;
+                                        GenericValue orderAdjBilling = 
EntityUtil.getFirst(delegator.findByAnd("OrderAdjustmentBilling", 
UtilMisc.toMap("orderAdjustmentId", adj.getString("orderAdjustmentId")), null, 
false));
+                                        if 
(UtilValidate.isNotEmpty(orderAdjBilling)) {
+                                            List<GenericValue> invoiceItems = 
delegator.findByAnd("InvoiceItem", 
+                                                    
UtilMisc.toMap("invoiceId", orderAdjBilling.getString("invoiceId"), 
"invoiceItemTypeId", "ITM_SALES_TAX", "productId", 
originalOrderItem.getString("productId")), null, isTaxIncludedInPrice);
+                                            for (GenericValue invoiceItem : 
invoiceItems) {
+                                                otherInvoiceTaxAmount = 
otherInvoiceTaxAmount.add(invoiceItem.getBigDecimal("amount"));
+                                            }
+                                            if 
(otherInvoiceTaxAmount.compareTo(BigDecimal.ZERO) > 0) {
+                                                BigDecimal remainingAmount = 
adj.getBigDecimal("amountAlreadyIncluded").subtract(otherInvoiceTaxAmount);
+                                                amount = 
amount.min(remainingAmount);
+                                            }
+                                        }
+                                        amount = 
amount.min(adj.getBigDecimal("amountAlreadyIncluded")).setScale(100, ROUNDING);
+                                    } else {
+                                        amount = 
adj.getBigDecimal("amount").divide(originalOrderItemQuantity, 100, ROUNDING);
+                                        amount = 
amount.multiply(billingQuantity);
+                                    }
+                                }                            
                             // Tax needs to be rounded differently from other 
order adjustments
                             if 
(adj.getString("orderAdjustmentTypeId").equals("SALES_TAX")) {
                                 amount = amount.setScale(TAX_DECIMALS, 
TAX_ROUNDING);

Modified: 
ofbiz/trunk/applications/accounting/src/main/java/org/apache/ofbiz/accounting/tax/TaxAuthorityServices.java
URL: 
http://svn.apache.org/viewvc/ofbiz/trunk/applications/accounting/src/main/java/org/apache/ofbiz/accounting/tax/TaxAuthorityServices.java?rev=1768186&r1=1768185&r2=1768186&view=diff
==============================================================================
--- 
ofbiz/trunk/applications/accounting/src/main/java/org/apache/ofbiz/accounting/tax/TaxAuthorityServices.java
 (original)
+++ 
ofbiz/trunk/applications/accounting/src/main/java/org/apache/ofbiz/accounting/tax/TaxAuthorityServices.java
 Sat Nov  5 11:05:04 2016
@@ -442,17 +442,29 @@ public class TaxAuthorityServices {
                 }
                 GenericValue taxAdjValue = 
delegator.makeValue("OrderAdjustment");
 
-                if (productPrice != null && 
"Y".equals(productPrice.getString("taxInPrice"))) {
-                    // tax is in the price already, so we want the adjustment 
to be a VAT_TAX adjustment to be subtracted instead of a SALES_TAX adjustment 
to be added
+                BigDecimal discountedSalesTax = BigDecimal.ZERO;
+                taxAdjValue.set("orderAdjustmentTypeId", "SALES_TAX");
+                if (productPrice != null && 
"Y".equals(productPrice.getString("taxInPrice")) && itemQuantity != 
BigDecimal.ZERO) {
+                    // For example product price is 43 with 20% VAT(means 
product actual price is 35.83).
+                    // itemPrice = 43;
+                    // itemQuantity = 3;
+                    // taxAmountIncludedInFullPrice = (43-(43/(1+(20/100))))*3 
= 21.51
                     taxAdjValue.set("orderAdjustmentTypeId", "VAT_TAX");
-
-                    // 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);
+                    BigDecimal taxAmountIncludedInFullPrice = 
itemPrice.subtract(itemPrice.divide(BigDecimal.ONE.add(taxRate.divide(PERCENT_SCALE,
 4, BigDecimal.ROUND_HALF_UP)), 2, 
BigDecimal.ROUND_HALF_UP)).multiply(itemQuantity);
+                    // If 1 quantity has 50% discount then itemAmount = 107.5 
otherwise 129 (In case of no discount)
+                    // Net price for each item
+                    // netItemPrice = itemAmount / quantity = 107.5 / 3 = 
35.833333333
+                    BigDecimal netItemPrice = itemAmount.divide(itemQuantity, 
BigDecimal.ROUND_HALF_UP);
+                    // Calculate tax on the discounted price, be sure to round 
to 2 decimal places before multiplying by quantity
+                    // netTax = (netItemPrice - netItemPrice / (1 + 
(taxRate/100))) * quantity
+                    // netTax = (35.833333333-(35.833333333/(1+(20/100))))*3 = 
17.92
+                    BigDecimal netTax = 
netItemPrice.subtract(netItemPrice.divide(BigDecimal.ONE.add(taxRate.divide(PERCENT_SCALE,
 4, BigDecimal.ROUND_HALF_UP)), 2, 
BigDecimal.ROUND_HALF_UP)).multiply(itemQuantity);
+                    //Subtract net tax from base tax 
(taxAmountIncludedFullPrice) to get the negative promotion tax adjustment amount
+                    // discountedSalesTax = 17.92 - 21.51 = −3.59 (If no 
discounted item quantity then discountedSalesTax will be ZERO)
+                    discountedSalesTax = 
netTax.subtract(taxAmountIncludedInFullPrice);
+                    taxAdjValue.set("amountAlreadyIncluded", 
taxAmountIncludedInFullPrice);
                     taxAdjValue.set("amount", BigDecimal.ZERO);
                 } else {
-                    taxAdjValue.set("orderAdjustmentTypeId", "SALES_TAX");
                     taxAdjValue.set("amount", taxAmount);
                 }
                 
@@ -482,7 +494,12 @@ public class TaxAuthorityServices {
                 } else {
                     Debug.logInfo("NOTE: A tax calculation was done without a 
billToPartyId or taxAuthGeoId, so no tax exemptions or tax IDs considered; 
billToPartyId=[" + billToPartyId + "] taxAuthGeoId=[" + taxAuthGeoId + "]", 
module);
                 }
-
+                if (discountedSalesTax.compareTo(BigDecimal.ZERO) < 0) {
+                    GenericValue taxAdjValueNegative = 
delegator.makeValue("OrderAdjustment");
+                    taxAdjValueNegative.setFields(taxAdjValue);
+                    taxAdjValueNegative.set("amountAlreadyIncluded", 
discountedSalesTax);
+                    adjustments.add(taxAdjValueNegative);
+                }
                 adjustments.add(taxAdjValue);
 
                 if (productPrice != null && itemQuantity != null && 


Reply via email to