diff --git a/app/DataMapper/Tax/BaseRule.php b/app/DataMapper/Tax/BaseRule.php
index 55666506218c..4aed440ef61e 100644
--- a/app/DataMapper/Tax/BaseRule.php
+++ b/app/DataMapper/Tax/BaseRule.php
@@ -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;
}
diff --git a/app/DataMapper/Tax/US/Rule.php b/app/DataMapper/Tax/US/Rule.php
index eb1b55b86c80..572f8e449e12 100644
--- a/app/DataMapper/Tax/US/Rule.php
+++ b/app/DataMapper/Tax/US/Rule.php
@@ -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;
}
-}
+}
\ No newline at end of file
diff --git a/app/DataMapper/Tax/ZipTax/Response.php b/app/DataMapper/Tax/ZipTax/Response.php
index 2a7e9d347337..4a8e03634355 100644
--- a/app/DataMapper/Tax/ZipTax/Response.php
+++ b/app/DataMapper/Tax/ZipTax/Response.php
@@ -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)
{
diff --git a/app/Jobs/Client/UpdateTaxData.php b/app/Jobs/Client/UpdateTaxData.php
index 314fbff04d24..fbe0a8527f89 100644
--- a/app/Jobs/Client/UpdateTaxData.php
+++ b/app/Jobs/Client/UpdateTaxData.php
@@ -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());
}
}
\ No newline at end of file
diff --git a/app/Jobs/Company/CompanyTaxRate.php b/app/Jobs/Company/CompanyTaxRate.php
index 7da21fb92e24..8fbc6a3e4452 100644
--- a/app/Jobs/Company/CompanyTaxRate.php
+++ b/app/Jobs/Company/CompanyTaxRate.php
@@ -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());
+ }
}
\ No newline at end of file
diff --git a/app/Models/Presenters/ClientPresenter.php b/app/Models/Presenters/ClientPresenter.php
index 53450a587284..ac4b184d94e1 100644
--- a/app/Models/Presenters/ClientPresenter.php
+++ b/app/Models/Presenters/ClientPresenter.php
@@ -100,10 +100,10 @@ class ClientPresenter extends EntityPresenter
if ($address2 = $client->shipping_address2) {
$str .= e($address2).'
';
}
- if ($cityState = $this->getCityState()) {
+ if ($cityState = $this->getShippingCityState()) {
$str .= e($cityState).'
';
}
- if ($country = $client->country) {
+ if ($country = $client->shipping_country) {
$str .= e($country->name).'
';
}
@@ -194,4 +194,6 @@ class ClientPresenter extends EntityPresenter
return false;
}
}
+
+
}
diff --git a/app/Observers/ClientObserver.php b/app/Observers/ClientObserver.php
index 4ab5d076e4a4..0b06b2cf3dee 100644
--- a/app/Observers/ClientObserver.php
+++ b/app/Observers/ClientObserver.php
@@ -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);
}
diff --git a/app/Observers/CompanyObserver.php b/app/Observers/CompanyObserver.php
index 93c2aeaf68c7..15ed25274945 100644
--- a/app/Observers/CompanyObserver.php
+++ b/app/Observers/CompanyObserver.php
@@ -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());
- // }
}
diff --git a/app/Repositories/BaseRepository.php b/app/Repositories/BaseRepository.php
index 005a117d02ee..15c93b5fe556 100644
--- a/app/Repositories/BaseRepository.php
+++ b/app/Repositories/BaseRepository.php
@@ -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) {
diff --git a/app/Services/Tax/Providers/TaxProvider.php b/app/Services/Tax/Providers/TaxProvider.php
index ccc5489f71f0..c43315d40920 100644
--- a/app/Services/Tax/Providers/TaxProvider.php
+++ b/app/Services/Tax/Providers/TaxProvider.php
@@ -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;
diff --git a/app/Services/Tax/Providers/ZipTax.php b/app/Services/Tax/Providers/ZipTax.php
index 8a6ffb78a2ef..0cc0dd1a5524 100644
--- a/app/Services/Tax/Providers/ZipTax.php
+++ b/app/Services/Tax/Providers/ZipTax.php
@@ -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']);
+
}
}
diff --git a/app/Utils/Traits/CompanySettingsSaver.php b/app/Utils/Traits/CompanySettingsSaver.php
index 79d08059fc20..915b2e88bea7 100644
--- a/app/Utils/Traits/CompanySettingsSaver.php
+++ b/app/Utils/Traits/CompanySettingsSaver.php
@@ -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);
}