You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by di...@apache.org on 2016/11/05 11:05:04 UTC

svn commit: r1768186 - in /ofbiz/trunk/applications/accounting: data/AccountingTypeData.xml src/main/java/org/apache/ofbiz/accounting/invoice/InvoiceServices.java src/main/java/org/apache/ofbiz/accounting/tax/TaxAuthorityServices.java

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 &&