Use enums for LabelOptions

This commit is contained in:
Jan Böhmer 2023-06-12 23:39:30 +02:00
parent 485b35fbd4
commit 71cd4057a7
23 changed files with 329 additions and 157 deletions

View file

@ -159,7 +159,7 @@ abstract class BaseAdminController extends AbstractController
//Disable editing of options, if user is not allowed to use twig... //Disable editing of options, if user is not allowed to use twig...
if ( if (
$entity instanceof LabelProfile $entity instanceof LabelProfile
&& 'twig' === $entity->getOptions()->getLinesMode() && 'twig' === $entity->getOptions()->getProcessMode()
&& !$this->isGranted('@labels.use_twig') && !$this->isGranted('@labels.use_twig')
) { ) {
$form_options['disable_options'] = true; $form_options['disable_options'] = true;

View file

@ -44,6 +44,7 @@ namespace App\Controller;
use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractDBElement;
use App\Entity\LabelSystem\LabelOptions; use App\Entity\LabelSystem\LabelOptions;
use App\Entity\LabelSystem\LabelProfile; use App\Entity\LabelSystem\LabelProfile;
use App\Entity\LabelSystem\LabelSupportedElement;
use App\Exceptions\TwigModeException; use App\Exceptions\TwigModeException;
use App\Form\LabelSystem\LabelDialogType; use App\Form\LabelSystem\LabelDialogType;
use App\Repository\DBElementRepository; use App\Repository\DBElementRepository;
@ -80,18 +81,18 @@ class LabelController extends AbstractController
$label_options = $profile instanceof LabelProfile ? $profile->getOptions() : new LabelOptions(); $label_options = $profile instanceof LabelProfile ? $profile->getOptions() : new LabelOptions();
//We have to disable the options, if twig mode is selected and user is not allowed to use it. //We have to disable the options, if twig mode is selected and user is not allowed to use it.
$disable_options = 'twig' === $label_options->getLinesMode() && !$this->isGranted('@labels.use_twig'); $disable_options = 'twig' === $label_options->getProcessMode() && !$this->isGranted('@labels.use_twig');
$form = $this->createForm(LabelDialogType::class, null, [ $form = $this->createForm(LabelDialogType::class, null, [
'disable_options' => $disable_options, 'disable_options' => $disable_options,
]); ]);
//Try to parse given target_type and target_id //Try to parse given target_type and target_id
$target_type = $request->query->get('target_type', null); $target_type = $request->query->getEnum('target_type', LabelSupportedElement::class, null);
$target_id = $request->query->get('target_id', null); $target_id = $request->query->get('target_id', null);
$generate = $request->query->getBoolean('generate', false); $generate = $request->query->getBoolean('generate', false);
if (!$profile instanceof LabelProfile && is_string($target_type)) { if (!$profile instanceof LabelProfile && $target_type instanceof LabelSupportedElement) {
$label_options->setSupportedElement($target_type); $label_options->setSupportedElement($target_type);
} }
if (is_string($target_id)) { if (is_string($target_id)) {
@ -142,16 +143,12 @@ class LabelController extends AbstractController
return $ret.'.pdf'; return $ret.'.pdf';
} }
protected function findObjects(string $type, string $ids): array protected function findObjects(LabelSupportedElement $type, string $ids): array
{ {
if (!isset(LabelGenerator::CLASS_SUPPORT_MAPPING[$type])) {
throw new InvalidArgumentException('The given type is not known and can not be mapped to a class!');
}
$id_array = $this->rangeParser->parse($ids); $id_array = $this->rangeParser->parse($ids);
/** @var DBElementRepository $repo */ /** @var DBElementRepository $repo */
$repo = $this->em->getRepository(LabelGenerator::CLASS_SUPPORT_MAPPING[$type]); $repo = $this->em->getRepository($type->getEntityClass());
return $repo->getElementsFromIDArray($id_array); return $repo->getElementsFromIDArray($id_array);
} }

View file

@ -100,7 +100,7 @@ class LabelProfileFixtures extends Fixture
$option4->setLines('{{ element.name }}'); $option4->setLines('{{ element.name }}');
$option4->setBarcodeType('code39'); $option4->setBarcodeType('code39');
$option4->setSupportedElement('part'); $option4->setSupportedElement('part');
$option4->setLinesMode('twig'); $option4->setProcessMode('twig');
$profile4->setOptions($option4); $profile4->setOptions($option4);
$manager->persist($profile4); $manager->persist($profile4);

View file

@ -38,10 +38,8 @@ class BigDecimalType extends Type
/** /**
* @param string|null $value * @param string|null $value
*
* @return BigDecimal|BigNumber|mixed
*/ */
public function convertToPHPValue($value, AbstractPlatform $platform) public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?BigNumber
{ {
if (null === $value) { if (null === $value) {
return null; return null;

View file

@ -0,0 +1,64 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 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\Entity\LabelSystem;
enum BarcodeType: string
{
case NONE = 'none';
case QR = 'qr';
case CODE39 = 'code39';
case DATAMATRIX = 'datamatrix';
case CODE93 = 'code93';
case CODE128 = 'code128';
/**
* Returns true if the barcode is none. (Useful for twig templates)
* @return bool
*/
public function isNone(): bool
{
return $this === self::NONE;
}
/**
* Returns true if the barcode is a 1D barcode (Code39, etc.).
* @return bool
*/
public function is1D(): bool
{
return match ($this) {
self::CODE39, self::CODE93, self::CODE128 => true,
default => false,
};
}
/**
* Returns true if the barcode is a 2D barcode (QR code, datamatrix).
* @return bool
*/
public function is2D(): bool
{
return match ($this) {
self::QR, self::DATAMATRIX => true,
default => false,
};
}
}

View file

@ -48,12 +48,6 @@ use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Embeddable] #[ORM\Embeddable]
class LabelOptions class LabelOptions
{ {
final public const BARCODE_TYPES = ['none', /*'ean8',*/ 'qr', 'code39', 'datamatrix', 'code93', 'code128'];
final public const SUPPORTED_ELEMENTS = ['part', 'part_lot', 'storelocation'];
final public const PICTURE_TYPES = ['none', 'element_picture', 'main_attachment'];
final public const LINES_MODES = ['html', 'twig'];
/** /**
* @var float The page size of the label in mm * @var float The page size of the label in mm
*/ */
@ -69,25 +63,19 @@ class LabelOptions
protected float $height = 30.0; protected float $height = 30.0;
/** /**
* @var string The type of the barcode that should be used in the label (e.g. 'qr') * @var BarcodeType The type of the barcode that should be used in the label (e.g. 'qr')
*/ */
#[Assert\Choice(choices: LabelOptions::BARCODE_TYPES)] #[ORM\Column(type: Types::STRING, enumType: BarcodeType::class)]
#[ORM\Column(type: Types::STRING)] protected BarcodeType $barcode_type = BarcodeType::NONE;
protected string $barcode_type = 'none';
/** /**
* @var string What image should be shown along the * @var LabelPictureType What image should be shown along the label
*/ */
#[Assert\Choice(choices: LabelOptions::PICTURE_TYPES)] #[ORM\Column(type: Types::STRING, enumType: LabelPictureType::class)]
#[ORM\Column(type: Types::STRING)] protected LabelPictureType $picture_type = LabelPictureType::NONE;
protected string $picture_type = 'none';
/** #[ORM\Column(type: Types::STRING, enumType: LabelSupportedElement::class)]
* @var string protected LabelSupportedElement $supported_element = LabelSupportedElement::PART;
*/
#[Assert\Choice(choices: LabelOptions::SUPPORTED_ELEMENTS)]
#[ORM\Column(type: Types::STRING)]
protected string $supported_element = 'part';
/** /**
* @var string any additional CSS for the label * @var string any additional CSS for the label
@ -95,11 +83,10 @@ class LabelOptions
#[ORM\Column(type: Types::TEXT)] #[ORM\Column(type: Types::TEXT)]
protected string $additional_css = ''; protected string $additional_css = '';
/** @var string The mode that will be used to interpret the lines /** @var LabelProcessMode The mode that will be used to interpret the lines
*/ */
#[Assert\Choice(choices: LabelOptions::LINES_MODES)] #[ORM\Column(type: Types::STRING, enumType: LabelProcessMode::class, name: 'lines_mode')]
#[ORM\Column(type: Types::STRING)] protected LabelProcessMode $process_mode = LabelProcessMode::PLACEHOLDER;
protected string $lines_mode = 'html';
/** /**
* @var string * @var string
@ -131,36 +118,36 @@ class LabelOptions
return $this; return $this;
} }
public function getBarcodeType(): string public function getBarcodeType(): BarcodeType
{ {
return $this->barcode_type; return $this->barcode_type;
} }
public function setBarcodeType(string $barcode_type): self public function setBarcodeType(BarcodeType $barcode_type): self
{ {
$this->barcode_type = $barcode_type; $this->barcode_type = $barcode_type;
return $this; return $this;
} }
public function getPictureType(): string public function getPictureType(): LabelPictureType
{ {
return $this->picture_type; return $this->picture_type;
} }
public function setPictureType(string $picture_type): self public function setPictureType(LabelPictureType $picture_type): self
{ {
$this->picture_type = $picture_type; $this->picture_type = $picture_type;
return $this; return $this;
} }
public function getSupportedElement(): string public function getSupportedElement(): LabelSupportedElement
{ {
return $this->supported_element; return $this->supported_element;
} }
public function setSupportedElement(string $supported_element): self public function setSupportedElement(LabelSupportedElement $supported_element): self
{ {
$this->supported_element = $supported_element; $this->supported_element = $supported_element;
@ -194,14 +181,14 @@ class LabelOptions
return $this; return $this;
} }
public function getLinesMode(): string public function getProcessMode(): LabelProcessMode
{ {
return $this->lines_mode; return $this->process_mode;
} }
public function setLinesMode(string $lines_mode): self public function setProcessMode(LabelProcessMode $process_mode): self
{ {
$this->lines_mode = $lines_mode; $this->process_mode = $process_mode;
return $this; return $this;
} }

View file

@ -0,0 +1,37 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 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\Entity\LabelSystem;
enum LabelPictureType: string
{
/**
* Show no picture on the label
*/
case NONE = 'none';
/**
* Show the preview picture of the element on the label
*/
case ELEMENT_PICTURE = 'element_picture';
/**
* Show the main attachment of the element on the label
*/
case MAIN_ATTACHMENT = 'main_attachment';
}

View file

@ -0,0 +1,29 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 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\Entity\LabelSystem;
enum LabelProcessMode: string
{
/** Use placeholders like [[PLACEHOLDER]] which gets replaced with content */
case PLACEHOLDER = 'html';
/** Interpret the given lines as twig template */
case TWIG = 'twig';
}

View file

@ -0,0 +1,45 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 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\Entity\LabelSystem;
use App\Entity\Parts\Part;
use App\Entity\Parts\PartLot;
use App\Entity\Parts\Storelocation;
enum LabelSupportedElement: string
{
case PART = 'part';
case PART_LOT = 'part_lot';
case STORELOCATION = 'storelocation';
/**
* Returns the entity class for the given element type
* @return string
*/
public function getEntityClass(): string
{
return match ($this) {
self::PART => Part::class,
self::PART_LOT => PartLot::class,
self::STORELOCATION => Storelocation::class,
};
}
}

View file

@ -41,11 +41,15 @@ declare(strict_types=1);
namespace App\Form; namespace App\Form;
use App\Entity\LabelSystem\BarcodeType;
use App\Entity\LabelSystem\LabelProcessMode;
use App\Entity\LabelSystem\LabelSupportedElement;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
use App\Entity\LabelSystem\LabelOptions; use App\Entity\LabelSystem\LabelOptions;
use App\Form\Type\RichTextEditorType; use App\Form\Type\RichTextEditorType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EnumType;
use Symfony\Component\Form\Extension\Core\Type\NumberType; use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
@ -78,31 +82,33 @@ class LabelOptionsType extends AbstractType
], ],
]); ]);
$builder->add('supported_element', ChoiceType::class, [ $builder->add('supported_element', EnumType::class, [
'label' => 'label_options.supported_elements.label', 'label' => 'label_options.supported_elements.label',
'choices' => [ 'class' => LabelSupportedElement::class,
'part.label' => 'part', 'choice_label' => fn(LabelSupportedElement $choice) => match($choice) {
'part_lot.label' => 'part_lot', LabelSupportedElement::PART => 'part.label',
'storelocation.label' => 'storelocation', LabelSupportedElement::PART_LOT => 'part_lot.label',
], LabelSupportedElement::STORELOCATION => 'storelocation.label',
},
]); ]);
$builder->add('barcode_type', ChoiceType::class, [ $builder->add('barcode_type', EnumType::class, [
'label' => 'label_options.barcode_type.label', 'label' => 'label_options.barcode_type.label',
'empty_data' => 'none', 'empty_data' => 'none',
'choices' => [ 'class' => BarcodeType::class,
'label_options.barcode_type.none' => 'none', 'choice_label' => fn(BarcodeType $choice) => match($choice) {
'label_options.barcode_type.qr' => 'qr', BarcodeType::NONE => 'label_options.barcode_type.none',
'label_options.barcode_type.code128' => 'code128', BarcodeType::QR => 'label_options.barcode_type.qr',
'label_options.barcode_type.code39' => 'code39', BarcodeType::CODE128 => 'label_options.barcode_type.code128',
'label_options.barcode_type.code93' => 'code93', BarcodeType::CODE39 => 'label_options.barcode_type.code39',
'label_options.barcode_type.datamatrix' => 'datamatrix', BarcodeType::CODE93 => 'label_options.barcode_type.code93',
], BarcodeType::DATAMATRIX => 'label_options.barcode_type.datamatrix',
'group_by' => static function ($choice, $key, $value): ?string { },
if (in_array($choice, ['qr', 'datamatrix'], true)) { 'group_by' => static function (BarcodeType $choice, $key, $value): ?string {
if ($choice->is2D()) {
return 'label_options.barcode_type.2D'; return 'label_options.barcode_type.2D';
} }
if (in_array($choice, ['code39', 'code93', 'code128'], true)) { if ($choice->is1D()) {
return 'label_options.barcode_type.1D'; return 'label_options.barcode_type.1D';
} }
@ -129,12 +135,13 @@ class LabelOptionsType extends AbstractType
'required' => false, 'required' => false,
]); ]);
$builder->add('lines_mode', ChoiceType::class, [ $builder->add('process_mode', EnumType::class, [
'label' => 'label_options.lines_mode.label', 'label' => 'label_options.lines_mode.label',
'choices' => [ 'class' => LabelProcessMode::class,
'label_options.lines_mode.html' => 'html', 'choice_label' => fn(LabelProcessMode $choice) => match($choice) {
'label.options.lines_mode.twig' => 'twig', LabelProcessMode::PLACEHOLDER => 'label_options.lines_mode.html',
], LabelProcessMode::TWIG => 'label.options.lines_mode.twig',
},
'help' => 'label_options.lines_mode.help', 'help' => 'label_options.lines_mode.help',
'help_html' => true, 'help_html' => true,
'expanded' => true, 'expanded' => true,

View file

@ -43,6 +43,7 @@ namespace App\Repository;
use App\Entity\LabelSystem\LabelOptions; use App\Entity\LabelSystem\LabelOptions;
use App\Entity\LabelSystem\LabelProfile; use App\Entity\LabelSystem\LabelProfile;
use App\Entity\LabelSystem\LabelSupportedElement;
use App\Helpers\Trees\TreeViewNode; use App\Helpers\Trees\TreeViewNode;
use InvalidArgumentException; use InvalidArgumentException;
@ -52,12 +53,8 @@ class LabelProfileRepository extends NamedDBElementRepository
* Find the profiles that are shown in the dropdown for the given type. * Find the profiles that are shown in the dropdown for the given type.
* You should maybe use the cached version of this in LabelProfileDropdownHelper. * You should maybe use the cached version of this in LabelProfileDropdownHelper.
*/ */
public function getDropdownProfiles(string $type): array public function getDropdownProfiles(LabelSupportedElement $type): array
{ {
if (!in_array($type, LabelOptions::SUPPORTED_ELEMENTS, true)) {
throw new InvalidArgumentException('Invalid supported_element type given.');
}
return $this->findBy([ return $this->findBy([
'options.supported_element' => $type, 'options.supported_element' => $type,
'show_in_dropdown' => true, 'show_in_dropdown' => true,
@ -74,7 +71,7 @@ class LabelProfileRepository extends NamedDBElementRepository
{ {
$result = []; $result = [];
foreach (LabelOptions::SUPPORTED_ELEMENTS as $type) { foreach (LabelSupportedElement::cases() as $type) {
$type_children = []; $type_children = [];
$entities = $this->findForSupportedElement($type); $entities = $this->findForSupportedElement($type);
foreach ($entities as $entity) { foreach ($entities as $entity) {
@ -86,7 +83,7 @@ class LabelProfileRepository extends NamedDBElementRepository
if ($type_children !== []) { if ($type_children !== []) {
//Use default label e.g. 'part_label'. $$ marks that it will be translated in TreeViewGenerator //Use default label e.g. 'part_label'. $$ marks that it will be translated in TreeViewGenerator
$tmp = new TreeViewNode('$$'.$type.'.label', null, $type_children); $tmp = new TreeViewNode('$$'.$type->value.'.label', null, $type_children);
$result[] = $tmp; $result[] = $tmp;
} }
@ -98,15 +95,11 @@ class LabelProfileRepository extends NamedDBElementRepository
/** /**
* Find all LabelProfiles that can be used with the given type. * Find all LabelProfiles that can be used with the given type.
* *
* @param string $type see LabelOptions::SUPPORTED_ELEMENTS for valid values * @param LabelSupportedElement $type see LabelOptions::SUPPORTED_ELEMENTS for valid values
* @param array $order_by The way the results should be sorted. By default ordered by * @param array $order_by The way the results should be sorted. By default ordered by
*/ */
public function findForSupportedElement(string $type, array $order_by = ['name' => 'ASC']): array public function findForSupportedElement(LabelSupportedElement $type, array $order_by = ['name' => 'ASC']): array
{ {
if (!in_array($type, LabelOptions::SUPPORTED_ELEMENTS, true)) {
throw new InvalidArgumentException('Invalid supported_element type given.');
}
return $this->findBy(['options.supported_element' => $type], $order_by); return $this->findBy(['options.supported_element' => $type], $order_by);
} }
@ -115,7 +108,7 @@ class LabelProfileRepository extends NamedDBElementRepository
*/ */
public function getPartLabelProfiles(): array public function getPartLabelProfiles(): array
{ {
return $this->getDropdownProfiles('part'); return $this->getDropdownProfiles(LabelSupportedElement::PART);
} }
/** /**
@ -123,7 +116,7 @@ class LabelProfileRepository extends NamedDBElementRepository
*/ */
public function getPartLotsLabelProfiles(): array public function getPartLotsLabelProfiles(): array
{ {
return $this->getDropdownProfiles('part_lot'); return $this->getDropdownProfiles(LabelSupportedElement::PART_LOT);
} }
/** /**
@ -131,6 +124,6 @@ class LabelProfileRepository extends NamedDBElementRepository
*/ */
public function getStorelocationsLabelProfiles(): array public function getStorelocationsLabelProfiles(): array
{ {
return $this->getDropdownProfiles('storelocation'); return $this->getDropdownProfiles(LabelSupportedElement::STORELOCATION);
} }
} }

View file

@ -43,6 +43,7 @@ namespace App\Services\LabelSystem;
use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractDBElement;
use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Base\AbstractStructuralDBElement;
use App\Entity\LabelSystem\BarcodeType;
use App\Entity\LabelSystem\LabelOptions; use App\Entity\LabelSystem\LabelOptions;
use App\Services\LabelSystem\Barcodes\BarcodeContentGenerator; use App\Services\LabelSystem\Barcodes\BarcodeContentGenerator;
use Com\Tecnick\Barcode\Barcode; use Com\Tecnick\Barcode\Barcode;
@ -91,44 +92,31 @@ final class BarcodeGenerator
{ {
$barcode = new Barcode(); $barcode = new Barcode();
switch ($options->getBarcodeType()) { $type = match ($options->getBarcodeType()) {
case 'qr': BarcodeType::NONE => null,
$type = 'QRCODE'; BarcodeType::QR => 'QRCODE',
BarcodeType::DATAMATRIX => 'DATAMATRIX',
BarcodeType::CODE39 => 'C39',
BarcodeType::CODE93 => 'C93',
BarcodeType::CODE128 => 'C128A',
default => throw new InvalidArgumentException('Unknown label type!'),
};
break; if ($type === null) {
case 'datamatrix':
$type = 'DATAMATRIX';
break;
case 'code39':
$type = 'C39';
break;
case 'code93':
$type = 'C93';
break;
case 'code128':
$type = 'C128A';
break;
case 'none':
return null; return null;
default:
throw new InvalidArgumentException('Unknown label type!');
} }
$bobj = $barcode->getBarcodeObj($type, $this->getContent($options, $target));
return $bobj->getSvgCode(); return $barcode->getBarcodeObj($type, $this->getContent($options, $target))->getSvgCode();
} }
public function getContent(LabelOptions $options, AbstractDBElement $target): ?string public function getContent(LabelOptions $options, AbstractDBElement $target): ?string
{ {
return match ($options->getBarcodeType()) { $barcode = $options->getBarcodeType();
'qr', 'datamatrix' => $this->barcodeContentGenerator->getURLContent($target), return match (true) {
'code39', 'code93', 'code128' => $this->barcodeContentGenerator->get1DBarcodeContent($target), $barcode->is2D() => $this->barcodeContentGenerator->getURLContent($target),
'none' => null, $barcode->is1D() => $this->barcodeContentGenerator->get1DBarcodeContent($target),
$barcode === BarcodeType::NONE => null,
default => throw new InvalidArgumentException('Unknown label type!'), default => throw new InvalidArgumentException('Unknown label type!'),
}; };
} }

View file

@ -42,6 +42,7 @@ declare(strict_types=1);
namespace App\Services\LabelSystem; namespace App\Services\LabelSystem;
use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Base\AbstractStructuralDBElement;
use App\Entity\LabelSystem\LabelSupportedElement;
use App\Entity\Parts\Category; use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint; use App\Entity\Parts\Footprint;
use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Manufacturer;
@ -55,12 +56,12 @@ use ReflectionClass;
final class LabelExampleElementsGenerator final class LabelExampleElementsGenerator
{ {
public function getElement(string $type): object public function getElement(LabelSupportedElement $type): object
{ {
return match ($type) { return match ($type) {
'part' => $this->getExamplePart(), LabelSupportedElement::PART => $this->getExamplePart(),
'part_lot' => $this->getExamplePartLot(), LabelSupportedElement::PART_LOT => $this->getExamplePartLot(),
'storelocation' => $this->getStorelocation(), LabelSupportedElement::STORELOCATION => $this->getStorelocation(),
default => throw new InvalidArgumentException('Unknown $type.'), default => throw new InvalidArgumentException('Unknown $type.'),
}; };
} }

View file

@ -53,12 +53,6 @@ use InvalidArgumentException;
*/ */
final class LabelGenerator final class LabelGenerator
{ {
public const CLASS_SUPPORT_MAPPING = [
'part' => Part::class,
'part_lot' => PartLot::class,
'storelocation' => Storelocation::class,
];
public const MM_TO_POINTS_FACTOR = 2.83465; public const MM_TO_POINTS_FACTOR = 2.83465;
public function __construct(private readonly LabelHTMLGenerator $labelHTMLGenerator) public function __construct(private readonly LabelHTMLGenerator $labelHTMLGenerator)
@ -68,7 +62,7 @@ final class LabelGenerator
/** /**
* @param object|object[] $elements An element or an array of elements for which labels should be generated * @param object|object[] $elements An element or an array of elements for which labels should be generated
*/ */
public function generateLabel(LabelOptions $options, $elements): string public function generateLabel(LabelOptions $options, object|array $elements): string
{ {
if (!is_array($elements) && !is_object($elements)) { if (!is_array($elements) && !is_object($elements)) {
throw new InvalidArgumentException('$element must be an object or an array of objects!'); throw new InvalidArgumentException('$element must be an object or an array of objects!');
@ -98,11 +92,8 @@ final class LabelGenerator
public function supports(LabelOptions $options, object $element): bool public function supports(LabelOptions $options, object $element): bool
{ {
$supported_type = $options->getSupportedElement(); $supported_type = $options->getSupportedElement();
if (!isset(static::CLASS_SUPPORT_MAPPING[$supported_type])) {
throw new InvalidArgumentException('Supported type name of the Label options not known!');
}
return is_a($element, static::CLASS_SUPPORT_MAPPING[$supported_type]); return is_a($element, $supported_type->getEntityClass());
} }
/** /**

View file

@ -41,6 +41,7 @@ declare(strict_types=1);
namespace App\Services\LabelSystem; namespace App\Services\LabelSystem;
use App\Entity\LabelSystem\LabelProcessMode;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
use App\Entity\Contracts\NamedElementInterface; use App\Entity\Contracts\NamedElementInterface;
use App\Entity\LabelSystem\LabelOptions; use App\Entity\LabelSystem\LabelOptions;
@ -64,14 +65,14 @@ final class LabelHTMLGenerator
$twig_elements = []; $twig_elements = [];
if ('twig' === $options->getLinesMode()) { if (LabelProcessMode::TWIG === $options->getProcessMode()) {
$sandboxed_twig = $this->sandboxedTwigProvider->getTwig($options); $sandboxed_twig = $this->sandboxedTwigProvider->getTwig($options);
$current_user = $this->security->getUser(); $current_user = $this->security->getUser();
} }
$page = 1; $page = 1;
foreach ($elements as $element) { foreach ($elements as $element) {
if (isset($sandboxed_twig, $current_user) && 'twig' === $options->getLinesMode()) { if (isset($sandboxed_twig, $current_user) && LabelProcessMode::TWIG === $options->getProcessMode()) {
try { try {
$lines = $sandboxed_twig->render( $lines = $sandboxed_twig->render(
'lines', 'lines',

View file

@ -42,6 +42,7 @@ declare(strict_types=1);
namespace App\Services\LabelSystem; namespace App\Services\LabelSystem;
use App\Entity\LabelSystem\LabelProfile; use App\Entity\LabelSystem\LabelProfile;
use App\Entity\LabelSystem\LabelSupportedElement;
use App\Repository\LabelProfileRepository; use App\Repository\LabelProfileRepository;
use App\Services\UserSystem\UserCacheKeyGenerator; use App\Services\UserSystem\UserCacheKeyGenerator;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@ -54,10 +55,20 @@ final class LabelProfileDropdownHelper
{ {
} }
public function getDropdownProfiles(string $type): array /**
* Return all label profiles for the given supported element type
* @param LabelSupportedElement|string $type
* @return array
*/
public function getDropdownProfiles(LabelSupportedElement|string $type): array
{ {
//Useful for the twig templates, where we use the string representation of the enum
if (is_string($type)) {
$type = LabelSupportedElement::from($type);
}
$secure_class_name = str_replace('\\', '_', LabelProfile::class); $secure_class_name = str_replace('\\', '_', LabelProfile::class);
$key = 'profile_dropdown_'.$this->keyGenerator->generateKey().'_'.$secure_class_name.'_'.$type; $key = 'profile_dropdown_'.$this->keyGenerator->generateKey().'_'.$secure_class_name.'_'.$type->value;
/** @var LabelProfileRepository $repo */ /** @var LabelProfileRepository $repo */
$repo = $this->entityManager->getRepository(LabelProfile::class); $repo = $this->entityManager->getRepository(LabelProfile::class);

View file

@ -49,6 +49,7 @@ use App\Entity\Base\AbstractStructuralDBElement;
use App\Entity\Contracts\NamedElementInterface; use App\Entity\Contracts\NamedElementInterface;
use App\Entity\Contracts\TimeStampableInterface; use App\Entity\Contracts\TimeStampableInterface;
use App\Entity\LabelSystem\LabelOptions; use App\Entity\LabelSystem\LabelOptions;
use App\Entity\LabelSystem\LabelProcessMode;
use App\Entity\Parameters\AbstractParameter; use App\Entity\Parameters\AbstractParameter;
use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\MeasurementUnit;
use App\Entity\Parts\Part; use App\Entity\Parts\Part;
@ -123,7 +124,7 @@ final class SandboxedTwigProvider
public function getTwig(LabelOptions $options): Environment public function getTwig(LabelOptions $options): Environment
{ {
if ('twig' !== $options->getLinesMode()) { if (LabelProcessMode::TWIG !== $options->getProcessMode()) {
throw new InvalidArgumentException('The LabelOptions must explicitly allow twig via lines_mode = "twig"!'); throw new InvalidArgumentException('The LabelOptions must explicitly allow twig via lines_mode = "twig"!');
} }

View file

@ -25,6 +25,7 @@ namespace App\Twig;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Twig\Extension\AbstractExtension; use Twig\Extension\AbstractExtension;
use Twig\TwigFilter; use Twig\TwigFilter;
use Twig\TwigFunction;
use Twig\TwigTest; use Twig\TwigTest;
/** /**
@ -37,6 +38,14 @@ final class TwigCoreExtension extends AbstractExtension
{ {
} }
public function getFunctions()
{
return [
/* Returns the enum cases as values */
new TwigFunction('enum_cases', [$this, 'getEnumCases']),
];
}
public function getTests(): array public function getTests(): array
{ {
return [ return [
@ -49,6 +58,15 @@ final class TwigCoreExtension extends AbstractExtension
]; ];
} }
public function getEnumCases(string $enum_class): array
{
if (!enum_exists($enum_class)) {
throw new \InvalidArgumentException(sprintf('The given class "%s" is not an enum!', $enum_class));
}
return ($enum_class)::cases();
}
public function getFilters(): array public function getFilters(): array
{ {
return [ return [

View file

@ -77,10 +77,10 @@
</button> </button>
<div class="dropdown-menu" aria-labelledby="loadProfilesButton"> <div class="dropdown-menu" aria-labelledby="loadProfilesButton">
{% if is_granted("@labels.create_labels") %} {% if is_granted("@labels.create_labels") %}
{% for type in constant("App\\Entity\\LabelSystem\\LabelOptions::SUPPORTED_ELEMENTS") %} {% for type in enum_cases("App\\Entity\\LabelSystem\\LabelSupportedElement") %}
{% set profiles = label_profile_dropdown_helper.dropdownProfiles(type) %} {% set profiles = label_profile_dropdown_helper.dropdownProfiles(type) %}
{% if profiles is not empty %} {% if profiles is not empty %}
<h6 class="dropdown-header">{{ (type~'.label') | trans }}</h6> <h6 class="dropdown-header">{{ (type.value~'.label') | trans }}</h6>
{% endif %} {% endif %}
{% for profile in profiles %} {% for profile in profiles %}
<a class="dropdown-item" href="{{ path('label_dialog_profile', {'profile': profile.id }) }}">{{ profile.name }}</a> <a class="dropdown-item" href="{{ path('label_dialog_profile', {'profile': profile.id }) }}">{{ profile.name }}</a>

View file

@ -14,11 +14,11 @@
<body> <body>
{% for element in elements %} {% for element in elements %}
<div class="page"> <div class="page">
{% if options.barcodeType == 'none' %} {% if options.barcodeType.none %}
{% include "label_system/labels/label_page_none.html.twig" %} {% include "label_system/labels/label_page_none.html.twig" %}
{% elseif options.barcodeType in ['qr', 'datamatrix'] %} {% elseif options.barcodeType.is2D() %}
{% include "label_system/labels/label_page_qr.html.twig" %} {% include "label_system/labels/label_page_qr.html.twig" %}
{% elseif options.barcodeType in ['code39', 'code93', 'code128'] %} {% elseif options.barcodeType.is1D() %}
{% include "label_system/labels/label_page_1d.html.twig" %} {% include "label_system/labels/label_page_1d.html.twig" %}
{% endif %} {% endif %}
</div> </div>

View file

@ -41,6 +41,7 @@ declare(strict_types=1);
namespace App\Tests\Services\LabelSystem; namespace App\Tests\Services\LabelSystem;
use App\Entity\LabelSystem\BarcodeType;
use App\Entity\LabelSystem\LabelOptions; use App\Entity\LabelSystem\LabelOptions;
use App\Entity\Parts\Part; use App\Entity\Parts\Part;
use App\Services\LabelSystem\BarcodeGenerator; use App\Services\LabelSystem\BarcodeGenerator;
@ -65,13 +66,13 @@ final class BarcodeGeneratorTest extends WebTestCase
$part->setName('Test'); $part->setName('Test');
//Test that all barcodes types are supported //Test that all barcodes types are supported
foreach (LabelOptions::BARCODE_TYPES as $type) { foreach (BarcodeType::cases() as $type) {
$options = new LabelOptions(); $options = new LabelOptions();
$options->setBarcodeType($type); $options->setBarcodeType($type);
$content = $this->services->generateSVG($options, $part); $content = $this->services->generateSVG($options, $part);
//When type is none, service must return null. //When type is none, service must return null.
if ('none' === $type) { if (BarcodeType::NONE === $type) {
$this->assertNull($content); $this->assertNull($content);
} else { } else {
$this->assertIsString($content); $this->assertIsString($content);
@ -85,13 +86,13 @@ final class BarcodeGeneratorTest extends WebTestCase
$part->setName('Test'); $part->setName('Test');
//Test that all barcodes types are supported //Test that all barcodes types are supported
foreach (LabelOptions::BARCODE_TYPES as $type) { foreach (BarcodeType::cases() as $type) {
$options = new LabelOptions(); $options = new LabelOptions();
$options->setBarcodeType($type); $options->setBarcodeType($type);
$svg = $this->services->generateSVG($options, $part); $svg = $this->services->generateSVG($options, $part);
//When type is none, service must return null. //When type is none, service must return null.
if ('none' === $type) { if (BarcodeType::NONE === $type) {
$this->assertNull($svg); $this->assertNull($svg);
} else { } else {
$this->assertStringContainsStringIgnoringCase('SVG', $svg); $this->assertStringContainsStringIgnoringCase('SVG', $svg);

View file

@ -43,6 +43,7 @@ namespace App\Tests\Services\LabelSystem;
use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractDBElement;
use App\Entity\LabelSystem\LabelOptions; use App\Entity\LabelSystem\LabelOptions;
use App\Entity\LabelSystem\LabelSupportedElement;
use App\Entity\Parts\Part; use App\Entity\Parts\Part;
use App\Entity\Parts\PartLot; use App\Entity\Parts\PartLot;
use App\Entity\Parts\Storelocation; use App\Entity\Parts\Storelocation;
@ -62,19 +63,19 @@ class LabelGeneratorTest extends WebTestCase
$this->service = self::getContainer()->get(LabelGenerator::class); $this->service = self::getContainer()->get(LabelGenerator::class);
} }
public function supportsDataProvider(): array public static function supportsDataProvider(): array
{ {
return [ return [
['part', Part::class], [LabelSupportedElement::PART, Part::class],
['part_lot', PartLot::class], [LabelSupportedElement::PART_LOT, PartLot::class],
['storelocation', Storelocation::class], [LabelSupportedElement::STORELOCATION, Storelocation::class],
]; ];
} }
/** /**
* @dataProvider supportsDataProvider * @dataProvider supportsDataProvider
*/ */
public function testSupports(string $type, string $class): void public function testSupports(LabelSupportedElement $type, string $class): void
{ {
$options = new LabelOptions(); $options = new LabelOptions();
$options->setSupportedElement($type); $options->setSupportedElement($type);
@ -102,7 +103,7 @@ class LabelGeneratorTest extends WebTestCase
$part = new Part(); $part = new Part();
$options = new LabelOptions(); $options = new LabelOptions();
$options->setLines('Test'); $options->setLines('Test');
$options->setSupportedElement('part'); $options->setSupportedElement(LabelSupportedElement::PART);
//Test for a single passed element: //Test for a single passed element:
$pdf = $this->service->generateLabel($options, $part); $pdf = $this->service->generateLabel($options, $part);

View file

@ -42,6 +42,8 @@ declare(strict_types=1);
namespace App\Tests\Services\LabelSystem; namespace App\Tests\Services\LabelSystem;
use App\Entity\LabelSystem\LabelOptions; use App\Entity\LabelSystem\LabelOptions;
use App\Entity\LabelSystem\LabelProcessMode;
use App\Entity\LabelSystem\LabelSupportedElement;
use App\Entity\Parts\Part; use App\Entity\Parts\Part;
use App\Entity\Parts\PartLot; use App\Entity\Parts\PartLot;
use App\Entity\Parts\Storelocation; use App\Entity\Parts\Storelocation;
@ -104,9 +106,9 @@ class SandboxedTwigProviderTest extends WebTestCase
public function testTwigFeatures(string $twig): void public function testTwigFeatures(string $twig): void
{ {
$options = new LabelOptions(); $options = new LabelOptions();
$options->setSupportedElement('part'); $options->setSupportedElement(LabelSupportedElement::PART);
$options->setLines($twig); $options->setLines($twig);
$options->setLinesMode('twig'); $options->setProcessMode(LabelProcessMode::TWIG);
$twig = $this->service->getTwig($options); $twig = $this->service->getTwig($options);
$str = $twig->render('lines', [ $str = $twig->render('lines', [
@ -126,9 +128,9 @@ class SandboxedTwigProviderTest extends WebTestCase
$this->expectException(SecurityError::class); $this->expectException(SecurityError::class);
$options = new LabelOptions(); $options = new LabelOptions();
$options->setSupportedElement('part'); $options->setSupportedElement(LabelSupportedElement::PART);
$options->setLines($twig); $options->setLines($twig);
$options->setLinesMode('twig'); $options->setProcessMode(LabelProcessMode::TWIG);
$twig = $this->service->getTwig($options); $twig = $this->service->getTwig($options);
$str = $twig->render('lines', [ $str = $twig->render('lines', [