From 2f9601364ee58c273e093eda035d878680d39bc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 10 Feb 2026 22:23:54 +0100 Subject: [PATCH] Allow to set stocktake date for part lots --- config/permissions.yaml | 3 ++ src/Form/Part/PartLotType.php | 9 ++++ src/Security/Voter/PartLotVoter.php | 4 +- .../parts/edit/edit_form_styles.html.twig | 1 + templates/parts/info/_part_lots.html.twig | 49 ++++++++++--------- translations/messages.en.xlf | 12 +++++ 6 files changed, 53 insertions(+), 25 deletions(-) diff --git a/config/permissions.yaml b/config/permissions.yaml index 0dabf9d3..39e91b57 100644 --- a/config/permissions.yaml +++ b/config/permissions.yaml @@ -68,6 +68,9 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co move: label: "perm.parts_stock.move" apiTokenRole: ROLE_API_EDIT + stocktake: + label: "perm.parts_stock.stocktake" + apiTokenRole: ROLE_API_EDIT storelocations: &PART_CONTAINING diff --git a/src/Form/Part/PartLotType.php b/src/Form/Part/PartLotType.php index 7d545340..ae86fb61 100644 --- a/src/Form/Part/PartLotType.php +++ b/src/Form/Part/PartLotType.php @@ -31,6 +31,7 @@ use App\Form\Type\StructuralEntityType; use App\Form\Type\UserSelectType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\DateTimeType; use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; @@ -110,6 +111,14 @@ class PartLotType extends AbstractType //Do not remove whitespace chars on the beginning and end of the string 'trim' => false, ]); + + $builder->add('last_stocktake_at', DateTimeType::class, [ + 'label' => 'part_lot.edit.last_stocktake_at', + 'widget' => 'single_text', + 'disabled' => !$this->security->isGranted('@parts_stock.stocktake'), + 'required' => false, + 'empty_data' => null, + ]); } public function configureOptions(OptionsResolver $resolver): void diff --git a/src/Security/Voter/PartLotVoter.php b/src/Security/Voter/PartLotVoter.php index 87c3d135..5748f4af 100644 --- a/src/Security/Voter/PartLotVoter.php +++ b/src/Security/Voter/PartLotVoter.php @@ -58,13 +58,13 @@ final class PartLotVoter extends Voter { } - protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element', 'withdraw', 'add', 'move']; + protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element', 'withdraw', 'add', 'move', 'stocktake']; protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { $user = $this->helper->resolveUser($token); - if (in_array($attribute, ['withdraw', 'add', 'move'], true)) + if (in_array($attribute, ['withdraw', 'add', 'move', 'stocktake'], true)) { $base_permission = $this->helper->isGranted($token, 'parts_stock', $attribute, $vote); diff --git a/templates/parts/edit/edit_form_styles.html.twig b/templates/parts/edit/edit_form_styles.html.twig index 1856dbff..844c8700 100644 --- a/templates/parts/edit/edit_form_styles.html.twig +++ b/templates/parts/edit/edit_form_styles.html.twig @@ -110,6 +110,7 @@ {{ form_row(form.comment) }} {{ form_row(form.owner) }} {{ form_row(form.user_barcode) }} + {{ form_row(form.last_stocktake_at) }} diff --git a/templates/parts/info/_part_lots.html.twig b/templates/parts/info/_part_lots.html.twig index 1ef25ae4..f4ee4812 100644 --- a/templates/parts/info/_part_lots.html.twig +++ b/templates/parts/info/_part_lots.html.twig @@ -19,53 +19,56 @@ {% for lot in part.partLots %} + {# @var lot App\Entity\Parts\PartLot #} {{ lot.description }} {% if lot.storageLocation %} {{ helper.structural_entity_link(lot.storageLocation) }} {% else %} - + {% trans %}part_lots.location_unknown{% endtrans %} {% endif %} {% if lot.instockUnknown %} - + {% trans %}part_lots.instock_unknown{% endtrans %} {% else %} {{ lot.amount | format_amount(part.partUnit, {'decimals': 5}) }} {% endif %} - -
- {% if lot.owner %} - + + {% if lot.owner %} + {{ helper.user_icon_link(lot.owner) }} -
- {% endif %} - {% if lot.expirationDate %} - + + {% endif %} + {% if lot.expirationDate %} + {{ lot.expirationDate | format_date() }}
- {% endif %} - {% if lot.expired %} -
- + {% endif %} + {% if lot.expired %} + {% trans %}part_lots.is_expired{% endtrans %} - {% endif %} - {% if lot.needsRefill %} -
- - - {% trans %}part_lots.need_refill{% endtrans %} - - {% endif %} -
+ {% endif %} + {% if lot.needsRefill %} + + + {% trans %}part_lots.need_refill{% endtrans %} + + {% endif %} + {% if lot.lastStocktakeAt %} + + + {{ lot.lastStocktakeAt | format_datetime("short") }} + + {% endif %}
diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 36fc10d1..e73caaf2 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -12467,5 +12467,17 @@ Buerklin-API Authentication server: 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 + +