mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Fixes for taxes
This commit is contained in:
parent
36269e4e1b
commit
8908bc318c
@ -16,6 +16,7 @@ use App\Models\Invoice;
|
||||
use App\Models\Product;
|
||||
use App\DataProviders\USStates;
|
||||
use App\DataMapper\Tax\ZipTax\Response;
|
||||
use App\Services\Tax\Providers\TaxProvider;
|
||||
|
||||
class BaseRule implements RuleInterface
|
||||
{
|
||||
@ -103,9 +104,6 @@ class BaseRule implements RuleInterface
|
||||
/** EU TAXES */
|
||||
|
||||
|
||||
/** US TAXES */
|
||||
/** US TAXES */
|
||||
|
||||
public string $tax_name1 = '';
|
||||
public float $tax_rate1 = 0;
|
||||
|
||||
@ -129,75 +127,132 @@ class BaseRule implements RuleInterface
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the tax rule for the entity.
|
||||
*
|
||||
* @param mixed $invoice
|
||||
* @return self
|
||||
*/
|
||||
public function setEntity(mixed $invoice): self
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
|
||||
$this->client = $invoice->client;
|
||||
|
||||
$this->configTaxData()
|
||||
->resolveRegions();
|
||||
$this->resolveRegions()
|
||||
->configTaxData();
|
||||
|
||||
$this->tax_data = new Response($this->invoice->tax_data);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configigures the Tax Data for the entity
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function configTaxData(): self
|
||||
{
|
||||
/* If the client Country is not in the region_codes, we force the company country onto the client? @TODO */
|
||||
/* We should only apply taxes for configured states */
|
||||
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');
|
||||
|
||||
nlog("With new logic, we should never see this");
|
||||
}
|
||||
|
||||
/** 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;
|
||||
|
||||
//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([]);
|
||||
/**
|
||||
* Origin - Company Tax Data
|
||||
* Destination - Client Tax Data
|
||||
*
|
||||
*/
|
||||
$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;
|
||||
if($this->seller_region == 'US'){
|
||||
|
||||
$company = $this->invoice->company;
|
||||
|
||||
/** If no company tax data has been configured, lets do that now. */
|
||||
if(!$company->origin_tax_data && \DB::transactionLevel() == 0)
|
||||
{
|
||||
|
||||
$tp = new TaxProvider($company);
|
||||
$tp->updateCompanyTaxData();
|
||||
$company->fresh();
|
||||
|
||||
if($this->invoice instanceof Invoice) {
|
||||
$this->invoice->tax_data = $tax_data;
|
||||
$this->invoice->saveQuietly();
|
||||
}
|
||||
|
||||
|
||||
/** If we are in a Origin based state, force the company tax here */
|
||||
if($company->origin_tax_data->originDestination == 'O' && ($company->tax_data->seller_subregion == $this->client_subregion)) {
|
||||
|
||||
$tax_data = $company->origin_tax_data;
|
||||
|
||||
}
|
||||
else{
|
||||
|
||||
/** Ensures the client tax data has been updated */
|
||||
if(!$this->client->tax_data && \DB::transactionLevel() == 0) {
|
||||
|
||||
$tp = new TaxProvider($company, $this->client);
|
||||
$tp->updateClientTaxData();
|
||||
$this->client->fresh();
|
||||
}
|
||||
|
||||
$tax_data = $this->client->tax_data;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Applies the tax data to the invoice */
|
||||
if($this->invoice instanceof Invoice && \DB::transactionLevel() == 0) {
|
||||
$this->invoice->tax_data = $tax_data;
|
||||
$this->invoice->saveQuietly();
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
// Refactor to support switching between shipping / billing country / region / subregion
|
||||
|
||||
/**
|
||||
* Resolve Regions & Subregions
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function resolveRegions(): self
|
||||
{
|
||||
|
||||
$this->client_region = $this->region_codes[$this->client->country->iso_3166_2];
|
||||
|
||||
match($this->client_region){
|
||||
'US' => $this->client_subregion = strlen($this->invoice?->tax_data?->geoState) > 1 ? $this->invoice?->tax_data?->geoState : $this->getUSState(),
|
||||
'US' => $this->client_subregion = strlen($this->invoice?->client?->tax_data?->geoState) > 1 ? $this->invoice->client->tax_data->geoState : $this->getUSState(),
|
||||
'EU' => $this->client_subregion = $this->client->country->iso_3166_2,
|
||||
'AU' => $this->client_subregion = 'AU',
|
||||
default => $this->client_subregion = $this->client->country->iso_3166_2,
|
||||
};
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
private function getUSState(): string
|
||||
{
|
||||
try {
|
||||
|
||||
$states = USStates::$states;
|
||||
|
||||
if(isset($states[$this->client->state]))
|
||||
return $this->client->state;
|
||||
|
||||
return USStates::getState(strlen($this->client->postal_code) > 1 ? $this->client->postal_code : $this->client->shipping_postal_code);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->client->company->country()->iso_3166_2 == 'US' ? $this->client->company->tax_data->seller_subregion : 'CA';
|
||||
}
|
||||
|
@ -159,18 +159,8 @@ class Rule extends BaseRule implements RuleInterface
|
||||
|
||||
if($this->tax_data?->stateSalesTax == 0) {
|
||||
|
||||
if($this->tax_data->originDestination == "O"){
|
||||
$tax_region = $this->client->company->tax_data->seller_subregion;
|
||||
$this->tax_rate1 = $this->invoice->client->company->tax_data->regions->US->subregions->{$tax_region}->tax_rate;
|
||||
$this->tax_name1 = "{$this->tax_data->geoState} Sales Tax";
|
||||
} else {
|
||||
$this->tax_rate1 = $this->invoice->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_rate;
|
||||
$this->tax_name1 = $this->invoice->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_name;
|
||||
|
||||
if($this->client_region == 'US')
|
||||
$this->tax_name1 = "{$this->client_subregion} ".$this->tax_name1;
|
||||
|
||||
}
|
||||
$this->tax_rate1 = $this->invoice->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_rate;
|
||||
$this->tax_name1 = $this->invoice->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -178,7 +168,6 @@ class Rule extends BaseRule implements RuleInterface
|
||||
$this->tax_rate1 = $this->tax_data->taxSales * 100;
|
||||
$this->tax_name1 = "{$this->tax_data->geoState} Sales Tax";
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ use Illuminate\Support\Facades\Http;
|
||||
|
||||
class USStates
|
||||
{
|
||||
protected static array $states = [
|
||||
public static array $states = [
|
||||
'AL' => 'Alabama',
|
||||
'AK' => 'Alaska',
|
||||
'AZ' => 'Arizona',
|
||||
|
@ -146,9 +146,6 @@ class InvoiceItemSum
|
||||
|
||||
if (in_array($this->client->company->country()->iso_3166_2, $this->tax_jurisdictions) && in_array($this->client->country->iso_3166_2, $this->tax_jurisdictions)) { //only calculate for supported tax jurisdictions
|
||||
|
||||
nlog($this->client->country->iso_3166_2);
|
||||
nlog($this->client->company->country()->iso_3166_2);
|
||||
|
||||
$class = "App\DataMapper\Tax\\".$this->client->company->country()->iso_3166_2."\\Rule";
|
||||
|
||||
$this->rule = new $class();
|
||||
|
@ -359,6 +359,7 @@ class Company extends BaseModel
|
||||
'deleted_at' => 'timestamp',
|
||||
'client_registration_fields' => 'array',
|
||||
'tax_data' => 'object',
|
||||
'origin_tax_data' => 'object',
|
||||
'e_invoice_certificate_passphrase' => EncryptedCast::class,
|
||||
];
|
||||
|
||||
|
@ -52,14 +52,14 @@ class TaxProvider
|
||||
|
||||
private mixed $api_credentials;
|
||||
|
||||
public function __construct(public Company $company, public Client $client)
|
||||
public function __construct(public Company $company, public ?Client $client = null)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function updateCompanyTaxData(): self
|
||||
{
|
||||
$this->configureProvider($this->provider); //hard coded for now to one provider, but we'll be able to swap these out later
|
||||
$this->configureProvider($this->provider, $this->company->country()->iso_3166_2); //hard coded for now to one provider, but we'll be able to swap these out later
|
||||
|
||||
$company_details = [
|
||||
'address1' => $this->company->settings->address1,
|
||||
@ -76,7 +76,7 @@ class TaxProvider
|
||||
|
||||
$tax_data = $tax_provider->run();
|
||||
|
||||
$this->company->tax_data = $tax_data;
|
||||
$this->company->origin_tax_data = $tax_data;
|
||||
|
||||
$this->company->save();
|
||||
|
||||
@ -86,7 +86,7 @@ class TaxProvider
|
||||
|
||||
public function updateClientTaxData(): self
|
||||
{
|
||||
$this->configureProvider($this->provider); //hard coded for now to one provider, but we'll be able to swap these out later
|
||||
$this->configureProvider($this->provider, $this->client->country->iso_3166_2); //hard coded for now to one provider, but we'll be able to swap these out later
|
||||
|
||||
$billing_details =[
|
||||
'address1' => $this->client->address1,
|
||||
@ -112,9 +112,7 @@ class TaxProvider
|
||||
$tax_provider->setApiCredentials($this->api_credentials);
|
||||
|
||||
$tax_data = $tax_provider->run();
|
||||
|
||||
nlog($tax_data);
|
||||
|
||||
|
||||
$this->client->tax_data = $tax_data;
|
||||
|
||||
$this->client->save();
|
||||
@ -123,10 +121,10 @@ class TaxProvider
|
||||
|
||||
}
|
||||
|
||||
private function configureProvider(?string $provider): self
|
||||
private function configureProvider(?string $provider, string $country_code): self
|
||||
{
|
||||
|
||||
match($this->client->country->iso_3166_2){
|
||||
match($country_code){
|
||||
'US' => $this->configureZipTax(),
|
||||
"AT" => $this->configureEuTax(),
|
||||
"BE" => $this->configureEuTax(),
|
||||
@ -169,11 +167,11 @@ class TaxProvider
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function noTaxRegionDefined(): self
|
||||
private function noTaxRegionDefined()
|
||||
{
|
||||
throw new \Exception("No tax region defined for this country");
|
||||
|
||||
return $this;
|
||||
// return $this;
|
||||
}
|
||||
|
||||
private function configureZipTax(): self
|
||||
|
@ -13,9 +13,11 @@ return new class extends Migration
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
|
||||
Schema::table('companies', function (Illuminate\Database\Schema\Blueprint $table) {
|
||||
$table->text('e_invoice_certificate')->nullable();
|
||||
$table->text('e_invoice_certificate_passphrase')->nullable();
|
||||
$table->text('origin_tax_data')->nullable();
|
||||
});
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user