1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-09 20:52:56 +01:00

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\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;
}

View File

@ -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;
}

View File

@ -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();
}

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
*
* @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;
}
}

View File

@ -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();

View File

@ -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);

View File

@ -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')) {

View File

@ -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,

View File

@ -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,
];
}

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()
{
//
}
};