mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-12 14:12:44 +01:00
Updates for taxes
This commit is contained in:
parent
f299033896
commit
66aa198cf4
@ -16,7 +16,6 @@ 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
|
||||
{
|
||||
@ -203,18 +202,9 @@ class BaseRule implements RuleInterface
|
||||
$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();
|
||||
// }
|
||||
|
||||
if($this->client->tax_data)
|
||||
$tax_data = $this->client->tax_data;
|
||||
elseif($this->client->tax_data){
|
||||
|
||||
$tax_data = $this->client->tax_data;
|
||||
|
||||
}
|
||||
|
||||
|
@ -164,7 +164,6 @@ class Rule extends BaseRule implements RuleInterface
|
||||
|
||||
$this->tax_rate1 = $this->invoice->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_rate;
|
||||
$this->tax_name1 = "Sales Tax";
|
||||
// $this->tax_name1 = $this->invoice->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -220,4 +219,4 @@ class Rule extends BaseRule implements RuleInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -67,8 +67,8 @@ class Response
|
||||
public float $taxSales = 0;
|
||||
public string $taxName = "";
|
||||
public float $taxUse = 0;
|
||||
public string $txbService = ""; // N = No, Y = Yes
|
||||
public string $txbFreight = ""; // N = No, Y = Yes
|
||||
public string $txbService = "Y"; // N = No, Y = Yes
|
||||
public string $txbFreight = "Y"; // N = No, Y = Yes
|
||||
public float $stateSalesTax = 0;
|
||||
public float $stateUseTax = 0;
|
||||
public float $citySalesTax = 0;
|
||||
@ -98,7 +98,7 @@ class Response
|
||||
public float $district5UseTax = 0;
|
||||
/* US SPECIFIC TAX CODES */
|
||||
|
||||
public string $originDestination = ""; // defines if the client origin is the locale where the tax is remitted to
|
||||
public string $originDestination = "D"; // defines if the client origin is the locale where the tax is remitted to
|
||||
|
||||
public function __construct($data = null)
|
||||
{
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Jobs\Client;
|
||||
|
||||
use App\DataMapper\Tax\ZipTax\Response;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Libraries\MultiDB;
|
||||
@ -51,9 +52,9 @@ class UpdateTaxData implements ShouldQueue
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
if(!config('services.tax.zip_tax.key'))
|
||||
if($this->company->account->isFreeHostedClient())
|
||||
return;
|
||||
|
||||
|
||||
$tax_provider = new \App\Services\Tax\Providers\TaxProvider($this->company, $this->client);
|
||||
|
||||
try {
|
||||
@ -63,8 +64,7 @@ class UpdateTaxData implements ShouldQueue
|
||||
if (!$this->client->state && $this->client->postal_code) {
|
||||
|
||||
$this->client->state = USStates::getState($this->client->postal_code);
|
||||
|
||||
$this->client->save();
|
||||
$this->client->saveQuietly();
|
||||
|
||||
}
|
||||
|
||||
@ -73,11 +73,80 @@ class UpdateTaxData implements ShouldQueue
|
||||
nlog("problem getting tax data => ".$e->getMessage());
|
||||
}
|
||||
|
||||
/** Set static tax information */
|
||||
if(!$tax_provider->updatedTaxStatus() && $this->client->country_id == 840){
|
||||
|
||||
$calculated_state = false;
|
||||
|
||||
/** State must be calculated else default to the company state for taxes */
|
||||
if(array_key_exists($this->client->shipping_state, USStates::get())) {
|
||||
$calculated_state = $this->client->shipping_state;
|
||||
$calculated_postal_code = $this->client->shipping_postal_code;
|
||||
$calculated_city = $this->client->shipping_city;
|
||||
}
|
||||
elseif(array_key_exists($this->client->state, USStates::get())){
|
||||
$calculated_state = $this->client->state;
|
||||
$calculated_postal_code = $this->client->postal_code;
|
||||
$calculated_city = $this->client->city;
|
||||
}
|
||||
else {
|
||||
|
||||
try{
|
||||
$calculated_state = USStates::getState($this->client->shipping_postal_code);
|
||||
$calculated_postal_code = $this->client->shipping_postal_code;
|
||||
$calculated_city = $this->client->shipping_city;
|
||||
}
|
||||
catch(\Exception $e){
|
||||
nlog("could not calculate state from postal code => {$this->client->shipping_postal_code} or from state {$this->client->shipping_state}");
|
||||
}
|
||||
|
||||
if(!$calculated_state) {
|
||||
try {
|
||||
$calculated_state = USStates::getState($this->client->postal_code);
|
||||
$calculated_postal_code = $this->client->postal_code;
|
||||
$calculated_city = $this->client->city;
|
||||
} catch(\Exception $e) {
|
||||
nlog("could not calculate state from postal code => {$this->client->postal_code} or from state {$this->client->state}");
|
||||
}
|
||||
}
|
||||
|
||||
if($this->company->tax_data?->seller_subregion)
|
||||
$calculated_state = $this->company->tax_data?->seller_subregion;
|
||||
|
||||
nlog("i am trying");
|
||||
|
||||
if(!$calculated_state) {
|
||||
nlog("could not determine state");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$data = [
|
||||
'seller_subregion' => $this->company->origin_tax_data?->seller_subregion ?: '',
|
||||
'geoPostalCode' => $this->client->postal_code ?? '',
|
||||
'geoCity' => $this->client->city ?? '',
|
||||
'geoState' => $calculated_state,
|
||||
'taxSales' => $this->company->tax_data->regions->US->subregions?->{$calculated_state}?->taxSales ?? 0,
|
||||
];
|
||||
|
||||
$tax_data = new Response($data);
|
||||
|
||||
$this->client->tax_data = $tax_data;
|
||||
$this->client->saveQuietly();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function middleware()
|
||||
{
|
||||
return [new WithoutOverlapping($this->company->id)];
|
||||
return [new WithoutOverlapping($this->client->id.$this->company->id)];
|
||||
}
|
||||
|
||||
public function failed($exception)
|
||||
{
|
||||
nlog("UpdateTaxData failed => ".$exception->getMessage());
|
||||
}
|
||||
|
||||
}
|
@ -11,12 +11,12 @@
|
||||
|
||||
namespace App\Jobs\Company;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Libraries\MultiDB;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use App\Jobs\Client\UpdateTaxData;
|
||||
use App\DataProviders\USStates;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\DataMapper\Tax\ZipTax\Response;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use App\Services\Tax\Providers\TaxProvider;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
@ -40,33 +40,52 @@ class CompanyTaxRate implements ShouldQueue
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
||||
if(!config('services.tax.zip_tax.key')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
MultiDB::setDB($this->company->db);
|
||||
|
||||
$tp = new TaxProvider($this->company);
|
||||
|
||||
$tp->updateCompanyTaxData();
|
||||
|
||||
$tp = null;
|
||||
|
||||
Client::query()
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', false)
|
||||
->where('country_id', 840)
|
||||
->whereNotNull('postal_code')
|
||||
->whereNull('tax_data')
|
||||
->where('is_tax_exempt', false)
|
||||
->cursor()
|
||||
->each(function ($client) {
|
||||
|
||||
(new UpdateTaxData($client, $this->company))->handle();
|
||||
|
||||
});
|
||||
|
||||
if(!$tp->updatedTaxStatus() && $this->company->settings->country_id == '840') {
|
||||
|
||||
$calculated_state = false;
|
||||
|
||||
/** State must be calculated else default to the company state for taxes */
|
||||
if(array_key_exists($this->company->settings->state, USStates::get())) {
|
||||
$calculated_state = $this->company->setting->state;
|
||||
}
|
||||
else {
|
||||
|
||||
try{
|
||||
$calculated_state = USStates::getState($this->company->settings->postal_code);
|
||||
}
|
||||
catch(\Exception $e){
|
||||
nlog("could not calculate state from postal code => {$this->company->settings->postal_code} or from state {$this->company->settings->state}");
|
||||
}
|
||||
|
||||
if(!$calculated_state && $this->company->tax_data?->seller_subregion)
|
||||
$calculated_state = $this->company->tax_data?->seller_subregion;
|
||||
|
||||
if(!$calculated_state)
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
$data = [
|
||||
'seller_subregion' => $this->company->origin_tax_data?->seller_subregion ?: '',
|
||||
'geoPostalCode' => $this->company->settings->postal_code ?? '',
|
||||
'geoCity' => $this->company->settings->city ?? '',
|
||||
'geoState' => $calculated_state,
|
||||
'taxSales' => $this->company->tax_data->regions->US->subregions?->{$calculated_state}?->taxSales ?? 0,
|
||||
];
|
||||
|
||||
$tax_data = new Response($data);
|
||||
|
||||
$this->company->origin_tax_data = $tax_data;
|
||||
$this->company->saveQuietly();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function middleware()
|
||||
@ -74,4 +93,7 @@ class CompanyTaxRate implements ShouldQueue
|
||||
return [new WithoutOverlapping($this->company->id)];
|
||||
}
|
||||
|
||||
public function failed($e){
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
}
|
@ -100,10 +100,10 @@ class ClientPresenter extends EntityPresenter
|
||||
if ($address2 = $client->shipping_address2) {
|
||||
$str .= e($address2).'<br/>';
|
||||
}
|
||||
if ($cityState = $this->getCityState()) {
|
||||
if ($cityState = $this->getShippingCityState()) {
|
||||
$str .= e($cityState).'<br/>';
|
||||
}
|
||||
if ($country = $client->country) {
|
||||
if ($country = $client->shipping_country) {
|
||||
$str .= e($country->name).'<br/>';
|
||||
}
|
||||
|
||||
@ -194,4 +194,6 @@ class ClientPresenter extends EntityPresenter
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -11,7 +11,6 @@
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Client;
|
||||
use App\Models\Webhook;
|
||||
use App\Jobs\Client\CheckVat;
|
||||
@ -60,11 +59,12 @@ class ClientObserver
|
||||
*/
|
||||
public function created(Client $client)
|
||||
{
|
||||
|
||||
if ($client->country_id == 840 && $client->company->calculate_taxes) {
|
||||
/** Fix Tax Data for Clients */
|
||||
if ($client->country_id == 840 && $client->company->calculate_taxes && !$client->company->account->isFreeHostedClient()) {
|
||||
UpdateTaxData::dispatch($client, $client->company);
|
||||
}
|
||||
|
||||
/** Check VAT records for client */
|
||||
if(in_array($client->country_id, $this->eu_country_codes) && $client->company->calculate_taxes) {
|
||||
CheckVat::dispatch($client, $client->company);
|
||||
}
|
||||
@ -88,7 +88,7 @@ class ClientObserver
|
||||
{
|
||||
|
||||
/** Monitor postal code changes for US based clients for tax calculations */
|
||||
if(Ninja::isHosted() && $client->getOriginal('postal_code') != $client->postal_code && $client->country_id == 840 && $client->company->calculate_taxes) {
|
||||
if($client->getOriginal('postal_code') != $client->postal_code && $client->country_id == 840 && $client->company->calculate_taxes && !$client->company->account->isFreeHostedClient()) {
|
||||
UpdateTaxData::dispatch($client, $client->company);
|
||||
}
|
||||
|
||||
|
@ -36,15 +36,8 @@ class CompanyObserver
|
||||
*/
|
||||
public function updated(Company $company)
|
||||
{
|
||||
if (Ninja::isHosted() && $company->portal_mode == 'domain' && $company->isDirty('portal_domain')) {
|
||||
//fire event to build new custom portal domain
|
||||
if (Ninja::isHosted() && $company->portal_mode == 'domain' && $company->isDirty('portal_domain'))
|
||||
\Modules\Admin\Jobs\Domain\CustomDomain::dispatch($company->getOriginal('portal_domain'), $company)->onQueue('domain');
|
||||
}
|
||||
|
||||
// if($company->wasChanged()) {
|
||||
// nlog("updated event");
|
||||
// nlog($company->getChanges());
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
@ -11,18 +11,19 @@
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Jobs\Product\UpdateOrCreateProduct;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Company;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\Helpers;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Quote;
|
||||
use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Utils\Helpers;
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\ClientContact;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Jobs\Client\UpdateTaxData;
|
||||
use App\Utils\Traits\SavesDocuments;
|
||||
use App\Jobs\Product\UpdateOrCreateProduct;
|
||||
|
||||
class BaseRepository
|
||||
{
|
||||
@ -308,6 +309,11 @@ class BaseRepository
|
||||
} else {
|
||||
event('eloquent.updated: App\Models\Invoice', $model);
|
||||
}
|
||||
|
||||
/** If the client does not have tax_data - then populate this now */
|
||||
if($client->country_id == 840 && !$client->tax_data && $model->company->calculate_taxes && !$model->company->account->isFreeHostedClient())
|
||||
UpdateTaxData::dispatch($client, $client->company);
|
||||
|
||||
}
|
||||
|
||||
if ($model instanceof Credit) {
|
||||
|
@ -52,11 +52,22 @@ class TaxProvider
|
||||
|
||||
private mixed $api_credentials;
|
||||
|
||||
private bool $updated_client = false;
|
||||
|
||||
public function __construct(public Company $company, public ?Client $client = null)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Flag if tax has been updated successfull.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updatedTaxStatus(): bool
|
||||
{
|
||||
return $this->updated_client;
|
||||
}
|
||||
|
||||
/**
|
||||
* updateCompanyTaxData
|
||||
*
|
||||
@ -67,23 +78,31 @@ class TaxProvider
|
||||
$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,
|
||||
'address2' => $this->company->settings->address2,
|
||||
'address1' => $this->company->settings->address1,
|
||||
'city' => $this->company->settings->city,
|
||||
'state' => $this->company->settings->state,
|
||||
'postal_code' => $this->company->settings->postal_code,
|
||||
'country_id' => $this->company->settings->country_id,
|
||||
'country' => $this->company->country()->name,
|
||||
];
|
||||
|
||||
$tax_provider = new $this->provider($company_details);
|
||||
try {
|
||||
$tax_provider = new $this->provider($company_details);
|
||||
|
||||
$tax_provider->setApiCredentials($this->api_credentials);
|
||||
|
||||
$tax_data = $tax_provider->run();
|
||||
|
||||
$this->company->origin_tax_data = $tax_data;
|
||||
|
||||
$this->company->save();
|
||||
$tax_provider->setApiCredentials($this->api_credentials);
|
||||
|
||||
$tax_data = $tax_provider->run();
|
||||
|
||||
if($tax_data) {
|
||||
$this->company->origin_tax_data = $tax_data;
|
||||
$this->company->saveQuietly();
|
||||
$this->updated_client = true;
|
||||
}
|
||||
|
||||
}
|
||||
catch(\Exception $e){
|
||||
nlog("Could not updated company tax data: " . $e->getMessage());
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
||||
@ -99,21 +118,21 @@ class TaxProvider
|
||||
$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,
|
||||
'address2' => $this->client->address2,
|
||||
'address1' => $this->client->address1,
|
||||
'city' => $this->client->city,
|
||||
'state' => $this->client->state,
|
||||
'postal_code' => $this->client->postal_code,
|
||||
'country_id' => $this->client->country_id,
|
||||
'country' => $this->client->country->name,
|
||||
];
|
||||
|
||||
$shipping_details =[
|
||||
'address1' => $this->client->shipping_address1,
|
||||
'address2' => $this->client->shipping_address2,
|
||||
'address1' => $this->client->shipping_address1,
|
||||
'city' => $this->client->shipping_city,
|
||||
'state' => $this->client->shipping_state,
|
||||
'postal_code' => $this->client->shipping_postal_code,
|
||||
'country_id' => $this->client->shipping_country_id,
|
||||
'country' => $this->client->shipping_country->name,
|
||||
];
|
||||
|
||||
$taxable_address = $this->taxShippingAddress() ? $shipping_details : $billing_details;
|
||||
@ -123,10 +142,14 @@ class TaxProvider
|
||||
$tax_provider->setApiCredentials($this->api_credentials);
|
||||
|
||||
$tax_data = $tax_provider->run();
|
||||
|
||||
$this->client->tax_data = $tax_data;
|
||||
|
||||
nlog($tax_data);
|
||||
|
||||
$this->client->save();
|
||||
if($tax_data) {
|
||||
$this->client->tax_data = $tax_data;
|
||||
$this->client->saveQuietly();
|
||||
$this->updated_client = true;
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
||||
@ -224,10 +247,12 @@ class TaxProvider
|
||||
*/
|
||||
private function configureZipTax(): self
|
||||
{
|
||||
if(!config('services.tax.zip_tax.key'))
|
||||
throw new \Exception("ZipTax API key not set in .env file");
|
||||
|
||||
$this->provider = ZipTax::class;
|
||||
|
||||
$this->api_credentials = config('services.tax.zip_tax.key');
|
||||
|
||||
$this->provider = ZipTax::class;
|
||||
|
||||
return $this;
|
||||
|
||||
|
@ -32,9 +32,7 @@ class ZipTax implements TaxProviderInterface
|
||||
$response = $this->callApi(['key' => $this->api_key, 'address' => $string_address]);
|
||||
|
||||
if($response->successful()){
|
||||
|
||||
return $this->parseResponse($response->json());
|
||||
|
||||
}
|
||||
|
||||
if(isset($this->address['postal_code'])) {
|
||||
@ -45,8 +43,7 @@ class ZipTax implements TaxProviderInterface
|
||||
|
||||
}
|
||||
|
||||
// $response->throw();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setApiCredentials($api_key): self
|
||||
@ -64,18 +61,21 @@ class ZipTax implements TaxProviderInterface
|
||||
*/
|
||||
private function callApi(array $parameters): Response
|
||||
{
|
||||
$response = Http::retry(3, 1000)->withHeaders([])->get($this->endpoint, $parameters);
|
||||
|
||||
return $response;
|
||||
return Http::retry(3, 1000)->withHeaders([])->get($this->endpoint, $parameters);
|
||||
|
||||
}
|
||||
|
||||
private function parseResponse($response)
|
||||
{
|
||||
if(isset($response['results']['0']))
|
||||
|
||||
if(isset($response['rCode']) && $response['rCode'] == 100)
|
||||
return $response['results']['0'];
|
||||
|
||||
if(isset($response['rCode']) && class_exists(\Modules\Admin\Events\TaxProviderException::class))
|
||||
event(new \Modules\Admin\Events\TaxProviderException($response['rCode']));
|
||||
|
||||
return null;
|
||||
// throw new \Exception("Error resolving tax (code) = " . $response['rCode']);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -79,22 +79,18 @@ trait CompanySettingsSaver
|
||||
|
||||
$entity->settings = $company_settings;
|
||||
|
||||
if( $entity?->calculate_taxes && $company_settings->country_id == "840" && array_key_exists('settings', $entity->getDirty()))
|
||||
if($entity?->calculate_taxes && $company_settings->country_id == "840" && array_key_exists('settings', $entity->getDirty()) && !$entity?->account->isFreeHostedClient())
|
||||
{
|
||||
$old_settings = $entity->getOriginal()['settings'];
|
||||
|
||||
/** Monitor changes of the Postal code */
|
||||
if($old_settings->postal_code != $company_settings->postal_code)
|
||||
{
|
||||
nlog("postal code change");
|
||||
CompanyTaxRate::dispatch($entity);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
elseif( $entity?->calculate_taxes && $company_settings->country_id == "840" && array_key_exists('calculate_taxes', $entity->getDirty()) && $entity->getOriginal('calculate_taxes') == 0)
|
||||
elseif( $entity?->calculate_taxes && $company_settings->country_id == "840" && array_key_exists('calculate_taxes', $entity->getDirty()) && $entity->getOriginal('calculate_taxes') == 0 && !$entity?->account->isFreeHostedClient())
|
||||
{
|
||||
nlog("calc taxes change");
|
||||
nlog($entity->getOriginal('calculate_taxes'));
|
||||
CompanyTaxRate::dispatch($entity);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user