From 12d3e3501917108884e1a5bd330a5b2445044dc2 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 19 Mar 2023 16:14:04 +1100 Subject: [PATCH] Global Tax Rules --- app/DataMapper/Tax/RuleInterface.php | 17 +++ app/DataMapper/Tax/de/Rule.php | 19 ++- app/Services/Tax/ProcessRule.php | 133 ++++++++++++++++++++- app/Services/Tax/VatNumberCheck.php | 24 +++- tests/Unit/Tax/ProcessRuleTest.php | 154 +++++++++++++++++++++++++ tests/Unit/{ => Tax}/VatNumberTest.php | 40 +------ 6 files changed, 340 insertions(+), 47 deletions(-) create mode 100644 app/DataMapper/Tax/RuleInterface.php create mode 100644 tests/Unit/Tax/ProcessRuleTest.php rename tests/Unit/{ => Tax}/VatNumberTest.php (50%) diff --git a/app/DataMapper/Tax/RuleInterface.php b/app/DataMapper/Tax/RuleInterface.php new file mode 100644 index 000000000000..ec88cd72c67d --- /dev/null +++ b/app/DataMapper/Tax/RuleInterface.php @@ -0,0 +1,17 @@ +setUp() + ->validateVat() + ->calculateVatRates(); + } + + public function hasValidVatNumber(): bool + { + return $this->valid_vat_number; + } + + public function getVatRate(): float + { + return $this->vat_rate; + } + + public function getVatReducedRate(): float + { + return $this->vat_reduced_rate; + } + + public function getVendorCountryCode(): string + { + return $this->vendor_country_code; + } + + public function getClientCountryCode(): string + { + return $this->client_country_code; + } + + private function setUp(): self + { + $this->vendor_country_code = Str::lower($this->company->country()->iso_3166_2); + + $this->client_country_code = $this->client->shipping_country ? Str::lower($this->client->shipping_country->iso_3166_2) : Str::lower($this->client->country->iso_3166_2); + + $class = "App\\DataMapper\\Tax\\".$this->vendor_country_code."\\Rule"; + + $this->rule = new $class(); + + return $this; + } + + private function validateVat(): self + { + $vat_check = (new VatNumberCheck($this->client->vat_number, $this->client_country_code))->run(); + + $this->valid_vat_number = $vat_check->isValid(); + + return $this; + } + + private function calculateVatRates(): self + { + + if( + (($this->vendor_country_code == $this->client_country_code) && $this->valid_vat_number && $this->rule->business_tax_exempt) || + (in_array($this->client_country_code, $this->eu_country_codes) && $this->valid_vat_number && $this->rule->business_tax_exempt) + ) { + $this->vat_rate = 0.0; + $this->vat_reduced_rate = 0.0; + } + elseif(!in_array(strtoupper($this->client_country_code), $this->eu_country_codes) && ($this->rule->foreign_consumer_tax_exempt || $this->rule->foreign_business_tax_exempt)) { + nlog($this->client_country_code); + $this->vat_rate = 0.0; + $this->vat_reduced_rate = 0.0; + } + elseif(in_array(strtoupper($this->client_country_code), $this->eu_country_codes) && !$this->valid_vat_number) { + $rate_name = $this->client_country_code."_vat_rate"; + $this->vat_rate = $this->rule->{$rate_name}; + $this->vat_reduced_rate = $this->rule->vat_reduced_rate; + } + else { + $rate_name = $this->vendor_country_code."_vat_rate"; + $this->vat_rate = $this->rule->{$rate_name}; + $this->vat_reduced_rate = $this->rule->vat_reduced_rate; + } + + return $this; + } } diff --git a/app/Services/Tax/VatNumberCheck.php b/app/Services/Tax/VatNumberCheck.php index af60bf22f3fb..e2eea03ada7e 100644 --- a/app/Services/Tax/VatNumberCheck.php +++ b/app/Services/Tax/VatNumberCheck.php @@ -13,6 +13,7 @@ namespace App\Services\Tax; class VatNumberCheck { + private array $response = []; public function __construct(protected string $vat_number, protected string $country_code) { @@ -23,9 +24,10 @@ class VatNumberCheck return $this->checkvat_number(); } - private function checkvat_number(): array + private function checkvat_number(): self { $wsdl = "https://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl"; + try { $client = new \SoapClient($wsdl); $params = [ @@ -35,18 +37,30 @@ class VatNumberCheck $response = $client->checkVat($params); if ($response->valid) { - return [ + + $this->response = [ 'valid' => true, 'name' => $response->name, 'address' => $response->address ]; } else { - return ['valid' => false]; + $this->response = ['valid' => false]; } } catch (\SoapFault $e) { - // Handle error, e.g., log or display an error message - return ['error' => $e->getMessage()]; + + $this->response = ['valid' => false, 'error' => $e->getMessage()]; } + + return $this; } + public function getResponse() + { + return $this->response; + } + + public function isValid(): bool + { + return $this->response['valid']; + } } diff --git a/tests/Unit/Tax/ProcessRuleTest.php b/tests/Unit/Tax/ProcessRuleTest.php new file mode 100644 index 000000000000..f8c00ce48bb0 --- /dev/null +++ b/tests/Unit/Tax/ProcessRuleTest.php @@ -0,0 +1,154 @@ +withoutMiddleware( + ThrottleRequests::class + ); + + $this->withoutExceptionHandling(); + + $this->makeTestData(); + } + + public function testCorrectRuleInit() + { + + $settings = CompanySettings::defaults(); + $settings->country_id = '276'; // germany + + $company = Company::factory()->create([ + 'account_id' => $this->account->id, + 'settings' => $settings + ]); + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'country_id' => 276, + 'shipping_country_id' => 276, + ]); + + $process = new ProcessRule($company, $client); + $process->run(); + + $this->assertEquals('de', $process->getVendorCountryCode()); + + $this->assertEquals('de', $process->getClientCountryCode()); + + $this->assertFalse($process->hasValidVatNumber()); + + $this->assertInstanceOf(Rule::class, $process->rule); + + $this->assertEquals(19, $process->getVatRate()); + + $this->assertEquals(7, $process->getVatReducedRate()); + + + } + + public function testEuCorrectRuleInit() + { + + $settings = CompanySettings::defaults(); + $settings->country_id = '276'; // germany + + $company = Company::factory()->create([ + 'account_id' => $this->account->id, + 'settings' => $settings + ]); + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'country_id' => 56, + 'shipping_country_id' => 56, + ]); + + $process = new ProcessRule($company, $client); + $process->run(); + + $this->assertEquals('de', $process->getVendorCountryCode()); + + $this->assertEquals('be', $process->getClientCountryCode()); + + $this->assertFalse($process->hasValidVatNumber()); + + $this->assertInstanceOf(Rule::class, $process->rule); + + $this->assertEquals(21, $process->getVatRate()); + + $this->assertEquals(7, $process->getVatReducedRate()); + + + } + + public function testForeignCorrectRuleInit() + { + + $settings = CompanySettings::defaults(); + $settings->country_id = '276'; // germany + + $company = Company::factory()->create([ + 'account_id' => $this->account->id, + 'settings' => $settings + ]); + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'country_id' => 840, + 'shipping_country_id' => 840, + ]); + + $process = new ProcessRule($company, $client); + $process->run(); + + $this->assertEquals('de', $process->getVendorCountryCode()); + + $this->assertEquals('us', $process->getClientCountryCode()); + + $this->assertFalse($process->hasValidVatNumber()); + + $this->assertInstanceOf(Rule::class, $process->rule); + + $this->assertEquals(0, $process->getVatRate()); + + $this->assertEquals(0, $process->getVatReducedRate()); + + + } + + +} diff --git a/tests/Unit/VatNumberTest.php b/tests/Unit/Tax/VatNumberTest.php similarity index 50% rename from tests/Unit/VatNumberTest.php rename to tests/Unit/Tax/VatNumberTest.php index 326eea1d2244..50135007d359 100644 --- a/tests/Unit/VatNumberTest.php +++ b/tests/Unit/Tax/VatNumberTest.php @@ -9,10 +9,10 @@ * @license https://www.elastic.co/licensing/elastic-license */ -namespace Tests\Unit; +namespace Tests\Unit\Tax; -use Tests\TestCase; use App\Services\Tax\VatNumberCheck; +use Tests\TestCase; /** * @test App\Services\Tax\VatNumberCheck @@ -24,11 +24,8 @@ class VatNumberTest extends TestCase parent::setUp(); } - - public function testVatNumber() { - // Usage example $country_code = "IE"; // Ireland $vat_number = "1234567L"; // Example VAT number @@ -37,22 +34,10 @@ class VatNumberTest extends TestCase $vat_checker = new VatNumberCheck($vat_number, $country_code); $result = $vat_checker->run(); - if (isset($result['valid'])) { - if ($result['valid']) { - echo "The VAT number is valid.\n"; - echo "Name: " . $result['name'] . "\n"; - echo "Address: " . $result['address'] . "\n"; - } else { - echo "The VAT number is invalid.\n"; - } - } else { - echo "Error: " . $result['error'] . "\n"; - } - - $this->assertFalse($result['valid']); + $this->assertFalse($result->isValid()); } - private function testValidVatNumber() + public function testValidVatNumber() { // Usage example $country_code = "AT"; // Ireland @@ -62,21 +47,6 @@ class VatNumberTest extends TestCase $vat_checker = new VatNumberCheck($vat_number, $country_code); $result = $vat_checker->run(); - if (isset($result['valid'])) { - if ($result['valid']) { - echo "The VAT number is valid.\n"; - echo "Name: " . $result['name'] . "\n"; - echo "Address: " . $result['address'] . "\n"; - } else { - echo "The VAT number is invalid.\n"; - } - } else { - echo "Error: " . $result['error'] . "\n"; - } - - $this->assertFalse($result['valid']); - + $this->assertFalse($result->isValid()); } - } -