mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-21 07:03:49 +00:00
Merge remote-tracking branch 'origin/master' into issue715_household_move_email
This commit is contained in:
@@ -23,6 +23,9 @@ interface DocGeneratorContextWithPublicFormInterface extends DocGeneratorContext
|
||||
*/
|
||||
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void;
|
||||
|
||||
/**
|
||||
* Fill the form with initial data
|
||||
*/
|
||||
public function getFormData(DocGeneratorTemplate $template, $entity): array;
|
||||
|
||||
/**
|
||||
@@ -31,4 +34,14 @@ interface DocGeneratorContextWithPublicFormInterface extends DocGeneratorContext
|
||||
* @param mixed $entity
|
||||
*/
|
||||
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool;
|
||||
|
||||
/**
|
||||
* Transform the data from the form into serializable data, storable into messenger's message
|
||||
*/
|
||||
public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array;
|
||||
|
||||
/**
|
||||
* Reverse the data from the messenger's message into data usable for doc's generation
|
||||
*/
|
||||
public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array;
|
||||
}
|
||||
|
@@ -16,68 +16,58 @@ use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
|
||||
use Chill\DocGeneratorBundle\Context\Exception\ContextNotFoundException;
|
||||
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||
use Chill\DocGeneratorBundle\GeneratorDriver\DriverInterface;
|
||||
use Chill\DocGeneratorBundle\GeneratorDriver\Exception\TemplateException;
|
||||
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository;
|
||||
use Chill\DocGeneratorBundle\Service\Generator\GeneratorInterface;
|
||||
use Chill\DocGeneratorBundle\Service\Messenger\RequestGenerationMessage;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Chill\MainBundle\Serializer\Model\Collection;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
// TODO à mettre dans services
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Throwable;
|
||||
use function strlen;
|
||||
use const JSON_PRETTY_PRINT;
|
||||
|
||||
final class DocGeneratorTemplateController extends AbstractController
|
||||
{
|
||||
private HttpClientInterface $client;
|
||||
|
||||
private ContextManager $contextManager;
|
||||
|
||||
private DocGeneratorTemplateRepository $docGeneratorTemplateRepository;
|
||||
|
||||
private DriverInterface $driver;
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
private LoggerInterface $logger;
|
||||
private GeneratorInterface $generator;
|
||||
|
||||
private MessageBusInterface $messageBus;
|
||||
|
||||
private PaginatorFactory $paginatorFactory;
|
||||
|
||||
private StoredObjectManagerInterface $storedObjectManager;
|
||||
|
||||
public function __construct(
|
||||
ContextManager $contextManager,
|
||||
DocGeneratorTemplateRepository $docGeneratorTemplateRepository,
|
||||
DriverInterface $driver,
|
||||
LoggerInterface $logger,
|
||||
GeneratorInterface $generator,
|
||||
MessageBusInterface $messageBus,
|
||||
PaginatorFactory $paginatorFactory,
|
||||
HttpClientInterface $client,
|
||||
StoredObjectManagerInterface $storedObjectManager,
|
||||
EntityManagerInterface $entityManager
|
||||
) {
|
||||
$this->contextManager = $contextManager;
|
||||
$this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository;
|
||||
$this->driver = $driver;
|
||||
$this->logger = $logger;
|
||||
$this->generator = $generator;
|
||||
$this->messageBus = $messageBus;
|
||||
$this->paginatorFactory = $paginatorFactory;
|
||||
$this->client = $client;
|
||||
$this->storedObjectManager = $storedObjectManager;
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
@@ -95,7 +85,6 @@ final class DocGeneratorTemplateController extends AbstractController
|
||||
): Response {
|
||||
return $this->generateDocFromTemplate(
|
||||
$template,
|
||||
$entityClassName,
|
||||
$entityId,
|
||||
$request,
|
||||
true
|
||||
@@ -116,7 +105,6 @@ final class DocGeneratorTemplateController extends AbstractController
|
||||
): Response {
|
||||
return $this->generateDocFromTemplate(
|
||||
$template,
|
||||
$entityClassName,
|
||||
$entityId,
|
||||
$request,
|
||||
false
|
||||
@@ -186,7 +174,6 @@ final class DocGeneratorTemplateController extends AbstractController
|
||||
|
||||
private function generateDocFromTemplate(
|
||||
DocGeneratorTemplate $template,
|
||||
string $entityClassName,
|
||||
int $entityId,
|
||||
Request $request,
|
||||
bool $isTest
|
||||
@@ -207,7 +194,7 @@ final class DocGeneratorTemplateController extends AbstractController
|
||||
|
||||
if (null === $entity) {
|
||||
throw new NotFoundHttpException(
|
||||
sprintf('Entity with classname %s and id %s is not found', $entityClassName, $entityId)
|
||||
sprintf('Entity with classname %s and id %s is not found', $context->getEntityClass(), $entityId)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -260,99 +247,68 @@ final class DocGeneratorTemplateController extends AbstractController
|
||||
}
|
||||
}
|
||||
|
||||
$document = $template->getFile();
|
||||
|
||||
if ($isTest && ($contextGenerationData['test_file'] instanceof File)) {
|
||||
$dataDecrypted = file_get_contents($contextGenerationData['test_file']->getPathname());
|
||||
} else {
|
||||
try {
|
||||
$dataDecrypted = $this->storedObjectManager->read($document);
|
||||
} catch (Throwable $exception) {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
// transform context generation data
|
||||
$contextGenerationDataSanitized =
|
||||
$context instanceof DocGeneratorContextWithPublicFormInterface ?
|
||||
$context->contextGenerationDataNormalize($template, $entity, $contextGenerationData)
|
||||
: [];
|
||||
|
||||
// if is test, render the data or generate the doc
|
||||
if ($isTest && isset($form) && $form['show_data']->getData()) {
|
||||
return $this->render('@ChillDocGenerator/Generator/debug_value.html.twig', [
|
||||
'datas' => json_encode($context->getData($template, $entity, $contextGenerationData), JSON_PRETTY_PRINT),
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$generatedResource = $this
|
||||
->driver
|
||||
->generateFromString(
|
||||
$dataDecrypted,
|
||||
$template->getFile()->getType(),
|
||||
$context->getData($template, $entity, $contextGenerationData),
|
||||
$template->getFile()->getFilename()
|
||||
);
|
||||
} catch (TemplateException $e) {
|
||||
return new Response(
|
||||
implode("\n", $e->getErrors()),
|
||||
400,
|
||||
[
|
||||
'Content-Type' => 'text/plain',
|
||||
]
|
||||
} elseif ($isTest) {
|
||||
$generated = $this->generator->generateDocFromTemplate(
|
||||
$template,
|
||||
$entityId,
|
||||
$contextGenerationDataSanitized,
|
||||
null,
|
||||
true,
|
||||
isset($form) ? $form['test_file']->getData() : null
|
||||
);
|
||||
}
|
||||
|
||||
if ($isTest) {
|
||||
return new Response(
|
||||
$generatedResource,
|
||||
$generated,
|
||||
Response::HTTP_OK,
|
||||
[
|
||||
'Content-Transfer-Encoding', 'binary',
|
||||
'Content-Type' => 'application/vnd.oasis.opendocument.text',
|
||||
'Content-Disposition' => 'attachment; filename="generated.odt"',
|
||||
'Content-Length' => strlen($generatedResource),
|
||||
'Content-Length' => strlen($generated),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/** @var StoredObject $storedObject */
|
||||
$storedObject = (new ObjectNormalizer())
|
||||
->denormalize(
|
||||
[
|
||||
'type' => $template->getFile()->getType(),
|
||||
'filename' => sprintf('%s_odt', uniqid('doc_', true)),
|
||||
],
|
||||
StoredObject::class
|
||||
);
|
||||
|
||||
try {
|
||||
$this->storedObjectManager->write($storedObject, $generatedResource);
|
||||
} catch (Throwable $exception) {
|
||||
throw $exception;
|
||||
}
|
||||
// this is not a test
|
||||
// we prepare the object to store the document
|
||||
$storedObject = (new StoredObject())
|
||||
->setStatus(StoredObject::STATUS_PENDING)
|
||||
;
|
||||
|
||||
$this->entityManager->persist($storedObject);
|
||||
|
||||
try {
|
||||
$context
|
||||
->storeGenerated(
|
||||
$template,
|
||||
$storedObject,
|
||||
$entity,
|
||||
$contextGenerationData
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$this
|
||||
->logger
|
||||
->error(
|
||||
'Unable to store the associated document to entity',
|
||||
[
|
||||
'entityClassName' => $entityClassName,
|
||||
'entityId' => $entityId,
|
||||
'contextKey' => $context->getName(),
|
||||
]
|
||||
);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
// we store the generated document
|
||||
$context
|
||||
->storeGenerated(
|
||||
$template,
|
||||
$storedObject,
|
||||
$entity,
|
||||
$contextGenerationData
|
||||
);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->messageBus->dispatch(
|
||||
new RequestGenerationMessage(
|
||||
$this->getUser(),
|
||||
$template,
|
||||
$entityId,
|
||||
$storedObject,
|
||||
$contextGenerationDataSanitized,
|
||||
)
|
||||
);
|
||||
|
||||
return $this
|
||||
->redirectToRoute(
|
||||
'chill_wopi_file_edit',
|
||||
|
@@ -0,0 +1,16 @@
|
||||
{{ creator.label }},
|
||||
|
||||
{{ 'docgen.failure_email.The generation of the document {template_name} failed'|trans({'{template_name}': template.name|localize_translatable_string}) }}
|
||||
|
||||
{{ 'docgen.failure_email.Forward this email to your administrator for solving'|trans }}
|
||||
|
||||
{{ 'docgen.failure_email.References'|trans }}:
|
||||
{% if errors|length > 0 %}
|
||||
{{ 'docgen.failure_email.The following errors were encoutered'|trans }}:
|
||||
|
||||
{% for error in errors %}
|
||||
- {{ error }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
- template_id: {{ template.id }}
|
||||
- stored_object_destination_id: {{ stored_object_id }}
|
@@ -1,29 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Service\Generator;
|
||||
|
||||
use Chill\DocGeneratorBundle\Context\ContextManagerInterface;
|
||||
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
|
||||
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||
use Chill\DocGeneratorBundle\GeneratorDriver\DriverInterface;
|
||||
use Chill\DocGeneratorBundle\GeneratorDriver\Exception\TemplateException;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
use Throwable;
|
||||
|
||||
class Generator
|
||||
class Generator implements GeneratorInterface
|
||||
{
|
||||
private ContextManagerInterface $contextManager;
|
||||
|
||||
@@ -35,6 +25,8 @@ class Generator
|
||||
|
||||
private StoredObjectManagerInterface $storedObjectManager;
|
||||
|
||||
private const LOG_PREFIX = '[docgen generator] ';
|
||||
|
||||
public function __construct(
|
||||
ContextManagerInterface $contextManager,
|
||||
DriverInterface $driver,
|
||||
@@ -52,49 +44,69 @@ class Generator
|
||||
/**
|
||||
* @template T of File|null
|
||||
* @template B of bool
|
||||
*
|
||||
* @param B $isTest
|
||||
* @param (B is true ? T : null) $testFile
|
||||
* @psalm-return (B is true ? string : null)
|
||||
*
|
||||
* @throws \Symfony\Component\Serializer\Exception\ExceptionInterface|Throwable
|
||||
* @throws \Symfony\Component\Serializer\Exception\ExceptionInterface|\Throwable
|
||||
*/
|
||||
public function generateDocFromTemplate(
|
||||
DocGeneratorTemplate $template,
|
||||
string $entityClassName,
|
||||
int $entityId,
|
||||
?StoredObject $destinationStoredObject = null,
|
||||
bool $isTest = false,
|
||||
?File $testFile = null
|
||||
int $entityId,
|
||||
array $contextGenerationDataNormalized,
|
||||
?StoredObject $destinationStoredObject = null,
|
||||
bool $isTest = false,
|
||||
?File $testFile = null
|
||||
): ?string {
|
||||
if ($destinationStoredObject instanceof StoredObject && StoredObject::STATUS_PENDING !== $destinationStoredObject->getStatus()) {
|
||||
$this->logger->info(self::LOG_PREFIX.'Aborting generation of an already generated document');
|
||||
throw new ObjectReadyException();
|
||||
}
|
||||
|
||||
$this->logger->info(self::LOG_PREFIX.'Starting generation of a document', [
|
||||
'entity_id' => $entityId,
|
||||
'destination_stored_object' => $destinationStoredObject === null ? null : $destinationStoredObject->getId()
|
||||
]);
|
||||
|
||||
$context = $this->contextManager->getContextByDocGeneratorTemplate($template);
|
||||
$contextGenerationData = ['test_file' => $testFile];
|
||||
|
||||
$entity = $this
|
||||
->entityManager
|
||||
->find($context->getEntityClass(), $entityId);
|
||||
->find($context->getEntityClass(), $entityId)
|
||||
;
|
||||
|
||||
if (null === $entity) {
|
||||
throw new RelatedEntityNotFoundException($entityClassName, $entityId);
|
||||
throw new RelatedEntityNotFoundException($template->getEntity(), $entityId);
|
||||
}
|
||||
|
||||
$contextGenerationDataNormalized = array_merge(
|
||||
$contextGenerationDataNormalized,
|
||||
$context instanceof DocGeneratorContextWithPublicFormInterface ?
|
||||
$context->contextGenerationDataDenormalize($template, $entity, $contextGenerationDataNormalized)
|
||||
: []
|
||||
);
|
||||
|
||||
$data = $context->getData($template, $entity, $contextGenerationDataNormalized);
|
||||
|
||||
$destinationStoredObjectId = $destinationStoredObject instanceof StoredObject ? $destinationStoredObject->getId() : null;
|
||||
$this->entityManager->clear();
|
||||
gc_collect_cycles();
|
||||
if (null !== $destinationStoredObjectId) {
|
||||
$destinationStoredObject = $this->entityManager->find(StoredObject::class, $destinationStoredObjectId);
|
||||
}
|
||||
|
||||
if ($isTest && ($testFile instanceof File)) {
|
||||
$dataDecrypted = file_get_contents($testFile->getPathname());
|
||||
$templateDecrypted = file_get_contents($testFile->getPathname());
|
||||
} else {
|
||||
$dataDecrypted = $this->storedObjectManager->read($template->getFile());
|
||||
$templateDecrypted = $this->storedObjectManager->read($template->getFile());
|
||||
}
|
||||
|
||||
try {
|
||||
$generatedResource = $this
|
||||
->driver
|
||||
->generateFromString(
|
||||
$dataDecrypted,
|
||||
$templateDecrypted,
|
||||
$template->getFile()->getType(),
|
||||
$context->getData($template, $entity, $contextGenerationData),
|
||||
$data,
|
||||
$template->getFile()->getFilename()
|
||||
);
|
||||
} catch (TemplateException $e) {
|
||||
@@ -102,6 +114,11 @@ class Generator
|
||||
}
|
||||
|
||||
if ($isTest) {
|
||||
$this->logger->info(self::LOG_PREFIX.'Finished generation of a document', [
|
||||
'is_test' => true,
|
||||
'entity_id' => $entityId,
|
||||
'destination_stored_object' => $destinationStoredObject === null ? null : $destinationStoredObject->getId()
|
||||
]);
|
||||
return $generatedResource;
|
||||
}
|
||||
|
||||
@@ -109,35 +126,18 @@ class Generator
|
||||
$destinationStoredObject
|
||||
->setType($template->getFile()->getType())
|
||||
->setFilename(sprintf('%s_odt', uniqid('doc_', true)))
|
||||
->setStatus(StoredObject::STATUS_READY);
|
||||
->setStatus(StoredObject::STATUS_READY)
|
||||
;
|
||||
|
||||
$this->storedObjectManager->write($destinationStoredObject, $generatedResource);
|
||||
|
||||
try {
|
||||
$context
|
||||
->storeGenerated(
|
||||
$template,
|
||||
$destinationStoredObject,
|
||||
$entity,
|
||||
$contextGenerationData
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$this
|
||||
->logger
|
||||
->error(
|
||||
'Unable to store the associated document to entity',
|
||||
[
|
||||
'entityClassName' => $entityClassName,
|
||||
'entityId' => $entityId,
|
||||
'contextKey' => $context->getName(),
|
||||
]
|
||||
);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->logger->info(self::LOG_PREFIX.'Finished generation of a document', [
|
||||
'entity_id' => $entityId,
|
||||
'destination_stored_object' => $destinationStoredObject->getId(),
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -30,4 +30,12 @@ class GeneratorException extends RuntimeException
|
||||
$previous
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getErrors(): array
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Service\Generator;
|
||||
|
||||
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
interface GeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @template T of File|null
|
||||
* @template B of bool
|
||||
* @param B $isTest
|
||||
* @param (B is true ? T : null) $testFile
|
||||
* @psalm-return (B is true ? string : null)
|
||||
* @throws \Symfony\Component\Serializer\Exception\ExceptionInterface|\Throwable
|
||||
*/
|
||||
public function generateDocFromTemplate(
|
||||
DocGeneratorTemplate $template,
|
||||
int $entityId,
|
||||
array $contextGenerationDataNormalized,
|
||||
?StoredObject $destinationStoredObject = null,
|
||||
bool $isTest = false,
|
||||
?File $testFile = null
|
||||
): ?string;
|
||||
}
|
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Service\Messenger;
|
||||
|
||||
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository;
|
||||
use Chill\DocGeneratorBundle\Service\Generator\GeneratorException;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
|
||||
use Chill\MainBundle\Repository\UserRepositoryInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
final class OnGenerationFails implements EventSubscriberInterface
|
||||
{
|
||||
private DocGeneratorTemplateRepository $docGeneratorTemplateRepository;
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
private LoggerInterface $logger;
|
||||
|
||||
private MailerInterface $mailer;
|
||||
|
||||
private StoredObjectRepository $storedObjectRepository;
|
||||
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
private UserRepositoryInterface $userRepository;
|
||||
|
||||
const LOG_PREFIX = '[docgen failed] ';
|
||||
|
||||
/**
|
||||
* @param DocGeneratorTemplateRepository $docGeneratorTemplateRepository
|
||||
* @param EntityManagerInterface $entityManager
|
||||
* @param LoggerInterface $logger
|
||||
* @param MailerInterface $mailer
|
||||
* @param StoredObjectRepository $storedObjectRepository
|
||||
* @param TranslatorInterface $translator
|
||||
* @param UserRepositoryInterface $userRepository
|
||||
*/
|
||||
public function __construct(
|
||||
DocGeneratorTemplateRepository $docGeneratorTemplateRepository,
|
||||
EntityManagerInterface $entityManager,
|
||||
LoggerInterface $logger,
|
||||
MailerInterface $mailer,
|
||||
StoredObjectRepository $storedObjectRepository,
|
||||
TranslatorInterface $translator,
|
||||
UserRepositoryInterface $userRepository
|
||||
) {
|
||||
$this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->logger = $logger;
|
||||
$this->mailer = $mailer;
|
||||
$this->storedObjectRepository = $storedObjectRepository;
|
||||
$this->translator = $translator;
|
||||
$this->userRepository = $userRepository;
|
||||
}
|
||||
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
WorkerMessageFailedEvent::class => 'onMessageFailed'
|
||||
];
|
||||
}
|
||||
|
||||
public function onMessageFailed(WorkerMessageFailedEvent $event): void
|
||||
{
|
||||
if ($event->willRetry()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$event->getEnvelope()->getMessage() instanceof RequestGenerationMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var \Chill\DocGeneratorBundle\Service\Messenger\RequestGenerationMessage $message */
|
||||
$message = $event->getEnvelope()->getMessage();
|
||||
|
||||
$this->logger->error(self::LOG_PREFIX.'Docgen failed', [
|
||||
'stored_object_id' => $message->getDestinationStoredObjectId(),
|
||||
'entity_id' => $message->getEntityId(),
|
||||
'template_id' => $message->getTemplateId(),
|
||||
'creator_id' => $message->getCreatorId(),
|
||||
'throwable_class' => get_class($event->getThrowable()),
|
||||
]);
|
||||
|
||||
$this->markObjectAsFailed($message);
|
||||
$this->warnCreator($message, $event);
|
||||
}
|
||||
|
||||
private function markObjectAsFailed(RequestGenerationMessage $message): void
|
||||
{
|
||||
$object = $this->storedObjectRepository->find($message->getDestinationStoredObjectId());
|
||||
|
||||
if (null === $object) {
|
||||
$this->logger->error(self::LOG_PREFIX.'Stored object not found', ['stored_object_id', $message->getDestinationStoredObjectId()]);
|
||||
}
|
||||
|
||||
$object->setStatus(StoredObject::STATUS_FAILURE);
|
||||
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
private function warnCreator(RequestGenerationMessage $message, WorkerMessageFailedEvent $event): void
|
||||
{
|
||||
if (null === $creatorId = $message->getCreatorId()) {
|
||||
$this->logger->info(self::LOG_PREFIX.'creator id is null');
|
||||
return;
|
||||
}
|
||||
|
||||
if (null === $creator = $this->userRepository->find($creatorId)) {
|
||||
$this->logger->error(self::LOG_PREFIX.'Creator not found with given id', ['creator_id', $creatorId]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (null === $creator->getEmail() || '' === $creator->getEmail()) {
|
||||
$this->logger->info(self::LOG_PREFIX.'Creator does not have any email', ['user' => $creator->getUsernameCanonical()]);
|
||||
return;
|
||||
}
|
||||
|
||||
// if the exception is not a GeneratorException, we try the previous one...
|
||||
$throwable = $event->getThrowable();
|
||||
if (!$throwable instanceof GeneratorException) {
|
||||
$throwable = $throwable->getPrevious();
|
||||
}
|
||||
|
||||
if ($throwable instanceof GeneratorException) {
|
||||
$errors = $throwable->getErrors();
|
||||
} else {
|
||||
$errors = [$throwable->getTraceAsString()];
|
||||
}
|
||||
|
||||
if (null === $template = $this->docGeneratorTemplateRepository->find($message->getTemplateId())) {
|
||||
$this->logger->info(self::LOG_PREFIX.'Template not found', ['template_id' => $message->getTemplateId()]);
|
||||
return;
|
||||
}
|
||||
|
||||
$email = (new TemplatedEmail())
|
||||
->to($creator->getEmail())
|
||||
->subject($this->translator->trans('docgen.failure_email.The generation of a document failed'))
|
||||
->textTemplate('@ChillDocGenerator/Email/on_generation_failed_email.txt.twig')
|
||||
->context([
|
||||
'errors' => $errors,
|
||||
'template' => $template,
|
||||
'creator' => $creator,
|
||||
'stored_object_id' => $message->getDestinationStoredObjectId(),
|
||||
]);
|
||||
|
||||
$this->mailer->send($email);
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Service\Messenger;
|
||||
|
||||
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository;
|
||||
use Chill\DocGeneratorBundle\Service\Generator\Generator;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
|
||||
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
|
||||
|
||||
/**
|
||||
* Handle the request of document generation
|
||||
*/
|
||||
class RequestGenerationHandler implements MessageHandlerInterface
|
||||
{
|
||||
private StoredObjectRepository $storedObjectRepository;
|
||||
|
||||
private DocGeneratorTemplateRepository $docGeneratorTemplateRepository;
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
private Generator $generator;
|
||||
|
||||
public const AUTHORIZED_TRIALS = 5;
|
||||
|
||||
public function __construct(
|
||||
DocGeneratorTemplateRepository $docGeneratorTemplateRepository,
|
||||
EntityManagerInterface $entityManager,
|
||||
Generator $generator,
|
||||
StoredObjectRepository $storedObjectRepository
|
||||
) {
|
||||
$this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->generator = $generator;
|
||||
$this->storedObjectRepository = $storedObjectRepository;
|
||||
}
|
||||
|
||||
public function __invoke(RequestGenerationMessage $message)
|
||||
{
|
||||
if (null === $template = $this->docGeneratorTemplateRepository->find($message->getTemplateId())) {
|
||||
throw new \RuntimeException('template not found: ' . $message->getTemplateId());
|
||||
}
|
||||
|
||||
if (null === $destinationStoredObject = $this->storedObjectRepository->find($message->getDestinationStoredObjectId())) {
|
||||
throw new \RuntimeException('destination stored object not found : ' . $message->getDestinationStoredObjectId());
|
||||
}
|
||||
|
||||
if ($destinationStoredObject->getGenerationTrialsCounter() >= self::AUTHORIZED_TRIALS) {
|
||||
throw new UnrecoverableMessageHandlingException('maximum number of retry reached');
|
||||
}
|
||||
|
||||
$destinationStoredObject->addGenerationTrial();
|
||||
$this->entityManager->createQuery('UPDATE '.StoredObject::class.' s SET s.generationTrialsCounter = s.generationTrialsCounter + 1 WHERE s.id = :id')
|
||||
->setParameter('id', $destinationStoredObject->getId())
|
||||
->execute();
|
||||
|
||||
$this->generator->generateDocFromTemplate(
|
||||
$template,
|
||||
$message->getEntityId(),
|
||||
$message->getContextGenerationData(),
|
||||
$destinationStoredObject
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,35 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Service\Messenger;
|
||||
|
||||
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
|
||||
class RequestGenerationMessage
|
||||
{
|
||||
private int $creatorId;
|
||||
private int $creatorId;
|
||||
|
||||
private string $entityClassName;
|
||||
private int $templateId;
|
||||
|
||||
private int $entityId;
|
||||
private int $entityId;
|
||||
|
||||
private int $templateId;
|
||||
private int $destinationStoredObjectId;
|
||||
|
||||
public function __construct(User $creator, DocGeneratorTemplate $template, int $entityId, string $entityClassName)
|
||||
{
|
||||
private array $contextGenerationData;
|
||||
|
||||
public function __construct(
|
||||
User $creator,
|
||||
DocGeneratorTemplate $template,
|
||||
int $entityId,
|
||||
StoredObject $destinationStoredObject,
|
||||
array $contextGenerationData
|
||||
) {
|
||||
$this->creatorId = $creator->getId();
|
||||
$this->templateId = $template->getId();
|
||||
$this->entityId = $entityId;
|
||||
$this->entityClassName = $entityClassName;
|
||||
$this->destinationStoredObjectId = $destinationStoredObject->getId();
|
||||
$this->contextGenerationData = $contextGenerationData;
|
||||
}
|
||||
|
||||
public function getCreatorId(): int
|
||||
@@ -37,9 +37,14 @@ class RequestGenerationMessage
|
||||
return $this->creatorId;
|
||||
}
|
||||
|
||||
public function getEntityClassName(): string
|
||||
public function getDestinationStoredObjectId(): int
|
||||
{
|
||||
return $this->entityClassName;
|
||||
return $this->destinationStoredObjectId;
|
||||
}
|
||||
|
||||
public function getTemplateId(): int
|
||||
{
|
||||
return $this->templateId;
|
||||
}
|
||||
|
||||
public function getEntityId(): int
|
||||
@@ -47,8 +52,8 @@ class RequestGenerationMessage
|
||||
return $this->entityId;
|
||||
}
|
||||
|
||||
public function getTemplateId(): int
|
||||
public function getContextGenerationData(): array
|
||||
{
|
||||
return $this->templateId;
|
||||
return $this->contextGenerationData;
|
||||
}
|
||||
}
|
||||
|
@@ -20,10 +20,14 @@ services:
|
||||
resource: '../Serializer/Normalizer/'
|
||||
tags:
|
||||
- { name: 'serializer.normalizer', priority: -152 }
|
||||
|
||||
Chill\DocGeneratorBundle\Serializer\Normalizer\CollectionDocGenNormalizer:
|
||||
tags:
|
||||
- { name: 'serializer.normalizer', priority: -126 }
|
||||
|
||||
Chill\DocGeneratorBundle\Service\Context\:
|
||||
resource: "../Service/Context"
|
||||
|
||||
Chill\DocGeneratorBundle\Controller\:
|
||||
resource: "../Controller"
|
||||
autowire: true
|
||||
@@ -34,18 +38,20 @@ services:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\DocGeneratorBundle\Service\Context\:
|
||||
resource: "../Service/Context/"
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\DocGeneratorBundle\GeneratorDriver\:
|
||||
resource: "../GeneratorDriver/"
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\DocGeneratorBundle\Service\Messenger\:
|
||||
resource: "../Service/Messenger/"
|
||||
|
||||
Chill\DocGeneratorBundle\Service\Generator\Generator: ~
|
||||
Chill\DocGeneratorBundle\Service\Generator\GeneratorInterface: '@Chill\DocGeneratorBundle\Service\Generator\Generator'
|
||||
|
||||
Chill\DocGeneratorBundle\Driver\RelatorioDriver: '@Chill\DocGeneratorBundle\Driver\DriverInterface'
|
||||
|
||||
Chill\DocGeneratorBundle\Context\ContextManager:
|
||||
arguments:
|
||||
$contexts: !tagged_iterator { tag: chill_docgen.context, default_index_method: getKey }
|
||||
Chill\DocGeneratorBundle\Context\ContextManagerInterface: '@Chill\DocGeneratorBundle\Context\ContextManager'
|
||||
|
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\DocGenerator;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20230214192558 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add status, template_id and fix defaults on chill_doc.stored_object';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object ADD template_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object ADD status TEXT DEFAULT \'ready\' NOT NULL');
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object ADD createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
|
||||
$this->addSql('UPDATE chill_doc.stored_object SET createdAt = creation_date');
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object ADD createdBy_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object DROP creation_date;');
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object ALTER type SET DEFAULT \'\'');
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object ALTER title DROP DEFAULT');
|
||||
$this->addSql('COMMENT ON COLUMN chill_doc.stored_object.createdAt IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object ADD CONSTRAINT FK_49604E365DA0FB8 FOREIGN KEY (template_id) REFERENCES chill_docgen_template (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object ADD CONSTRAINT FK_49604E363174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('CREATE INDEX IDX_49604E365DA0FB8 ON chill_doc.stored_object (template_id)');
|
||||
$this->addSql('CREATE INDEX IDX_49604E363174800F ON chill_doc.stored_object (createdBy_id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object DROP CONSTRAINT FK_49604E365DA0FB8');
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object DROP CONSTRAINT FK_49604E363174800F');
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object DROP template_id');
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object DROP status');
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object ADD creation_date TIMESTAMP(0) DEFAULT NOW()');
|
||||
$this->addSql('UPDATE chill_doc.stored_object SET creation_date = createdAt');
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object DROP createdAt');
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object DROP createdBy_id');
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object ALTER title SET DEFAULT \'\'');
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object ALTER type DROP DEFAULT');
|
||||
}
|
||||
}
|
@@ -1,14 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\DocGeneratorBundle\tests\Service\Context\Generator;
|
||||
|
||||
use Chill\DocGeneratorBundle\Context\ContextManagerInterface;
|
||||
@@ -26,14 +17,64 @@ use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
final class GeneratorTest extends TestCase
|
||||
class GeneratorTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
public function testSuccessfulGeneration(): void
|
||||
{
|
||||
$template = (new DocGeneratorTemplate())->setFile($templateStoredObject = (new StoredObject())
|
||||
->setType('application/test'));
|
||||
$destinationStoredObject = (new StoredObject())->setStatus(StoredObject::STATUS_PENDING);
|
||||
$reflection = new \ReflectionClass($destinationStoredObject);
|
||||
$reflection->getProperty('id')->setAccessible(true);
|
||||
$reflection->getProperty('id')->setValue($destinationStoredObject, 1);
|
||||
$entity = new class {};
|
||||
$data = [];
|
||||
|
||||
$context = $this->prophesize(DocGeneratorContextInterface::class);
|
||||
$context->getData($template, $entity, Argument::type('array'))->willReturn($data);
|
||||
$context->getName()->willReturn('dummy_context');
|
||||
$context->getEntityClass()->willReturn('DummyClass');
|
||||
$context = $context->reveal();
|
||||
|
||||
$contextManagerInterface = $this->prophesize(ContextManagerInterface::class);
|
||||
$contextManagerInterface->getContextByDocGeneratorTemplate($template)
|
||||
->willReturn($context);
|
||||
|
||||
$driver = $this->prophesize(DriverInterface::class);
|
||||
$driver->generateFromString('template', 'application/test', $data, Argument::any())
|
||||
->willReturn('generated');
|
||||
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$entityManager->find(StoredObject::class, 1)
|
||||
->willReturn($destinationStoredObject);
|
||||
$entityManager->find('DummyClass', Argument::type('int'))
|
||||
->willReturn($entity);
|
||||
$entityManager->clear()->shouldBeCalled();
|
||||
$entityManager->flush()->shouldBeCalled();
|
||||
|
||||
$storedObjectManager = $this->prophesize(StoredObjectManagerInterface::class);
|
||||
$storedObjectManager->read($templateStoredObject)->willReturn('template');
|
||||
$storedObjectManager->write($destinationStoredObject, 'generated')->shouldBeCalled();
|
||||
|
||||
|
||||
$generator = new Generator(
|
||||
$contextManagerInterface->reveal(),
|
||||
$driver->reveal(),
|
||||
$entityManager->reveal(),
|
||||
new NullLogger(),
|
||||
$storedObjectManager->reveal()
|
||||
);
|
||||
|
||||
$generator->generateDocFromTemplate(
|
||||
$template,
|
||||
1,
|
||||
[],
|
||||
$destinationStoredObject
|
||||
);
|
||||
}
|
||||
|
||||
public function testPreventRegenerateDocument(): void
|
||||
{
|
||||
$this->expectException(ObjectReadyException::class);
|
||||
@@ -52,8 +93,8 @@ final class GeneratorTest extends TestCase
|
||||
|
||||
$generator->generateDocFromTemplate(
|
||||
$template,
|
||||
'DummyEntity',
|
||||
1,
|
||||
[],
|
||||
$destinationStoredObject
|
||||
);
|
||||
}
|
||||
@@ -65,6 +106,9 @@ final class GeneratorTest extends TestCase
|
||||
$template = (new DocGeneratorTemplate())->setFile($templateStoredObject = (new StoredObject())
|
||||
->setType('application/test'));
|
||||
$destinationStoredObject = (new StoredObject())->setStatus(StoredObject::STATUS_PENDING);
|
||||
$reflection = new \ReflectionClass($destinationStoredObject);
|
||||
$reflection->getProperty('id')->setAccessible(true);
|
||||
$reflection->getProperty('id')->setValue($destinationStoredObject, 1);
|
||||
|
||||
$context = $this->prophesize(DocGeneratorContextInterface::class);
|
||||
$context->getName()->willReturn('dummy_context');
|
||||
@@ -89,58 +133,8 @@ final class GeneratorTest extends TestCase
|
||||
|
||||
$generator->generateDocFromTemplate(
|
||||
$template,
|
||||
'DummyEntity',
|
||||
1,
|
||||
$destinationStoredObject
|
||||
);
|
||||
}
|
||||
|
||||
public function testSuccessfulGeneration(): void
|
||||
{
|
||||
$template = (new DocGeneratorTemplate())->setFile($templateStoredObject = (new StoredObject())
|
||||
->setType('application/test'));
|
||||
$destinationStoredObject = (new StoredObject())->setStatus(StoredObject::STATUS_PENDING);
|
||||
$entity = new class() {
|
||||
};
|
||||
$data = [];
|
||||
|
||||
$context = $this->prophesize(DocGeneratorContextInterface::class);
|
||||
$context->getData($template, $entity, Argument::type('array'))->willReturn($data);
|
||||
$context->storeGenerated($template, $destinationStoredObject, $entity, Argument::type('array'))
|
||||
->shouldBeCalled();
|
||||
$context->getName()->willReturn('dummy_context');
|
||||
$context->getEntityClass()->willReturn('DummyClass');
|
||||
$context = $context->reveal();
|
||||
|
||||
$contextManagerInterface = $this->prophesize(ContextManagerInterface::class);
|
||||
$contextManagerInterface->getContextByDocGeneratorTemplate($template)
|
||||
->willReturn($context);
|
||||
|
||||
$driver = $this->prophesize(DriverInterface::class);
|
||||
$driver->generateFromString('template', 'application/test', $data, Argument::any())
|
||||
->willReturn('generated');
|
||||
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$entityManager->find(Argument::type('string'), Argument::type('int'))
|
||||
->willReturn($entity);
|
||||
$entityManager->flush()->shouldBeCalled();
|
||||
|
||||
$storedObjectManager = $this->prophesize(StoredObjectManagerInterface::class);
|
||||
$storedObjectManager->read($templateStoredObject)->willReturn('template');
|
||||
$storedObjectManager->write($destinationStoredObject, 'generated')->shouldBeCalled();
|
||||
|
||||
$generator = new Generator(
|
||||
$contextManagerInterface->reveal(),
|
||||
$driver->reveal(),
|
||||
$entityManager->reveal(),
|
||||
new NullLogger(),
|
||||
$storedObjectManager->reveal()
|
||||
);
|
||||
|
||||
$generator->generateDocFromTemplate(
|
||||
$template,
|
||||
'DummyEntity',
|
||||
1,
|
||||
[],
|
||||
$destinationStoredObject
|
||||
);
|
||||
}
|
||||
|
@@ -10,6 +10,16 @@ docgen:
|
||||
test generate: Tester la génération
|
||||
With context %name%: 'Avec le contexte "%name%"'
|
||||
|
||||
Doc generation failed: La génération de ce document a échoué
|
||||
Doc generation is pending: La génération de ce document est en cours
|
||||
Come back later: Revenir plus tard
|
||||
|
||||
failure_email:
|
||||
The generation of a document failed: La génération d'un document a échoué
|
||||
The generation of the document {template_name} failed: La génération d'un document à partir du modèle {{ template_name }} a échoué.
|
||||
The following errors were encoutered: Les erreurs suivantes ont été rencontrées
|
||||
Forward this email to your administrator for solving: Faites suivre ce message vers votre administrateur pour la résolution du problème.
|
||||
References: Références
|
||||
|
||||
crud:
|
||||
docgen_template:
|
||||
@@ -19,4 +29,4 @@ crud:
|
||||
|
||||
|
||||
Show data instead of generating: Montrer les données au lieu de générer le document
|
||||
Template file: Fichier modèle
|
||||
Template file: Fichier modèle
|
||||
|
Reference in New Issue
Block a user