mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-22 01:49:05 +02:00
Show element history on part info page in history tab.
This commit is contained in:
parent
fff1864a68
commit
c14d6d91ff
12 changed files with 331 additions and 26 deletions
|
@ -42,6 +42,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\DataTables\LogDataTable;
|
||||||
use App\Entity\Parts\Category;
|
use App\Entity\Parts\Category;
|
||||||
use App\Entity\Parts\Part;
|
use App\Entity\Parts\Part;
|
||||||
use App\Exceptions\AttachmentDownloadException;
|
use App\Exceptions\AttachmentDownloadException;
|
||||||
|
@ -49,9 +50,11 @@ use App\Form\Part\PartBaseType;
|
||||||
use App\Services\Attachments\AttachmentManager;
|
use App\Services\Attachments\AttachmentManager;
|
||||||
use App\Services\Attachments\AttachmentSubmitHandler;
|
use App\Services\Attachments\AttachmentSubmitHandler;
|
||||||
use App\Services\Attachments\PartPreviewGenerator;
|
use App\Services\Attachments\PartPreviewGenerator;
|
||||||
|
use App\Services\LogSystem\HistoryHelper;
|
||||||
use App\Services\LogSystem\TimeTravel;
|
use App\Services\LogSystem\TimeTravel;
|
||||||
use App\Services\PricedetailHelper;
|
use App\Services\PricedetailHelper;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Omines\DataTablesBundle\DataTableFactory;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\Form\FormInterface;
|
use Symfony\Component\Form\FormInterface;
|
||||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
|
@ -82,11 +85,23 @@ class PartController extends AbstractController
|
||||||
*
|
*
|
||||||
* @param Part $part
|
* @param Part $part
|
||||||
* @return Response
|
* @return Response
|
||||||
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function show(Part $part, TimeTravel $timeTravel, ?string $timestamp = null): Response
|
public function show(Part $part, Request $request, TimeTravel $timeTravel, HistoryHelper $historyHelper,
|
||||||
|
DataTableFactory $dataTable, ?string $timestamp = null): Response
|
||||||
{
|
{
|
||||||
$this->denyAccessUnlessGranted('read', $part);
|
$this->denyAccessUnlessGranted('read', $part);
|
||||||
|
|
||||||
|
$table = $dataTable->createFromType(LogDataTable::class, [
|
||||||
|
'filter_elements' => $historyHelper->getAssociatedElements($part),
|
||||||
|
'mode' => 'element_history'
|
||||||
|
], ['pageLength' => 10])
|
||||||
|
->handleRequest($request);
|
||||||
|
|
||||||
|
if ($table->isCallback()) {
|
||||||
|
return $table->getResponse();
|
||||||
|
}
|
||||||
|
|
||||||
$timeTravel_timestamp = null;
|
$timeTravel_timestamp = null;
|
||||||
if ($timestamp !== null) {
|
if ($timestamp !== null) {
|
||||||
//If the timestamp only contains numbers interpret it as unix timestamp
|
//If the timestamp only contains numbers interpret it as unix timestamp
|
||||||
|
@ -103,6 +118,7 @@ class PartController extends AbstractController
|
||||||
'Parts/info/show_part_info.html.twig',
|
'Parts/info/show_part_info.html.twig',
|
||||||
[
|
[
|
||||||
'part' => $part,
|
'part' => $part,
|
||||||
|
'datatable' => $table,
|
||||||
'attachment_helper' => $this->attachmentManager,
|
'attachment_helper' => $this->attachmentManager,
|
||||||
'pricedetail_helper' => $this->pricedetailHelper,
|
'pricedetail_helper' => $this->pricedetailHelper,
|
||||||
'pictures' => $this->partPreviewGenerator->getPreviewAttachments($part),
|
'pictures' => $this->partPreviewGenerator->getPreviewAttachments($part),
|
||||||
|
@ -123,7 +139,7 @@ class PartController extends AbstractController
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
public function edit(Part $part, Request $request, EntityManagerInterface $em, TranslatorInterface $translator,
|
public function edit(Part $part, Request $request, EntityManagerInterface $em, TranslatorInterface $translator,
|
||||||
AttachmentSubmitHandler $attachmentSubmitHandler): Response
|
AttachmentSubmitHandler $attachmentSubmitHandler): Response
|
||||||
{
|
{
|
||||||
$this->denyAccessUnlessGranted('edit', $part);
|
$this->denyAccessUnlessGranted('edit', $part);
|
||||||
|
|
||||||
|
@ -160,11 +176,11 @@ class PartController extends AbstractController
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('Parts/edit/edit_part_info.html.twig',
|
return $this->render('Parts/edit/edit_part_info.html.twig',
|
||||||
[
|
[
|
||||||
'part' => $part,
|
'part' => $part,
|
||||||
'form' => $form->createView(),
|
'form' => $form->createView(),
|
||||||
'attachment_helper' => $this->attachmentManager,
|
'attachment_helper' => $this->attachmentManager,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -204,7 +220,7 @@ class PartController extends AbstractController
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
public function new(Request $request, EntityManagerInterface $em, TranslatorInterface $translator,
|
public function new(Request $request, EntityManagerInterface $em, TranslatorInterface $translator,
|
||||||
AttachmentManager $attachmentHelper, AttachmentSubmitHandler $attachmentSubmitHandler): Response
|
AttachmentManager $attachmentHelper, AttachmentSubmitHandler $attachmentSubmitHandler): Response
|
||||||
{
|
{
|
||||||
$new_part = new Part();
|
$new_part = new Part();
|
||||||
|
|
||||||
|
@ -253,11 +269,11 @@ class PartController extends AbstractController
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('Parts/edit/new_part.html.twig',
|
return $this->render('Parts/edit/new_part.html.twig',
|
||||||
[
|
[
|
||||||
'part' => $new_part,
|
'part' => $new_part,
|
||||||
'form' => $form->createView(),
|
'form' => $form->createView(),
|
||||||
'attachment_helper' => $attachmentHelper,
|
'attachment_helper' => $attachmentHelper,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -289,9 +305,9 @@ class PartController extends AbstractController
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('Parts/edit/new_part.html.twig',
|
return $this->render('Parts/edit/new_part.html.twig',
|
||||||
[
|
[
|
||||||
'part' => $new_part,
|
'part' => $new_part,
|
||||||
'form' => $form->createView(),
|
'form' => $form->createView(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
108
src/DataTables/Column/IconLinkColumn.php
Normal file
108
src/DataTables/Column/IconLinkColumn.php
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2020 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\Column;
|
||||||
|
|
||||||
|
|
||||||
|
use Omines\DataTablesBundle\Column\AbstractColumn;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class IconLinkColumn extends AbstractColumn
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function normalize($value)
|
||||||
|
{
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
|
{
|
||||||
|
parent::configureOptions($resolver);
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'icon' => 'fas fa-fw fa-edit',
|
||||||
|
'title' => null,
|
||||||
|
'href' => null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$resolver->setAllowedTypes('title', ['null', 'string', 'callable']);
|
||||||
|
$resolver->setAllowedTypes('icon', ['null', 'string', 'callable']);
|
||||||
|
$resolver->setAllowedTypes('href', ['null', 'string', 'callable']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render($value, $context)
|
||||||
|
{
|
||||||
|
$href = $this->getHref($value, $context);
|
||||||
|
$icon = $this->getIcon($value, $context);
|
||||||
|
$title = $this->getTitle($value, $context);
|
||||||
|
|
||||||
|
if ($href !== null) {
|
||||||
|
return sprintf(
|
||||||
|
'<a href="%s" title="%s"><i class="%s"></i></a>',
|
||||||
|
$href,
|
||||||
|
$title,
|
||||||
|
$icon
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getHref($value, $context): ?string
|
||||||
|
{
|
||||||
|
$provider = $this->options['href'];
|
||||||
|
if (is_string($provider)) {
|
||||||
|
return $provider;
|
||||||
|
}
|
||||||
|
if (is_callable($provider)) {
|
||||||
|
return call_user_func($provider, $value, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getIcon($value, $context): ?string
|
||||||
|
{
|
||||||
|
$provider = $this->options['icon'];
|
||||||
|
if (is_string($provider)) {
|
||||||
|
return $provider;
|
||||||
|
}
|
||||||
|
if (is_callable($provider)) {
|
||||||
|
return call_user_func($provider, $value, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTitle($value, $context): ?string
|
||||||
|
{
|
||||||
|
$provider = $this->options['title'];
|
||||||
|
if (is_string($provider)) {
|
||||||
|
return $provider;
|
||||||
|
}
|
||||||
|
if (is_callable($provider)) {
|
||||||
|
return call_user_func($provider, $value, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,36 +42,63 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\DataTables;
|
namespace App\DataTables;
|
||||||
|
|
||||||
|
use App\DataTables\Column\IconLinkColumn;
|
||||||
use App\DataTables\Column\LocaleDateTimeColumn;
|
use App\DataTables\Column\LocaleDateTimeColumn;
|
||||||
use App\DataTables\Column\LogEntryExtraColumn;
|
use App\DataTables\Column\LogEntryExtraColumn;
|
||||||
use App\DataTables\Column\LogEntryTargetColumn;
|
use App\DataTables\Column\LogEntryTargetColumn;
|
||||||
|
use App\Entity\Base\AbstractDBElement;
|
||||||
|
use App\Entity\Contracts\TimeTravelInterface;
|
||||||
use App\Entity\LogSystem\AbstractLogEntry;
|
use App\Entity\LogSystem\AbstractLogEntry;
|
||||||
|
use App\Exceptions\EntityNotSupportedException;
|
||||||
use App\Services\ElementTypeNameGenerator;
|
use App\Services\ElementTypeNameGenerator;
|
||||||
|
use App\Services\EntityURLGenerator;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapter;
|
use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapter;
|
||||||
use Omines\DataTablesBundle\Column\TextColumn;
|
use Omines\DataTablesBundle\Column\TextColumn;
|
||||||
use Omines\DataTablesBundle\DataTable;
|
use Omines\DataTablesBundle\DataTable;
|
||||||
use Omines\DataTablesBundle\DataTableTypeInterface;
|
use Omines\DataTablesBundle\DataTableTypeInterface;
|
||||||
use Psr\Log\LogLevel;
|
use Psr\Log\LogLevel;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
use Symfony\Flex\Options;
|
||||||
|
|
||||||
class LogDataTable implements DataTableTypeInterface
|
class LogDataTable implements DataTableTypeInterface
|
||||||
{
|
{
|
||||||
protected $elementTypeNameGenerator;
|
protected $elementTypeNameGenerator;
|
||||||
protected $translator;
|
protected $translator;
|
||||||
protected $urlGenerator;
|
protected $urlGenerator;
|
||||||
|
protected $entityURLGenerator;
|
||||||
|
protected $logRepo;
|
||||||
|
|
||||||
public function __construct(ElementTypeNameGenerator $elementTypeNameGenerator, TranslatorInterface $translator,
|
public function __construct(ElementTypeNameGenerator $elementTypeNameGenerator, TranslatorInterface $translator,
|
||||||
UrlGeneratorInterface $urlGenerator)
|
UrlGeneratorInterface $urlGenerator, EntityURLGenerator $entityURLGenerator, EntityManagerInterface $entityManager)
|
||||||
{
|
{
|
||||||
$this->elementTypeNameGenerator = $elementTypeNameGenerator;
|
$this->elementTypeNameGenerator = $elementTypeNameGenerator;
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
$this->urlGenerator = $urlGenerator;
|
$this->urlGenerator = $urlGenerator;
|
||||||
|
$this->entityURLGenerator = $entityURLGenerator;
|
||||||
|
$this->logRepo = $entityManager->getRepository(AbstractLogEntry::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $optionsResolver)
|
||||||
|
{
|
||||||
|
$optionsResolver->setDefaults([
|
||||||
|
'mode' => 'system_log',
|
||||||
|
'filter_elements' => [],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$optionsResolver->setAllowedValues('mode', ['system_log', 'element_history']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configure(DataTable $dataTable, array $options): void
|
public function configure(DataTable $dataTable, array $options): void
|
||||||
{
|
{
|
||||||
|
$resolver = new OptionsResolver();
|
||||||
|
$this->configureOptions($resolver);
|
||||||
|
$options = $resolver->resolve($options);
|
||||||
|
|
||||||
|
|
||||||
$dataTable->add('symbol', TextColumn::class, [
|
$dataTable->add('symbol', TextColumn::class, [
|
||||||
'label' => '',
|
'label' => '',
|
||||||
'render' => function ($value, AbstractLogEntry $context) {
|
'render' => function ($value, AbstractLogEntry $context) {
|
||||||
|
@ -138,6 +165,7 @@ class LogDataTable implements DataTableTypeInterface
|
||||||
|
|
||||||
$dataTable->add('level', TextColumn::class, [
|
$dataTable->add('level', TextColumn::class, [
|
||||||
'label' => $this->translator->trans('log.level'),
|
'label' => $this->translator->trans('log.level'),
|
||||||
|
'visible' => $options['mode'] === 'system_log',
|
||||||
'propertyPath' => 'levelString',
|
'propertyPath' => 'levelString',
|
||||||
'render' => function (string $value, AbstractLogEntry $context) {
|
'render' => function (string $value, AbstractLogEntry $context) {
|
||||||
return $value;
|
return $value;
|
||||||
|
@ -178,21 +206,51 @@ class LogDataTable implements DataTableTypeInterface
|
||||||
'label' => $this->translator->trans('log.extra'),
|
'label' => $this->translator->trans('log.extra'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$dataTable->add('timeTravel', IconLinkColumn::class,[
|
||||||
|
'label' => '',
|
||||||
|
'icon' => 'fas fa-fw fa-eye',
|
||||||
|
'href' => function ($value, AbstractLogEntry $context) {
|
||||||
|
if (
|
||||||
|
$context instanceof TimeTravelInterface
|
||||||
|
&& $context->hasOldDataInformations()
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
$target = $this->logRepo->getTargetElement($context);
|
||||||
|
$str = $this->entityURLGenerator->timeTravelURL($target, $context->getTimestamp());
|
||||||
|
return $str;
|
||||||
|
} catch (EntityNotSupportedException $exception) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
$dataTable->addOrderBy('timestamp', DataTable::SORT_DESCENDING);
|
$dataTable->addOrderBy('timestamp', DataTable::SORT_DESCENDING);
|
||||||
|
|
||||||
$dataTable->createAdapter(ORMAdapter::class, [
|
$dataTable->createAdapter(ORMAdapter::class, [
|
||||||
'entity' => AbstractLogEntry::class,
|
'entity' => AbstractLogEntry::class,
|
||||||
'query' => function (QueryBuilder $builder): void {
|
'query' => function (QueryBuilder $builder) use ($options): void {
|
||||||
$this->getQuery($builder);
|
$this->getQuery($builder, $options);
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getQuery(QueryBuilder $builder): void
|
protected function getQuery(QueryBuilder $builder, array $options): void
|
||||||
{
|
{
|
||||||
$builder->distinct()->select('log')
|
$builder->distinct()->select('log')
|
||||||
->addSelect('user')
|
->addSelect('user')
|
||||||
->from(AbstractLogEntry::class, 'log')
|
->from(AbstractLogEntry::class, 'log')
|
||||||
->leftJoin('log.user', 'user');
|
->leftJoin('log.user', 'user');
|
||||||
|
|
||||||
|
if (!empty($options['filter_elements'])) {
|
||||||
|
foreach ($options['filter_elements'] as $element) {
|
||||||
|
/** @var AbstractDBElement $element */
|
||||||
|
|
||||||
|
$target_type = AbstractLogEntry::targetTypeClassToID(get_class($element));
|
||||||
|
$target_id = $element->getID();
|
||||||
|
$builder->orWhere("log.target_type = $target_type AND log.target_id = $target_id");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,7 +90,7 @@ class LogEntryRepository extends EntityRepository
|
||||||
->where('log INSTANCE OF ' . ElementEditedLogEntry::class)
|
->where('log INSTANCE OF ' . ElementEditedLogEntry::class)
|
||||||
->andWhere('log.target_type = :target_type')
|
->andWhere('log.target_type = :target_type')
|
||||||
->andWhere('log.target_id = :target_id')
|
->andWhere('log.target_id = :target_id')
|
||||||
->andWhere('log.timestamp >= :until')
|
->andWhere('log.timestamp > :until')
|
||||||
->orderBy('log.timestamp', 'DESC');
|
->orderBy('log.timestamp', 'DESC');
|
||||||
|
|
||||||
$qb->setParameters([
|
$qb->setParameters([
|
||||||
|
|
|
@ -44,6 +44,7 @@ namespace App\Services;
|
||||||
|
|
||||||
use App\Entity\Attachments\Attachment;
|
use App\Entity\Attachments\Attachment;
|
||||||
use App\Entity\Attachments\AttachmentType;
|
use App\Entity\Attachments\AttachmentType;
|
||||||
|
use App\Entity\Attachments\PartAttachment;
|
||||||
use App\Entity\Base\AbstractDBElement;
|
use App\Entity\Base\AbstractDBElement;
|
||||||
use App\Entity\Devices\Device;
|
use App\Entity\Devices\Device;
|
||||||
use App\Entity\Parts\Category;
|
use App\Entity\Parts\Category;
|
||||||
|
@ -51,9 +52,12 @@ use App\Entity\Parts\Footprint;
|
||||||
use App\Entity\Parts\Manufacturer;
|
use App\Entity\Parts\Manufacturer;
|
||||||
use App\Entity\Parts\MeasurementUnit;
|
use App\Entity\Parts\MeasurementUnit;
|
||||||
use App\Entity\Parts\Part;
|
use App\Entity\Parts\Part;
|
||||||
|
use App\Entity\Parts\PartLot;
|
||||||
use App\Entity\Parts\Storelocation;
|
use App\Entity\Parts\Storelocation;
|
||||||
use App\Entity\Parts\Supplier;
|
use App\Entity\Parts\Supplier;
|
||||||
use App\Entity\PriceInformations\Currency;
|
use App\Entity\PriceInformations\Currency;
|
||||||
|
use App\Entity\PriceInformations\Orderdetail;
|
||||||
|
use App\Entity\PriceInformations\Pricedetail;
|
||||||
use App\Entity\UserSystem\Group;
|
use App\Entity\UserSystem\Group;
|
||||||
use App\Entity\UserSystem\User;
|
use App\Entity\UserSystem\User;
|
||||||
use App\Exceptions\EntityNotSupportedException;
|
use App\Exceptions\EntityNotSupportedException;
|
||||||
|
@ -120,6 +124,49 @@ class EntityURLGenerator
|
||||||
throw new InvalidArgumentException('Method is not supported!');
|
throw new InvalidArgumentException('Method is not supported!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the URL to view the given element at a given timestamp
|
||||||
|
* @param $entity
|
||||||
|
* @param \DateTime $dateTime
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function timeTravelURL($entity, \DateTime $dateTime): string
|
||||||
|
{
|
||||||
|
if ($entity instanceof Part) {
|
||||||
|
return $this->urlGenerator->generate('part_info', [
|
||||||
|
'id' => $entity->getID(),
|
||||||
|
'timestamp' => $dateTime->getTimestamp()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($entity instanceof PartLot) {
|
||||||
|
return $this->urlGenerator->generate('part_info', [
|
||||||
|
'id' => $entity->getPart()->getID(),
|
||||||
|
'timestamp' => $dateTime->getTimestamp()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($entity instanceof PartAttachment) {
|
||||||
|
return $this->urlGenerator->generate('part_info', [
|
||||||
|
'id' => $entity->getElement()->getID(),
|
||||||
|
'timestamp' => $dateTime->getTimestamp()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($entity instanceof Orderdetail) {
|
||||||
|
return $this->urlGenerator->generate('part_info', [
|
||||||
|
'id' => $entity->getPart()->getID(),
|
||||||
|
'timestamp' => $dateTime->getTimestamp()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($entity instanceof Pricedetail) {
|
||||||
|
return $this->urlGenerator->generate('part_info', [
|
||||||
|
'id' => $entity->getOrderdetail()->getPart()->getID(),
|
||||||
|
'timestamp' => $dateTime->getTimestamp()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Otherwise throw an error
|
||||||
|
throw new EntityNotSupportedException('The given entity is not supported yet!');
|
||||||
|
}
|
||||||
|
|
||||||
public function viewURL($entity): string
|
public function viewURL($entity): string
|
||||||
{
|
{
|
||||||
if ($entity instanceof Attachment) {
|
if ($entity instanceof Attachment) {
|
||||||
|
|
57
src/Services/LogSystem/HistoryHelper.php
Normal file
57
src/Services/LogSystem/HistoryHelper.php
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2020 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\Services\LogSystem;
|
||||||
|
|
||||||
|
|
||||||
|
use App\Entity\Base\AbstractDBElement;
|
||||||
|
use App\Entity\Parts\Part;
|
||||||
|
|
||||||
|
class HistoryHelper
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array containing all elements that are associated with the argument.
|
||||||
|
* The returned array contains the given element.
|
||||||
|
* @param AbstractDBElement $element
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getAssociatedElements(AbstractDBElement $element): array
|
||||||
|
{
|
||||||
|
$array = [$element];
|
||||||
|
if ($element instanceof Part) {
|
||||||
|
$array = array_merge(
|
||||||
|
$array,
|
||||||
|
$element->getAttachments()->toArray(),
|
||||||
|
$element->getPartLots()->toArray(),
|
||||||
|
$element->getOrderdetails()->toArray()
|
||||||
|
);
|
||||||
|
foreach ($element->getOrderdetails() as $orderdetail) {
|
||||||
|
$array = array_merge($array, $orderdetail->getPricedetails()->toArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
}
|
3
templates/Parts/info/_history.html.twig
Normal file
3
templates/Parts/info/_history.html.twig
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<div class="mt-2">
|
||||||
|
{% include "Parts/lists/_parts_list.html.twig" %}
|
||||||
|
</div>
|
|
@ -23,7 +23,9 @@
|
||||||
</h5>
|
</h5>
|
||||||
<h3>{{ part.name }}
|
<h3>{{ part.name }}
|
||||||
{# You need edit permission to use the edit button #}
|
{# You need edit permission to use the edit button #}
|
||||||
{% if is_granted('edit', part) %}
|
{% if timeTravel is not null %}
|
||||||
|
<a href="{{ part|entityURL('info') }}"><i title="{% trans %}part.back_to_info{% endtrans %}" class="fas fa-fw fa-undo"></i></a>
|
||||||
|
{% elseif is_granted('edit', part) %}
|
||||||
<a href="{{ part|entityURL('edit') }}"><i class="fas fa-fw fa-sm fa-edit"></i></a>
|
<a href="{{ part|entityURL('edit') }}"><i class="fas fa-fw fa-sm fa-edit"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
{% import "helper.twig" as helper %}
|
{% import "helper.twig" as helper %}
|
||||||
|
|
||||||
|
{% if timeTravel is not null %}
|
||||||
|
<b class="mb-2">{% trans with {'%timestamp%': timeTravel|format_datetime('short')} %}part.info.timetravel_hint{% endtrans %}</b>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<span class="text-muted" title="{% trans %}lastModified{% endtrans %}">
|
<span class="text-muted" title="{% trans %}lastModified{% endtrans %}">
|
||||||
<i class="fas fa-history fa-fw"></i> {{ helper.date_user_combination(part, true) }}
|
<i class="fas fa-history fa-fw"></i> {{ helper.date_user_combination(part, true) }}
|
||||||
|
|
|
@ -114,13 +114,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane fade" id="history" role="tabpanel" aria-labelledby="history-tab">
|
<div class="tab-pane fade" id="history" role="tabpanel" aria-labelledby="history-tab">
|
||||||
TODO
|
{% include "Parts/info/_history.html.twig" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane fade" id="tools" role="tabpanel" aria-labelledby="tools-tab">
|
<div class="tab-pane fade" id="tools" role="tabpanel" aria-labelledby="tools-tab">
|
||||||
|
|
||||||
{% include "Parts/info/_tools.html.twig" %}
|
{% include "Parts/info/_tools.html.twig" %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane fade" id="extended_info" role="tabpanel" aria-labelledby="extended_info-tab">
|
<div class="tab-pane fade" id="extended_info" role="tabpanel" aria-labelledby="extended_info-tab">
|
||||||
|
|
|
@ -6395,5 +6395,11 @@ Element 3</target>
|
||||||
<target>Element gelöscht</target>
|
<target>Element gelöscht</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
|
<unit id="tagdXMa" name="part.info.timetravel_hint">
|
||||||
|
<segment>
|
||||||
|
<source>part.info.timetravel_hint</source>
|
||||||
|
<target>part.info.timetravel_hint</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|
|
@ -6373,5 +6373,11 @@ Element 3</target>
|
||||||
<target>System log</target>
|
<target>System log</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
|
<unit id="tagdXMa" name="part.info.timetravel_hint">
|
||||||
|
<segment>
|
||||||
|
<source>part.info.timetravel_hint</source>
|
||||||
|
<target><![CDATA[This is how the part appeared on %timestamp%. <i>Please note that this feature is experimental, so the infos are maybe not correct.</i>]]></target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue