diff --git a/CHANGELOG.md b/CHANGELOG.md index 22639a120..c52322c92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,17 +12,24 @@ and this project adheres to * [person] add validator for accompanying period with a test on social issues (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/76) + +## Test releases + +### test release 2021-12-06 + * [main] address: use search API end points for getting postal code and reference address (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/316) * [main] address: in edit mode, select the encoded values in multiselect for address reference and city (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/316) * [person search] fix bug when using birthdate after and birthdate before * [person search] increase pertinence when lastname begins with search pattern +* [activity/actions] Améliore la cohérence du design entre + * la page résumé d'un parcours (liste d'actions récentes et liste d'activités récentes) + * la page liste des actions + * la page liste des activités (contexte personne / contexte parcours) * [household] field to edit wheter person is titulaire of household or not removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/322) * [activity] create work if a work with same social action is not associated to the activity * [visgraph] improve and fix bugs on vis-network relationship graph * [bugfix] posting of birth- and deathdate through api fixed. -## Test releases - ### Test release 2021-11-19 - bis * [household] do not allow to create two addresses on the same date @@ -175,7 +182,7 @@ and this project adheres to * fast creation buttons * add ordering for types -* [AccompanyingCourse Resume page] badge-title for AccompanyingCourseWork and for Activities; +* [AccompanyingCourse Resume page] dashboard for AccompanyingCourseWork and for Activities; * Improve badges behaviour with small screens; * [ThirdParty]: diff --git a/composer.json b/composer.json index 254486913..6e18ea69a 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ "phpoffice/phpspreadsheet": "^1.16", "ramsey/uuid-doctrine": "^1.7", "sensio/framework-extra-bundle": "^5.5", + "spomky-labs/base64url": "^2.0", "symfony/asset": "4.*", "symfony/browser-kit": "^5.2", "symfony/css-selector": "^5.2", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a3a2f6e04..d996ff3b8 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -10,16 +10,6 @@ parameters: count: 1 path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php - - - message: "#^Instantiated class PhpOffice\\\\PhpWord\\\\TemplateProcessor not found\\.$#" - count: 1 - path: src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorController.php - - - - message: "#^Instantiated class PhpOffice\\\\PhpWord\\\\TemplateProcessor not found\\.$#" - count: 1 - path: src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php - - message: "#^Variable \\$participation might not be defined\\.$#" count: 3 diff --git a/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss b/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss index 28c02e23e..275f67950 100644 --- a/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss +++ b/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss @@ -24,14 +24,16 @@ div.new-activity-select-type { } //// ACTIVITY LIST PAGE -// precise badge-title specific details +// precise dashboard specific details +p.date-label { + display: inline-block; + margin: 0 0.5em 0 0; + font-weight: 700; + font-size: 18pt; +} +div.dashboard, h2.badge-title { - div.duration { - font-size: smaller; - padding-left: 1em; - margin-top: 1em; - } ul.list-content { font-size: 70%; list-style-type: none; @@ -39,16 +41,13 @@ h2.badge-title { margin: 0; li { margin-bottom: 0.2em; - // exception: change bg color for action badges above badge-title + // exception: change bg color for action badges above dashboard .bg-light { background-color: $chill-light-gray !important; } } } } -div.main { - padding: 1em; -} //// ACTIVITY SHOW AND FORM PAGES // Exceptions for flex-bloc in concerned-groups diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/activity-badge-title.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/activity-badge-title.html.twig deleted file mode 100644 index f51854163..000000000 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/activity-badge-title.html.twig +++ /dev/null @@ -1,110 +0,0 @@ -

- - - {% if activity.date %} -

{{ activity.date|format_date('short') }}

- {% endif %} - -
- {% if activity.durationTime and t.durationTimeVisible %} -

- - {{ activity.durationTime|date('H:i') }} -

- {% endif %} - - {% if activity.travelTime and t.travelTimeVisible %} -

- - {{ activity.travelTime|date('H:i') }} -

- {% endif %} -
- -
- - - {{ activity.type.name | localize_translatable_string }} - - {% if activity.emergency %} - {{ 'Emergency'|trans|upper }} - {% endif %} - - - - - -

- -{% if context == 'person' and activity.accompanyingPeriod is not empty %} -
- - -
-{% endif %} - diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/concernedGroups.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/concernedGroups.html.twig index f6b052265..2808cca60 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/concernedGroups.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/concernedGroups.html.twig @@ -126,7 +126,7 @@
{% if bloc.items|length > 0 %}
-

{{ bloc.title }}

+

{{ bloc.title }}

{% for item in bloc.items %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig index 7c75f5bf0..83d8cbcdb 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig @@ -10,49 +10,174 @@ {% for activity in activities %} {% set t = activity.type %}
+
- {% include '@ChillActivity/Activity/activity-badge-title.html.twig' %} +
+
+
+ + {% if activity.date %} +

+ {{ activity.date|format_date('short') }} +

+ {% endif %} + +
+
+ +

+ + + {{ activity.type.name | localize_translatable_string }} + + {% if activity.emergency %} + {{ 'Emergency'|trans|upper }} + {% endif %} + +

+ +
+
+
- {% if activity.comment.comment is not empty - or activity.persons|length > 0 - or activity.thirdParties|length > 0 - or activity.users|length > 0 - %} -
- {% if activity.comment.comment is not empty %} - {{ activity.comment|chill_entity_render_box({ - 'disable_markdown': false, - 'limit_lines': 3, - 'metadata': false, - }) }} - {% endif %} +
+
+ {% if activity.location and t.locationVisible %} +
+

{{ 'location'|trans }}

+
+

+ {{ activity.location.locationType.title|localize_translatable_string }} + {{ activity.location.name }} +

+
+
+ {% endif %} + + {% if activity.sentReceived is not empty and t.sentReceivedVisible %} +
+

{{ 'Sent received'|trans }}

+
+

+ {{ activity.sentReceived|capitalize|trans }} +

+
+
+ {% endif %} + + {% if activity.user and t.userVisible %} +
+

{{ 'Referrer'|trans }}

+
+

+ {{ activity.user.usernameCanonical|chill_entity_render_string|capitalize }} +

+
+
+ {% endif %} +
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with { 'context': context, - 'with_display': 'row', + 'with_display': 'wrap-list', 'entity': activity, 'badge_person': true } %} + +
+ {%- if activity.reasons is not empty and t.reasonsVisible -%} +
+
+

{{ 'Reasons'|trans }}

+
+
+ {% for r in activity.reasons %} +

+ {{ r|chill_entity_render_box }} +

+ {% endfor %} +
+
+ {% endif %} + + {%- if activity.socialIssues is not empty and t.socialIssuesVisible -%} +
+
+

{{ 'Social issues'|trans }}

+
+
+ {% for r in activity.socialIssues %} + + {% endfor %} +
+
+ {% endif %} + + {%- if activity.socialActions is not empty and t.socialActionsVisible -%} +
+
+

{{ 'Social actions'|trans }}

+
+
+ {% for r in activity.socialActions %} + + {% endfor %} +
+
+ {% endif %} + + {# SEULEMENT SI DÉTAILLÉ + {% if activity.comment.comment is not empty %} +
+
+

{{ 'Comment'|trans }}

+
+
+ {{ activity.comment|chill_entity_render_box({ + 'disable_markdown': false, + 'limit_lines': 3, + 'metadata': false + }) }} +
+
+ {% endif %} + #} +
- {% endif %}
-
diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/listAccompanyingCourse.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/listAccompanyingCourse.html.twig index c5de72308..431a06ba6 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/listAccompanyingCourse.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/listAccompanyingCourse.html.twig @@ -23,8 +23,8 @@ {% if is_granted('CHILL_ACTIVITY_CREATE', accompanyingCourse) %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/list_recent.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/list_recent.html.twig index 2829d8153..0f426c5a9 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/list_recent.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/list_recent.html.twig @@ -3,10 +3,88 @@ {% set t = activity.type %} + class="dashboard-link" title="{{ 'Show the activity'|trans }}"> - {% include '@ChillActivity/Activity/activity-badge-title.html.twig' %} +
+ + + + {%- if activity.date -%} +

{{ activity.date|format_date('short') }}

+ {%- endif -%} + + + + {% if activity.emergency %} + {{ 'Emergency'|trans|upper }} + {% endif %} + +
    + + {% if activity.sentReceived is not empty and t.sentReceivedVisible %} +
  • + {{ 'Sent received'|trans ~ ' : ' }} + {{ activity.sentReceived|capitalize|trans }} +
  • + {% endif %} + + {% if activity.location and t.locationVisible %} +
  • + {{ 'location'|trans ~ ': ' }} + + {{ activity.location.locationType.title|localize_translatable_string }} + {{ activity.location.name }} + +
  • + {% endif %} + + {% if activity.user and t.userVisible %} +
  • + {{ 'Referrer'|trans ~ ': ' }} + {{ activity.user.usernameCanonical }} +
  • + {% endif %} + +
  • + {{ 'Participants'|trans ~ ' : ' }} + {% for p in activity.personsAssociated %} + {{ p|chill_entity_render_box }} + {% endfor %} +
  • +
+ +
    + {%- if t.reasonsVisible -%} + {%- if activity.reasons is not empty -%} +
  • + {% for r in activity.reasons %} + {{ r|chill_entity_render_box }} + {% endfor %} +
  • + {%- endif -%} + {% endif %} + {%- if t.socialIssuesVisible %} + {%- if activity.socialIssues is not empty -%} + + {%- endif -%} + {% endif %} + {%- if t.socialActionsVisible -%} + {%- if activity.socialActions is not empty -%} + + {%- endif -%} + {% endif %} +
+ +
+
- {% endfor %}
diff --git a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml index 098a6f6df..3de1cc600 100644 --- a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml @@ -222,3 +222,5 @@ Aggregate by activity type: Aggréger par type d'activité Aggregate by activity reason: Aggréger par sujet de l'activité Last activities: Les dernières activités + +See activity in accompanying course context: Voir l'activité dans le contexte du parcours d'accompagnement diff --git a/src/Bundle/ChillDocGeneratorBundle/ChillDocGeneratorBundle.php b/src/Bundle/ChillDocGeneratorBundle/ChillDocGeneratorBundle.php index d52f0f4b1..aa02a6101 100644 --- a/src/Bundle/ChillDocGeneratorBundle/ChillDocGeneratorBundle.php +++ b/src/Bundle/ChillDocGeneratorBundle/ChillDocGeneratorBundle.php @@ -11,8 +11,17 @@ declare(strict_types=1); namespace Chill\DocGeneratorBundle; +use Chill\DocGeneratorBundle\Context\DocGeneratorContextInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; class ChillDocGeneratorBundle extends Bundle { + public function build(ContainerBuilder $container) + { + parent::build($container); + + $container->registerForAutoconfiguration(DocGeneratorContextInterface::class) + ->addTag('chill_docgen.context'); + } } diff --git a/src/Bundle/ChillDocGeneratorBundle/Context/ContextManager.php b/src/Bundle/ChillDocGeneratorBundle/Context/ContextManager.php new file mode 100644 index 000000000..8dfecb730 --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Context/ContextManager.php @@ -0,0 +1,58 @@ +contexts = $contexts; + } + + /** + * @throw ContextNotFoundException when the context is not found + */ + public function getContextByDocGeneratorTemplate(DocGeneratorTemplate $docGeneratorTemplate): DocGeneratorContextInterface + { + foreach ($this->contexts as $key => $context) { + if ($docGeneratorTemplate->getContext() === $key) { + return $context; + } + } + + throw new ContextNotFoundException($docGeneratorTemplate->getContext()); + } + + public function getContextByKey(string $searchedKey): DocGeneratorContextInterface + { + foreach ($this->contexts as $key => $context) { + if ($searchedKey === $key) { + return $context; + } + } + + throw new ContextNotFoundException($searchedKey); + } + + public function getContexts(): array + { + return iterator_to_array($this->contexts); + } +} diff --git a/src/Bundle/ChillDocGeneratorBundle/Context/DocGeneratorContextInterface.php b/src/Bundle/ChillDocGeneratorBundle/Context/DocGeneratorContextInterface.php index 7493f7000..df148f6e1 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Context/DocGeneratorContextInterface.php +++ b/src/Bundle/ChillDocGeneratorBundle/Context/DocGeneratorContextInterface.php @@ -11,8 +11,11 @@ declare(strict_types=1); namespace Chill\DocGeneratorBundle\Context; +use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate; +use Chill\DocStoreBundle\Entity\StoredObject; + /** - * Interface for context for for document generation. + * Interface for context for document generation. */ interface DocGeneratorContextInterface { @@ -21,22 +24,15 @@ interface DocGeneratorContextInterface * * @param mixed $entity */ - public function getData($entity): array; + public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array; - /** - * Generate the form that display. - * - * @param mixed $entity - */ - public function getForm($entity); + public function getDescription(): string; - /** - * has form. - */ - public function hasForm(): bool; + public function getEntityClass(): string; - /** - * True of false which entity supports. - */ - public function supports(string $entityClass): bool; + public static function getKey(): string; + + public function getName(): string; + + public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void; } diff --git a/src/Bundle/ChillDocGeneratorBundle/Context/DocGeneratorContextWithAdminFormInterface.php b/src/Bundle/ChillDocGeneratorBundle/Context/DocGeneratorContextWithAdminFormInterface.php new file mode 100644 index 000000000..3b606ccb5 --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Context/DocGeneratorContextWithAdminFormInterface.php @@ -0,0 +1,25 @@ + [], - 'cloneRowAndSetValues' => [], - ]; - - $persons = $entity->getAccompanyingPeriodWork()->getPersons(); - - if (count($persons) > 0) { - $firstPerson = $persons[0]; - - $datas['setValues'][] = [ - 'firstPersonFirstName' => $firstPerson->getFirstName(), - 'firstPersonLastName' => $firstPerson->getLastName(), ]; - } - - if (get_class($entity) === AccompanyingPeriodWorkEvaluation::class) { - $values = []; - - foreach ($entity->getAccompanyingPeriodWork()->getPersons() as $person) { - $i = 1; - $values[] = [ - 'personRowId' => $i, - 'personFirstName' => $person->getFirstName(), - 'personLastName' => $person->getLastName(), - ]; - } - - $datas['cloneRowAndSetValues'][] = [ - 'personRowId', $values, ]; - } - - return $datas; - } - - /** - * Generate the form that display. - * - * @param mixed $entity - */ - public function getForm($entity) - { - throw new Exception('No implemented yet', 1); - $choices = []; - - if (get_class($entity) === AccompanyingPeriodWorkEvaluation::class) { - foreach ($entity->getAccompanyingPeriodWork()->getPersons() as $person) { - $choices[$person->getId()] = $person->getName(); - } - } - - $builder->add('members', ChoiceType::class, [ - 'choices' => $choices, - 'placeholder' => 'Choose a person', - 'label' => 'Person to add', - ]); - - return $builder; - } - - /** - * has form. - */ - public function hasForm(): bool - { - return true; - } - - /** - * True of false which entity supports. - */ - public function supports(string $entityClass): bool - { - return - (AccompanyingPeriod::class === $entityClass) - || (SocialAction::class === $entityClass); - } -} diff --git a/src/Bundle/ChillDocGeneratorBundle/Controller/AdminDocGeneratorTemplateController.php b/src/Bundle/ChillDocGeneratorBundle/Controller/AdminDocGeneratorTemplateController.php index a7d24f127..c92aff527 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Controller/AdminDocGeneratorTemplateController.php +++ b/src/Bundle/ChillDocGeneratorBundle/Controller/AdminDocGeneratorTemplateController.php @@ -11,8 +11,77 @@ declare(strict_types=1); namespace Chill\DocGeneratorBundle\Controller; +use Chill\DocGeneratorBundle\Context\ContextManager; +use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate; use Chill\MainBundle\CRUD\Controller\CRUDController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Annotation\Route; class AdminDocGeneratorTemplateController extends CRUDController { + private ContextManager $contextManager; + + public function __construct(ContextManager $contextManager) + { + $this->contextManager = $contextManager; + } + + public function generateTemplateParameter(string $action, $entity, Request $request, array $defaultTemplateParameters = []) + { + switch ($action) { + case 'new': + $context = $this->contextManager->getContextByKey($request->get('context')); + // no break + case 'edit': + $context = $this->contextManager->getContextByDocGeneratorTemplate($entity); + + return array_merge( + $defaultTemplateParameters, + ['context' => $context] + ); + + case 'index': + return array_merge( + $defaultTemplateParameters, + ['contextManager' => $this->contextManager] + ); + + default: + return parent::generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters); // TODO: Change the autogenerated stub + } + } + + public function new(Request $request): Response + { + if (!$request->query->has('context')) { + return $this->redirectToRoute('chill_docgen_admin_template_pick-context'); + } + + return parent::new($request); + } + + /** + * @Route("{_locale}/admin/docgen/template/pick-context", name="chill_docgen_admin_template_pick-context") + */ + public function pickContext(Request $request): Response + { + $this->denyAccessUnlessGranted('ROLE_ADMIN'); + + return $this->render('ChillDocGeneratorBundle:Admin/DocGeneratorTemplate:pick-context.html.twig', [ + 'contexts' => $this->contextManager->getContexts(), + ]); + } + + protected function createEntity(string $action, Request $request): object + { + /** @var DocGeneratorTemplate $entity */ + $entity = parent::createEntity($action, $request); + $key = $request->query->get('context'); + $context = $this->contextManager->getContextByKey($key); + + $entity->setContext($key)->setEntity($context->getEntityClass()); + + return $entity; + } } diff --git a/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorController.php b/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorController.php deleted file mode 100644 index 50201f6b0..000000000 --- a/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorController.php +++ /dev/null @@ -1,76 +0,0 @@ -generate( - 'GET', - 'FORMULAIRE_AEB.docx', - $request->query->has('expires_delay') ? $request->query->getInt('expires_delay', 0) : null - ); - - $tmpfname = tempnam(sys_get_temp_dir(), 'DOC_TEMPLATE'); - file_put_contents($tmpfname, file_get_contents($p->url)); - - $templateProcessor = new TemplateProcessor($tmpfname); - $templateProcessor->setValues(['firstname' => 'John', 'lastname' => 'Doe']); - - $tmpfname2 = tempnam(sys_get_temp_dir(), 'DOC_GENERATED'); - $templateProcessor->saveAs($tmpfname2); - - unlink($tmpfname); - - $fileContent = fopen($tmpfname2, 'rb'); // the generated file content - $response = new Response(fread($fileContent, filesize($tmpfname2))); - - $disposition = HeaderUtils::makeDisposition( - HeaderUtils::DISPOSITION_ATTACHMENT, - 'foo.docx' - ); - - $response->headers->set('Content-Disposition', $disposition); - unlink($tmpfname2); - - return $response; - } - - /** - * @Route( - * "{_locale}/doc/gen/test", - * name="chill_docgenerator_test" - * ) - */ - public function testAction(): Response - { - return (new Response())->setContent('Test'); - } -} diff --git a/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php b/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php index f517a7cc6..21a830bfd 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php +++ b/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php @@ -11,28 +11,73 @@ declare(strict_types=1); namespace Chill\DocGeneratorBundle\Controller; +use Base64Url\Base64Url; use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface; -use Chill\DocGeneratorBundle\Context\HouseholdMemberSelectionContext; +use Chill\DocGeneratorBundle\Context\ContextManager; +use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface; +use Chill\DocGeneratorBundle\Context\Exception\ContextNotFoundException; use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate; +use Chill\DocGeneratorBundle\GeneratorDriver\DriverInterface; use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository; use Chill\DocStoreBundle\Entity\StoredObject; -use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; -use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument; -use Chill\PersonBundle\Entity\SocialWork\Evaluation; +use Chill\MainBundle\Pagination\PaginatorFactory; +use Chill\MainBundle\Serializer\Model\Collection; use Exception; use GuzzleHttp\Client; use GuzzleHttp\Exception\TransferException; -use PhpOffice\PhpWord\TemplateProcessor; +use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\JsonResponse; + use Symfony\Component\HttpFoundation\Request; // TODO à mettre dans services use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Routing\Annotation\Route; -class DocGeneratorTemplateController extends AbstractController +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +final class DocGeneratorTemplateController extends AbstractController { + private HttpClientInterface $client; + + private ContextManager $contextManager; + + private DocGeneratorTemplateRepository $docGeneratorTemplateRepository; + + private DriverInterface $driver; + + private KernelInterface $kernel; + + private LoggerInterface $logger; + + private PaginatorFactory $paginatorFactory; + + private TempUrlGeneratorInterface $tempUrlGenerator; + + public function __construct( + ContextManager $contextManager, + DocGeneratorTemplateRepository $docGeneratorTemplateRepository, + DriverInterface $driver, + LoggerInterface $logger, + PaginatorFactory $paginatorFactory, + TempUrlGeneratorInterface $tempUrlGenerator, + KernelInterface $kernel, + HttpClientInterface $client + ) { + $this->contextManager = $contextManager; + $this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository; + $this->driver = $driver; + $this->logger = $logger; + $this->paginatorFactory = $paginatorFactory; + $this->tempUrlGenerator = $tempUrlGenerator; + $this->kernel = $kernel; + $this->client = $client; + } + /** * @Route( * "{_locale}/doc/gen/generate/from/{template}/for/{entityClassName}/{entityId}", @@ -40,80 +85,112 @@ class DocGeneratorTemplateController extends AbstractController * ) */ public function generateDocFromTemplateAction( - TempUrlGeneratorInterface $tempUrlGenerator, DocGeneratorTemplate $template, string $entityClassName, int $entityId, Request $request ): Response { - $getUrlGen = $tempUrlGenerator->generate( + try { + $context = $this->contextManager->getContextByDocGeneratorTemplate($template); + } catch (ContextNotFoundException $e) { + throw new NotFoundHttpException($e->getMessage(), $e); + } + + $entity = $this->getDoctrine()->getRepository($context->getEntityClass())->find($entityId); + + if (null === $entity) { + throw new NotFoundHttpException("Entity with classname {$entityClassName} and id {$entityId} is not found"); + } + + $contextGenerationData = []; + + if ($context instanceof DocGeneratorContextWithPublicFormInterface + && $context->hasPublicForm($template, $entity)) { + $builder = $this->createFormBuilder($context->getFormData($template, $entity)); + $context->buildPublicForm($builder, $template, $entity); + $form = $builder->getForm()->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $contextGenerationData = $form->getData(); + } elseif (!$form->isSubmitted() || ($form->isSubmitted() && !$form->isValid())) { + $templatePath = '@ChillDocGenerator/Generator/basic_form.html.twig'; + $templateOptions = ['entity' => $entity, 'form' => $form->createView(), + 'template' => $template, 'context' => $context, ]; + + return $this->render($templatePath, $templateOptions); + } + } + + $getUrlGen = $this->tempUrlGenerator->generate( 'GET', - $template->getFile() + $template->getFile()->getFilename() ); - $tmpfname = tempnam(sys_get_temp_dir(), 'DOC_TEMPLATE'); - file_put_contents($tmpfname, file_get_contents($getUrlGen->url)); + $data = $this->client->request('GET', $getUrlGen->url); - $entity = $this->getDoctrine()->getRepository($entityClassName)->find($entityId); + $iv = $template->getFile()->getIv(); // iv as an Array + $ivGoodFormat = pack('C*', ...$iv); // iv as a String (ok for openssl_decrypt) - if ($template->getContext() === HouseholdMemberSelectionContext::class) { - $context = new HouseholdMemberSelectionContext(); - $datas = $context->getData($entity); - } else { - throw new Exception('Not implemented', 1); + $method = 'AES-256-CBC'; + + $key = $template->getFile()->getKeyInfos()['k']; + $keyGoodFormat = Base64Url::decode($key); + + $dataDecrypted = openssl_decrypt($data->getContent(), $method, $keyGoodFormat, 1, $ivGoodFormat); + + if (false === $dataDecrypted) { + throw new Exception('Error during Decrypt ', 1); } - $templateProcessor = new TemplateProcessor($tmpfname); + if (false === $templateResource = fopen('php://memory', 'r+b')) { + $this->logger->error('Could not write data to memory'); - foreach ($datas['setValues'] as $setValuesConf) { - $templateProcessor->setValues($setValuesConf); + throw new HttpException(500); } - foreach ($datas['cloneRowAndSetValues'] as $cloneRowAndSetValues) { - $templateProcessor->cloneRowAndSetValues($cloneRowAndSetValues[0], $cloneRowAndSetValues[1]); - } + fwrite($templateResource, $dataDecrypted); + rewind($templateResource); - $tmpfname2 = tempnam(sys_get_temp_dir(), 'DOC_GENERATED'); - $templateProcessor->saveAs($tmpfname2); + $datas = $context->getData($template, $entity, $contextGenerationData); + dump('datas compiled', $datas); - unlink($tmpfname); + $generatedResource = $this->driver->generateFromResource($templateResource, $template->getFile()->getType(), $datas, $template->getFile()->getFilename()); - $fileContent = fopen($tmpfname2, 'rb'); // the generated file content + fclose($templateResource); - $genDocName = 'doc_' . sprintf('%010d', mt_rand()) . '.docx'; + $genDocName = 'doc_' . sprintf('%010d', mt_rand()) . 'odt'; - $getUrlGen = $tempUrlGenerator->generate( + $getUrlGen = $this->tempUrlGenerator->generate( 'PUT', $genDocName ); - unlink($tmpfname2); - $client = new Client(); try { $putResponse = $client->request('PUT', $getUrlGen->url, [ - 'body' => $fileContent, + 'body' => $generatedResource, ]); if ($putResponse->getStatusCode() === 201) { $em = $this->getDoctrine()->getManager(); $storedObject = new StoredObject(); $storedObject - // currently, only docx is supported - ->setType('application/vnd.openxmlformats-officedocument.wordprocessingml.document') + ->setType($template->getFile()->getType()) ->setFilename($genDocName); $em->persist($storedObject); - // Only for evaluation - if ($entity instanceof AccompanyingPeriodWorkEvaluation) { - $doc = new AccompanyingPeriodWorkEvaluationDocument(); - $doc - ->setStoredObject($storedObject) - ->setTemplate($template); - $entity->addDocument($doc); - $em->persist($doc); + try { + $context->storeGenerated($template, $storedObject, $entity, $contextGenerationData); + } catch (Exception $e) { + $this->logger->error('Could not store the associated document to entity', [ + 'entityClassName' => $entityClassName, + 'entityId' => $entityId, + 'contextKey' => $context->getName(), + ]); + + throw $e; } $em->flush(); @@ -132,26 +209,25 @@ class DocGeneratorTemplateController extends AbstractController /** * @Route( - * "{_locale}/doc/gen/templates/for/{entityClassName}", + * "/api/1.0/docgen/templates/by-entity/{entityClassName}", * name="chill_docgenerator_templates_for_entity_api" * ) */ - public function listTemplateApiAction( - string $entityClassName, - DocGeneratorTemplateRepository $templateRepository - ): Response { - $entities = $templateRepository->findByEntity($entityClassName); + public function listTemplateApiAction(string $entityClassName): Response + { + $nb = $this->docGeneratorTemplateRepository->countByEntity($entityClassName); + $paginator = $this->paginatorFactory->create($nb); + $entities = $this->docGeneratorTemplateRepository->findByEntity( + $entityClassName, + $paginator->getCurrentPageFirstItemNumber(), + $paginator->getItemsPerPage() + ); - $ret = []; - - foreach ($entities as $entity) { - $ret[] = [ - 'id' => $entity->getId(), - 'name' => $entity->getName(), - 'description' => $entity->getDescription(), - ]; - } - - return new JsonResponse(['results' => $ret]); + return $this->json( + new Collection($entities, $paginator), + Response::HTTP_OK, + [], + [AbstractNormalizer::GROUPS => ['read']] + ); } } diff --git a/src/Bundle/ChillDocGeneratorBundle/DataFixtures/ORM/LoadDocGeneratorTemplate.php b/src/Bundle/ChillDocGeneratorBundle/DataFixtures/ORM/LoadDocGeneratorTemplate.php index 082605fd7..0db7bbd44 100644 --- a/src/Bundle/ChillDocGeneratorBundle/DataFixtures/ORM/LoadDocGeneratorTemplate.php +++ b/src/Bundle/ChillDocGeneratorBundle/DataFixtures/ORM/LoadDocGeneratorTemplate.php @@ -12,9 +12,10 @@ declare(strict_types=1); namespace Chill\DocGeneratorBundle\DataFixtures\ORM; use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate; +use Chill\DocStoreBundle\Entity\StoredObject; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; +use DateTime; use Doctrine\Common\DataFixtures\AbstractFixture; - use Doctrine\Persistence\ObjectManager; /** @@ -28,13 +29,23 @@ class LoadDocGeneratorTemplate extends AbstractFixture [ 'name' => ['fr' => 'FORMULAIRE AEB'], 'desc' => 'stocké sur openstack comedienbe', - 'file' => 'FORMULAIRE_AEB_WITH_DATA_INJ.docx', + 'file' => [ + 'filename' => 'pKNlhCrQDCRsAuC8vYHDKa', + 'key' => '{"alg":"A256CBC","ext":true,"k":"_VihnD41-VDHlpS-ouwtbMPnu-OXVdtA7ENQWWtAQYM","key_ops":["encrypt","decrypt"],"kty":"oct"}', + 'iv' => '[86,231,83,148,117,107,149,173,130,19,105,194,224,145,8,48]', + 'type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + ], 'context' => 'Chill\DocGeneratorBundle\Context\HouseholdMemberSelectionContext', 'entities' => [AccompanyingPeriodWorkEvaluation::class], ], [ 'name' => ['fr' => 'AIDE ALIMENTAIRE'], 'desc' => 'stocké sur openstack comedienbe', - 'file' => 'AIDE_ALIMENTAIRE.docx', + 'file' => [ + 'filename' => 'pKNlhCrQDCRsAuC8vYHDKa', + 'key' => '{"alg":"A256CBC","ext":true,"k":"_VihnD41-VDHlpS-ouwtbMPnu-OXVdtA7ENQWWtAQYM","key_ops":["encrypt","decrypt"],"kty":"oct"}', + 'iv' => '[86,231,83,148,117,107,149,173,130,19,105,194,224,145,8,48]', + 'type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + ], 'context' => 'Chill\DocGeneratorBundle\Context\HouseholdMemberSelectionContext', 'entities' => ['Chill\PersonBundle\Entity\AccompanyingPeriod', 'Chill\PersonBundle\Entity\SocialWork\SocialAction', AccompanyingPeriodWorkEvaluation::class], ], @@ -43,10 +54,19 @@ class LoadDocGeneratorTemplate extends AbstractFixture foreach ($templates as $template) { echo 'Adding doc generator templates ' . $template['file'] . ")\n"; + $newStoredObj = (new StoredObject()) + ->setFilename($template['file']['filename']) + ->setKeyInfos(json_decode($template['file']['key'], true)) + ->setIv(json_decode($template['file']['iv'], true)) + ->setCreationDate(new DateTime('today')) + ->setType($template['file']['type']); + + $manager->persist($newStoredObj); + $newTemplate = (new DocGeneratorTemplate()) ->setName($template['name']) ->setDescription($template['desc']) - ->setFile($template['file']) + ->setFile($newStoredObj) ->setContext($template['context']) ->setEntities($template['entities']); diff --git a/src/Bundle/ChillDocGeneratorBundle/DependencyInjection/ChillDocGeneratorExtension.php b/src/Bundle/ChillDocGeneratorBundle/DependencyInjection/ChillDocGeneratorExtension.php index 15fd49140..9712ffac8 100644 --- a/src/Bundle/ChillDocGeneratorBundle/DependencyInjection/ChillDocGeneratorExtension.php +++ b/src/Bundle/ChillDocGeneratorBundle/DependencyInjection/ChillDocGeneratorExtension.php @@ -26,6 +26,10 @@ class ChillDocGeneratorExtension extends Extension implements PrependExtensionIn { public function load(array $configs, ContainerBuilder $container) { + $configuration = new Configuration(); + $config = $this->processConfiguration($configuration, $configs); + $container->setParameter('chill_doc_generator', $config); + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../config')); $loader->load('services.yaml'); $loader->load('services/controller.yaml'); @@ -37,6 +41,7 @@ class ChillDocGeneratorExtension extends Extension implements PrependExtensionIn { $this->preprendRoutes($container); $this->prependCruds($container); + $this->prependClientConfig($container); } protected function prependCruds(ContainerBuilder $container) @@ -78,4 +83,23 @@ class ChillDocGeneratorExtension extends Extension implements PrependExtensionIn ], ]); } + + private function prependClientConfig(ContainerBuilder $container) + { + $configs = $container->getExtensionConfig($this->getAlias()); + $resolvingBag = $container->getParameterBag(); + $configs = $resolvingBag->resolveValue($configs); + + $config = $this->processConfiguration(new Configuration(), $configs); + + $container->prependExtensionConfig('framework', [ + 'http_client' => [ + 'scoped_clients' => [ + 'relatorio.client' => [ + 'scope' => $config['driver']['relatorio']['url'], + ], + ], + ], + ]); + } } diff --git a/src/Bundle/ChillDocGeneratorBundle/DependencyInjection/Configuration.php b/src/Bundle/ChillDocGeneratorBundle/DependencyInjection/Configuration.php index 161886d87..1117a5fc0 100644 --- a/src/Bundle/ChillDocGeneratorBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillDocGeneratorBundle/DependencyInjection/Configuration.php @@ -14,21 +14,35 @@ namespace Chill\DocGeneratorBundle\DependencyInjection; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; -/** - * This is the class that validates and merges configuration from your app/config files. - * - * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/configuration.html} - */ class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder() { - $treeBuilder = new TreeBuilder('chill_calendar'); - $rootNode = $treeBuilder->getRootNode('chill_calendar'); + $treeBuilder = new TreeBuilder('chill_doc_generator'); + $rootNode = $treeBuilder->getRootNode(); - // Here you should define the parameters that are allowed to - // configure your bundle. See the documentation linked above for - // more information on that topic. + $rootNode + ->children() + ->arrayNode('driver') + ->addDefaultsIfNotSet() + ->children() + ->enumNode('type') + ->isRequired() + ->values(['relatorio']) + ->defaultValue('relatorio') + ->end() + ->arrayNode('relatorio') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('url') + ->isRequired() + ->defaultValue('http://relatorio:8888/') + ->end() + ->end() + ->end() + ->end() + ->end() + ->end(); return $treeBuilder; } diff --git a/src/Bundle/ChillDocGeneratorBundle/Entity/DocGeneratorTemplate.php b/src/Bundle/ChillDocGeneratorBundle/Entity/DocGeneratorTemplate.php index 75edbcbdf..ee93f74fb 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Entity/DocGeneratorTemplate.php +++ b/src/Bundle/ChillDocGeneratorBundle/Entity/DocGeneratorTemplate.php @@ -11,44 +11,51 @@ declare(strict_types=1); namespace Chill\DocGeneratorBundle\Entity; +use Chill\DocStoreBundle\Entity\StoredObject; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation as Serializer; /** * @ORM\Entity * @ORM\Table(name="chill_docgen_template") + * @Serializer\DiscriminatorMap(typeProperty="type", mapping={ + * "docgen_template": DocGeneratorTemplate::class + * }) */ class DocGeneratorTemplate { /** - * @ORM\Column(type="string", length=255) - * - * Class name of the context to use + * @ORM\Column(type="boolean", options={"default": true}) + */ + private bool $active = true; + + /** + * Class name of the context to use. * * so if $context = '' * this template will use '' as context + * + * @ORM\Column(type="string", length=255) */ private string $context; /** * @ORM\Column(type="text", nullable=true) + * @Serializer\Groups({"read"}) */ - private string $description; + private ?string $description = null; /** - * @ORM\Column(type="simple_array") + * Class name of the entity for which this template can be used. * - * Class name of the entities for which this template can be used - * - * so if $entities = ['Chill\PersonBundle\Entity\AccompanyingPeriod', 'Chill\PersonBundle\Entity\SocialWork\SocialAction'] - * this template can be selected for an AccompanyingPeriod or a SocialAction + * @ORM\Column(type="string", options={"default": ""}) */ - private array $entities = []; + private string $entity = ''; /** - * @ORM\Column(type="string", length=255) + * @ORM\ManyToOne(targetEntity=StoredObject::class, cascade={"persist"})) */ - private string $file; + private ?StoredObject $file = null; /** * @ORM\Id @@ -64,6 +71,13 @@ class DocGeneratorTemplate */ private array $name = []; + /** + * Options for the template. + * + * @ORM\Column(type="json", name="template_options", options={"default":"[]"}) + */ + private array $options = []; + public function getContext(): ?string { return $this->context; @@ -74,12 +88,12 @@ class DocGeneratorTemplate return $this->description; } - public function getEntities(): ?array + public function getEntity(): string { - return $this->entities; + return $this->entity; } - public function getFile(): ?string + public function getFile(): ?StoredObject { return $this->file; } @@ -94,6 +108,23 @@ class DocGeneratorTemplate return $this->name; } + public function getOptions(): array + { + return $this->options; + } + + public function isActive(): bool + { + return $this->active; + } + + public function setActive(bool $active): DocGeneratorTemplate + { + $this->active = $active; + + return $this; + } + public function setContext(string $context): self { $this->context = $context; @@ -108,14 +139,14 @@ class DocGeneratorTemplate return $this; } - public function setEntities(array $entities): self + public function setEntity(string $entity): self { - $this->entities = $entities; + $this->entity = $entity; return $this; } - public function setFile(string $file): self + public function setFile(StoredObject $file): self { $this->file = $file; @@ -128,4 +159,11 @@ class DocGeneratorTemplate return $this; } + + public function setOptions(array $options): self + { + $this->options = $options; + + return $this; + } } diff --git a/src/Bundle/ChillDocGeneratorBundle/Form/DocGeneratorTemplateType.php b/src/Bundle/ChillDocGeneratorBundle/Form/DocGeneratorTemplateType.php index 75d33d0e5..4275cf53e 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Form/DocGeneratorTemplateType.php +++ b/src/Bundle/ChillDocGeneratorBundle/Form/DocGeneratorTemplateType.php @@ -11,22 +11,64 @@ declare(strict_types=1); namespace Chill\DocGeneratorBundle\Form; +use Chill\DocGeneratorBundle\Context\ContextManager; +use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface; use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate; +use Chill\DocStoreBundle\Form\StoredObjectType; use Chill\MainBundle\Form\Type\TranslatableStringFormType; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\CallbackTransformer; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; class DocGeneratorTemplateType extends AbstractType { + private ContextManager $contextManager; + + public function __construct(ContextManager $contextManager) + { + $this->contextManager = $contextManager; + } + public function buildForm(FormBuilderInterface $builder, array $options) { + /** @var DocGeneratorTemplate $template */ + $template = $options['data']; + $context = $this->contextManager->getContextByKey($template->getContext()); + $builder ->add('name', TranslatableStringFormType::class, [ 'label' => 'Nom', ]) ->add('description') - ->add('file'); + ->add('file', StoredObjectType::class, [ + 'error_bubbling' => true, + ]) + ->add('active', ChoiceType::class, [ + 'label' => 'Active', + 'choices' => [ + 'Yes' => true, + 'No' => false, + ], + 'required' => true, + ]); + + if ($context instanceof DocGeneratorContextWithAdminFormInterface + && $context->hasAdminForm()) { + $sub = $builder + ->create('options', null, ['compound' => true]) + ->addModelTransformer(new CallbackTransformer( + static function (array $data) use ($context) { + return $context->adminFormTransform($data); + }, + static function (array $data) use ($context) { + return $context->adminFormReverseTransform($data); + } + )); + $context->buildAdminForm($sub); + $builder->add($sub); + } } public function configureOptions(OptionsResolver $resolver) diff --git a/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/DriverInterface.php b/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/DriverInterface.php new file mode 100644 index 000000000..2572484e1 --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/DriverInterface.php @@ -0,0 +1,22 @@ +relatorioClient = $relatorioClient; + $this->logger = $logger; + $this->url = $parameterBag->get('chill_doc_generator')['driver']['relatorio']['url']; + } + + public function generateFromResource($template, string $resourceType, array $data, ?string $templateName = null) + { + $formFields = [ + 'variables' => json_encode($data), + 'template' => new DataPart($template, $templateName ?? uniqid('template_'), $resourceType), + ]; + $form = new FormDataPart($formFields); + + try { + $response = $this->relatorioClient->request('POST', $this->url, [ + 'headers' => $form->getPreparedHeaders()->toArray(), + 'body' => $form->bodyToIterable(), + ]); + + return $response->toStream(); + } catch (HttpExceptionInterface $e) { + $this->logger->error('relatorio: error while generating document', [ + 'msg' => $e->getMessage(), + 'response' => $e->getResponse()->getStatusCode(), + 'content' => $e->getResponse()->getContent(false), + ]); + + throw $e; + } catch (TransportExceptionInterface $e) { + $this->logger->error('relatorio: transport exception', [ + 'msg' => $e->getMessage(), + 'e' => $e->getTraceAsString(), + ]); + + throw $e; + } catch (DecodingExceptionInterface $e) { + $this->logger->error('relatorio: could not decode response', [ + 'msg' => $e->getMessage(), + 'e' => $e->getTraceAsString(), + ]); + + throw $e; + } + } +} diff --git a/src/Bundle/ChillDocGeneratorBundle/Menu/AdminMenuBuilder.php b/src/Bundle/ChillDocGeneratorBundle/Menu/AdminMenuBuilder.php new file mode 100644 index 000000000..39b21ed0e --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Menu/AdminMenuBuilder.php @@ -0,0 +1,50 @@ +translator = $translator; + $this->security = $security; + } + + public function buildMenu($menuId, MenuItem $menu, array $parameters) + { + if ($this->security->isGranted('ROLE_ADMIN')) { + if (in_array($menuId, ['admin_index', 'admin_section'], true)) { + $menu->addChild($this->translator->trans('docgen.Document generation'), [ + 'route' => 'chill_crud_docgen_template_index', + ])->setExtras([ + 'order' => 350, + 'explain' => 'docgen.Manage templates and document generation', + ]); + } + } + } + + public static function getMenuIds(): array + { + return ['admin_index', 'admin_section', 'docgen_admin']; + } +} diff --git a/src/Bundle/ChillDocGeneratorBundle/Repository/DocGeneratorTemplateRepository.php b/src/Bundle/ChillDocGeneratorBundle/Repository/DocGeneratorTemplateRepository.php index df0e0bd3d..5d99efa94 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Repository/DocGeneratorTemplateRepository.php +++ b/src/Bundle/ChillDocGeneratorBundle/Repository/DocGeneratorTemplateRepository.php @@ -25,6 +25,18 @@ final class DocGeneratorTemplateRepository implements ObjectRepository $this->repository = $entityManager->getRepository(DocGeneratorTemplate::class); } + public function countByEntity(string $entity): int + { + $builder = $this->repository->createQueryBuilder('t'); + + $builder + ->select('count(t)') + ->where('t.entity LIKE :entity') + ->setParameter('entity', addslashes($entity)); + + return $builder->getQuery()->getSingleScalarResult(); + } + public function find($id, $lockMode = null, $lockVersion = null): ?DocGeneratorTemplate { return $this->repository->find($id, $lockMode, $lockVersion); @@ -49,15 +61,22 @@ final class DocGeneratorTemplateRepository implements ObjectRepository return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } - public function findByEntity($entity) + /** + * @return array|DocGeneratorTemplate[] + */ + public function findByEntity(string $entity, ?int $start = 0, ?int $limit = 50): array { $builder = $this->repository->createQueryBuilder('t'); $builder - ->where('t.entities LIKE :entity') - ->setParameter('entity', '%' . addslashes($entity) . '%'); + ->where('t.entity LIKE :entity') + ->setParameter('entity', addslashes($entity)); - return $builder->getQuery()->execute(); + return $builder + ->getQuery() + ->setFirstResult($start) + ->setMaxResults($limit) + ->getResult(); } public function findOneBy(array $criteria, ?array $orderBy = null): ?DocGeneratorTemplate diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/public/api/pickTemplate.js b/src/Bundle/ChillDocGeneratorBundle/Resources/public/api/pickTemplate.js new file mode 100644 index 000000000..a668fe29d --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/public/api/pickTemplate.js @@ -0,0 +1,10 @@ +import { fetchResults } from "ChillMainAssets/lib/api/apiMethods.js"; + +const fetchTemplates = (entityClass) => { + let fqdnEntityClass = encodeURI(entityClass); + return fetchResults(`/api/1.0/docgen/templates/by-entity/${fqdnEntityClass}`); +} + +export { + fetchTemplates +}; diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/public/module/PickTemplate/index.js b/src/Bundle/ChillDocGeneratorBundle/Resources/public/module/PickTemplate/index.js new file mode 100644 index 000000000..9d26d1009 --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/public/module/PickTemplate/index.js @@ -0,0 +1,27 @@ +import {createApp} from 'vue'; +import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue'; +import {fetchTemplates} from 'ChillDocGeneratorAssets/api/pickTemplate.js'; +import {_createI18n} from 'ChillMainAssets/vuejs/_js/i18n'; + +const i18n = _createI18n({}); + +document.querySelectorAll('div[data-docgen-template-picker]').forEach(el => { + fetchTemplates(el.dataset.entityClass).then(templates => { + let + picker = { + template: '', + components: { + PickTemplate, + }, + data() { + return { + templates: templates, + entityId: el.dataset.entityId, + } + }, + } + ; + createApp(picker).use(i18n).mount(el); + }) + +}); diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/public/vuejs/_components/PickTemplate.vue b/src/Bundle/ChillDocGeneratorBundle/Resources/public/vuejs/_components/PickTemplate.vue new file mode 100644 index 000000000..f1fb6c84b --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/public/vuejs/_components/PickTemplate.vue @@ -0,0 +1,141 @@ + + + + + diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Admin/DocGeneratorTemplate/edit.html.twig b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Admin/DocGeneratorTemplate/edit.html.twig index 040dea1c0..93b08f688 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Admin/DocGeneratorTemplate/edit.html.twig +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Admin/DocGeneratorTemplate/edit.html.twig @@ -1,12 +1,25 @@ -{% extends '@ChillPerson/Admin/layout.html.twig' %} +{% extends '@ChillDocGenerator/Admin/layout.html.twig' %} -{% block title %} - {% include('@ChillMain/CRUD/_edit_title.html.twig') %} -{% endblock %} +{% block title 'docgen.Edit template'|trans %} {% block layout_wvm_content %} {% embed '@ChillMain/CRUD/_edit_content.html.twig' %} + + {% block crud_content_header %} +

{{ 'docgen.Edit template'|trans }}

+

{{ 'docgen.With context %name%'|trans({'%name%': context.name|trans }) }}

+ {% endblock crud_content_header %} + {% block content_form_actions_view %}{% endblock %} {% block content_form_actions_save_and_show %}{% endblock %} {% endembed %} {% endblock %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_async_upload') }} +{% endblock %} + +{% block css %} + {{ encore_entry_link_tags('mod_async_upload') }} +{% endblock %} diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Admin/DocGeneratorTemplate/index.html.twig b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Admin/DocGeneratorTemplate/index.html.twig index c601b8836..1cdad36ff 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Admin/DocGeneratorTemplate/index.html.twig +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Admin/DocGeneratorTemplate/index.html.twig @@ -1,11 +1,12 @@ -{% extends '@ChillPerson/Admin/layout.html.twig' %} +{% extends '@ChillDocGenerator/Admin/layout.html.twig' %} {% block layout_wvm_content %} {% embed '@ChillMain/CRUD/_index.html.twig' %} {% block table_entities_thead_tr %} {{ 'Title'|trans }} - {{ 'File'|trans }} + {{ 'docgen.Context'|trans }} + {{ 'Edit'|trans }} {% endblock %} {% block table_entities_tbody %} @@ -13,7 +14,12 @@ {{ entity.id }} {{ entity.name | localize_translatable_string }} - {{ entity.file }} + {{ contextManager.getContextByKey(entity.context).name|trans }} + + + {{ 'Edit'|trans }} + + {% endfor %} {% endblock %} diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Admin/DocGeneratorTemplate/new.html.twig b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Admin/DocGeneratorTemplate/new.html.twig index f31ac39c9..fe4991ffa 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Admin/DocGeneratorTemplate/new.html.twig +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Admin/DocGeneratorTemplate/new.html.twig @@ -1,11 +1,25 @@ -{% extends '@ChillPerson/Admin/layout.html.twig' %} +{% extends '@ChillDocGenerator/Admin/layout.html.twig' %} -{% block title %} - {% include('@ChillMain/CRUD/_new_title.html.twig') %} -{% endblock %} +{% block title 'docgen.New template'|trans %} {% block layout_wvm_content %} {% embed '@ChillMain/CRUD/_new_content.html.twig' %} + + {% block crud_content_header %} +

{{ 'docgen.New template'|trans }}

+

{{ 'docgen.With context %name%'|trans({'%name%': context.name|trans }) }}

+ {% endblock crud_content_header %} + {% block content_form_actions_save_and_show %}{% endblock %} + {% endembed %} {% endblock %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_async_upload') }} +{% endblock %} + +{% block css %} + {{ encore_entry_link_tags('mod_async_upload') }} +{% endblock %} diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Admin/DocGeneratorTemplate/pick-context.html.twig b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Admin/DocGeneratorTemplate/pick-context.html.twig new file mode 100644 index 000000000..5e3ff2049 --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Admin/DocGeneratorTemplate/pick-context.html.twig @@ -0,0 +1,27 @@ +{% extends '@ChillDocGenerator/Admin/layout.html.twig' %} + +{% block title 'docgen.Pick template context'|trans %} + +{% block layout_wvm_content %} +
+ +

{{ block('title') }}

+
+ {% for key, context in contexts %} +
+ +
+ {{ context.description|trans|nl2br }} +
+
+ {% endfor %} +
+
+ +{% endblock %} diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Admin/layout.html.twig b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Admin/layout.html.twig new file mode 100644 index 000000000..6e7c9387c --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Admin/layout.html.twig @@ -0,0 +1,4 @@ +{% extends '@ChillPerson/Admin/layout.html.twig' %} + +{% block vertical_menu_content %} +{% endblock %} diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Generator/basic_form.html.twig b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Generator/basic_form.html.twig new file mode 100644 index 000000000..7b24eae0d --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Generator/basic_form.html.twig @@ -0,0 +1,22 @@ +{% extends 'ChillMainBundle::layout.html.twig' %} + +{% block title 'docgen.Generate a document'|trans %} + +{% block content %} +
+

{{ block('title') }}

+

{{ template.name|localize_translatable_string }}

+ + {{ form_start(form, { 'attr': { 'id': 'generate_doc_form' }}) }} + {{ form(form) }} + {{ form_end(form) }} + +
    +
  • + +
  • +
+
+{% endblock %} diff --git a/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php b/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php index 040738571..c32be9913 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php +++ b/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php @@ -86,19 +86,52 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte private function getExpectedType(AttributeMetadata $attribute, ReflectionClass $reflection): string { - // we have to get the expected content - if ($reflection->hasProperty($attribute->getName())) { - $type = $reflection->getProperty($attribute->getName())->getType(); - } elseif ($reflection->hasMethod($attribute->getName())) { - $type = $reflection->getMethod($attribute->getName())->getReturnType(); - } else { - throw new \LogicException(sprintf( - 'Could not determine how the content is determined for the attribute %s. Add attribute property only on property or method', - $attribute->getName() - )); - } + $type = null; - if (null === $type) { + do { + // we have to get the expected content + if ($reflection->hasProperty($attribute->getName())) { + if (!$reflection->getProperty($attribute->getName())->hasType()) { + throw new \LogicException(sprintf( + 'Could not determine how the content is determined for the attribute %s. Add a type on this property', + $attribute->getName() + )); + } + + $type = $reflection->getProperty($attribute->getName())->getType(); + } elseif ($reflection->hasMethod($method = 'get' . ucfirst($attribute->getName()))) { + if (!$reflection->getMethod($method)->hasReturnType()) { + throw new \LogicException(sprintf( + 'Could not determine how the content is determined for the attribute %s. Add a return type on the method', + $attribute->getName() + )); + } + + $type = $reflection->getMethod($method)->getReturnType(); + } elseif ($reflection->hasMethod($method = 'is' . ucfirst($attribute->getName()))) { + if (!$reflection->getMethod($method)->hasReturnType()) { + throw new \LogicException(sprintf( + 'Could not determine how the content is determined for the attribute %s. Add a return type on the method', + $attribute->getName() + )); + } + + $type = $reflection->getMethod($method)->getReturnType(); + } elseif ($reflection->hasMethod($attribute->getName())) { + if (!$reflection->getMethod($attribute->getName())->hasReturnType()) { + throw new \LogicException(sprintf( + 'Could not determine how the content is determined for the attribute %s. Add a return type on the method', + $attribute->getName() + )); + } + + $type = $reflection->getMethod($attribute->getName())->getReturnType(); + } else { + $reflection = $reflection->getParentClass(); + } + } while (null === $type && $reflection instanceof ReflectionClass); + + if (null === $type ?? null) { throw new \LogicException(sprintf( 'Could not determine the type for this attribute: %s. Add a return type to the method or property declaration', $attribute->getName() @@ -134,11 +167,15 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte switch ($type) { case 'array': + return []; + case 'bool': case 'double': case 'float': case 'int': case 'resource': + return null; + case 'string': return ''; @@ -173,7 +210,18 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte $value = $this->propertyAccess->getValue($object, $attribute->getName()); $key = $attribute->getSerializedName() ?? $attribute->getName(); - if (is_object($value)) { + if (is_iterable($value)) { + $arr = []; + + foreach ($value as $k => $v) { + $arr[$k] = + $this->normalizer->normalize($v, $format, array_merge( + $context, + $attribute->getNormalizationContextForGroups($expectedGroups) + )); + } + $data[$key] = $arr; + } elseif (is_object($value)) { $data[$key] = $this->normalizer->normalize($value, $format, array_merge( $context, @@ -182,7 +230,7 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte } elseif (null === $value) { $data[$key] = $this->normalizeNullOutputValue($format, $context, $attribute, $reflection); } else { - $data[$key] = (string) $value; + $data[$key] = $value; } } diff --git a/src/Bundle/ChillDocGeneratorBundle/chill.webpack.config.js b/src/Bundle/ChillDocGeneratorBundle/chill.webpack.config.js index d894be9b0..9017c7e08 100644 --- a/src/Bundle/ChillDocGeneratorBundle/chill.webpack.config.js +++ b/src/Bundle/ChillDocGeneratorBundle/chill.webpack.config.js @@ -3,4 +3,6 @@ module.exports = function(encore, entries) { encore.addAliases({ ChillDocGeneratorAssets: __dirname + '/Resources/public' }); + + encore.addEntry('mod_docgen_picktemplate', __dirname + '/Resources/public/module/PickTemplate/index.js'); }; diff --git a/src/Bundle/ChillDocGeneratorBundle/config/services.yaml b/src/Bundle/ChillDocGeneratorBundle/config/services.yaml index 6d87dbd25..10ab6d172 100644 --- a/src/Bundle/ChillDocGeneratorBundle/config/services.yaml +++ b/src/Bundle/ChillDocGeneratorBundle/config/services.yaml @@ -9,9 +9,35 @@ services: autoconfigure: true resource: '../Repository/' + Chill\DocGeneratorBundle\Menu\: + autoconfigure: true + autowire: true + resource: '../Menu/' + Chill\DocGeneratorBundle\Serializer\Normalizer\: autowire: true autoconfigure: true resource: '../Serializer/Normalizer/' tags: - { name: 'serializer.normalizer', priority: -152 } + + Chill\DocGeneratorBundle\Controller\: + resource: "../Controller" + autowire: true + autoconfigure: true + + Chill\DocGeneratorBundle\Form\: + resource: "../Form/" + autowire: true + autoconfigure: true + + Chill\DocGeneratorBundle\GeneratorDriver\: + resource: "../GeneratorDriver/" + autowire: true + autoconfigure: true + + Chill\DocGeneratorBundle\Driver\RelatorioDriver: '@Chill\DocGeneratorBundle\Driver\DriverInterface' + + Chill\DocGeneratorBundle\Context\ContextManager: + arguments: + $contexts: !tagged_iterator { tag: chill_docgen.context, default_index_method: getKey } diff --git a/src/Bundle/ChillDocGeneratorBundle/migrations/Version20211103111010.php b/src/Bundle/ChillDocGeneratorBundle/migrations/Version20211103111010.php new file mode 100644 index 000000000..1530fd065 --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/migrations/Version20211103111010.php @@ -0,0 +1,46 @@ +addSql('ALTER TABLE chill_docgen_template DROP CONSTRAINT FK_49A347E893CB796C'); + $this->addSql('DROP INDEX IDX_49A347E893CB796C'); + $this->addSql('ALTER TABLE chill_docgen_template ADD file VARCHAR(255) NOT NULL'); + $this->addSql('ALTER TABLE chill_docgen_template DROP file_id'); + $this->addSql('ALTER TABLE chill_docgen_template ALTER entities DROP NOT NULL'); + $this->addSql('ALTER TABLE chill_docgen_template ALTER context DROP NOT NULL'); + } + + public function getDescription(): string + { + return 'Using DocStore objects inside the DocGenTemplate'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_docgen_template ADD file_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_docgen_template DROP file'); + $this->addSql('ALTER TABLE chill_docgen_template ALTER entities SET NOT NULL'); + $this->addSql('ALTER TABLE chill_docgen_template ALTER context SET NOT NULL'); + $this->addSql('ALTER TABLE chill_docgen_template ADD CONSTRAINT FK_49A347E893CB796C FOREIGN KEY (file_id) REFERENCES chill_doc.stored_object (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_49A347E893CB796C ON chill_docgen_template (file_id)'); + } +} diff --git a/src/Bundle/ChillDocGeneratorBundle/migrations/Version20211201191757.php b/src/Bundle/ChillDocGeneratorBundle/migrations/Version20211201191757.php new file mode 100644 index 000000000..814974d7b --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/migrations/Version20211201191757.php @@ -0,0 +1,41 @@ +addSql('ALTER TABLE chill_docgen_template DROP active'); + $this->addSql('ALTER TABLE chill_docgen_template DROP entity'); + $this->addSql('ALTER TABLE chill_docgen_template DROP template_options'); + $this->addSql('ALTER TABLE chill_docgen_template ADD entities TEXT'); + $this->addSql('COMMENT ON COLUMN chill_docgen_template.entities IS \'(DC2Type:simple_array)\''); + } + + public function getDescription(): string + { + return 'Add options, active and link to entity in docgen_template'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_docgen_template ADD active BOOLEAN DEFAULT true NOT NULL'); + $this->addSql('ALTER TABLE chill_docgen_template ADD entity VARCHAR(255) NOT NULL DEFAULT \'\''); + $this->addSql('ALTER TABLE chill_docgen_template ADD template_options JSONB NOT NULL DEFAULT \'[]\'::jsonb '); + $this->addSql('COMMENT ON COLUMN chill_docgen_template.template_options IS \'(DC2Type:json)\''); + $this->addSql('ALTER TABLE chill_docgen_template DROP entities'); + } +} diff --git a/src/Bundle/ChillDocGeneratorBundle/translations/messages.fr.yml b/src/Bundle/ChillDocGeneratorBundle/translations/messages.fr.yml index e69de29bb..af08cf1c8 100644 --- a/src/Bundle/ChillDocGeneratorBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillDocGeneratorBundle/translations/messages.fr.yml @@ -0,0 +1,19 @@ +docgen: + Generate a document: Génerer un document + Generate: Génerer + Document generation: Génération de documents + Manage templates and document generation: Gestion des documents générés et de leurs gabarits + Pick template context: Choisir un contexte + Context: Contexte + New template: Nouveau gabarit + Edit template: Modifier gabarit + With context: 'Avec le contexte :' + + +crud: + docgen_template: + index: + title: Génération de documents + add_new: Créer + + diff --git a/src/Bundle/ChillDocStoreBundle/Entity/DocumentCategory.php b/src/Bundle/ChillDocStoreBundle/Entity/DocumentCategory.php index 1ded5ccb2..55850b157 100644 --- a/src/Bundle/ChillDocStoreBundle/Entity/DocumentCategory.php +++ b/src/Bundle/ChillDocStoreBundle/Entity/DocumentCategory.php @@ -15,7 +15,7 @@ use Doctrine\ORM\Mapping as ORM; /** * @ORM\Table("chill_doc.document_category") - * @ORM\Entity(repositoryClass="Chill\DocStoreBundle\EntityRepository\DocumentCategoryRepository") + * @ORM\Entity */ class DocumentCategory { diff --git a/src/Bundle/ChillDocStoreBundle/EntityRepository/DocumentCategoryRepository.php b/src/Bundle/ChillDocStoreBundle/EntityRepository/DocumentCategoryRepository.php deleted file mode 100644 index fbf4acb15..000000000 --- a/src/Bundle/ChillDocStoreBundle/EntityRepository/DocumentCategoryRepository.php +++ /dev/null @@ -1,31 +0,0 @@ -getEntityManager() - ->createQuery( - 'SELECT MAX(c.idInsideBundle) + 1 FROM ChillDocStoreBundle:DocumentCategory c' - ) - ->getSingleResult(); - - return reset($array_res); - } -} diff --git a/src/Bundle/ChillDocStoreBundle/Repository/DocumentCategoryRepository.php b/src/Bundle/ChillDocStoreBundle/Repository/DocumentCategoryRepository.php new file mode 100644 index 000000000..f930e8ece --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Repository/DocumentCategoryRepository.php @@ -0,0 +1,75 @@ +em = $em; + $this->repository = $em->getRepository(DocumentCategory::class); + } + + /** + * @param mixed $id + */ + public function find($id): ?DocumentCategory + { + return $this->repository->find($id); + } + + /** + * @return array|DocumentCategory[] + */ + public function findAll(): array + { + return $this->repository->findAll(); + } + + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null) + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?DocumentCategory + { + return $this->findOneBy($criteria); + } + + public function getClassName() + { + return DocumentCategory::class; + } + + public function nextIdInsideBundle() + { + $array_res = $this->em + ->createQuery( + 'SELECT MAX(c.idInsideBundle) + 1 FROM ChillDocStoreBundle:DocumentCategory c' + ) + ->getSingleResult(); + + return reset($array_res); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/module/async_upload/downloader.js b/src/Bundle/ChillDocStoreBundle/Resources/public/module/async_upload/downloader.js index deeb2828f..b8abcae62 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/module/async_upload/downloader.js +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/module/async_upload/downloader.js @@ -5,7 +5,7 @@ var algo = 'AES-CBC'; var initializeButtons = (root) => { var buttons = root.querySelectorAll('a[data-download-button]'); - + for (let i = 0; i < buttons.length; i ++) { initialize(buttons[i]); } @@ -33,9 +33,12 @@ var download = (button) => { fetchError = "Error while fetching file", key, url ; - + + console.log('keyData', keyData); + console.log('ivData', ivData); + button.textContent = labelPreparing; - + window.fetch(urlGenerator) .then((r) => { if (r.ok) { @@ -46,26 +49,19 @@ var download = (button) => { }) .then(data => { url = data.url; - - return window.crypto.subtle.importKey('jwk', keyData, { name: algo, iv: iv}, false, ['decrypt']); - }) - .catch(e => { - console.error("error while importing key"); - console.error(e); - button.appendChild(document.createTextNode(decryptError)); + + if (keyData.length > 0) { + return window.crypto.subtle.importKey('jwk', keyData, { name: algo, iv: iv}, false, ['decrypt']); + } + return Promise.resolve(undefined); }) .then(nKey => { key = nKey; return window.fetch(url); }) - .catch(e => { - console.error("error while fetching data"); - console.error(e); - button.textContent = ""; - button.appendChild(document.createTextNode(fetchError)); - }) .then(r => { + console.log('r', r); if (r.ok) { return r.arrayBuffer(); } else { @@ -73,16 +69,16 @@ var download = (button) => { } }) .then(buffer => { - return window.crypto.subtle.decrypt({ name: algo, iv: iv }, key, buffer); - }) - .catch(e => { - console.error("error while importing key"); - console.error(e); - button.textContent = ""; - button.appendChild(document.createTextNode(decryptError)); + console.log('buffer', buffer); + if (keyData.length > 0) { + return window.crypto.subtle.decrypt({ name: algo, iv: iv }, key, buffer); + } + + return Promise.resolve(buffer); }) .then(decrypted => { - var + console.log('decrypted', decrypted); + var blob = new Blob([decrypted], { type: mimeType }), url = window.URL.createObjectURL(blob) ; diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig index dd0c9cc62..893f504bc 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig @@ -11,6 +11,13 @@ {% block js %} {{ parent() }} {{ encore_entry_script_tags('mod_async_upload') }} + {{ encore_entry_script_tags('mod_docgen_picktemplate') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_async_upload') }} + {{ encore_entry_link_tags('mod_docgen_picktemplate') }} {% endblock %} {% block content %} @@ -29,7 +36,7 @@ {% for document in documents %} {{ document.title }} - {{ document.category.name|localize_translatable_string }} + {% if document.category %}{{ document.category.name|localize_translatable_string }}{% endif %}
    {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %} @@ -58,6 +65,8 @@ +
    + {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_CREATE', accompanyingCourse) %}
    • diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig index 3bf9cac43..90bb6289d 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig @@ -22,8 +22,10 @@
      {{ 'Title'|trans }}
      {{ document.title }}
      -
      {{ 'Category'|trans }}
      -
      {{ document.category.name|localize_translatable_string }}
      + {% if document.category is not null %} +
      {{ 'Category'|trans }}
      +
      {{ document.category.name|localize_translatable_string }}
      + {% endif %}
      {{ 'Description' | trans }}
      diff --git a/src/Bundle/ChillMainBundle/Entity/Scope.php b/src/Bundle/ChillMainBundle/Entity/Scope.php index 3a6cbd953..fc6084883 100644 --- a/src/Bundle/ChillMainBundle/Entity/Scope.php +++ b/src/Bundle/ChillMainBundle/Entity/Scope.php @@ -33,7 +33,7 @@ class Scope * @ORM\Id * @ORM\Column(name="id", type="integer") * @ORM\GeneratedValue(strategy="AUTO") - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) */ private $id; @@ -43,7 +43,7 @@ class Scope * @var array * * @ORM\Column(type="json") - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) */ private $name = []; diff --git a/src/Bundle/ChillMainBundle/Phonenumber/PhonenumberHelper.php b/src/Bundle/ChillMainBundle/Phonenumber/PhonenumberHelper.php index e807efebe..0bf1cc8f4 100644 --- a/src/Bundle/ChillMainBundle/Phonenumber/PhonenumberHelper.php +++ b/src/Bundle/ChillMainBundle/Phonenumber/PhonenumberHelper.php @@ -204,7 +204,7 @@ class PhonenumberHelper return null; } - $format = json_decode($response->getBody())->national_format; + $format = json_decode($response->getBody()->getContents())->national_format; $item ->set($format) diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss index 8e3a9121b..6b22302b2 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss @@ -101,6 +101,10 @@ div.flex-table { div.item-row { flex-direction: row; + &.column { // exception + flex-direction: column + } + div.item-col { &:first-child { flex-grow: 0; flex-shrink: 0; flex-basis: auto; @@ -160,6 +164,12 @@ div.wrap-list { & > * { padding-right: 1em; } + + h3, h4 { + font-weight: 700; + font-size: 100%; + font-family: 'Open Sans'; + } } div.wl-col.list { @@ -171,6 +181,10 @@ div.wrap-list { padding: 0em; display: inline-block; } + + blockquote.chill-user-quote { + margin: 0.7em 0; + } } } diff --git a/src/Bundle/ChillMainBundle/Security/Resolver/ScopeResolverDispatcher.php b/src/Bundle/ChillMainBundle/Security/Resolver/ScopeResolverDispatcher.php index 1cd03e2b7..0e4cfdc78 100644 --- a/src/Bundle/ChillMainBundle/Security/Resolver/ScopeResolverDispatcher.php +++ b/src/Bundle/ChillMainBundle/Security/Resolver/ScopeResolverDispatcher.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Security\Resolver; use Chill\MainBundle\Entity\Scope; +use Doctrine\Common\Collections\Collection; final class ScopeResolverDispatcher { @@ -45,7 +46,13 @@ final class ScopeResolverDispatcher { foreach ($this->resolvers as $resolver) { if ($resolver->supports($entity, $options)) { - return $resolver->resolveScope($entity, $options); + $scopes = $resolver->resolveScope($entity, $options); + + if ($scopes instanceof Collection) { + return $scopes->toArray(); + } + + return $scopes; } } diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php index f046cb34f..22219b108 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php @@ -45,6 +45,6 @@ class UserNormalizer implements NormalizerAwareInterface, NormalizerInterface public function supportsNormalization($data, ?string $format = null): bool { - return 'json' === $format && $data instanceof User; + return $data instanceof User && ('json' === $format || 'docgen' === $format); } } diff --git a/src/Bundle/ChillMainBundle/Templating/TranslatableStringHelper.php b/src/Bundle/ChillMainBundle/Templating/TranslatableStringHelper.php index 5da53f515..88bb8f89d 100644 --- a/src/Bundle/ChillMainBundle/Templating/TranslatableStringHelper.php +++ b/src/Bundle/ChillMainBundle/Templating/TranslatableStringHelper.php @@ -11,20 +11,24 @@ declare(strict_types=1); namespace Chill\MainBundle\Templating; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Contracts\Translation\TranslatorInterface; use function array_key_exists; final class TranslatableStringHelper implements TranslatableStringHelperInterface { + private string $defaultLocale; + private RequestStack $requestStack; private TranslatorInterface $translator; - public function __construct(RequestStack $requestStack, TranslatorInterface $translator) + public function __construct(RequestStack $requestStack, TranslatorInterface $translator, ParameterBagInterface $parameterBag) { $this->requestStack = $requestStack; $this->translator = $translator; + $this->defaultLocale = $parameterBag->get('kernel.default_locale'); } public function localize(array $translatableStrings): ?string @@ -35,11 +39,7 @@ final class TranslatableStringHelper implements TranslatableStringHelperInterfac $request = $this->requestStack->getCurrentRequest(); - if (null === $request) { - return null; - } - - $language = $request->getLocale(); + $language = null === $request ? $this->defaultLocale : $request->getLocale(); if (array_key_exists($language, $translatableStrings)) { return $translatableStrings[$language]; diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodWorkEvaluationApiController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodWorkEvaluationApiController.php new file mode 100644 index 000000000..ddd3445a4 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodWorkEvaluationApiController.php @@ -0,0 +1,78 @@ +docGeneratorTemplateRepository = $docGeneratorTemplateRepository; + $this->serializer = $serializer; + $this->paginatorFactory = $paginatorFactory; + } + + /** + * @Route("/api/1.0/person/docgen/template/by-evaluation/{id}.{_format}", + * requirements={"format": "json"}) + */ + public function listTemplateByEvaluation(Evaluation $evaluation, string $_format): JsonResponse + { + if ('json' !== $_format) { + throw new BadRequestHttpException('format not supported'); + } + + $evaluations = + array_filter( + $this->docGeneratorTemplateRepository + ->findByEntity(AccompanyingPeriodWorkEvaluation::class), + static function (DocGeneratorTemplate $t) use ($evaluation) { + $ids = $t->getOptions()['evaluations'] ?? []; + + return in_array($evaluation->getId(), $ids, true); + } + ); + + $paginator = $this->paginatorFactory->create(count($evaluations)); + $paginator->setItemsPerPage(count($evaluations)); + + return new JsonResponse($this->serializer->serialize( + new Collection($evaluations, $paginator), + 'json', + [ + AbstractNormalizer::GROUPS => ['read'], + ] + ), JsonResponse::HTTP_OK, [], true); + } +} diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index d4154ff4f..849983187 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -120,11 +120,11 @@ class AccompanyingPeriod implements * @var DateTime * * @ORM\Column(type="date", nullable=true) - * @Groups({"read", "write"}) + * @Groups({"read", "write", "docgen:read"}) * @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CLOSED}) * @Assert\GreaterThan(propertyPath="openingDate", groups={AccompanyingPeriod::STEP_CLOSED}) */ - private $closingDate; + private ?DateTime $closingDate = null; /** * @var AccompanyingPeriod\ClosingMotive @@ -135,11 +135,9 @@ class AccompanyingPeriod implements * @Groups({"read", "write"}) * @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CLOSED}) */ - private $closingMotive; + private ?ClosingMotive $closingMotive = null; /** - * @var Collection - * * @ORM\OneToMany(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\Comment", * mappedBy="accompanyingPeriod", * cascade={"persist", "remove"}, @@ -147,33 +145,32 @@ class AccompanyingPeriod implements * ) * @Assert\NotBlank(groups={AccompanyingPeriod::STEP_DRAFT}) */ - private $comments; + private Collection $comments; /** - * @var bool * @ORM\Column(type="boolean", options={"default": false}) - * @Groups({"read", "write"}) + * @Groups({"read", "write", "docgen:read"}) */ - private $confidential = false; + private bool $confidential = false; /** * @ORM\Column(type="datetime", nullable=true, options={"default": NULL}) + * @Groups({"docgen:read"}) */ - private DateTimeInterface $createdAt; + private ?DateTimeInterface $createdAt = null; /** * @ORM\ManyToOne(targetEntity=User::class) * @ORM\JoinColumn(nullable=true) - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) */ - private $createdBy; + private ?User $createdBy = null; /** - * @var bool * @ORM\Column(type="boolean", options={"default": false}) - * @Groups({"read", "write"}) + * @Groups({"read", "write", "docgen:read"}) */ - private $emergency = false; + private bool $emergency = false; /** * @var int @@ -181,9 +178,9 @@ class AccompanyingPeriod implements * @ORM\Id * @ORM\Column(name="id", type="integer") * @ORM\GeneratedValue(strategy="AUTO") - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) */ - private $id; + private ?int $id = null; /** * @ORM\ManyToOne( @@ -205,9 +202,9 @@ class AccompanyingPeriod implements * @var DateTime * * @ORM\Column(type="date") - * @Groups({"read", "write"}) + * @Groups({"read", "write", "docgen:read"}) */ - private $openingDate; + private ?DateTime $openingDate = null; /** * @ORM\ManyToOne(targetEntity=Origin::class) @@ -215,18 +212,16 @@ class AccompanyingPeriod implements * @Groups({"read", "write"}) * @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CONFIRMED}) */ - private $origin; + private ?Origin $origin = null; /** - * @var Collection - * * @ORM\OneToMany(targetEntity=AccompanyingPeriodParticipation::class, * mappedBy="accompanyingPeriod", orphanRemoval=true, * cascade={"persist", "refresh", "remove", "merge", "detach"}) - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) * @ParticipationOverlap(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED}) */ - private $participations; + private Collection $participations; /** * @ORM\ManyToOne( @@ -237,48 +232,42 @@ class AccompanyingPeriod implements private ?Person $personLocation = null; /** - * @var string - * * @ORM\Column(type="text") * @Groups({"read", "write"}) */ - private $remark = ''; + private string $remark = ''; /** - * @var bool * @ORM\Column(type="boolean", options={"default": false}) - * @Groups({"read", "write"}) + * @Groups({"read", "write", "docgen:read"}) */ - private $requestorAnonymous = false; + private bool $requestorAnonymous = false; /** * @ORM\ManyToOne(targetEntity=Person::class, inversedBy="accompanyingPeriodRequested") * @ORM\JoinColumn(nullable=true) */ - private $requestorPerson; + private ?Person $requestorPerson = null; /** * @ORM\ManyToOne(targetEntity=ThirdParty::class) * @ORM\JoinColumn(nullable=true) */ - private $requestorThirdParty; + private ?ThirdParty $requestorThirdParty = null; /** - * @var Collection - * * @ORM\OneToMany( * targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\Resource", * mappedBy="accompanyingPeriod", * cascade={"persist", "remove"}, * orphanRemoval=true * ) - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) * @ResourceDuplicateCheck(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED, "Default", "default"}) */ - private $resources; + private Collection $resources; /** - * @var Collection * @ORM\ManyToMany( * targetEntity=Scope::class, * cascade={} @@ -288,10 +277,10 @@ class AccompanyingPeriod implements * joinColumns={@ORM\JoinColumn(name="accompanying_period_id", referencedColumnName="id")}, * inverseJoinColumns={@ORM\JoinColumn(name="scope_id", referencedColumnName="id")} * ) - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) * @Assert\Count(min=1, groups={AccompanyingPeriod::STEP_CONFIRMED}, minMessage="A course must be associated to at least one scope") */ - private $scopes; + private Collection $scopes; /** * @ORM\ManyToMany( @@ -300,36 +289,35 @@ class AccompanyingPeriod implements * @ORM\JoinTable( * name="chill_person_accompanying_period_social_issues" * ) - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) * @Assert\Count(min=1, groups={AccompanyingPeriod::STEP_CONFIRMED}, minMessage="A course must contains at least one social issue") */ private Collection $socialIssues; /** - * @var string * @ORM\Column(type="string", length=32, nullable=true) * @Groups({"read"}) */ - private $step = self::STEP_DRAFT; + private string $step = self::STEP_DRAFT; /** * @ORM\Column(type="datetime", nullable=true, options={"default": NULL}) */ - private DateTimeInterface $updatedAt; + private ?DateTimeInterface $updatedAt = null; /** * @ORM\ManyToOne( * targetEntity=User::class * ) */ - private User $updatedBy; + private ?User $updatedBy = null; /** * @ORM\ManyToOne(targetEntity=User::class) * @ORM\JoinColumn(nullable=true) - * @Groups({"read", "write"}) + * @Groups({"read", "write", "docgen:read"}) */ - private $user; + private ?User $user = null; /** * @ORM\OneToMany( @@ -355,6 +343,7 @@ class AccompanyingPeriod implements $this->socialIssues = new ArrayCollection(); $this->comments = new ArrayCollection(); $this->works = new ArrayCollection(); + $this->resources = new ArrayCollection(); } /** @@ -580,11 +569,19 @@ class AccompanyingPeriod implements }); } + public function getCreatedAt(): ?DateTime + { + return $this->createdAt; + } + public function getCreatedBy(): ?User { return $this->createdBy; } + /** + * @Groups({"docgen:read"}) + */ public function getCurrentParticipations(): Collection { return $this->getOpenParticipations(); @@ -608,7 +605,7 @@ class AccompanyingPeriod implements * * @return int */ - public function getId() + public function getId(): ?int { return $this->id; } @@ -634,7 +631,7 @@ class AccompanyingPeriod implements public function getLocation(?DateTimeImmutable $at = null): ?Address { if ($this->getPersonLocation() instanceof Person) { - return $this->getPersonLocation()->getCurrentHouseholdAddress($at); + return $this->getPersonLocation()->getCurrentPersonAddress(); } return $this->getAddressLocation(); @@ -663,7 +660,7 @@ class AccompanyingPeriod implements * * @return DateTime */ - public function getOpeningDate() + public function getOpeningDate(): ?DateTime { return $this->openingDate; } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php index 2d0bfe918..0e7b27cef 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php @@ -53,7 +53,7 @@ use Symfony\Component\Validator\Constraints as Assert; * cascade={"remove", "persist"}, * orphanRemoval=true * ) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) * * @internal /!\ the serialization for write evaluations is handled in `AccompanyingPeriodWorkDenormalizer` */ @@ -61,24 +61,26 @@ use Symfony\Component\Validator\Constraints as Assert; /** * @ORM\Column(type="datetime_immutable") - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private ?DateTimeImmutable $createdAt = null; /** * @ORM\Column(type="boolean") + * @Serializer\Groups({"read", "docgen:read"}) */ private bool $createdAutomatically = false; /** * @ORM\Column(type="text") + * @Serializer\Groups({"read", "docgen:read"}) */ private string $createdAutomaticallyReason = ''; /** * @ORM\ManyToOne(targetEntity=User::class) * @ORM\JoinColumn(nullable=false) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private ?User $createdBy = null; @@ -86,7 +88,7 @@ use Symfony\Component\Validator\Constraints as Assert; * @ORM\Column(type="date_immutable", nullable=true, options={"default": null}) * @Serializer\Groups({"accompanying_period_work:create"}) * @Serializer\Groups({"accompanying_period_work:edit"}) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) * @Assert\GreaterThan(propertyPath="startDate", * message="accompanying_course_work.The endDate should be greater than the start date" * ) @@ -100,14 +102,14 @@ use Symfony\Component\Validator\Constraints as Assert; * cascade={"persist"}, * orphanRemoval=true * ) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) * @Serializer\Groups({"accompanying_period_work:edit"}) */ private Collection $goals; /** * @ORM\ManyToOne(targetEntity=ThirdParty::class) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) * @Serializer\Groups({"accompanying_period_work:edit"}) * * In schema : traitant @@ -118,20 +120,20 @@ use Symfony\Component\Validator\Constraints as Assert; * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ - private ?int $id; + private ?int $id = null; /** * @ORM\Column(type="text") - * @Serializer\Groups({"read", "accompanying_period_work:edit"}) + * @Serializer\Groups({"read", "accompanying_period_work:edit", "docgen:read"}) */ private string $note = ''; /** * @ORM\ManyToMany(targetEntity=Person::class) * @ORM\JoinTable(name="chill_person_accompanying_period_work_person") - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) * @Serializer\Groups({"accompanying_period_work:edit"}) * @Serializer\Groups({"accompanying_period_work:create"}) */ @@ -140,14 +142,14 @@ use Symfony\Component\Validator\Constraints as Assert; /** * @ORM\ManyToMany(targetEntity=Result::class, inversedBy="accompanyingPeriodWorks") * @ORM\JoinTable(name="chill_person_accompanying_period_work_result") - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) * @Serializer\Groups({"accompanying_period_work:edit"}) */ private Collection $results; /** * @ORM\ManyToOne(targetEntity=SocialAction::class) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) * @Serializer\Groups({"accompanying_period_work:create"}) */ private ?SocialAction $socialAction = null; @@ -156,30 +158,30 @@ use Symfony\Component\Validator\Constraints as Assert; * @ORM\Column(type="date_immutable") * @Serializer\Groups({"accompanying_period_work:create"}) * @Serializer\Groups({"accompanying_period_work:edit"}) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ - private DateTimeImmutable $startDate; + private ?DateTimeImmutable $startDate = null; /** * @ORM\ManyToMany(targetEntity=ThirdParty::class) * @ORM\JoinTable(name="chill_person_accompanying_period_work_third_party") * * In schema : intervenants - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) * @Serializer\Groups({"accompanying_period_work:edit"}) */ private Collection $thirdParties; /** * @ORM\Column(type="datetime_immutable") - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private ?DateTimeImmutable $updatedAt = null; /** * @ORM\ManyToOne(targetEntity=User::class) * @ORM\JoinColumn(nullable=false) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private ?User $updatedBy = null; diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php index cdcad98bd..479bbfb87 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php @@ -44,7 +44,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU /** * @ORM\Column(type="text", nullable=false, options={"default": ""}) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) * @Serializer\Groups({"write"}) * @Serializer\Groups({"accompanying_period_work_evaluation:create"}) */ @@ -52,7 +52,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU /** * @ORM\Column(type="date_immutable", nullable=true, options={"default": null}) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private ?DateTimeImmutable $createdAt = null; @@ -60,7 +60,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU * @ORM\ManyToOne( * targetEntity=User::class * ) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private ?User $createdBy = null; @@ -76,7 +76,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU /** * @ORM\Column(type="date_immutable", nullable=true, options={"default": null}) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) * @Serializer\Groups({"write"}) * @Serializer\Groups({"accompanying_period_work_evaluation:create"}) */ @@ -86,7 +86,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU * @ORM\ManyToOne( * targetEntity=Evaluation::class * ) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) * @Serializer\Groups({"accompanying_period_work_evaluation:create"}) */ private ?Evaluation $evaluation = null; @@ -95,7 +95,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private ?int $id = null; @@ -116,14 +116,14 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU /** * @ORM\Column(type="date_immutable", nullable=true, options={"default": null}) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) * @Serializer\Groups({"accompanying_period_work_evaluation:create"}) */ private ?DateTimeImmutable $maxDate = null; /** * @ORM\Column(type="date_immutable", nullable=true, options={"default": null}) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) * @Serializer\Groups({"write"}) * @Serializer\Groups({"accompanying_period_work_evaluation:create"}) */ @@ -131,7 +131,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU /** * @ORM\Column(type="date_immutable", nullable=true, options={"default": null}) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private ?DateTimeImmutable $updatedAt = null; @@ -139,7 +139,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU * @ORM\ManyToOne( * targetEntity=User::class * ) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private ?User $updatedBy = null; @@ -239,6 +239,18 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU return $this->updatedBy; } + /** + * @Serializer\Groups({"docgen:read"}) + */ + public function getWarningDate(): ?DateTimeImmutable + { + if (null === $this->getEndDate() || null === $this->getWarningInterval()) { + return null; + } + + return $this->getEndDate()->sub($this->getWarningInterval()); + } + public function getWarningInterval(): ?DateInterval { return $this->warningInterval; diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Origin.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Origin.php index 7d35b8684..ec22e806e 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Origin.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Origin.php @@ -33,7 +33,7 @@ class Origin * @ORM\Column(type="integer") * @Groups({"read"}) */ - private $id; + private ?int $id = null; /** * @ORM\Column(type="json") @@ -45,7 +45,7 @@ class Origin * @ORM\Column(type="date_immutable", nullable=true) * @Groups({"read"}) */ - private $noActiveAfter; + private ?DateTimeImmutable $noActiveAfter = null; public function getId(): ?int { @@ -62,7 +62,7 @@ class Origin return $this->noActiveAfter; } - public function setLabel(string $label): self + public function setLabel(array $label): self { $this->label = $label; diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php index ea20666fb..a11dc9b3d 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php @@ -41,11 +41,10 @@ class Resource * ) * @ORM\JoinColumn(nullable=false) */ - private $accompanyingPeriod; + private ?AccompanyingPeriod $accompanyingPeriod = null; /** * @ORM\ManyToOne(targetEntity=Comment::class) - * @ORM\JoinColumn(nullable=true) */ private $comment; @@ -53,21 +52,23 @@ class Resource * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) */ - private $id; + private ?int $id = null; /** * @ORM\ManyToOne(targetEntity=Person::class) * @ORM\JoinColumn(nullable=true) + * @Groups({"docgen:read"}) */ - private $person; + private ?Person $person = null; /** * @ORM\ManyToOne(targetEntity=ThirdParty::class) * @ORM\JoinColumn(nullable=true) + * @Groups({"docgen:read"}) */ - private $thirdParty; + private ?ThirdParty $thirdParty = null; public function getAccompanyingPeriod(): ?AccompanyingPeriod { diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php index 3478b2631..055da93e9 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php @@ -11,7 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Entity; -use DateTimeImmutable; +use DateTime; use DateTimeInterface; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; @@ -32,38 +32,38 @@ class AccompanyingPeriodParticipation * @ORM\ManyToOne(targetEntity=AccompanyingPeriod::class, inversedBy="participations", cascade={"persist"}) * @ORM\JoinColumn(name="accompanyingperiod_id", referencedColumnName="id", nullable=false) */ - private $accompanyingPeriod; + private ?AccompanyingPeriod $accompanyingPeriod = null; /** * @ORM\Column(type="date", nullable=true) - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) */ - private $endDate; + private ?DateTime $endDate = null; /** * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) */ - private $id; + private ?int $id = null; /** * @ORM\ManyToOne(targetEntity=Person::class, inversedBy="accompanyingPeriodParticipations") * @ORM\JoinColumn(name="person_id", referencedColumnName="id", nullable=false) - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) */ - private $person; + private ?Person $person = null; /** * @ORM\Column(type="date", nullable=false) - * @Groups({"read"}) + * @Groups({"read", "docgen:read"}) */ - private $startDate; + private ?DateTime $startDate = null; public function __construct(AccompanyingPeriod $accompanyingPeriod, Person $person) { - $this->startDate = new DateTimeImmutable('now'); + $this->startDate = new DateTime('now'); $this->accompanyingPeriod = $accompanyingPeriod; $this->person = $person; } @@ -73,10 +73,6 @@ class AccompanyingPeriodParticipation return $this->accompanyingPeriod; } - /* - * public function setStartDate(\DateTimeInterface $startDate): self { $this->startDate = $startDate; return $this; } - */ - public function getEndDate(): ?DateTimeInterface { return $this->endDate; diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php index c03644214..95aa83872 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php @@ -59,7 +59,7 @@ class Household * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private ?int $id = null; @@ -68,17 +68,19 @@ class Household * targetEntity=HouseholdMember::class, * mappedBy="household" * ) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private Collection $members; /** * @ORM\Column(type="boolean", name="waiting_for_birth", options={"default": false}) + * @Serializer\Groups({"docgen:read"}) */ private bool $waitingForBirth = false; /** * @ORM\Column(type="date_immutable", name="waiting_for_birth_date", nullable=true, options={"default": null}) + * @Serializer\Groups({"docgen:read"}) */ private ?DateTimeImmutable $waitingForBirthDate = null; @@ -134,7 +136,7 @@ class Household } /** - * @Serializer\Groups({ "read" }) + * @Serializer\Groups({"read", "docgen:read"}) * @Serializer\SerializedName("current_address") */ public function getCurrentAddress(?DateTime $at = null): ?Address @@ -154,6 +156,9 @@ class Household return null; } + /** + * @Serializer\Groups({"docgen:read"}) + */ public function getCurrentMembers(?DateTimeImmutable $now = null): Collection { return $this->getMembers()->matching($this->buildCriteriaCurrentMembers($now)); diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMember.php b/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMember.php index af3ead806..829bc3ade 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMember.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMember.php @@ -28,13 +28,13 @@ class HouseholdMember { /** * @ORM\Column(type="string", length=255, nullable=true) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private ?string $comment = null; /** * @ORM\Column(type="date_immutable", nullable=true, options={"default": null}) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) * @Assert\GreaterThan( * propertyPath="startDate", * message="household_membership.The end date must be after start date", @@ -45,7 +45,7 @@ class HouseholdMember /** * @ORM\Column(type="boolean", options={"default": false}) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private bool $holder = false; @@ -63,7 +63,7 @@ class HouseholdMember * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private $id; @@ -72,7 +72,7 @@ class HouseholdMember * @ORM\ManyToOne( * targetEntity="\Chill\PersonBundle\Entity\Person" * ) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) * @Assert\Valid(groups={"household_memberships"}) * @Assert\NotNull(groups={"household_memberships"}) */ @@ -80,7 +80,7 @@ class HouseholdMember /** * @ORM\ManyToOne(targetEntity=Position::class) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) * @Assert\NotNull(groups={"household_memberships_created"}) */ private ?Position $position = null; @@ -92,7 +92,7 @@ class HouseholdMember /** * @ORM\Column(type="date_immutable", nullable=true, options={"default": null}) - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) * @Assert\NotNull(groups={"household_memberships"}) */ private ?DateTimeImmutable $startDate = null; diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Position.php b/src/Bundle/ChillPersonBundle/Entity/Household/Position.php index 721c35433..b8f707126 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/Position.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/Position.php @@ -33,25 +33,25 @@ class Position * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") - * @Serializer\Groups({ "read" }) + * @Serializer\Groups({"read", "docgen:read"}) */ private ?int $id; /** * @ORM\Column(type="json") - * @Serializer\Groups({ "read" }) + * @Serializer\Groups({"read", "docgen:read"}) */ private array $label = []; /** * @ORM\Column(type="float") - * @Serializer\Groups({ "read" }) + * @Serializer\Groups({"read"}) */ private float $ordering = 0.00; /** * @ORM\Column(type="boolean") - * @Serializer\Groups({ "read" }) + * @Serializer\Groups({"read"}) */ private bool $shareHouseHold = true; diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php index 6b1d81700..4c21cd348 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php @@ -28,21 +28,21 @@ class Evaluation * @ORM\Column(type="dateinterval", nullable=true, options={"default": null}) * @Serializer\Groups({"read"}) */ - private $delay; + private ?DateInterval $delay = null; /** * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ - private $id; + private ?int $id = null; /** * @ORM\Column(type="dateinterval", nullable=true, options={"default": null}) * @Serializer\Groups({"read"}) */ - private $notificationDelay; + private ?DateInterval $notificationDelay = null; /** * @ORM\ManyToOne( @@ -50,13 +50,13 @@ class Evaluation * inversedBy="evaluations" * ) */ - private $socialAction; + private ?SocialAction $socialAction = null; /** * @ORM\Column(type="json") - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ - private $title = []; + private array $title = []; public function getDelay(): ?DateInterval { diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Goal.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Goal.php index 9c02a129d..f3d8f5289 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Goal.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Goal.php @@ -38,7 +38,7 @@ class Goal * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private $id; @@ -55,7 +55,7 @@ class Goal /** * @ORM\Column(type="json") - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private $title = []; diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Result.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Result.php index b2894d210..17d4ddd0a 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Result.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Result.php @@ -55,7 +55,7 @@ class Result * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private $id; @@ -66,7 +66,7 @@ class Result /** * @ORM\Column(type="json") - * @Serializer\Groups({"read"}) + * @Serializer\Groups({"read", "docgen:read"}) */ private $title = []; diff --git a/src/Bundle/ChillPersonBundle/Form/PersonType.php b/src/Bundle/ChillPersonBundle/Form/PersonType.php index eddf8c751..de9b3bbb1 100644 --- a/src/Bundle/ChillPersonBundle/Form/PersonType.php +++ b/src/Bundle/ChillPersonBundle/Form/PersonType.php @@ -114,10 +114,10 @@ class PersonType extends AbstractType $builder->get('placeOfBirth')->addModelTransformer(new CallbackTransformer( static function ($string) { - return strtoupper($string); + return strtoupper((string) $string); }, static function ($string) { - return strtoupper($string); + return strtoupper((string) $string); } )); } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss index d246b3c92..daaae9e28 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss @@ -1,51 +1,6 @@ /// AccompanyingCourse Work list Page div.accompanying_course_work-list { - div.timeline { - width: 100%; - ul { - display: flex; - align-items: center; - justify-content: center; - - padding: 0; - list-style-type: none; - - > li { - flex-grow: 1; flex-shrink: 1; flex-basis: auto; - - div { - display: flex; - flex-direction: column; - align-items: center; - - &.date { - margin-bottom: 1em; - } - &.label { - border-top: 3px solid $chill-green; - - &:before { - content: ''; - display: inline-block; - position: relative; - width: 15px; - height: 15px; - top: -9px; - - background-color: $white; - border-radius: 12px; - border: 2px solid $chill-green; - } - &.no-label:before { - display: none; - } - } - } - } - } - } - div.objective_results { width: 100%; display: grid; @@ -69,8 +24,10 @@ div.accompanying_course_work-list { //&:nth-child(even) { background-color: $chill-llight-gray; } &.without-objectives {} &.with-objectives {} + } - + div.objective_results, + div.evaluations { h4.title_label { display: block; margin: 0.4em 0; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/badge.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/badge.scss index 6ffe0ac9d..39dbb4e55 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/badge.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/badge.scss @@ -39,69 +39,15 @@ span.fa-holder { } /* -* BADGE_TITLE -* Display Title like a badge (with background-colored label) +* DASHBOARDS */ -h2.badge-title { - display: flex; - flex-direction: row; - width: 100%; - color: $dark; - - span.title_label { - border-radius: 0.35rem 0 0 0.35rem; - color: $white; - font-size: 80%; - padding: 0.5em; - padding-right: 0; - h3 { - margin-bottom: 0.5rem; - } - - //position: relative; - span { - display: none; - //position: absolute; - //top: 0; - //left: 0; - //transform: rotate(270deg); - //transform-origin: 0 0; - } - } - span.title_action { - flex-grow: 1; - margin: 0 0 0 auto; - border-radius: 0 0.35rem 0.35rem 0; - background-color: $chill-llight-gray; - padding: 0.2em 1em; - - ul.small_in_title { - margin: 0; - //margin-top: 0.5em; - font-size: 70%; - padding-left: 1rem; - &.evaluations { - @include list_marker_triangle($orange); - } - } - ul.columns { // XS:1 SM:2 MD:1 LG:2 XL:2 XXL:2 - @include media-breakpoint-only(sm) { - columns: 2; -webkit-columns: 2; -moz-columns: 2; - } - @include media-breakpoint-up(lg) { - columns: 2; -webkit-columns: 2; -moz-columns: 2; - } - } - } -} - -/// Theses links apply on badge as parent tag. +/// Theses links apply on dashboards as parent tag. /// They don't look like button, picto or simple text links -a.badge-link { +a.dashboard-link { color: unset; text-decoration: unset; - & > h2.badge-title { + & > div.dashboard { &:hover { //box-shadow: 0 0 7px 0 $chill-gray; //opacity: 0.8; @@ -114,21 +60,80 @@ a.badge-link { } } -/// badge_title in AccompanyingCourse Work list Page +div.dashboard { + font-weight: 700; + font-size: 1.5rem; + margin-bottom: 0.5rem; + line-height: 1.2; + span.like-h3 { + color: #334d5c; + } +} +div.dashboard, +h2.badge-title { + display: flex; + flex-direction: row; + width: 100%; + color: $dark; + span.title_label { + color: $white; + font-size: 80%; + padding: 0.5em; + padding-right: 0; + border-radius: 0.35rem 0 0 0.35rem; + h3 { + margin-bottom: 0.5rem; + } + } + span.title_action { + flex-grow: 1; + margin: 0 0 0 auto; + background-color: $chill-llight-gray; + padding: 0.2em 1em; + border-radius: 0 0.35rem 0.35rem 0; + + ul.small_in_title { + font-size: 70%; + } + } +} + +ul.small_in_title { + margin: 0; + //margin-top: 0.5em; + padding-left: 1rem; + &.evaluations { + @include list_marker_triangle($orange); + } +} +ul.columns { // XS:1 SM:2 MD:1 LG:2 XL:2 XXL:2 + @include media-breakpoint-only(sm) { + columns: 2; -webkit-columns: 2; -moz-columns: 2; + } + @include media-breakpoint-up(lg) { + columns: 2; -webkit-columns: 2; -moz-columns: 2; + } +} + + + +/// dashboard_like_badge in AccompanyingCourse Work list Page div.accompanying_course_work-list { + div.dashboard, h2.badge-title { span.title_label { // Calculate same color then border:groove background-color: shade-color($social-action-color, 34%); } span.title_action { - @include badge_title($social-action-color); + @include dashboard_like_badge($social-action-color); } } } -/// badge_title in Activities on resume page +/// dashboard_like_badge in Activities on resume page div.activity-list { + div.dashboard, h2.badge-title { span.title_label { // Calculate same color then border:groove @@ -138,7 +143,7 @@ div.activity-list { } } span.title_action { - @include badge_title($activity-color); + @include dashboard_like_badge($activity-color); } span.title_label { div.duration { diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/flex_table.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/flex_table.scss index 0229f53b1..2383e43b1 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/flex_table.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/flex_table.scss @@ -18,12 +18,6 @@ div.accompanyingcourse-list { //&:nth-child(2) { flex-direction: row; } //&:last-child { flex-direction: column; } } - div.title h3 { - font-weight: 700; - font-size: 100%; - font-family: 'Open Sans'; - } - div.list {} } /// Search Page (list_with_period.html.twig) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/mixins.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/mixins.scss index 8a3d21ece..228f10178 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/mixins.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/mixins.scss @@ -27,11 +27,10 @@ } /// -/// Generic mixin for titles like badge -// define visual badge used in title area +/// Mixin for dashboards (with design like badge_social) /// -@mixin badge_title($color) { +@mixin dashboard_like_badge($color) { @include chill_badge($color); &:before { margin: 0 0.3em 0 -1.05em; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue index 2a3489fce..0bdb91ab5 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue @@ -195,6 +195,18 @@
+
+ + + +
+

{{ $t('fix_these_errors') }}

    @@ -230,6 +242,7 @@ import AddEvaluation from './components/AddEvaluation.vue'; import PersonRenderBox from 'ChillPersonAssets/vuejs/_components/Entity/PersonRenderBox.vue'; import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue'; import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue'; +import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue'; const i18n = { messages: { @@ -276,6 +289,7 @@ export default { AddPersons, PersonRenderBox, AddressRenderBox, + PickTemplate, }, i18n, data() { @@ -318,6 +332,7 @@ export default { 'thirdParties', 'isPosting', 'errors', + 'templatesAvailablesForAction', ]), ...mapGetters([ 'hasResultsForAction', @@ -386,7 +401,7 @@ export default { this.$store.commit('removeGoal', g); }, addEvaluation(e) { - this.$store.commit('addEvaluation', e); + this.$store.dispatch('addEvaluation', e); }, toggleAddEvaluation() { this.showAddEvaluation = !this.showAddEvaluation; @@ -414,6 +429,10 @@ export default { submit() { this.$store.dispatch('submit'); }, + beforeGenerateTemplate() { + console.log('before generate'); + return Promise.resolve(); + } } }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue index 33436f222..7ea4dfc16 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue @@ -61,21 +61,18 @@
-
- -
-
- - - -
-
-
+
+ + + +
@@ -85,6 +82,7 @@ import {dateToISO, ISOToDate, ISOToDatetime} from 'ChillMainAssets/chill/js/date import CKEditor from '@ckeditor/ckeditor5-vue'; import ClassicEditor from 'ChillMainAssets/module/ckeditor5/index.js'; import { mapGetters, mapState } from 'vuex'; +import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue'; const i18n = { messages: { @@ -111,6 +109,7 @@ export default { props: ['evaluation'], components: { ckeditor: CKEditor.component, + PickTemplate, }, i18n, data() { @@ -120,12 +119,12 @@ export default { } }, computed: { - ...mapGetters([ - 'getTemplatesAvailaibleForEvaluation' - ]), ...mapState([ 'isPosting' ]), + getTemplatesAvailables() { + return this.$store.getters.getTemplatesAvailablesForEvaluation(this.evaluation.evaluation); + }, canGenerate() { return !this.$store.state.isPosting && this.template !== null; }, @@ -176,13 +175,14 @@ export default { }) ; }, - generateDocument() { - console.log('template picked', this.template); - this.$store.dispatch('generateDocument', { key: this.evaluation.key, templateId: this.template}) + submitBeforeGenerate() { + const callback = (data) => { + let evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key).id; + return Promise.resolve({entityId: evaluationId}); + }; + + return this.$store.dispatch('submit', callback).catch(e => { console.log(e); throw e; }); } }, - mounted() { - //this.listAllStatus(); - } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js index 22218f04f..92ee414a7 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js @@ -2,6 +2,8 @@ import { createStore } from 'vuex'; import { datetimeToISO, ISOToDatetime, intervalDaysToISO, intervalISOToDays } from 'ChillMainAssets/chill/js/date.js'; import { findSocialActionsBySocialIssue } from 'ChillPersonAssets/vuejs/_api/SocialWorkSocialAction.js'; import { create } from 'ChillPersonAssets/vuejs/_api/AccompanyingCourseWork.js'; +import { fetchResults, makeFetch } from 'ChillMainAssets/lib/api/apiMethods.js'; +import { fetchTemplates } from 'ChillDocGeneratorAssets/api/pickTemplate.js'; const debug = process.env.NODE_ENV !== 'production'; const evalFQDN = encodeURIComponent("Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation"); @@ -20,20 +22,10 @@ const store = createStore({ resultsPicked: window.accompanyingCourseWork.results, resultsForAction: [], resultsForGoal: [], - evaluationsPicked: window.accompanyingCourseWork.accompanyingPeriodWorkEvaluations.map((e, index) => { - var k = Object.assign(e, { - key: index, - editEvaluation: false, - startDate: e.startDate !== null ? ISOToDatetime(e.startDate.datetime) : null, - endDate: e.endDate !== null ? ISOToDatetime(e.endDate.datetime) : null, - maxDate: e.maxDate !== null ? ISOToDatetime(e.maxDate.datetime) : null, - warningInterval: e.warningInterval !== null ? intervalISOToDays(e.warningInterval) : null, - }); - - return k; - }), + evaluationsPicked: [], evaluationsForAction: [], - templatesAvailableForEvaluation: [], + templatesAvailablesForAction: [], + templatesAvailablesForEvaluation: new Map([]), personsPicked: window.accompanyingCourseWork.persons, personsReachables: window.accompanyingCourseWork.accompanyingPeriod.participations.filter(p => p.endDate == null) .map(p => p.person), @@ -65,8 +57,8 @@ const store = createStore({ hasThirdParties(state) { return state.thirdParties.length > 0; }, - getTemplatesAvailaibleForEvaluation(state) { - return state.templatesAvailableForEvaluation; + getTemplatesAvailablesForEvaluation: (state) => (evaluation) => { + return state.templatesAvailablesForEvaluation.get(evaluation.id) || []; }, buildPayload(state) { return { @@ -125,6 +117,20 @@ const store = createStore({ } }, mutations: { + setEvaluationsPicked(state, evaluations) { + state.evaluationsPicked = evaluations.map((e, index) => { + var k = Object.assign(e, { + key: index, + editEvaluation: false, + startDate: e.startDate !== null ? ISOToDatetime(e.startDate.datetime) : null, + endDate: e.endDate !== null ? ISOToDatetime(e.endDate.datetime) : null, + maxDate: e.maxDate !== null ? ISOToDatetime(e.maxDate.datetime) : null, + warningInterval: e.warningInterval !== null ? intervalISOToDays(e.warningInterval) : null, + }); + + return k; + }); + }, setStartDate(state, date) { state.startDate = date; }, @@ -222,10 +228,11 @@ const store = createStore({ let evaluation = state.evaluationsPicked.find(e => e.key === key); evaluation.editEvaluation = !evaluation.editEvaluation; }, - setTemplatesAvailableForEvaluation(state, templates) { - for (let i in templates) { - state.templatesAvailableForEvaluation.push(templates[i]); - } + setTemplatesForEvaluation(state, {templates, evaluation}) { + state.templatesAvailablesForEvaluation.set(evaluation.id, templates); + }, + setTemplatesAvailablesForAction(state, templates) { + state.templatesAvailablesForAction = templates; }, setPersonsPickedIds(state, ids) { state.personsPicked = state.personsReachables @@ -328,36 +335,19 @@ const store = createStore({ commit('setEvaluationsForAction', data.results); }); }, - getReachableTemplatesForEvaluation({commit}) { - const - url = `/fr/doc/gen/templates/for/${evalFQDN}` - ; - window.fetch(url).then(r => { - if (r.ok) { - return r.json(); - } - throw new Error("not possible to load templates for evaluations") - }).then(data => { - commit('setTemplatesAvailableForEvaluation', data.results); - }).catch(e => { - console.error(e); - }) + addEvaluation({commit, dispatch}, evaluation) { + commit('addEvaluation', evaluation); + dispatch('fetchTemplatesAvailablesForEvaluation', evaluation); }, - generateDocument({ dispatch }, {key, templateId}) { - const callback = function(data) { - // get the evaluation id from the data - const - evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === key).id, - returnPath = encodeURIComponent(window.location.pathname + window.location.search + window.location.hash), - url = `/fr/doc/gen/generate/from/${templateId}/for/${evalFQDN}/${evaluationId}?returnPath=${returnPath}` - ; - //http://localhost:8001/fr/doc/gen/generate/from/12/for/Chill%5CPersonBundle%5CEntity%5CAccompanyingPeriod%5CAccompanyingPeriodWorkEvaluation/41 - - console.log('I will generate your doc at', url); - window.location.assign(url); - }; - - dispatch('submit', callback); + fetchTemplatesAvailablesForEvaluation({commit, state}, evaluation) { + if (!state.templatesAvailablesForEvaluation.has(evaluation.id)) { + // commit an empty array to avoid parallel fetching for same evaluation id + commit('setTemplatesForEvaluation', {templates: [], evaluation}); + fetchResults(`/api/1.0/person/docgen/template/by-evaluation/${evaluation.id}.json`) + .then(templates => { + commit('setTemplatesForEvaluation', {templates, evaluation}); + }); + } }, submit({ getters, state, commit }, callback) { let @@ -368,47 +358,36 @@ const store = createStore({ commit('setIsPosting', true); - window.fetch(url, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(payload) - }).then(response => { - if (response.ok || response.status === 422) { - return response.json().then(data => ({data, status: response.status})); - } - - throw new Error(response.status); - }).then(({data, status}) => { - if (status === 422) { - for (let i in data.violations) { - errors.push(data.violations[i].title); + return makeFetch('PUT', url, payload) + .then(data => { + console.log('data received', data); + if (typeof(callback) !== 'undefined') { + return callback(data); + } else { + console.info('nothing to do here, bye bye');window.location.assign(`/fr/person/accompanying-period/${state.work.accompanyingPeriod.id}/work`); } - commit('setErrors', errors); + }).catch(error => { + console.log('error on submit', error); commit('setIsPosting', false); - } else if (typeof(callback) !== 'undefined') { - callback(data); - } else { - console.info('nothing to do here, bye bye'); - window.location.assign(`/fr/person/accompanying-period/${state.work.accompanyingPeriod.id}/work`); - } - }).catch(e => { - commit('setErrors', [ - 'Erreur serveur ou réseau: veuillez ré-essayer. Code erreur: ' + e - ]); - commit('setIsPosting', false); - }); - }, - initAsync({ dispatch }) { - dispatch('getReachablesResultsForAction'); - dispatch('getReachablesGoalsForAction'); - dispatch('getReachablesEvaluationsForAction'); - dispatch('getReachableTemplatesForEvaluation'); + commit('setErrors', error.violations); + }); }, } }); -store.dispatch('initAsync'); +store.commit('setEvaluationsPicked', window.accompanyingCourseWork.accompanyingPeriodWorkEvaluations); +store.dispatch('getReachablesResultsForAction'); +store.dispatch('getReachablesGoalsForAction'); +store.dispatch('getReachablesEvaluationsForAction'); + +store.state.evaluationsPicked.forEach(evaluation => { + store.dispatch('fetchTemplatesAvailablesForEvaluation', evaluation.evaluation) +}); + +fetchTemplates('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork') + .then(templates => { + store.commit('setTemplatesAvailablesForAction', templates); + } +) export { store }; diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig index 96e2cb534..cdd0363b4 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig @@ -103,7 +103,7 @@
-

{{ 'Last social actions'|trans }}

+

{{ 'Last social actions'|trans }}

{% include 'ChillPersonBundle:AccompanyingCourseWork:list_recent_by_accompanying_period.html.twig' with {'buttonText': false } %}
@@ -121,7 +121,7 @@ {% set accompanying_course_id = accompanyingCourse.id %} {% endif %} -

{{ 'Last activities' |trans }}

+

{{ 'Last activities' |trans }}

{% include 'ChillActivityBundle:Activity:list_recent.html.twig' with { 'context': 'accompanyingCourse', 'no_action': true } %} {% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/delete.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/delete.html.twig index c66e5aa3d..37a6812bb 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/delete.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/delete.html.twig @@ -8,7 +8,7 @@

- {{ 'accompanying_course_work.action'|trans }} + {{ work.socialAction|chill_entity_render_string }}

diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/index.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/index.html.twig index 92b1919af..844b2d611 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/index.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/index.html.twig @@ -7,7 +7,217 @@

{{ block('title') }}

- {% include 'ChillPersonBundle:AccompanyingCourseWork:list_by_accompanying_period.html.twig' %} + {% if works|length == 0 %} +

{{ 'accompanying_course_work.Any work'|trans }} + +

+ + {% else %} +
+ {% for w in works %} +
+ +
+

+ + {{ w.socialAction|chill_entity_render_string }} + +
    +
  • + {{ 'accompanying_course_work.start_date'|trans ~ ' : ' }} + {{ w.startDate|format_date('short') }} +
  • + {% if w.endDate %} +
  • + {{ 'accompanying_course_work.end_date'|trans ~ ' : ' }} + {{ w.endDate|format_date('short') }} +
  • + {% endif %} +
+ +
+

+
+ +
+
+ + {% if w.createdBy %} +
+
+

{{ 'Referrer'|trans }}

+
+
+

+ {{ w.createdBy.usernameCanonical|chill_entity_render_string|capitalize }} +

+
+
+ {% endif %} + + {%- if w.persons -%} +
+
+

{{ 'Persons in accompanying course'|trans }}

+
+
+ {% for p in w.persons %} + + {{ p|chill_entity_render_box({ + 'render': 'raw', + 'addAltNames': false + }) }} + + {% endfor %} +
+
+ {% endif %} + + {%- if w.handlingThierParty -%} +
+
+

{{ 'Thirdparty handling'|trans }}

+
+
+ + {{ w.handlingThierParty|chill_entity_render_box({ + 'render': 'raw', + 'addAltNames': false + }) }} + +
+
+ {% endif %} + + {%- if w.socialAction.issue -%} +
+
+

{{ 'Social issue'|trans }}

+
+
+ +
+
+ {% endif %} + + {% if w.accompanyingPeriodWorkEvaluations|length > 0 %} +
+
+

{{ 'accompanying_course_work.evaluations'|trans }}

+
+
+
    + {% for e in w.accompanyingPeriodWorkEvaluations %} +
  • + {{ e.evaluation.title|localize_translatable_string }} + +
      +
    • + {{ 'accompanying_course_work.start_date'|trans ~ ' : ' }} + {{ e.startDate|format_date('short') }} +
    • + {% if e.endDate %} +
    • + {{ 'accompanying_course_work.end_date'|trans ~ ' : ' }} + {{ e.endDate|format_date('short') }} +
    • + {% endif %} + {% if e.maxDate %} +
    • + {{ 'accompanying_course_work.max_date'|trans ~ ' : ' }} + {{ e.maxDate|format_date('short') }} +
    • + {% endif %} + {% if e.warningInterval and e.warningInterval.d > 0 %} +
    • + {% set days = (e.warningInterval.d + e.warningInterval.m * 30) %} + {{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }} + {{ 'accompanying_course_work.%days% days before max_date'|trans({'%days%': days }) }} +
    • + {% endif %} +
    + +
  • + {% endfor %} +
+
+
+ {% endif %} + +
+
+ +
+ {# SEULEMENT SI DÉTAILLÉ + {% if w.results|length > 0 %} +
+
+

{{ 'accompanying_course_work.goal'|trans }}

+

{{ 'accompanying_course_work.results without objective'|trans }}

+
+
+

{{ 'accompanying_course_work.results'|trans }}

+
    + {% for r in w.results %} +
  • {{ r.title|localize_translatable_string }}
  • + {% endfor %} +
+
+
+ {% endif %} + {% if w.goals|length > 0 %} + {% for g in w.goals %} +
+
+

{{ 'accompanying_course_work.goal'|trans }}

+
  • {{ g.goal.title|localize_translatable_string }}
+
+
+

{{ 'accompanying_course_work.results'|trans }}

+ {% if g.results|length == 0 %} +

{{ 'accompanying_course_work.no_results'|trans }}

+ {% else %} +
    + {% for r in g.results %} +
  • {{ r.title|localize_translatable_string }}
  • + {% endfor %} +
+ {% endif %} +
+
+ {% endfor %} + {% endif %} + #} +
+ +
+
+ {{ 'Last updated by'|trans}} {{ w.updatedBy|chill_entity_render_box }},
+ {{ 'le ' ~ w.updatedAt|format_datetime('long', 'short') }} +
+
    +
  • + +
  • +
  • + +
  • +
+
+ +
+ {% endfor %} +
+ {% endif %}
{# {{ dump(w) }} #} {% endfor %} diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodDocGenNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodDocGenNormalizer.php new file mode 100644 index 000000000..473f6d75d --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodDocGenNormalizer.php @@ -0,0 +1,160 @@ + '', + 'closingDate' => DateTime::class, + 'confidential' => '', + 'confidentialText' => '', + 'createdAt' => DateTime::class, + 'createdBy' => User::class, + 'emergency' => '', + 'emergencyText' => '', + 'openingDate' => DateTime::class, + 'originText' => '', + 'requestorAnonymous' => false, + 'socialIssues' => [], + 'intensity' => '', + 'step' => '', + 'closingMotiveText' => '', + 'socialIssuesText' => '', + 'scopes' => [], + 'scopesText' => '', + 'ref' => User::class, + 'participations' => [], + ]; + + private ClosingMotiveRender $closingMotiveRender; + + private ScopeResolverDispatcher $scopeResolverDispatcher; + + private SocialIssueRender $socialIssueRender; + + private TranslatableStringHelper $translatableStringHelper; + + private TranslatorInterface $translator; + + public function __construct( + TranslatorInterface $translator, + TranslatableStringHelper $translatableStringHelper, + SocialIssueRender $socialIssueRender, + ClosingMotiveRender $closingMotiveRender, + ScopeResolverDispatcher $scopeResolverDispatcher + ) { + $this->translator = $translator; + $this->translatableStringHelper = $translatableStringHelper; + $this->socialIssueRender = $socialIssueRender; + $this->closingMotiveRender = $closingMotiveRender; + $this->scopeResolverDispatcher = $scopeResolverDispatcher; + } + + /** + * @param AccompanyingPeriod|null $period + */ + public function normalize($period, ?string $format = null, array $context = []) + { + if ($period instanceof AccompanyingPeriod) { + $ignored = $context[self::IGNORE_FIRST_PASS_KEY] ?? []; + $ignored[] = spl_object_hash($period); + $initial = + $this->normalizer->normalize($period, $format, array_merge( + $context, + [self::IGNORE_FIRST_PASS_KEY => $ignored, AbstractNormalizer::GROUPS => 'docgen:read'] + )); + + // some transformation + $user = $initial['user']; + unset($initial['user']); + + $scopes = $this->scopeResolverDispatcher->isConcerned($period) ? $this->scopeResolverDispatcher->resolveScope($period) : []; + + if (!is_array($scopes)) { + $scopes = [$scopes]; + } + + return array_merge( + // get a first default data + $initial, + // and add data custom + [ + 'intensity' => $this->translator->trans($period->getIntensity()), + 'step' => $this->translator->trans('accompanying_period.' . $period->getStep()), + 'emergencyText' => $period->isEmergency() ? $this->translator->trans('accompanying_period.emergency') : '', + 'confidentialText' => $period->isConfidential() ? $this->translator->trans('confidential') : '', + //'originText' => null !== $period->getOrigin() ? $this->translatableStringHelper->localize($period->getOrigin()->getLabel()) : '', + 'closingMotiveText' => null !== $period->getClosingMotive() ? + $this->closingMotiveRender->renderString($period->getClosingMotive(), []) : '', + 'ref' => $user, + 'socialIssuesText' => implode(', ', array_map(function (SocialIssue $s) { + return $this->socialIssueRender->renderString($s, []); + }, $period->getSocialIssues()->toArray())), + 'scopesText' => implode(', ', array_map(function (Scope $s) { + return $this->translatableStringHelper->localize($s->getName()); + }, $scopes)), + 'scopes' => $scopes, + ] + ); + } elseif (null === $period) { + return self::PERIOD_NULL; + } + + throw new InvalidArgumentException('this neither an accompanying period or null'); + } + + public function supportsNormalization($data, ?string $format = null, array $context = []): bool + { + if ('docgen' !== $format) { + return false; + } + + if ($data instanceof AccompanyingPeriod) { + if (array_key_exists(self::IGNORE_FIRST_PASS_KEY, $context) + && in_array(spl_object_hash($data), $context[self::IGNORE_FIRST_PASS_KEY], true)) { + return false; + } + + return true; + } + + if (null === $data && AccompanyingPeriod::class === ($context['docgen:expects'] ?? null)) { + return true; + } + + return false; + } +} diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialActionNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialActionNormalizer.php index 94341197b..5fa12f741 100644 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialActionNormalizer.php +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialActionNormalizer.php @@ -30,18 +30,49 @@ class SocialActionNormalizer implements NormalizerAwareInterface, NormalizerInte public function normalize($socialAction, ?string $format = null, array $context = []) { - return [ - 'id' => $socialAction->getId(), - 'type' => 'social_work_social_action', - 'text' => $this->render->renderString($socialAction, []), - 'parent' => $this->normalizer->normalize($socialAction->getParent()), - 'desactivationDate' => $this->normalizer->normalize($socialAction->getDesactivationDate()), - 'title' => $socialAction->getTitle(), - ]; + switch ($format) { + case 'json': + return [ + 'id' => $socialAction->getId(), + 'type' => 'social_work_social_action', + 'text' => $this->render->renderString($socialAction, []), + 'parent' => $this->normalizer->normalize($socialAction->getParent()), + 'desactivationDate' => $this->normalizer->normalize($socialAction->getDesactivationDate()), + 'title' => $socialAction->getTitle(), + ]; + + case 'docgen': + if (null === $socialAction) { + return ['id' => 0, 'title' => '', 'text' => '']; + } + + return [ + 'id' => $socialAction->getId(), + 'text' => $this->render->renderString($socialAction, []), + 'title' => $socialAction->getTitle(), + ]; + + default: + throw new \Symfony\Component\Serializer\Exception\RuntimeException('format not supported'); + } } - public function supportsNormalization($data, ?string $format = null) + public function supportsNormalization($data, ?string $format = null, array $context = []) { - return $data instanceof SocialAction; + if ($data instanceof SocialAction && 'json' === $format) { + return true; + } + + if ('docgen' === $format) { + if ($data instanceof SocialAction) { + return true; + } + + if (null === $data && SocialAction::class === ($context['docgen:expects'] ?? null)) { + return true; + } + } + + return false; } } diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php index f255e6f8c..5be7df4bc 100644 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialIssueNormalizer.php @@ -13,11 +13,11 @@ namespace Chill\PersonBundle\Serializer\Normalizer; use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Chill\PersonBundle\Templating\Entity\SocialIssueRender; +use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -class SocialIssueNormalizer implements NormalizerAwareInterface, NormalizerInterface +class SocialIssueNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface { use NormalizerAwareTrait; @@ -31,18 +31,46 @@ class SocialIssueNormalizer implements NormalizerAwareInterface, NormalizerInter public function normalize($socialIssue, ?string $format = null, array $context = []) { /** @var SocialIssue $socialIssue */ - return [ - 'type' => 'social_issue', - 'id' => $socialIssue->getId(), - 'parent_id' => $socialIssue->hasParent() ? $socialIssue->getParent()->getId() : null, - 'children_ids' => $socialIssue->getChildren()->map(static function (SocialIssue $si) { return $si->getId(); }), - 'title' => $socialIssue->getTitle(), - 'text' => $this->render->renderString($socialIssue, []), - ]; + switch ($format) { + case 'json': + return [ + 'type' => 'social_issue', + 'id' => $socialIssue->getId(), + 'parent_id' => $socialIssue->hasParent() ? $socialIssue->getParent()->getId() : null, + 'children_ids' => $socialIssue->getChildren()->map(static function (SocialIssue $si) { return $si->getId(); }), + 'title' => $socialIssue->getTitle(), + 'text' => $this->render->renderString($socialIssue, []), + ]; + + case 'docgen': + if (null === $socialIssue) { + return ['id' => 0, 'title' => '', 'text' => '']; + } + + return [ + 'id' => $socialIssue->getId(), + 'title' => $socialIssue->getTitle(), + 'text' => $this->render->renderString($socialIssue, []), + ]; + } } - public function supportsNormalization($data, ?string $format = null): bool + public function supportsNormalization($data, ?string $format = null, array $context = []) { - return $data instanceof SocialIssue; + if ($data instanceof SocialIssue && 'json' === $format) { + return true; + } + + if ('docgen' === $format) { + if ($data instanceof SocialIssue) { + return true; + } + + if (null === $data && SocialIssue::class === ($context['docgen:expects'] ?? null)) { + return true; + } + } + + return false; } } diff --git a/src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodContext.php b/src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodContext.php new file mode 100644 index 000000000..10431d7aa --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodContext.php @@ -0,0 +1,245 @@ +documentCategoryRepository = $documentCategoryRepository; + $this->normalizer = $normalizer; + $this->translatableStringHelper = $translatableStringHelper; + $this->em = $em; + $this->personRender = $personRender; + $this->translator = $translator; + } + + public function adminFormReverseTransform(array $data): array + { + if (array_key_exists('category', $data)) { + $data['category'] = [ + 'idInsideBundle' => $data['category']->getIdInsideBundle(), + 'bundleId' => $data['category']->getBundleId(), + ]; + } + + return $data; + } + + public function adminFormTransform(array $data): array + { + $data = [ + 'mainPerson' => $data['mainPerson'] ?? false, + 'mainPersonLabel' => $data['mainPersonLabel'] ?? $this->translator->trans('docgen.Main person'), + 'person1' => $data['person1'] ?? false, + 'person1Label' => $data['person1Label'] ?? $this->translator->trans('docgen.person 1'), + 'person2' => $data['person2'] ?? false, + 'person2Label' => $data['person2Label'] ?? $this->translator->trans('docgen.person 2'), + ]; + + if (array_key_exists('category', $data)) { + $data['category'] = array_key_exists('category', $data) ? + $this->documentCategoryRepository->find($data['category']) : null; + } + + return $data; + } + + public function buildAdminForm(FormBuilderInterface $builder): void + { + $builder + ->add('mainPerson', CheckboxType::class, [ + 'required' => false, + 'label' => 'docgen.Ask for main person', + ]) + ->add('mainPersonLabel', TextType::class, [ + 'label' => 'main person label', + 'required' => true, + ]) + ->add('person1', CheckboxType::class, [ + 'required' => false, + 'label' => 'docgen.Ask for person 1', + ]) + ->add('person1Label', TextType::class, [ + 'label' => 'person 1 label', + 'required' => true, + ]) + ->add('person2', CheckboxType::class, [ + 'required' => false, + 'label' => 'docgen.Ask for person 2', + ]) + ->add('person2Label', TextType::class, [ + 'label' => 'person 2 label', + 'required' => true, + ]) + ->add('category', EntityType::class, [ + 'placeholder' => 'Choose a document category', + 'class' => 'ChillDocStoreBundle:DocumentCategory', + 'query_builder' => static function (EntityRepository $er) { + return $er->createQueryBuilder('c') + ->where('c.documentClass = :docClass') + ->setParameter('docClass', AccompanyingCourseDocument::class); + }, + 'choice_label' => function ($entity = null) { + return $entity ? $this->translatableStringHelper->localize($entity->getName()) : ''; + }, + ]); + } + + /** + * @param AccompanyingPeriod $entity + */ + public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void + { + $options = $template->getOptions(); + $persons = $entity->getCurrentParticipations()->map(static function (AccompanyingPeriodParticipation $p) { return $p->getPerson(); }) + ->toArray(); + + foreach (['mainPerson', 'person1', 'person2'] as $key) { + if ($options[$key] ?? false) { + $builder->add($key, EntityType::class, [ + 'class' => Person::class, + 'choices' => $persons, + 'choice_label' => function (Person $p) { return $this->personRender->renderString($p, []); }, + 'multiple' => false, + 'expanded' => true, + 'label' => $options[$key . 'Label'], + ]); + } + } + } + + public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array + { + if (!$entity instanceof AccompanyingPeriod) { + throw new UnexpectedTypeException($entity, AccompanyingPeriod::class); + } + $options = $template->getOptions(); + + $data = []; + $data['course'] = $this->normalizer->normalize($entity, 'docgen', ['docgen:expects' => AccompanyingPeriod::class]); + + foreach (['mainPerson', 'person1', 'person2'] as $k) { + if ($options[$k]) { + $data[$k] = $this->normalizer->normalize($contextGenerationData[$k], 'docgen', ['docgen:expects' => Person::class]); + } + } + + return $data; + } + + public function getDescription(): string + { + return 'docgen.A basic context for accompanying period'; + } + + public function getEntityClass(): string + { + return AccompanyingPeriod::class; + } + + public function getFormData(DocGeneratorTemplate $template, $entity): array + { + return [ + 'course' => $entity, + ]; + } + + public static function getKey(): string + { + return self::class; + } + + public function getName(): string + { + return 'Accompanying Period basic'; + } + + public function hasAdminForm(): bool + { + return true; + } + + public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool + { + $options = $template->getOptions(); + + return $options['mainPerson'] || $options['person1'] || $options['person2']; + } + + /** + * @param AccompanyingPeriod $entity + */ + public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void + { + $doc = new AccompanyingCourseDocument(); + $doc->setTitle($this->translatableStringHelper->localize($template->getName())) + ->setDate(new DateTime()) + ->setDescription($this->translatableStringHelper->localize($template->getName())) + ->setCourse($entity) + ->setObject($storedObject); + + if (array_key_exists('category', $template->getOptions()['category'])) { + $doc + ->setCategory( + $this->documentCategoryRepository->find( + $template->getOptions()['category'] + ) + ); + } + + $this->em->persist($doc); + } +} diff --git a/src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodWorkContext.php b/src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodWorkContext.php new file mode 100644 index 000000000..09d154900 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodWorkContext.php @@ -0,0 +1,122 @@ +periodContext = $periodContext; + $this->normalizer = $normalizer; + } + + public function adminFormReverseTransform(array $data): array + { + return $this->periodContext->adminFormReverseTransform($data); + } + + public function adminFormTransform(array $data): array + { + return $this->periodContext->adminFormTransform($data); + } + + public function buildAdminForm(FormBuilderInterface $builder): void + { + $this->periodContext->buildAdminForm($builder); + + $builder->remove('category'); + } + + /** + * @param AccompanyingPeriodWork $entity + */ + public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void + { + $this->periodContext->buildPublicForm($builder, $template, $entity->getAccompanyingPeriod()); + } + + /** + * @param AccompanyingPeriodWork $entity + */ + public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array + { + $data = $this->periodContext->getData($template, $entity->getAccompanyingPeriod(), $contextGenerationData); + $data['work'] = $this->normalizer->normalize($entity, 'docgen', [ + AbstractNormalizer::GROUPS => ['docgen:read'], + 'docgen:expects' => AccompanyingPeriodWork::class, + ]); + + return $data; + } + + public function getDescription(): string + { + return 'docgen.A context for accompanying period work'; + } + + public function getEntityClass(): string + { + return AccompanyingPeriodWork::class; + } + + /** + * @param AccompanyingPeriodWork $entity + */ + public function getFormData(DocGeneratorTemplate $template, $entity): array + { + return $this->periodContext->getFormData($template, $entity->getAccompanyingPeriod()); + } + + public static function getKey(): string + { + return 'accompanying_period_work_regular'; + } + + public function getName(): string + { + return 'Accompanying period work'; + } + + public function hasAdminForm(): bool + { + return $this->periodContext->hasAdminForm(); + } + + public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool + { + return $this->periodContext->hasPublicForm($template, $entity); + } + + public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void + { + // TODO: Implement storeGenerated() method. + } +} diff --git a/src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodWorkEvaluationContext.php b/src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodWorkEvaluationContext.php new file mode 100644 index 000000000..9c8d6172a --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodWorkEvaluationContext.php @@ -0,0 +1,181 @@ +accompanyingPeriodWorkContext = $accompanyingPeriodWorkContext; + $this->em = $em; + $this->evaluationRepository = $evaluationRepository; + $this->normalizer = $normalizer; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function adminFormReverseTransform(array $data): array + { + return array_merge( + $this->accompanyingPeriodWorkContext->adminFormReverseTransform($data), + [ + 'evaluations' => array_map( + static function (Evaluation $e) { return $e->getId(); }, + $data['evaluations'] + ), + ] + ); + } + + public function adminFormTransform(array $data): array + { + return array_merge( + $this->accompanyingPeriodWorkContext->adminFormTransform($data), + [ + 'evaluations' => array_map( + function ($id) { return $this->evaluationRepository->find($id); }, + $data['evaluations'] ?? [] + ), + ] + ); + } + + public function buildAdminForm(FormBuilderInterface $builder): void + { + $this->accompanyingPeriodWorkContext->buildAdminForm($builder); + + $builder->remove('category'); + + $builder->add('evaluations', EntityType::class, [ + 'class' => Evaluation::class, + 'label' => 'Linked evaluations', + 'choices' => $this->evaluationRepository->findAll(), + 'choice_label' => function (Evaluation $e) { + return $this->translatableStringHelper->localize($e->getTitle()); + }, + 'multiple' => true, + 'expanded' => true, + ]); + } + + /** + * @param AccompanyingPeriodWorkEvaluation $entity + */ + public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void + { + $this->accompanyingPeriodWorkContext->buildPublicForm($builder, $template, $entity->getAccompanyingPeriodWork()); + } + + /** + * @param AccompanyingPeriodWorkEvaluation $entity + */ + public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array + { + $data = $this->accompanyingPeriodWorkContext + ->getData($template, $entity->getAccompanyingPeriodWork(), $contextGenerationData); + $data['evaluation'] = $this->normalizer->normalize( + $entity, + 'docgen', + [ + 'docgen:expect' => AccompanyingPeriodWorkEvaluation::class, + AbstractNormalizer::GROUPS => ['docgen:read'], + ] + ); + + return $data; + } + + public function getDescription(): string + { + return 'docgen.A context for accompanying period work evaluation'; + } + + public function getEntityClass(): string + { + return AccompanyingPeriodWorkEvaluation::class; + } + + /** + * @param AccompanyingPeriodWorkEvaluation $entity + */ + public function getFormData(DocGeneratorTemplate $template, $entity): array + { + return $this->accompanyingPeriodWorkContext->getFormData( + $template, + $entity->getAccompanyingPeriodWork() + ); + } + + public static function getKey(): string + { + return 'accompanying_period_work_evaluation_regular'; + } + + public function getName(): string + { + return 'Accompanying period work context'; + } + + public function hasAdminForm(): bool + { + return true; + } + + /** + * @param AccompanyingPeriodWorkEvaluation $entity + */ + public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool + { + return $this->accompanyingPeriodWorkContext + ->hasPublicForm($template, $entity->getAccompanyingPeriodWork()); + } + + public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void + { + $doc = new AccompanyingPeriodWorkEvaluationDocument(); + $doc->setStoredObject($storedObject) + ->setAccompanyingPeriodWorkEvaluation($entity) + ->setTemplate($template); + $this->em->persist($doc); + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/AccompanyingPeriodDocGenNormalizerTest.php b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/AccompanyingPeriodDocGenNormalizerTest.php new file mode 100644 index 000000000..af2dca1f3 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/AccompanyingPeriodDocGenNormalizerTest.php @@ -0,0 +1,137 @@ +normalizer = self::$container->get(NormalizerInterface::class); + } + + public function testNormalize() + { + $period = new AccompanyingPeriod(); + $period->setConfidential(true); + $period->setEmergency(true); + $period->setOrigin((new AccompanyingPeriod\Origin())->setLabel(['fr' => 'origin'])); + $period->setClosingMotive((new AccompanyingPeriod\ClosingMotive())->setName(['closing'])); + $period->addScope((new Scope())->setName(['fr' => 'scope1'])); + $period->addScope((new Scope())->setName(['fr' => 'scope2'])); + $period->addSocialIssue((new SocialIssue())->setTitle(['fr' => 'issue1'])); + $period->addSocialIssue((new SocialIssue())->setTitle(['fr' => 'issue2'])); + $data = $this->normalizer->normalize($period, 'docgen', ['docgen:expects' => AccompanyingPeriod::class]); + + $expected = [ + 'id' => null, + 'closingDate' => '@ignored', + 'confidential' => true, + 'confidentialText' => 'confidentiel', + 'createdAt' => '@ignored', + 'createdBy' => '@ignored', + 'emergency' => true, + 'emergencyText' => 'Urgent', + 'openingDate' => '@ignored', + 'originText' => 'origin', + 'requestorAnonymous' => false, + 'socialIssues' => '@ignored', + 'intensity' => 'ponctuel', + 'step' => 'Brouillon', + 'closingMotiveText' => 'closing', + 'socialIssuesText' => 'issue1, issue2', + 'scopes' => '@ignored', + 'scopesText' => 'scope1, scope2', + 'ref' => '@ignored', + 'participations' => '@ignored', + ]; + + $this->assertIsArray($data); + $this->assertEqualsCanonicalizing(array_keys($expected), array_keys($data)); + + foreach ($expected as $key => $item) { + if ('@ignored' === $item) { + continue; + } + + $this->assertEquals($item, $data[$key]); + } + } + + public function testNormalizeNull() + { + $data = $this->normalizer->normalize(null, 'docgen', ['docgen:expects' => AccompanyingPeriod::class]); + + $expected = [ + 'id' => '', + 'closingDate' => '@ignored', + 'confidential' => '', + 'confidentialText' => '', + 'createdAt' => '@ignored', + 'createdBy' => '@ignored', + 'emergency' => '', + 'emergencyText' => '', + 'openingDate' => '@ignored', + 'originText' => '', + 'requestorAnonymous' => '', + 'socialIssues' => '@ignored', + 'intensity' => '', + 'step' => '', + 'closingMotiveText' => '', + 'socialIssuesText' => '', + 'scopes' => '@ignored', + 'scopesText' => '', + 'ref' => '@ignored', + 'participations' => '@ignored', + ]; + + $this->assertIsArray($data); + $this->assertEqualsCanonicalizing(array_keys($expected), array_keys($data)); + + foreach ($expected as $key => $item) { + if ('@ignored' === $item) { + continue; + } + + $this->assertEquals($item, $data[$key]); + } + } + + public function testNormalizeParticipations() + { + $period = new AccompanyingPeriod(); + $period->addPerson($person = new Person()); + $person->setFirstName('test'); + + $data = $this->normalizer->normalize($period, 'docgen', ['docgen:expects' => AccompanyingPeriod::class]); + + $this->assertIsArray($data); + $this->assertArrayHasKey('participations', $data); + $this->assertCount(1, $data['participations']); + + $this->assertArrayHasKey('currentParticipations', $data); + $this->assertCount(1, $data['currentParticipations']); + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/AccompanyingPeriodWorkDocGenNormalizerTest.php b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/AccompanyingPeriodWorkDocGenNormalizerTest.php new file mode 100644 index 000000000..2417e93da --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/AccompanyingPeriodWorkDocGenNormalizerTest.php @@ -0,0 +1,100 @@ +normalizer = self::$container->get(NormalizerInterface::class); + } + + public function testNormalizationNull() + { + $actual = $this->normalizer->normalize(null, 'docgen', [ + 'docgen:expects' => AccompanyingPeriodWork::class, + AbstractNormalizer::GROUPS => ['docgen:read'], + ]); + + dump($actual); + + $expected = [ + 'id' => '', + ]; + + $this->assertIsArray($actual); + $this->assertEqualsCanonicalizing(array_keys($expected), array_keys($actual)); + + foreach ($expected as $key => $item) { + if ('@ignored' === $item) { + continue; + } + + $this->assertEquals($item, $actual[$key]); + } + } + + public function testNormlalize() + { + $work = new AccompanyingPeriodWork(); + $work + ->addPerson((new Person())->setFirstName('hello')->setLastName('name')) + ->addGoal($g = new AccompanyingPeriodWorkGoal()) + ->addResult($r = new Result()) + ->setCreatedAt(new DateTimeImmutable()) + ->setUpdatedAt(new DateTimeImmutable()) + ->setCreatedBy($user = new User()) + ->setUpdatedBy($user); + $g->addResult($r)->setGoal($goal = new Goal()); + $goal->addResult($r); + + $actual = $this->normalizer->normalize($work, 'docgen', [ + 'docgen:expects' => AccompanyingPeriodWork::class, + AbstractNormalizer::GROUPS => ['docgen:read'], + ]); + + var_dump($actual); + + $expected = [ + 'id' => 0, + ]; + + $this->assertIsArray($actual); + $this->assertEqualsCanonicalizing(array_keys($expected), array_keys($actual)); + + foreach ($expected as $key => $item) { + if ('@ignored' === $item) { + continue; + } + + $this->assertEquals($item, $actual[$key]); + } + } +} diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 46077fdf0..0ca5ce19d 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -198,6 +198,7 @@ Resources: Interlocuteurs privilégiés Any requestor to this accompanying course: Aucun demandeur pour ce parcours Social actions: Actions d'accompagnement Last social actions: Les dernières actions d'accompagnement +Social issue: Problématique sociale Social issues: Problématiques sociales Last events on accompanying course: Dernières actions de suivi Edit & activate accompanying course: Modifier et valider @@ -377,9 +378,14 @@ accompanying_period: dates: Période dates_from_%opening_date%: Ouvert depuis le %opening_date% dates_from_%opening_date%_to_%closing_date%: Ouvert du %opening_date% au %closing_date% + DRAFT: Brouillon + CONFIRMED: Confirmé + CLOSED: Clotûré + emergency: Urgent occasional: ponctuel regular: régulier Confidential: confidentiel +confidential: confidentiel Draft: brouillon Confirmed: en file active Closed: Cloturé @@ -429,10 +435,14 @@ accompanying_course_work: create_date: Date de création start_date: Date de début end_date: Date de fin + max_date: Date d'échéance + warning_interval: Rappel + '%days% days before max_date': "%days% jour(s) avant l'échéance" results without objective: Aucun objectif - motif - dispositif no_results: Aucun résultat - orientation results: Résultats - orientations goal: Objectif - motif - dispositif + evaluations: Évaluations Any work: Aucune action d'accompagnement remove: Supprimer une action d'accompagnement social_evaluation: Évaluation @@ -443,3 +453,15 @@ Household addresses: Adresses de domicile Insert an address: Insérer une adresse see social issues: Voir les problématiques sociales see persons associated: Voir les usagers concernés + +docgen: + Main person: Personne principale + person 1: Première personne + person 2: Seconde personne + Ask for main person: Demander à l'utilisateur de préciser la personne principale + Ask for person 1: Demander à l'utilisateur de préciser la première personne + Ask for person 2: Demander à l'utilisateur de préciser la seconde personne + Accompanying period work: Actions + A basic context for accompanying period: Contexte pour les parcours + A context for accompanying period work: Contexte pour les actions d'accompagnement + A context for accompanying period work evaluation: Contexte pour les évaluations dans les actions d'accompagnement