Dolibarr / dolibarr

Dolibarr ERP CRM is a modern software package to manage your company or foundation's activity (contacts, suppliers, invoices, orders, stocks, agenda, accounting, ...). it's an open source Web application (written in PHP) designed for businesses of any sizes, foundations and freelancers.
https://www.dolibarr.org
GNU General Public License v3.0
5.39k stars 2.77k forks source link

Local Tax 2 (IRPF Spain) Not being added. #23165

Open Daviid-P opened 1 year ago

Daviid-P commented 1 year ago

Bug

I have configured in admin/company.php that localtax2on is true (Sujeto a IRPF) and the value is set (-15)

But when I create an invoice for a client I don't get any IRPF added.

image

As far as I can see, when adding tax to a service I get 21/0/0%, those zeros come from product/price.php because for some reason there's this comment:

// If spain, we don't use the localtax found into tax record in database with same code, but using the get_localtax rule.
if (in_array($mysoc->country_code, array('ES'))) {
    $localtax1 = get_localtax($tva_tx, 1);
    $localtax2 = get_localtax($tva_tx, 2);
}

Then in get_localtax() there's this:

// Some test to guess with no need to make database access
if ($mysoc->country_code == 'ES') { // For spain localtaxes 1 and 2, tax is qualified if buyer use local tax
  if ($local == 1) {
      if (!$mysoc->localtax1_assuj || (string) $vatratecleaned == "0") {
          return 0;
      }
      if ($thirdparty_seller->id == $mysoc->id) {
          if (!$thirdparty_buyer->localtax1_assuj) {
              return 0;
          }
      } else {
          if (!$thirdparty_seller->localtax1_assuj) {
              return 0;
          }
      }
  }

  if ($local == 2) {
      // $conf->global->MAIN_INFO_VALUE_LOCALTAX2
      //if (! $mysoc->localtax2_assuj || (string) $vatratecleaned == "0") return 0;
      if (!$mysoc->localtax2_assuj) {
          return 0; // If main vat is 0, IRPF may be different than 0.
      }
      if ($thirdparty_seller->id == $mysoc->id) {
          if (!$thirdparty_buyer->localtax2_assuj) {
              return 0;
          }
      } else {
          if (!$thirdparty_seller->localtax2_assuj) {
              return 0;
          }
      }
  }
}

image

I'm creating a client invoice, so my company is the $thirdparty_seller. Then it checks if the $thirdparty_buyer is subject to localtax2.

From this page:

AM I OBLIGED TO RETAIN IRPF IN MY INVOICES?

First of all, only certain freelancers are required to retain IRPF in their sales invoices. Companies do not have this obligation, and only need to apply VAT in their invoices, unless we are talking about a rental invoice to another company. In this last case, VAT is added to the taxable base of the invoice and IRPF is deducted from the same taxable base.

If you are a freelancer, you need to know in what code (epígrafe) of economic activity (IAE) you have been registered. In Spain, freelance activities are divided into business (Sección 1), professional (Sección 2) and artistic categories (Sección 3). Only freelancers that carry out professional activities (doctors, lawyers, teachers, translators etc.) must issue their invoices with income tax retention and only if their clients are other freelancers or businesses in Spain.

I thought that the configuration in admin/company.php was something "global" as in, if localtax2 is on then all invoices to clients have localtax2 by default.

Am I misunderstanding something here??

Environment Version

No response

Environment OS

No response

Environment Web server

No response

Environment PHP

No response

Environment Database

No response

Environment URL(s)

No response

Expected and actual behavior

If local tax 2 is enabled in admin/company.php add local tax 2 by default to client invoices except when explicitly set to otherwise in the thirdparty card in client tab.

If local tax 2 is enabled in admin/company.php change all thirdparty clients to subject to localtax2 (by default, with a checkbox or with a confirmation form)

Steps to reproduce the behavior

No response

Attached files

No response

simnandez commented 1 year ago

Hi David, You answer yourself in your approach:

"Only freelancers that carry out professional activities (doctors, lawyers, teachers, translators etc.) must issue their invoices with income tax retention and only if their clients are other freelancers or businesses in Spain"

Only if their clients are other freelancers or businesses. Therefore, you must indicate whether or not your client is subject to IRPF.

In the Third card you can set if the client is subject and the value, since even the type of client can change the rate to be applied (services, leases, etc.)

Daviid-P commented 1 year ago

Hi David, You answer yourself in your approach:

"Only freelancers that carry out professional activities (doctors, lawyers, teachers, translators etc.) must issue their invoices with income tax retention and only if their clients are other freelancers or businesses in Spain"

Only if their clients are other freelancers or businesses. Therefore, you must indicate whether or not your client is subject to IRPF.

In the Third card you can set if the client is subject and the value, since even the type of client can change the rate to be applied (services, leases, etc.)

Isn't "Subject to Local Tax 2" for providers only?

simnandez commented 1 year ago

It is for both suppliers and customers

Daviid-P commented 1 year ago

I have a Freelancer that is subject to IRPF so he goes to htdocs/admin/company.php and sets "Gestión de IRPF" to "Sujeto a IRPF" and selects a default value of -15.

If that freelancer tries to create an invoice of 100€ to Company A (Seller freelancer and Client Company A) which does not apply IRPF when they are providers then FC will be 100 base amount, 21 IVA and 0 IRPF.

If I set in the thirdparty for Company A that they're subject to IRPF then get the -15 of IRPF but I can't create invoices with that thirdparty as a provider because it'll apply IRPF then too.

What's the point of the main configuration if I still have to add the localtax2 to each thirdparty.

Is there any case where I'm the seller and the IRPF set in htdocs/admin/company.php is different from the IRPF that I should apply to a client? (Needing then to set a IRPF in the thirdparty card)

If not, then the IRPF set in a thirdparty card should only apply for when that thirdparty is a provider, no? I've noticed that, if I set up a thirdparty subject to IRPF I only see that info in the provider tab of the thirdparty card which I think makes it seems as if it only applies to providers.

Daviid-P commented 1 year ago

@simnandez

I think I'm confused, I can't turn off the localtax2 on the main configuration because on htdocs\societe\card.php it checks if $mysoc->localtax1_assuj == "1" && $mysoc->localtax2_assuj == "1" before it shows the inputs for localtaxes on the thirdparty.

This doesn't work if my company is not subject to IRPF but we have a provider that is, right?

Am I wrong or is this an actual bug?

eldy commented 10 months ago

@Daviid-P i try to see how to close this ticket. Do you still have trouble. There is still a lot of things i don't understand myself on IRPF. Even if IRPF is not applied by default when you add a product, are you able to apply it after when you edit the line in the order or invoice by selecting another VAT rate with IRPF defined on this VAT rate ?

Daviid-P commented 9 months ago

I don't remember well but I got this working good enough for my company, at least.

Here's how it's on develop branch right now


/**
 *  Return localtax rate for a particular vat, when selling a product with vat $vatrate, from a $thirdparty_buyer to a $thirdparty_seller
 *  Note: This function applies same rules than get_default_tva
 *
 *  @param  float       $vatrate                Vat rate. Can be '8.5' or '8.5 (VATCODEX)' for example
 *  @param  int         $local                  Local tax to search and return (1 or 2 return only tax rate 1 or tax rate 2)
 *  @param  Societe     $thirdparty_buyer       Object of buying third party
 *  @param  Societe     $thirdparty_seller      Object of selling third party ($mysoc if not defined)
 *  @param  int         $vatnpr                 If vat rate is NPR or not
 *  @return mixed                               0 if not found, localtax rate if found
 *  @see get_default_tva()
 */
function get_localtax($vatrate, $local, $thirdparty_buyer = "", $thirdparty_seller = "", $vatnpr = 0)
{
    global $db, $conf, $mysoc;

    if (empty($thirdparty_seller) || !is_object($thirdparty_seller)) {
        $thirdparty_seller = $mysoc;
    }

    dol_syslog("get_localtax tva=" . $vatrate . " local=" . $local . " thirdparty_buyer id=" . (is_object($thirdparty_buyer) ? $thirdparty_buyer->id : '') . "/country_code=" . (is_object($thirdparty_buyer) ? $thirdparty_buyer->country_code : '') . " thirdparty_seller id=" . $thirdparty_seller->id . "/country_code=" . $thirdparty_seller->country_code . " thirdparty_seller localtax1_assuj=" . $thirdparty_seller->localtax1_assuj . "  thirdparty_seller localtax2_assuj=" . $thirdparty_seller->localtax2_assuj);

    $vatratecleaned = $vatrate;
    $reg = array();
    if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {     // If vat is "xx (yy)"
        $vatratecleaned = trim($reg[1]);
        $vatratecode = $reg[2];
    }

    /*if ($thirdparty_buyer->country_code != $thirdparty_seller->country_code)
    {
        return 0;
    }*/

    // Some test to guess with no need to make database access
    if ($mysoc->country_code == 'ES') { // For spain localtaxes 1 and 2, tax is qualified if buyer use local tax
        if ($local == 1) {
            if (!$mysoc->localtax1_assuj || (string) $vatratecleaned == "0") {
                return 0;
            }
            if ($thirdparty_seller->id == $mysoc->id) {
                if (!$thirdparty_buyer->localtax1_assuj) {
                    return 0;
                }
            } else {
                if (!$thirdparty_seller->localtax1_assuj) {
                    return 0;
                }
            }
        }

        if ($local == 2) {
            //if (! $mysoc->localtax2_assuj || (string) $vatratecleaned == "0") return 0;
            if (!$mysoc->localtax2_assuj) {
                return 0; // If main vat is 0, IRPF may be different than 0.
            }
            if ($thirdparty_seller->id == $mysoc->id) {
                if (!$thirdparty_buyer->localtax2_assuj) {
                    return 0;
                }
            } else {
                if (!$thirdparty_seller->localtax2_assuj) {
                    return 0;
                }
            }
        }
    } else {
        if ($local == 1 && !$thirdparty_seller->localtax1_assuj) {
            return 0;
        }
        if ($local == 2 && !$thirdparty_seller->localtax2_assuj) {
            return 0;
        }
    }

    // For some country MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY is forced to on.
    if (in_array($mysoc->country_code, array('ES'))) {
        $conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY = 1;
    }

    // Search local taxes
    if (getDolGlobalString('MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY')) {
        if ($local == 1) {
            if ($thirdparty_seller != $mysoc) {
                if (!isOnlyOneLocalTax($local)) {  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
                    return $thirdparty_seller->localtax1_value;
                }
            } else { // i am the seller
                if (!isOnlyOneLocalTax($local)) { // TODO If seller is me, why not always returning this, even if there is only one locatax vat.
                    return $conf->global->MAIN_INFO_VALUE_LOCALTAX1;
                }
            }
        }
        if ($local == 2) {
            if ($thirdparty_seller != $mysoc) {
                if (!isOnlyOneLocalTax($local)) {  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
                    // TODO We should also return value defined on thirdparty only if defined
                    return $thirdparty_seller->localtax2_value;
                }
            } else { // i am the seller
                if (in_array($mysoc->country_code, array('ES'))) {
                    return $thirdparty_buyer->localtax2_value;
                } else {
                    return $conf->global->MAIN_INFO_VALUE_LOCALTAX2;
                }
            }
        }
    }

    // By default, search value of local tax on line of common tax
    $sql = "SELECT t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
    $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
    $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($thirdparty_seller->country_code) . "'";
    $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
    $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
    if (!empty($vatratecode)) {
        $sql .= " AND t.code ='" . $db->escape($vatratecode) . "'"; // If we have the code, we use it in priority
    } else {
        $sql .= " AND t.recuperableonly = '" . $db->escape($vatnpr) . "'";
    }

    $resql = $db->query($sql);

    if ($resql) {
        $obj = $db->fetch_object($resql);
        if ($obj) {
            if ($local == 1) {
                return $obj->localtax1;
            } elseif ($local == 2) {
                return $obj->localtax2;
            }
        }
    }

    return 0;
}

and here's what I have in my dolibarr


/**
 *  Return localtax rate for a particular vat, when selling a product with vat $vatrate, from a $thirdparty_buyer to a $thirdparty_seller
 *  Note: This function applies same rules than get_default_tva
 *
 *  @param  float       $vatrate                Vat rate. Can be '8.5' or '8.5 (VATCODEX)' for example
 *  @param  int         $local                  Local tax to search and return (1 or 2 return only tax rate 1 or tax rate 2)
 *  @param  Societe     $thirdparty_buyer       Object of buying third party
 *  @param  Societe     $thirdparty_seller      Object of selling third party ($mysoc if not defined)
 *  @param  int         $vatnpr                 If vat rate is NPR or not
 *  @return mixed                               0 if not found, localtax rate if found
 *  @see get_default_tva()
 */
function get_localtax($vatrate, $local, $thirdparty_buyer = "", $thirdparty_seller = "", $vatnpr = 0)
{
    global $db, $conf, $mysoc;

    if (empty($thirdparty_seller) || !is_object($thirdparty_seller)) {
        $thirdparty_seller = $mysoc;
    }

    dol_syslog("get_localtax tva=" . $vatrate . " local=" . $local . " thirdparty_buyer id=" . (is_object($thirdparty_buyer) ? $thirdparty_buyer->id : '') . "/country_code=" . (is_object($thirdparty_buyer) ? $thirdparty_buyer->country_code : '') . " thirdparty_seller id=" . $thirdparty_seller->id . "/country_code=" . $thirdparty_seller->country_code . " thirdparty_seller localtax1_assuj=" . $thirdparty_seller->localtax1_assuj . "  thirdparty_seller localtax2_assuj=" . $thirdparty_seller->localtax2_assuj);

    $vatratecleaned = $vatrate;
    $reg = array();
    if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {     // If vat is "xx (yy)"
        $vatratecleaned = trim($reg[1]);
        $vatratecode = $reg[2];
    }

    /*if ($thirdparty_buyer->country_code != $thirdparty_seller->country_code)
    {
        return 0;
    }*/

    // Some test to guess with no need to make database access
    if ($mysoc->country_code == 'ES') { // For spain localtaxes 1 and 2, tax is qualified if buyer use local tax
        if ($local == 1) {
            if (!$mysoc->localtax1_assuj || (string) $vatratecleaned == "0") {
                return 0;
            }
            if ($thirdparty_seller->id == $mysoc->id) {
                if (!$thirdparty_buyer->localtax1_assuj) {
                    return 0;
                }
            } else {
                if (!$thirdparty_seller->localtax1_assuj) {
                    return 0;
                }
            }
        }

        if ($local == 2) {
            //if (! $mysoc->localtax2_assuj || (string) $vatratecleaned == "0") return 0;
            //if (!$mysoc->localtax2_assuj) {
            //  return 0; // If main vat is 0, IRPF may be different than 0.
            //}
            if ($thirdparty_seller->id == $mysoc->id) {
                // if (!$thirdparty_buyer->localtax2_assuj) {
                //  return 0;
                // }
                // evCustom
                if (
                    $conf->global->FACTURE_LOCAL_TAX2_OPTION != "localtax2off" &&
                    isset($conf->global->MAIN_INFO_VALUE_LOCALTAX2)
                ) {
                    return $conf->global->MAIN_INFO_VALUE_LOCALTAX2;
                } elseif ($thirdparty_seller->localtax2_assuj) {
                    return $thirdparty_seller->localtax2_value;
                }
                // evCustom
            } else {
                if (!$thirdparty_seller->localtax2_assuj) {
                    return 0;
                }
            }
        }
    } else {
        if ($local == 1 && !$thirdparty_seller->localtax1_assuj) {
            return 0;
        }
        if ($local == 2 && !$thirdparty_seller->localtax2_assuj) {
            return 0;
        }
    }

    // For some country MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY is forced to on.
    if (in_array($mysoc->country_code, array('ES'))) {
        $conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY = 1;
    }

    // Search local taxes
    if (!empty($conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY)) {
        if ($local == 1) {
            if ($thirdparty_seller != $mysoc) {
                if (!isOnlyOneLocalTax($local)) {  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
                    return $thirdparty_seller->localtax1_value;
                }
            } else { // i am the seller
                if (!isOnlyOneLocalTax($local)) { // TODO If seller is me, why not always returning this, even if there is only one locatax vat.
                    return $conf->global->MAIN_INFO_VALUE_LOCALTAX1;
                }
            }
        }
        if ($local == 2) {
            if ($thirdparty_seller != $mysoc) {
                if (!isOnlyOneLocalTax($local)) {  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
                    // TODO We should also return value defined on thirdparty only if defined
                    return $thirdparty_seller->localtax2_value;
                }
            } else { // i am the seller
                // evCustom
                if (
                    $conf->global->FACTURE_LOCAL_TAX2_OPTION != "localtax2off" &&
                    isset($conf->global->MAIN_INFO_VALUE_LOCALTAX2)
                ) {
                    return $conf->global->MAIN_INFO_VALUE_LOCALTAX2;
                } elseif ($thirdparty_buyer->localtax2_value > 0) {
                    return $thirdparty_buyer->localtax2_value;
                }
                // evCustom
                //if (in_array($mysoc->country_code, array('ES'))) {
                //  return $thirdparty_buyer->localtax2_value;
                //} else {
                //  return $conf->global->MAIN_INFO_VALUE_LOCALTAX2;
                //}
            }
        }
    }

    // By default, search value of local tax on line of common tax
    $sql = "SELECT t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
    $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
    $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($thirdparty_seller->country_code) . "'";
    $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
    if (!empty($vatratecode)) {
        $sql .= " AND t.code ='" . $db->escape($vatratecode) . "'"; // If we have the code, we use it in priority
    } else {
        $sql .= " AND t.recuperableonly = '" . $db->escape($vatnpr) . "'";
    }

    $resql = $db->query($sql);

    if ($resql) {
        $obj = $db->fetch_object($resql);
        if ($obj) {
            if ($local == 1) {
                return $obj->localtax1;
            } elseif ($local == 2) {
                return $obj->localtax2;
            }
        }
    }

    return 0;
}

Images of the changes:

WinMergeU_1bT8EqkY1a

WinMergeU_trvmLDi5zz

Like this I don't need to add localtax to the products, I just select TVA when adding the Invoice Line

image image

Third party must be set to locatax2_assuj and the correct tax must be selected.

I don't dare touch it anymore as long as it's working for us.

TL;DR You can close this without any trouble. I don't remember why I changed what I changed.

prietojc commented 8 months ago

This bug is still present in version 18

Daviid-P said:

Is there any case where I'm the seller and the IRPF set in htdocs/admin/company.php is different from the IRPF that I should apply to a client? (Needing then to set a IRPF in the thirdparty card)

If not, then the IRPF set in a thirdparty card should only apply for when that thirdparty is a provider, no? I've noticed that, if I set up a thirdparty subject to IRPF I only see that info in the provider tab of the thirdparty card which I think makes it seems as if it only applies to providers.

First answer is NOT, but you need to know if must apply it (only companies, not individual customers). We'll use $conf->global->MAIN_INFO_VALUE_LOCALTAX2 and should be mandatory.

So second answer is YES. Only sellers set type of IRPF.

I think code is wrong, the spanish exception should be the other way around, buyer don't set IRPF.

if (in_array($mysoc->country_code, array('ES'))) {
    return $thirdparty_buyer->localtax2_value;
} else {
    return $conf->global->MAIN_INFO_VALUE_LOCALTAX2;
}

And finaly, code show localtax2 in products, but that doesn't make sense if localtax2 is IRPF. It is unique for freelancers (IRPF means Impuesto sobre la Renta de las Personas Físicas = personal income tax).

coop2540 commented 6 months ago

Sorry, I don't know if the topic is related.

Normally, I can make invoices with the -15% IRPF tax.

But this does not happen when lines are added from project task times.

Editing the lines of the draft invoice does not add the -15% IRPF tax either.

My company has marked the use of -15% IRPF Tax and the client has also indicated it. If I make a normal invoice everything is correct, but not the time spent invoiced on a project task. Nor if I select a service for that time dedicated to billing.