mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-02-16 06:29:36 +00:00
Added a modal to stocktake / set part lots amount from info page
This commit is contained in:
parent
2f9601364e
commit
d8fdaa9529
8 changed files with 225 additions and 2 deletions
|
|
@ -54,12 +54,14 @@ 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;
|
||||
|
|
@ -463,6 +465,54 @@ 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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ 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
|
||||
|
|
@ -38,6 +40,7 @@ enum PartStockChangeType: string
|
|||
self::ADD => 'a',
|
||||
self::WITHDRAW => 'w',
|
||||
self::MOVE => 'm',
|
||||
self::STOCKTAKE => 's',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -52,6 +55,7 @@ enum PartStockChangeType: string
|
|||
'a' => self::ADD,
|
||||
'w' => self::WITHDRAW,
|
||||
'm' => self::MOVE,
|
||||
's' => self::STOCKTAKE,
|
||||
default => throw new \InvalidArgumentException("Invalid short type: $value"),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,6 +122,11 @@ 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
|
||||
|
|
|
|||
|
|
@ -197,4 +197,45 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue