Allow to edit the GTIN property of a part and validate the GTIN

This commit is contained in:
Jan Böhmer 2026-02-08 14:44:56 +01:00
parent 7fd7697c02
commit 57c8368b5e
10 changed files with 236 additions and 6 deletions

View file

@ -90,6 +90,7 @@
"symfony/yaml": "7.4.*", "symfony/yaml": "7.4.*",
"symplify/easy-coding-standard": "^12.5.20", "symplify/easy-coding-standard": "^12.5.20",
"tecnickcom/tc-lib-barcode": "^2.1.4", "tecnickcom/tc-lib-barcode": "^2.1.4",
"tiendanube/gtinvalidation": "^1.0",
"twig/cssinliner-extra": "^3.0", "twig/cssinliner-extra": "^3.0",
"twig/extra-bundle": "^3.8", "twig/extra-bundle": "^3.8",
"twig/html-extra": "^3.8", "twig/html-extra": "^3.8",

50
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "7ca9c95fb85f6bf3d9b8a3aa98ca33f6", "content-hash": "b47c8579efa64349c5e65296e2a44206",
"packages": [ "packages": [
{ {
"name": "amphp/amp", "name": "amphp/amp",
@ -16830,6 +16830,54 @@
], ],
"time": "2025-05-14T06:15:44+00:00" "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", "name": "tijsverkoyen/css-to-inline-styles",
"version": "v2.4.0", "version": "v2.4.0",

View file

@ -24,6 +24,7 @@ namespace App\Entity\Parts\PartTraits;
use App\Entity\Parts\InfoProviderReference; use App\Entity\Parts\InfoProviderReference;
use App\Entity\Parts\PartCustomState; use App\Entity\Parts\PartCustomState;
use App\Validator\Constraints\ValidGTIN;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use App\Entity\Parts\Part; use App\Entity\Parts\Part;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
@ -89,7 +90,7 @@ trait AdvancedPropertyTrait
*/ */
#[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])]
#[ORM\Column(type: Types::STRING, nullable: true)] #[ORM\Column(type: Types::STRING, nullable: true)]
#[Length(max: 14)] #[ValidGTIN]
protected ?string $gtin = null; protected ?string $gtin = null;
/** /**

View file

@ -216,7 +216,13 @@ class PartBaseType extends AbstractType
'disable_not_selectable' => true, 'disable_not_selectable' => true,
'label' => 'part.edit.partCustomState', '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 //Comment section
$builder->add('comment', RichTextEditorType::class, [ $builder->add('comment', RichTextEditorType::class, [

View file

@ -0,0 +1,35 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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
{
}

View file

@ -0,0 +1,54 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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();
}
}
}

View file

@ -13,4 +13,5 @@
{{ form_row(form.ipn) }} {{ form_row(form.ipn) }}
</div> </div>
{{ form_row(form.partUnit) }} {{ form_row(form.partUnit) }}
{{ form_row(form.partCustomState) }} {{ form_row(form.partCustomState) }}
{{ form_row(form.gtin) }}

View file

@ -0,0 +1,72 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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();
}
}

View file

@ -12401,5 +12401,11 @@ Buerklin-API Authentication server:
<target>Update to</target> <target>Update to</target>
</segment> </segment>
</unit> </unit>
<unit id="XPhnMxn" name="part.gtin">
<segment>
<source>part.gtin</source>
<target>GTIN / EAN</target>
</segment>
</unit>
</file> </file>
</xliff> </xliff>

View file

@ -247,5 +247,11 @@
<target>There is already a translation defined for this type and language!</target> <target>There is already a translation defined for this type and language!</target>
</segment> </segment>
</unit> </unit>
<unit id="zT_j_oQ" name="validator.invalid_gtin">
<segment>
<source>validator.invalid_gtin</source>
<target>This is not an valid GTIN / EAN!</target>
</segment>
</unit>
</file> </file>
</xliff> </xliff>