diff --git a/app/DataMapper/Tax/BaseRule.php b/app/DataMapper/Tax/BaseRule.php index 6f664dd8148f..37299abc5115 100644 --- a/app/DataMapper/Tax/BaseRule.php +++ b/app/DataMapper/Tax/BaseRule.php @@ -14,7 +14,6 @@ namespace App\DataMapper\Tax; use App\Models\Client; use App\Models\Invoice; use App\Models\Product; -use App\DataMapper\Tax\TaxData; use App\DataProviders\USStates; use App\DataMapper\Tax\ZipTax\Response; @@ -147,21 +146,25 @@ class BaseRule implements RuleInterface private function configTaxData(): self { - + /* If the client Country is not in the region_codes, we force the company country onto the client? @TODO */ if(!array_key_exists($this->client->country->iso_3166_2, $this->region_codes)) { $this->client->country_id = $this->invoice->company->settings->country_id; $this->client->saveQuietly(); + nlog('Automatic tax calculations not supported for this country - defaulting to company country'); } + /** Harvest the client_region */ $this->client_region = $this->region_codes[$this->client->country->iso_3166_2]; + /** If the tax data is already set and the invoice is marked as sent, do not adjust the rates */ if($this->invoice->tax_data && $this->invoice->status_id > 1) return $this; - //determine if we are taxing locally or if we are taxing globally + //Pass the client tax data into the invoice tax data object $tax_data = is_object($this->invoice->client->tax_data) ? $this->invoice->client->tax_data : new Response([]); + /** If no Origin / Destination has been set and the seller and client sub regions are not the same, force destination tax */ if(strlen($this->invoice->tax_data?->originDestination) == 0 && $this->client->company->tax_data->seller_subregion != $this->client_subregion) { $tax_data->originDestination = "D"; $tax_data->geoState = $this->client_subregion; @@ -235,18 +238,21 @@ class BaseRule implements RuleInterface { if ($this->client->is_tax_exempt) { - return $this->taxExempt(); + + return $this->taxExempt($item); + } elseif($this->client_region == $this->seller_region && $this->isTaxableRegion()) { - $this->taxByType($item->tax_id); + $this->taxByType($item); return $this; + } elseif($this->isTaxableRegion()) { //other regions outside of US match(intval($item->tax_id)) { - Product::PRODUCT_TYPE_EXEMPT => $this->taxExempt(), - Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced(), - Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override(), + Product::PRODUCT_TYPE_EXEMPT => $this->taxExempt($item), + Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced($item), + Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override($item), default => $this->defaultForeign(), }; @@ -260,42 +266,42 @@ class BaseRule implements RuleInterface return $this; } - public function taxReduced(): self + public function taxReduced($item): self { return $this; } - public function taxExempt(): self + public function taxExempt($item): self { return $this; } - public function taxDigital(): self + public function taxDigital($item): self { return $this; } - public function taxService(): self + public function taxService($item): self { return $this; } - public function taxShipping(): self + public function taxShipping($item): self { return $this; } - public function taxPhysical(): self + public function taxPhysical($item): self { return $this; } - public function default(): self + public function default($item): self { return $this; } - public function override(): self + public function override($item): self { return $this; } diff --git a/app/DataMapper/Tax/DE/Rule.php b/app/DataMapper/Tax/DE/Rule.php index 98f7016eb5fd..288a5aec2e6a 100644 --- a/app/DataMapper/Tax/DE/Rule.php +++ b/app/DataMapper/Tax/DE/Rule.php @@ -56,27 +56,27 @@ class Rule extends BaseRule implements RuleInterface /** * Sets the correct tax rate based on the product type. * - * @param mixed $product_tax_type + * @param mixed $item * @return self */ - public function taxByType($product_tax_type): self + public function taxByType($item): self { if ($this->client->is_tax_exempt) { - return $this->taxExempt(); + return $this->taxExempt($item); } - match(intval($product_tax_type)){ - Product::PRODUCT_TYPE_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(), - Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced(), - Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override(), - Product::PRODUCT_TYPE_ZERO_RATED => $this->zeroRated(), - Product::PRODUCT_TYPE_REVERSE_TAX => $this->reverseTax(), - default => $this->default(), + match(intval($item->tax_id)){ + Product::PRODUCT_TYPE_EXEMPT => $this->taxExempt($item), + Product::PRODUCT_TYPE_DIGITAL => $this->taxDigital($item), + Product::PRODUCT_TYPE_SERVICE => $this->taxService($item), + Product::PRODUCT_TYPE_SHIPPING => $this->taxShipping($item), + Product::PRODUCT_TYPE_PHYSICAL => $this->taxPhysical($item), + Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced($item), + Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override($item), + Product::PRODUCT_TYPE_ZERO_RATED => $this->zeroRated($item), + Product::PRODUCT_TYPE_REVERSE_TAX => $this->reverseTax($item), + default => $this->default($item), }; return $this; @@ -87,7 +87,7 @@ class Rule extends BaseRule implements RuleInterface * * @return self */ - public function reverseTax(): self + public function reverseTax($item): self { $this->tax_rate1 = 0; $this->tax_name1 = 'ermäßigte MwSt.'; @@ -100,7 +100,7 @@ class Rule extends BaseRule implements RuleInterface * * @return self */ - public function taxReduced(): self + public function taxReduced($item): self { $this->tax_rate1 = $this->reduced_tax_rate; $this->tax_name1 = 'ermäßigte MwSt.'; @@ -113,7 +113,7 @@ class Rule extends BaseRule implements RuleInterface * * @return self */ - public function zeroRated(): self + public function zeroRated($item): self { $this->tax_rate1 = 0; $this->tax_name1 = 'ermäßigte MwSt.'; @@ -127,7 +127,7 @@ class Rule extends BaseRule implements RuleInterface * * @return self */ - public function taxExempt(): self + public function taxExempt($item): self { $this->tax_name1 = ''; $this->tax_rate1 = 0; @@ -140,7 +140,7 @@ class Rule extends BaseRule implements RuleInterface * * @return self */ - public function taxDigital(): self + public function taxDigital($item): self { $this->tax_rate1 = $this->tax_rate; @@ -154,7 +154,7 @@ class Rule extends BaseRule implements RuleInterface * * @return self */ - public function taxService(): self + public function taxService($item): self { $this->tax_rate1 = $this->tax_rate; @@ -168,7 +168,7 @@ class Rule extends BaseRule implements RuleInterface * * @return self */ - public function taxShipping(): self + public function taxShipping($item): self { $this->tax_rate1 = $this->tax_rate; @@ -182,7 +182,7 @@ class Rule extends BaseRule implements RuleInterface * * @return self */ - public function taxPhysical(): self + public function taxPhysical($item): self { $this->tax_rate1 = $this->tax_rate; @@ -196,7 +196,7 @@ class Rule extends BaseRule implements RuleInterface * * @return self */ - public function default(): self + public function default($item): self { $this->tax_name1 = ''; @@ -210,7 +210,7 @@ class Rule extends BaseRule implements RuleInterface * * @return self */ - public function override(): self + public function override($item): self { return $this; } diff --git a/app/DataMapper/Tax/RuleInterface.php b/app/DataMapper/Tax/RuleInterface.php index 6cd933567645..e4ff2c0e2da9 100644 --- a/app/DataMapper/Tax/RuleInterface.php +++ b/app/DataMapper/Tax/RuleInterface.php @@ -15,25 +15,25 @@ interface RuleInterface { public function init(); - public function tax($item = null); + public function tax($item); public function taxByType($type); - public function taxExempt(); + public function taxExempt($item); - public function taxDigital(); + public function taxDigital($item); - public function taxService(); + public function taxService($item); - public function taxShipping(); + public function taxShipping($item); - public function taxPhysical(); + public function taxPhysical($item); - public function taxReduced(); + public function taxReduced($item); - public function default(); + public function default($item); - public function override(); + public function override($item); 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 index d012aae93089..24f65c9b5dfa 100644 --- a/app/DataMapper/Tax/US/Rule.php +++ b/app/DataMapper/Tax/US/Rule.php @@ -41,32 +41,39 @@ class Rule extends BaseRule implements RuleInterface /** * Override tax class, we use this when we do not modify the input taxes * + * @param mixed $item * @return self */ - public function override(): self + public function override($item): self { + + $this->tax_rate1 = $item->tax_rate1; + + $this->tax_name1 = $item->tax_name1; + return $this; + } /** * Sets the correct tax rate based on the product type. * - * @param mixed $product_tax_type + * @param mixed $item * @return self */ - public function taxByType($product_tax_type): self + public function taxByType($item): self { - match(intval($product_tax_type)) { - Product::PRODUCT_TYPE_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(), - Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced(), - Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override(), - Product::PRODUCT_TYPE_ZERO_RATED => $this->zeroRated(), - default => $this->default(), + match(intval($item->tax_id)) { + Product::PRODUCT_TYPE_EXEMPT => $this->taxExempt($item), + Product::PRODUCT_TYPE_DIGITAL => $this->taxDigital($item), + Product::PRODUCT_TYPE_SERVICE => $this->taxService($item), + Product::PRODUCT_TYPE_SHIPPING => $this->taxShipping($item), + Product::PRODUCT_TYPE_PHYSICAL => $this->taxPhysical($item), + Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced($item), + Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override($item), + Product::PRODUCT_TYPE_ZERO_RATED => $this->zeroRated($item), + default => $this->default($item), }; return $this; @@ -74,10 +81,11 @@ class Rule extends BaseRule implements RuleInterface /** * Sets the tax as exempt (0) + * @param mixed $item * * @return self */ - public function taxExempt(): self + public function taxExempt($item): self { $this->tax_name1 = ''; $this->tax_rate1 = 0; @@ -87,25 +95,27 @@ class Rule extends BaseRule implements RuleInterface /** * Calculates the tax rate for a digital product + * @param mixed $item * * @return self */ - public function taxDigital(): self + public function taxDigital($item): self { - $this->default(); + $this->default($item); return $this; } /** * Calculates the tax rate for a service product + * @param mixed $item * * @return self */ - public function taxService(): self + public function taxService($item): self { if($this->tax_data?->txbService == 'Y') { - $this->default(); + $this->default($item); } return $this; @@ -113,13 +123,14 @@ class Rule extends BaseRule implements RuleInterface /** * Calculates the tax rate for a shipping product + * @param mixed $item * * @return self */ - public function taxShipping(): self + public function taxShipping($item): self { if($this->tax_data?->txbFreight == 'Y') { - $this->default(); + $this->default($item); } return $this; @@ -127,12 +138,13 @@ class Rule extends BaseRule implements RuleInterface /** * Calculates the tax rate for a physical product + * @param mixed $item * * @return self */ - public function taxPhysical(): self + public function taxPhysical($item): self { - $this->default(); + $this->default($item); return $this; } @@ -142,10 +154,8 @@ class Rule extends BaseRule implements RuleInterface * * @return self */ - public function default(): self + public function default($item): self { -nlog("default rate"); -nlog($this->tax_data); if($this->tax_data?->stateSalesTax == 0) { @@ -172,7 +182,7 @@ nlog($this->tax_data); return $this; } - public function zeroRated(): self + public function zeroRated($item): self { $this->tax_rate1 = 0; @@ -187,13 +197,25 @@ nlog($this->tax_data); * * @return self */ - public function taxReduced(): self + public function taxReduced($item): self { - $this->default(); + $this->default($item); return $this; } - + + /** + * Calculates the tax rate for a reverse tax product + * + * @return self + */ + public function reverseTax($item): self + { + $this->default($item); + + return $this; + } + /** * Calculates the tax rates to be applied * @@ -204,17 +226,4 @@ nlog($this->tax_data); return $this; } - /** - * Calculates the tax rate for a reverse tax product - * - * @return self - */ - public function reverseTax(): self - { - $this->default(); - - return $this; - } - - } diff --git a/app/Helpers/Invoice/InvoiceItemSum.php b/app/Helpers/Invoice/InvoiceItemSum.php index dd2d9b2e434e..fd1cc9c390ff 100644 --- a/app/Helpers/Invoice/InvoiceItemSum.php +++ b/app/Helpers/Invoice/InvoiceItemSum.php @@ -144,12 +144,8 @@ class InvoiceItemSum return $this; } - //should we be filtering by client country here? do we need to reflect at the company <=> client level? - // if (in_array($this->client->country->iso_3166_2, $this->tax_jurisdictions)) { //only calculate for supported tax jurisdictions if (in_array($this->client->company->country()->iso_3166_2, $this->tax_jurisdictions)) { //only calculate for supported tax jurisdictions - nlog($this->client->company->country()->iso_3166_2); - $class = "App\DataMapper\Tax\\".$this->client->company->country()->iso_3166_2."\\Rule"; $this->rule = new $class(); diff --git a/app/Http/Controllers/CompanyController.php b/app/Http/Controllers/CompanyController.php index 6b32a6bee2e4..108d0d74abbe 100644 --- a/app/Http/Controllers/CompanyController.php +++ b/app/Http/Controllers/CompanyController.php @@ -41,6 +41,7 @@ use App\Utils\Traits\Uploadable; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Http\Response; use Illuminate\Support\Facades\Storage; +use Str; use Turbo124\Beacon\Facades\LightLogs; /** @@ -417,6 +418,13 @@ class CompanyController extends BaseController $this->saveDocuments($request->input('documents'), $company, false); } + if($request->has('e_invoice_certificate')){ + + $company->e_invoice_certificate = base64_encode($request->file("e_invoice_certificate")->get()); + $company->save(); + + } + $this->uploadLogo($request->file('company_logo'), $company, $company); return $this->itemResponse($company); diff --git a/app/Http/Requests/Company/UpdateCompanyRequest.php b/app/Http/Requests/Company/UpdateCompanyRequest.php index f1c66b0a3a4f..f4070350a887 100644 --- a/app/Http/Requests/Company/UpdateCompanyRequest.php +++ b/app/Http/Requests/Company/UpdateCompanyRequest.php @@ -53,7 +53,8 @@ class UpdateCompanyRequest extends Request $rules['country_id'] = 'integer|nullable'; $rules['work_email'] = 'email|nullable'; $rules['matomo_id'] = 'nullable|integer'; - + $rules['e_invoice_certificate_passphrase'] = 'sometimes|nullable'; + $rules['e_invoice_certificate'] = 'sometimes|file|mimes:p12,pfx,pem,cer,crt,der,txt,p7b,spc,bin'; // $rules['client_registration_fields'] = 'array'; if (isset($input['portal_mode']) && ($input['portal_mode'] == 'domain' || $input['portal_mode'] == 'iframe')) { diff --git a/app/Models/Company.php b/app/Models/Company.php index 2b0d05a12cbc..a0d926b86f23 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -339,6 +339,7 @@ class Company extends BaseModel 'notify_vendor_when_paid', 'calculate_taxes', 'tax_data', + 'e_invoice_certificate_passphrase', ]; protected $hidden = [ @@ -357,6 +358,7 @@ class Company extends BaseModel 'deleted_at' => 'timestamp', 'client_registration_fields' => 'array', 'tax_data' => 'object', + 'e_invoice_certificate_passphrase' => 'encrypted', ]; protected $with = []; @@ -365,7 +367,6 @@ class Company extends BaseModel self::ENTITY_RECURRING_INVOICE => 1, self::ENTITY_CREDIT => 2, self::ENTITY_QUOTE => 4, - // @phpstan-ignore-next-line self::ENTITY_TASK => 8, self::ENTITY_EXPENSE => 16, self::ENTITY_PROJECT => 32, diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index eff5e27b6be7..4152fa1988ff 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -199,6 +199,8 @@ class CompanyTransformer extends EntityTransformer 'invoice_task_hours' => (bool) $company->invoice_task_hours, 'calculate_taxes' => (bool) $company->calculate_taxes, 'tax_data' => $company->tax_data ?: new \stdClass, + 'has_e_invoice_certificate' => $company->e_invoice_certificate ? true : false, + 'has_e_invoice_certificate_passphrase' => $company->e_invoice_certificate_passphrase ? true : false, ]; } diff --git a/database/migrations/2023_05_15_103212_e_invoice_ssl_storage.php b/database/migrations/2023_05_15_103212_e_invoice_ssl_storage.php new file mode 100644 index 000000000000..f6dab48bd698 --- /dev/null +++ b/database/migrations/2023_05_15_103212_e_invoice_ssl_storage.php @@ -0,0 +1,32 @@ +text('e_invoice_certificate')->nullable(); + $table->string('e_invoice_certificate_passphrase')->nullable(); + }); + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +};