From fbc0895f104a5f8025b1e4bf0a85b83e93e97fb7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 25 May 2023 16:09:12 +1000 Subject: [PATCH 1/7] Improve auto bill text in client portal --- lang/en/texts.php | 1 + .../livewire/recurring-invoices-switch-autobilling.blade.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lang/en/texts.php b/lang/en/texts.php index 0d420fe76a..b73e5f782c 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5094,6 +5094,7 @@ $LANG = array( 'order_id' => 'Order', 'total_invoices_outstanding' => 'Total Invoices Outstanding', 'recent_activity' => 'Recent Activity', + 'enable_auto_bill' => 'Enable auto billing', ); diff --git a/resources/views/portal/ninja2020/components/livewire/recurring-invoices-switch-autobilling.blade.php b/resources/views/portal/ninja2020/components/livewire/recurring-invoices-switch-autobilling.blade.php index c9c080cd41..a7392639f9 100644 --- a/resources/views/portal/ninja2020/components/livewire/recurring-invoices-switch-autobilling.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/recurring-invoices-switch-autobilling.blade.php @@ -3,6 +3,6 @@ wire:change="updateAutoBilling" {{ $invoice->auto_bill_enabled ? 'checked' : '' }}> - {{ $invoice->auto_bill_enabled || $invoice->auto_bill === 'optout' ? ctrans('texts.auto_bill_enabled') : ctrans('texts.auto_bill_disabled') }} + {{ ctrans('texts.enable_auto_bill') }} From 05accdf1c58869ee9833ca37808db50e296adf31 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 25 May 2023 16:24:27 +1000 Subject: [PATCH 2/7] Minor fixes --- app/Services/Bank/ProcessBankRules.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Services/Bank/ProcessBankRules.php b/app/Services/Bank/ProcessBankRules.php index 80da449f7a..14dd32fb58 100644 --- a/app/Services/Bank/ProcessBankRules.php +++ b/app/Services/Bank/ProcessBankRules.php @@ -31,6 +31,8 @@ class ProcessBankRules extends AbstractService protected $categories; + protected $invoices; + public function __construct(public BankTransaction $bank_transaction) { } From 6485e48896a6978af2ebaa2663780782a544f5f0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 25 May 2023 16:41:29 +1000 Subject: [PATCH 3/7] Fixes for VAT number checks --- app/Jobs/Client/CheckVat.php | 65 +++++++++++++++++++++++++++ app/Observers/ClientObserver.php | 46 ++++++++++++++++++- app/Repositories/ClientRepository.php | 5 ++- app/Services/Tax/TaxService.php | 7 ++- 4 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 app/Jobs/Client/CheckVat.php diff --git a/app/Jobs/Client/CheckVat.php b/app/Jobs/Client/CheckVat.php new file mode 100644 index 0000000000..49d5f17d2a --- /dev/null +++ b/app/Jobs/Client/CheckVat.php @@ -0,0 +1,65 @@ +company->db); + + $tax_service = new TaxService($this->client); + $tax_service->validateVat(); + + } + + public function middleware() + { + return [new WithoutOverlapping($this->client->id)]; + } + +} diff --git a/app/Observers/ClientObserver.php b/app/Observers/ClientObserver.php index 814ee7f0c4..4ab5d076e4 100644 --- a/app/Observers/ClientObserver.php +++ b/app/Observers/ClientObserver.php @@ -11,8 +11,10 @@ namespace App\Observers; +use App\Utils\Ninja; use App\Models\Client; use App\Models\Webhook; +use App\Jobs\Client\CheckVat; use App\Jobs\Util\WebhookHandler; use App\Jobs\Client\UpdateTaxData; @@ -20,6 +22,36 @@ class ClientObserver { public $afterCommit = true; + private $eu_country_codes = [ + 'AT' => '40', + 'BE' => '56', + 'BG' => '100', + 'CY' => '196', + 'CZ' => '203', + 'DE' => '276', + 'DK' => '208', + 'EE' => '233', + 'ES' => '724', + 'FI' => '246', + 'FR' => '250', + 'GR' => '300', + 'HR' => '191', + 'HU' => '348', + 'IE' => '372', + 'IT' => '380', + 'LT' => '440', + 'LU' => '442', + 'LV' => '428', + 'MT' => '470', + 'NL' => '528', + 'PL' => '616', + 'PT' => '620', + 'RO' => '642', + 'SE' => '752', + 'SI' => '705', + 'SK' => '703', + ]; + /** * Handle the client "created" event. * @@ -33,6 +65,10 @@ class ClientObserver UpdateTaxData::dispatch($client, $client->company); } + if(in_array($client->country_id, $this->eu_country_codes) && $client->company->calculate_taxes) { + CheckVat::dispatch($client, $client->company); + } + $subscriptions = Webhook::where('company_id', $client->company_id) ->where('event_id', Webhook::EVENT_CREATE_CLIENT) ->exists(); @@ -50,11 +86,17 @@ class ClientObserver */ public function updated(Client $client) { - if($client->getOriginal('postal_code') != $client->postal_code && $client->country_id == 840 && $client->company->calculate_taxes) - { + + /** 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) { UpdateTaxData::dispatch($client, $client->company); } + /** Monitor vat numbers for EU based clients for tax calculations */ + if($client->getOriginal('vat_number') != $client->vat_number && in_array($client->country_id, $this->eu_country_codes) && $client->company->calculate_taxes) { + CheckVat::dispatch($client, $client->company); + } + $event = Webhook::EVENT_UPDATE_CLIENT; if ($client->getOriginal('deleted_at') && !$client->deleted_at) { diff --git a/app/Repositories/ClientRepository.php b/app/Repositories/ClientRepository.php index 851dd83e7d..ec324ef693 100644 --- a/app/Repositories/ClientRepository.php +++ b/app/Repositories/ClientRepository.php @@ -117,9 +117,12 @@ class ClientRepository extends BaseRepository */ public function create($client): ?Client { + /** @var \App\Models\User $user */ + $user = auth()->user(); + return $this->save( $client, - ClientFactory::create(auth()->user()->company()->id, auth()->user()->id) + ClientFactory::create($user->company()->id, $user->id) ); } diff --git a/app/Services/Tax/TaxService.php b/app/Services/Tax/TaxService.php index 89dd1ba7c3..02d1f21623 100644 --- a/app/Services/Tax/TaxService.php +++ b/app/Services/Tax/TaxService.php @@ -26,10 +26,13 @@ class TaxService $vat_check = (new VatNumberCheck($this->client->vat_number, $client_country_code))->run(); - $this->client->has_valid_vat_number = $vat_check->isValid(); - $this->client->saveQuietly(); + if($vat_check->isValid()) { + $this->client->has_valid_vat_number = true; + $this->client->saveQuietly(); + } return $this; + } public function initTaxProvider() From 310d14fd9cc9e0e26a09764c355f0282acbb5892 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 25 May 2023 17:19:52 +1000 Subject: [PATCH 4/7] VAT number checker --- app/Services/Tax/TaxService.php | 19 ++++++++++++++++++- app/Services/Tax/VatNumberCheck.php | 14 ++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/app/Services/Tax/TaxService.php b/app/Services/Tax/TaxService.php index 02d1f21623..d724e175ac 100644 --- a/app/Services/Tax/TaxService.php +++ b/app/Services/Tax/TaxService.php @@ -22,17 +22,34 @@ class TaxService public function validateVat(): self { + if(!extension_loaded('soap')) { + nlog("Install the PHP SOAP extension if you wish to check VAT Numbers. See https://www.php.net/manual/en/soap.installation.php for more information on installing the PHP"); + return $this; + } + $client_country_code = $this->client->shipping_country ? $this->client->shipping_country->iso_3166_2 : $this->client->country->iso_3166_2; $vat_check = (new VatNumberCheck($this->client->vat_number, $client_country_code))->run(); + nlog($vat_check); + if($vat_check->isValid()) { + $this->client->has_valid_vat_number = true; + + if(!$this->client->name && strlen($vat_check->getName()) > 2) { + $this->client->name = $vat_check->getName(); + } + + if(empty($this->client->private_notes) && strlen($vat_check->getAddress()) > 2) { + $this->client->private_notes = $vat_check->getAddress(); + } + $this->client->saveQuietly(); } return $this; - + } public function initTaxProvider() diff --git a/app/Services/Tax/VatNumberCheck.php b/app/Services/Tax/VatNumberCheck.php index e2eea03ada..61c5f2bb50 100644 --- a/app/Services/Tax/VatNumberCheck.php +++ b/app/Services/Tax/VatNumberCheck.php @@ -15,7 +15,7 @@ class VatNumberCheck { private array $response = []; - public function __construct(protected string $vat_number, protected string $country_code) + public function __construct(protected ?string $vat_number, protected string $country_code) { } @@ -32,7 +32,7 @@ class VatNumberCheck $client = new \SoapClient($wsdl); $params = [ 'countryCode' => $this->country_code, - 'vatNumber' => $this->vat_number + 'vatNumber' => $this->vat_number ?? '' ]; $response = $client->checkVat($params); @@ -63,4 +63,14 @@ class VatNumberCheck { return $this->response['valid']; } + + public function getName() + { + return isset($this->response['name']) ? $this->response['name'] : ''; + } + + public function getAddress() + { + return isset($this->response['address']) ? $this->response['address'] : ''; + } } From b66272c974dfe45406c29e73c0e3a5718ecc69cf Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 25 May 2023 17:21:17 +1000 Subject: [PATCH 5/7] only send statements with a balance greater than zero --- app/Services/Scheduler/EmailStatementService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/Scheduler/EmailStatementService.php b/app/Services/Scheduler/EmailStatementService.php index 235e142938..a7e6f56ae4 100644 --- a/app/Services/Scheduler/EmailStatementService.php +++ b/app/Services/Scheduler/EmailStatementService.php @@ -36,7 +36,7 @@ class EmailStatementService //Email only the selected clients if (count($this->scheduler->parameters['clients']) >= 1) { - $query->whereIn('id', $this->transformKeys($this->scheduler->parameters['clients'])); + $query->whereIn('id', $this->transformKeys($this->scheduler->parameters['clients']))->where('balance', '>', 0); } $query->cursor() From 57e7b4f8794a0dcc8dad32563c7087a9ae89f74c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 26 May 2023 12:07:37 +1000 Subject: [PATCH 6/7] Enhancements for reports --- app/DataMapper/Tax/US/Rule.php | 6 ++++-- app/Export/CSV/BaseExport.php | 4 +++- app/Export/CSV/ClientExport.php | 6 +++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/DataMapper/Tax/US/Rule.php b/app/DataMapper/Tax/US/Rule.php index 600dcf2b0f..eb1b55b86c 100644 --- a/app/DataMapper/Tax/US/Rule.php +++ b/app/DataMapper/Tax/US/Rule.php @@ -129,11 +129,13 @@ class Rule extends BaseRule implements RuleInterface */ public function taxShipping($item): self { - if($this->tax_data?->txbFreight == 'Y') { - $this->default($item); + return $this->default($item); } + $this->tax_rate1 = 0; + $this->tax_name1 = ''; + return $this; } diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index 65b3bd50b3..f2a4e1001d 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -33,6 +33,8 @@ class BaseExport public string $client_description = 'All Clients'; + public array $forced_keys = []; + protected function filterByClients($query) { if (isset($this->input['client_id']) && $this->input['client_id'] != 'all') { @@ -170,7 +172,7 @@ class BaseExport { $header = []; - foreach ($this->input['report_keys'] as $value) { + foreach (array_merge($this->input['report_keys'], $this->forced_keys) as $value) { $key = array_search($value, $this->entity_keys); $key = str_replace('item.', '', $key); diff --git a/app/Export/CSV/ClientExport.php b/app/Export/CSV/ClientExport.php index 43cfa9ab7b..7d539795a9 100644 --- a/app/Export/CSV/ClientExport.php +++ b/app/Export/CSV/ClientExport.php @@ -81,6 +81,10 @@ class ClientExport extends BaseExport 'client.industry', ]; + public array $forced_keys = [ + 'status', + ]; + public function __construct(Company $company, array $input) { $this->company = $company; @@ -103,7 +107,7 @@ class ClientExport extends BaseExport if (count($this->input['report_keys']) == 0) { $this->input['report_keys'] = array_values($this->entity_keys); } - + //insert the header $this->csv->insertOne($this->buildHeader()); From 34f019bacd6dcf329ae1b73cd858e45443c173b9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 26 May 2023 13:13:32 +1000 Subject: [PATCH 7/7] Shorten sleep window in reminders --- app/Jobs/Util/ReminderJob.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Jobs/Util/ReminderJob.php b/app/Jobs/Util/ReminderJob.php index 76a048675b..c7955d3f40 100644 --- a/app/Jobs/Util/ReminderJob.php +++ b/app/Jobs/Util/ReminderJob.php @@ -75,7 +75,7 @@ class ReminderJob implements ShouldQueue $this->sendReminderForInvoice($invoice); } - sleep(2); + sleep(1); }); } else { //multiDB environment, need to @@ -105,7 +105,7 @@ class ReminderJob implements ShouldQueue $this->sendReminderForInvoice($invoice); } - sleep(2); + sleep(1); }); } }