mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Refactor for taxes
This commit is contained in:
parent
046a72326e
commit
d52d2f1f37
@ -11,6 +11,9 @@
|
|||||||
|
|
||||||
namespace App\DataMapper\Tax;
|
namespace App\DataMapper\Tax;
|
||||||
|
|
||||||
|
use App\Models\Client;
|
||||||
|
use App\DataMapper\Tax\ZipTax\Response;
|
||||||
|
|
||||||
interface RuleInterface
|
interface RuleInterface
|
||||||
{
|
{
|
||||||
public function tax();
|
public function tax();
|
||||||
@ -27,5 +30,11 @@ interface RuleInterface
|
|||||||
|
|
||||||
public function taxPhysical();
|
public function taxPhysical();
|
||||||
|
|
||||||
|
public function taxReduced();
|
||||||
|
|
||||||
public function default();
|
public function default();
|
||||||
|
|
||||||
|
public function setClient(Client $client);
|
||||||
|
|
||||||
|
public function setTaxData(Response $tax_data);
|
||||||
}
|
}
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace App\DataMapper\Tax\de;
|
namespace App\DataMapper\Tax\de;
|
||||||
|
|
||||||
|
use App\Models\Client;
|
||||||
use App\Models\Product;
|
use App\Models\Product;
|
||||||
use App\DataMapper\Tax\RuleInterface;
|
use App\DataMapper\Tax\RuleInterface;
|
||||||
use App\DataMapper\Tax\ZipTax\Response;
|
use App\DataMapper\Tax\ZipTax\Response;
|
||||||
@ -100,10 +101,24 @@ class Rule implements RuleInterface
|
|||||||
public string $tax_name3 = '';
|
public string $tax_name3 = '';
|
||||||
public float $tax_rate3 = 0;
|
public float $tax_rate3 = 0;
|
||||||
|
|
||||||
|
protected ?Client $client;
|
||||||
|
|
||||||
public function __construct(public Response $tax_data)
|
public function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setClient(Client $client): self
|
||||||
|
{
|
||||||
|
$this->client = $client;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTaxData(Response $tax_data): self
|
||||||
{
|
{
|
||||||
$this->tax_data = $tax_data;
|
$this->tax_data = $tax_data;
|
||||||
|
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tax(): self
|
public function tax(): self
|
||||||
@ -126,12 +141,21 @@ class Rule implements RuleInterface
|
|||||||
Product::PRODUCT_TYPE_SERVICE => $this->taxService(),
|
Product::PRODUCT_TYPE_SERVICE => $this->taxService(),
|
||||||
Product::PRODUCT_TYPE_SHIPPING => $this->taxShipping(),
|
Product::PRODUCT_TYPE_SHIPPING => $this->taxShipping(),
|
||||||
Product::PRODUCT_TYPE_PHYSICAL => $this->taxPhysical(),
|
Product::PRODUCT_TYPE_PHYSICAL => $this->taxPhysical(),
|
||||||
|
Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced(),
|
||||||
default => $this->default(),
|
default => $this->default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function taxReduced(): self
|
||||||
|
{
|
||||||
|
$this->tax_rate1 = $this->vat_reduced_rate;
|
||||||
|
$this->tax_name1 = 'VAT';
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function taxExempt(): self
|
public function taxExempt(): self
|
||||||
{
|
{
|
||||||
$this->tax_name1 = '';
|
$this->tax_name1 = '';
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace App\DataMapper\Tax\us;
|
namespace App\DataMapper\Tax\us;
|
||||||
|
|
||||||
|
use App\Models\Client;
|
||||||
use App\Models\Product;
|
use App\Models\Product;
|
||||||
use App\DataMapper\Tax\RuleInterface;
|
use App\DataMapper\Tax\RuleInterface;
|
||||||
use App\DataMapper\Tax\ZipTax\Response;
|
use App\DataMapper\Tax\ZipTax\Response;
|
||||||
@ -80,10 +81,26 @@ class Rule implements RuleInterface
|
|||||||
public string $tax_name3 = '';
|
public string $tax_name3 = '';
|
||||||
public float $tax_rate3 = 0;
|
public float $tax_rate3 = 0;
|
||||||
|
|
||||||
public function __construct(public Response $tax_data)
|
public ?Client $client;
|
||||||
|
|
||||||
|
public ?Response $tax_data;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTaxData(Response $tax_data): self
|
||||||
{
|
{
|
||||||
$this->tax_data = $tax_data;
|
$this->tax_data = $tax_data;
|
||||||
nlog($tax_data);
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setClient(Client $client):self
|
||||||
|
{
|
||||||
|
$this->client = $client;
|
||||||
|
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tax(): self
|
public function tax(): self
|
||||||
@ -158,4 +175,11 @@ class Rule implements RuleInterface
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function taxReduced(): self
|
||||||
|
{
|
||||||
|
$this->tax();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,8 +112,8 @@ class InvoiceItemSum
|
|||||||
|
|
||||||
$tax_data = new Response($this->invoice->tax_data);
|
$tax_data = new Response($this->invoice->tax_data);
|
||||||
|
|
||||||
$this->rule = new $class($tax_data);
|
$this->rule = new $class();
|
||||||
|
$this->rule->setTaxData($tax_data);
|
||||||
$this->calc_tax = true;
|
$this->calc_tax = true;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -116,6 +116,7 @@ class Product extends BaseModel
|
|||||||
public const PRODUCT_TYPE_DIGITAL = 3;
|
public const PRODUCT_TYPE_DIGITAL = 3;
|
||||||
public const PRODUCT_TYPE_SHIPPING = 4;
|
public const PRODUCT_TYPE_SHIPPING = 4;
|
||||||
public const PRODUCT_TAX_EXEMPT = 5;
|
public const PRODUCT_TAX_EXEMPT = 5;
|
||||||
|
public const PRODUCT_TYPE_REDUCED_TAX = 6;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'custom_value1',
|
'custom_value1',
|
||||||
|
@ -9,14 +9,14 @@
|
|||||||
* @license https://www.elastic.co/licensing/elastic-license
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace App\Services\Tax;
|
namespace App\Services\Tax\Providers;
|
||||||
|
|
||||||
use App\Models\Client;
|
use App\Models\Client;
|
||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use App\DataMapper\Tax\de\Rule;
|
use App\DataMapper\Tax\de\Rule;
|
||||||
use App\Services\Tax\VatNumberCheck;
|
use App\Services\Tax\VatNumberCheck;
|
||||||
class ProcessRule
|
class EuTax
|
||||||
{
|
{
|
||||||
public Rule $rule;
|
public Rule $rule;
|
||||||
|
|
194
app/Services/Tax/Providers/TaxProvider.php
Normal file
194
app/Services/Tax/Providers/TaxProvider.php
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Services\Tax\Providers;
|
||||||
|
|
||||||
|
use App\Models\Client;
|
||||||
|
use App\Models\Company;
|
||||||
|
use Illuminate\Http\Client\Response;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
|
class TaxProvider
|
||||||
|
{
|
||||||
|
|
||||||
|
public array $eu_countries = [
|
||||||
|
"AT",
|
||||||
|
"BE",
|
||||||
|
"BG",
|
||||||
|
"HR",
|
||||||
|
"CY",
|
||||||
|
"CZ",
|
||||||
|
"DK",
|
||||||
|
"EE",
|
||||||
|
"FI",
|
||||||
|
"FR",
|
||||||
|
"DE",
|
||||||
|
"GR",
|
||||||
|
"HU",
|
||||||
|
"IE",
|
||||||
|
"IT",
|
||||||
|
"LV",
|
||||||
|
"LT",
|
||||||
|
"LU",
|
||||||
|
"MT",
|
||||||
|
"NL",
|
||||||
|
"PL",
|
||||||
|
"PT",
|
||||||
|
"RO",
|
||||||
|
"SK",
|
||||||
|
"SI",
|
||||||
|
"ES",
|
||||||
|
"SE"
|
||||||
|
];
|
||||||
|
|
||||||
|
private string $provider = ZipTax::class;
|
||||||
|
|
||||||
|
private mixed $api_credentials;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(protected Company $company, protected Client $client)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function updateCompanyTaxData(): self
|
||||||
|
{
|
||||||
|
$this->configureProvider($this->provider); //hard coded for now to one provider, but we'll be able to swap these out later
|
||||||
|
|
||||||
|
$company_details = [
|
||||||
|
'address1' => $this->company->settings->address1,
|
||||||
|
'address2' => $this->company->settings->address2,
|
||||||
|
'city' => $this->company->settings->city,
|
||||||
|
'state' => $this->company->settings->state,
|
||||||
|
'postal_code' => $this->company->settings->postal_code,
|
||||||
|
'country_id' => $this->company->settings->country_id,
|
||||||
|
];
|
||||||
|
|
||||||
|
$tax_provider = new $this->provider($company_details);
|
||||||
|
|
||||||
|
$tax_provider->setApiCredentials($this->api_credentials);
|
||||||
|
|
||||||
|
$tax_data = $tax_provider->run();
|
||||||
|
|
||||||
|
$this->company->tax_data = $tax_data;
|
||||||
|
|
||||||
|
$this->company->save();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateClientTaxData(): self
|
||||||
|
{
|
||||||
|
$this->configureProvider($this->provider); //hard coded for now to one provider, but we'll be able to swap these out later
|
||||||
|
|
||||||
|
$billing_details =[
|
||||||
|
'address1' => $this->client->address1,
|
||||||
|
'address2' => $this->client->address2,
|
||||||
|
'city' => $this->client->city,
|
||||||
|
'state' => $this->client->state,
|
||||||
|
'postal_code' => $this->client->postal_code,
|
||||||
|
'country_id' => $this->client->country_id,
|
||||||
|
];
|
||||||
|
|
||||||
|
$shipping_details =[
|
||||||
|
'address1' => $this->client->shipping_address1,
|
||||||
|
'address2' => $this->client->shipping_address2,
|
||||||
|
'city' => $this->client->shipping_city,
|
||||||
|
'state' => $this->client->shipping_state,
|
||||||
|
'postal_code' => $this->client->shipping_postal_code,
|
||||||
|
'country_id' => $this->client->shipping_country_id,
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
$tax_provider = new $this->provider();
|
||||||
|
|
||||||
|
$tax_provider->setApiCredentials($this->api_credentials);
|
||||||
|
|
||||||
|
$tax_data = $tax_provider->run();
|
||||||
|
|
||||||
|
$this->company->tax_data = $tax_data;
|
||||||
|
|
||||||
|
$this->company->save();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function configureProvider(?string $provider): self
|
||||||
|
{
|
||||||
|
|
||||||
|
match($this->client->country->iso_3166_2){
|
||||||
|
'US' => $this->configureZipTax(),
|
||||||
|
"AT" => $this->configureEuTax(),
|
||||||
|
"BE" => $this->configureEuTax(),
|
||||||
|
"BG" => $this->configureEuTax(),
|
||||||
|
"HR" => $this->configureEuTax(),
|
||||||
|
"CY" => $this->configureEuTax(),
|
||||||
|
"CZ" => $this->configureEuTax(),
|
||||||
|
"DK" => $this->configureEuTax(),
|
||||||
|
"EE" => $this->configureEuTax(),
|
||||||
|
"FI" => $this->configureEuTax(),
|
||||||
|
"FR" => $this->configureEuTax(),
|
||||||
|
"DE" => $this->configureEuTax(),
|
||||||
|
"GR" => $this->configureEuTax(),
|
||||||
|
"HU" => $this->configureEuTax(),
|
||||||
|
"IE" => $this->configureEuTax(),
|
||||||
|
"IT" => $this->configureEuTax(),
|
||||||
|
"LV" => $this->configureEuTax(),
|
||||||
|
"LT" => $this->configureEuTax(),
|
||||||
|
"LU" => $this->configureEuTax(),
|
||||||
|
"MT" => $this->configureEuTax(),
|
||||||
|
"NL" => $this->configureEuTax(),
|
||||||
|
"PL" => $this->configureEuTax(),
|
||||||
|
"PT" => $this->configureEuTax(),
|
||||||
|
"RO" => $this->configureEuTax(),
|
||||||
|
"SK" => $this->configureEuTax(),
|
||||||
|
"SI" => $this->configureEuTax(),
|
||||||
|
"ES" => $this->configureEuTax(),
|
||||||
|
"SE" => $this->configureEuTax(),
|
||||||
|
default => $this->noTaxRegionDefined(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function configureEuTax(): self
|
||||||
|
{
|
||||||
|
$this->provider = EuTax::class;
|
||||||
|
|
||||||
|
// $this->api_credentials = config('services.tax.eu_tax.key');
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function noTaxRegionDefined(): self
|
||||||
|
{
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function configureZipTax(): self
|
||||||
|
{
|
||||||
|
|
||||||
|
$this->provider = ZipTax::class;
|
||||||
|
|
||||||
|
$this->api_credentials = config('services.tax.zip_tax.key');
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -18,101 +18,10 @@ use App\Services\Tax\Providers\ZipTax;
|
|||||||
|
|
||||||
class TaxService
|
class TaxService
|
||||||
{
|
{
|
||||||
private string $provider = ZipTax::class;
|
|
||||||
|
|
||||||
private mixed $api_credentials;
|
|
||||||
|
|
||||||
public function __construct(protected Company $company, protected Client $client)
|
public function __construct(protected Company $company, protected Client $client)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateCompanyTaxData(): self
|
|
||||||
{
|
|
||||||
$this->configureProvider($this->provider); //hard coded for now to one provider, but we'll be able to swap these out later
|
|
||||||
|
|
||||||
$company_details = [
|
|
||||||
'address1' => $this->company->settings->address1,
|
|
||||||
'address2' => $this->company->settings->address2,
|
|
||||||
'city' => $this->company->settings->city,
|
|
||||||
'state' => $this->company->settings->state,
|
|
||||||
'postal_code' => $this->company->settings->postal_code,
|
|
||||||
'country_id' => $this->company->settings->country_id,
|
|
||||||
];
|
|
||||||
|
|
||||||
$tax_provider = new $this->provider($company_details);
|
|
||||||
|
|
||||||
$tax_provider->setApiCredentials($this->api_credentials);
|
|
||||||
|
|
||||||
$tax_data = $tax_provider->run();
|
|
||||||
|
|
||||||
$this->company->tax_data = $tax_data;
|
|
||||||
|
|
||||||
$this->company->save();
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateClientTaxData(): self
|
|
||||||
{
|
|
||||||
$this->configureProvider($this->provider); //hard coded for now to one provider, but we'll be able to swap these out later
|
|
||||||
|
|
||||||
$billing_details =[
|
|
||||||
'address1' => $this->client->address1,
|
|
||||||
'address2' => $this->client->address2,
|
|
||||||
'city' => $this->client->city,
|
|
||||||
'state' => $this->client->state,
|
|
||||||
'postal_code' => $this->client->postal_code,
|
|
||||||
'country_id' => $this->client->country_id,
|
|
||||||
];
|
|
||||||
|
|
||||||
$shipping_details =[
|
|
||||||
'address1' => $this->client->shipping_address1,
|
|
||||||
'address2' => $this->client->shipping_address2,
|
|
||||||
'city' => $this->client->shipping_city,
|
|
||||||
'state' => $this->client->shipping_state,
|
|
||||||
'postal_code' => $this->client->shipping_postal_code,
|
|
||||||
'country_id' => $this->client->shipping_country_id,
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
$tax_provider = new $this->provider();
|
|
||||||
|
|
||||||
$tax_provider->setApiCredentials($this->api_credentials);
|
|
||||||
|
|
||||||
$tax_data = $tax_provider->run();
|
|
||||||
|
|
||||||
$this->company->tax_data = $tax_data;
|
|
||||||
|
|
||||||
$this->company->save();
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private function configureProvider(?string $provider): self
|
|
||||||
{
|
|
||||||
|
|
||||||
match($provider){
|
|
||||||
ZipTax::class => $this->configureZipTax(),
|
|
||||||
default => $this->configureZipTax(),
|
|
||||||
};
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private function configureZipTax(): self
|
|
||||||
{
|
|
||||||
|
|
||||||
$this->provider = ZipTax::class;
|
|
||||||
|
|
||||||
$this->api_credentials = config('services.tax.zip_tax.key');
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -16,15 +16,15 @@ use App\Models\Client;
|
|||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
use Tests\MockAccountData;
|
use Tests\MockAccountData;
|
||||||
use App\DataMapper\Tax\de\Rule;
|
use App\DataMapper\Tax\de\Rule;
|
||||||
use App\Services\Tax\ProcessRule;
|
use App\Services\Tax\Providers\EuTax;
|
||||||
use App\DataMapper\CompanySettings;
|
use App\DataMapper\CompanySettings;
|
||||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @test App\Services\Tax\ProcessRule
|
* @test App\Services\Tax\Providers\EuTax
|
||||||
*/
|
*/
|
||||||
class ProcessRuleTest extends TestCase
|
class EuTaxTest extends TestCase
|
||||||
{
|
{
|
||||||
use MockAccountData;
|
use MockAccountData;
|
||||||
use DatabaseTransactions;
|
use DatabaseTransactions;
|
||||||
@ -60,7 +60,7 @@ class ProcessRuleTest extends TestCase
|
|||||||
'shipping_country_id' => 276,
|
'shipping_country_id' => 276,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$process = new ProcessRule($company, $client);
|
$process = new EuTax($company, $client);
|
||||||
$process->run();
|
$process->run();
|
||||||
|
|
||||||
$this->assertEquals('de', $process->getVendorCountryCode());
|
$this->assertEquals('de', $process->getVendorCountryCode());
|
||||||
@ -96,7 +96,7 @@ class ProcessRuleTest extends TestCase
|
|||||||
'shipping_country_id' => 56,
|
'shipping_country_id' => 56,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$process = new ProcessRule($company, $client);
|
$process = new EuTax($company, $client);
|
||||||
$process->run();
|
$process->run();
|
||||||
|
|
||||||
$this->assertEquals('de', $process->getVendorCountryCode());
|
$this->assertEquals('de', $process->getVendorCountryCode());
|
||||||
@ -132,7 +132,7 @@ class ProcessRuleTest extends TestCase
|
|||||||
'shipping_country_id' => 840,
|
'shipping_country_id' => 840,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$process = new ProcessRule($company, $client);
|
$process = new EuTax($company, $client);
|
||||||
$process->run();
|
$process->run();
|
||||||
|
|
||||||
$this->assertEquals('de', $process->getVendorCountryCode());
|
$this->assertEquals('de', $process->getVendorCountryCode());
|
Loading…
x
Reference in New Issue
Block a user