ofbiz-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Nicolas Malin <nicolas.ma...@nereide.fr>
Subject Re: Triangular VAT in Europe ?
Date Thu, 26 May 2016 20:25:08 GMT
Hi Jacques,


Le 01/05/2016 19:47, Jacques Le Roux a écrit :
> Le 27/01/2016 à 16:37, Nicolas Malin a écrit :
>> Hello, In Europe with the B2B drop shipment process we have a 
>> specific rule to calculate the VAT. The particularity comes from the 
>> purchase order, applying the VAT of the product origin country if 
>> billing address OR shipping address is in the same country. 1. I'm a 
>> French society that ordered product from Italy to sale in Denmark -> 
>> No VAT 2. I'm a French society that ordered product from Italy to 
>> sale in Italy -> Italian VAT 3. I'm a French society that ordered 
>> product from France to sale in Italy -> French VAT Currently to 
>> resolve the taxAuth in OFBiz we use the shipping address only, so we 
>> can see that the point 3. isn't covered because the product wasn't 
>> shipped in France. Does anyone ever met the same problem ? I propose 
>> to improve the taxAuth resolution with also the origin address and 
>> the billing address. After that, I have the problem to say when we 
>> need to check the origin instead of the shipping. Hmmmm ... my first 
>> idea would be to check if the payToPartyId is an internal 
>> organisation. If yes, resolve taxAuth with the shipping else, I check 
>> if the origin is the same country than the shipping or billing. After 
>> TaxAuth the rate resolved come without change. However, I suggest to 
>> add a customMethodId on taxAuthRateProduct to increase the 
>> configuration of complex cases.
>>
>> Any suggest ?
>>
>> -- 
>> #jeSuisCharlie
>> logoNrd <http://nereide.fr/>
>> 	Nicolas Malin
>> Ingénieur d'étude. Dernier sujet : "Les vaches portant un prénom 
>> pouvent trouver la sortie d'un labyrinthe en cas de toxoplasmose
>> information@nereide.fr
>> 8 rue des Déportés 37000 TOURS, 02 47 50 30 54
>>
>> Apache OFBiz <http://ofbiz.apache.org/> | ofbiz-fr 
>> <http://www.ofbiz-fr.org/> | | réseau LE 
>> <http://www.libre-entreprise.org/>
> Hi Nicolas,
>
> Did you finally implement this? If yes would you contribute (just 
> curious)?
I sharing with pleasure, because is now in production ;)

To manage this I extend the function 
TaxAuthorityServices.getTaxAuthorities with this  (I implement the 
solution on 13.07):
****************************************
EntityCondition cond = EntityCondition.makeCondition(UtilMisc.toList(
                 EntityCondition.makeCondition("partyId", payToPartyId),
                 EntityCondition.makeCondition("isNexus", "Y")));
         List<GenericValue> taxAuthorityRawList = 
delegator.findList("PartyTaxAuthInfo", cond, null, null, null, true);
         List<EntityCondition> taxCondList = new 
ArrayList<EntityCondition>();
         if (!taxAuthorityRawList.isEmpty()) {
taxCondList.add(EntityCondition.makeCondition("taxAuthPartyId", 
EntityOperator.IN, 
EntityUtil.getFieldListFromEntityList(taxAuthorityRawList, 
"taxAuthPartyId", true)));
             if (Debug.infoOn()) Debug.logInfo("Search tax authority 
relation for " + payToPartyId + " " + taxCondList, module);
             if (originAddress == null) {
                 originAddress = 
ContactMechWorker.getTaxOriginAddress(delegator, payToPartyId);
             }
             if (billingAddress == null) {
                 billingAddress = 
ContactMechWorker.getTaxBillingAddress(delegator, billToPartyId);
             }
             if (originAddress != null && billingAddress != null) {
                 if 
(originAddress.getString("countryGeoId").equals(billingAddress.getString("countryGeoId")))

{
                     //ok we will analyse the country where come from 
the flow
                     address = originAddress;
                 }
             }
             if (Debug.infoOn()) {
                 Debug.logInfo("          shipping found " + 
shippingAddress.getString("countryGeoId"), module);
                 Debug.logInfo("          origin found " + 
originAddress.getString("countryGeoId"), module);
                 Debug.logInfo("          billing found " + 
billingAddress.getString("countryGeoId"), module);
                 Debug.logInfo("          country found " + 
address.getString("countryGeoId"), module);
             }
         } else {
             if (Debug.infoOn()) Debug.logInfo("No specific relation, 
run the std resolution", module);
         }
******************************************

The idea, when an order check the vat, I resolv the taxAuth to use first 
with the payToParty. I check if this party have a dedicate relation with 
a specific tax authority by PartyTaxAuthInfo. If yes, I check with the 
shipping and the billing adress to understand if this order is cover by 
the same country.

If no I continue with the standard method.

This is a simple hack, because a better solution would be use the 
orderContachMech to resolve the shipping, billing and origin adress, but 
this works fine like that :) .

The rest is only data configuration on the PartyAuth and bill from vendor.


After to implement a specific text on invoice template and resolve the 
reason of an exoneration, I use a seca like this :

     <eca service="setInvoiceStatus" event="invoke">
          <condition operator="equals" field-name="statusId" 
value="INVOICE_READY"/>
          <action service="checkInvoiceForVATExemptReason" mode="sync" 
ignore-error="false"/>
     </eca>

The service checkInvoiceForVATExemptReason, check if the invoice have 
vat line and if not call this :

****************
  GenericValue partyAuth = EntityUtil.getFirst(partyAuths);
         EntityCondition condTax = 
EntityCondition.makeCondition(UtilMisc.toList(
                 EntityExpr.makeCondition("taxAuthPartyId", 
partyAuth.get("taxAuthPartyId")),
                 EntityExpr.makeCondition("taxAuthGeoId", 
partyAuth.get("taxAuthGeoId")),
                 EntityExpr.makeCondition("taxPercentage", 
GenericEntity.NULL_FIELD),
EntityExpr.makeCondition("taxAmount",GenericEntity.NULL_FIELD),
EntityCondition.makeCondition("taxAuthorityRateTypeId", 
EntityOperator.NOT_EQUAL, "SALES_TAX")));
         List<GenericValue> taxRates = 
delegator.findList("TaxAuthorityRateProduct", condTax, null, 
UtilMisc.toList("sequenceNum"), null, true);
         if (Debug.infoOn()) Debug.logInfo(" ### taxRates " + 
taxRates.size(), module);
         taxRates = EntityUtil.filterByDate(taxRates, 
invoice.getTimestamp("invoiceDate"));
         if (UtilValidate.isEmpty(taxRates)) {
             if (Debug.infoOn()) Debug.logInfo(" no specific rules 
find", module);
             return result;
         }
         //check match case rate
         for (GenericValue taxRate : taxRates) {
             GenericValue custMethod = null;
             String serviceName = null;
             if 
(UtilValidate.isNotEmpty(taxRate.getString("customMethodId"))) {
                 custMethod = delegator.findOne("CustomMethod", true, 
"customMethodId", taxRate.get("customMethodId"));
             }
             if (custMethod != null) serviceName = 
custMethod.getString("customMethodName");
             if (UtilValidate.isNotEmpty(serviceName)) {
                 ModelService service = dctx.getModelService(serviceName);
                 if (service != null) {
                     if (Debug.infoOn()) Debug.logInfo(" call service " 
+ serviceName + "related to taxRate : " + 
taxRate.get("taxAuthorityRateSeqId"), module);
                     Map<String,Object> serviceCtx = 
service.makeValid(context, ModelService.IN_PARAM);
                     serviceCtx.put("taxAuthorityRateSeqId", 
taxRate.get("taxAuthorityRateSeqId"));
                     Map<String,Object> serviceResult = 
dctx.getDispatcher().runSync(serviceName, serviceCtx);
                     if (ServiceUtil.isError(serviceResult)) {
                         throw new 
GeneralException(ServiceUtil.getErrorMessage(serviceResult));
                     }
                     if ("Y".equalsIgnoreCase((String) 
serviceResult.get("taxExempt"))) {
                         Map<String, Object> invoiceItemMap = 
dctx.makeValidContext("createInvoiceItem", "IN", context);
                         invoiceItemMap.put("taxAuthorityRateSeqId", 
taxRate.get("taxAuthorityRateSeqId"));
                         invoiceItemMap.put("taxAuthPartyId", 
taxRate.get("taxAuthPartyId"));
                         invoiceItemMap.put("taxAuthGeoId", 
taxRate.get("taxAuthGeoId"));
                         invoiceItemMap.put("invoiceItemTypeId", 
"ITM_SALES_TAX");
                         invoiceItemMap.put("quantity", 0);
                         invoiceItemMap.put("amount", 0);
                         if (Debug.infoOn()) Debug.logInfo( "Nice is an 
exempt reason, store it on invoice.", module);
                         serviceResult = 
dctx.getDispatcher().runSync("createInvoiceItem", invoiceItemMap);
                         if (ServiceUtil.isError(serviceResult)) {
                             throw new 
GeneralException(ServiceUtil.getErrorMessage(serviceResult));
                         }
                         break;
                     }
                 }
             }
         }
**********************
* You check the related TaxAuth with empty rate (I used Export type)
* For each find, you call a customMethod to understand why you don't 
have VAT

Example :
     private static boolean isEuropeanIntracomCountry(Delegator 
delegator, String countryGeoId) throws GenericEntityException {
         if (countryGeoId != null) {
             return delegator.findOne("GeoAssoc", true, "geoIdTo", "EU", 
"geoId", countryGeoId) != null;
         }
         return false;
Or
     public static Map<String, Object> 
checkEuropeanVatExempt(DispatchContext dctx, Map<String, Object> context)
     throws GeneralException {
         Delegator delegator = dctx.getDelegator();
         Map<String, Object> result = ServiceUtil.returnSuccess();
         String invoiceId = (String) context.get("invoiceId");
         result.put("taxExempt", "N");

         String deliveryCountryGeoId = 
resolvDeliveryCountryGeoId(delegator, invoiceId);
         //Si pas TVA triangulaire dans l'UE alors TVA export Intra com
         if (isEuropeanIntracomCountry(delegator, deliveryCountryGeoId)) {
             result.put("taxExempt", "Y");
         }
         return result;

My apologies for the big raw email and congrat to read up to here ! :)
Nicolas
>
> Thanks
> Jacques


Mime
View raw message