Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support the new ARES (Administrativní registr ekonomických subjektů) version #225

Merged
merged 1 commit into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 47 additions & 52 deletions site/app/CompanyInfo/CompanyRegisterAres.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,24 @@
use MichalSpacekCz\CompanyInfo\Exceptions\CompanyInfoException;
use MichalSpacekCz\CompanyInfo\Exceptions\CompanyNotFoundException;
use Nette\Http\IResponse;
use SimpleXMLElement;
use Nette\Schema\Expect;
use Nette\Schema\Processor;
use Nette\Schema\ValidationException;
use Nette\Utils\Json;
use Nette\Utils\JsonException;

/**
* ARES service.
*
* See https://wwwinfo.mfcr.cz/ares/xml_doc/schemas/documentation/zkr_103.txt
* for meaning of abbreviations like AA, NU, CD, CO etc. (in Czech)
* See https://ares.gov.cz/stranky/vyvojar-info & https://ares.gov.cz/swagger-ui/ for the docs.
* This is using the /ekonomicke-subjekty endpoint because it returns DIČ/tax id as well.
*/
class CompanyRegisterAres implements CompanyRegister
{

/**
* See kod_vyhledani in https://wwwinfo.mfcr.cz/ares/xml_doc/schemas/ares/ares_datatypes/v_1.0.5/ares_datatypes_v_1.0.5.xsd
*/
private const STATUS_FOUND = 1;


public function __construct(
private readonly string $url,
private readonly Processor $schemaProcessor,
) {
}

Expand All @@ -45,53 +44,59 @@ public function getDetails(string $companyId): CompanyInfoDetails
throw new CompanyInfoException('Company Id is empty');
}
$content = $this->fetch($companyId);
$xml = simplexml_load_string($content);
if (!$xml) {
throw new CompanyInfoException("Can't parse XML received for company {$companyId}");
}
/** @var array<string, string> $ns */
$ns = $xml->getDocNamespaces();
$result = $xml->children($ns['are'])->children($ns['D'])->VH;
$data = $xml->children($ns['are'])->children($ns['D'])->VBAS;
if ((int)$result->K !== self::STATUS_FOUND) {
throw new CompanyNotFoundException((int)$result->K);
}

if (isset($data->AA)) {
$street = (string)$data->AA->NU;
$houseNumber = (string)$data->AA->CD;
$streetNumber = (string)$data->AA->CO;
$city = (string)$data->AA->N;
$zip = (string)$data->AA->PSC;
$country = strtolower($this->countryCode($data));
} else {
$street = $houseNumber = $streetNumber = $city = $zip = $country = null;
try {
$schema = Expect::structure([
'ico' => Expect::string()->required(),
'dic' => Expect::string(),
'obchodniJmeno' => Expect::string()->required(),
'sidlo' => Expect::structure([
'nazevObce' => Expect::string(),
'nazevUlice' => Expect::string(),
'cisloDomovni' => Expect::int(),
'cisloOrientacni' => Expect::int(),
'cisloOrientacniPismeno' => Expect::string(),
'psc' => Expect::int(),
'kodStatu' => Expect::string(),
])->otherItems(),
])->otherItems();
/** @var object{ico:string, dic:string, obchodniJmeno:string, sidlo:object{nazevObce:string, nazevUlice:string, cisloDomovni:int, cisloOrientacni:int, cisloOrientacniPismeno:string, psc:int, kodStatu:string}} $data */
$data = $this->schemaProcessor->process($schema, Json::decode($content));
} catch (JsonException | ValidationException $e) {
throw new CompanyInfoException($e->getMessage(), previous: $e);
}

$streetAndNumber = $this->formatStreet(
$data->sidlo->nazevObce,
$data->sidlo->nazevUlice,
$data->sidlo->cisloDomovni,
$data->sidlo->cisloOrientacni,
$data->sidlo->cisloOrientacniPismeno,
);
return new CompanyInfoDetails(
IResponse::S200_OK,
'OK',
(string)$data->ICO,
(string)$data->DIC,
(string)$data->OF,
$this->formatStreet($city, $street, $houseNumber, $streetNumber),
$city,
$zip,
$country,
$data->ico,
$data->dic ? $data->sidlo->kodStatu . $data->dic : '',
$data->obchodniJmeno,
$streetAndNumber,
$data->sidlo->nazevObce,
(string)$data->sidlo->psc,
strtolower($data->sidlo->kodStatu),
);
}


/**
* @throws CompanyInfoException
* @throws CompanyNotFoundException
*/
private function fetch(string $companyId): string
{
$context = stream_context_create();
$setResult = stream_context_set_params($context, [
'notification' => function (int $notificationCode, int $severity, ?string $message, int $messageCode) {
if ($severity === STREAM_NOTIFY_SEVERITY_ERR) {
throw new CompanyInfoException($notificationCode . ($message ? trim(' ' . $message) : ''), $messageCode);
throw new CompanyNotFoundException($messageCode !== IResponse::S404_NotFound ? $messageCode : null);
}
},
'options' => [
Expand All @@ -110,12 +115,15 @@ private function fetch(string $companyId): string
}


private function formatStreet(?string $city, ?string $street, ?string $houseNumber, ?string $streetNumber): ?string
private function formatStreet(?string $city, ?string $street, ?int $houseNumber, ?int $streetNumber, ?string $streetLetter): ?string
{
$result = $street;
if (empty($result)) {
$result = $city;
}
if (!empty($streetLetter)) {
$streetNumber .= $streetLetter;
}
if (!empty($houseNumber) && !empty($streetNumber)) {
$result .= " {$houseNumber}/{$streetNumber}";
} elseif (!empty($houseNumber)) {
Expand All @@ -126,17 +134,4 @@ private function formatStreet(?string $city, ?string $street, ?string $houseNumb
return $result;
}


/**
* Return ISO 3166-1 alpha-2 by ISO 3166-1 numeric.
*/
private function countryCode(SimpleXMLElement $data): string
{
$codes = [
'203' => 'CZ',
'703' => 'SK',
];
return ($codes[(string)$data->AA->KS] ?? substr((string)$data->DIC, 0, 2) ?: 'CZ');
}

}
2 changes: 1 addition & 1 deletion site/config/parameters.neon
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ parameters:
vatRate: 0.21
loadCompanyDataVisible: true
ares:
url: 'https://wwwinfo.mfcr.cz/cgi-bin/ares/darv_bas.cgi?ico=%s'
url: 'https://ares.gov.cz/ekonomicke-subjekty-v-be/rest/ekonomicke-subjekty/%s'
registerUz:
rootUrl: 'http://www.registeruz.sk/cruz-public/api/'
returningUser:
Expand Down
99 changes: 99 additions & 0 deletions site/tests/CompanyInfo/CompanyRegisterAresTest.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php
/** @noinspection PhpUnhandledExceptionInspection */
declare(strict_types = 1);

namespace MichalSpacekCz\CompanyInfo;

use MichalSpacekCz\CompanyInfo\Exceptions\CompanyNotFoundException;
use MichalSpacekCz\Test\TestCaseRunner;
use Tester\Assert;
use Tester\Environment;
use Tester\TestCase;

require __DIR__ . '/../bootstrap.php';

/** @testCase */
class CompanyRegisterAresTest extends TestCase
{

public function __construct(
private readonly CompanyRegisterAres $ares,
) {
}


public function testGetDetails(): void
{
if (getenv(Environment::VariableRunner)) {
$file = basename(__FILE__);
Environment::skip("The test uses the Internet, to not skip the test run it with `php {$file}`");
}
$expected = new CompanyInfoDetails(
200,
'OK',
'26688093',
'CZ26688093',
'Landis+Gyr s.r.o.',
'Plzeňská 3185/5a',
'Praha',
'15000',
'cz',
);
Assert::equal($expected, $this->ares->getDetails('26688093'), 'All house number & street number & extra letter');
sleep(3);

$expected = new CompanyInfoDetails(
200,
'OK',
'44741561',
'CZ44741561',
'VSACAN TOUR, s.r.o.',
'Dolní náměstí 344',
'Vsetín',
'75501',
'cz',
);
Assert::equal($expected, $this->ares->getDetails('44741561'), 'Just house number');
sleep(3);

$expected = new CompanyInfoDetails(
200,
'OK',
'00256081',
'CZ00256081',
'Obec Srní',
'Srní 113',
'Srní',
'34192',
'cz',
);
Assert::equal($expected, $this->ares->getDetails('00256081'), 'No street');
sleep(3);

$expected = new CompanyInfoDetails(
200,
'OK',
'12466743',
'',
'JUDr. Ivo Javůrek',
'Jeřabinová 874/12',
'Plzeň',
'32600',
'cz',
);
Assert::equal($expected, $this->ares->getDetails('12466743'), 'No tax id');
sleep(3);

Assert::exception(function (): void {
$this->ares->getDetails('1337');
}, CompanyNotFoundException::class, 'Invalid status 400');
sleep(3);

Assert::exception(function (): void {
$this->ares->getDetails('13371338');
}, CompanyNotFoundException::class, 'Company not found');
}

}

TestCaseRunner::run(CompanyRegisterAresTest::class);