From d4716048624208d6e6131a59c70318ce829417ca Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 29 Mar 2023 14:23:06 +1100 Subject: [PATCH] Refactor for tax structure --- app/DataMapper/Tax/BaseRule.php | 45 +++++ app/DataMapper/Tax/{de => DE}/Rule.php | 64 ++++++- app/DataMapper/Tax/RuleInterface.php | 4 + app/DataMapper/Tax/{us => US}/Rule.php | 2 +- app/Helpers/Invoice/InvoiceItemSum.php | 2 +- app/Models/Account.php | 4 + app/Models/BankAccount.php | 1 + app/Models/BankIntegration.php | 1 + app/Models/Client.php | 17 ++ app/Models/ClientContact.php | 5 + app/Models/Company.php | 44 +++++ app/Models/CompanyGateway.php | 1 + app/Models/CompanyUser.php | 3 + app/Models/Credit.php | 7 + app/Models/Expense.php | 1 + app/Models/GatewayType.php | 1 + app/Models/GroupSetting.php | 2 + app/Models/Invoice.php | 9 + app/Models/Payment.php | 5 + app/Models/Product.php | 1 + app/Models/Project.php | 2 + app/Models/Proposal.php | 1 + app/Models/PurchaseOrder.php | 6 + app/Models/Quote.php | 4 + app/Models/RecurringExpense.php | 1 + app/Models/RecurringInvoice.php | 5 + app/Models/RecurringQuote.php | 5 + app/Models/Task.php | 1 + app/Models/User.php | 7 + app/Models/Vendor.php | 4 + app/Models/VendorContact.php | 2 + app/Services/Tax/Providers/EuTax.php | 165 ------------------ app/Services/Tax/TaxService.php | 12 +- ...054758_add_client_is_exempt_from_taxes.php | 3 +- tests/Unit/Tax/EuTaxTest.php | 102 ++++++++--- 35 files changed, 340 insertions(+), 199 deletions(-) rename app/DataMapper/Tax/{de => DE}/Rule.php (52%) rename app/DataMapper/Tax/{us => US}/Rule.php (98%) delete mode 100644 app/Services/Tax/Providers/EuTax.php diff --git a/app/DataMapper/Tax/BaseRule.php b/app/DataMapper/Tax/BaseRule.php index fd7cfea6da3b..4cda526ee49e 100644 --- a/app/DataMapper/Tax/BaseRule.php +++ b/app/DataMapper/Tax/BaseRule.php @@ -18,10 +18,45 @@ class BaseRule implements RuleInterface { /** EU TAXES */ public bool $consumer_tax_exempt = false; + public bool $business_tax_exempt = true; + public bool $eu_business_tax_exempt = true; + public bool $foreign_business_tax_exempt = true; + public bool $foreign_consumer_tax_exempt = true; + + public array $eu_country_codes = [ + 'AT', // Austria + 'BE', // Belgium + 'BG', // Bulgaria + 'CY', // Cyprus + 'CZ', // Czech Republic + 'DE', // Germany + 'DK', // Denmark + 'EE', // Estonia + 'ES', // Spain + 'FI', // Finland + 'FR', // France + 'GR', // Greece + 'HR', // Croatia + 'HU', // Hungary + 'IE', // Ireland + 'IT', // Italy + 'LT', // Lithuania + 'LU', // Luxembourg + 'LV', // Latvia + 'MT', // Malta + 'NL', // Netherlands + 'PL', // Poland + 'PT', // Portugal + 'RO', // Romania + 'SE', // Sweden + 'SI', // Slovenia + 'SK', // Slovakia + ]; + /** EU TAXES */ @@ -45,6 +80,11 @@ class BaseRule implements RuleInterface { } + public function init(): self + { + return $this; + } + public function setClient(Client $client): self { $this->client = $client; @@ -108,4 +148,9 @@ class BaseRule implements RuleInterface { return $this; } + + public function calculateRates(): self + { + return $this; + } } diff --git a/app/DataMapper/Tax/de/Rule.php b/app/DataMapper/Tax/DE/Rule.php similarity index 52% rename from app/DataMapper/Tax/de/Rule.php rename to app/DataMapper/Tax/DE/Rule.php index 54f33978ef07..cd1b032aae4b 100644 --- a/app/DataMapper/Tax/de/Rule.php +++ b/app/DataMapper/Tax/DE/Rule.php @@ -9,16 +9,20 @@ * @license https://www.elastic.co/licensing/elastic-license */ -namespace App\DataMapper\Tax\de; +namespace App\DataMapper\Tax\DE; use App\Models\Client; use App\Models\Product; +use Illuminate\Support\Str; use App\DataMapper\Tax\BaseRule; use App\DataMapper\Tax\RuleInterface; use App\DataMapper\Tax\ZipTax\Response; class Rule extends BaseRule implements RuleInterface { + public string $vendor_country_code = 'DE'; + + public string $client_country_code = 'DE'; public bool $consumer_tax_exempt = false; @@ -41,7 +45,11 @@ class Rule extends BaseRule implements RuleInterface public string $tax_name3 = ''; public float $tax_rate3 = 0; - + + public float $vat_rate = 0; + + public float $reduced_vat_rate = 0; + protected ?Client $client; protected ?Response $tax_data; @@ -50,6 +58,14 @@ class Rule extends BaseRule implements RuleInterface { } + public function init(): self + { + $this->client_country_code = $this->client->shipping_country ? $this->client->shipping_country->iso_3166_2 : $this->client->country->iso_3166_2; + $this->calculateRates(); + + return $this; + } + public function setClient(Client $client): self { $this->client = $client; @@ -103,7 +119,7 @@ class Rule extends BaseRule implements RuleInterface public function taxReduced(): self { - $this->tax_rate1 = $this->vat_reduced_rate; + $this->tax_rate1 = $this->reduced_vat_rate; $this->tax_name1 = 'ermäßigte MwSt.'; return $this; @@ -159,4 +175,46 @@ class Rule extends BaseRule implements RuleInterface return $this; } + public function calculateRates(): self + { + + if( + (($this->vendor_country_code == $this->client_country_code) && $this->client->has_valid_vat_number && $this->business_tax_exempt) || //same country / exempt for tax / valid vat number + (in_array($this->client_country_code, $this->eu_country_codes) && $this->client->has_valid_vat_number && $this->eu_business_tax_exempt) //eu country / exempt for tax / valid vat number + ) { + $this->vat_rate = 0; + $this->reduced_vat_rate = 0; + nlog("euro zone and tax exempt"); + } + elseif(!in_array(strtoupper($this->client_country_code), $this->eu_country_codes) && ($this->foreign_consumer_tax_exempt || $this->foreign_business_tax_exempt)) //foreign + tax exempt + { + $this->vat_rate = 0; + $this->reduced_vat_rate = 0; + nlog("foreign and tax exempt"); + } + elseif(in_array(strtoupper($this->client_country_code), $this->eu_country_codes) && !$this->client->has_valid_vat_number) //eu country / no valid vat + { + if(($this->vendor_country_code != $this->client_country_code) && $this->client->company->tax_data->regions->EU->has_sales_above_threshold) + { + $this->vat_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->vat_rate; + $this->reduced_vat_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->reduced_vat_rate; + nlog("eu zone with sales above threshold"); + } + else { + $this->vat_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->vat_rate; + $this->reduced_vat_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->reduced_vat_rate; + nlog("same eu country with"); + } + } + else { + nlog("default tax"); + $this->vat_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->vat_rate; + $this->reduced_vat_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->reduced_vat_rate; + } + + return $this; + + } + + } diff --git a/app/DataMapper/Tax/RuleInterface.php b/app/DataMapper/Tax/RuleInterface.php index 8ee20a3866a1..91c5f6ed113a 100644 --- a/app/DataMapper/Tax/RuleInterface.php +++ b/app/DataMapper/Tax/RuleInterface.php @@ -16,6 +16,8 @@ use App\DataMapper\Tax\ZipTax\Response; interface RuleInterface { + public function init(); + public function tax(); public function taxByType(?int $type); @@ -39,4 +41,6 @@ interface RuleInterface public function setClient(Client $client); public function setTaxData(Response $tax_data); + + public function calculateRates(); } \ No newline at end of file diff --git a/app/DataMapper/Tax/us/Rule.php b/app/DataMapper/Tax/US/Rule.php similarity index 98% rename from app/DataMapper/Tax/us/Rule.php rename to app/DataMapper/Tax/US/Rule.php index c465b7a82a87..5531a341cb24 100644 --- a/app/DataMapper/Tax/us/Rule.php +++ b/app/DataMapper/Tax/US/Rule.php @@ -9,7 +9,7 @@ * @license https://www.elastic.co/licensing/elastic-license */ -namespace App\DataMapper\Tax\us; +namespace App\DataMapper\Tax\US; use App\Models\Client; use App\Models\Product; diff --git a/app/Helpers/Invoice/InvoiceItemSum.php b/app/Helpers/Invoice/InvoiceItemSum.php index 6e859b2de5a9..6070e176f3ee 100644 --- a/app/Helpers/Invoice/InvoiceItemSum.php +++ b/app/Helpers/Invoice/InvoiceItemSum.php @@ -199,7 +199,7 @@ class InvoiceItemSum if ($this->invoice->company->tax_all_products || $this->item->tax_id != '') { $this->rule->tax(); } else { - $this->rule->taxByType($this->item->tax_id); + $this->rule->init()->taxByType($this->item->tax_id); } $this->item->tax_name1 = $this->rule->tax_name1; diff --git a/app/Models/Account.php b/app/Models/Account.php index a603a2582c4e..90c3a8b23589 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -158,6 +158,10 @@ use Laracasts\Presenter\PresentableTrait; * @property-read \Illuminate\Database\Eloquent\Collection $companies * @property-read \Illuminate\Database\Eloquent\Collection $company_users * @property-read \Illuminate\Database\Eloquent\Collection $users + * @property-read \Illuminate\Database\Eloquent\Collection $bank_integrations + * @property-read \Illuminate\Database\Eloquent\Collection $companies + * @property-read \Illuminate\Database\Eloquent\Collection $company_users + * @property-read \Illuminate\Database\Eloquent\Collection $users * @mixin \Eloquent */ class Account extends BaseModel diff --git a/app/Models/BankAccount.php b/app/Models/BankAccount.php index 0762baf121dc..cf20fc461d3c 100644 --- a/app/Models/BankAccount.php +++ b/app/Models/BankAccount.php @@ -39,6 +39,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property-read \Illuminate\Database\Eloquent\Collection $bank_subaccounts * @property-read \Illuminate\Database\Eloquent\Collection $bank_subaccounts * @property-read \Illuminate\Database\Eloquent\Collection $bank_subaccounts + * @property-read \Illuminate\Database\Eloquent\Collection $bank_subaccounts * @mixin \Eloquent */ class BankAccount extends BaseModel diff --git a/app/Models/BankIntegration.php b/app/Models/BankIntegration.php index e5908e34a506..82d2d7200264 100644 --- a/app/Models/BankIntegration.php +++ b/app/Models/BankIntegration.php @@ -82,6 +82,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property-read \Illuminate\Database\Eloquent\Collection $transactions * @property-read \Illuminate\Database\Eloquent\Collection $transactions * @property-read \Illuminate\Database\Eloquent\Collection $transactions + * @property-read \Illuminate\Database\Eloquent\Collection $transactions * @mixin \Eloquent */ class BankIntegration extends BaseModel diff --git a/app/Models/Client.php b/app/Models/Client.php index 36286c79229c..e4907deaf4a8 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -291,6 +291,23 @@ use Laracasts\Presenter\PresentableTrait; * @property-read \Illuminate\Database\Eloquent\Collection $recurring_invoices * @property-read \Illuminate\Database\Eloquent\Collection $system_logs * @property-read \Illuminate\Database\Eloquent\Collection $tasks + * @property-read \Illuminate\Database\Eloquent\Collection $activities + * @property-read \Illuminate\Database\Eloquent\Collection $company_ledger + * @property-read \Illuminate\Database\Eloquent\Collection $contacts + * @property-read \Illuminate\Database\Eloquent\Collection $credits + * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Database\Eloquent\Collection $expenses + * @property-read \Illuminate\Database\Eloquent\Collection $gateway_tokens + * @property-read \Illuminate\Database\Eloquent\Collection $invoices + * @property-read \Illuminate\Database\Eloquent\Collection $ledger + * @property-read \Illuminate\Database\Eloquent\Collection $payments + * @property-read \Illuminate\Database\Eloquent\Collection $primary_contact + * @property-read \Illuminate\Database\Eloquent\Collection $projects + * @property-read \Illuminate\Database\Eloquent\Collection $quotes + * @property-read \Illuminate\Database\Eloquent\Collection $recurring_expenses + * @property-read \Illuminate\Database\Eloquent\Collection $recurring_invoices + * @property-read \Illuminate\Database\Eloquent\Collection $system_logs + * @property-read \Illuminate\Database\Eloquent\Collection $tasks * @mixin \Eloquent */ class Client extends BaseModel implements HasLocalePreference diff --git a/app/Models/ClientContact.php b/app/Models/ClientContact.php index 4060c4588c81..361ab3101e73 100644 --- a/app/Models/ClientContact.php +++ b/app/Models/ClientContact.php @@ -158,6 +158,11 @@ use Laracasts\Presenter\PresentableTrait; * @property-read \Illuminate\Notifications\DatabaseNotificationCollection $notifications * @property-read \Illuminate\Database\Eloquent\Collection $quote_invitations * @property-read \Illuminate\Database\Eloquent\Collection $recurring_invoice_invitations + * @property-read \Illuminate\Database\Eloquent\Collection $credit_invitations + * @property-read \Illuminate\Database\Eloquent\Collection $invoice_invitations + * @property-read \Illuminate\Notifications\DatabaseNotificationCollection $notifications + * @property-read \Illuminate\Database\Eloquent\Collection $quote_invitations + * @property-read \Illuminate\Database\Eloquent\Collection $recurring_invoice_invitations * @mixin \Eloquent */ class ClientContact extends Authenticatable implements HasLocalePreference diff --git a/app/Models/Company.php b/app/Models/Company.php index 8abf25eaf6cd..6a71b7d519ce 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -596,6 +596,50 @@ use Laracasts\Presenter\PresentableTrait; * @property-read \Illuminate\Database\Eloquent\Collection $users * @property-read \Illuminate\Database\Eloquent\Collection $vendors * @property-read \Illuminate\Database\Eloquent\Collection $webhooks + * @property-read \Illuminate\Database\Eloquent\Collection $activities + * @property-read \Illuminate\Database\Eloquent\Collection $all_activities + * @property-read \Illuminate\Database\Eloquent\Collection $all_documents + * @property-read \Illuminate\Database\Eloquent\Collection $bank_integrations + * @property-read \Illuminate\Database\Eloquent\Collection $bank_transaction_rules + * @property-read \Illuminate\Database\Eloquent\Collection $bank_transactions + * @property-read \Illuminate\Database\Eloquent\Collection $client_contacts + * @property-read \Illuminate\Database\Eloquent\Collection $client_gateway_tokens + * @property-read \Illuminate\Database\Eloquent\Collection $clients + * @property-read \Illuminate\Database\Eloquent\Collection $company_gateways + * @property-read \Illuminate\Database\Eloquent\Collection $company_users + * @property-read \Illuminate\Database\Eloquent\Collection $contacts + * @property-read \Illuminate\Database\Eloquent\Collection $credits + * @property-read \Illuminate\Database\Eloquent\Collection $designs + * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Database\Eloquent\Collection $expense_categories + * @property-read \Illuminate\Database\Eloquent\Collection $expenses + * @property-read \Illuminate\Database\Eloquent\Collection $group_settings + * @property-read \Illuminate\Database\Eloquent\Collection $groups + * @property-read \Illuminate\Database\Eloquent\Collection $invoices + * @property-read \Illuminate\Database\Eloquent\Collection $ledger + * @property-read \Illuminate\Database\Eloquent\Collection $payment_terms + * @property-read \Illuminate\Database\Eloquent\Collection $payments + * @property-read \Illuminate\Database\Eloquent\Collection $products + * @property-read \Illuminate\Database\Eloquent\Collection $projects + * @property-read \Illuminate\Database\Eloquent\Collection $purchase_orders + * @property-read \Illuminate\Database\Eloquent\Collection $quotes + * @property-read \Illuminate\Database\Eloquent\Collection $recurring_expenses + * @property-read \Illuminate\Database\Eloquent\Collection $recurring_invoices + * @property-read \Illuminate\Database\Eloquent\Collection $schedulers + * @property-read \Illuminate\Database\Eloquent\Collection $subscriptions + * @property-read \Illuminate\Database\Eloquent\Collection $system_log_relation + * @property-read \Illuminate\Database\Eloquent\Collection $system_logs + * @property-read \Illuminate\Database\Eloquent\Collection $task_schedulers + * @property-read \Illuminate\Database\Eloquent\Collection $task_statuses + * @property-read \Illuminate\Database\Eloquent\Collection $tasks + * @property-read \Illuminate\Database\Eloquent\Collection $tax_rates + * @property-read \Illuminate\Database\Eloquent\Collection $tokens + * @property-read \Illuminate\Database\Eloquent\Collection $tokens_hashed + * @property-read \Illuminate\Database\Eloquent\Collection $user_designs + * @property-read \Illuminate\Database\Eloquent\Collection $user_payment_terms + * @property-read \Illuminate\Database\Eloquent\Collection $users + * @property-read \Illuminate\Database\Eloquent\Collection $vendors + * @property-read \Illuminate\Database\Eloquent\Collection $webhooks * @mixin \Eloquent */ class Company extends BaseModel diff --git a/app/Models/CompanyGateway.php b/app/Models/CompanyGateway.php index 4b51a9d91fc9..b19b65d98071 100644 --- a/app/Models/CompanyGateway.php +++ b/app/Models/CompanyGateway.php @@ -99,6 +99,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property-read \Illuminate\Database\Eloquent\Collection $client_gateway_tokens * @property-read \Illuminate\Database\Eloquent\Collection $client_gateway_tokens * @property-read \Illuminate\Database\Eloquent\Collection $client_gateway_tokens + * @property-read \Illuminate\Database\Eloquent\Collection $client_gateway_tokens * @mixin \Eloquent */ class CompanyGateway extends BaseModel diff --git a/app/Models/CompanyUser.php b/app/Models/CompanyUser.php index fb1c274b66d0..b88e459c54ab 100644 --- a/app/Models/CompanyUser.php +++ b/app/Models/CompanyUser.php @@ -88,6 +88,9 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property-read \Illuminate\Database\Eloquent\Collection $token * @property-read \Illuminate\Database\Eloquent\Collection $tokens * @property-read \Illuminate\Database\Eloquent\Collection $users + * @property-read \Illuminate\Database\Eloquent\Collection $token + * @property-read \Illuminate\Database\Eloquent\Collection $tokens + * @property-read \Illuminate\Database\Eloquent\Collection $users * @mixin \Eloquent */ class CompanyUser extends Pivot diff --git a/app/Models/Credit.php b/app/Models/Credit.php index c86dc743c9a6..5f3255a1df57 100644 --- a/app/Models/Credit.php +++ b/app/Models/Credit.php @@ -234,6 +234,13 @@ use Laracasts\Presenter\PresentableTrait; * @property-read \Illuminate\Database\Eloquent\Collection $invitations * @property-read \Illuminate\Database\Eloquent\Collection $invoices * @property-read \Illuminate\Database\Eloquent\Collection $payments + * @property-read \Illuminate\Database\Eloquent\Collection $activities + * @property-read \Illuminate\Database\Eloquent\Collection $company_ledger + * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Database\Eloquent\Collection $history + * @property-read \Illuminate\Database\Eloquent\Collection $invitations + * @property-read \Illuminate\Database\Eloquent\Collection $invoices + * @property-read \Illuminate\Database\Eloquent\Collection $payments * @mixin \Eloquent */ class Credit extends BaseModel diff --git a/app/Models/Expense.php b/app/Models/Expense.php index fc92194c8dec..f4a48d1cf35a 100644 --- a/app/Models/Expense.php +++ b/app/Models/Expense.php @@ -137,6 +137,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Database\Eloquent\Collection $documents * @mixin \Eloquent */ class Expense extends BaseModel diff --git a/app/Models/GatewayType.php b/app/Models/GatewayType.php index 0b525e6f7ba1..98a9e6e71439 100644 --- a/app/Models/GatewayType.php +++ b/app/Models/GatewayType.php @@ -35,6 +35,7 @@ namespace App\Models; * @property-read \Illuminate\Database\Eloquent\Collection $payment_methods * @property-read \Illuminate\Database\Eloquent\Collection $payment_methods * @property-read \Illuminate\Database\Eloquent\Collection $payment_methods + * @property-read \Illuminate\Database\Eloquent\Collection $payment_methods * @mixin \Eloquent */ class GatewayType extends StaticModel diff --git a/app/Models/GroupSetting.php b/app/Models/GroupSetting.php index efbf46b55716..b736b4c6a72e 100644 --- a/app/Models/GroupSetting.php +++ b/app/Models/GroupSetting.php @@ -68,6 +68,8 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read \Illuminate\Database\Eloquent\Collection $clients * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Database\Eloquent\Collection $clients + * @property-read \Illuminate\Database\Eloquent\Collection $documents * @mixin \Eloquent */ class GroupSetting extends StaticModel diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 4ae6a4f577a2..ffce822140e2 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -268,6 +268,15 @@ use Laracasts\Presenter\PresentableTrait; * @property-read \Illuminate\Database\Eloquent\Collection $invitations * @property-read \Illuminate\Database\Eloquent\Collection $payments * @property-read \Illuminate\Database\Eloquent\Collection $tasks + * @property-read \Illuminate\Database\Eloquent\Collection $activities + * @property-read \Illuminate\Database\Eloquent\Collection $company_ledger + * @property-read \Illuminate\Database\Eloquent\Collection $credits + * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Database\Eloquent\Collection $expenses + * @property-read \Illuminate\Database\Eloquent\Collection $history + * @property-read \Illuminate\Database\Eloquent\Collection $invitations + * @property-read \Illuminate\Database\Eloquent\Collection $payments + * @property-read \Illuminate\Database\Eloquent\Collection $tasks * @mixin \Eloquent */ class Invoice extends BaseModel diff --git a/app/Models/Payment.php b/app/Models/Payment.php index 6117d931b42d..aed3fbc295d2 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -167,6 +167,11 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read \Illuminate\Database\Eloquent\Collection $invoices * @property-read \Illuminate\Database\Eloquent\Collection $paymentables + * @property-read \Illuminate\Database\Eloquent\Collection $company_ledger + * @property-read \Illuminate\Database\Eloquent\Collection $credits + * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Database\Eloquent\Collection $invoices + * @property-read \Illuminate\Database\Eloquent\Collection $paymentables * @mixin \Eloquent */ class Payment extends BaseModel diff --git a/app/Models/Product.php b/app/Models/Product.php index 54f2f8a95cb1..b04767ae6146 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -105,6 +105,7 @@ use League\CommonMark\CommonMarkConverter; * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Database\Eloquent\Collection $documents * @mixin \Eloquent */ class Product extends BaseModel diff --git a/app/Models/Project.php b/app/Models/Project.php index 2d0823c9f2fb..a4c837c69f80 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -86,6 +86,8 @@ use Laracasts\Presenter\PresentableTrait; * @property-read \Illuminate\Database\Eloquent\Collection $tasks * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read \Illuminate\Database\Eloquent\Collection $tasks + * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Database\Eloquent\Collection $tasks * @mixin \Eloquent */ class Project extends BaseModel diff --git a/app/Models/Proposal.php b/app/Models/Proposal.php index d22141aa991a..7b86e0ed4b9c 100644 --- a/app/Models/Proposal.php +++ b/app/Models/Proposal.php @@ -35,6 +35,7 @@ use App\Utils\Traits\MakesHash; * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Database\Eloquent\Collection $documents * @mixin \Eloquent */ class Proposal extends BaseModel diff --git a/app/Models/PurchaseOrder.php b/app/Models/PurchaseOrder.php index d40ff825e686..c44cf6abc865 100644 --- a/app/Models/PurchaseOrder.php +++ b/app/Models/PurchaseOrder.php @@ -222,6 +222,12 @@ use Illuminate\Support\Facades\Storage; * @property-read \Illuminate\Database\Eloquent\Collection $invitations * @property-read \Illuminate\Database\Eloquent\Collection $invoices * @property-read \Illuminate\Database\Eloquent\Collection $payments + * @property-read \Illuminate\Database\Eloquent\Collection $activities + * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Database\Eloquent\Collection $history + * @property-read \Illuminate\Database\Eloquent\Collection $invitations + * @property-read \Illuminate\Database\Eloquent\Collection $invoices + * @property-read \Illuminate\Database\Eloquent\Collection $payments * @mixin \Eloquent */ class PurchaseOrder extends BaseModel diff --git a/app/Models/Quote.php b/app/Models/Quote.php index df4c252c8fbf..1c0940c88d41 100644 --- a/app/Models/Quote.php +++ b/app/Models/Quote.php @@ -209,6 +209,10 @@ use Laracasts\Presenter\PresentableTrait; * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read \Illuminate\Database\Eloquent\Collection $history * @property-read \Illuminate\Database\Eloquent\Collection $invitations + * @property-read \Illuminate\Database\Eloquent\Collection $activities + * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Database\Eloquent\Collection $history + * @property-read \Illuminate\Database\Eloquent\Collection $invitations * @mixin \Eloquent */ class Quote extends BaseModel diff --git a/app/Models/RecurringExpense.php b/app/Models/RecurringExpense.php index c197081c4fac..f96a0d9a85a4 100644 --- a/app/Models/RecurringExpense.php +++ b/app/Models/RecurringExpense.php @@ -146,6 +146,7 @@ use Illuminate\Support\Carbon; * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Database\Eloquent\Collection $documents * @mixin \Eloquent */ class RecurringExpense extends BaseModel diff --git a/app/Models/RecurringInvoice.php b/app/Models/RecurringInvoice.php index be2b5fb7655b..3667b25583f3 100644 --- a/app/Models/RecurringInvoice.php +++ b/app/Models/RecurringInvoice.php @@ -214,6 +214,11 @@ use Laracasts\Presenter\PresentableTrait; * @property-read \Illuminate\Database\Eloquent\Collection $history * @property-read \Illuminate\Database\Eloquent\Collection $invitations * @property-read \Illuminate\Database\Eloquent\Collection $invoices + * @property-read \Illuminate\Database\Eloquent\Collection $activities + * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Database\Eloquent\Collection $history + * @property-read \Illuminate\Database\Eloquent\Collection $invitations + * @property-read \Illuminate\Database\Eloquent\Collection $invoices * @mixin \Eloquent */ class RecurringInvoice extends BaseModel diff --git a/app/Models/RecurringQuote.php b/app/Models/RecurringQuote.php index c7e504eb574a..4759f90f5660 100644 --- a/app/Models/RecurringQuote.php +++ b/app/Models/RecurringQuote.php @@ -206,6 +206,11 @@ use Laracasts\Presenter\PresentableTrait; * @property-read \Illuminate\Database\Eloquent\Collection $history * @property-read \Illuminate\Database\Eloquent\Collection $invitations * @property-read \Illuminate\Database\Eloquent\Collection $quotes + * @property-read \Illuminate\Database\Eloquent\Collection $activities + * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Database\Eloquent\Collection $history + * @property-read \Illuminate\Database\Eloquent\Collection $invitations + * @property-read \Illuminate\Database\Eloquent\Collection $quotes * @mixin \Eloquent */ class RecurringQuote extends BaseModel diff --git a/app/Models/Task.php b/app/Models/Task.php index 58e009b9db63..70e00f1ac34f 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -98,6 +98,7 @@ use Illuminate\Support\Carbon; * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Database\Eloquent\Collection $documents * @mixin \Eloquent */ class Task extends BaseModel diff --git a/app/Models/User.php b/app/Models/User.php index 0e004f313046..ea4185e21620 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -188,6 +188,13 @@ use Laracasts\Presenter\PresentableTrait; * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read \Illuminate\Notifications\DatabaseNotificationCollection $notifications * @property-read \Illuminate\Database\Eloquent\Collection $tokens + * @property-read \Illuminate\Database\Eloquent\Collection $clients + * @property-read \Illuminate\Database\Eloquent\Collection $companies + * @property-read \Illuminate\Database\Eloquent\Collection $company_users + * @property-read \Illuminate\Database\Eloquent\Collection $contacts + * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Notifications\DatabaseNotificationCollection $notifications + * @property-read \Illuminate\Database\Eloquent\Collection $tokens * @mixin \Eloquent */ class User extends Authenticatable implements MustVerifyEmail diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php index 681cfa384f58..4c8f916729d0 100644 --- a/app/Models/Vendor.php +++ b/app/Models/Vendor.php @@ -132,6 +132,10 @@ use Laracasts\Presenter\PresentableTrait; * @property-read \Illuminate\Database\Eloquent\Collection $contacts * @property-read \Illuminate\Database\Eloquent\Collection $documents * @property-read \Illuminate\Database\Eloquent\Collection $primary_contact + * @property-read \Illuminate\Database\Eloquent\Collection $activities + * @property-read \Illuminate\Database\Eloquent\Collection $contacts + * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Database\Eloquent\Collection $primary_contact * @mixin \Eloquent */ class Vendor extends BaseModel diff --git a/app/Models/VendorContact.php b/app/Models/VendorContact.php index 66da46635ae4..6f8fec742068 100644 --- a/app/Models/VendorContact.php +++ b/app/Models/VendorContact.php @@ -124,6 +124,8 @@ use Laracasts\Presenter\PresentableTrait; * @property-read \Illuminate\Database\Eloquent\Collection $purchase_order_invitations * @property-read \Illuminate\Notifications\DatabaseNotificationCollection $notifications * @property-read \Illuminate\Database\Eloquent\Collection $purchase_order_invitations + * @property-read \Illuminate\Notifications\DatabaseNotificationCollection $notifications + * @property-read \Illuminate\Database\Eloquent\Collection $purchase_order_invitations * @mixin \Eloquent */ class VendorContact extends Authenticatable implements HasLocalePreference diff --git a/app/Services/Tax/Providers/EuTax.php b/app/Services/Tax/Providers/EuTax.php deleted file mode 100644 index 79cb15e2e669..000000000000 --- a/app/Services/Tax/Providers/EuTax.php +++ /dev/null @@ -1,165 +0,0 @@ -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->reduced_vat_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) || //same country / exempt for tax / valid vat number - (in_array($this->client_country_code, $this->eu_country_codes) && $this->valid_vat_number && $this->rule->eu_business_tax_exempt) //eu country / exempt for tax / valid vat number - ) { - $this->vat_rate = 0; - $this->reduced_vat_rate = 0; - nlog("euro zone and tax exempt"); - } - elseif(!in_array(strtoupper($this->client_country_code), $this->eu_country_codes) && ($this->rule->foreign_consumer_tax_exempt || $this->rule->foreign_business_tax_exempt)) //foreign + tax exempt - { - $this->vat_rate = 0; - $this->reduced_vat_rate = 0; - nlog("foreign and tax exempt"); - } - elseif(in_array(strtoupper($this->client_country_code), $this->eu_country_codes) && !$this->valid_vat_number) //eu country / no valid vat - { - if(($this->vendor_country_code != $this->client_country_code) && $this->company->tax_data->regions->EU->has_sales_above_threshold) - { - $this->vat_rate = $this->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->vat_rate; - $this->reduced_vat_rate = $this->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->reduced_vat_rate; - nlog("eu zone with sales above threshold"); - } - else { - $this->vat_rate = $this->company->tax_data->regions->EU->subregions->{$this->company->country()->iso_3166_2}->vat_rate; - $this->reduced_vat_rate = $this->company->tax_data->regions->EU->subregions->{$this->company->country()->iso_3166_2}->reduced_vat_rate; - nlog("same eu country with"); - } - } - else { - nlog("default tax"); - $this->vat_rate = $this->company->tax_data->regions->EU->subregions->{$this->company->country()->iso_3166_2}->vat_rate; - $this->reduced_vat_rate = $this->company->tax_data->regions->EU->subregions->{$this->company->country()->iso_3166_2}->reduced_vat_rate; - } - - return $this; - - } -} - diff --git a/app/Services/Tax/TaxService.php b/app/Services/Tax/TaxService.php index 678418c240b5..354681502bb7 100644 --- a/app/Services/Tax/TaxService.php +++ b/app/Services/Tax/TaxService.php @@ -23,5 +23,15 @@ class TaxService { } - + private function validateVat(): self + { + $client_country_code = $this->client->shipping_country ? $this->client->shipping_country->iso_3166_2 : $this->client->country->iso_3166_2; + + $vat_check = (new VatNumberCheck($this->client->vat_number, $client_country_code))->run(); + + $this->client->has_valid_vat_number = $vat_check->isValid(); + $this->client->saveQuietly(); + + return $this; + } } \ No newline at end of file diff --git a/database/migrations/2023_03_24_054758_add_client_is_exempt_from_taxes.php b/database/migrations/2023_03_24_054758_add_client_is_exempt_from_taxes.php index 4b77fa0fb869..b298eeb46708 100644 --- a/database/migrations/2023_03_24_054758_add_client_is_exempt_from_taxes.php +++ b/database/migrations/2023_03_24_054758_add_client_is_exempt_from_taxes.php @@ -22,8 +22,7 @@ return new class extends Migration Schema::table('companies', function (Illuminate\Database\Schema\Blueprint $table) { $table->mediumText('tax_data')->nullable()->change(); }); - - + } /** diff --git a/tests/Unit/Tax/EuTaxTest.php b/tests/Unit/Tax/EuTaxTest.php index 8f2cd0fc61e4..995844ac10b1 100644 --- a/tests/Unit/Tax/EuTaxTest.php +++ b/tests/Unit/Tax/EuTaxTest.php @@ -15,10 +15,9 @@ use Tests\TestCase; use App\Models\Client; use App\Models\Company; use Tests\MockAccountData; -use App\DataMapper\Tax\de\Rule; +use App\DataMapper\Tax\DE\Rule; use App\DataMapper\Tax\TaxModel; use App\DataMapper\CompanySettings; -use App\Services\Tax\Providers\EuTax; use Illuminate\Routing\Middleware\ThrottleRequests; use Illuminate\Foundation\Testing\DatabaseTransactions; @@ -66,22 +65,24 @@ class EuTaxTest extends TestCase 'company_id' => $company->id, 'country_id' => 276, 'shipping_country_id' => 276, + 'has_valid_vat_number' => false, ]); - $process = new EuTax($company, $client); - $process->run(); + $process = new Rule(); + $process->setClient($client); + $process->init(); - $this->assertEquals('de', $process->getVendorCountryCode()); + $this->assertEquals('DE', $process->vendor_country_code); - $this->assertEquals('de', $process->getClientCountryCode()); + $this->assertEquals('DE', $process->client_country_code); - $this->assertFalse($process->hasValidVatNumber()); + $this->assertFalse($client->has_valid_vat_number); - $this->assertInstanceOf(Rule::class, $process->rule); + $this->assertInstanceOf(Rule::class, $process); - $this->assertEquals(19, $process->getVatRate()); + $this->assertEquals(19, $process->vat_rate); - $this->assertEquals(7, $process->getVatReducedRate()); + $this->assertEquals(7, $process->reduced_vat_rate); } @@ -110,22 +111,25 @@ class EuTaxTest extends TestCase 'company_id' => $company->id, 'country_id' => 56, 'shipping_country_id' => 56, + 'has_valid_vat_number' => false, ]); - $process = new EuTax($company, $client); - $process->run(); + $process = new Rule(); + $process->setClient($client); + $process->init(); - $this->assertEquals('de', $process->getVendorCountryCode()); - $this->assertEquals('be', $process->getClientCountryCode()); + $this->assertEquals('DE', $process->vendor_country_code); - $this->assertFalse($process->hasValidVatNumber()); + $this->assertEquals('BE', $process->client_country_code); - $this->assertInstanceOf(Rule::class, $process->rule); + $this->assertFalse($client->has_valid_vat_number); - $this->assertEquals(21, $process->getVatRate()); + $this->assertInstanceOf(Rule::class, $process); - $this->assertEquals(6, $process->getVatReducedRate()); + $this->assertEquals(21, $process->vat_rate); + + $this->assertEquals(6, $process->reduced_vat_rate); } @@ -146,25 +150,71 @@ class EuTaxTest extends TestCase 'company_id' => $company->id, 'country_id' => 840, 'shipping_country_id' => 840, + 'has_valid_vat_number' => false, ]); - $process = new EuTax($company, $client); - $process->run(); + $process = new Rule(); + $process->setClient($client); + $process->init(); - $this->assertEquals('de', $process->getVendorCountryCode()); - $this->assertEquals('us', $process->getClientCountryCode()); + $this->assertEquals('DE', $process->vendor_country_code); - $this->assertFalse($process->hasValidVatNumber()); + $this->assertEquals('US', $process->client_country_code); - $this->assertInstanceOf(Rule::class, $process->rule); + $this->assertFalse($client->has_valid_vat_number); - $this->assertEquals(0, $process->getVatRate()); + $this->assertInstanceOf(Rule::class, $process); - $this->assertEquals(0, $process->getVatReducedRate()); + $this->assertEquals(0, $process->vat_rate); + + $this->assertEquals(0, $process->reduced_vat_rate); } + public function testSubThresholdCorrectRate() + { + + $settings = CompanySettings::defaults(); + $settings->country_id = '276'; // germany + + $tax_data = new TaxModel(); + $tax_data->seller_region = 'DE'; + $tax_data->seller_subregion = 'DE'; + $tax_data->regions->EU->has_sales_above_threshold = false; + $tax_data->regions->EU->tax_all = true; + + $company = Company::factory()->create([ + 'account_id' => $this->account->id, + 'settings' => $settings, + 'tax_data' => $tax_data, + ]); + + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'country_id' => 56, + 'shipping_country_id' => 56, + 'has_valid_vat_number' => false, + ]); + + $process = new Rule(); + $process->setClient($client); + $process->init(); + + $this->assertInstanceOf(Rule::class, $process); + + $this->assertFalse($client->has_valid_vat_number); + + $this->assertEquals(19, $process->vat_rate); + + $this->assertEquals(7, $process->reduced_vat_rate); + + } + + + //tests with valid vat. }