Merge branch 'master' into log_detail_page

This commit is contained in:
Jan Böhmer 2023-05-14 16:43:52 +02:00
commit 3c724a227a
19 changed files with 2547 additions and 635 deletions

View file

@ -22,6 +22,7 @@ declare(strict_types=1);
namespace App\Controller;
use App\DataTables\ErrorDataTable;
use App\DataTables\Filters\PartFilter;
use App\DataTables\Filters\PartSearchFilter;
use App\DataTables\PartsDataTable;
@ -30,9 +31,11 @@ use App\Entity\Parts\Footprint;
use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\Storelocation;
use App\Entity\Parts\Supplier;
use App\Exceptions\InvalidRegexException;
use App\Form\Filters\PartFilterType;
use App\Services\Parts\PartsTableActionHandler;
use App\Services\Trees\NodesListBuilder;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\ORM\EntityManagerInterface;
use Omines\DataTablesBundle\DataTableFactory;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@ -41,6 +44,7 @@ use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
class PartListsController extends AbstractController
{
@ -48,11 +52,14 @@ class PartListsController extends AbstractController
private NodesListBuilder $nodesListBuilder;
private DataTableFactory $dataTableFactory;
public function __construct(EntityManagerInterface $entityManager, NodesListBuilder $nodesListBuilder, DataTableFactory $dataTableFactory)
private TranslatorInterface $translator;
public function __construct(EntityManagerInterface $entityManager, NodesListBuilder $nodesListBuilder, DataTableFactory $dataTableFactory, TranslatorInterface $translator)
{
$this->entityManager = $entityManager;
$this->nodesListBuilder = $nodesListBuilder;
$this->dataTableFactory = $dataTableFactory;
$this->translator = $translator;
}
/**
@ -144,7 +151,21 @@ class PartListsController extends AbstractController
->handleRequest($request);
if ($table->isCallback()) {
return $table->getResponse();
try {
try {
return $table->getResponse();
} catch (DriverException $driverException) {
if ($driverException->getCode() === 1139) {
//Convert the driver exception to InvalidRegexException so it has the same hanlder as for SQLite
throw InvalidRegexException::fromDriverException($driverException);
} else {
throw $driverException;
}
}
} catch (InvalidRegexException $exception) {
$errors = $this->translator->trans('part.table.invalid_regex').': '.$exception->getReason();
return ErrorDataTable::errorTable($this->dataTableFactory, $request, $errors);
}
}
return $this->render($template, array_merge([
@ -288,21 +309,22 @@ class PartListsController extends AbstractController
{
$filter = new PartSearchFilter($request->query->get('keyword', ''));
$filter->setName($request->query->getBoolean('name', true));
$filter->setCategory($request->query->getBoolean('category', true));
$filter->setDescription($request->query->getBoolean('description', true));
$filter->setMpn($request->query->getBoolean('mpn', true));
$filter->setTags($request->query->getBoolean('tags', true));
$filter->setStorelocation($request->query->getBoolean('storelocation', true));
$filter->setComment($request->query->getBoolean('comment', true));
$filter->setIPN($request->query->getBoolean('ipn', true));
$filter->setOrdernr($request->query->getBoolean('ordernr', true));
$filter->setSupplier($request->query->getBoolean('supplier', false));
$filter->setManufacturer($request->query->getBoolean('manufacturer', false));
$filter->setFootprint($request->query->getBoolean('footprint', false));
//As an unchecked checkbox is not set in the query, the default value for all bools have to be false (which is the default argument value)!
$filter->setName($request->query->getBoolean('name'));
$filter->setCategory($request->query->getBoolean('category'));
$filter->setDescription($request->query->getBoolean('description'));
$filter->setMpn($request->query->getBoolean('mpn'));
$filter->setTags($request->query->getBoolean('tags'));
$filter->setStorelocation($request->query->getBoolean('storelocation'));
$filter->setComment($request->query->getBoolean('comment'));
$filter->setIPN($request->query->getBoolean('ipn'));
$filter->setOrdernr($request->query->getBoolean('ordernr'));
$filter->setSupplier($request->query->getBoolean('supplier'));
$filter->setManufacturer($request->query->getBoolean('manufacturer'));
$filter->setFootprint($request->query->getBoolean('footprint'));
$filter->setRegex($request->query->getBoolean('regex', false));
$filter->setRegex($request->query->getBoolean('regex'));
return $filter;
}

View file

@ -0,0 +1,85 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\DataTables;
use App\DataTables\Column\RowClassColumn;
use App\Entity\Parts\Part;
use Omines\DataTablesBundle\Adapter\ArrayAdapter;
use Omines\DataTablesBundle\Column\TextColumn;
use Omines\DataTablesBundle\DataTable;
use Omines\DataTablesBundle\DataTableFactory;
use Omines\DataTablesBundle\DataTableTypeInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ErrorDataTable implements DataTableTypeInterface
{
public function configureOptions(OptionsResolver $optionsResolver): void
{
$optionsResolver->setRequired('errors');
$optionsResolver->setAllowedTypes('errors', ['array', 'string']);
$optionsResolver->setNormalizer('errors', function (OptionsResolver $optionsResolver, $errors) {
if (is_string($errors)) {
$errors = [$errors];
}
return $errors;
});
}
public function configure(DataTable $dataTable, array $options)
{
$optionsResolver = new OptionsResolver();
$this->configureOptions($optionsResolver);
$options = $optionsResolver->resolve($options);
$dataTable
->add('dont_matter_we_only_set_color', RowClassColumn::class, [
'render' => function ($value, $context) {
return 'table-warning';
},
])
->add('error', TextColumn::class, [
'label' => 'error_table.error',
'render' => function ($value, $context) {
return '<i class="fa-solid fa-triangle-exclamation fa-fw"></i> ' . $value;
},
])
;
//Build the array containing data
$data = [];
foreach ($options['errors'] as $error) {
$data[] = ['error' => $error];
}
$dataTable->createAdapter(ArrayAdapter::class, $data);
}
public static function errorTable(DataTableFactory $dataTableFactory, Request $request, $errors): Response
{
$error_table = $dataTableFactory->createFromType(self::class, ['errors' => $errors]);
$error_table->handleRequest($request);
return $error_table->getResponse();
}
}

View file

@ -86,6 +86,9 @@ class PartSearchFilter implements FilterInterface
if($this->description) {
$fields_to_search[] = 'part.description';
}
if ($this->comment) {
$fields_to_search[] = 'part.comment';
}
if($this->tags) {
$fields_to_search[] = 'part.tags';
}

View file

@ -20,6 +20,7 @@
namespace App\Doctrine;
use App\Exceptions\InvalidRegexException;
use Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface;
use Doctrine\DBAL\Event\ConnectionEventArgs;
use Doctrine\DBAL\Events;
@ -43,7 +44,11 @@ class SQLiteRegexExtension implements EventSubscriberInterface
//Ensure that the function really exists on the connection, as it is marked as experimental according to PHP documentation
if($native_connection instanceof \PDO && method_exists($native_connection, 'sqliteCreateFunction' )) {
$native_connection->sqliteCreateFunction('REGEXP', function ($pattern, $value) {
return (false !== mb_ereg($pattern, $value)) ? 1 : 0;
try {
return (false !== mb_ereg($pattern, $value)) ? 1 : 0;
} catch (\ErrorException $e) {
throw InvalidRegexException::fromMBRegexError($e);
}
});
}
}

View file

@ -0,0 +1,80 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Exceptions;
use Doctrine\DBAL\Exception\DriverException;
use ErrorException;
class InvalidRegexException extends \RuntimeException
{
private ?string $reason;
public function __construct(string $reason = null)
{
$this->reason = $reason;
parent::__construct('Invalid regular expression');
}
/**
* Returns the reason for the exception (what the regex driver deemed invalid)
* @return string|null
*/
public function getReason(): ?string
{
return $this->reason;
}
/**
* Creates a new exception from a driver exception happening, when MySQL encounters an invalid regex
* @param DriverException $exception
* @return self
*/
public static function fromDriverException(DriverException $exception): self
{
//1139 means invalid regex error
if ($exception->getCode() !== 1139) {
throw new \InvalidArgumentException('The given exception is not a driver exception', 0, $exception);
}
//Reason is the part after the erorr code
$reason = preg_replace('/^.*1139 /', '', $exception->getMessage());
return new self($reason);
}
/**
* Creates a new exception from the errorException thrown by mb_ereg
* @param ErrorException $ex
* @return self
*/
public static function fromMBRegexError(ErrorException $ex): self
{
//Ensure that the error is really a mb_ereg error
if ($ex->getSeverity() !== E_WARNING || !strpos($ex->getMessage(), 'mb_ereg()') !== false) {
throw new \InvalidArgumentException('The given exception is not a mb_ereg error', 0, $ex);
}
//Reason is the part after the erorr code
$reason = preg_replace('/^.*mb_ereg\(\): /', '', $ex->getMessage());
return new self($reason);
}
}

View file

@ -69,7 +69,10 @@ class MySQLDumpXMLConverter
//Iterate over all <table> nodes and convert them to arrays
foreach ($tables as $table) {
$table_data[$table->getAttribute('name')] = $this->convertTableToArray($table);
//Normalize the table name to lowercase. On Linux filesystems the tables sometimes contain uppercase letters
//However we expect the table names to be lowercase in the further steps
$table_name = strtolower($table->getAttribute('name'));
$table_data[$table_name] = $this->convertTableToArray($table);
}
return $table_data;

View file

@ -168,11 +168,21 @@ class PKPartImporter
$entity->setName($name);
$entity->setValueText($partparameter['stringValue'] ?? '');
$entity->setUnit($this->getUnitSymbol($data, (int) $partparameter['unit_id']));
if ($partparameter['unit_id'] === null) {
$entity->setUnit($this->getUnitSymbol($data, (int)$partparameter['unit_id']));
} else {
$entity->setUnit("");
}
$entity->setValueMin($partparameter['normalizedMinValue'] ?? null);
$entity->setValueTypical($partparameter['normalizedValue'] ?? null);
$entity->setValueMax($partparameter['normalizedMaxValue'] ?? null);
if ($partparameter['normalizedMinValue'] !== null) {
$entity->setValueMin((float) $partparameter['normalizedMinValue']);
}
if ($partparameter['normalizedValue'] !== null) {
$entity->setValueTypical((float) $partparameter['normalizedValue']);
}
if ($partparameter['normalizedMaxValue'] !== null) {
$entity->setValueMax((float) $partparameter['normalizedMaxValue']);
}
$part = $this->em->find(Part::class, (int) $partparameter['part_id']);
if (!$part) {
@ -226,6 +236,7 @@ class PKPartImporter
$orderdetail->setSupplier($supplier);
$orderdetail->setSupplierpartnr($spn);
$part->addOrderdetail($orderdetail);
$this->em->persist($orderdetail);
}
//Add the price information to the orderdetail
@ -236,10 +247,13 @@ class PKPartImporter
$price_per_item = BigDecimal::of($partdistributor['price']);
$pricedetail->setPrice($price_per_item->multipliedBy($partdistributor['packagingUnit']));
$pricedetail->setPriceRelatedQuantity($partdistributor['packagingUnit'] ?? 1);
$this->em->persist($pricedetail);
}
//We have to flush the changes in every loop, so the find function can find newly created entities
$this->em->flush();
//Clear the entity manager to improve performance
$this->em->clear();
}
}