mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2026-04-18 12:10:50 +00:00
Handle DE => DE
This commit is contained in:
@@ -121,7 +121,7 @@ class CreatePeppolTestData extends Command
|
||||
'gov_id' => '003798765432', 'individual_id' => '', 'individual_vat' => '',
|
||||
],
|
||||
'FR' => [
|
||||
'vat' => 'FR82345678911', 'id_number' => '82345678911', 'tax_rate' => 20, 'tax_name' => 'TVA',
|
||||
'vat' => 'FR82345678911', 'id_number' => '823456789', 'tax_rate' => 20, 'tax_name' => 'TVA',
|
||||
'city' => 'Paris', 'state' => 'Ile-de-France', 'postal_code' => '75001', 'currency' => '3',
|
||||
'address1' => 'Rue de Rivoli 1',
|
||||
'gov_id' => '12345678901234', 'individual_id' => '', 'individual_vat' => '',
|
||||
@@ -321,7 +321,7 @@ class CreatePeppolTestData extends Command
|
||||
|
||||
// ── Oceania ──
|
||||
'AU' => [
|
||||
'vat' => '12345678901', 'id_number' => 'ABN12345678901', 'tax_rate' => 10, 'tax_name' => 'GST',
|
||||
'vat' => '12345678901', 'id_number' => '12345678901', 'tax_rate' => 10, 'tax_name' => 'GST',
|
||||
'city' => 'Sydney', 'state' => 'NSW', 'postal_code' => '2000', 'currency' => '12',
|
||||
'address1' => 'George Street 1',
|
||||
'gov_id' => 'ABN98765432100', 'individual_id' => '', 'individual_vat' => '',
|
||||
@@ -347,7 +347,7 @@ class CreatePeppolTestData extends Command
|
||||
'gov_id' => 'T9876543210987', 'individual_id' => '', 'individual_vat' => '',
|
||||
],
|
||||
'MY' => [
|
||||
'vat' => 'MY123456789012', 'id_number' => 'C12345678', 'tax_rate' => 8, 'tax_name' => 'SST',
|
||||
'vat' => 'MY123456789012', 'id_number' => 'C1234567890', 'tax_rate' => 8, 'tax_name' => 'SST',
|
||||
'city' => 'Kuala Lumpur', 'state' => 'WP Kuala Lumpur', 'postal_code' => '50000', 'currency' => '19',
|
||||
'address1' => 'Jalan Bukit Bintang 1',
|
||||
'gov_id' => 'GOV-MY-001', 'individual_id' => '', 'individual_vat' => '',
|
||||
|
||||
@@ -33,7 +33,7 @@ class ClientFactory
|
||||
$client->is_deleted = false;
|
||||
$client->client_hash = Str::random(40);
|
||||
$client->settings = ClientSettings::defaults();
|
||||
$client->classification = '';
|
||||
$client->classification = 'business';
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ use App\Http\Requests\EInvoice\UpdateEInvoiceConfiguration;
|
||||
use App\Services\EDocument\Standards\Validation\Peppol\EntityLevel;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use App\Services\EDocument\Gateway\Storecove\StorecoveRouter;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\BranchType\FinancialInstitutionBranch;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\FinancialInstitutionType\FinancialInstitution;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\FinancialAccountType\PayeeFinancialAccount;
|
||||
@@ -198,6 +199,19 @@ class EInvoiceController extends BaseController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the static Peppol delivery map for the UI.
|
||||
*
|
||||
* Per country: which classifications are routable and
|
||||
* what client identifiers are required for each.
|
||||
*/
|
||||
public function deliveryMap(): JsonResponse
|
||||
{
|
||||
$router = new StorecoveRouter();
|
||||
|
||||
return response()->json($router->getDeliveryMap());
|
||||
}
|
||||
|
||||
public function healthcheck(HealthcheckRequest $request): JsonResponse
|
||||
{
|
||||
/** @var \App\Models\User $user */
|
||||
|
||||
@@ -1060,18 +1060,20 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
}
|
||||
|
||||
$br = new \App\DataMapper\Tax\BaseRule();
|
||||
$supported_countries = array_unique(array_merge($br->peppol_business_countries, $br->peppol_government_countries));
|
||||
$country_code = $this->country->iso_3166_2;
|
||||
|
||||
$government_countries = array_merge($br->peppol_business_countries, $br->peppol_government_countries);
|
||||
|
||||
if (in_array($this->country->iso_3166_2, $government_countries) && $this->classification == 'government') {
|
||||
return null;
|
||||
if (!in_array($country_code, $supported_countries)) {
|
||||
return "Country {$this->country->full_name} ( {$country_code} ) is not supported by the PEPPOL network for e-delivery.";
|
||||
}
|
||||
|
||||
if (in_array($this->country->iso_3166_2, $br->peppol_business_countries)) {
|
||||
return null;
|
||||
$router = new \App\Services\EDocument\Gateway\Storecove\StorecoveRouter();
|
||||
|
||||
if (!$router->isClassificationRoutable($country_code, $this->classification)) {
|
||||
return ucfirst($this->classification) . " clients in {$this->country->full_name} ( {$country_code} ) are not routable on the Peppol network.";
|
||||
}
|
||||
|
||||
return "Country {$this->country->full_name} ( {$this->country->iso_3166_2} ) is not supported by the PEPPOL network for e-delivery.";
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -253,11 +253,16 @@ class Mutator implements MutatorInterface
|
||||
|
||||
$identifier = false;
|
||||
|
||||
// Non-VAT routing schemes (DK:DIGST, SE:ORGNR, FI:OVT, EE:CC, NO:ORG, LT:LEC, etc.)
|
||||
// use id_number (org/registry number), not vat_number.
|
||||
// IT:CUUO uses routing_id (SDI code).
|
||||
$is_vat_scheme = str_contains($code, ':VAT') || str_contains($code, ':IVA') || str_contains($code, ':CF');
|
||||
|
||||
if ($this->invoice->client->country->iso_3166_2 == 'FR') {
|
||||
$identifier = $this->invoice->client->id_number;
|
||||
} elseif ($this->invoice->client->country->iso_3166_2 == 'DK') {
|
||||
$identifier = $this->invoice->client->id_number;
|
||||
} elseif ($this->invoice->client->country->iso_3166_2 == 'SE' && strlen($this->invoice->client->id_number ?? '') > 2) {
|
||||
} elseif (str_contains($code, ':CUUO') && strlen($this->invoice->client->routing_id ?? '') > 1) {
|
||||
$identifier = $this->invoice->client->routing_id;
|
||||
} elseif (!$is_vat_scheme && strlen($this->invoice->client->id_number ?? '') > 1) {
|
||||
$identifier = $this->invoice->client->id_number;
|
||||
} else {
|
||||
$identifier = $this->invoice->client->vat_number;
|
||||
@@ -274,6 +279,11 @@ class Mutator implements MutatorInterface
|
||||
$country_prefix = $this->invoice->client->country->iso_3166_2;
|
||||
$identifier = preg_replace("/[^a-zA-Z0-9]/", "", $identifier);
|
||||
|
||||
// DK:DIGST expects DK prefix on the CVR number — ensure it's present
|
||||
if ($code === 'DK:DIGST' && !str_starts_with(strtoupper($identifier), 'DK')) {
|
||||
$identifier = 'DK' . $identifier;
|
||||
}
|
||||
|
||||
//Check the recipient is on the network, and can be delivered the correct document.
|
||||
if($this->invoice->client->country->iso_3166_2 == "BE"){
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@ class StorecoveRouter
|
||||
["B","DUNS, GLN, LEI","US:EIN","DUNS, GLN, LEI"],
|
||||
// ["B","DUNS, GLN, LEI","US:SSN","DUNS, GLN, LEI"],
|
||||
],
|
||||
"CA" => ["B","CA:CBN",false,"CA:CBN"],
|
||||
"MX" => ["B","MX:RFC",false,"MX:RFC"],
|
||||
"CA" => ["B","CA:CBN","CA:CBN","CA:CBN"],
|
||||
"MX" => ["B","MX:RFC","MX:RFC","MX:RFC"],
|
||||
"AU" => ["B+G","AU:ABN","AU:ABN","AU:ABN"],
|
||||
"NZ" => ["B+G","GLN","NZ:GST","GLN"],
|
||||
"CH" => ["B+G","CH:UIDB","CH:VAT","CH:UIDB"],
|
||||
@@ -70,7 +70,7 @@ class StorecoveRouter
|
||||
["G","","IT:IVA","IT:CUUO"],// (SDI)
|
||||
],
|
||||
"LT" => ["B+G","LT:LEC","LT:VAT","LT:LEC"],
|
||||
"LU" => ["B+G","LU:MAT","LU:VAT","LU:VAT"],
|
||||
"LU" => ["B+G","LU:VAT","LU:VAT","LU:VAT"],
|
||||
"LV" => ["B+G","","LV:VAT","LV:VAT"],
|
||||
"MC" => ["B+G","","MC:VAT","MC:VAT"],
|
||||
"ME" => ["B+G","","ME:VAT","ME:VAT"],
|
||||
@@ -165,7 +165,7 @@ class StorecoveRouter
|
||||
'SE:ORGNR' => '/^\d{10}$/',
|
||||
'NO:ORG' => '/^\d{9}$/',
|
||||
'BE:EN' => '/^(BE)?\d{10}$/i',
|
||||
'DK:DIGST' => '/^\d{8,10}$/',
|
||||
'DK:DIGST' => '/^(DK)?\d{8}$/i',
|
||||
'EE:CC' => '/^\d{8}$/',
|
||||
'FI:OVT' => '/^\d{12,13}$/',
|
||||
'FR:SIRENE' => '/^\d{9}$/',
|
||||
@@ -316,6 +316,43 @@ class StorecoveRouter
|
||||
return $rules[0][2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a classification (business/government/individual) is routable
|
||||
* on the Peppol network for a given country.
|
||||
*
|
||||
* @param string $country ISO 3166-2 country code
|
||||
* @param string $classification business|government|individual
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassificationRoutable(string $country, string $classification): bool
|
||||
{
|
||||
$rules = $this->routing_rules[$country] ?? null;
|
||||
|
||||
if (!$rules) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$code = match ($classification) {
|
||||
'government' => 'G',
|
||||
'individual' => 'C',
|
||||
default => 'B',
|
||||
};
|
||||
|
||||
// Single-array country (e.g. ["B+G", ...])
|
||||
if (is_array($rules) && !is_array($rules[0])) {
|
||||
return stripos($rules[0], $code) !== false;
|
||||
}
|
||||
|
||||
// Multi-array — check if any rule matches this classification
|
||||
foreach ($rules as $r) {
|
||||
if (stripos($r[0], $code) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the required client fields for a given country/classification.
|
||||
*
|
||||
@@ -520,6 +557,42 @@ class StorecoveRouter
|
||||
return $map[$scheme] ?? $scheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a static delivery map for all supported countries.
|
||||
*
|
||||
* Each entry contains routability by classification and the required
|
||||
* client identifiers, so the UI can determine sendability without
|
||||
* calling the validation endpoint.
|
||||
*
|
||||
* @return array<string, array{
|
||||
* classifications: array<string, bool>,
|
||||
* required_fields: array<string, array<string, string>>
|
||||
* }>
|
||||
*/
|
||||
public function getDeliveryMap(): array
|
||||
{
|
||||
$map = [];
|
||||
|
||||
foreach ($this->routing_rules as $country => $rules) {
|
||||
$entry = [
|
||||
'classifications' => [
|
||||
'business' => $this->isClassificationRoutable($country, 'business'),
|
||||
'government' => $this->isClassificationRoutable($country, 'government'),
|
||||
'individual' => $this->isClassificationRoutable($country, 'individual'),
|
||||
],
|
||||
'required_fields' => [
|
||||
'business' => $this->resolveRequiredClientFields($country, 'business'),
|
||||
'government' => $this->resolveRequiredClientFields($country, 'government'),
|
||||
'individual' => $this->resolveRequiredClientFields($country, 'individual'),
|
||||
],
|
||||
];
|
||||
|
||||
$map[$country] = $entry;
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
public function resolveIdentifierTypeByValue(string $identifier): string
|
||||
{
|
||||
$parts = explode(":", $identifier);
|
||||
|
||||
@@ -102,7 +102,7 @@ class SendEDocument implements ShouldQueue
|
||||
'e_invoicing_token' => $model->company->account->e_invoicing_token,
|
||||
];
|
||||
|
||||
nlog("payload", $payload);
|
||||
// nlog("payload", $payload);
|
||||
|
||||
//Self Hosted Sending Code Path
|
||||
if (Ninja::isSelfHost() && ($model instanceof Invoice || $model instanceof Credit) && $model->company->peppolSendingEnabled()) {
|
||||
|
||||
@@ -176,6 +176,7 @@ class EntityLevel implements EntityLevelInterface
|
||||
public function checkClient(Client $client): array
|
||||
{
|
||||
$this->init($client->locale());
|
||||
|
||||
$this->errors['client'] = $this->testClientState($client);
|
||||
$this->errors['passes'] = count($this->errors['client']) == 0;
|
||||
|
||||
@@ -204,7 +205,7 @@ class EntityLevel implements EntityLevelInterface
|
||||
$this->init($invoice->client->locale());
|
||||
|
||||
$this->errors['invoice'] = [];
|
||||
$this->errors['client'] = $this->testClientState($invoice->client);
|
||||
$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) {
|
||||
@@ -312,21 +313,21 @@ class EntityLevel implements EntityLevelInterface
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//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];
|
||||
if ($client->country_id && $client->country) {
|
||||
$non_routable = $client->checkDeliveryNetwork();
|
||||
|
||||
if (is_string($non_routable)) {
|
||||
$errors[] = ['field' => 'classification', 'label' => $non_routable];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return $errors;
|
||||
|
||||
}
|
||||
|
||||
@@ -258,6 +258,7 @@ Route::group(['middleware' => ['throttle:api', 'token_auth', 'valid_json','local
|
||||
Route::post('einvoice/token/update', EInvoiceTokenController::class)->name('einvoice.token.update');
|
||||
Route::get('einvoice/quota', [EInvoiceController::class, 'quota'])->name('einvoice.quota');
|
||||
Route::get('einvoice/health_check', [EInvoiceController::class, 'healthcheck'])->name('einvoice.healthcheck');
|
||||
Route::get('einvoice/delivery_map', [EInvoiceController::class, 'deliveryMap'])->name('einvoice.delivery_map');
|
||||
|
||||
Route::post('emails', [EmailController::class, 'send'])->name('email.send')->middleware('user_verified');
|
||||
Route::post('emails/clientHistory/{client}', [EmailHistoryController::class, 'clientHistory'])->name('email.clientHistory');
|
||||
|
||||
311
tests/Feature/EInvoice/CheckDeliveryNetworkTest.php
Normal file
311
tests/Feature/EInvoice/CheckDeliveryNetworkTest.php
Normal file
@@ -0,0 +1,311 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2026. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace Tests\Feature\EInvoice;
|
||||
|
||||
use Tests\TestCase;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use Tests\MockAccountData;
|
||||
use App\Models\ClientContact;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
|
||||
class CheckDeliveryNetworkTest extends TestCase
|
||||
{
|
||||
use MockAccountData;
|
||||
use DatabaseTransactions;
|
||||
|
||||
private Company $testCompany;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->makeTestData();
|
||||
|
||||
$this->testCompany = Company::factory()->create([
|
||||
'account_id' => $this->account->id,
|
||||
]);
|
||||
}
|
||||
|
||||
private function makeClient(int $countryId, string $classification): Client
|
||||
{
|
||||
$client = Client::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'company_id' => $this->testCompany->id,
|
||||
'country_id' => $countryId,
|
||||
'classification' => $classification,
|
||||
]);
|
||||
|
||||
ClientContact::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $client->id,
|
||||
'company_id' => $this->testCompany->id,
|
||||
'is_primary' => 1,
|
||||
'email' => 'test@example.com',
|
||||
]);
|
||||
|
||||
return $client->fresh();
|
||||
}
|
||||
|
||||
// ────────────────────────────<E29480><E29480><EFBFBD>──────────────────────<E29480><E29480><EFBFBD>──
|
||||
// No country set
|
||||
// ───────────────────────────<E29480><E29480>──────────────────────────
|
||||
|
||||
public function testNoCountryReturnsError(): void
|
||||
{
|
||||
$client = Client::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'company_id' => $this->testCompany->id,
|
||||
'country_id' => null,
|
||||
'classification' => 'business',
|
||||
]);
|
||||
|
||||
$result = $client->fresh()->checkDeliveryNetwork();
|
||||
$this->assertIsString($result);
|
||||
$this->assertStringContainsString('country', strtolower($result));
|
||||
}
|
||||
|
||||
// ───────────────────────<E29480><E29480><EFBFBD>─────────────────────<E29480><E29480><EFBFBD>────────
|
||||
// Peppol Business Countries — B+G routable, C blocked
|
||||
// AT, BE, DK, EE, FI, DE, IS, LT, LU, NL, NO, SE, IE
|
||||
// ──────────────────────────────────<E29480><E29480>───────────────────
|
||||
|
||||
/**
|
||||
* @dataProvider peppolBusinessCountryProvider
|
||||
*/
|
||||
public function testBusinessCountryBusinessIsRoutable(int $countryId, string $countryCode): void
|
||||
{
|
||||
$client = $this->makeClient($countryId, 'business');
|
||||
$this->assertNull($client->checkDeliveryNetwork(), "$countryCode business should be routable");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider peppolBusinessCountryProvider
|
||||
*/
|
||||
public function testBusinessCountryGovernmentIsRoutable(int $countryId, string $countryCode): void
|
||||
{
|
||||
$client = $this->makeClient($countryId, 'government');
|
||||
$this->assertNull($client->checkDeliveryNetwork(), "$countryCode government should be routable");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider peppolBusinessCountryProvider
|
||||
*/
|
||||
public function testBusinessCountryIndividualIsBlocked(int $countryId, string $countryCode): void
|
||||
{
|
||||
$client = $this->makeClient($countryId, 'individual');
|
||||
$result = $client->checkDeliveryNetwork();
|
||||
$this->assertIsString($result, "$countryCode individual should be blocked");
|
||||
$this->assertStringContainsStringIgnoringCase('individual', $result);
|
||||
}
|
||||
|
||||
public static function peppolBusinessCountryProvider(): array
|
||||
{
|
||||
return [
|
||||
'AT' => [40, 'AT'],
|
||||
'BE' => [56, 'BE'],
|
||||
'DK' => [208, 'DK'],
|
||||
'EE' => [233, 'EE'],
|
||||
'FI' => [246, 'FI'],
|
||||
'DE' => [276, 'DE'],
|
||||
'IS' => [352, 'IS'],
|
||||
'LT' => [440, 'LT'],
|
||||
'LU' => [442, 'LU'],
|
||||
'NL' => [528, 'NL'],
|
||||
'NO' => [578, 'NO'],
|
||||
'SE' => [752, 'SE'],
|
||||
'IE' => [372, 'IE'],
|
||||
];
|
||||
}
|
||||
|
||||
// ───────────────────────────────────<E29480><E29480><EFBFBD>──────────────────
|
||||
// Peppol Government Countries — G routable, C blocked
|
||||
// FR, GR, PT, RO, SI, ES, GB
|
||||
// ──────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @dataProvider peppolGovernmentCountryProvider
|
||||
*/
|
||||
public function testGovernmentCountryGovernmentIsRoutable(int $countryId, string $countryCode): void
|
||||
{
|
||||
$client = $this->makeClient($countryId, 'government');
|
||||
$this->assertNull($client->checkDeliveryNetwork(), "$countryCode government should be routable");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider peppolGovernmentCountryProvider
|
||||
*/
|
||||
public function testGovernmentCountryIndividualIsBlocked(int $countryId, string $countryCode): void
|
||||
{
|
||||
$client = $this->makeClient($countryId, 'individual');
|
||||
$result = $client->checkDeliveryNetwork();
|
||||
$this->assertIsString($result, "$countryCode individual should be blocked");
|
||||
}
|
||||
|
||||
public static function peppolGovernmentCountryProvider(): array
|
||||
{
|
||||
return [
|
||||
'FR' => [250, 'FR'],
|
||||
'GR' => [300, 'GR'],
|
||||
'PT' => [620, 'PT'],
|
||||
'RO' => [642, 'RO'],
|
||||
'SI' => [705, 'SI'],
|
||||
];
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────
|
||||
// ES and GB — business in routing rules
|
||||
// ─────────────────────────────────<E29480><E29480>────────────────────
|
||||
|
||||
public function testEsBusinessIsRoutable(): void
|
||||
{
|
||||
$client = $this->makeClient(724, 'business');
|
||||
$this->assertNull($client->checkDeliveryNetwork(), "ES business should be routable");
|
||||
}
|
||||
|
||||
public function testGbBusinessIsRoutable(): void
|
||||
{
|
||||
$client = $this->makeClient(826, 'business');
|
||||
$this->assertNull($client->checkDeliveryNetwork(), "GB business should be routable");
|
||||
}
|
||||
|
||||
public function testEsGovernmentIsBlocked(): void
|
||||
{
|
||||
$client = $this->makeClient(724, 'government');
|
||||
$result = $client->checkDeliveryNetwork();
|
||||
$this->assertIsString($result, "ES government should be blocked - routing rules only support B");
|
||||
}
|
||||
|
||||
public function testGbGovernmentIsBlocked(): void
|
||||
{
|
||||
$client = $this->makeClient(826, 'government');
|
||||
$result = $client->checkDeliveryNetwork();
|
||||
$this->assertIsString($result, "GB government should be blocked - routing rules only support B");
|
||||
}
|
||||
|
||||
// ───<E29480><E29480><EFBFBD>──────────────────────────────────────────────────
|
||||
// Unsupported countries — all classifications blocked
|
||||
// ───────────────────<E29480><E29480>─────────────────────────────<E29480><E29480>────
|
||||
|
||||
/**
|
||||
* @dataProvider unsupportedCountryProvider
|
||||
*/
|
||||
public function testUnsupportedCountryBusinessIsBlocked(int $countryId, string $countryCode): void
|
||||
{
|
||||
$client = $this->makeClient($countryId, 'business');
|
||||
$result = $client->checkDeliveryNetwork();
|
||||
$this->assertIsString($result, "$countryCode business should be blocked");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider unsupportedCountryProvider
|
||||
*/
|
||||
public function testUnsupportedCountryGovernmentIsBlocked(int $countryId, string $countryCode): void
|
||||
{
|
||||
$client = $this->makeClient($countryId, 'government');
|
||||
$result = $client->checkDeliveryNetwork();
|
||||
$this->assertIsString($result, "$countryCode government should be blocked");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider unsupportedCountryProvider
|
||||
*/
|
||||
public function testUnsupportedCountryIndividualIsBlocked(int $countryId, string $countryCode): void
|
||||
{
|
||||
$client = $this->makeClient($countryId, 'individual');
|
||||
$result = $client->checkDeliveryNetwork();
|
||||
$this->assertIsString($result, "$countryCode individual should be blocked");
|
||||
}
|
||||
|
||||
public static function unsupportedCountryProvider(): array
|
||||
{
|
||||
return [
|
||||
'US' => [840, 'US'],
|
||||
'AU' => [36, 'AU'],
|
||||
'JP' => [392, 'JP'],
|
||||
'BR' => [76, 'BR'],
|
||||
'IN' => [356, 'IN'],
|
||||
'CN' => [156, 'CN'],
|
||||
];
|
||||
}
|
||||
|
||||
// ─────<E29480><E29480><EFBFBD>──────────────────────<E29480><E29480>─────────────────────────
|
||||
// IT — commented out of peppol_business_countries
|
||||
// ───────────────────────────<E29480><E29480><EFBFBD>──────────────────────────
|
||||
|
||||
public function testItBusinessIsBlocked(): void
|
||||
{
|
||||
$client = $this->makeClient(380, 'business');
|
||||
$result = $client->checkDeliveryNetwork();
|
||||
$this->assertIsString($result, "IT should be blocked — not in supported country lists");
|
||||
}
|
||||
|
||||
public function testItGovernmentIsBlocked(): void
|
||||
{
|
||||
$client = $this->makeClient(380, 'government');
|
||||
$result = $client->checkDeliveryNetwork();
|
||||
$this->assertIsString($result, "IT government should be blocked — not in supported country lists");
|
||||
}
|
||||
|
||||
public function testItIndividualIsBlocked(): void
|
||||
{
|
||||
$client = $this->makeClient(380, 'individual');
|
||||
$result = $client->checkDeliveryNetwork();
|
||||
$this->assertIsString($result, "IT individual should be blocked — not in supported country lists");
|
||||
}
|
||||
|
||||
// ──────────────<E29480><E29480>───────────────────────<E29480><E29480><EFBFBD>───────────────
|
||||
// Null classification — must NOT silently pass
|
||||
// ──────────────────────────<E29480><E29480>───────────────────────────
|
||||
|
||||
public function testNullClassificationBeIsBlocked(): void
|
||||
{
|
||||
$client = Client::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'company_id' => $this->testCompany->id,
|
||||
'country_id' => 56, // BE
|
||||
'classification' => null,
|
||||
]);
|
||||
|
||||
$result = $client->fresh()->checkDeliveryNetwork();
|
||||
$this->assertIsString($result, "BE with null classification should be blocked");
|
||||
}
|
||||
|
||||
public function testNullClassificationDeIsBlocked(): void
|
||||
{
|
||||
$client = Client::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'company_id' => $this->testCompany->id,
|
||||
'country_id' => 276, // DE
|
||||
'classification' => null,
|
||||
]);
|
||||
|
||||
$result = $client->fresh()->checkDeliveryNetwork();
|
||||
$this->assertIsString($result, "DE with null classification should be blocked");
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────
|
||||
// peppolSendingEnabled cross-check
|
||||
// <20><><EFBFBD>────────────<E29480><E29480><EFBFBD>────────────────────────────────────────
|
||||
|
||||
public function testPeppolSendingEnabledFalseForIndividualBe(): void
|
||||
{
|
||||
$client = $this->makeClient(56, 'individual');
|
||||
$this->assertFalse($client->peppolSendingEnabled(), "BE individual peppolSendingEnabled should be false");
|
||||
}
|
||||
|
||||
public function testPeppolSendingEnabledFalseForUnsupportedCountry(): void
|
||||
{
|
||||
$client = $this->makeClient(840, 'business');
|
||||
$this->assertFalse($client->peppolSendingEnabled(), "US business peppolSendingEnabled should be false");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user