diff --git a/composer.json b/composer.json index 36dd461e..dd12097c 100644 --- a/composer.json +++ b/composer.json @@ -90,6 +90,7 @@ "symfony/yaml": "7.4.*", "symplify/easy-coding-standard": "^12.5.20", "tecnickcom/tc-lib-barcode": "^2.1.4", + "tiendanube/gtinvalidation": "^1.0", "twig/cssinliner-extra": "^3.0", "twig/extra-bundle": "^3.8", "twig/html-extra": "^3.8", diff --git a/composer.lock b/composer.lock index b56a61f9..9ccb922e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7ca9c95fb85f6bf3d9b8a3aa98ca33f6", + "content-hash": "b47c8579efa64349c5e65296e2a44206", "packages": [ { "name": "amphp/amp", @@ -16830,6 +16830,54 @@ ], "time": "2025-05-14T06:15:44+00:00" }, + { + "name": "tiendanube/gtinvalidation", + "version": "v1.0", + "source": { + "type": "git", + "url": "https://github.com/TiendaNube/GtinValidator.git", + "reference": "7ff5794b6293eb748bf1efcddf4e20a657c31855" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TiendaNube/GtinValidator/zipball/7ff5794b6293eb748bf1efcddf4e20a657c31855", + "reference": "7ff5794b6293eb748bf1efcddf4e20a657c31855", + "shasum": "" + }, + "require": { + "php": ">=5.4" + }, + "require-dev": { + "phpunit/phpunit": "4.6.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "GtinValidation\\": "src/GtinValidation" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ryan Blakemore", + "homepage": "https://github.com/ryanblak", + "role": "developer" + } + ], + "description": "Validates GTIN product codes.", + "keywords": [ + "gtin", + "product codes" + ], + "support": { + "issues": "https://github.com/TiendaNube/GtinValidator/issues", + "source": "https://github.com/TiendaNube/GtinValidator/tree/master" + }, + "time": "2018-07-25T22:31:29+00:00" + }, { "name": "tijsverkoyen/css-to-inline-styles", "version": "v2.4.0", diff --git a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php index 38130f0d..065469b5 100644 --- a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php +++ b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php @@ -24,6 +24,7 @@ namespace App\Entity\Parts\PartTraits; use App\Entity\Parts\InfoProviderReference; use App\Entity\Parts\PartCustomState; +use App\Validator\Constraints\ValidGTIN; use Doctrine\DBAL\Types\Types; use App\Entity\Parts\Part; use Doctrine\ORM\Mapping as ORM; @@ -89,7 +90,7 @@ trait AdvancedPropertyTrait */ #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::STRING, nullable: true)] - #[Length(max: 14)] + #[ValidGTIN] protected ?string $gtin = null; /** diff --git a/src/Form/Part/PartBaseType.php b/src/Form/Part/PartBaseType.php index b8276589..902aff40 100644 --- a/src/Form/Part/PartBaseType.php +++ b/src/Form/Part/PartBaseType.php @@ -216,7 +216,13 @@ class PartBaseType extends AbstractType 'disable_not_selectable' => true, 'label' => 'part.edit.partCustomState', ]) - ->add('ipn', TextType::class, $ipnOptions); + ->add('ipn', TextType::class, $ipnOptions) + ->add('gtin', TextType::class, [ + 'required' => false, + 'empty_data' => null, + 'label' => 'part.gtin', + ]) + ; //Comment section $builder->add('comment', RichTextEditorType::class, [ diff --git a/src/Validator/Constraints/ValidGTIN.php b/src/Validator/Constraints/ValidGTIN.php new file mode 100644 index 00000000..20e81f1d --- /dev/null +++ b/src/Validator/Constraints/ValidGTIN.php @@ -0,0 +1,35 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * A constraint to ensure that a GTIN is valid. + */ +#[\Attribute(\Attribute::TARGET_PROPERTY)] +class ValidGTIN extends Constraint +{ + +} diff --git a/src/Validator/Constraints/ValidGTINValidator.php b/src/Validator/Constraints/ValidGTINValidator.php new file mode 100644 index 00000000..57eb23e6 --- /dev/null +++ b/src/Validator/Constraints/ValidGTINValidator.php @@ -0,0 +1,54 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Validator\Constraints; + +use GtinValidation\GtinValidator; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +class ValidGTINValidator extends ConstraintValidator +{ + + public function validate(mixed $value, Constraint $constraint): void + { + if (!$constraint instanceof ValidGTIN) { + throw new UnexpectedTypeException($constraint, ValidGTIN::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!is_string($value)) { + throw new UnexpectedTypeException($value, 'string'); + } + + $gtinValidator = new GtinValidator($value); + if (!$gtinValidator->isValid()) { + $this->context->buildViolation('validator.invalid_gtin') + ->addViolation(); + } + } +} diff --git a/templates/parts/edit/_advanced.html.twig b/templates/parts/edit/_advanced.html.twig index b0f1ff86..30479d11 100644 --- a/templates/parts/edit/_advanced.html.twig +++ b/templates/parts/edit/_advanced.html.twig @@ -13,4 +13,5 @@ {{ form_row(form.ipn) }} {{ form_row(form.partUnit) }} -{{ form_row(form.partCustomState) }} \ No newline at end of file +{{ form_row(form.partCustomState) }} +{{ form_row(form.gtin) }} diff --git a/tests/Validator/Constraints/ValidGTINValidatorTest.php b/tests/Validator/Constraints/ValidGTINValidatorTest.php new file mode 100644 index 00000000..f35b053a --- /dev/null +++ b/tests/Validator/Constraints/ValidGTINValidatorTest.php @@ -0,0 +1,72 @@ +. + */ + +namespace App\Tests\Validator\Constraints; + +use App\Validator\Constraints\ValidGTINValidator; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Validator\ConstraintValidatorInterface; +use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; + +class ValidGTINValidatorTest extends ConstraintValidatorTestCase +{ + + public function testAllowNull(): void + { + $this->validator->validate(null, new \App\Validator\Constraints\ValidGTIN()); + $this->assertNoViolation(); + } + + public function testValidGTIN8(): void + { + $this->validator->validate('12345670', new \App\Validator\Constraints\ValidGTIN()); + $this->assertNoViolation(); + } + + public function testValidGTIN12(): void + { + $this->validator->validate('123456789012', new \App\Validator\Constraints\ValidGTIN()); + $this->assertNoViolation(); + } + + public function testValidGTIN13(): void + { + $this->validator->validate('1234567890128', new \App\Validator\Constraints\ValidGTIN()); + $this->assertNoViolation(); + } + + public function testValidGTIN14(): void + { + $this->validator->validate('12345678901231', new \App\Validator\Constraints\ValidGTIN()); + $this->assertNoViolation(); + } + + public function testInvalidGTIN(): void + { + $this->validator->validate('1234567890123', new \App\Validator\Constraints\ValidGTIN()); + $this->buildViolation('validator.invalid_gtin') + ->assertRaised(); + } + + protected function createValidator(): ConstraintValidatorInterface + { + return new ValidGTINValidator(); + } +} diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 5692ed25..66053133 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -12401,5 +12401,11 @@ Buerklin-API Authentication server: Update to + + + part.gtin + GTIN / EAN + + - \ No newline at end of file + diff --git a/translations/validators.en.xlf b/translations/validators.en.xlf index e2e70d03..ed824f0b 100644 --- a/translations/validators.en.xlf +++ b/translations/validators.en.xlf @@ -247,5 +247,11 @@ There is already a translation defined for this type and language! + + + validator.invalid_gtin + This is not an valid GTIN / EAN! + + - \ No newline at end of file +