mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 01:25:55 +02:00
Added a very basic modal on part info page for changing part instock
Related to issue #201
This commit is contained in:
parent
0e020dab74
commit
ba4085d882
7 changed files with 488 additions and 4 deletions
41
assets/controllers/pages/part_withdraw_modal_controller.js
Normal file
41
assets/controllers/pages/part_withdraw_modal_controller.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
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));
|
||||||
|
|
||||||
|
//Register an event to remove the backdrop, when the form is submitted
|
||||||
|
const form = this.element.querySelector('form');
|
||||||
|
form.addEventListener('submit', event => {
|
||||||
|
//Remove the backdrop
|
||||||
|
document.querySelector('.modal-backdrop').remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_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 action = button.getAttribute('data-action');
|
||||||
|
const lotID = button.getAttribute('data-lot-id');
|
||||||
|
const lotAmount = button.getAttribute('data-lot-amount');
|
||||||
|
|
||||||
|
//Set the action and lotID inputs in the form
|
||||||
|
this.element.querySelector('input[name="action"]').setAttribute('value', action);
|
||||||
|
this.element.querySelector('input[name="lot_id"]').setAttribute('value', lotID);
|
||||||
|
|
||||||
|
//For adding parts there is no limit on the amount to add
|
||||||
|
if (action == 'add') {
|
||||||
|
amountInput.removeAttribute('max');
|
||||||
|
} else { //Every other action is limited to the amount of parts in the lot
|
||||||
|
amountInput.setAttribute('max', lotAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ use App\Services\LogSystem\EventCommentHelper;
|
||||||
use App\Services\LogSystem\HistoryHelper;
|
use App\Services\LogSystem\HistoryHelper;
|
||||||
use App\Services\LogSystem\TimeTravel;
|
use App\Services\LogSystem\TimeTravel;
|
||||||
use App\Services\Parameters\ParameterExtractor;
|
use App\Services\Parameters\ParameterExtractor;
|
||||||
|
use App\Services\Parts\PartLotWithdrawAddHelper;
|
||||||
use App\Services\Parts\PricedetailHelper;
|
use App\Services\Parts\PricedetailHelper;
|
||||||
use App\Services\ProjectSystem\ProjectBuildPartHelper;
|
use App\Services\ProjectSystem\ProjectBuildPartHelper;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
|
@ -52,6 +53,7 @@ use Symfony\Component\Form\FormInterface;
|
||||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
|
@ -319,4 +321,60 @@ class PartController extends AbstractController
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/{id}/add_withdraw", name="part_add_withdraw", methods={"POST"})
|
||||||
|
*/
|
||||||
|
public function withdrawAddHandler(Part $part, Request $request, EntityManagerInterface $em, PartLotWithdrawAddHelper $withdrawAddHelper): Response
|
||||||
|
{
|
||||||
|
if ($this->isCsrfTokenValid('part_withraw' . $part->getID(), $request->request->get('_csfr'))) {
|
||||||
|
//Retrieve partlot from the request
|
||||||
|
$partLot = $em->find(PartLot::class, $request->request->get('lot_id'));
|
||||||
|
//Ensure that the partlot belongs to the part
|
||||||
|
if($partLot->getPart() !== $part) {
|
||||||
|
throw new \RuntimeException("The origin partlot does not belong to the part!");
|
||||||
|
}
|
||||||
|
//Try to determine the target lot (used for move actions)
|
||||||
|
$targetLot = $em->find(PartLot::class, $request->request->get('target_id'));
|
||||||
|
if ($targetLot && $targetLot->getPart() !== $part) {
|
||||||
|
throw new \RuntimeException("The target partlot does not belong to the part!");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Extract the amount and comment from the request
|
||||||
|
$amount = (float) $request->request->get('amount');
|
||||||
|
$comment = $request->request->get('comment');
|
||||||
|
$action = $request->request->get('action');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
case "withdraw":
|
||||||
|
case "remove":
|
||||||
|
$withdrawAddHelper->withdraw($partLot, $amount, $comment);
|
||||||
|
break;
|
||||||
|
case "add":
|
||||||
|
$withdrawAddHelper->add($partLot, $amount, $comment);
|
||||||
|
break;
|
||||||
|
case "move":
|
||||||
|
$withdrawAddHelper->move($partLot, $targetLot, $amount, $comment);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new \RuntimeException("Unknown action!");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Save the changes to the DB
|
||||||
|
$em->flush();
|
||||||
|
$this->addFlash('success', 'part.withdraw.success');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->addFlash('error', 'CSRF Token invalid!');
|
||||||
|
}
|
||||||
|
|
||||||
|
//If an 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()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,21 +38,21 @@ class StorelocationAdminForm extends BaseEntityAdminForm
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'label' => 'storelocation.edit.is_full.label',
|
'label' => 'storelocation.edit.is_full.label',
|
||||||
'help' => 'storelocation.edit.is_full.help',
|
'help' => 'storelocation.edit.is_full.help',
|
||||||
'disabled' => !$this->security->isGranted($is_new ? 'create' : 'move', $entity),
|
'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$builder->add('limit_to_existing_parts', CheckboxType::class, [
|
$builder->add('limit_to_existing_parts', CheckboxType::class, [
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'label' => 'storelocation.limit_to_existing.label',
|
'label' => 'storelocation.limit_to_existing.label',
|
||||||
'help' => 'storelocation.limit_to_existing.help',
|
'help' => 'storelocation.limit_to_existing.help',
|
||||||
'disabled' => !$this->security->isGranted($is_new ? 'create' : 'move', $entity),
|
'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$builder->add('only_single_part', CheckboxType::class, [
|
$builder->add('only_single_part', CheckboxType::class, [
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'label' => 'storelocation.only_single_part.label',
|
'label' => 'storelocation.only_single_part.label',
|
||||||
'help' => 'storelocation.only_single_part.help',
|
'help' => 'storelocation.only_single_part.help',
|
||||||
'disabled' => !$this->security->isGranted($is_new ? 'create' : 'move', $entity),
|
'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$builder->add('storage_type', StructuralEntityType::class, [
|
$builder->add('storage_type', StructuralEntityType::class, [
|
||||||
|
@ -61,7 +61,7 @@ class StorelocationAdminForm extends BaseEntityAdminForm
|
||||||
'help' => 'storelocation.storage_type.help',
|
'help' => 'storelocation.storage_type.help',
|
||||||
'class' => MeasurementUnit::class,
|
'class' => MeasurementUnit::class,
|
||||||
'disable_not_selectable' => true,
|
'disable_not_selectable' => true,
|
||||||
'disabled' => !$this->security->isGranted($is_new ? 'create' : 'move', $entity),
|
'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
157
src/Services/Parts/PartLotWithdrawAddHelper.php
Normal file
157
src/Services/Parts/PartLotWithdrawAddHelper.php
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Parts;
|
||||||
|
|
||||||
|
use App\Entity\Parts\Part;
|
||||||
|
use App\Entity\Parts\PartLot;
|
||||||
|
|
||||||
|
class PartLotWithdrawAddHelper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Checks whether the given part can
|
||||||
|
* @param PartLot $partLot
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function canAdd(PartLot $partLot): bool
|
||||||
|
{
|
||||||
|
//We cannot add or withdraw parts from lots with unknown instock value.
|
||||||
|
if($partLot->isInstockUnknown()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//So far all other restrictions are defined at the storelocation level
|
||||||
|
if($partLot->getStorageLocation() === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//We can not add parts if the storage location of the lot is marked as full
|
||||||
|
if($partLot->getStorageLocation()->isFull()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canWithdraw(PartLot $partLot): bool
|
||||||
|
{
|
||||||
|
//We cannot add or withdraw parts from lots with unknown instock value.
|
||||||
|
if ($partLot->isInstockUnknown()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Withdraw the specified amount of parts from the given part lot.
|
||||||
|
* Please note that the changes are not flushed to DB yet, you have to do this yourself
|
||||||
|
* @param PartLot $partLot The partLot from which the instock should be taken (which value should be decreased)
|
||||||
|
* @param float $amount The amount of parts that should be taken from the part lot
|
||||||
|
* @param string|null $comment The optional comment describing the reason for the withdrawal
|
||||||
|
* @return PartLot The modified part lot
|
||||||
|
*/
|
||||||
|
public function withdraw(PartLot $partLot, float $amount, ?string $comment = null): PartLot
|
||||||
|
{
|
||||||
|
//Ensure that amount is positive
|
||||||
|
if ($amount <= 0) {
|
||||||
|
throw new \InvalidArgumentException('Amount must be positive');
|
||||||
|
}
|
||||||
|
|
||||||
|
$part = $partLot->getPart();
|
||||||
|
|
||||||
|
//Check whether we have to round the amount
|
||||||
|
if (!$part->useFloatAmount()) {
|
||||||
|
$amount = round($amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Ensure that we can withdraw from the part lot
|
||||||
|
if (!$this->canWithdraw($partLot)) {
|
||||||
|
throw new \RuntimeException("Cannot withdraw from this part lot!");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Ensure that there is enough stock to withdraw
|
||||||
|
if ($amount > $partLot->getAmount()) {
|
||||||
|
throw new \RuntimeException('Not enough stock to withdraw!');
|
||||||
|
}
|
||||||
|
|
||||||
|
//Subtract the amount from the part lot
|
||||||
|
$partLot->setAmount($partLot->getAmount() - $amount);
|
||||||
|
|
||||||
|
return $partLot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the specified amount of parts to the given part lot.
|
||||||
|
* Please note that the changes are not flushed to DB yet, you have to do this yourself
|
||||||
|
* @param PartLot $partLot The partLot from which the instock should be taken (which value should be decreased)
|
||||||
|
* @param float $amount The amount of parts that should be taken from the part lot
|
||||||
|
* @param string|null $comment The optional comment describing the reason for the withdrawal
|
||||||
|
* @return PartLot The modified part lot
|
||||||
|
*/
|
||||||
|
public function add(PartLot $partLot, float $amount, ?string $comment = null): PartLot
|
||||||
|
{
|
||||||
|
if ($amount <= 0) {
|
||||||
|
throw new \InvalidArgumentException('Amount must be positive');
|
||||||
|
}
|
||||||
|
|
||||||
|
$part = $partLot->getPart();
|
||||||
|
|
||||||
|
//Check whether we have to round the amount
|
||||||
|
if (!$part->useFloatAmount()) {
|
||||||
|
$amount = round($amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Ensure that we can add to the part lot
|
||||||
|
if (!$this->canAdd($partLot)) {
|
||||||
|
throw new \RuntimeException("Cannot add to this part lot!");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Subtract the amount from the part lot
|
||||||
|
$partLot->setAmount($partLot->getAmount() + $amount);
|
||||||
|
|
||||||
|
return $partLot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the specified amount of parts from the given source part lot to the given target part lot.
|
||||||
|
* Please note that the changes are not flushed to DB yet, you have to do this yourself
|
||||||
|
* @param PartLot $origin The part lot from which the parts should be taken
|
||||||
|
* @param PartLot $target The part lot to which the parts should be added
|
||||||
|
* @param float $amount The amount of parts that should be moved
|
||||||
|
* @param string|null $comment A comment describing the reason for the move
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function move(PartLot $origin, PartLot $target, float $amount, ?string $comment = null): void
|
||||||
|
{
|
||||||
|
if ($amount <= 0) {
|
||||||
|
throw new \InvalidArgumentException('Amount must be positive');
|
||||||
|
}
|
||||||
|
|
||||||
|
$part = $origin->getPart();
|
||||||
|
|
||||||
|
//Ensure that both part lots belong to the same part
|
||||||
|
if($origin->getPart() !== $target->getPart()) {
|
||||||
|
throw new \RuntimeException("Cannot move instock between different parts!");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check whether we have to round the amount
|
||||||
|
if (!$part->useFloatAmount()) {
|
||||||
|
$amount = round($amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Ensure that we can withdraw from origin and add to target
|
||||||
|
if (!$this->canWithdraw($origin) || !$this->canAdd($target)) {
|
||||||
|
throw new \RuntimeException("Cannot move instock between these part lots!");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Ensure that there is enough stock to withdraw
|
||||||
|
if ($amount > $origin->getAmount()) {
|
||||||
|
throw new \RuntimeException('Not enough stock to withdraw!');
|
||||||
|
}
|
||||||
|
|
||||||
|
//Subtract the amount from the part lot
|
||||||
|
$origin->setAmount($origin->getAmount() - $amount);
|
||||||
|
//And add it to the target
|
||||||
|
$target->setAmount($target->getAmount() + $amount);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
{% import "helper.twig" as helper %}
|
{% import "helper.twig" as helper %}
|
||||||
{% import "LabelSystem/dropdown_macro.html.twig" as dropdown %}
|
{% import "LabelSystem/dropdown_macro.html.twig" as dropdown %}
|
||||||
|
|
||||||
|
{% include "Parts/info/_withdraw_modal.html.twig" %}
|
||||||
|
|
||||||
<table class="table table-striped table-hover table-responsive-sm">
|
<table class="table table-striped table-hover table-responsive-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -8,6 +10,7 @@
|
||||||
<th>{% trans %}part_lots.storage_location{% endtrans %}</th>
|
<th>{% trans %}part_lots.storage_location{% endtrans %}</th>
|
||||||
<th>{% trans %}part_lots.amount{% endtrans %}</th>
|
<th>{% trans %}part_lots.amount{% endtrans %}</th>
|
||||||
<th></th> {# Tags row #}
|
<th></th> {# Tags row #}
|
||||||
|
<th></th>
|
||||||
<th></th> {# Button row #}
|
<th></th> {# Button row #}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -57,6 +60,26 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h6>
|
</h6>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<button type="button" class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#withdraw-modal"
|
||||||
|
data-action="add" data-lot-id="{{ lot.id }}" data-lot-amount="{{ lot.amount }}"
|
||||||
|
{% if lot.storageLocation and lot.storageLocation.full %}disabled{% endif %}
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-plus fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#withdraw-modal"
|
||||||
|
data-action="remove" data-lot-id="{{ lot.id }}" data-lot-amount="{{ lot.amount }}"
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-minus fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#withdraw-modal"
|
||||||
|
data-action="move" data-lot-id="{{ lot.id }}" data-lot-amount="{{ lot.amount }}"
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-right-left fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ dropdown.profile_dropdown('part_lot', lot.id, false) }}
|
{{ dropdown.profile_dropdown('part_lot', lot.id, false) }}
|
||||||
</td>
|
</td>
|
||||||
|
|
56
templates/Parts/info/_withdraw_modal.html.twig
Normal file
56
templates/Parts/info/_withdraw_modal.html.twig
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<div class="modal fade" id="withdraw-modal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true" {{ stimulus_controller('pages/part_withdraw_modal') }}>
|
||||||
|
<form method="post" action="{{ path('part_add_withdraw', {"id": part.id}) }}">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="exampleModalLabel">Modal title</h1>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{# non visible form elements #}
|
||||||
|
<input type="hidden" name="lot_id" value="">
|
||||||
|
<input type="hidden" name="action" value="">
|
||||||
|
<input type="hidden" name="_csfr" value="{{ csrf_token('part_withraw' ~ part.iD) }}">
|
||||||
|
<input type="hidden" name="_redirect" value="{{ app.request.requestUri }}">
|
||||||
|
|
||||||
|
<div class="row mb-2">
|
||||||
|
<label class="col-form-label col-sm-3">
|
||||||
|
Amount
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="number" required class="form-control" min="0" step="{{ (part.partUnit and not part.partUnit.integer) ? 'any' : '1' }}" name="amount" value="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-2">
|
||||||
|
<label class="col-form-label col-sm-3">Move to</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
{% for lots in part.partLots|filter(l => l.instockUnknown == false) %}
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="radio" name="target_id" value="{{ lots.iD }}" id="modal_target_radio_{{ lots.iD }}" {% if lots.storageLocation and lots.storageLocation.full %}disabled{% endif %} required>
|
||||||
|
<label class="form-check-label" for="modal_target_radio_{{ lots.iD }}">
|
||||||
|
{{ (lots.storageLocation) ? lots.storageLocation.fullPath : ("Lot " ~ loop.index) }}{% if lots.name is not empty %} ({{ lots.name }}){% endif %}: <b>{{ lots.amount | format_amount(part.partUnit) }}</b>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-2">
|
||||||
|
<label class="col-form-label col-sm-3">
|
||||||
|
Comment
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" name="comment" value="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
149
tests/Services/Parts/PartLotWithdrawAddHelperTest.php
Normal file
149
tests/Services/Parts/PartLotWithdrawAddHelperTest.php
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Services\Parts;
|
||||||
|
|
||||||
|
use App\Entity\Parts\Part;
|
||||||
|
use App\Entity\Parts\PartLot;
|
||||||
|
use App\Entity\Parts\Storelocation;
|
||||||
|
use App\Services\ElementTypeNameGenerator;
|
||||||
|
use App\Services\Parts\PartLotWithdrawAddHelper;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||||
|
|
||||||
|
class PartLotWithdrawAddHelperTest extends WebTestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var PartLotWithdrawAddHelper
|
||||||
|
*/
|
||||||
|
protected $service;
|
||||||
|
|
||||||
|
/** @var Part */
|
||||||
|
private $part;
|
||||||
|
|
||||||
|
/** @var Storelocation */
|
||||||
|
private $storageLocation;
|
||||||
|
/** @var Storelocation */
|
||||||
|
private $full_storageLocation;
|
||||||
|
|
||||||
|
/** @var PartLot */
|
||||||
|
private $partLot1;
|
||||||
|
/** @var PartLot */
|
||||||
|
private $partLot2;
|
||||||
|
/** @var PartLot */
|
||||||
|
private $partLot3;
|
||||||
|
|
||||||
|
/** @var PartLot */
|
||||||
|
private $fullLot;
|
||||||
|
/** @var PartLot */
|
||||||
|
private $lotWithUnknownInstock;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
//Get an service instance.
|
||||||
|
self::bootKernel();
|
||||||
|
$this->service = self::getContainer()->get(PartLotWithdrawAddHelper::class);
|
||||||
|
|
||||||
|
$this->fillTestData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function fillTestData(): void
|
||||||
|
{
|
||||||
|
$this->part = new Part();
|
||||||
|
|
||||||
|
$this->storageLocation = new Storelocation();
|
||||||
|
$this->full_storageLocation = new Storelocation();
|
||||||
|
$this->full_storageLocation->setIsFull(true);
|
||||||
|
|
||||||
|
$this->partLot1 = new PartLot();
|
||||||
|
$this->partLot1->setPart($this->part);
|
||||||
|
$this->partLot1->setAmount(10);
|
||||||
|
|
||||||
|
$this->partLot2 = new PartLot();
|
||||||
|
$this->partLot2->setPart($this->part);
|
||||||
|
$this->partLot2->setStorageLocation($this->storageLocation);
|
||||||
|
$this->partLot2->setAmount(2);
|
||||||
|
|
||||||
|
$this->partLot3 = new PartLot();
|
||||||
|
$this->partLot3->setPart($this->part);
|
||||||
|
$this->partLot3->setAmount(0);
|
||||||
|
|
||||||
|
$this->fullLot = new PartLot();
|
||||||
|
$this->fullLot->setPart($this->part);
|
||||||
|
$this->fullLot->setAmount(45);
|
||||||
|
$this->fullLot->setStorageLocation($this->full_storageLocation);
|
||||||
|
|
||||||
|
$this->lotWithUnknownInstock = new PartLot();
|
||||||
|
$this->lotWithUnknownInstock->setPart($this->part);
|
||||||
|
$this->lotWithUnknownInstock->setAmount(5);
|
||||||
|
$this->lotWithUnknownInstock->setInstockUnknown(true);
|
||||||
|
$this->lotWithUnknownInstock->setStorageLocation($this->storageLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanWithdraw()
|
||||||
|
{
|
||||||
|
//Normal lots should be withdrawable
|
||||||
|
$this->assertTrue($this->service->canWithdraw($this->partLot1));
|
||||||
|
$this->assertTrue($this->service->canWithdraw($this->partLot2));
|
||||||
|
$this->assertTrue($this->service->canWithdraw($this->partLot3));
|
||||||
|
|
||||||
|
//Full lots should be withdrawable
|
||||||
|
$this->assertTrue($this->service->canWithdraw($this->fullLot));
|
||||||
|
//Lots with unknown instock should not be withdrawable
|
||||||
|
$this->assertFalse($this->service->canWithdraw($this->lotWithUnknownInstock));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanAdd()
|
||||||
|
{
|
||||||
|
//Normal lots should be addable
|
||||||
|
$this->assertTrue($this->service->canAdd($this->partLot1));
|
||||||
|
$this->assertTrue($this->service->canAdd($this->partLot2));
|
||||||
|
$this->assertTrue($this->service->canAdd($this->partLot3));
|
||||||
|
|
||||||
|
//Full lots should not be addable
|
||||||
|
$this->assertFalse($this->service->canAdd($this->fullLot));
|
||||||
|
//Lots with unknown instock should not be addable
|
||||||
|
$this->assertFalse($this->service->canAdd($this->lotWithUnknownInstock));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAdd()
|
||||||
|
{
|
||||||
|
//Add 5 to lot 1
|
||||||
|
$this->service->add($this->partLot1, 5, "Test");
|
||||||
|
$this->assertEquals(15, $this->partLot1->getAmount());
|
||||||
|
|
||||||
|
//Add 3.2 to lot 2
|
||||||
|
$this->service->add($this->partLot2, 3.2, "Test");
|
||||||
|
$this->assertEquals(5, $this->partLot2->getAmount());
|
||||||
|
|
||||||
|
//Add 1.5 to lot 3
|
||||||
|
$this->service->add($this->partLot3, 1.5, "Test");
|
||||||
|
$this->assertEquals(2, $this->partLot3->getAmount());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWithdraw()
|
||||||
|
{
|
||||||
|
//Withdraw 5 from lot 1
|
||||||
|
$this->service->withdraw($this->partLot1, 5, "Test");
|
||||||
|
$this->assertEquals(5, $this->partLot1->getAmount());
|
||||||
|
|
||||||
|
//Withdraw 2.2 from lot 2
|
||||||
|
$this->service->withdraw($this->partLot2, 2.2, "Test");
|
||||||
|
$this->assertEquals(0, $this->partLot2->getAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMove()
|
||||||
|
{
|
||||||
|
//Move 5 from lot 1 to lot 2
|
||||||
|
$this->service->move($this->partLot1, $this->partLot2, 5, "Test");
|
||||||
|
$this->assertEquals(5, $this->partLot1->getAmount());
|
||||||
|
$this->assertEquals(7, $this->partLot2->getAmount());
|
||||||
|
|
||||||
|
//Move 2.2 from lot 2 to lot 3
|
||||||
|
$this->service->move($this->partLot2, $this->partLot3, 2.2, "Test");
|
||||||
|
$this->assertEquals(5, $this->partLot2->getAmount());
|
||||||
|
$this->assertEquals(2, $this->partLot3->getAmount());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue