diff --git a/assets/controllers/elements/collection_type_controller.js b/assets/controllers/elements/collection_type_controller.js index 647ed5e5..048600a9 100644 --- a/assets/controllers/elements/collection_type_controller.js +++ b/assets/controllers/elements/collection_type_controller.js @@ -81,7 +81,7 @@ export default class extends Controller { //Afterwards return the newly created row if(targetTable.tBodies[0]) { targetTable.tBodies[0].insertAdjacentHTML('beforeend', newElementStr); - ret = targetTable.tBodies[0].lastElementChild; + ret = targetTable.tBodies[0].lastElementChild; } else { //Otherwise just insert it targetTable.insertAdjacentHTML('beforeend', newElementStr); ret = targetTable.lastElementChild; @@ -90,20 +90,10 @@ export default class extends Controller { //Trigger an event to notify other components that a new element has been created, so they can for example initialize select2 on it targetTable.dispatchEvent(new CustomEvent("collection:elementAdded", {bubbles: true})); - this.focusNumberInput(ret); - return ret; } - focusNumberInput(element) { - const fields = element.querySelectorAll("input[type=number]"); - //Focus the first available number input field to open the numeric keyboard on mobile devices - if(fields.length > 0) { - fields[0].focus(); - } - } - /** * This action opens a file dialog to select multiple files and then creates a new element for each file, where * the file is assigned to the input field. diff --git a/assets/controllers/pages/part_withdraw_modal_controller.js b/assets/controllers/pages/part_withdraw_modal_controller.js index 0e5c0fc5..2d6742b4 100644 --- a/assets/controllers/pages/part_withdraw_modal_controller.js +++ b/assets/controllers/pages/part_withdraw_modal_controller.js @@ -5,7 +5,6 @@ export default class extends Controller { connect() { this.element.addEventListener('show.bs.modal', event => this._handleModalOpen(event)); - this.element.addEventListener('shown.bs.modal', event => this._handleModalShown(event)); } _handleModalOpen(event) { @@ -62,8 +61,4 @@ export default class extends Controller amountInput.setAttribute('max', lotAmount); } } - - _handleModalShown(event) { - this.element.querySelector('input[name="amount"]').focus(); - } } \ No newline at end of file diff --git a/composer.lock b/composer.lock index 2db828a5..6566ebbc 100644 --- a/composer.lock +++ b/composer.lock @@ -16558,16 +16558,16 @@ }, { "name": "thecodingmachine/safe", - "version": "v3.4.0", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/thecodingmachine/safe.git", - "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19" + "reference": "2cdd579eeaa2e78e51c7509b50cc9fb89a956236" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19", - "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/2cdd579eeaa2e78e51c7509b50cc9fb89a956236", + "reference": "2cdd579eeaa2e78e51c7509b50cc9fb89a956236", "shasum": "" }, "require": { @@ -16677,7 +16677,7 @@ "description": "PHP core functions that throw exceptions instead of returning FALSE on error", "support": { "issues": "https://github.com/thecodingmachine/safe/issues", - "source": "https://github.com/thecodingmachine/safe/tree/v3.4.0" + "source": "https://github.com/thecodingmachine/safe/tree/v3.3.0" }, "funding": [ { @@ -16688,16 +16688,12 @@ "url": "https://github.com/shish", "type": "github" }, - { - "url": "https://github.com/silasjoisten", - "type": "github" - }, { "url": "https://github.com/staabm", "type": "github" } ], - "time": "2026-02-04T18:08:13+00:00" + "time": "2025-05-14T06:15:44+00:00" }, { "name": "tiendanube/gtinvalidation", diff --git a/src/ApiResource/LabelGenerationRequest.php b/src/ApiResource/LabelGenerationRequest.php deleted file mode 100644 index 9b4462a0..00000000 --- a/src/ApiResource/LabelGenerationRequest.php +++ /dev/null @@ -1,84 +0,0 @@ -. - */ - -namespace App\ApiResource; - -use ApiPlatform\Metadata\ApiProperty; -use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Post; -use ApiPlatform\OpenApi\Model\Operation; -use ApiPlatform\OpenApi\Model\RequestBody; -use ApiPlatform\OpenApi\Model\Response; -use App\Entity\LabelSystem\LabelSupportedElement; -use App\State\LabelGenerationProcessor; -use App\Validator\Constraints\Misc\ValidRange; -use Symfony\Component\Validator\Constraints as Assert; - -/** - * API Resource for generating PDF labels for parts, part lots, or storage locations. - * This endpoint allows generating labels using saved label profiles. - */ -#[ApiResource( - uriTemplate: '/labels/generate', - description: 'Generate PDF labels for parts, part lots, or storage locations using label profiles.', - operations: [ - new Post( - inputFormats: ['json' => ['application/json']], - outputFormats: [], - openapi: new Operation( - responses: [ - "200" => new Response(description: "PDF file containing the generated labels"), - ], - summary: 'Generate PDF labels', - description: 'Generate PDF labels for one or more elements using a label profile. Returns a PDF file.', - requestBody: new RequestBody( - description: 'Label generation request', - required: true, - ), - ), - ) - ], - processor: LabelGenerationProcessor::class, -)] -class LabelGenerationRequest -{ - /** - * @var int The ID of the label profile to use for generation - */ - #[Assert\NotBlank(message: 'Profile ID is required')] - #[Assert\Positive(message: 'Profile ID must be a positive integer')] - public int $profileId = 0; - - /** - * @var string Comma-separated list of element IDs or ranges (e.g., "1,2,5-10,15") - */ - #[Assert\NotBlank(message: 'Element IDs are required')] - #[ValidRange()] - #[ApiProperty(example: "1,2,5-10,15")] - public string $elementIds = ''; - - /** - * @var LabelSupportedElement|null Optional: Override the element type. If not provided, uses profile's default. - */ - public ?LabelSupportedElement $elementType = null; -} diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php index d2faba76..fbc5211d 100644 --- a/src/DataTables/PartsDataTable.php +++ b/src/DataTables/PartsDataTable.php @@ -47,7 +47,6 @@ use App\Services\EntityURLGenerator; use App\Services\Formatters\AmountFormatter; use App\Settings\BehaviorSettings\TableSettings; use Doctrine\ORM\AbstractQuery; -use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; use Omines\DataTablesBundle\Adapter\Doctrine\ORM\SearchCriteriaProvider; use Omines\DataTablesBundle\Column\TextColumn; @@ -334,7 +333,6 @@ final class PartsDataTable implements DataTableTypeInterface ->addSelect('orderdetails') ->addSelect('attachments') ->addSelect('storelocations') - ->addSelect('projectBomEntries') ->from(Part::class, 'part') ->leftJoin('part.category', 'category') ->leftJoin('part.master_picture_attachment', 'master_picture_attachment') @@ -349,7 +347,6 @@ final class PartsDataTable implements DataTableTypeInterface ->leftJoin('part.partUnit', 'partUnit') ->leftJoin('part.partCustomState', 'partCustomState') ->leftJoin('part.parameters', 'parameters') - ->leftJoin('part.project_bom_entries', 'projectBomEntries') ->where('part.id IN (:ids)') ->setParameter('ids', $ids) @@ -367,12 +364,7 @@ final class PartsDataTable implements DataTableTypeInterface ->addGroupBy('attachments') ->addGroupBy('partUnit') ->addGroupBy('partCustomState') - ->addGroupBy('parameters') - ->addGroupBy('projectBomEntries') - - ->setHint(Query::HINT_READ_ONLY, true) - ->setHint(Query::HINT_FORCE_PARTIAL_LOAD, false) - ; + ->addGroupBy('parameters'); //Get the results in the same order as the IDs were passed FieldHelper::addOrderByFieldParam($builder, 'part.id', 'ids'); diff --git a/src/Entity/LabelSystem/LabelProfile.php b/src/Entity/LabelSystem/LabelProfile.php index 236c07f7..d3616c34 100644 --- a/src/Entity/LabelSystem/LabelProfile.php +++ b/src/Entity/LabelSystem/LabelProfile.php @@ -41,12 +41,6 @@ declare(strict_types=1); namespace App\Entity\LabelSystem; -use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; -use ApiPlatform\Metadata\ApiFilter; -use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Get; -use ApiPlatform\Metadata\GetCollection; -use ApiPlatform\OpenApi\Model\Operation; use Doctrine\Common\Collections\Criteria; use App\Entity\Attachments\Attachment; use App\Repository\LabelProfileRepository; @@ -64,22 +58,6 @@ use Symfony\Component\Validator\Constraints as Assert; /** * @extends AttachmentContainingDBElement */ -#[ApiResource( - operations: [ - new Get( - normalizationContext: ['groups' => ['label_profile:read', 'simple']], - security: "is_granted('read', object)", - openapi: new Operation(summary: 'Get a label profile by ID') - ), - new GetCollection( - normalizationContext: ['groups' => ['label_profile:read', 'simple']], - security: "is_granted('@labels.create_labels')", - openapi: new Operation(summary: 'List all available label profiles') - ), - ], - paginationEnabled: false, -)] -#[ApiFilter(SearchFilter::class, properties: ['options.supported_element' => 'exact', 'show_in_dropdown' => 'exact'])] #[UniqueEntity(['name', 'options.supported_element'])] #[ORM\Entity(repositoryClass: LabelProfileRepository::class)] #[ORM\EntityListeners([TreeCacheInvalidationListener::class])] @@ -102,21 +80,20 @@ class LabelProfile extends AttachmentContainingDBElement */ #[Assert\Valid] #[ORM\Embedded(class: 'LabelOptions')] - #[Groups(["extended", "full", "import", "label_profile:read"])] + #[Groups(["extended", "full", "import"])] protected LabelOptions $options; /** * @var string The comment info for this element */ #[ORM\Column(type: Types::TEXT)] - #[Groups(["extended", "full", "import", "label_profile:read"])] protected string $comment = ''; /** * @var bool determines, if this label profile should be shown in the dropdown quick menu */ #[ORM\Column(type: Types::BOOLEAN)] - #[Groups(["extended", "full", "import", "label_profile:read"])] + #[Groups(["extended", "full", "import"])] protected bool $show_in_dropdown = true; public function __construct() diff --git a/src/Form/AdminPages/BaseEntityAdminForm.php b/src/Form/AdminPages/BaseEntityAdminForm.php index f4bf37f8..5a4ef5bc 100644 --- a/src/Form/AdminPages/BaseEntityAdminForm.php +++ b/src/Form/AdminPages/BaseEntityAdminForm.php @@ -71,7 +71,6 @@ class BaseEntityAdminForm extends AbstractType 'label' => 'name.label', 'attr' => [ 'placeholder' => 'part.name.placeholder', - 'autofocus' => $is_new, ], 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity), ]); diff --git a/src/Form/Part/PartBaseType.php b/src/Form/Part/PartBaseType.php index 6b929486..2145db93 100644 --- a/src/Form/Part/PartBaseType.php +++ b/src/Form/Part/PartBaseType.php @@ -117,7 +117,6 @@ class PartBaseType extends AbstractType 'label' => 'part.edit.name', 'attr' => [ 'placeholder' => 'part.edit.name.placeholder', - 'autofocus' => $new_part, ], ]) ->add('description', RichTextEditorType::class, [ diff --git a/src/Services/InfoProviderSystem/PartInfoRetriever.php b/src/Services/InfoProviderSystem/PartInfoRetriever.php index 9a24f3ae..0eb74642 100644 --- a/src/Services/InfoProviderSystem/PartInfoRetriever.php +++ b/src/Services/InfoProviderSystem/PartInfoRetriever.php @@ -82,7 +82,7 @@ final class PartInfoRetriever protected function searchInProvider(InfoProviderInterface $provider, string $keyword): array { //Generate key and escape reserved characters from the provider id - $escaped_keyword = hash('xxh3', $keyword); + $escaped_keyword = urlencode($keyword); return $this->partInfoCache->get("search_{$provider->getProviderKey()}_{$escaped_keyword}", function (ItemInterface $item) use ($provider, $keyword) { //Set the expiration time $item->expiresAfter(!$this->debugMode ? self::CACHE_RESULT_EXPIRATION : 1); @@ -108,7 +108,7 @@ final class PartInfoRetriever } //Generate key and escape reserved characters from the provider id - $escaped_part_id = hash('xxh3', $part_id); + $escaped_part_id = urlencode($part_id); return $this->partInfoCache->get("details_{$provider_key}_{$escaped_part_id}", function (ItemInterface $item) use ($provider, $part_id) { //Set the expiration time $item->expiresAfter(!$this->debugMode ? self::CACHE_DETAIL_EXPIRATION : 1); @@ -145,4 +145,4 @@ final class PartInfoRetriever return $this->dto_to_entity_converter->convertPart($details); } -} +} \ No newline at end of file diff --git a/src/State/LabelGenerationProcessor.php b/src/State/LabelGenerationProcessor.php deleted file mode 100644 index 0472bbbd..00000000 --- a/src/State/LabelGenerationProcessor.php +++ /dev/null @@ -1,141 +0,0 @@ -. - */ - -namespace App\State; - -use ApiPlatform\Metadata\Operation; -use ApiPlatform\State\ProcessorInterface; -use App\ApiResource\LabelGenerationRequest; -use App\Entity\Base\AbstractDBElement; -use App\Entity\LabelSystem\LabelProfile; -use App\Repository\DBElementRepository; -use App\Repository\LabelProfileRepository; -use App\Services\ElementTypeNameGenerator; -use App\Services\LabelSystem\LabelGenerator; -use App\Services\Misc\RangeParser; -use Doctrine\ORM\EntityManagerInterface; -use Symfony\Bundle\SecurityBundle\Security; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; - -class LabelGenerationProcessor implements ProcessorInterface -{ - public function __construct( - private readonly EntityManagerInterface $entityManager, - private readonly LabelGenerator $labelGenerator, - private readonly RangeParser $rangeParser, - private readonly ElementTypeNameGenerator $elementTypeNameGenerator, - private readonly Security $security, - ) { - } - - public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): Response - { - // Check if user has permission to create labels - if (!$this->security->isGranted('@labels.create_labels')) { - throw new AccessDeniedHttpException('You do not have permission to generate labels.'); - } - - if (!$data instanceof LabelGenerationRequest) { - throw new BadRequestHttpException('Invalid request data for label generation.'); - } - - /** @var LabelGenerationRequest $request */ - $request = $data; - - // Fetch the label profile - /** @var LabelProfileRepository $profileRepo */ - $profileRepo = $this->entityManager->getRepository(LabelProfile::class); - $profile = $profileRepo->find($request->profileId); - if (!$profile instanceof LabelProfile) { - throw new NotFoundHttpException(sprintf('Label profile with ID %d not found.', $request->profileId)); - } - - // Check if user has read permission for the profile - if (!$this->security->isGranted('read', $profile)) { - throw new AccessDeniedHttpException('You do not have permission to access this label profile.'); - } - - // Get label options from profile - $options = $profile->getOptions(); - - // Override element type if provided, otherwise use profile's default - if ($request->elementType !== null) { - $options->setSupportedElement($request->elementType); - } - - // Parse element IDs from the range string - try { - $idArray = $this->rangeParser->parse($request->elementIds); - } catch (\InvalidArgumentException $e) { - throw new BadRequestHttpException('Invalid element IDs format: ' . $e->getMessage()); - } - - if (empty($idArray)) { - throw new BadRequestHttpException('No valid element IDs provided.'); - } - - // Fetch the target entities - /** @var DBElementRepository $repo */ - $repo = $this->entityManager->getRepository($options->getSupportedElement()->getEntityClass()); - - $elements = $repo->getElementsFromIDArray($idArray); - - if (empty($elements)) { - throw new NotFoundHttpException('No elements found with the provided IDs.'); - } - - // Generate the PDF - try { - $pdfContent = $this->labelGenerator->generateLabel($options, $elements); - } catch (\Exception $e) { - throw new BadRequestHttpException('Failed to generate label: ' . $e->getMessage()); - } - - // Generate filename - $filename = $this->generateFilename($elements[0], $profile); - - - // Return PDF as response - return new Response( - $pdfContent, - Response::HTTP_OK, - [ - 'Content-Type' => 'application/pdf', - 'Content-Disposition' => sprintf('attachment; filename="%s"', $filename), - 'Content-Length' => (string) strlen($pdfContent), - ] - ); - } - - private function generateFilename(AbstractDBElement $element, LabelProfile $profile): string - { - $ret = 'label_' . $this->elementTypeNameGenerator->typeLabel($element); - $ret .= $element->getID(); - $ret .= '_' . preg_replace('/[^a-z0-9_\-]/i', '_', $profile->getName()); - - return $ret . '.pdf'; - } -} diff --git a/src/Twig/Sandbox/SandboxedLabelExtension.php b/src/Twig/Sandbox/SandboxedLabelExtension.php index 6fe85e80..3b8eeed2 100644 --- a/src/Twig/Sandbox/SandboxedLabelExtension.php +++ b/src/Twig/Sandbox/SandboxedLabelExtension.php @@ -65,28 +65,28 @@ class SandboxedLabelExtension extends AbstractExtension */ public function associatedParts(AbstractPartsContainingDBElement $element): array { - /** @var AbstractPartsContainingRepository $repo */ + /** @var AbstractPartsContainingRepository $repo */ $repo = $this->em->getRepository($element::class); return $repo->getParts($element); } public function associatedPartsCount(AbstractPartsContainingDBElement $element): int { - /** @var AbstractPartsContainingRepository $repo */ + /** @var AbstractPartsContainingRepository $repo */ $repo = $this->em->getRepository($element::class); return $repo->getPartsCount($element); } public function associatedPartsRecursive(AbstractPartsContainingDBElement $element): array { - /** @var AbstractPartsContainingRepository $repo */ + /** @var AbstractPartsContainingRepository $repo */ $repo = $this->em->getRepository($element::class); return $repo->getPartsRecursive($element); } public function associatedPartsCountRecursive(AbstractPartsContainingDBElement $element): int { - /** @var AbstractPartsContainingRepository $repo */ + /** @var AbstractPartsContainingRepository $repo */ $repo = $this->em->getRepository($element::class); return $repo->getPartsCountRecursive($element); } diff --git a/templates/form/extended_bootstrap_layout.html.twig b/templates/form/extended_bootstrap_layout.html.twig index 1227750c..ecd7caf0 100644 --- a/templates/form/extended_bootstrap_layout.html.twig +++ b/templates/form/extended_bootstrap_layout.html.twig @@ -155,8 +155,3 @@ {{- parent() -}} {% endif %} {% endblock %} - -{% block boolean_constraint_widget %} - {{ form_widget(form.value) }} - {{ form_errors(form.value) }} -{% endblock %} diff --git a/templates/parts/edit/_eda.html.twig b/templates/parts/edit/_eda.html.twig index 1383871e..4df675c4 100644 --- a/templates/parts/edit/_eda.html.twig +++ b/templates/parts/edit/_eda.html.twig @@ -1,7 +1,11 @@ {{ form_row(form.eda_info.reference_prefix) }} {{ form_row(form.eda_info.value) }} -{{ form_row(form.eda_info.visibility) }} +
+
+ {{ form_row(form.eda_info.visibility) }} +
+
@@ -17,4 +21,4 @@
{{ form_row(form.eda_info.kicad_symbol) }} -{{ form_row(form.eda_info.kicad_footprint) }} +{{ form_row(form.eda_info.kicad_footprint) }} \ No newline at end of file diff --git a/tests/API/Endpoints/LabelEndpointTest.php b/tests/API/Endpoints/LabelEndpointTest.php deleted file mode 100644 index 338af836..00000000 --- a/tests/API/Endpoints/LabelEndpointTest.php +++ /dev/null @@ -1,186 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Tests\API\Endpoints; - -use App\Tests\API\AuthenticatedApiTestCase; - -class LabelEndpointTest extends AuthenticatedApiTestCase -{ - public function testGetLabelProfiles(): void - { - $response = self::createAuthenticatedClient()->request('GET', '/api/label_profiles'); - - self::assertResponseIsSuccessful(); - self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); - - // Check that we get an array of label profiles - $json = $response->toArray(); - self::assertIsArray($json['hydra:member']); - self::assertNotEmpty($json['hydra:member']); - - // Check the structure of the first profile - $firstProfile = $json['hydra:member'][0]; - self::assertArrayHasKey('@id', $firstProfile); - self::assertArrayHasKey('name', $firstProfile); - self::assertArrayHasKey('options', $firstProfile); - self::assertArrayHasKey('show_in_dropdown', $firstProfile); - } - - public function testGetSingleLabelProfile(): void - { - $response = self::createAuthenticatedClient()->request('GET', '/api/label_profiles/1'); - - self::assertResponseIsSuccessful(); - self::assertJsonContains([ - '@id' => '/api/label_profiles/1', - ]); - - $json = $response->toArray(); - self::assertArrayHasKey('name', $json); - self::assertArrayHasKey('options', $json); - // Note: options is serialized but individual fields like width/height - // are only available in 'extended' or 'full' serialization groups - self::assertIsArray($json['options']); - } - - public function testFilterLabelProfilesByElementType(): void - { - $response = self::createAuthenticatedClient()->request('GET', '/api/label_profiles?options.supported_element=part'); - - self::assertResponseIsSuccessful(); - - $json = $response->toArray(); - // Check that we get results - the filter should work even if the field isn't in response - self::assertIsArray($json['hydra:member']); - // verify we got profiles - self::assertNotEmpty($json['hydra:member']); - } - - public function testGenerateLabelPdf(): void - { - $response = self::createAuthenticatedClient()->request('POST', '/api/labels/generate', [ - 'json' => [ - 'profileId' => 1, - 'elementIds' => '1', - ], - ]); - - self::assertResponseIsSuccessful(); - self::assertResponseHeaderSame('content-type', 'application/pdf'); - - // Check that the response contains PDF data - $content = $response->getContent(); - self::assertStringStartsWith('%PDF-', $content); - - // Check Content-Disposition header contains attachment and .pdf - $headers = $response->getHeaders(); - self::assertArrayHasKey('content-disposition', $headers); - $disposition = $headers['content-disposition'][0]; - self::assertStringContainsString('attachment', $disposition); - self::assertStringContainsString('.pdf', $disposition); - } - - public function testGenerateLabelPdfWithMultipleElements(): void - { - $response = self::createAuthenticatedClient()->request('POST', '/api/labels/generate', [ - 'json' => [ - 'profileId' => 1, - 'elementIds' => '1,2,3', - ], - ]); - - self::assertResponseIsSuccessful(); - self::assertResponseHeaderSame('content-type', 'application/pdf'); - self::assertStringStartsWith('%PDF-', $response->getContent()); - } - - public function testGenerateLabelPdfWithRange(): void - { - $response = self::createAuthenticatedClient()->request('POST', '/api/labels/generate', [ - 'json' => [ - 'profileId' => 1, - 'elementIds' => '1-3', - ], - ]); - - self::assertResponseIsSuccessful(); - self::assertResponseHeaderSame('content-type', 'application/pdf'); - self::assertStringStartsWith('%PDF-', $response->getContent()); - } - - public function testGenerateLabelPdfWithInvalidProfileId(): void - { - self::createAuthenticatedClient()->request('POST', '/api/labels/generate', [ - 'json' => [ - 'profileId' => 99999, - 'elementIds' => '1', - ], - ]); - - self::assertResponseStatusCodeSame(404); - } - - public function testGenerateLabelPdfWithInvalidElementIds(): void - { - $client = self::createAuthenticatedClient(); - $client->request('POST', '/api/labels/generate', [ - 'json' => [ - 'profileId' => 1, - 'elementIds' => 'invalid', - ], - ]); - - // Should return 400 or 422 (validation error) - $response = $client->getResponse(); - $statusCode = $response->getStatusCode(); - self::assertTrue( - $statusCode === 400 || $statusCode === 422, - "Expected status code 400 or 422, got {$statusCode}" - ); - } - - public function testGenerateLabelPdfWithNonExistentElements(): void - { - self::createAuthenticatedClient()->request('POST', '/api/labels/generate', [ - 'json' => [ - 'profileId' => 1, - 'elementIds' => '99999', - ], - ]); - - self::assertResponseStatusCodeSame(404); - } - - public function testGenerateLabelPdfRequiresAuthentication(): void - { - // Create a non-authenticated client - self::createClient()->request('POST', '/api/labels/generate', [ - 'json' => [ - 'profileId' => 1, - 'elementIds' => '1', - ], - ]); - - self::assertResponseStatusCodeSame(401); - } -} diff --git a/translations/frontend.da.xlf b/translations/frontend.da.xlf index 4b6a15b9..9c6b3129 100644 --- a/translations/frontend.da.xlf +++ b/translations/frontend.da.xlf @@ -56,4 +56,4 @@ - + \ No newline at end of file diff --git a/translations/frontend.de.xlf b/translations/frontend.de.xlf index 9ebd0d32..4eaded60 100644 --- a/translations/frontend.de.xlf +++ b/translations/frontend.de.xlf @@ -56,4 +56,4 @@ - + \ No newline at end of file diff --git a/translations/frontend.en.xlf b/translations/frontend.en.xlf index 91617f79..aa3cf2d9 100644 --- a/translations/frontend.en.xlf +++ b/translations/frontend.en.xlf @@ -56,4 +56,4 @@ - + \ No newline at end of file diff --git a/translations/frontend.hu.xlf b/translations/frontend.hu.xlf index c303dedc..bdcda170 100644 --- a/translations/frontend.hu.xlf +++ b/translations/frontend.hu.xlf @@ -56,4 +56,4 @@ - + \ No newline at end of file diff --git a/translations/frontend.uk.xlf b/translations/frontend.uk.xlf index fee1b03e..86f51f95 100644 --- a/translations/frontend.uk.xlf +++ b/translations/frontend.uk.xlf @@ -56,4 +56,4 @@ - + \ No newline at end of file diff --git a/translations/frontend.zh.xlf b/translations/frontend.zh.xlf index 8bb063b8..d2ea6fd0 100644 --- a/translations/frontend.zh.xlf +++ b/translations/frontend.zh.xlf @@ -56,4 +56,4 @@ - + \ No newline at end of file diff --git a/translations/messages.de.xlf b/translations/messages.de.xlf index c20e8152..c4f2d5d8 100644 --- a/translations/messages.de.xlf +++ b/translations/messages.de.xlf @@ -11868,7 +11868,7 @@ Buerklin-API-Authentication-Server: update_manager.view_release - Release ansehen + update_manager.view_release @@ -11964,7 +11964,7 @@ Buerklin-API-Authentication-Server: update_manager.view_release_notes - Release notes ansehen + update_manager.view_release_notes @@ -12102,7 +12102,7 @@ Buerklin-API-Authentication-Server: perm.system.manage_updates - Part-DB Updated verwalten + perm.system.manage_updates @@ -12354,13 +12354,13 @@ Buerklin-API-Authentication-Server: settings.ips.generic_web_provider.enabled.help - Wenn der Anbieter aktiviert ist, können Benutzer im Namen des Part-DB-Servers Anfragen an beliebige Websites stellen. Aktivieren Sie diese Option nur, wenn Sie sich der möglichen Folgen bewusst sind. + settings.ips.generic_web_provider.enabled.help info_providers.from_url.title - Erstelle [Part] aus URL + Erstelle [part] aus URL @@ -12399,113 +12399,5 @@ Buerklin-API-Authentication-Server: Update zu - - - part.gtin - GTIN / EAN - - - - - info_providers.capabilities.gtin - GTIN / EAN - - - - - part.table.gtin - GTIN - - - - - scan_dialog.mode.gtin - GTIN / EAN Barcode - - - - - attachment_type.edit.allowed_targets - Nur verwenden für - - - - - attachment_type.edit.allowed_targets.help - Machen Sie diesen Anhangstyp nur für bestimmte Elementtypen verfügbar. Leer lassen, um diesen Anhangstyp für alle Elementtypen anzuzeigen. - - - - - orderdetails.edit.prices_includes_vat - Preise einschl. MwSt. - - - - - prices.incl_vat - Inkl. MwSt. - - - - - prices.excl_vat - Exkl. MwSt. - - - - - settings.system.localization.prices_include_tax_by_default - Preise enthalten standardmäßig Mehrwertsteuer - - - - - settings.system.localization.prices_include_tax_by_default.description - Der Standardwert für neu erstellte Einkaufinformationen, ob die Preise Mehrwertsteuer enthalten oder nicht. - - - - - part_lot.edit.last_stocktake_at - Letzte Inventur - - - - - perm.parts_stock.stocktake - Inventur - - - - - part.info.stocktake_modal.title - Inventur des Bestandes - - - - - part.info.stocktake_modal.expected_amount - Erwartete Menge - - - - - part.info.stocktake_modal.actual_amount - Tatsächliche Menge - - - - - log.part_stock_changed.stock_take - Inventur - - - - - log.element_edited.changed_fields.last_stocktake_at - Letzte Inventur - - - + \ No newline at end of file diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index d9418563..bbd96ac6 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -12402,109 +12402,109 @@ Buerklin-API Authentication server: - + part.gtin GTIN / EAN - + info_providers.capabilities.gtin GTIN / EAN - + part.table.gtin GTIN - + scan_dialog.mode.gtin GTIN / EAN barcode - + attachment_type.edit.allowed_targets Use only for - + attachment_type.edit.allowed_targets.help Make this attachment type only available for certain element classes. Leave empty to show this attachment type for all element classes. - + orderdetails.edit.prices_includes_vat Prices include VAT - + prices.incl_vat Incl. VAT - + prices.excl_vat Excl. VAT - + settings.system.localization.prices_include_tax_by_default Prices include VAT by default - + settings.system.localization.prices_include_tax_by_default.description The default value for newly created purchase infos, if prices include VAT or not. - + part_lot.edit.last_stocktake_at Last stocktake - + perm.parts_stock.stocktake Stocktake - + part.info.stocktake_modal.title Stocktake lot - + part.info.stocktake_modal.expected_amount Expected amount - + part.info.stocktake_modal.actual_amount Actual amount - + log.part_stock_changed.stock_take Stocktake - + log.element_edited.changed_fields.last_stocktake_at Last stocktake diff --git a/translations/security.da.xlf b/translations/security.da.xlf index ab35c605..99329533 100644 --- a/translations/security.da.xlf +++ b/translations/security.da.xlf @@ -20,4 +20,4 @@ - + \ No newline at end of file diff --git a/translations/security.de.xlf b/translations/security.de.xlf index 927f8f9c..2a357094 100644 --- a/translations/security.de.xlf +++ b/translations/security.de.xlf @@ -20,4 +20,4 @@ - + \ No newline at end of file diff --git a/translations/security.en.xlf b/translations/security.en.xlf index 0b0b4569..5a79d6ec 100644 --- a/translations/security.en.xlf +++ b/translations/security.en.xlf @@ -20,4 +20,4 @@ - + \ No newline at end of file diff --git a/translations/security.hu.xlf b/translations/security.hu.xlf index 7c448da0..3c885815 100644 --- a/translations/security.hu.xlf +++ b/translations/security.hu.xlf @@ -20,4 +20,4 @@ - + \ No newline at end of file diff --git a/translations/security.nl.xlf b/translations/security.nl.xlf index 7ba9fcc1..0e4ecc41 100644 --- a/translations/security.nl.xlf +++ b/translations/security.nl.xlf @@ -20,4 +20,4 @@ - + \ No newline at end of file diff --git a/translations/security.uk.xlf b/translations/security.uk.xlf index 12737cf3..03be9410 100644 --- a/translations/security.uk.xlf +++ b/translations/security.uk.xlf @@ -20,4 +20,4 @@ - + \ No newline at end of file diff --git a/translations/security.zh.xlf b/translations/security.zh.xlf index 58fbb26f..181c9c0f 100644 --- a/translations/security.zh.xlf +++ b/translations/security.zh.xlf @@ -20,4 +20,4 @@ - + \ No newline at end of file diff --git a/translations/validators.en.xlf b/translations/validators.en.xlf index 624c6a89..ed824f0b 100644 --- a/translations/validators.en.xlf +++ b/translations/validators.en.xlf @@ -248,7 +248,7 @@ - + validator.invalid_gtin This is not an valid GTIN / EAN! diff --git a/translations/validators.pl.xlf b/translations/validators.pl.xlf index e5392d76..03942667 100644 --- a/translations/validators.pl.xlf +++ b/translations/validators.pl.xlf @@ -148,7 +148,7 @@ project.bom_has_to_include_all_subelement_parts - BOM projektu musi zawierać wszystkie komponenty produkcyjne pod projektów + BOM projektu musi zawierać wszystkie komponenty produkcyjne podprojektów. Brakuje komponentu %part_name% projektu %project_name%! @@ -223,12 +223,6 @@ Ze względu na ograniczenia techniczne nie jest możliwe wybranie daty po 19 stycznia 2038 w systemach 32-bitowych! - - - validator.fileSize.invalidFormat - Niewłaściwy format - - validator.invalid_range @@ -241,11 +235,5 @@ Nieprawidłowy kod. Sprawdź, czy aplikacja uwierzytelniająca jest poprawnie skonfigurowana i czy zarówno serwer, jak i urządzenie uwierzytelniające mają poprawnie ustawiony czas. - - - settings.synonyms.type_synonyms.collection_type.duplicate - Duplikuj - - - + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index e3d72ad7..e890f0fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2800,9 +2800,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001759: - version "1.0.30001770" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz#4dc47d3b263a50fbb243448034921e0a88591a84" - integrity sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw== + version "1.0.30001769" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz#1ad91594fad7dc233777c2781879ab5409f7d9c2" + integrity sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg== ccount@^2.0.0: version "2.0.1" @@ -7855,9 +7855,9 @@ webpack-sources@^2.0.1, webpack-sources@^2.2.0: source-map "^0.6.1" webpack-sources@^3.3.3: - version "3.3.4" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.4.tgz#a338b95eb484ecc75fbb196cbe8a2890618b4891" - integrity sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q== + version "3.3.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.3.tgz#d4bf7f9909675d7a070ff14d0ef2a4f3c982c723" + integrity sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg== webpack@^5.74.0: version "5.105.2"