Updates for e-invoice signatures

This commit is contained in:
David Bomba 2023-05-15 21:20:47 +10:00
parent 913599334b
commit 7a88d631dc
10 changed files with 152 additions and 97 deletions

View File

@ -14,7 +14,6 @@ namespace App\DataMapper\Tax;
use App\Models\Client; use App\Models\Client;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Product; use App\Models\Product;
use App\DataMapper\Tax\TaxData;
use App\DataProviders\USStates; use App\DataProviders\USStates;
use App\DataMapper\Tax\ZipTax\Response; use App\DataMapper\Tax\ZipTax\Response;
@ -147,21 +146,25 @@ class BaseRule implements RuleInterface
private function configTaxData(): self 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)) { 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->country_id = $this->invoice->company->settings->country_id;
$this->client->saveQuietly(); $this->client->saveQuietly();
nlog('Automatic tax calculations not supported for this country - defaulting to company country'); 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]; $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) if($this->invoice->tax_data && $this->invoice->status_id > 1)
return $this; 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([]); $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) { if(strlen($this->invoice->tax_data?->originDestination) == 0 && $this->client->company->tax_data->seller_subregion != $this->client_subregion) {
$tax_data->originDestination = "D"; $tax_data->originDestination = "D";
$tax_data->geoState = $this->client_subregion; $tax_data->geoState = $this->client_subregion;
@ -235,18 +238,21 @@ class BaseRule implements RuleInterface
{ {
if ($this->client->is_tax_exempt) { if ($this->client->is_tax_exempt) {
return $this->taxExempt();
return $this->taxExempt($item);
} elseif($this->client_region == $this->seller_region && $this->isTaxableRegion()) { } elseif($this->client_region == $this->seller_region && $this->isTaxableRegion()) {
$this->taxByType($item->tax_id); $this->taxByType($item);
return $this; return $this;
} elseif($this->isTaxableRegion()) { //other regions outside of US } elseif($this->isTaxableRegion()) { //other regions outside of US
match(intval($item->tax_id)) { match(intval($item->tax_id)) {
Product::PRODUCT_TYPE_EXEMPT => $this->taxExempt(), Product::PRODUCT_TYPE_EXEMPT => $this->taxExempt($item),
Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced(), Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced($item),
Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override(), Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override($item),
default => $this->defaultForeign(), default => $this->defaultForeign(),
}; };
@ -260,42 +266,42 @@ class BaseRule implements RuleInterface
return $this; return $this;
} }
public function taxReduced(): self public function taxReduced($item): self
{ {
return $this; return $this;
} }
public function taxExempt(): self public function taxExempt($item): self
{ {
return $this; return $this;
} }
public function taxDigital(): self public function taxDigital($item): self
{ {
return $this; return $this;
} }
public function taxService(): self public function taxService($item): self
{ {
return $this; return $this;
} }
public function taxShipping(): self public function taxShipping($item): self
{ {
return $this; return $this;
} }
public function taxPhysical(): self public function taxPhysical($item): self
{ {
return $this; return $this;
} }
public function default(): self public function default($item): self
{ {
return $this; return $this;
} }
public function override(): self public function override($item): self
{ {
return $this; return $this;
} }

View File

@ -56,27 +56,27 @@ class Rule extends BaseRule implements RuleInterface
/** /**
* Sets the correct tax rate based on the product type. * Sets the correct tax rate based on the product type.
* *
* @param mixed $product_tax_type * @param mixed $item
* @return self * @return self
*/ */
public function taxByType($product_tax_type): self public function taxByType($item): self
{ {
if ($this->client->is_tax_exempt) { if ($this->client->is_tax_exempt) {
return $this->taxExempt(); return $this->taxExempt($item);
} }
match(intval($product_tax_type)){ match(intval($item->tax_id)){
Product::PRODUCT_TYPE_EXEMPT => $this->taxExempt(), Product::PRODUCT_TYPE_EXEMPT => $this->taxExempt($item),
Product::PRODUCT_TYPE_DIGITAL => $this->taxDigital(), Product::PRODUCT_TYPE_DIGITAL => $this->taxDigital($item),
Product::PRODUCT_TYPE_SERVICE => $this->taxService(), Product::PRODUCT_TYPE_SERVICE => $this->taxService($item),
Product::PRODUCT_TYPE_SHIPPING => $this->taxShipping(), Product::PRODUCT_TYPE_SHIPPING => $this->taxShipping($item),
Product::PRODUCT_TYPE_PHYSICAL => $this->taxPhysical(), Product::PRODUCT_TYPE_PHYSICAL => $this->taxPhysical($item),
Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced(), Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced($item),
Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override(), Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override($item),
Product::PRODUCT_TYPE_ZERO_RATED => $this->zeroRated(), Product::PRODUCT_TYPE_ZERO_RATED => $this->zeroRated($item),
Product::PRODUCT_TYPE_REVERSE_TAX => $this->reverseTax(), Product::PRODUCT_TYPE_REVERSE_TAX => $this->reverseTax($item),
default => $this->default(), default => $this->default($item),
}; };
return $this; return $this;
@ -87,7 +87,7 @@ class Rule extends BaseRule implements RuleInterface
* *
* @return self * @return self
*/ */
public function reverseTax(): self public function reverseTax($item): self
{ {
$this->tax_rate1 = 0; $this->tax_rate1 = 0;
$this->tax_name1 = 'ermäßigte MwSt.'; $this->tax_name1 = 'ermäßigte MwSt.';
@ -100,7 +100,7 @@ class Rule extends BaseRule implements RuleInterface
* *
* @return self * @return self
*/ */
public function taxReduced(): self public function taxReduced($item): self
{ {
$this->tax_rate1 = $this->reduced_tax_rate; $this->tax_rate1 = $this->reduced_tax_rate;
$this->tax_name1 = 'ermäßigte MwSt.'; $this->tax_name1 = 'ermäßigte MwSt.';
@ -113,7 +113,7 @@ class Rule extends BaseRule implements RuleInterface
* *
* @return self * @return self
*/ */
public function zeroRated(): self public function zeroRated($item): self
{ {
$this->tax_rate1 = 0; $this->tax_rate1 = 0;
$this->tax_name1 = 'ermäßigte MwSt.'; $this->tax_name1 = 'ermäßigte MwSt.';
@ -127,7 +127,7 @@ class Rule extends BaseRule implements RuleInterface
* *
* @return self * @return self
*/ */
public function taxExempt(): self public function taxExempt($item): self
{ {
$this->tax_name1 = ''; $this->tax_name1 = '';
$this->tax_rate1 = 0; $this->tax_rate1 = 0;
@ -140,7 +140,7 @@ class Rule extends BaseRule implements RuleInterface
* *
* @return self * @return self
*/ */
public function taxDigital(): self public function taxDigital($item): self
{ {
$this->tax_rate1 = $this->tax_rate; $this->tax_rate1 = $this->tax_rate;
@ -154,7 +154,7 @@ class Rule extends BaseRule implements RuleInterface
* *
* @return self * @return self
*/ */
public function taxService(): self public function taxService($item): self
{ {
$this->tax_rate1 = $this->tax_rate; $this->tax_rate1 = $this->tax_rate;
@ -168,7 +168,7 @@ class Rule extends BaseRule implements RuleInterface
* *
* @return self * @return self
*/ */
public function taxShipping(): self public function taxShipping($item): self
{ {
$this->tax_rate1 = $this->tax_rate; $this->tax_rate1 = $this->tax_rate;
@ -182,7 +182,7 @@ class Rule extends BaseRule implements RuleInterface
* *
* @return self * @return self
*/ */
public function taxPhysical(): self public function taxPhysical($item): self
{ {
$this->tax_rate1 = $this->tax_rate; $this->tax_rate1 = $this->tax_rate;
@ -196,7 +196,7 @@ class Rule extends BaseRule implements RuleInterface
* *
* @return self * @return self
*/ */
public function default(): self public function default($item): self
{ {
$this->tax_name1 = ''; $this->tax_name1 = '';
@ -210,7 +210,7 @@ class Rule extends BaseRule implements RuleInterface
* *
* @return self * @return self
*/ */
public function override(): self public function override($item): self
{ {
return $this; return $this;
} }

View File

@ -15,25 +15,25 @@ interface RuleInterface
{ {
public function init(); public function init();
public function tax($item = null); public function tax($item);
public function taxByType($type); 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(); public function calculateRates();
} }

View File

@ -41,32 +41,39 @@ class Rule extends BaseRule implements RuleInterface
/** /**
* Override tax class, we use this when we do not modify the input taxes * Override tax class, we use this when we do not modify the input taxes
* *
* @param mixed $item
* @return self * @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; return $this;
} }
/** /**
* Sets the correct tax rate based on the product type. * Sets the correct tax rate based on the product type.
* *
* @param mixed $product_tax_type * @param mixed $item
* @return self * @return self
*/ */
public function taxByType($product_tax_type): self public function taxByType($item): self
{ {
match(intval($product_tax_type)) { match(intval($item->tax_id)) {
Product::PRODUCT_TYPE_EXEMPT => $this->taxExempt(), Product::PRODUCT_TYPE_EXEMPT => $this->taxExempt($item),
Product::PRODUCT_TYPE_DIGITAL => $this->taxDigital(), Product::PRODUCT_TYPE_DIGITAL => $this->taxDigital($item),
Product::PRODUCT_TYPE_SERVICE => $this->taxService(), Product::PRODUCT_TYPE_SERVICE => $this->taxService($item),
Product::PRODUCT_TYPE_SHIPPING => $this->taxShipping(), Product::PRODUCT_TYPE_SHIPPING => $this->taxShipping($item),
Product::PRODUCT_TYPE_PHYSICAL => $this->taxPhysical(), Product::PRODUCT_TYPE_PHYSICAL => $this->taxPhysical($item),
Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced(), Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced($item),
Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override(), Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override($item),
Product::PRODUCT_TYPE_ZERO_RATED => $this->zeroRated(), Product::PRODUCT_TYPE_ZERO_RATED => $this->zeroRated($item),
default => $this->default(), default => $this->default($item),
}; };
return $this; return $this;
@ -74,10 +81,11 @@ class Rule extends BaseRule implements RuleInterface
/** /**
* Sets the tax as exempt (0) * Sets the tax as exempt (0)
* @param mixed $item
* *
* @return self * @return self
*/ */
public function taxExempt(): self public function taxExempt($item): self
{ {
$this->tax_name1 = ''; $this->tax_name1 = '';
$this->tax_rate1 = 0; $this->tax_rate1 = 0;
@ -87,25 +95,27 @@ class Rule extends BaseRule implements RuleInterface
/** /**
* Calculates the tax rate for a digital product * Calculates the tax rate for a digital product
* @param mixed $item
* *
* @return self * @return self
*/ */
public function taxDigital(): self public function taxDigital($item): self
{ {
$this->default(); $this->default($item);
return $this; return $this;
} }
/** /**
* Calculates the tax rate for a service product * Calculates the tax rate for a service product
* @param mixed $item
* *
* @return self * @return self
*/ */
public function taxService(): self public function taxService($item): self
{ {
if($this->tax_data?->txbService == 'Y') { if($this->tax_data?->txbService == 'Y') {
$this->default(); $this->default($item);
} }
return $this; return $this;
@ -113,13 +123,14 @@ class Rule extends BaseRule implements RuleInterface
/** /**
* Calculates the tax rate for a shipping product * Calculates the tax rate for a shipping product
* @param mixed $item
* *
* @return self * @return self
*/ */
public function taxShipping(): self public function taxShipping($item): self
{ {
if($this->tax_data?->txbFreight == 'Y') { if($this->tax_data?->txbFreight == 'Y') {
$this->default(); $this->default($item);
} }
return $this; return $this;
@ -127,12 +138,13 @@ class Rule extends BaseRule implements RuleInterface
/** /**
* Calculates the tax rate for a physical product * Calculates the tax rate for a physical product
* @param mixed $item
* *
* @return self * @return self
*/ */
public function taxPhysical(): self public function taxPhysical($item): self
{ {
$this->default(); $this->default($item);
return $this; return $this;
} }
@ -142,10 +154,8 @@ class Rule extends BaseRule implements RuleInterface
* *
* @return self * @return self
*/ */
public function default(): self public function default($item): self
{ {
nlog("default rate");
nlog($this->tax_data);
if($this->tax_data?->stateSalesTax == 0) { if($this->tax_data?->stateSalesTax == 0) {
@ -172,7 +182,7 @@ nlog($this->tax_data);
return $this; return $this;
} }
public function zeroRated(): self public function zeroRated($item): self
{ {
$this->tax_rate1 = 0; $this->tax_rate1 = 0;
@ -187,9 +197,21 @@ nlog($this->tax_data);
* *
* @return self * @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; return $this;
} }
@ -204,17 +226,4 @@ nlog($this->tax_data);
return $this; return $this;
} }
/**
* Calculates the tax rate for a reverse tax product
*
* @return self
*/
public function reverseTax(): self
{
$this->default();
return $this;
}
} }

View File

@ -144,12 +144,8 @@ class InvoiceItemSum
return $this; 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 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"; $class = "App\DataMapper\Tax\\".$this->client->company->country()->iso_3166_2."\\Rule";
$this->rule = new $class(); $this->rule = new $class();

View File

@ -41,6 +41,7 @@ use App\Utils\Traits\Uploadable;
use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Str;
use Turbo124\Beacon\Facades\LightLogs; use Turbo124\Beacon\Facades\LightLogs;
/** /**
@ -417,6 +418,13 @@ class CompanyController extends BaseController
$this->saveDocuments($request->input('documents'), $company, false); $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); $this->uploadLogo($request->file('company_logo'), $company, $company);
return $this->itemResponse($company); return $this->itemResponse($company);

View File

@ -53,7 +53,8 @@ class UpdateCompanyRequest extends Request
$rules['country_id'] = 'integer|nullable'; $rules['country_id'] = 'integer|nullable';
$rules['work_email'] = 'email|nullable'; $rules['work_email'] = 'email|nullable';
$rules['matomo_id'] = 'nullable|integer'; $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'; // $rules['client_registration_fields'] = 'array';
if (isset($input['portal_mode']) && ($input['portal_mode'] == 'domain' || $input['portal_mode'] == 'iframe')) { if (isset($input['portal_mode']) && ($input['portal_mode'] == 'domain' || $input['portal_mode'] == 'iframe')) {

View File

@ -339,6 +339,7 @@ class Company extends BaseModel
'notify_vendor_when_paid', 'notify_vendor_when_paid',
'calculate_taxes', 'calculate_taxes',
'tax_data', 'tax_data',
'e_invoice_certificate_passphrase',
]; ];
protected $hidden = [ protected $hidden = [
@ -357,6 +358,7 @@ class Company extends BaseModel
'deleted_at' => 'timestamp', 'deleted_at' => 'timestamp',
'client_registration_fields' => 'array', 'client_registration_fields' => 'array',
'tax_data' => 'object', 'tax_data' => 'object',
'e_invoice_certificate_passphrase' => 'encrypted',
]; ];
protected $with = []; protected $with = [];
@ -365,7 +367,6 @@ class Company extends BaseModel
self::ENTITY_RECURRING_INVOICE => 1, self::ENTITY_RECURRING_INVOICE => 1,
self::ENTITY_CREDIT => 2, self::ENTITY_CREDIT => 2,
self::ENTITY_QUOTE => 4, self::ENTITY_QUOTE => 4,
// @phpstan-ignore-next-line
self::ENTITY_TASK => 8, self::ENTITY_TASK => 8,
self::ENTITY_EXPENSE => 16, self::ENTITY_EXPENSE => 16,
self::ENTITY_PROJECT => 32, self::ENTITY_PROJECT => 32,

View File

@ -199,6 +199,8 @@ class CompanyTransformer extends EntityTransformer
'invoice_task_hours' => (bool) $company->invoice_task_hours, 'invoice_task_hours' => (bool) $company->invoice_task_hours,
'calculate_taxes' => (bool) $company->calculate_taxes, 'calculate_taxes' => (bool) $company->calculate_taxes,
'tax_data' => $company->tax_data ?: new \stdClass, '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,
]; ];
} }

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('companies', function (Illuminate\Database\Schema\Blueprint $table) {
$table->text('e_invoice_certificate')->nullable();
$table->string('e_invoice_certificate_passphrase')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
};