You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ofbiz.apache.org by Jacques Le Roux <ja...@les7arts.com> on 2016/05/01 19:47:57 UTC
Re: Triangular VAT in Europe ?
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)?
Thanks
Jacques
Re: Triangular VAT in Europe ?
Posted by Nicolas Malin <ni...@nereide.fr>.
Le 27/05/2016 07:35, Jacques Le Roux a �crit :
>
> Thanks Nicolas,
>
> Could you please open a Jira improvement with your comments below?
>
Ok done Jacques,
https://issues.apache.org/jira/browse/OFBIZ-7138
All suggest are welcome because my solution came from my brain ... warn !!
Nicolas
>
> Thanks
>
> Jacques
>
>
> Le 26/05/2016 � 22:25, Nicolas Malin a �crit :
>> 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
>>
>
Re: Triangular VAT in Europe ?
Posted by Jacques Le Roux <ja...@les7arts.com>.
Thanks Nicolas,
Could you please open a Jira improvement with your comments below?
Thanks
Jacques
Le 26/05/2016 � 22:25, Nicolas Malin a �crit :
> 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
>
Re: Triangular VAT in Europe ?
Posted by Nicolas Malin <ni...@nereide.fr>.
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