[jira] [Commented] (OFBIZ-7138) Manage Triangular European VAT

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[jira] [Commented] (OFBIZ-7138) Manage Triangular European VAT

Nicolas Malin (Jira)

    [ https://issues.apache.org/jira/browse/OFBIZ-7138?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=15304070#comment-15304070 ]

Nicolas Malin commented on OFBIZ-7138:
--------------------------------------

To manage this I extend the function TaxAuthorityServices.getTaxAuthorities with this  (I implement the solution on 13.07):
{code}
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);
        }
{code}

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 :

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

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

{code}
 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;
                    }
                }
            }
        }
{code}
* 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 :
{code}
    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;
{code}
Or
{code}
    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;
{code}

> Manage Triangular European VAT
> -------------------------------
>
>                 Key: OFBIZ-7138
>                 URL: https://issues.apache.org/jira/browse/OFBIZ-7138
>             Project: OFBiz
>          Issue Type: Bug
>    Affects Versions: Trunk
>            Reporter: Nicolas Malin
>            Assignee: Nicolas Malin
>            Priority: Minor
>              Labels: tax, vat
>
> I open an issue related to mailing thread https://lists.apache.org/thread.html/Z8ksgxdskmbcg9n
> The origin came from here :
> {quote}
> 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.
> {quote}
> I will load a patch in few week ;)



--
This message was sent by Atlassian JIRA
(v6.3.4#6332)