diff --git a/app/DataMapper/Tax/InvoiceTaxData.php b/app/DataMapper/Tax/InvoiceTaxData.php index f8e8667109..1c265f37fb 100644 --- a/app/DataMapper/Tax/InvoiceTaxData.php +++ b/app/DataMapper/Tax/InvoiceTaxData.php @@ -24,5 +24,8 @@ class InvoiceTaxData public function __construct(public Response $origin) { + foreach($origin as $key => $value) { + $this->{$key} = $value; + } } } diff --git a/app/DataMapper/Tax/RuleInterface.php b/app/DataMapper/Tax/RuleInterface.php index 6d322aea20..a9403451b5 100644 --- a/app/DataMapper/Tax/RuleInterface.php +++ b/app/DataMapper/Tax/RuleInterface.php @@ -13,7 +13,7 @@ namespace App\DataMapper\Tax; interface RuleInterface { - public function run(); + public function tax(); public function taxByType(?int $type); diff --git a/app/DataMapper/Tax/ZipTax/Response.php b/app/DataMapper/Tax/ZipTax/Response.php index 457ea18d49..73b510b749 100644 --- a/app/DataMapper/Tax/ZipTax/Response.php +++ b/app/DataMapper/Tax/ZipTax/Response.php @@ -93,7 +93,7 @@ class Response public float $district5UseTax = 0; public string $originDestination = ""; - public function __construct(?array $data) + public function __construct($data) { foreach($data as $key => $value){ diff --git a/app/DataMapper/Tax/de/Rule.php b/app/DataMapper/Tax/de/Rule.php index 4332abfa81..e379a4ce47 100644 --- a/app/DataMapper/Tax/de/Rule.php +++ b/app/DataMapper/Tax/de/Rule.php @@ -11,7 +11,9 @@ namespace App\DataMapper\Tax\de; +use App\Models\Product; use App\DataMapper\Tax\RuleInterface; +use App\DataMapper\Tax\ZipTax\Response; class Rule implements RuleInterface { @@ -89,8 +91,91 @@ class Rule implements RuleInterface public bool $foreign_consumer_tax_exempt = true; - public function run() + public string $tax_name1 = ''; + public float $tax_rate1 = 0; + + public string $tax_name2 = ''; + public float $tax_rate2 = 0; + + public string $tax_name3 = ''; + public float $tax_rate3 = 0; + + + public function __construct(public Response $tax_data) { + $this->tax_data = $tax_data; + } + + public function tax(): self + { + $this->tax_name1 = 21; + $this->tax_rate1 = "VAT"; + + return $this; + + } + + public function taxByType(?int $product_tax_type): self + { + if(!$product_tax_type) + return $this; + + match($product_tax_type){ + Product::PRODUCT_TAX_EXEMPT => $this->taxExempt(), + Product::PRODUCT_TYPE_DIGITAL => $this->taxDigital(), + Product::PRODUCT_TYPE_SERVICE => $this->taxService(), + Product::PRODUCT_TYPE_SHIPPING => $this->taxShipping(), + Product::PRODUCT_TYPE_PHYSICAL => $this->taxPhysical(), + default => $this->default(), + }; + + return $this; + } + + public function taxExempt(): self + { + $this->tax_name1 = ''; + $this->tax_rate1 = 0; + + return $this; + } + + public function taxDigital(): self + { + $this->tax(); + + return $this; + } + + public function taxService(): self + { + if($this->tax_data->txbService == 'Y') + $this->tax(); + + return $this; + } + + public function taxShipping(): self + { + if($this->tax_data->txbFreight == 'Y') + $this->tax(); + + return $this; + } + + public function taxPhysical(): self + { + $this->tax(); + + return $this; + } + + public function default(): self + { + + $this->tax_name1 = ''; + $this->tax_rate1 = 0; + return $this; } } diff --git a/app/DataMapper/Tax/us/Rule.php b/app/DataMapper/Tax/us/Rule.php index d6738bb8a9..6259faeb43 100644 --- a/app/DataMapper/Tax/us/Rule.php +++ b/app/DataMapper/Tax/us/Rule.php @@ -13,6 +13,7 @@ namespace App\DataMapper\Tax\us; use App\Models\Product; use App\DataMapper\Tax\RuleInterface; +use App\DataMapper\Tax\ZipTax\Response; class Rule implements RuleInterface { @@ -79,22 +80,25 @@ class Rule implements RuleInterface public string $tax_name3 = ''; public float $tax_rate3 = 0; - public function __construct(public RuleInterface $tax_data) + public function __construct(public Response $tax_data) { $this->tax_data = $tax_data; + nlog($tax_data); } - public function run() + public function tax(): self { - $this->tax_name1 = $this->tax_data->taxSales * 100; - $this->tax_rate1 = "{$this->tax_data->geoState} Sales Tax"; + $this->tax_rate1 = $this->tax_data->taxSales * 100; + $this->tax_name1 = "{$this->tax_data->geoState} Sales Tax"; + + return $this; } - public function taxByType(?int $product_tax_type) + public function taxByType(?int $product_tax_type): self { if(!$product_tax_type) - return; + return $this; match($product_tax_type){ Product::PRODUCT_TAX_EXEMPT => $this->taxExempt(), @@ -108,39 +112,50 @@ class Rule implements RuleInterface return $this; } - public function taxExempt() + public function taxExempt(): self { $this->tax_name1 = ''; $this->tax_rate1 = 0; + + return $this; } - public function taxDigital() + public function taxDigital(): self { - $this->tax_name1 = ''; - $this->tax_rate1 = 0; + $this->tax(); + + return $this; } - public function taxService() + public function taxService(): self { if($this->tax_data->txbService == 'Y') - $this->run(); + $this->tax(); + + return $this; } - public function taxShipping() + public function taxShipping(): self { - if($this->tax_data->txbFreight == 'N') - $this->run(); + if($this->tax_data->txbFreight == 'Y') + $this->tax(); + + return $this; } - public function taxPhysical() + public function taxPhysical(): self { + $this->tax(); + + return $this; + } + + public function default(): self + { + $this->tax_name1 = ''; $this->tax_rate1 = 0; - } - public function default() - { - $this->tax_name1 = ''; - $this->tax_rate1 = 0; + return $this; } } diff --git a/app/Helpers/Invoice/InvoiceItemSum.php b/app/Helpers/Invoice/InvoiceItemSum.php index 1863edfed5..e6d389a468 100644 --- a/app/Helpers/Invoice/InvoiceItemSum.php +++ b/app/Helpers/Invoice/InvoiceItemSum.php @@ -17,6 +17,7 @@ use App\DataMapper\InvoiceItem; use App\DataMapper\BaseSettings; use App\DataMapper\Tax\RuleInterface; use App\Utils\Traits\NumberFormatter; +use App\DataMapper\Tax\ZipTax\Response; class InvoiceItemSum { @@ -55,7 +56,7 @@ class InvoiceItemSum private bool $calc_tax = false; private RuleInterface $rule; - + public function __construct($invoice) { $this->tax_collection = collect([]); @@ -65,7 +66,7 @@ class InvoiceItemSum if ($this->invoice->client) { $this->currency = $this->invoice->client->currency(); $this->client = $this->invoice->client; - $this->calc_tax = $this->shouldCalculateTax(); + $this->shouldCalculateTax(); } else { $this->currency = $this->invoice->vendor->currency(); } @@ -99,22 +100,29 @@ class InvoiceItemSum return $this; } - private function shouldCalculateTax(): bool + private function shouldCalculateTax(): self { - if(!$this->invoice->company->calculate_taxes || $this->client->is_tax_exempt) - return false; + if (!$this->invoice->company->calculate_taxes || $this->client->is_tax_exempt) { + $this->calc_tax = false; + nlog("returning false"); + return $this; + } - if(in_array($this->client->country->iso_3166_2, ['US'])){ //only calculate for USA - + if (in_array($this->client->country->iso_3166_2, ['US'])) { //only calculate for USA $class = "App\DataMapper\Tax\\".strtolower($this->client->country->iso_3166_2)."\\Rule"; - $this->rule = new $class($this->invoice->tax_data); + $tax_data = new Response($this->invoice->tax_data); - return true; - - } + $this->rule = new $class($tax_data); - return false; + nlog("returning true"); + + $this->calc_tax = true; + + return $this; + } + + return $this; } private function push() @@ -149,20 +157,17 @@ class InvoiceItemSum return $this; } - + /** - * Attempts to calculate taxes based on the clients location + * Attempts to calculate taxes based on the clients location * * @return self */ - private function calcTaxesAutomatically(): self + private function calcTaxesAutomatically(): self { - if($this->invoice->company->tax_all_products || $this->item->tax_id != ''){ - $this->rule->run(); - - } - else { - + if ($this->invoice->company->tax_all_products || $this->item->tax_id != '') { + $this->rule->tax(); + } else { $this->rule->taxByType($this->item->tax_id); } @@ -180,8 +185,9 @@ class InvoiceItemSum private function calcTaxes() { - if($this->calc_tax) + if ($this->calc_tax) { $this->calcTaxesAutomatically(); + } $item_tax = 0; @@ -190,7 +196,6 @@ class InvoiceItemSum $item_tax += $item_tax_rate1_total; - // if($item_tax_rate1_total != 0) if (strlen($this->item->tax_name1) > 1) { $this->groupTax($this->item->tax_name1, $this->item->tax_rate1, $item_tax_rate1_total); } diff --git a/phpstan.neon b/phpstan.neon index 772e2bd19f..0324dc8d09 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,4 +2,5 @@ parameters: level: 2 paths: - app -# - tests + universalObjectCratesClasses: + - App\DataMapper\Tax\RuleInterface \ No newline at end of file diff --git a/tests/Unit/Tax/SumTaxTest.php b/tests/Unit/Tax/SumTaxTest.php index 98dc46fc24..91e50be5aa 100644 --- a/tests/Unit/Tax/SumTaxTest.php +++ b/tests/Unit/Tax/SumTaxTest.php @@ -14,11 +14,14 @@ namespace Tests\Unit\Tax; use Tests\TestCase; use App\Models\Client; use App\Models\Invoice; +use App\Models\Product; use Tests\MockAccountData; +use App\DataMapper\InvoiceItem; use App\DataMapper\Tax\ClientTaxData; use App\DataMapper\Tax\CompanyTaxData; use App\DataMapper\Tax\InvoiceTaxData; use App\DataMapper\Tax\ZipTax\Response; +use App\Factory\InvoiceFactory; use Illuminate\Routing\Middleware\ThrottleRequests; use Illuminate\Foundation\Testing\DatabaseTransactions; @@ -86,10 +89,89 @@ class SumTaxTest extends TestCase } - public function testCalcLogic() + + public function testCalcInvoiceNoTax() { + $this->company->calculate_taxes = false; + $this->company->tax_all_products = true; + $this->company->save(); + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'country_id' => 840, + ]); + + $invoice = InvoiceFactory::create($this->company->id, $this->user->id); + $invoice->client_id = $client->id; + $invoice->uses_inclusive_taxes = false; + + $line_items = []; + + $invoice->tax_data = new InvoiceTaxData($this->response); + + $line_item = new InvoiceItem(); + $line_item->quantity = 1; + $line_item->cost = 10; + $line_item->product_key = 'Test'; + $line_item->notes = 'Test'; + $line_item->tax_id = Product::PRODUCT_TYPE_PHYSICAL; + $line_items[] = $line_item; + + $invoice->line_items = $line_items; + $invoice->save(); + + $invoice = $invoice->calc()->getInvoice(); + + $line_items = $invoice->line_items; + + + $this->assertEquals(10, $invoice->amount); + $this->assertEquals("", $line_items[0]->tax_name1); + $this->assertEquals(0, $line_items[0]->tax_rate1); + } + + + public function testCalcInvoiceTax() + { + $this->company->calculate_taxes = true; $this->company->tax_all_products = true; + $this->company->save(); + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'country_id' => 840, + ]); + + $invoice = InvoiceFactory::create($this->company->id, $this->user->id); + $invoice->client_id = $client->id; + $invoice->uses_inclusive_taxes = false; + + $line_items = []; + + $invoice->tax_data = new InvoiceTaxData($this->response); + + $line_item = new InvoiceItem; + $line_item->quantity = 1; + $line_item->cost = 10; + $line_item->product_key = 'Test'; + $line_item->notes = 'Test'; + $line_item->tax_id = Product::PRODUCT_TYPE_PHYSICAL; + $line_items[] = $line_item; + + $invoice->line_items = $line_items; + $invoice->save(); + + $invoice = $invoice->calc()->getInvoice(); + + $line_items = $invoice->line_items; + + + $this->assertEquals(10.88, $invoice->amount); + $this->assertEquals("CA Sales Tax", $line_items[0]->tax_name1); + $this->assertEquals(8.75, $line_items[0]->tax_rate1); } public function testTaxOnCompany()