From 62754bdbf3022634f73665eb1bd61949a52b1419 Mon Sep 17 00:00:00 2001 From: Fabian Wunsch Date: Thu, 5 Sep 2024 15:41:00 +0200 Subject: [PATCH 01/17] First order details draft --- migrations/Version20240905085300.php | 38 ++++++++++++++++++ .../Part/LessThanDesiredConstraint.php | 4 +- src/DataTables/Filters/PartFilter.php | 4 ++ src/DataTables/PartsDataTable.php | 9 +++++ src/Entity/Parts/Part.php | 34 +++++++++++++++- src/Entity/Parts/PartTraits/InstockTrait.php | 40 ++++++++++++++++++- src/Form/Filters/PartFilterType.php | 10 +++++ src/Form/Part/PartBaseType.php | 16 ++++++++ src/Serializer/PartNormalizer.php | 3 ++ .../LabelSystem/SandboxedTwigFactory.php | 2 +- templates/parts/edit/_main.html.twig | 2 + templates/parts/info/_main_infos.html.twig | 11 +++++ templates/parts/lists/_filter.html.twig | 4 +- 13 files changed, 170 insertions(+), 7 deletions(-) create mode 100644 migrations/Version20240905085300.php diff --git a/migrations/Version20240905085300.php b/migrations/Version20240905085300.php new file mode 100644 index 00000000..4c3d4571 --- /dev/null +++ b/migrations/Version20240905085300.php @@ -0,0 +1,38 @@ +addSql('ALTER TABLE parts ADD orderamount DOUBLE PRECISION NOT NULL DEFAULT 0, ADD orderDate DATETIME'); + } + + public function mySQLDown(Schema $schema): void + { + $this->addSql('ALTER TABLE `parts` DROP orderamount, DROP orderDate'); + } + + public function sqLiteUp(Schema $schema): void + { + $this->addSql('ALTER TABLE parts ADD COLUMN orderamount DOUBLE PRECISION NOT NULL DEFAULT 0'); + $this->addSql('ALTER TABLE parts ADD COLUMN orderDate DATETIME'); + } + + public function sqLiteDown(Schema $schema): void + { + $error; + // TODO: implement backwards migration for SQlite + } +} diff --git a/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php b/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php index 011824e5..164ff62e 100644 --- a/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php +++ b/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php @@ -48,9 +48,9 @@ class LessThanDesiredConstraint extends BooleanConstraint //If value is true, we want to filter for parts with stock < desired stock if ($this->value) { - $queryBuilder->andHaving( $this->property . ' < part.minamount'); + $queryBuilder->andHaving($this->property . ' + part.orderamount < part.minamount'); } else { - $queryBuilder->andHaving($this->property . ' >= part.minamount'); + $queryBuilder->andHaving($this->property . ' + part.orderamount >= part.minamount'); } } } diff --git a/src/DataTables/Filters/PartFilter.php b/src/DataTables/Filters/PartFilter.php index 8dcbd6b3..3e79ed45 100644 --- a/src/DataTables/Filters/PartFilter.php +++ b/src/DataTables/Filters/PartFilter.php @@ -59,6 +59,8 @@ class PartFilter implements FilterInterface public readonly TextConstraint $comment; public readonly TagsConstraint $tags; public readonly NumberConstraint $minAmount; + public readonly NumberConstraint $orderAmount; + public readonly DateTimeConstraint $orderDelivery; public readonly BooleanConstraint $favorite; public readonly BooleanConstraint $needsReview; public readonly NumberConstraint $mass; @@ -124,6 +126,8 @@ class PartFilter implements FilterInterface $this->lastModified = new DateTimeConstraint('part.lastModified'); $this->minAmount = new NumberConstraint('part.minamount'); + $this->orderAmount = new NumberConstraint('part.orderamount'); + $this->orderDelivery = new DateTimeConstraint('part.orderDelivery'); /* We have to use an IntConstraint here because otherwise we get just an empty result list when applying the filter This seems to be related to the fact, that PDO does not have an float parameter type and using string type does not work in this situation (at least in SQLite) TODO: Find a better solution here diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php index f0decf27..62be8b80 100644 --- a/src/DataTables/PartsDataTable.php +++ b/src/DataTables/PartsDataTable.php @@ -155,6 +155,15 @@ final class PartsDataTable implements DataTableTypeInterface 'render' => fn($value, Part $context): string => htmlspecialchars($this->amountFormatter->format($value, $context->getPartUnit())), ]) + ->add('orderamount', TextColumn::class, [ + 'label' => $this->translator->trans('part.table.orderamount'), + 'render' => fn($value, Part $context): string => htmlspecialchars($this->amountFormatter->format($value, + $context->getPartUnit())), + ]) + ->add('orderDelivery', LocaleDateTimeColumn::class, [ + 'label' => $this->translator->trans('part.table.orderDelivery'), + 'timeFormat' => 'none', + ]) ->add('partUnit', TextColumn::class, [ 'label' => $this->translator->trans('part.table.partUnit'), 'orderField' => 'NATSORT(_partUnit.name)', diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index 14a7903f..50f036c0 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -60,6 +60,7 @@ use App\Validator\Constraints\UniqueObjectCollection; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Doctrine\DBAL\Types\Types; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; @@ -101,9 +102,9 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; #[ApiFilter(LikeFilter::class, properties: ["name", "comment", "description", "ipn", "manufacturer_product_number"])] #[ApiFilter(TagFilter::class, properties: ["tags"])] #[ApiFilter(BooleanFilter::class, properties: ["favorite" , "needs_review"])] -#[ApiFilter(RangeFilter::class, properties: ["mass", "minamount"])] +#[ApiFilter(RangeFilter::class, properties: ["mass", "minamount", "orderamount"])] #[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] -#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'orderDelivery', 'addedDate', 'lastModified'])] class Part extends AttachmentContainingDBElement { use AdvancedPropertyTrait; @@ -126,6 +127,15 @@ class Part extends AttachmentContainingDBElement #[UniqueObjectCollection(fields: ['name', 'group', 'element'])] protected Collection $parameters; + /** + * @var \DateTimeInterface|null Set a time when the new order will arive. + * Set to null, if there is no known date or no order. + */ + #[Groups(['extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] + #[ORM\Column(name: 'orderDelivery', type: Types::DATETIME_MUTABLE, nullable: true)] + #[Year2038BugWorkaround] + protected ?\DateTimeInterface $orderDelivery = null; + /** ************************************************************* * Overridden properties @@ -216,6 +226,26 @@ class Part extends AttachmentContainingDBElement parent::__clone(); } + /** + * Gets the expected delivery date of the part. Returns null, if no delivery is due. + */ + public function getOrderDelivery(): ?\DateTimeInterface + { + return $this->orderDelivery; + } + + /** + * Sets the expected delivery date of the part. Set to null, if no delivery is due. + * + * + */ + public function setOrderDelivery(?\DateTimeInterface $orderDelivery): self + { + $this->orderDelivery = $orderDelivery; + + return $this; + } + #[Assert\Callback] public function validate(ExecutionContextInterface $context, $payload): void { diff --git a/src/Entity/Parts/PartTraits/InstockTrait.php b/src/Entity/Parts/PartTraits/InstockTrait.php index 08b070f3..e616b7c3 100644 --- a/src/Entity/Parts/PartTraits/InstockTrait.php +++ b/src/Entity/Parts/PartTraits/InstockTrait.php @@ -55,6 +55,14 @@ trait InstockTrait #[ORM\Column(type: Types::FLOAT)] protected float $minamount = 0; + /** + * @var float The number of already ordered units + */ + #[Assert\PositiveOrZero] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] + #[ORM\Column(type: Types::FLOAT)] + protected float $orderamount = 0; + /** * @var ?MeasurementUnit the unit in which the part's amount is measured */ @@ -137,6 +145,21 @@ trait InstockTrait return round($this->minamount); } + /** + * Get the count of parts which are already ordered. + * If an integer-based part unit is selected, the value will be rounded to integers. + * + * @return float count of parts which are already ordered + */ + public function getOrderAmount(): float + { + if ($this->useFloatAmount()) { + return $this->orderamount; + } + + return round($this->orderamount); + } + /** * Checks if this part uses the float amount . * This setting is based on the part unit (see MeasurementUnit->isInteger()). @@ -158,7 +181,7 @@ trait InstockTrait */ public function isNotEnoughInstock(): bool { - return $this->getAmountSum() < $this->getMinAmount(); + return ($this->getAmountSum() + $this->getOrderAmount()) < $this->getMinAmount(); } /** @@ -238,4 +261,19 @@ trait InstockTrait return $this; } + + /** + * Set the amount of already ordered parts. + * See getPartUnit() for the associated unit. + * + * @param float $new_orderamount the new count of parts are already ordered + * + * @return $this + */ + public function setOrderAmount(float $new_orderamount): self + { + $this->orderamount = $new_orderamount; + + return $this; + } } diff --git a/src/Form/Filters/PartFilterType.php b/src/Form/Filters/PartFilterType.php index dfe449d1..ed38e2f2 100644 --- a/src/Form/Filters/PartFilterType.php +++ b/src/Form/Filters/PartFilterType.php @@ -202,6 +202,16 @@ class PartFilterType extends AbstractType 'min' => 0, ]); + $builder->add('orderAmount', NumberConstraintType::class, [ + 'label' => 'part.edit.orderstock', + 'min' => 0, + ]); + + $builder->add('orderDelivery', DateTimeConstraintType::class, [ + 'label' => 'part.edit.orderDelivery', + 'input_type' => DateType::class, + ]); + $builder->add('lotCount', NumberConstraintType::class, [ 'label' => 'part.filter.lot_count', 'min' => 0, diff --git a/src/Form/Part/PartBaseType.php b/src/Form/Part/PartBaseType.php index 0bd3d0e3..a54a0937 100644 --- a/src/Form/Part/PartBaseType.php +++ b/src/Form/Part/PartBaseType.php @@ -45,6 +45,7 @@ use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; +use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\EnumType; use Symfony\Component\Form\Extension\Core\Type\ResetType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; @@ -96,6 +97,21 @@ class PartBaseType extends AbstractType 'label' => 'part.edit.mininstock', 'measurement_unit' => $part->getPartUnit(), ]) + ->add('orderAmount', SIUnitType::class, [ + 'attr' => [ + 'min' => 0, + 'placeholder' => 'part.editmininstock.placeholder', + ], + 'label' => 'part.edit.orderstock', + 'measurement_unit' => $part->getPartUnit(), + ]) + ->add('orderDelivery', DateType::class, [ + 'label' => 'part.edit.orderDelivery', + 'attr' => [], + 'widget' => 'single_text', + 'model_timezone' => 'UTC', + 'required' => false, + ]) ->add('category', StructuralEntityType::class, [ 'class' => Category::class, 'allow_add' => $this->security->isGranted('@categories.create'), diff --git a/src/Serializer/PartNormalizer.php b/src/Serializer/PartNormalizer.php index 775df77f..e6dc7416 100644 --- a/src/Serializer/PartNormalizer.php +++ b/src/Serializer/PartNormalizer.php @@ -136,6 +136,9 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface, Norm if (empty($data['minamount'])) { $data['minamount'] = 0.0; } + if (empty($data['orderamount'])) { + $data['orderamount'] = 0.0; + } $context[self::ALREADY_CALLED] = true; diff --git a/src/Services/LabelSystem/SandboxedTwigFactory.php b/src/Services/LabelSystem/SandboxedTwigFactory.php index d6ea6968..cfeab34b 100644 --- a/src/Services/LabelSystem/SandboxedTwigFactory.php +++ b/src/Services/LabelSystem/SandboxedTwigFactory.php @@ -133,7 +133,7 @@ final class SandboxedTwigFactory Supplier::class => ['getShippingCosts', 'getDefaultCurrency'], Part::class => ['isNeedsReview', 'getTags', 'getMass', 'getIpn', 'getProviderReference', 'getDescription', 'getComment', 'isFavorite', 'getCategory', 'getFootprint', - 'getPartLots', 'getPartUnit', 'useFloatAmount', 'getMinAmount', 'getAmountSum', 'isNotEnoughInstock', 'isAmountUnknown', 'getExpiredAmountSum', + 'getPartLots', 'getPartUnit', 'useFloatAmount', 'getMinAmount', 'getOrderAmount', 'getOrderDate', 'getAmountSum', 'isNotEnoughInstock', 'isAmountUnknown', 'getExpiredAmountSum', 'getManufacturerProductUrl', 'getCustomProductURL', 'getManufacturingStatus', 'getManufacturer', 'getManufacturerProductNumber', 'getOrderdetails', 'isObsolete', 'getParameters', 'getGroupedParameters', diff --git a/templates/parts/edit/_main.html.twig b/templates/parts/edit/_main.html.twig index f153d878..22a0c564 100644 --- a/templates/parts/edit/_main.html.twig +++ b/templates/parts/edit/_main.html.twig @@ -10,6 +10,8 @@ {{ form_row(form.category) }} {{ form_row(form.tags) }} {{ form_row(form.minAmount) }} +{{ form_row(form.orderAmount) }} +{{ form_row(form.orderDate) }} {{ form_row(form.footprint) }} diff --git a/templates/parts/info/_main_infos.html.twig b/templates/parts/info/_main_infos.html.twig index bced5624..7273e8f6 100644 --- a/templates/parts/info/_main_infos.html.twig +++ b/templates/parts/info/_main_infos.html.twig @@ -76,6 +76,17 @@ {% if part.expiredAmountSum > 0 %} (+{{ part.expiredAmountSum }}) {% endif %} + {% if part.orderAmount > 0 %} + (+ + {{ part.orderAmount | format_amount(part.partUnit) }} + {% if part.orderDate %} + @ + + {{ part.orderDate | format_date() }}
+
+ {% endif %} + ) + {% endif %} / {{ part.minAmount | format_amount(part.partUnit) }} diff --git a/templates/parts/lists/_filter.html.twig b/templates/parts/lists/_filter.html.twig index c29e8ecd..f578f0df 100644 --- a/templates/parts/lists/_filter.html.twig +++ b/templates/parts/lists/_filter.html.twig @@ -66,6 +66,8 @@
{{ form_row(filterForm.storelocation) }} {{ form_row(filterForm.minAmount) }} + {{ form_row(filterForm.orderAmount) }} + {{ form_row(filterForm.orderDelivery) }} {{ form_row(filterForm.amountSum) }} {{ form_row(filterForm.lessThanDesired) }} {{ form_row(filterForm.lotCount) }} @@ -150,4 +152,4 @@ {{ form_end(filterForm) }}
- \ No newline at end of file + From 2f7fb397d2598e87cc3ac74df3eb0125fdf6787f Mon Sep 17 00:00:00 2001 From: Fabian Wunsch Date: Thu, 5 Sep 2024 16:01:27 +0200 Subject: [PATCH 02/17] fixed typos --- migrations/Version20240905085300.php | 6 +++--- src/Services/LabelSystem/SandboxedTwigFactory.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/migrations/Version20240905085300.php b/migrations/Version20240905085300.php index 4c3d4571..834444ae 100644 --- a/migrations/Version20240905085300.php +++ b/migrations/Version20240905085300.php @@ -16,18 +16,18 @@ final class Version20240905085300 extends AbstractMultiPlatformMigration public function mySQLUp(Schema $schema): void { - $this->addSql('ALTER TABLE parts ADD orderamount DOUBLE PRECISION NOT NULL DEFAULT 0, ADD orderDate DATETIME'); + $this->addSql('ALTER TABLE parts ADD orderamount DOUBLE PRECISION NOT NULL DEFAULT 0, ADD orderDelivery DATETIME'); } public function mySQLDown(Schema $schema): void { - $this->addSql('ALTER TABLE `parts` DROP orderamount, DROP orderDate'); + $this->addSql('ALTER TABLE `parts` DROP orderamount, DROP orderDelivery'); } public function sqLiteUp(Schema $schema): void { $this->addSql('ALTER TABLE parts ADD COLUMN orderamount DOUBLE PRECISION NOT NULL DEFAULT 0'); - $this->addSql('ALTER TABLE parts ADD COLUMN orderDate DATETIME'); + $this->addSql('ALTER TABLE parts ADD COLUMN orderDelivery DATETIME'); } public function sqLiteDown(Schema $schema): void diff --git a/src/Services/LabelSystem/SandboxedTwigFactory.php b/src/Services/LabelSystem/SandboxedTwigFactory.php index cfeab34b..32b50e06 100644 --- a/src/Services/LabelSystem/SandboxedTwigFactory.php +++ b/src/Services/LabelSystem/SandboxedTwigFactory.php @@ -133,7 +133,7 @@ final class SandboxedTwigFactory Supplier::class => ['getShippingCosts', 'getDefaultCurrency'], Part::class => ['isNeedsReview', 'getTags', 'getMass', 'getIpn', 'getProviderReference', 'getDescription', 'getComment', 'isFavorite', 'getCategory', 'getFootprint', - 'getPartLots', 'getPartUnit', 'useFloatAmount', 'getMinAmount', 'getOrderAmount', 'getOrderDate', 'getAmountSum', 'isNotEnoughInstock', 'isAmountUnknown', 'getExpiredAmountSum', + 'getPartLots', 'getPartUnit', 'useFloatAmount', 'getMinAmount', 'getOrderAmount', 'getOrderDelivery', 'getAmountSum', 'isNotEnoughInstock', 'isAmountUnknown', 'getExpiredAmountSum', 'getManufacturerProductUrl', 'getCustomProductURL', 'getManufacturingStatus', 'getManufacturer', 'getManufacturerProductNumber', 'getOrderdetails', 'isObsolete', 'getParameters', 'getGroupedParameters', From 71102f0ad971ad11542a5e6eed2b85b49b773940 Mon Sep 17 00:00:00 2001 From: Fabian Wunsch Date: Thu, 5 Sep 2024 16:03:34 +0200 Subject: [PATCH 03/17] Fixed typos --- templates/parts/edit/_main.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/parts/edit/_main.html.twig b/templates/parts/edit/_main.html.twig index 22a0c564..8fcdfe86 100644 --- a/templates/parts/edit/_main.html.twig +++ b/templates/parts/edit/_main.html.twig @@ -11,7 +11,7 @@ {{ form_row(form.tags) }} {{ form_row(form.minAmount) }} {{ form_row(form.orderAmount) }} -{{ form_row(form.orderDate) }} +{{ form_row(form.orderDelivery) }} {{ form_row(form.footprint) }} From ec7f79369d5028ee45c2eaa19707aba47a7dddac Mon Sep 17 00:00:00 2001 From: Fabian Wunsch Date: Mon, 15 Sep 2025 14:03:58 +0200 Subject: [PATCH 04/17] Fix migration issues --- migrations/Version20240905085300.php | 10 ++++++++++ templates/parts/info/_main_infos.html.twig | 6 +++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/migrations/Version20240905085300.php b/migrations/Version20240905085300.php index 834444ae..e29d967b 100644 --- a/migrations/Version20240905085300.php +++ b/migrations/Version20240905085300.php @@ -24,6 +24,16 @@ final class Version20240905085300 extends AbstractMultiPlatformMigration $this->addSql('ALTER TABLE `parts` DROP orderamount, DROP orderDelivery'); } + public function postgreSQLUp(Schema $schema): void + { + $this->addSql('ALTER TABLE parts ADD orderamount DOUBLE PRECISION NOT NULL DEFAULT 0, ADD orderDelivery timestamp'); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->addSql('ALTER TABLE parts DROP orderamount, DROP orderDelivery'); + } + public function sqLiteUp(Schema $schema): void { $this->addSql('ALTER TABLE parts ADD COLUMN orderamount DOUBLE PRECISION NOT NULL DEFAULT 0'); diff --git a/templates/parts/info/_main_infos.html.twig b/templates/parts/info/_main_infos.html.twig index 7273e8f6..f30b7407 100644 --- a/templates/parts/info/_main_infos.html.twig +++ b/templates/parts/info/_main_infos.html.twig @@ -79,10 +79,10 @@ {% if part.orderAmount > 0 %} (+ {{ part.orderAmount | format_amount(part.partUnit) }} - {% if part.orderDate %} + {% if part.orderDelivery %} @ - - {{ part.orderDate | format_date() }}
+ + {{ part.orderDelivery | format_date() }}
{% endif %} ) From b62d4aaeb734d3ddd0d7ce4e2da5f4c6a3c78863 Mon Sep 17 00:00:00 2001 From: Fabian Wunsch Date: Mon, 15 Sep 2025 14:38:19 +0200 Subject: [PATCH 05/17] Add translations --- translations/messages.de.xlf | 24 ++++++++++++++++++++++++ translations/messages.en.xlf | 24 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/translations/messages.de.xlf b/translations/messages.de.xlf index 9fb3f6ef..ec4672a9 100644 --- a/translations/messages.de.xlf +++ b/translations/messages.de.xlf @@ -4820,6 +4820,18 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr Min. Menge + + + part.table.orderamount + Bestellte Menge + + + + + part.table.orderDelivery + Lieferdatum + + Part-DB1\src\DataTables\PartsDataTable.php:232 @@ -5584,6 +5596,18 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr Mindestbestand + + + part.edit.orderstock + Bestellte Menge + + + + + part.edit.orderDelivery + Lieferdatum + + Part-DB1\src\Form\Part\PartBaseType.php:129 diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index be1e6348..94e28689 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -2345,6 +2345,18 @@ Sub elements will be moved upwards. Minimum amount + + + part.table.orderamount + Ordered amount + + + + + part.table.orderDelivery + Delivery date + + Part-DB1\templates\Parts\info\_order_infos.html.twig:29 @@ -5585,6 +5597,18 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Minimum stock + + + part.edit.orderstock + Ordered amount + + + + + part.edit.orderDelivery + Delivery date + + Part-DB1\src\Form\Part\PartBaseType.php:129 From e6b1b533a877b0fbfc6e37396048c6b1bb15446d Mon Sep 17 00:00:00 2001 From: Fabian Wunsch Date: Tue, 16 Sep 2025 08:21:55 +0200 Subject: [PATCH 06/17] Add a button to automatically add the ordered amount once delivered --- src/Controller/PartController.php | 21 +++++++++++++++++++++ src/Services/EntityURLGenerator.php | 6 ++++++ templates/parts/info/_tools.html.twig | 8 ++++++++ translations/messages.de.xlf | 6 ++++++ translations/messages.en.xlf | 6 ++++++ 5 files changed, 47 insertions(+) diff --git a/src/Controller/PartController.php b/src/Controller/PartController.php index 6708ed4c..b9f6c775 100644 --- a/src/Controller/PartController.php +++ b/src/Controller/PartController.php @@ -135,6 +135,27 @@ class PartController extends AbstractController return $this->renderPartForm('edit', $request, $part); } + #[Route(path: '/{id}/delivered', name: 'part_delivered')] + public function delivered(Part $part, Request $request): Response + { + $this->denyAccessUnlessGranted('edit', $part); + + $partLot = $part->getPartLots()[0] ?? null; + if (!$partLot instanceof PartLot) { + $this->addFlash('error', 'part.delivered.error.no_lot'); + return $this->redirectToRoute('part_info', ['id' => $part->getID()]); + } + + $partLot->setAmount($partLot->getAmount() + $part->getOrderAmount()); + $part->setOrderAmount(0); + $part->setOrderDelivery(null); + + $this->em->persist($part); + $this->em->flush(); + + return $this->redirectToRoute('part_info', ['id' => $part->getID()]); + } + #[Route(path: '/{id}/delete', name: 'part_delete', methods: ['DELETE'])] public function delete(Request $request, Part $part): RedirectResponse { diff --git a/src/Services/EntityURLGenerator.php b/src/Services/EntityURLGenerator.php index 78db06f0..4a2d6425 100644 --- a/src/Services/EntityURLGenerator.php +++ b/src/Services/EntityURLGenerator.php @@ -83,6 +83,7 @@ class EntityURLGenerator 'delete' => $this->deleteURL($entity), 'file_download' => $this->downloadURL($entity), 'file_view' => $this->viewURL($entity), + 'delivered' => $this->deliveredURL($entity), default => throw new InvalidArgumentException('Method is not supported!'), }; } @@ -169,6 +170,11 @@ class EntityURLGenerator throw new \RuntimeException('Attachment has no internal nor external path!'); } + public function deliveredURL(Part $entity): string + { + return $this->urlGenerator->generate('part_delivered', ['id' => $entity->getID()]); + } + public function downloadURL($entity): string { if (!($entity instanceof Attachment)) { diff --git a/templates/parts/info/_tools.html.twig b/templates/parts/info/_tools.html.twig index 455d51b7..0c9fe7cb 100644 --- a/templates/parts/info/_tools.html.twig +++ b/templates/parts/info/_tools.html.twig @@ -39,6 +39,14 @@ {% endif %} +{% if is_granted('edit', part) %} +
+ + + {% trans %}part.delivered.btn{% endtrans %} + +{% endif %} +
Bauteil aus Informationsquelle aktualisieren + + + part.delivered.btn + Bestellte Menge wurde geliefert + + info_providers.update_part.title diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 94e28689..7488bee9 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -12023,6 +12023,12 @@ Please note, that you can not impersonate a disabled user. If you try you will g Update part from info providers + + + part.delivered.btn + Ordered amount has been delivered + + info_providers.update_part.title From 2e32bdd0aff4675324d3bd50a61f1f7268adbe15 Mon Sep 17 00:00:00 2001 From: Fabian Wunsch Date: Mon, 6 Oct 2025 07:46:11 +0200 Subject: [PATCH 07/17] Fix less than desired filter --- .../Filters/Constraints/Part/LessThanDesiredConstraint.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php b/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php index 164ff62e..6e824b66 100644 --- a/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php +++ b/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php @@ -31,7 +31,7 @@ class LessThanDesiredConstraint extends BooleanConstraint public function __construct(?string $property = null, ?string $identifier = null, ?bool $default_value = null) { parent::__construct($property ?? '( - SELECT COALESCE(SUM(ld_partLot.amount), 0.0) + SELECT COALESCE(SUM(ld_partLot.amount) + part.orderamount, 0.0) FROM '.PartLot::class.' ld_partLot WHERE ld_partLot.part = part.id AND ld_partLot.instock_unknown = false @@ -48,9 +48,9 @@ class LessThanDesiredConstraint extends BooleanConstraint //If value is true, we want to filter for parts with stock < desired stock if ($this->value) { - $queryBuilder->andHaving($this->property . ' + part.orderamount < part.minamount'); + $queryBuilder->andHaving($this->property . ' < part.minamount'); } else { - $queryBuilder->andHaving($this->property . ' + part.orderamount >= part.minamount'); + $queryBuilder->andHaving($this->property . ' >= part.minamount'); } } } From 47cbf6c2ddc76ecb13b2e30ff01aa12db4abf5d6 Mon Sep 17 00:00:00 2001 From: Fabian Wunsch Date: Tue, 11 Nov 2025 14:49:15 +0100 Subject: [PATCH 08/17] Move logic to AdvancedPropertyTrait.php --- src/Entity/Parts/Part.php | 30 ------------------ .../PartTraits/AdvancedPropertyTrait.php | 31 +++++++++++++++++++ 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index d3c6ffdf..4a503ccd 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -138,16 +138,6 @@ class Part extends AttachmentContainingDBElement #[UniqueObjectCollection(fields: ['name', 'group', 'element'])] protected Collection $parameters; - /** - * @var \DateTimeInterface|null Set a time when the new order will arive. - * Set to null, if there is no known date or no order. - */ - #[Groups(['extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] - #[ORM\Column(name: 'orderDelivery', type: Types::DATETIME_MUTABLE, nullable: true)] - #[Year2038BugWorkaround] - protected ?\DateTimeInterface $orderDelivery = null; - - /** ************************************************************* * Overridden properties * (They are defined here and not in a trait, to avoid conflicts). @@ -244,26 +234,6 @@ class Part extends AttachmentContainingDBElement parent::__clone(); } - /** - * Gets the expected delivery date of the part. Returns null, if no delivery is due. - */ - public function getOrderDelivery(): ?\DateTimeInterface - { - return $this->orderDelivery; - } - - /** - * Sets the expected delivery date of the part. Set to null, if no delivery is due. - * - * - */ - public function setOrderDelivery(?\DateTimeInterface $orderDelivery): self - { - $this->orderDelivery = $orderDelivery; - - return $this; - } - #[Assert\Callback] public function validate(ExecutionContextInterface $context, $payload): void { diff --git a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php index 230ba7b7..a502e49e 100644 --- a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php +++ b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Entity\Parts\PartTraits; use App\Entity\Parts\InfoProviderReference; +use App\Validator\Constraints\Year2038BugWorkaround; use Doctrine\DBAL\Types\Types; use App\Entity\Parts\Part; use Doctrine\ORM\Mapping as ORM; @@ -57,6 +58,15 @@ trait AdvancedPropertyTrait #[ORM\Column(type: Types::FLOAT, nullable: true)] protected ?float $mass = null; + /** + * @var \DateTimeInterface|null Set a time when the new order will arive. + * Set to null, if there is no known date or no order. + */ + #[Groups(['extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] + #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] + #[Year2038BugWorkaround] + protected ?\DateTimeInterface $orderDelivery = null; + /** * @var string|null The internal part number of the part */ @@ -141,6 +151,27 @@ trait AdvancedPropertyTrait return $this; } + /** + * Gets the expected delivery date of the part. Returns null, if no delivery is due. + */ + public function getOrderDelivery(): ?\DateTimeInterface + { + return $this->orderDelivery; + } + + /** + * Sets the expected delivery date of the part. Set to null, if no delivery is due. + * + * @param \DateTimeInterface|null $orderDelivery The new delivery date + * + * @return $this + */ + public function setOrderDelivery(?\DateTimeInterface $orderDelivery): self + { + $this->orderDelivery = $orderDelivery; + return $this; + } + /** * Returns the internal part number of the part. * @return string From 571c58675ff51ce782f318bc59c5333bc94ead65 Mon Sep 17 00:00:00 2001 From: Fabian Wunsch Date: Thu, 13 Nov 2025 14:19:50 +0100 Subject: [PATCH 09/17] Fix order_delivery --- src/DataTables/Filters/PartFilter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataTables/Filters/PartFilter.php b/src/DataTables/Filters/PartFilter.php index 7e6db49c..d1c99720 100644 --- a/src/DataTables/Filters/PartFilter.php +++ b/src/DataTables/Filters/PartFilter.php @@ -138,7 +138,7 @@ class PartFilter implements FilterInterface $this->minAmount = new NumberConstraint('part.minamount'); $this->orderAmount = new NumberConstraint('part.orderamount'); - $this->orderDelivery = new DateTimeConstraint('part.orderDelivery'); + $this->orderDelivery = new DateTimeConstraint('part.order_delivery'); /* We have to use an IntConstraint here because otherwise we get just an empty result list when applying the filter This seems to be related to the fact, that PDO does not have an float parameter type and using string type does not work in this situation (at least in SQLite) TODO: Find a better solution here From 108d331a544690d9e2540a5d6ad2fd25096ae5d5 Mon Sep 17 00:00:00 2001 From: Fabian Wunsch Date: Thu, 13 Nov 2025 14:23:04 +0100 Subject: [PATCH 10/17] Revert "Fix order_delivery" This reverts commit 571c58675ff51ce782f318bc59c5333bc94ead65. --- src/DataTables/Filters/PartFilter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataTables/Filters/PartFilter.php b/src/DataTables/Filters/PartFilter.php index d1c99720..7e6db49c 100644 --- a/src/DataTables/Filters/PartFilter.php +++ b/src/DataTables/Filters/PartFilter.php @@ -138,7 +138,7 @@ class PartFilter implements FilterInterface $this->minAmount = new NumberConstraint('part.minamount'); $this->orderAmount = new NumberConstraint('part.orderamount'); - $this->orderDelivery = new DateTimeConstraint('part.order_delivery'); + $this->orderDelivery = new DateTimeConstraint('part.orderDelivery'); /* We have to use an IntConstraint here because otherwise we get just an empty result list when applying the filter This seems to be related to the fact, that PDO does not have an float parameter type and using string type does not work in this situation (at least in SQLite) TODO: Find a better solution here From dd2783cb7aa968478f867bea9a8213a5dcc685ec Mon Sep 17 00:00:00 2001 From: Fabian Wunsch Date: Tue, 23 Jun 2026 08:36:43 +0200 Subject: [PATCH 11/17] add minamound and orderamount to bom table --- src/DataTables/ProjectBomEntriesDataTable.php | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php index b5beeca0..2f0766ee 100644 --- a/src/DataTables/ProjectBomEntriesDataTable.php +++ b/src/DataTables/ProjectBomEntriesDataTable.php @@ -195,6 +195,30 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface return ''; } ]) + ->add('minAmount', TextColumn::class, [ + 'label' => $this->translator->trans('part.table.minamount'), + 'visible' => false, + 'orderField' => 'part.minamount', + 'render' => function ($value, ProjectBOMEntry $context): string { + if (!$context->getPart() instanceof Part) { + return ''; + } + + return htmlspecialchars($this->amountFormatter->format($context->getPart()->getMinAmount(), $context->getPart()->getPartUnit())); + }, + ]) + ->add('orderAmount', TextColumn::class, [ + 'label' => $this->translator->trans('part.table.orderamount'), + 'visible' => false, + 'orderField' => 'part.orderamount', + 'render' => function ($value, ProjectBOMEntry $context): string { + if (!$context->getPart() instanceof Part) { + return ''; + } + + return htmlspecialchars($this->amountFormatter->format($context->getPart()->getOrderAmount(), $context->getPart()->getPartUnit())); + }, + ]) ->add('storelocation', TextColumn::class, [ 'label' => $this->translator->trans('part.table.storeLocations'), //We need to use a aggregate function to get the first store location, as we have a one-to-many relation From 59acbc02b809664382330efe8d5ee30c50e89d4e Mon Sep 17 00:00:00 2001 From: Fabian Wunsch Date: Tue, 23 Jun 2026 08:54:24 +0200 Subject: [PATCH 12/17] also add suppliers --- src/DataTables/ProjectBomEntriesDataTable.php | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php index 651e3d46..cf2d8712 100644 --- a/src/DataTables/ProjectBomEntriesDataTable.php +++ b/src/DataTables/ProjectBomEntriesDataTable.php @@ -159,6 +159,27 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac 'label' => $this->translator->trans('part.table.manufacturer'), 'orderField' => 'NATSORT(manufacturer.name)' ]) + ->add('supplier', TextColumn::class, [ + 'label' => $this->translator->trans('supplier.label'), + 'visible' => false, + // Use an aggregate because a part can have multiple supplier orderdetails. + 'orderField' => 'NATSORT(MIN(_suppliers.name))', + 'data' => function (ProjectBOMEntry $context): string { + if (!$context->getPart() instanceof Part) { + return ''; + } + + $supplierNames = []; + foreach ($context->getPart()->getOrderdetails(true) as $orderdetail) { + $supplierName = trim((string) $orderdetail->getSupplier()->getName()); + if ($supplierName !== '') { + $supplierNames[$supplierName] = true; + } + } + + return implode(', ', array_keys($supplierNames)); + }, + ]) ->add('manufacturing_status', EnumColumn::class, [ 'label' => $this->translator->trans('part.table.manufacturingStatus'), @@ -296,6 +317,8 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac ->leftJoin('_partLots.storage_location', '_storelocations') ->leftJoin('part.footprint', 'footprint') ->leftJoin('part.manufacturer', 'manufacturer') + ->leftJoin('part.orderdetails', '_orderdetails') + ->leftJoin('_orderdetails.supplier', '_suppliers') ->leftJoin('part.partCustomState', 'partCustomState') ->where('bom_entry.project = :project') ->setParameter('project', $options['project']) @@ -323,6 +346,8 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac ->addSelect('storelocations') ->addSelect('footprint') ->addSelect('manufacturer') + ->addSelect('orderdetails') + ->addSelect('suppliers') ->addSelect('partCustomState') ->from(ProjectBOMEntry::class, 'bom_entry') ->leftJoin('bom_entry.part', 'part') @@ -331,6 +356,8 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac ->leftJoin('partLots.storage_location', 'storelocations') ->leftJoin('part.footprint', 'footprint') ->leftJoin('part.manufacturer', 'manufacturer') + ->leftJoin('part.orderdetails', 'orderdetails') + ->leftJoin('orderdetails.supplier', 'suppliers') ->leftJoin('part.partCustomState', 'partCustomState') ->where('bom_entry.id IN (:ids)') ->setParameter('ids', $ids) @@ -341,6 +368,8 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac ->addGroupBy('storelocations') ->addGroupBy('footprint') ->addGroupBy('manufacturer') + ->addGroupBy('orderdetails') + ->addGroupBy('suppliers') ->addGroupBy('partCustomState') ->setHint(Query::HINT_READ_ONLY, true) From b0e3664d3fe0fa2934b1de4a3d931d4714f97963 Mon Sep 17 00:00:00 2001 From: Fabian Wunsch Date: Tue, 23 Jun 2026 08:59:20 +0200 Subject: [PATCH 13/17] make suppliers klickable --- src/DataTables/ProjectBomEntriesDataTable.php | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php index cf2d8712..ed3f73fb 100644 --- a/src/DataTables/ProjectBomEntriesDataTable.php +++ b/src/DataTables/ProjectBomEntriesDataTable.php @@ -159,7 +159,7 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac 'label' => $this->translator->trans('part.table.manufacturer'), 'orderField' => 'NATSORT(manufacturer.name)' ]) - ->add('supplier', TextColumn::class, [ + ->add('supplier', HTMLColumn::class, [ 'label' => $this->translator->trans('supplier.label'), 'visible' => false, // Use an aggregate because a part can have multiple supplier orderdetails. @@ -169,15 +169,27 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac return ''; } - $supplierNames = []; + $supplierLinks = []; foreach ($context->getPart()->getOrderdetails(true) as $orderdetail) { - $supplierName = trim((string) $orderdetail->getSupplier()->getName()); - if ($supplierName !== '') { - $supplierNames[$supplierName] = true; + $supplier = $orderdetail->getSupplier(); + $supplierName = trim((string) $supplier->getName()); + if ($supplierName === '') { + continue; } + + $supplierId = $supplier->getId(); + if (isset($supplierLinks[$supplierId])) { + continue; + } + + $supplierLinks[$supplierId] = sprintf( + '%s', + htmlspecialchars($this->entityURLGenerator->listPartsURL($supplier)), + htmlspecialchars($supplierName) + ); } - return implode(', ', array_keys($supplierNames)); + return implode(', ', $supplierLinks); }, ]) From 17bf616dbe8ce45e5d92e434379bf959a1d641c0 Mon Sep 17 00:00:00 2001 From: Fabian Wunsch Date: Tue, 23 Jun 2026 09:05:14 +0200 Subject: [PATCH 14/17] make columns visible by default --- src/DataTables/ProjectBomEntriesDataTable.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php index ed3f73fb..d9d75a00 100644 --- a/src/DataTables/ProjectBomEntriesDataTable.php +++ b/src/DataTables/ProjectBomEntriesDataTable.php @@ -161,7 +161,7 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac ]) ->add('supplier', HTMLColumn::class, [ 'label' => $this->translator->trans('supplier.label'), - 'visible' => false, + 'visible' => true, // Use an aggregate because a part can have multiple supplier orderdetails. 'orderField' => 'NATSORT(MIN(_suppliers.name))', 'data' => function (ProjectBOMEntry $context): string { @@ -221,7 +221,7 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac ->add('instockAmount', HTMLColumn::class, [ 'label' => 'project.bom.instockAmount', - 'visible' => false, + 'visible' => true, 'data' => function (ProjectBOMEntry $context) { if ($context->getPart() !== null) { return $this->partDataTableHelper->renderAmount($context->getPart()); @@ -232,7 +232,7 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac ]) ->add('minAmount', HTMLColumn::class, [ 'label' => $this->translator->trans('part.table.minamount'), - 'visible' => false, + 'visible' => true, 'orderField' => 'part.minamount', 'data' => function (ProjectBOMEntry $context): string { if (!$context->getPart() instanceof Part) { @@ -244,7 +244,7 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac ]) ->add('orderAmount', HTMLColumn::class, [ 'label' => $this->translator->trans('part.table.orderamount'), - 'visible' => false, + 'visible' => true, 'orderField' => 'part.orderamount', 'data' => function (ProjectBOMEntry $context): string { if (!$context->getPart() instanceof Part) { From a25042fa877fec4a1b0698af02c1853de95116f4 Mon Sep 17 00:00:00 2001 From: Fabian Wunsch Date: Tue, 23 Jun 2026 09:09:53 +0200 Subject: [PATCH 15/17] change default bom columns --- src/DataTables/ProjectBomEntriesDataTable.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php index d9d75a00..4fb23c48 100644 --- a/src/DataTables/ProjectBomEntriesDataTable.php +++ b/src/DataTables/ProjectBomEntriesDataTable.php @@ -102,7 +102,7 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac ]) ->add('partId', TextColumn::class, [ 'label' => $this->translator->trans('project.bom.part_id'), - 'visible' => true, + 'visible' => false, 'orderField' => 'part.id', 'data' => function (ProjectBOMEntry $context) { return $context->getPart() instanceof Part ? (string) $context->getPart()->getId() : ''; @@ -150,6 +150,7 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac ]) ->add('footprint', EntityColumn::class, [ 'property' => 'part.footprint', + 'visible' => false, 'label' => $this->translator->trans('part.table.footprint'), 'orderField' => 'NATSORT(footprint.name)' ]) @@ -209,6 +210,7 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac ->add('mountnames', HTMLColumn::class, [ 'label' => 'project.bom.mountnames', + 'visible' => false, 'data' => function (ProjectBOMEntry $context) { $html = ''; From 24146763a91477baaf46f0f7c0965ef445a06269 Mon Sep 17 00:00:00 2001 From: Fabian Wunsch Date: Thu, 9 Jul 2026 09:57:27 +0200 Subject: [PATCH 16/17] add tags column --- src/DataTables/ProjectBomEntriesDataTable.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php index 4fb23c48..e340d050 100644 --- a/src/DataTables/ProjectBomEntriesDataTable.php +++ b/src/DataTables/ProjectBomEntriesDataTable.php @@ -207,6 +207,9 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac return $this->translator->trans($status->toTranslationKey()); }, ]) + ->add('tags', TagsColumn::class, [ + 'label' => $this->translator->trans('part.table.tags'), + ]) ->add('mountnames', HTMLColumn::class, [ 'label' => 'project.bom.mountnames', From a34df4c6493a539897c5a3a2fc0b3a188898b74d Mon Sep 17 00:00:00 2001 From: Fabian Wunsch Date: Thu, 9 Jul 2026 10:35:29 +0200 Subject: [PATCH 17/17] fix tags column --- src/DataTables/ProjectBomEntriesDataTable.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php index e340d050..714c6622 100644 --- a/src/DataTables/ProjectBomEntriesDataTable.php +++ b/src/DataTables/ProjectBomEntriesDataTable.php @@ -28,6 +28,7 @@ use App\DataTables\Column\EnumColumn; use App\DataTables\Column\HTMLColumn; use App\DataTables\Column\LocaleDateTimeColumn; use App\DataTables\Column\MarkdownColumn; +use App\DataTables\Column\TagsColumn; use App\DataTables\Helpers\PartDataTableHelper; use App\Doctrine\Helpers\FieldHelper; use App\Entity\Parts\ManufacturingStatus; @@ -209,6 +210,7 @@ final readonly class ProjectBomEntriesDataTable implements DataTableTypeInterfac ]) ->add('tags', TagsColumn::class, [ 'label' => $this->translator->trans('part.table.tags'), + 'data' => static fn (ProjectBOMEntry $context): string => $context->getPart()?->getTags() ?? '', ]) ->add('mountnames', HTMLColumn::class, [