init($client->locale()); $this->errors['client'] = $this->testClientState($client); $this->errors['passes'] = count($this->errors['client']) == 0; return $this->errors; } 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->errors['client'] = $this->testClientState($invoice->client); $this->errors['company'] = $this->testCompanyState($invoice->client); // uses client level settings which is what we want if (count($this->errors['client']) > 0) { $this->errors['passes'] = false; return $this->errors; } $p = new Peppol($invoice); $xml = false; try { $xml = $p->run()->toXml(); if (count($p->getErrors()) >= 1) { foreach ($p->getErrors() as $error) { $this->errors['invoice'][] = $error; } } } catch (PeppolValidationException $e) { $this->errors['invoice'] = ['field' => $e->getInvalidField(), 'label' => $e->getInvalidField()]; } catch (\Throwable $th) { } if ($xml) { // Second pass through the XSLT validator $xslt = new XsltDocumentValidator($xml); $errors = $xslt->validate()->getErrors(); if (isset($errors['stylesheet']) && count($errors['stylesheet']) > 0) { $this->errors['invoice'] = array_merge($this->errors['invoice'], $errors['stylesheet']); } if (isset($errors['general']) && count($errors['general']) > 0) { $this->errors['invoice'] = array_merge($this->errors['invoice'], $errors['general']); } if (isset($errors['xsd']) && count($errors['xsd']) > 0) { $this->errors['invoice'] = array_merge($this->errors['invoice'], $errors['xsd']); } } $this->checkNexus($invoice->client); $this->errors['passes'] = 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 = []; foreach ($this->client_fields as $field) { if ($this->validString($client->{$field})) { continue; } if ($field == 'country_id' && $client->country_id >= 1) { continue; } $errors[] = ['field' => $field, 'label' => ctrans("texts.{$field}")]; } //If not an individual, you MUST have a VAT number if you are in the EU if (!in_array($client->classification, ['government', 'individual']) && in_array($client->country->iso_3166_2, $this->eu_country_codes) && !$this->validString($client->vat_number)) { $errors[] = ['field' => 'vat_number', 'label' => ctrans("texts.vat_number")]; } //Primary contact email is present. if($client->present()->email() == 'No Email Set'){ $errors[] = ['field' => 'email', 'label' => ctrans("texts.email")]; } $delivery_network_supported = $client->checkDeliveryNetwork(); if(is_string($delivery_network_supported)) $errors[] = ['field' => ctrans("texts.country"), 'label' => $delivery_network_supported]; return $errors; } 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; } $errors = []; foreach ($this->company_settings_fields as $field) { if ($this->validString($settings_object->getSetting($field))) { continue; } $errors[] = ['field' => $field, 'label' => ctrans("texts.{$field}")]; } //test legal entity id present 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 ($company->getSetting('classification') != 'individual' && !$this->validString($company->getSetting('vat_number'))) { $errors[] = ['field' => 'vat_number', 'label' => ctrans("texts.vat_number")]; } elseif ($company->getSetting('classification') == 'individual' && !$this->validString($company->getSetting('id_number'))) { $errors[] = ['field' => 'id_number', 'label' => ctrans("texts.id_number")]; } // foreach($this->company_fields as $field) // { // } return $errors; } // private function testInvoiceState($entity): array // { // $errors = []; // foreach($this->invoice_fields as $field) // { // } // return $errors; // } // private function testVendorState(): array // { // } /************************************ helpers ************************************/ private function validString(?string $string): bool { return iconv_strlen($string) >= 1; } private function checkNexus(Client $client): self { $company_country_code = $client->company->country()->iso_3166_2; $client_country_code = $client->country->iso_3166_2; $br = new \App\DataMapper\Tax\BaseRule(); $eu_countries = $br->eu_country_codes; if ($client_country_code == $company_country_code) { } elseif (in_array($company_country_code, $eu_countries) && !in_array($client_country_code, $eu_countries)) { } elseif (in_array($client_country_code, $eu_countries)) { // First, determine if we're over threshold $is_over_threshold = isset($client->company->tax_data->regions->EU->has_sales_above_threshold) && $client->company->tax_data->regions->EU->has_sales_above_threshold; // Is this B2B or B2C? $is_b2c = strlen($client->vat_number) < 2 || !($client->has_valid_vat_number ?? false) || $client->classification == 'individual'; // B2C, under threshold, no Company VAT Registerd - must charge origin country VAT if ($is_b2c && !$is_over_threshold && strlen($client->company->settings->vat_number) < 2) { } elseif ($is_b2c) { if ($is_over_threshold) { // B2C over threshold - need destination VAT number if (!isset($client->company->tax_data->regions->EU->subregions->{$client_country_code}->vat_number)) { $this->errors['invoice'][] = "Tax Nexus is client country ({$client_country_code}) - however VAT number not present for this region."; } } } elseif ($is_over_threshold && !in_array($company_country_code, $eu_countries)) { } } return $this; } }