From 607f2ff69cbce9858357f35853ff47f6cfda5c69 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 3 Sep 2024 15:36:01 +1000 Subject: [PATCH] Tests for entity validation for einvoicing --- app/DataMapper/BaseSettings.php | 7 - app/Exceptions/PeppolValidationException.php | 34 ++ app/Services/EDocument/Standards/Peppol.php | 5 +- .../Standards/Peppol/BaseCountry.php | 23 + .../EDocument/Standards/Peppol/RO.php | 2 +- .../Validation/Peppol/EntityLevel.php | 136 ++++-- phpstan.neon | 3 +- .../Storecove/EInvoiceValidationTest.php | 439 ++++++++++++++++++ 8 files changed, 606 insertions(+), 43 deletions(-) create mode 100644 app/Exceptions/PeppolValidationException.php create mode 100644 app/Services/EDocument/Standards/Peppol/BaseCountry.php create mode 100644 tests/Integration/Einvoice/Storecove/EInvoiceValidationTest.php diff --git a/app/DataMapper/BaseSettings.php b/app/DataMapper/BaseSettings.php index 4cc61589abb9..882f1ed0ba8b 100644 --- a/app/DataMapper/BaseSettings.php +++ b/app/DataMapper/BaseSettings.php @@ -16,13 +16,6 @@ namespace App\DataMapper; */ class BaseSettings { - // //@deprecated - // public function __construct($obj) - // { - // // foreach ($obj as $key => $value) { - // // $obj->{$key} = $value; - // // } - // } public static function setCasts($obj, $casts) { diff --git a/app/Exceptions/PeppolValidationException.php b/app/Exceptions/PeppolValidationException.php new file mode 100644 index 000000000000..5d85eec04c96 --- /dev/null +++ b/app/Exceptions/PeppolValidationException.php @@ -0,0 +1,34 @@ +field = $field; + + // Ensure that everything is assigned properly by calling the parent constructor + parent::__construct($message, $code, $previous); + } + + public function getInvalidField() + { + return $this->field; + } +} diff --git a/app/Services/EDocument/Standards/Peppol.php b/app/Services/EDocument/Standards/Peppol.php index b6ea751536df..7b511525eb1f 100644 --- a/app/Services/EDocument/Standards/Peppol.php +++ b/app/Services/EDocument/Standards/Peppol.php @@ -11,6 +11,7 @@ namespace App\Services\EDocument\Standards; +use App\Exceptions\PeppolValidationException; use App\Models\Company; use App\Models\Invoice; use App\Helpers\Invoice\Taxer; @@ -1277,9 +1278,7 @@ class Peppol extends AbstractService */ private function checkRequired(bool $required, string $section): self { - - return $required ? throw new \Exception("e-invoice generation halted:: {$section} required", 400) : $this; - + return $required ? throw new PeppolValidationException("e-invoice generation halted:: {$section} required", $section, 400) : $this; } diff --git a/app/Services/EDocument/Standards/Peppol/BaseCountry.php b/app/Services/EDocument/Standards/Peppol/BaseCountry.php new file mode 100644 index 000000000000..e53db73956fc --- /dev/null +++ b/app/Services/EDocument/Standards/Peppol/BaseCountry.php @@ -0,0 +1,23 @@ + 'Alba', diff --git a/app/Services/EDocument/Standards/Validation/Peppol/EntityLevel.php b/app/Services/EDocument/Standards/Validation/Peppol/EntityLevel.php index f1a77ab4ead1..074de8f070da 100644 --- a/app/Services/EDocument/Standards/Validation/Peppol/EntityLevel.php +++ b/app/Services/EDocument/Standards/Validation/Peppol/EntityLevel.php @@ -11,8 +11,15 @@ namespace App\Services\EDocument\Standards\Validation\Peppol; +use App\Exceptions\PeppolValidationException; +use App\Models\Quote; use App\Models\Client; +use App\Models\Credit; +use App\Models\Vendor; +use App\Models\Company; use App\Models\Invoice; +use App\Models\PurchaseOrder; +use App\Services\EDocument\Standards\Peppol; use Illuminate\Support\Facades\App; class EntityLevel @@ -44,32 +51,68 @@ class EntityLevel private array $errors = []; - public function __invoke($entity): array + public function __construct() + { + } + + private function init(string $locale): self { App::forgetInstance('translator'); $t = app('translator'); - App::setLocale($entity->company->locale()); + App::setLocale($locale); - $this->errors['status'] = false; - $this->errors['client'] = $entity->client ? $this->testClientState($entity) : []; - $this->errors['company'] = $this->testCompanyState($entity->company); - $this->errors['invoice'] = $entity instanceof Invoice ? $this->testInvoiceState($entity) : []; - - // $this->errors['vendor']= $entity->client ? $this->testClientState($entity) : []; + return $this; - if( - count($this->errors['client']) == 0 && - count($this->errors['company']) == 0 - ){ - $this->errors['status'] = true; - } + } + + public function checkClient(Client $client): array + { + $this->init($client->locale()); + $this->errors['client'] = $this->testClientState($client); + $this->errors['passes'] = count($this->errors['client']) == 0; return $this->errors; } - private function testClientState($entity): array + public function checkCompany(Company $company): array + { + + $this->init($company->locale()); + $this->errors['company'] = $this->testCompanyState($company); + $this->errors['passes'] = count($this->errors['company']) == 0; + + return $this->errors; + + } + + public function checkInvoice(Invoice $invoice): array + { + $this->init($invoice->client->locale()); + + $this->errors['invoice'] = []; + $this->testClientState($invoice->client); + $this->testCompanyState($invoice->client); // uses client level settings which is what we want + + $p = new Peppol($invoice); + + try{ + $p->run()->toXml(); + } + catch(PeppolValidationException $e) { + + $this->errors['invoice'] = ['field' => $e->getInvalidField()]; + + }; + + $this->errors['status'] = count($this->errors['invoice']) == 0 && count($this->errors['client']) == 0 && count($this->errors['company']) == 0; + + return $this->errors; + + } + + private function testClientState(Client $client): array { $errors = []; @@ -77,7 +120,10 @@ class EntityLevel foreach($this->client_fields as $field) { - if($this->validString($entity->client->{$field})) + if($this->validString($client->{$field})) + continue; + + if($field == 'country_id' && $client->country_id >=1) continue; $errors[] = ['field' => ctrans("texts.{$field}")]; @@ -93,14 +139,44 @@ class EntityLevel } - private function testCompanyState($entity): array + private function testCompanyState(mixed $entity): array { + + $client = false; + $vendor = false; + $settings_object = false; + $company =false; + + if($entity instanceof Client){ + $client = $entity; + $company = $entity->company; + $settings_object = $client; + } + elseif($entity instanceof Company){ + $company = $entity; + $settings_object = $company; + } + elseif($entity instanceof Vendor){ + $vendor = $entity; + $company = $entity->company; + $settings_object = $company; + } + elseif($entity instanceof Invoice || $entity instanceof Credit || $entity instanceof Quote){ + $client = $entity->client; + $company = $entity->company; + $settings_object = $entity->client; + } + elseif($entity instanceof PurchaseOrder){ + $vendor = $entity->vendor; + $company = $entity->company; + $settings_object = $company; + } - $settings_object = $entity->client ? $entity->client : $entity->company; $errors = []; foreach($this->company_settings_fields as $field) { + if($this->validString($settings_object->getSetting($field))) continue; @@ -109,11 +185,11 @@ class EntityLevel } //test legal entity id present - if(!is_int($entity->company->legal_entity_id)) + if(!is_int($company->legal_entity_id)) $errors[] = ['field' => "You have not registered a legal entity id as yet."]; //If not an individual, you MUST have a VAT number - if($settings_object->getSetting('classification') != 'individual' && !$this->validString($settings_object->getSetting('vat_number'))) + if($company->getSetting('classification') != 'individual' && !$this->validString($company->getSetting('vat_number'))) { $errors[] = ['field' => ctrans("texts.vat_number")]; } @@ -127,17 +203,17 @@ class EntityLevel } - private function testInvoiceState(): array - { - $errors = []; + // private function testInvoiceState($entity): array + // { + // $errors = []; - foreach($this->invoice_fields as $field) - { + // foreach($this->invoice_fields as $field) + // { - } + // } - return $errors; - } + // return $errors; + // } // private function testVendorState(): array // { @@ -146,9 +222,9 @@ class EntityLevel /************************************ helpers ************************************/ - private function validString(?string $string) + private function validString(?string $string): bool { - return iconv_strlen($string) > 1; + return iconv_strlen($string) >= 1; } } \ No newline at end of file diff --git a/phpstan.neon b/phpstan.neon index aeb4c03e7948..f0eab2441b2c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -38,5 +38,4 @@ parameters: - '#Left side of && is always true.#' - '#Right side of && is always true.#' - '#is never read, only written.#' - - '#is never written#' - \ No newline at end of file + - '#is never written#' \ No newline at end of file diff --git a/tests/Integration/Einvoice/Storecove/EInvoiceValidationTest.php b/tests/Integration/Einvoice/Storecove/EInvoiceValidationTest.php new file mode 100644 index 000000000000..b14d2ea31921 --- /dev/null +++ b/tests/Integration/Einvoice/Storecove/EInvoiceValidationTest.php @@ -0,0 +1,439 @@ +makeTestData(); + + } + + public function testInvalidCompanySettings() + { + + $account = Account::factory()->create(); + $company = Company::factory()->create([ + 'account_id' => $account->id, + ]); + + $account->default_company_id = $company->id; + $account->save(); + + $el = new EntityLevel(); + $validation = $el->checkCompany($company); + + $this->assertFalse($validation['passes']); + + } + + public function testValidBusinessCompanySettings() + { + + $settings = CompanySettings::defaults(); + $settings->address1 = '10 Wallaby Way'; + $settings->city = 'Sydney'; + $settings->state = 'NSW'; + $settings->postal_code = '2113'; + $settings->country_id = '1'; + $settings->vat_number = 'ABN321231232'; + $settings->classification = 'business'; + + $account = Account::factory()->create(); + $company = Company::factory()->create([ + 'account_id' => $account->id, + 'legal_entity_id' => 123231, + 'settings' => $settings, + ]); + + $account->default_company_id = $company->id; + $account->save(); + + $el = new EntityLevel(); + $validation = $el->checkCompany($company); + + $this->assertTrue($validation['passes']); + + } + + + public function testInValidBusinessCompanySettingsNoVat() + { + + $settings = CompanySettings::defaults(); + $settings->address1 = '10 Wallaby Way'; + $settings->city = 'Sydney'; + $settings->state = 'NSW'; + $settings->postal_code = '2113'; + $settings->country_id = '1'; + $settings->vat_number = ''; + $settings->classification = 'business'; + + $account = Account::factory()->create(); + $company = Company::factory()->create([ + 'account_id' => $account->id, + 'legal_entity_id' => 123231, + 'settings' => $settings, + ]); + + $account->default_company_id = $company->id; + $account->save(); + + $el = new EntityLevel(); + $validation = $el->checkCompany($company); + + $this->assertFalse($validation['passes']); + + } + + public function testValidIndividualCompanySettingsNoVat() + { + + $settings = CompanySettings::defaults(); + $settings->address1 = '10 Wallaby Way'; + $settings->city = 'Sydney'; + $settings->state = 'NSW'; + $settings->postal_code = '2113'; + $settings->country_id = '1'; + $settings->vat_number = ''; + $settings->classification = 'individual'; + + $account = Account::factory()->create(); + $company = Company::factory()->create([ + 'account_id' => $account->id, + 'legal_entity_id' => 123231, + 'settings' => $settings, + ]); + + $account->default_company_id = $company->id; + $account->save(); + + $el = new EntityLevel(); + $validation = $el->checkCompany($company); + + $this->assertTrue($validation['passes']); + + } + + public function testInValidBusinessCompanySettingsNoLegalEntity() + { + + $settings = CompanySettings::defaults(); + $settings->address1 = '10 Wallaby Way'; + $settings->city = 'Sydney'; + $settings->state = 'NSW'; + $settings->postal_code = '2113'; + $settings->country_id = '1'; + $settings->vat_number = ''; + $settings->classification = 'business'; + + $account = Account::factory()->create(); + $company = Company::factory()->create([ + 'account_id' => $account->id, + 'settings' => $settings, + ]); + + $account->default_company_id = $company->id; + $account->save(); + + $el = new EntityLevel(); + $validation = $el->checkCompany($company); + + nlog($validation); + + $this->assertFalse($validation['passes']); + + } + + + + public function testInvalidClientSettings() + { + + $company = Company::factory()->create([ + 'account_id' => $this->account->id, + ]); + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'classification' => 'business', + 'vat_number' => '', + ]); + + $el = new EntityLevel(); + $validation = $el->checkClient($client); + + $this->assertFalse($validation['passes']); + + } + + public function testInvalidClientSettingsNoCountry() + { + + $company = Company::factory()->create([ + 'account_id' => $this->account->id, + ]); + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'classification' => 'individual', + 'vat_number' => '', + 'country_id' => null, + ]); + + $el = new EntityLevel(); + $validation = $el->checkClient($client); + + $this->assertFalse($validation['passes']); + + } + + public function testInvalidClientSettingsMissingAddress() + { + + $company = Company::factory()->create([ + 'account_id' => $this->account->id, + ]); + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'classification' => 'individual', + 'vat_number' => '', + 'country_id' => null, + ]); + + $el = new EntityLevel(); + $validation = $el->checkClient($client); + + $this->assertFalse($validation['passes']); + + } + + public function testInvalidClientSettingsMissingAddressOnlyCountry() + { + + $company = Company::factory()->create([ + 'account_id' => $this->account->id, + ]); + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'classification' => 'individual', + 'vat_number' => '', + 'country_id' => 1, + 'address1' => '', + 'address2' => '', + 'city' => '', + 'state' => '', + 'postal_code' => '', + ]); + + $el = new EntityLevel(); + $validation = $el->checkClient($client); + + $this->assertFalse($validation['passes']); + + } + + public function testInvalidClientSettingsMissingAddressOnlyCountryAndAddress1() + { + + $company = Company::factory()->create([ + 'account_id' => $this->account->id, + ]); + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'classification' => 'individual', + 'vat_number' => '', + 'country_id' => 1, + 'address1' => '10 Wallaby Way', + 'address2' => '', + 'city' => '', + 'state' => '', + 'postal_code' => '', + ]); + + $el = new EntityLevel(); + $validation = $el->checkClient($client); + + $this->assertFalse($validation['passes']); + + } + + public function testInvalidClientSettingsMissingAddressOnlyCountryAndAddress1AndCity() + { + + $company = Company::factory()->create([ + 'account_id' => $this->account->id, + ]); + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'classification' => 'individual', + 'vat_number' => '', + 'country_id' => 1, + 'address1' => '10 Wallaby Way', + 'address2' => '', + 'city' => 'Sydney', + 'state' => '', + 'postal_code' => '', + ]); + + $el = new EntityLevel(); + $validation = $el->checkClient($client); + + + $this->assertFalse($validation['passes']); + + } + + public function testInvalidClientSettingsMissingAddressOnlyCountryAndAddress1AndCityAndState() + { + + $company = Company::factory()->create([ + 'account_id' => $this->account->id, + ]); + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'classification' => 'individual', + 'vat_number' => '', + 'country_id' => 1, + 'address1' => '10 Wallaby Way', + 'address2' => '', + 'city' => 'Sydney', + 'state' => 'NSW', + 'postal_code' => '', + ]); + + $el = new EntityLevel(); + $validation = $el->checkClient($client); + + + $this->assertFalse($validation['passes']); + + } + + public function testValidIndividualClient() + { + + $company = Company::factory()->create([ + 'account_id' => $this->account->id, + ]); + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'classification' => 'individual', + 'vat_number' => '', + 'country_id' => 1, + 'address1' => '10 Wallaby Way', + 'address2' => '', + 'city' => 'Sydney', + 'state' => 'NSW', + 'postal_code' => '2113', + ]); + + $el = new EntityLevel(); + $validation = $el->checkClient($client); + + $this->assertTrue($validation['passes']); + + } + + public function testValidBusinessClient() + { + + $company = Company::factory()->create([ + 'account_id' => $this->account->id, + ]); + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'classification' => 'business', + 'vat_number' => 'DE123456789', + 'country_id' => 1, + 'address1' => '10 Wallaby Way', + 'address2' => '', + 'city' => 'Sydney', + 'state' => 'NSW', + 'postal_code' => '2113', + ]); + + $el = new EntityLevel(); + $validation = $el->checkClient($client); + + $this->assertTrue($validation['passes']); + + } + + public function testInValidBusinessClientNoVat() + { + + $company = Company::factory()->create([ + 'account_id' => $this->account->id, + ]); + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $company->id, + 'classification' => 'business', + 'vat_number' => '', + 'country_id' => 1, + 'address1' => '10 Wallaby Way', + 'address2' => '', + 'city' => 'Sydney', + 'state' => 'NSW', + 'postal_code' => '2113', + ]); + + $el = new EntityLevel(); + $validation = $el->checkClient($client); + + $this->assertEquals(0, strlen($client->vat_number)); + + $this->assertFalse($validation['passes']); + + } +} \ No newline at end of file