diff --git a/.changes/unreleased/Fixed-20250626-122030.yaml b/.changes/unreleased/Fixed-20250626-122030.yaml new file mode 100644 index 000000000..72ad87552 --- /dev/null +++ b/.changes/unreleased/Fixed-20250626-122030.yaml @@ -0,0 +1,6 @@ +kind: Fixed +body: 'Doc Generation: the "dump only" method send the document as an email attachment.' +time: 2025-06-26T12:20:30.083601058+02:00 +custom: + Issue: "393" + SchemaChange: No schema change diff --git a/.junie/guidelines.md b/.junie/guidelines.md index 8ef0b7c8d..eace2f4fa 100644 --- a/.junie/guidelines.md +++ b/.junie/guidelines.md @@ -22,7 +22,7 @@ Chill is a comprehensive web application built as a set of Symfony bundles. It i - **Backend**: PHP 8.3+, Symfony 5.4 - **Frontend**: JavaScript/TypeScript, Vue.js 3, Bootstrap 5 - **Build Tools**: Webpack Encore, Yarn -- **Database**: PostgreSQL with materialized views +- **Database**: PostgreSQL with materialized views. We do not support other databases. - **Other Services**: Redis, AMQP (RabbitMQ), SMTP ## Project Structure @@ -149,6 +149,42 @@ Key configuration files: - `package.json`: JavaScript dependencies and scripts - `.env`: Default environment variables. Must usually not be updated: use `.env.local` instead. +### Database migrations + +Each time a doctrine entity is created, we generate migration to adapt the database. + +The migration are created using the command `symfony console doctrine:migrations:diff --no-interaction --namespace `, where the namespace is the relevant namespace for migration. As this is a bash script, do not forget to quote the `\` (`\` must become `\\` in your command). + +Each bundle has his own namespace for migration (always ask me to confirm that command, with a list of updated / created entities so that I can confirm you that it is ok): + +- `Chill\Bundle\ActivityBundle` writes migrations to `Chill\Migrations\Activity`; +- `Chill\Bundle\BudgetBundle` writes migrations to `Chill\Migrations\Budget`; +- `Chill\Bundle\CustomFieldsBundle` writes migrations to `Chill\Migrations\CustomFields`; +- `Chill\Bundle\DocGeneratorBundle` writes migrations to `Chill\Migrations\DocGenerator`; +- `Chill\Bundle\DocStoreBundle` writes migrations to `Chill\Migrations\DocStore`; +- `Chill\Bundle\EventBundle` writes migrations to `Chill\Migrations\Event`; +- `Chill\Bundle\CalendarBundle` writes migrations to `Chill\Migrations\Calendar`; +- `Chill\Bundle\FamilyMembersBundle` writes migrations to `Chill\Migrations\FamilyMembers`; +- `Chill\Bundle\FranceTravailApiBundle` writes migrations to `Chill\Migrations\FranceTravailApi`; +- `Chill\Bundle\JobBundle` writes migrations to `Chill\Migrations\Job`; +- `Chill\Bundle\MainBundle` writes migrations to `Chill\Migrations\Main`; +- `Chill\Bundle\PersonBundle` writes migrations to `Chill\Migrations\Person`; +- `Chill\Bundle\ReportBundle` writes migrations to `Chill\Migrations\Report`; +- `Chill\Bundle\TaskBundle` writes migrations to `Chill\Migrations\Task`; +- `Chill\Bundle\ThirdPartyBundle` writes migrations to `Chill\Migrations\ThirdParty`; +- `Chill\Bundle\TicketBundle` writes migrations to `Chill\Migrations\Ticket`; +- `Chill\Bundle\WopiBundle` writes migrations to `Chill\Migrations\Wopi`; + +Once created the, comment's classes should be removed and a description of the changes made to the entities should be added to the migrations, using the `getDescription` method. The migration should not be cleaned by any artificial intelligence, as modifying this migration is error prone. + +### Guidelines related to code structure and requirements + +#### Usage of clock + +When we need to use a DateTime or DateTimeImmutable that need to express "now", we prefer the usage of +`Symfony\Component\Clock\ClockInterface`, where possible. This is usually not possible in doctrine entities, +where injection does not work when restoring an entity from database, but usually possible in services. + ### Testing Information The project uses PHPUnit for testing. Each bundle has its own test suite, and there's also a global test suite at the root level. @@ -218,7 +254,7 @@ class TicketTest extends TestCase #### Test Database -For tests that require a database, the project uses an in-memory SQLite database by default. You can configure a different database for testing in the `.env.test` file. +For tests that require a database, the project uses postgresql database filled by fixtures (usage of doctrine-fixtures). You can configure a different database for testing in the `.env.test` file. ### Code Quality Tools diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Email/send_data_dump_to_admin.txt.twig b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Email/send_data_dump_to_admin.txt.twig index 566bfbfa3..6e4228041 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Email/send_data_dump_to_admin.txt.twig +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Email/send_data_dump_to_admin.txt.twig @@ -1,7 +1,5 @@ {{ 'docgen.data_dump_email.Dear'|trans }} -{{ 'docgen.data_dump_email.data_dump_ready_and_link'|trans }} +{{ 'docgen.data_dump_email.data_dump_ready_and_attached'|trans }} -{{ link }} - -{{ 'docgen.data_dump_email.link_valid_until'|trans({validity: validity}) }} +{{ 'docgen.data_dump_email.filename'|trans({filename: filename}) }} diff --git a/src/Bundle/ChillDocGeneratorBundle/Service/Messenger/RequestGenerationHandler.php b/src/Bundle/ChillDocGeneratorBundle/Service/Messenger/RequestGenerationHandler.php index dbd45fde0..9dd20af91 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Service/Messenger/RequestGenerationHandler.php +++ b/src/Bundle/ChillDocGeneratorBundle/Service/Messenger/RequestGenerationHandler.php @@ -11,13 +11,13 @@ declare(strict_types=1); namespace Chill\DocGeneratorBundle\Service\Messenger; -use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository; -use Chill\DocGeneratorBundle\Service\Generator\Generator; +use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepositoryInterface; use Chill\DocGeneratorBundle\Service\Generator\GeneratorException; -use Chill\DocStoreBundle\AsyncUpload\TempUrlGeneratorInterface; +use Chill\DocGeneratorBundle\Service\Generator\GeneratorInterface; use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Exception\StoredObjectManagerException; -use Chill\DocStoreBundle\Repository\StoredObjectRepository; +use Chill\DocStoreBundle\Repository\StoredObjectRepositoryInterface; +use Chill\DocStoreBundle\Service\StoredObjectManagerInterface; use Chill\MainBundle\Repository\UserRepositoryInterface; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; @@ -37,15 +37,15 @@ class RequestGenerationHandler implements MessageHandlerInterface private const LOG_PREFIX = '[docgen message handler] '; public function __construct( - private readonly DocGeneratorTemplateRepository $docGeneratorTemplateRepository, + private readonly DocGeneratorTemplateRepositoryInterface $docGeneratorTemplateRepository, private readonly EntityManagerInterface $entityManager, - private readonly Generator $generator, + private readonly GeneratorInterface $generator, private readonly LoggerInterface $logger, - private readonly StoredObjectRepository $storedObjectRepository, + private readonly StoredObjectRepositoryInterface $storedObjectRepository, private readonly UserRepositoryInterface $userRepository, private readonly MailerInterface $mailer, - private readonly TempUrlGeneratorInterface $tempUrlGenerator, private readonly TranslatorInterface $translator, + private readonly StoredObjectManagerInterface $storedObjectManager, ) {} public function __invoke(RequestGenerationMessage $message) @@ -90,7 +90,7 @@ class RequestGenerationHandler implements MessageHandlerInterface $this->sendDataDump($destinationStoredObject, $message); } else { - $destinationStoredObject = $this->generator->generateDocFromTemplate( + $this->generator->generateDocFromTemplate( $template, $message->getEntityId(), $message->getContextGenerationData(), @@ -122,19 +122,20 @@ class RequestGenerationHandler implements MessageHandlerInterface private function sendDataDump(StoredObject $destinationStoredObject, RequestGenerationMessage $message): void { - $url = $this->tempUrlGenerator->generate('GET', $destinationStoredObject->getFilename(), 3600); - $parts = []; - parse_str(parse_url($url->url)['query'], $parts); - $validity = \DateTimeImmutable::createFromFormat('U', $parts['temp_url_expires']); + // Get the content of the document + $content = $this->storedObjectManager->read($destinationStoredObject); + $filename = $destinationStoredObject->getFilename(); + $contentType = $destinationStoredObject->getType(); + // Create the email with the document as an attachment $email = (new TemplatedEmail()) ->to($message->getSendResultToEmail()) ->textTemplate('@ChillDocGenerator/Email/send_data_dump_to_admin.txt.twig') ->context([ - 'link' => $url->url, - 'validity' => $validity, + 'filename' => $filename, ]) - ->subject($this->translator->trans('docgen.data_dump_email.subject')); + ->subject($this->translator->trans('docgen.data_dump_email.subject')) + ->attach($content, $filename, $contentType); $this->mailer->send($email); } diff --git a/src/Bundle/ChillDocGeneratorBundle/Tests/Service/Messenger/RequestGenerationHandlerTest.php b/src/Bundle/ChillDocGeneratorBundle/Tests/Service/Messenger/RequestGenerationHandlerTest.php new file mode 100644 index 000000000..a0af351a4 --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Tests/Service/Messenger/RequestGenerationHandlerTest.php @@ -0,0 +1,132 @@ +setPrivateProperty($template, 'id', 1); + + $storedObject = new StoredObject(); + $this->setPrivateProperty($storedObject, 'id', 2); + + $creator = new User(); + $creator->setEmail('test@example.com'); + $this->setPrivateProperty($creator, 'id', 3); + + $docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class); + $docGeneratorTemplateRepository->find(1)->willReturn($template); + + $storedObjectRepository = $this->prophesize(StoredObjectRepositoryInterface::class); + $storedObjectRepository->find(2)->willReturn($storedObject); + + $userRepository = $this->prophesize(UserRepositoryInterface::class); + $userRepository->find(3)->willReturn($creator); + + // Create a mock for the Query object + $query = $this->prophesize(Query::class); + $query->setParameter('id', 2)->willReturn($query->reveal()); + $query->execute()->shouldBeCalled(); + + // Create a mock for the EntityManager + $entityManager = $this->prophesize(EntityManagerInterface::class); + $entityManager->createQuery(Argument::containingString('UPDATE'))->willReturn($query->reveal()); + $entityManager->flush()->shouldBeCalled(); + + $generator = $this->prophesize(GeneratorInterface::class); + $generator->generateDocFromTemplate( + $template, + 123, // entityId + ['key' => 'value'], // contextGenerationData + $storedObject, + $creator + ) + ->willReturn($storedObject)->shouldBeCalled(); + + $logger = new NullLogger(); + + $mailer = $this->prophesize(MailerInterface::class); + + $translator = $this->prophesize(TranslatorInterface::class); + + $storedObjectManager = $this->prophesize(StoredObjectManagerInterface::class); + + // Create handler + $handler = new RequestGenerationHandler( + $docGeneratorTemplateRepository->reveal(), + $entityManager->reveal(), + $generator->reveal(), + $logger, + $storedObjectRepository->reveal(), + $userRepository->reveal(), + $mailer->reveal(), + $translator->reveal(), + $storedObjectManager->reveal() + ); + + // Create message + $message = new RequestGenerationMessage( + $creator, + $template, + 123, // entityId + $storedObject, + ['key' => 'value'], // contextGenerationData + false, // isTest + null, // sendResultToEmail + false // dumpOnly + ); + + // Invoke handler + $handler->__invoke($message); + + // Assertions + // The assertions are handled by the shouldBeCalled() expectations on the mocks + $this->assertTrue(true); // Just to have an assertion in the test + } + + private function setPrivateProperty(object $object, string $propertyName, $value): void + { + $reflection = new \ReflectionClass($object); + $property = $reflection->getProperty($propertyName); + $property->setAccessible(true); + $property->setValue($object, $value); + } +} diff --git a/src/Bundle/ChillDocGeneratorBundle/translations/messages+intl-icu.fr.yml b/src/Bundle/ChillDocGeneratorBundle/translations/messages+intl-icu.fr.yml index df20907c9..6bbaa5f7d 100644 --- a/src/Bundle/ChillDocGeneratorBundle/translations/messages+intl-icu.fr.yml +++ b/src/Bundle/ChillDocGeneratorBundle/translations/messages+intl-icu.fr.yml @@ -1,4 +1,2 @@ docgen: - data_dump_email: - link_valid_until: >- - Ce lien est valide jusqu'au {validity, date, full}, {validity, time, medium} + # No ICU messages needed for data_dump_email anymore diff --git a/src/Bundle/ChillDocGeneratorBundle/translations/messages.fr.yml b/src/Bundle/ChillDocGeneratorBundle/translations/messages.fr.yml index 1fdf14c02..c1601ea6e 100644 --- a/src/Bundle/ChillDocGeneratorBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillDocGeneratorBundle/translations/messages.fr.yml @@ -34,8 +34,10 @@ docgen: data_dump_email: subject: Contenu des données de génération de document disponible Dear: Cher - data_dump_ready_and_link: >- - Le contenu des données est disponible. Vous pouvez le télécharger à l'aide du lien suivant: + data_dump_ready_and_attached: >- + Le contenu des données est disponible. Vous le trouverez en pièce jointe à cet email. + filename: >- + Nom du fichier: %filename%