diff --git a/assets/controllers/pages/part_stocktake_modal_controller.js b/assets/controllers/pages/part_stocktake_modal_controller.js deleted file mode 100644 index 7aef2906..00000000 --- a/assets/controllers/pages/part_stocktake_modal_controller.js +++ /dev/null @@ -1,27 +0,0 @@ -import {Controller} from "@hotwired/stimulus"; -import {Modal} from "bootstrap"; - -export default class extends Controller -{ - connect() { - this.element.addEventListener('show.bs.modal', event => this._handleModalOpen(event)); - } - - _handleModalOpen(event) { - // Button that triggered the modal - const button = event.relatedTarget; - - const amountInput = this.element.querySelector('input[name="amount"]'); - - // Extract info from button attributes - const lotID = button.getAttribute('data-lot-id'); - const lotAmount = button.getAttribute('data-lot-amount'); - - //Find the expected amount field and set the value to the lot amount - const expectedAmountInput = this.element.querySelector('#stocktake-modal-expected-amount'); - expectedAmountInput.textContent = lotAmount; - - //Set the action and lotID inputs in the form - this.element.querySelector('input[name="lot_id"]').setAttribute('value', lotID); - } -} diff --git a/config/permissions.yaml b/config/permissions.yaml index 39e91b57..0dabf9d3 100644 --- a/config/permissions.yaml +++ b/config/permissions.yaml @@ -68,9 +68,6 @@ 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/Controller/PartController.php b/src/Controller/PartController.php index d9fcd7f1..ef2bae5f 100644 --- a/src/Controller/PartController.php +++ b/src/Controller/PartController.php @@ -54,14 +54,12 @@ use Exception; use Omines\DataTablesBundle\DataTableFactory; use Symfony\Bridge\Doctrine\Attribute\MapEntity; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Core\Exception\AccessDeniedException; -use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid; use Symfony\Contracts\Translation\TranslatorInterface; use function Symfony\Component\Translation\t; @@ -465,54 +463,6 @@ final class PartController extends AbstractController ); } - #[Route(path: '/{id}/stocktake', name: 'part_stocktake', methods: ['POST'])] - #[IsCsrfTokenValid(new Expression("'part_stocktake-' ~ args['part'].getid()"), '_token')] - public function stocktakeHandler(Part $part, EntityManagerInterface $em, PartLotWithdrawAddHelper $withdrawAddHelper, - Request $request, - ): Response - { - $partLot = $em->find(PartLot::class, $request->request->get('lot_id')); - - //Check that the user is allowed to stocktake the partlot - $this->denyAccessUnlessGranted('stocktake', $partLot); - - if (!$partLot instanceof PartLot) { - throw new \RuntimeException('Part lot not found!'); - } - //Ensure that the partlot belongs to the part - if ($partLot->getPart() !== $part) { - throw new \RuntimeException("The origin partlot does not belong to the part!"); - } - - $actualAmount = (float) $request->request->get('actual_amount'); - $comment = $request->request->get('comment'); - - $timestamp = null; - $timestamp_str = $request->request->getString('timestamp', ''); - //Try to parse the timestamp - if ($timestamp_str !== '') { - $timestamp = new DateTime($timestamp_str); - } - - $withdrawAddHelper->stocktake($partLot, $actualAmount, $comment, $timestamp); - - //Ensure that the timestamp is not in the future - if ($timestamp !== null && $timestamp > new DateTime("+20min")) { - throw new \LogicException("The timestamp must not be in the future!"); - } - - //Save the changes to the DB - $em->flush(); - $this->addFlash('success', 'part.withdraw.success'); - - //If a redirect was passed, then redirect there - if ($request->request->get('_redirect')) { - return $this->redirect($request->request->get('_redirect')); - } - //Otherwise just redirect to the part page - return $this->redirectToRoute('part_info', ['id' => $part->getID()]); - } - #[Route(path: '/{id}/add_withdraw', name: 'part_add_withdraw', methods: ['POST'])] public function withdrawAddHandler(Part $part, Request $request, EntityManagerInterface $em, PartLotWithdrawAddHelper $withdrawAddHelper): Response { diff --git a/src/Entity/LogSystem/PartStockChangeType.php b/src/Entity/LogSystem/PartStockChangeType.php index 79e4c6da..f69fe95f 100644 --- a/src/Entity/LogSystem/PartStockChangeType.php +++ b/src/Entity/LogSystem/PartStockChangeType.php @@ -28,8 +28,6 @@ enum PartStockChangeType: string case WITHDRAW = "withdraw"; case MOVE = "move"; - case STOCKTAKE = "stock_take"; - /** * Converts the type to a short representation usable in the extra field of the log entry. * @return string @@ -40,7 +38,6 @@ enum PartStockChangeType: string self::ADD => 'a', self::WITHDRAW => 'w', self::MOVE => 'm', - self::STOCKTAKE => 's', }; } @@ -55,7 +52,6 @@ enum PartStockChangeType: string 'a' => self::ADD, 'w' => self::WITHDRAW, 'm' => self::MOVE, - 's' => self::STOCKTAKE, default => throw new \InvalidArgumentException("Invalid short type: $value"), }; } diff --git a/src/Entity/LogSystem/PartStockChangedLogEntry.php b/src/Entity/LogSystem/PartStockChangedLogEntry.php index a46f2ecf..1bac9e9f 100644 --- a/src/Entity/LogSystem/PartStockChangedLogEntry.php +++ b/src/Entity/LogSystem/PartStockChangedLogEntry.php @@ -122,11 +122,6 @@ class PartStockChangedLogEntry extends AbstractLogEntry return new self(PartStockChangeType::MOVE, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment, $move_to_target, action_timestamp: $action_timestamp); } - public static function stocktake(PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment, ?\DateTimeInterface $action_timestamp = null): self - { - return new self(PartStockChangeType::STOCKTAKE, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment, action_timestamp: $action_timestamp); - } - /** * Returns the instock change type of this entry * @return PartStockChangeType diff --git a/src/Entity/UserSystem/PermissionData.php b/src/Entity/UserSystem/PermissionData.php index b7d1ff8f..9ebdc9c9 100644 --- a/src/Entity/UserSystem/PermissionData.php +++ b/src/Entity/UserSystem/PermissionData.php @@ -43,7 +43,7 @@ final class PermissionData implements \JsonSerializable /** * The current schema version of the permission data */ - public const CURRENT_SCHEMA_VERSION = 4; + public const CURRENT_SCHEMA_VERSION = 3; /** * Creates a new Permission Data Instance using the given data. diff --git a/src/Form/Part/PartLotType.php b/src/Form/Part/PartLotType.php index ae86fb61..7d545340 100644 --- a/src/Form/Part/PartLotType.php +++ b/src/Form/Part/PartLotType.php @@ -31,7 +31,6 @@ 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; @@ -111,14 +110,6 @@ 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 5748f4af..87c3d135 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', 'stocktake']; + protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element', 'withdraw', 'add', 'move']; protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { $user = $this->helper->resolveUser($token); - if (in_array($attribute, ['withdraw', 'add', 'move', 'stocktake'], true)) + if (in_array($attribute, ['withdraw', 'add', 'move'], true)) { $base_permission = $this->helper->isGranted($token, 'parts_stock', $attribute, $vote); diff --git a/src/Services/Parts/PartLotWithdrawAddHelper.php b/src/Services/Parts/PartLotWithdrawAddHelper.php index d6a95b34..34ec4c1d 100644 --- a/src/Services/Parts/PartLotWithdrawAddHelper.php +++ b/src/Services/Parts/PartLotWithdrawAddHelper.php @@ -197,45 +197,4 @@ final class PartLotWithdrawAddHelper $this->entityManager->remove($origin); } } - - /** - * Perform a stocktake for the given part lot, setting the amount to the given actual amount. - * Please note that the changes are not flushed to DB yet, you have to do this yourself - * @param PartLot $lot - * @param float $actualAmount - * @param string|null $comment - * @param \DateTimeInterface|null $action_timestamp - * @return void - */ - public function stocktake(PartLot $lot, float $actualAmount, ?string $comment = null, ?\DateTimeInterface $action_timestamp = null): void - { - if ($actualAmount < 0) { - throw new \InvalidArgumentException('Actual amount must be non-negative'); - } - - $part = $lot->getPart(); - - //Check whether we have to round the amount - if (!$part->useFloatAmount()) { - $actualAmount = round($actualAmount); - } - - $oldAmount = $lot->getAmount(); - //Clear any unknown status when doing a stocktake, as we now have a known amount - $lot->setInstockUnknown(false); - $lot->setAmount($actualAmount); - if ($action_timestamp) { - $lot->setLastStocktakeAt(\DateTimeImmutable::createFromInterface($action_timestamp)); - } else { - $lot->setLastStocktakeAt(new \DateTimeImmutable()); //Use now if no timestamp is given - } - - $event = PartStockChangedLogEntry::stocktake($lot, $oldAmount, $lot->getAmount(), $part->getAmountSum() , $comment, $action_timestamp); - $this->eventLogger->log($event); - - //Apply the comment also to global events, so it gets associated with the elementChanged log entry - if (!$this->eventCommentHelper->isMessageSet() && ($comment !== null && $comment !== '')) { - $this->eventCommentHelper->setMessage($comment); - } - } } diff --git a/src/Services/UserSystem/PermissionSchemaUpdater.php b/src/Services/UserSystem/PermissionSchemaUpdater.php index fd85ee7c..104800dc 100644 --- a/src/Services/UserSystem/PermissionSchemaUpdater.php +++ b/src/Services/UserSystem/PermissionSchemaUpdater.php @@ -157,20 +157,4 @@ class PermissionSchemaUpdater $permissions->setPermissionValue('system', 'show_updates', $new_value); } } - - private function upgradeSchemaToVersion4(HasPermissionsInterface $holder): void //@phpstan-ignore-line This is called via reflection - { - $permissions = $holder->getPermissions(); - - //If the reports.generate permission is not defined yet, set it to the value of reports.read - if (!$permissions->isPermissionSet('parts_stock', 'stocktake')) { - //Set the new permission to true only if both add and withdraw are allowed - $new_value = TrinaryLogicHelper::and( - $permissions->getPermissionValue('parts_stock', 'withdraw'), - $permissions->getPermissionValue('parts_stock', 'add') - ); - - $permissions->setPermissionValue('parts_stock', 'stocktake', $new_value); - } - } } diff --git a/templates/parts/edit/edit_form_styles.html.twig b/templates/parts/edit/edit_form_styles.html.twig index 844c8700..1856dbff 100644 --- a/templates/parts/edit/edit_form_styles.html.twig +++ b/templates/parts/edit/edit_form_styles.html.twig @@ -110,7 +110,6 @@ {{ 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 cfb7190b..1ef25ae4 100644 --- a/templates/parts/info/_part_lots.html.twig +++ b/templates/parts/info/_part_lots.html.twig @@ -2,7 +2,6 @@ {% import "label_system/dropdown_macro.html.twig" as dropdown %} {% include "parts/info/_withdraw_modal.html.twig" %} -{% include "parts/info/_stocktake_modal.html.twig" %}
@@ -20,56 +19,53 @@ {% for lot in part.partLots %} - {# @var lot App\Entity\Parts\PartLot #} - + + {% endfor %} diff --git a/templates/parts/info/_stocktake_modal.html.twig b/templates/parts/info/_stocktake_modal.html.twig deleted file mode 100644 index 5e8c1ae5..00000000 --- a/templates/parts/info/_stocktake_modal.html.twig +++ /dev/null @@ -1,63 +0,0 @@ - diff --git a/tests/Services/Parts/PartLotWithdrawAddHelperTest.php b/tests/Services/Parts/PartLotWithdrawAddHelperTest.php index b033f07e..697d3983 100644 --- a/tests/Services/Parts/PartLotWithdrawAddHelperTest.php +++ b/tests/Services/Parts/PartLotWithdrawAddHelperTest.php @@ -154,19 +154,4 @@ class PartLotWithdrawAddHelperTest extends WebTestCase $this->assertEqualsWithDelta(5.0, $this->partLot2->getAmount(), PHP_FLOAT_EPSILON); $this->assertEqualsWithDelta(2.0, $this->partLot3->getAmount(), PHP_FLOAT_EPSILON); } - - public function testStocktake(): void - { - //Stocktake lot 1 to 20 - $this->service->stocktake($this->partLot1, 20, "Test"); - $this->assertEqualsWithDelta(20.0, $this->partLot1->getAmount(), PHP_FLOAT_EPSILON); - $this->assertNotNull($this->partLot1->getLastStocktakeAt()); //Stocktake date should be set - - //Stocktake lot 2 to 5 - $this->partLot2->setInstockUnknown(true); - $this->service->stocktake($this->partLot2, 0, "Test"); - $this->assertEqualsWithDelta(0.0, $this->partLot2->getAmount(), PHP_FLOAT_EPSILON); - $this->assertFalse($this->partLot2->isInstockUnknown()); //Instock unknown should be cleared - - } } diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index bbd96ac6..36fc10d1 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -12467,47 +12467,5 @@ 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 - - - - - 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 - -
{{ 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 %} - {% if lot.lastStocktakeAt %} - - - {{ lot.lastStocktakeAt | format_datetime("short") }} - - {% endif %} + {% endif %} + {% if lot.needsRefill %} +
+ + + {% trans %}part_lots.need_refill{% endtrans %} + + {% endif %} +
@@ -94,15 +90,12 @@ > - -
{{ dropdown.profile_dropdown('part_lot', lot.id, false) }} {# Action for order information #}