From 708bfce98e03e6e7963efe20fd8de450a92ec3f4 Mon Sep 17 00:00:00 2001 From: Bokor Attila Date: Sat, 16 Aug 2025 11:23:00 +0300 Subject: [PATCH] Add XML validation --- README.md | 32 ++++++++- src/Resources/Efactura.php | 27 ++++++++ src/Responses/Efactura/ValidationResponse.php | 66 +++++++++++++++++++ tests/Fixtures/Efactura.php | 19 ++++++ tests/Resources/Efactura.php | 28 ++++++++ 5 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 src/Responses/Efactura/ValidationResponse.php diff --git a/README.md b/README.md index 0ee805a..3f7cafa 100644 --- a/README.md +++ b/README.md @@ -295,7 +295,37 @@ $file = $authorizedClient->efactura()->download([ $file->getContent(); // string - You can save/download the content to a file ``` #### [Validate](https://mfinante.gov.ro/static/10/eFactura/validare.html) Resource -TODO: implement `validate` from [here](https://mfinante.gov.ro/static/10/eFactura/validare.html) + +Validate the XML + +```php +/* + * $xmlStandard can be one of the following: 'FACT1', 'FCN'. + * The default value is 'FACT1' + */ + $response = $client->efactura()->validateXml($path_to_xml, $xmlStandard); + + $response->isValid(); //bool, you can proceed accordingly + $response->status; //string 'ok' or 'nok' + $response->messages; //array of messages, empty if validation successful + + $response->toArray(); + //Examples + $valid = [ + 'stare' => 'ok', + 'trace_id' => '....', + ]; + + $invalid = [ + 'stare' => 'nok', + 'Messages' => [ + [ + 'message' => "Fisierul transmis nu este valid.....", + ] + ], + 'trace_id' => '....', + ]; +``` #### [XmlToPdf](https://mfinante.gov.ro/static/10/eFactura/xmltopdf.html) Resource Convert XML eFactura to PDF. For this endpoint you need to use unauthenticated client diff --git a/src/Resources/Efactura.php b/src/Resources/Efactura.php index 144155e..c303dfa 100644 --- a/src/Resources/Efactura.php +++ b/src/Resources/Efactura.php @@ -6,9 +6,12 @@ use Anaf\Contracts\FileContract; use Anaf\Enums\Efactura\UploadStandard; +use Anaf\Exceptions\TransporterException; +use Anaf\Exceptions\UnserializableResponse; use Anaf\Responses\Efactura\CreateMessagesResponse; use Anaf\Responses\Efactura\CreatePaginatedMessagesResponse; use Anaf\Responses\Efactura\CreateUploadResponse; +use Anaf\Responses\Efactura\ValidationResponse; use Anaf\ValueObjects\Transporter\Payload; use Anaf\ValueObjects\Transporter\Xml; use Exception; @@ -131,4 +134,28 @@ public function xmlToPdf(string $xml_path, string $standard = 'FACT1', bool $val return $this->transporter->requestFile($payload); } + + /** + * Validates a given XML file + * + * @throws UnserializableResponse|TransporterException + */ + public function validateXml(string $xml_path, string $standard = 'FACT1'): ValidationResponse + { + if (!in_array($standard, ['FACT1', 'FCN'])) { + throw new RuntimeException("Invalid standard {$standard}"); + } + + $payload = Payload::upload( + resource: "prod/FCTEL/rest/validare/{$standard}", + body: Xml::from($xml_path)->toString(), + ); + + /** + * @var array{stare: string, trace_id: string, Messages: list} $response + */ + $response = $this->transporter->requestObject($payload); + + return ValidationResponse::from($response); + } } diff --git a/src/Responses/Efactura/ValidationResponse.php b/src/Responses/Efactura/ValidationResponse.php new file mode 100644 index 0000000..e18b76c --- /dev/null +++ b/src/Responses/Efactura/ValidationResponse.php @@ -0,0 +1,66 @@ +}> + */ +class ValidationResponse implements Response +{ + /** + * @use ArrayAccessible}> + */ + use ArrayAccessible; + + /** + * Creates a new ValidationResponse instance. + * + * @param string $status + * @param string $traceId + * @param list $messages + */ + public function __construct( + public readonly string $status, + public readonly string $traceId, + public readonly array $messages, + ){} + + /** + * Acts as static factory, and returns a new Response instance. + * + * @param array{stare: string, trace_id: string, Messages: null|list} $attributes + */ + public static function from(array $attributes): self + { + return new self( + $attributes['stare'], + $attributes['trace_id'], + $attributes['Messages'] ?? [], + ); + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'stare' => $this->status, + 'trace_id' => $this->traceId, + 'messages' => $this->messages, + ]; + } + + /** + * Returns a bool to quick check if the xml was valid or not + * + * @return bool + */ + public function isValid(): bool + { + return $this->status === 'ok'; + } +} diff --git a/tests/Fixtures/Efactura.php b/tests/Fixtures/Efactura.php index 1f40683..31d7ede 100644 --- a/tests/Fixtures/Efactura.php +++ b/tests/Fixtures/Efactura.php @@ -84,3 +84,22 @@ function getFakeFile(string $content = 'dummy zile file content') { return new FileHandler($content); } + +function getXmlValidationMessage($valid = true): array +{ + if ($valid) + return [ + 'stare' => 'ok', + 'trace_id' => '.....', + ]; + + return [ + 'stare' => 'nok', + 'Messages' => [ + [ + 'message' => "Fisierul transmis nu este valid. org.xml.sax.SAXParseException; lineNumber: 14; columnNumber: 27; cvc-complex-type.2.4.a:", + ] + ], + 'trace_id' => '.....', + ]; +} diff --git a/tests/Resources/Efactura.php b/tests/Resources/Efactura.php index 2530e6b..6c405c0 100644 --- a/tests/Resources/Efactura.php +++ b/tests/Resources/Efactura.php @@ -5,6 +5,7 @@ use Anaf\Responses\Efactura\CreatePaginatedMessagesResponse; use Anaf\Responses\Efactura\CreateUploadResponse; use Anaf\Responses\Efactura\Message; +use Anaf\Responses\Efactura\ValidationResponse; test('upload', function () { $authorizedClient = mockAuthorizedClient('POST', '/prod/FCTEL/rest/upload', getUploadMessage()); @@ -90,3 +91,30 @@ expect($response)->toBeInstanceOf(FileContract::class); }); + +test('validate valid xml', function () { + $authorizedClient = mockClient('POST', '/prod/FCTEL/rest/validare/FACT1', getXmlValidationMessage()); + + $response = $authorizedClient->efactura()->validateXml(__DIR__ . '/../Fixtures/dummyxml.xml'); + + expect($response)->toBeInstanceOf(ValidationResponse::class) + ->toArray() + ->toHaveKey('stare', 'ok') + ->and($response) + ->isValid() + ->toBeTrue(); +}); + + +test('validate invalid xml', function () { + $authorizedClient = mockClient('POST', '/prod/FCTEL/rest/validare/FACT1', getXmlValidationMessage(false)); + + $response = $authorizedClient->efactura()->validateXml(__DIR__ . '/../Fixtures/dummyxml.xml'); + + expect($response)->toBeInstanceOf(ValidationResponse::class) + ->toArray() + ->toHaveKey('stare', 'nok') + ->and($response) + ->isValid() + ->toBeFalse(); +});