diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b54facb6..5ea16e7fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,20 @@ and this project adheres to * [Documents] Validate storedObject and allow for null data (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/565) - * [Activity form] invert 'incoming' and 'receiving' in Activity form * [Activity form] keep the same order for 'attendee' field in new and edit form * [list with period] use "sameas" test operator to introduce requestor in list + +## Test releases + +### 2021-04-26 + +* [Datepickers] datepickers fixed when using keyboard to enter date (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/545) +* [social_action] Display 'agents traitants' in parcours resumé and social action list (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/568) +* [Person_search] Closed parcours shown within an accordeon that can be opened/closed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/574) + +### 2021-04-24 + * [notification email on course designation] allow raw string in email content generation * [Accompanying period work] list evaluations associated to a work by startDate, and then by id, from the most recent to older * [Documents] Change wording 'créer' to 'enregistrer' (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/634) @@ -26,11 +36,16 @@ and this project adheres to * [Accompanying period work evaluations] list documents associated to a work by creation date, and then by id, from the most recent to older * [Course comment] add validationConstraint NotNull and NotBlank on comment content, to avoid sql error * [Notifications] delay the sending of notificaiton to kernel.terminate -* [Notifications / Period user change] fix the sending of notification when user changes +* [Notifications / Period user change] fix the sending of notification when user changes +* [Activity form] invert 'incoming' and 'receiving' in Activity form +* [Activity form] keep the same order for 'attendee' field in new and edit form +* [list with period] use "sameas" test operator to introduce requestor in list +* [notification email on course designation] allow raw string in email content generation +* [Accompanying period work] list evaluations associated to a work by startDate, and then by id, from the most recent to older -## Test releases ### 2021-04-13 + * [person] household address: add a form for editing the validFrom date (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/541) * [person] householdmemberseditor: fix composition type bug in select form (vuejs) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/543) * [docgen] add more persons choices in docgen for course: amongst requestor (if person), resources of course (if person), and PersonResource (if person); @@ -101,6 +116,7 @@ and this project adheres to * [household] bugfix if position of member is null, renderbox no longer throws an error (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/480) * [parcours] location cannot be removed if linked to a user (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/478) * [person] email added to twig personRenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/490) +* [activity] Only youngest descendant is kept for social issues and actions (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/471) * [person] Add link to current household in person banner (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/484) * [address] person badge in address history changed to open OnTheFly with all person info (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/489) * [person] Change 'personne' with 'usager' and '&' with 'ET' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/499) @@ -128,6 +144,9 @@ and this project adheres to * [notification] Display of social action within workflow notification set to display block (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/537) * [onthefly] trim trailing whitespace in email of person and thirdparty (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/542) +* [action] Only youngest descendant is kept for social issues and actions (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/471) +## Test releases + ### test release 2022-02-21 * [notifications] Word 'un' changed to number '1' for notifications in user menu (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/483) diff --git a/composer.json b/composer.json index c96738df8..d0fc51c0e 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "knplabs/knp-time-bundle": "^1.12", "league/csv": "^9.7.1", "nyholm/psr7": "^1.4", - "ocramius/package-versions": "^1.10", + "ocramius/package-versions": "^1.10 || ^2", "odolbeau/phone-number-bundle": "^3.6", "phpoffice/phpspreadsheet": "^1.16", "ramsey/uuid-doctrine": "^1.7", @@ -33,6 +33,7 @@ "symfony/expression-language": "^4.4", "symfony/form": "^4.4", "symfony/framework-bundle": "^4.4", + "symfony/http-foundation": "^4.4", "symfony/intl": "^4.4", "symfony/mailer": "^5.4", "symfony/mime": "^5.4", @@ -103,7 +104,8 @@ "ergebnis/composer-normalize": true, "ocramius/package-versions": true, "phpro/grumphp": true, - "phpstan/extension-installer": true + "phpstan/extension-installer": true, + "roave/you-are-using-it-wrong": true }, "bin-dir": "bin", "optimize-autoloader": true, diff --git a/src/Bundle/ChillActivityBundle/Entity/Activity.php b/src/Bundle/ChillActivityBundle/Entity/Activity.php index d740f5fe4..0c0632722 100644 --- a/src/Bundle/ChillActivityBundle/Entity/Activity.php +++ b/src/Bundle/ChillActivityBundle/Entity/Activity.php @@ -233,11 +233,22 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac { if (!$this->socialActions->contains($socialAction)) { $this->socialActions[] = $socialAction; + $this->ensureSocialActionConsistency(); } return $this; } + /** + * Add a social issue. + * + * Note: the social issue consistency (the fact that only yougest social issues + * are kept) is processed by an entity listener: + * + * @see{\Chill\PersonBundle\AccompanyingPeriod\SocialIssueConsistency\AccompanyingPeriodSocialIssueConsistencyEntityListener} + * + * @return $this + */ public function addSocialIssue(SocialIssue $socialIssue): self { if (!$this->socialIssues->contains($socialIssue)) { @@ -633,4 +644,13 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac return $this; } + + private function ensureSocialActionConsistency(): void + { + $ancestors = SocialAction::findAncestorSocialActions($this->getSocialActions()); + + foreach ($ancestors as $ancestor) { + $this->removeSocialAction($ancestor); + } + } } diff --git a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/SocialIssuesAcc.vue b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/SocialIssuesAcc.vue index 51957b010..4691a860a 100644 --- a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/SocialIssuesAcc.vue +++ b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/SocialIssuesAcc.vue @@ -54,19 +54,19 @@ {{ $t('activity.select_first_a_social_issue') }} - + - {{ $t('activity.social_action_list_empty') }} + {{ $t('activity.social_action_list_empty') }} @@ -111,7 +111,7 @@ export default { return this.$store.state.socialIssuesOther; }, socialActionsList() { - return this.$store.state.socialActionsList; + return this.$store.getters.socialActionsListSorted; }, socialActionsSelected() { return this.$store.state.activity.socialActions; diff --git a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.js b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.js index f94381fc2..ca56a5dae 100644 --- a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.js +++ b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.js @@ -101,6 +101,9 @@ const store = createStore({ state.activity.activityType.thirdPartiesVisible !== 0) ); }, + socialActionsListSorted(state) { + return [ ...state.socialActionsList].sort((a, b) => a.ordering - b.ordering); + }, }, mutations: { // SocialIssueAcc @@ -131,7 +134,6 @@ const store = createStore({ state.socialActionsList = []; }, addActionInList(state, action) { - //console.log('add action list', action.id); state.socialActionsList.push(action); }, updateActionsSelected(state, actions) { diff --git a/src/Bundle/ChillActivityBundle/Tests/Entity/ActivityTest.php b/src/Bundle/ChillActivityBundle/Tests/Entity/ActivityTest.php new file mode 100644 index 000000000..7a68a36fc --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Entity/ActivityTest.php @@ -0,0 +1,114 @@ +addChild($child); + $grandChild = new SocialAction(); + $child->addChild($grandChild); + + $activity = new Activity(); + + $activity->addSocialAction($parent); + + $this->assertCount(1, $activity->getSocialActions()); + $this->assertContains($parent, $activity->getSocialActions()); + + $activity->addSocialAction($grandChild); + + $this->assertCount(1, $activity->getSocialActions()); + $this->assertContains($grandChild, $activity->getSocialActions()); + $this->assertNotContains($parent, $activity->getSocialActions()); + + $activity->addSocialAction($child); + + $this->assertCount(1, $activity->getSocialActions()); + $this->assertContains($grandChild, $activity->getSocialActions()); + $this->assertNotContains($parent, $activity->getSocialActions()); + $this->assertNotContains($child, $activity->getSocialActions()); + + $activity->addSocialAction($another = new SocialAction()); + + $this->assertCount(2, $activity->getSocialActions()); + $this->assertContains($grandChild, $activity->getSocialActions()); + $this->assertContains($another, $activity->getSocialActions()); + $this->assertNotContains($parent, $activity->getSocialActions()); + $this->assertNotContains($child, $activity->getSocialActions()); + } + + public function testHierarchySocialIssues(): void + { + $listener = new AccompanyingPeriodSocialIssueConsistencyEntityListener(); + $event = $this->prophesize(LifecycleEventArgs::class)->reveal(); + + $parent = new SocialIssue(); + $child = new SocialIssue(); + + $parent->addChild($child); + $grandChild = new SocialIssue(); + $child->addChild($grandChild); + + $activity = new Activity(); + $activity->setAccompanyingPeriod(new AccompanyingPeriod()); + + $activity->addSocialIssue($parent); + $listener->preUpdate($activity, $event); + + $this->assertCount(1, $activity->getSocialIssues()); + $this->assertContains($parent, $activity->getSocialIssues()); + + $activity->addSocialIssue($grandChild); + $listener->preUpdate($activity, $event); + + $this->assertCount(1, $activity->getSocialIssues()); + $this->assertContains($grandChild, $activity->getSocialIssues()); + $this->assertNotContains($parent, $activity->getSocialIssues()); + + $activity->addSocialIssue($child); + $listener->preUpdate($activity, $event); + + $this->assertCount(1, $activity->getSocialIssues()); + $this->assertContains($grandChild, $activity->getSocialIssues()); + $this->assertNotContains($parent, $activity->getSocialIssues()); + $this->assertNotContains($child, $activity->getSocialIssues()); + + $activity->addSocialIssue($another = new SocialIssue()); + $listener->preUpdate($activity, $event); + + $this->assertCount(2, $activity->getSocialIssues()); + $this->assertContains($grandChild, $activity->getSocialIssues()); + $this->assertContains($another, $activity->getSocialIssues()); + $this->assertNotContains($parent, $activity->getSocialIssues()); + $this->assertNotContains($child, $activity->getSocialIssues()); + } +} diff --git a/src/Bundle/ChillDocGeneratorBundle/Context/ContextManager.php b/src/Bundle/ChillDocGeneratorBundle/Context/ContextManager.php index 8dfecb730..b8f4d0eed 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Context/ContextManager.php +++ b/src/Bundle/ChillDocGeneratorBundle/Context/ContextManager.php @@ -14,7 +14,7 @@ namespace Chill\DocGeneratorBundle\Context; use Chill\DocGeneratorBundle\Context\Exception\ContextNotFoundException; use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate; -class ContextManager +final class ContextManager implements ContextManagerInterface { /** * @var DocGeneratorContextInterface[]|iterable @@ -26,9 +26,6 @@ class ContextManager $this->contexts = $contexts; } - /** - * @throw ContextNotFoundException when the context is not found - */ public function getContextByDocGeneratorTemplate(DocGeneratorTemplate $docGeneratorTemplate): DocGeneratorContextInterface { foreach ($this->contexts as $key => $context) { diff --git a/src/Bundle/ChillDocGeneratorBundle/Context/ContextManagerInterface.php b/src/Bundle/ChillDocGeneratorBundle/Context/ContextManagerInterface.php new file mode 100644 index 000000000..3468d787d --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Context/ContextManagerInterface.php @@ -0,0 +1,30 @@ +contextManager = $contextManager; $this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository; $this->driver = $driver; $this->logger = $logger; $this->paginatorFactory = $paginatorFactory; - $this->tempUrlGenerator = $tempUrlGenerator; - $this->kernel = $kernel; $this->client = $client; + $this->storedObjectManager = $storedObjectManager; + $this->entityManager = $entityManager; } /** @@ -178,8 +176,10 @@ final class DocGeneratorTemplateController extends AbstractController return $this->redirectToRoute( 'chill_docgenerator_test_generate_from_template', - ['template' => $template, 'entityClassName' => $entityClassName, 'entityId' => $entityId, - 'returnPath' => $request->query->get('returnPath', '/'), ] + [ + 'template' => $template, 'entityClassName' => $entityClassName, 'entityId' => $entityId, + 'returnPath' => $request->query->get('returnPath', '/'), + ] ); } @@ -193,16 +193,26 @@ final class DocGeneratorTemplateController extends AbstractController try { $context = $this->contextManager->getContextByDocGeneratorTemplate($template); } catch (ContextNotFoundException $e) { - throw new NotFoundHttpException($e->getMessage(), $e); + throw new NotFoundHttpException( + 'Context not found.', + $e + ); } - $entity = $this->getDoctrine()->getRepository($context->getEntityClass())->find($entityId); + $entity = $this + ->entityManager + ->getRepository($context->getEntityClass()) + ->find($entityId); if (null === $entity) { - throw new NotFoundHttpException("Entity with classname {$entityClassName} and id {$entityId} is not found"); + throw new NotFoundHttpException( + sprintf('Entity with classname %s and id %s is not found', $entityClassName, $entityId) + ); } - $contextGenerationData = []; + $contextGenerationData = [ + 'test_file' => null, + ]; if ( $context instanceof DocGeneratorContextWithPublicFormInterface @@ -240,128 +250,114 @@ final class DocGeneratorTemplateController extends AbstractController $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, ]; + $templateOptions = [ + 'entity' => $entity, 'form' => $form->createView(), + 'template' => $template, 'context' => $context, + ]; return $this->render($templatePath, $templateOptions); } } - if ($isTest && null !== $contextGenerationData['test_file']) { - /** @var File $file */ - $file = $contextGenerationData['test_file']; - $templateResource = fopen($file->getPathname(), 'rb'); + $document = $template->getFile(); + + if ($isTest && ($contextGenerationData['test_file'] instanceof File)) { + $dataDecrypted = file_get_contents($contextGenerationData['test_file']->getPathname()); } else { - $getUrlGen = $this->tempUrlGenerator->generate( - 'GET', - $template->getFile()->getFilename() - ); - - $data = $this->client->request('GET', $getUrlGen->url); - - $iv = $template->getFile()->getIv(); // iv as an Array - $ivGoodFormat = pack('C*', ...$iv); // iv as a String (ok for openssl_decrypt) - - $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); + try { + $dataDecrypted = $this->storedObjectManager->read($document); + } catch (Throwable $exception) { + throw $exception; } - - if (false === $templateResource = fopen('php://memory', 'r+b')) { - $this->logger->error('Could not write data to memory'); - - throw new HttpException(500); - } - fwrite($templateResource, $dataDecrypted); - rewind($templateResource); } - $datas = $context->getData($template, $entity, $contextGenerationData); if ($isTest && isset($form) && $form['show_data']->getData()) { // very ugly hack... - dd($datas); + dd($context->getData($template, $entity, $contextGenerationData)); } try { - $generatedResource = $this->driver->generateFromResource($templateResource, $template->getFile()->getType(), $datas, $template->getFile()->getFilename()); + $generatedResource = $this + ->driver + ->generateFromString( + $dataDecrypted, + $template->getFile()->getType(), + $context->getData($template, $entity, $contextGenerationData), + $template->getFile()->getFilename() + ); } catch (TemplateException $e) { - $msg = implode("\n", $e->getErrors()); - - return new Response($msg, 400, [ - 'Content-Type' => 'text/plain', - ]); + return new Response( + implode("\n", $e->getErrors()), + 400, + [ + 'Content-Type' => 'text/plain', + ] + ); } - fclose($templateResource); - if ($isTest) { - return new StreamedResponse( - static function () use ($generatedResource) { - fpassthru($generatedResource); - fclose($generatedResource); - }, + return new Response( + $generatedResource, Response::HTTP_OK, [ 'Content-Transfer-Encoding', 'binary', 'Content-Type' => 'application/vnd.oasis.opendocument.text', - 'Content-Disposition' => sprintf('attachment; filename="%s.odt"', 'generated'), - 'Content-Length' => fstat($generatedResource)['size'], + 'Content-Disposition' => 'attachment; filename="generated.odt"', + 'Content-Length' => strlen($generatedResource), ], ); } - $genDocName = 'doc_' . sprintf('%010d', mt_rand()) . 'odt'; - - $getUrlGen = $this->tempUrlGenerator->generate( - 'PUT', - $genDocName - ); - - $client = new Client(); + /** @var StoredObject $storedObject */ + $storedObject = (new ObjectNormalizer()) + ->denormalize( + [ + 'type' => $template->getFile()->getType(), + 'filename' => sprintf('%s_odt', uniqid('doc_', true)), + ], + StoredObject::class + ); try { - $putResponse = $client->request('PUT', $getUrlGen->url, [ - 'body' => $generatedResource, - ]); + $this->storedObjectManager->write($storedObject, $generatedResource); + } catch (Throwable $exception) { + throw $exception; + } - if ($putResponse->getStatusCode() === 201) { - $em = $this->getDoctrine()->getManager(); - $storedObject = new StoredObject(); - $storedObject - ->setType($template->getFile()->getType()) - ->setFilename($genDocName); + $this->entityManager->persist($storedObject); - $em->persist($storedObject); - - try { - $context->storeGenerated($template, $storedObject, $entity, $contextGenerationData); - } catch (Exception $e) { - $this->logger->error('Could not store the associated document to entity', [ + try { + $context + ->storeGenerated( + $template, + $storedObject, + $entity, + $contextGenerationData + ); + } catch (Exception $e) { + $this + ->logger + ->error( + 'Unable to store the associated document to entity', + [ 'entityClassName' => $entityClassName, 'entityId' => $entityId, 'contextKey' => $context->getName(), - ]); + ] + ); - throw $e; - } - - $em->flush(); - - return $this->redirectToRoute('chill_wopi_file_edit', [ - 'fileId' => $storedObject->getUuid(), - 'returnPath' => $request->query->get('returnPath', '/'), - ]); - } - } catch (TransferException $e) { throw $e; } - throw new Exception('Unable to generate document.'); + $this->entityManager->flush(); + + return $this + ->redirectToRoute( + 'chill_wopi_file_edit', + [ + 'fileId' => $storedObject->getUuid(), + 'returnPath' => $request->query->get('returnPath', '/'), + ] + ); } } diff --git a/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/DriverInterface.php b/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/DriverInterface.php index 2572484e1..d787523c9 100644 --- a/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/DriverInterface.php +++ b/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/DriverInterface.php @@ -13,10 +13,5 @@ namespace Chill\DocGeneratorBundle\GeneratorDriver; interface DriverInterface { - /** - * @param resource $template - * - * @return resource - */ - public function generateFromResource($template, string $resourceType, array $data, ?string $templateName = null); + public function generateFromString(string $template, string $resourceType, array $data, ?string $templateName = null): string; } diff --git a/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/RelatorioDriver.php b/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/RelatorioDriver.php index 7a3e4ac69..4cadcef1f 100644 --- a/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/RelatorioDriver.php +++ b/src/Bundle/ChillDocGeneratorBundle/GeneratorDriver/RelatorioDriver.php @@ -16,45 +16,47 @@ use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Mime\Part\DataPart; use Symfony\Component\Mime\Part\Multipart\FormDataPart; +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; -use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface; use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; +use Throwable; -class RelatorioDriver implements DriverInterface +final class RelatorioDriver implements DriverInterface { - private LoggerInterface $logger; + private HttpClientInterface $client; - private HttpClientInterface $relatorioClient; + private LoggerInterface $logger; private string $url; public function __construct( - HttpClientInterface $relatorioClient, + HttpClientInterface $client, ParameterBagInterface $parameterBag, LoggerInterface $logger ) { - $this->relatorioClient = $relatorioClient; + $this->client = $client; $this->logger = $logger; $this->url = $parameterBag->get('chill_doc_generator')['driver']['relatorio']['url']; } - public function generateFromResource($template, string $resourceType, array $data, ?string $templateName = null) + public function generateFromString(string $template, string $resourceType, array $data, ?string $templateName = null): string { - $formFields = [ - 'variables' => json_encode($data), - 'template' => new DataPart($template, $templateName ?? uniqid('template_'), $resourceType), - ]; - $form = new FormDataPart($formFields); + $form = new FormDataPart( + [ + 'variables' => json_encode($data), + 'template' => new DataPart($template, $templateName ?? uniqid('template_'), $resourceType), + ] + ); try { - $response = $this->relatorioClient->request('POST', $this->url, [ + $response = $this->client->request('POST', $this->url, [ 'headers' => $form->getPreparedHeaders()->toArray(), 'body' => $form->bodyToIterable(), ]); - return $response->toStream(); - } catch (HttpExceptionInterface $e) { + return $response->getContent(); + } catch (ClientExceptionInterface $e) { $content = $e->getResponse()->getContent(false); if (400 === $e->getResponse()->getStatusCode()) { @@ -87,6 +89,18 @@ class RelatorioDriver implements DriverInterface ]); throw $e; + } catch (Throwable $exception) { + $this + ->logger + ->error( + 'relatorio: Unable to get content from response.', + [ + 'msg' => $exception->getMessage(), + 'e' => $exception->getTraceAsString(), + ] + ); + + throw $exception; } } } diff --git a/src/Bundle/ChillDocGeneratorBundle/composer.json b/src/Bundle/ChillDocGeneratorBundle/composer.json index 4ae119636..42939bd2b 100644 --- a/src/Bundle/ChillDocGeneratorBundle/composer.json +++ b/src/Bundle/ChillDocGeneratorBundle/composer.json @@ -18,6 +18,7 @@ } ], "require": { + "spomky-labs/base64url": "^2" }, "require-dev": { }, diff --git a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php index 86bc930c6..56380ec74 100644 --- a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php +++ b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php @@ -109,12 +109,12 @@ class StoredObject implements AsyncFileInterface, Document return $this->id; } - public function getIv() + public function getIv(): array { return $this->iv; } - public function getKeyInfos() + public function getKeyInfos(): array { return $this->keyInfos; } diff --git a/src/Bundle/ChillDocStoreBundle/Exception/StoredObjectManagerException.php b/src/Bundle/ChillDocStoreBundle/Exception/StoredObjectManagerException.php new file mode 100644 index 000000000..b5d0bf679 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Exception/StoredObjectManagerException.php @@ -0,0 +1,40 @@ + {{ 'online_edit_document'|trans }} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig index 8fbc3fa5c..3963b0715 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig @@ -53,6 +53,11 @@
  • + {% if chill_document_is_editable(document.object) %} +
  • + {{ document.object|chill_document_edit_button }} +
  • + {% endif %} {% endif %} {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
  • @@ -75,6 +80,11 @@
  • + {% if chill_document_is_editable(document.object) %} +
  • + {{ document.object|chill_document_edit_button }} +
  • + {% endif %} {% endif %} {% if is_granted('CHILL_PERSON_DOCUMENT_SEE_DETAILS', document) %}
  • diff --git a/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php new file mode 100644 index 000000000..573d77e2c --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php @@ -0,0 +1,133 @@ +client = $client; + $this->tempUrlGenerator = $tempUrlGenerator; + } + + public function read(StoredObject $document): string + { + try { + $response = $this + ->client + ->request( + Request::METHOD_GET, + $this + ->tempUrlGenerator + ->generate( + Request::METHOD_GET, + $document->getFilename() + ) + ->url + ); + } catch (Throwable $e) { + throw StoredObjectManagerException::errorDuringHttpRequest($e); + } + + if ($response->getStatusCode() !== Response::HTTP_OK) { + throw StoredObjectManagerException::invalidStatusCode($response->getStatusCode()); + } + + try { + $data = $response->getContent(); + } catch (Throwable $e) { + throw StoredObjectManagerException::unableToGetResponseContent($e); + } + + if (false === $this->hasKeysAndIv($document)) { + return $data; + } + + $clearData = openssl_decrypt( + $data, + self::ALGORITHM, + // TODO: Why using this library and not use base64_decode() ? + Base64Url::decode($document->getKeyInfos()['k']), + OPENSSL_RAW_DATA, + pack('C*', ...$document->getIv()) + ); + + if (false === $clearData) { + throw StoredObjectManagerException::unableToDecrypt(openssl_error_string()); + } + + return $clearData; + } + + public function write(StoredObject $document, string $clearContent): void + { + $encryptedContent = $this->hasKeysAndIv($document) + ? openssl_encrypt( + $clearContent, + self::ALGORITHM, + // TODO: Why using this library and not use base64_decode() ? + Base64Url::decode($document->getKeyInfos()['k']), + OPENSSL_RAW_DATA, + pack('C*', ...$document->getIv()) + ) + : $clearContent; + + try { + $response = $this + ->client + ->request( + Request::METHOD_PUT, + $this + ->tempUrlGenerator + ->generate( + Request::METHOD_PUT, + $document->getFilename() + ) + ->url, + [ + 'body' => $encryptedContent, + ] + ); + } catch (TransportExceptionInterface $exception) { + throw StoredObjectManagerException::errorDuringHttpRequest($exception); + } + + if ($response->getStatusCode() !== Response::HTTP_CREATED) { + throw StoredObjectManagerException::invalidStatusCode($response->getStatusCode()); + } + } + + private function hasKeysAndIv(StoredObject $storedObject): bool + { + return ([] !== $storedObject->getKeyInfos()) && ([] !== $storedObject->getIv()); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php new file mode 100644 index 000000000..3cf67cb0c --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php @@ -0,0 +1,34 @@ +setFilename('encrypted.txt') + ->setKeyInfos(['k' => base64_encode('S9NIHMaFHOWzLPez3jZOIHBaNfBrMQUR5zvqBz6kme8')]) + ->setIv(unpack('C*', 'abcdefghijklmnop')), + hex2bin('741237d255fd4f7eddaaa9058912a84caae28a41b10b34d4e3e3abe41d3b9b47cb0dd8f22c3c883d4f0e9defa75ff662'), // Binary encoded string + 'The quick brown fox jumps over the lazy dog', // clear + ]; + + // Non-encrypted object + yield [ + (new StoredObject())->setFilename('non-encrypted.txt'), // The StoredObject + 'The quick brown fox jumps over the lazy dog', // Encrypted + 'The quick brown fox jumps over the lazy dog', // Clear + ]; + + /* UNHAPPY SCENARIO */ + + // Encrypted object with issue during HTTP communication + yield [ + (new StoredObject()) + ->setFilename('error_during_http_request.txt') + ->setKeyInfos(['k' => base64_encode('S9NIHMaFHOWzLPez3jZOIHBaNfBrMQUR5zvqBz6kme8')]) + ->setIv(unpack('C*', 'abcdefghijklmnop')), + hex2bin('741237d255fd4f7eddaaa9058912a84caae28a41b10b34d4e3e3abe41d3b9b47cb0dd8f22c3c883d4f0e9defa75ff662'), // Binary encoded string + 'The quick brown fox jumps over the lazy dog', // clear + StoredObjectManagerException::class, + ]; + + // Encrypted object with issue during HTTP communication: Invalid status code + yield [ + (new StoredObject()) + ->setFilename('invalid_statuscode.txt') + ->setKeyInfos(['k' => base64_encode('S9NIHMaFHOWzLPez3jZOIHBaNfBrMQUR5zvqBz6kme8')]) + ->setIv(unpack('C*', 'abcdefghijklmnop')), + hex2bin('741237d255fd4f7eddaaa9058912a84caae28a41b10b34d4e3e3abe41d3b9b47cb0dd8f22c3c883d4f0e9defa75ff662'), // Binary encoded string + 'The quick brown fox jumps over the lazy dog', // clear + StoredObjectManagerException::class, + ]; + + // Erroneous encrypted: Unable to decrypt exception. + yield [ + (new StoredObject()) + ->setFilename('unable_to_decrypt.txt') + ->setKeyInfos(['k' => base64_encode('WRONG_PASS_PHRASE')]) + ->setIv(unpack('C*', 'abcdefghijklmnop')), + 'WRONG_ENCODED_VALUE', // Binary encoded string + 'The quick brown fox jumps over the lazy dog', // clear + StoredObjectManagerException::class, + ]; + } + + /** + * @dataProvider getDataProvider + */ + public function testRead(StoredObject $storedObject, string $encodedContent, string $clearContent, ?string $exceptionClass = null) + { + if (null !== $exceptionClass) { + $this->expectException($exceptionClass); + } + + $storedObjectManager = $this->getSubject($storedObject, $encodedContent); + + self::assertEquals($clearContent, $storedObjectManager->read($storedObject)); + } + + /** + * @dataProvider getDataProvider + */ + public function testWrite(StoredObject $storedObject, string $encodedContent, string $clearContent, ?string $exceptionClass = null) + { + if (null !== $exceptionClass) { + $this->expectException($exceptionClass); + } + + $storedObjectManager = $this->getSubject($storedObject, $encodedContent); + + $storedObjectManager->write($storedObject, $clearContent); + + self::assertEquals($clearContent, $storedObjectManager->read($storedObject)); + } + + private function getHttpClient(string $encodedContent): HttpClientInterface + { + $callback = static function ($method, $url, $options) use ($encodedContent) { + if (Request::METHOD_GET === $method) { + switch ($url) { + case 'https://example.com/non-encrypted.txt': + case 'https://example.com/encrypted.txt': + return new MockResponse($encodedContent, ['http_code' => 200]); + + case 'https://example.com/error_during_http_request.txt': + return new TransportException('error_during_http_request.txt'); + + case 'https://example.com/invalid_statuscode.txt': + return new MockResponse($encodedContent, ['http_code' => 404]); + } + } + + if (Request::METHOD_PUT === $method) { + switch ($url) { + case 'https://example.com/non-encrypted.txt': + case 'https://example.com/encrypted.txt': + return new MockResponse($encodedContent, ['http_code' => 201]); + + case 'https://example.com/error_during_http_request.txt': + throw new TransportException('error_during_http_request.txt'); + + case 'https://example.com/invalid_statuscode.txt': + return new MockResponse($encodedContent, ['http_code' => 404]); + } + } + + return new MockResponse('Not found'); + }; + + return new MockHttpClient($callback); + } + + private function getSubject(StoredObject $storedObject, string $encodedContent): StoredObjectManagerInterface + { + return new StoredObjectManager( + $this->getHttpClient($encodedContent), + $this->getTempUrlGenerator($storedObject) + ); + } + + private function getTempUrlGenerator(StoredObject $storedObject): TempUrlGeneratorInterface + { + $response = new stdClass(); + $response->url = $storedObject->getFilename(); + + $tempUrlGenerator = $this + ->getMockBuilder(TempUrlGeneratorInterface::class) + ->getMock(); + + $tempUrlGenerator + ->method('generate') + ->withAnyParameters() + ->willReturn($response); + + return $tempUrlGenerator; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/composer.json b/src/Bundle/ChillDocStoreBundle/composer.json index c011ce29b..aa636535e 100644 --- a/src/Bundle/ChillDocStoreBundle/composer.json +++ b/src/Bundle/ChillDocStoreBundle/composer.json @@ -8,7 +8,8 @@ } }, "require": { - "symfony/mime": "^4 || ^5" + "symfony/mime": "^4 || ^5", + "symfony/http-foundation": "^4" }, "license": "AGPL-3.0" } diff --git a/src/Bundle/ChillDocStoreBundle/config/services.yaml b/src/Bundle/ChillDocStoreBundle/config/services.yaml index ff2818f6b..860495677 100644 --- a/src/Bundle/ChillDocStoreBundle/config/services.yaml +++ b/src/Bundle/ChillDocStoreBundle/config/services.yaml @@ -40,3 +40,8 @@ services: tags: - { name: 'serializer.normalizer', priority: 16 } + Chill\DocStoreBundle\Service\: + autowire: true + autoconfigure: true + resource: '../Service/' + diff --git a/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php b/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php index 3f3b08f6c..b4ab74a25 100644 --- a/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php @@ -56,7 +56,7 @@ final class NotificationRepository implements ObjectRepository if (null === $this->notificationByRelatedEntityAndUserAssociatedStatement) { $sql = 'SELECT - SUM((EXISTS (SELECT 1 AS c FROM chill_main_notification_addresses_unread cmnau JOIN chill_main_notification cmn ON cmnau.notification_id = cmn.id WHERE user_id = :userid and cmnau.notification_id = cmn.id and cmn.sender_id IS NOT NULL))::int) AS unread, + SUM((EXISTS (SELECT 1 AS c FROM chill_main_notification_addresses_unread cmnau WHERE user_id = :userid and cmnau.notification_id = cmn.id))::int) AS unread, SUM((cmn.sender_id = :userid)::int) AS sent, COUNT(cmn.*) AS total FROM chill_main_notification cmn diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/chillmain.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/chillmain.scss index 1750533f8..e4cdcb10c 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/chill/chillmain.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/chillmain.scss @@ -16,6 +16,9 @@ // Chill forms @import './scss/forms'; +// Extend bootstrap accordion +@import './scss/accordion'; + // Chill record_actions @import './scss/record_actions'; diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/accordion.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/accordion.scss new file mode 100644 index 000000000..88d4b5e08 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/accordion.scss @@ -0,0 +1,11 @@ +.accordion { + // show a folded / label on accordion + button[aria-expanded="true"] > span.folded, + button[aria-expanded="false"] > span.unfolded { display: none; } + button[aria-expanded="false"] > span.folded, + button[aria-expanded="true"] > span.unfolded { display: inline; } + + .accordion-item { + margin-bottom: 1rem; + } +} diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php index ec5e5a8e5..904950489 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php @@ -128,6 +128,44 @@ class SocialAction return $this; } + /** + * In a SocialIssues's collection, find the elements which are an ancestor of + * other elements. + * + * The difference of the given list (thus, the elements which are **not** kept + * in the returned collection) are the most-grand-child elements of the list. + * + * Removing those elements of the Collection (which is not done by this method) + * will ensure that only the most descendent elements are present in the collection, + * (any ancestor of another element are present). + * + * @param Collection|SocialAction[] $socialActions + * + * @return Collection|SocialAction[] a list with the elements of the given list which are parent of other elements in the given list + */ + public static function findAncestorSocialActions(Collection $socialActions): Collection + { + $ancestors = new ArrayCollection(); + + foreach ($socialActions as $candidateChild) { + if ($ancestors->contains($candidateChild)) { + continue; + } + + foreach ($socialActions as $candidateParent) { + if ($ancestors->contains($candidateParent)) { + continue; + } + + if ($candidateChild->isDescendantOf($candidateParent)) { + $ancestors->add($candidateParent); + } + } + } + + return $ancestors; + } + /** * @return Collection|self[] */ @@ -169,7 +207,7 @@ class SocialAction } /** - * @return Collection|self[] All the descendants with the current entity (this) + * @return Collection|self[] All the descendants including the current entity (this) */ public function getDescendantsWithThis(): Collection { @@ -233,6 +271,23 @@ class SocialAction return $this->getParent() instanceof self; } + /** + * Recursive method which return true if the current $action + * is a descendant of the $action given in parameter. + */ + public function isDescendantOf(SocialAction $action): bool + { + if (!$this->hasParent()) { + return false; + } + + if ($this->getParent() === $action) { + return true; + } + + return $this->getParent()->isDescendantOf($action); + } + public function removeChild(self $child): self { if ($this->children->removeElement($child)) { diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php index c735c0132..fb277f56c 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php @@ -202,7 +202,7 @@ class SocialIssue } /** - * @return Collection|self[] All the descendants with the current entity (this) + * @return Collection|self[] All the descendants including the current entity (this) */ public function getDescendantsWithThis(): Collection { 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 2383e43b1..30cac9c85 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/flex_table.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/flex_table.scss @@ -30,6 +30,7 @@ div.list-with-period { // override wrap-list div.wrap-list.periods-list { + padding-right: 1rem; div.wl-row { flex-wrap: nowrap; div.wl-col { @@ -63,6 +64,7 @@ div.list-with-period { } div.periods-list { + padding-right: 1rem; div.title { text-align: right; div.date {} diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue index 349581452..b6a55ef39 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue @@ -5,9 +5,8 @@
    - -
    - +
    +
    @@ -17,30 +16,54 @@ \ No newline at end of file + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js index 5bebdb52a..44238b0c7 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js @@ -153,7 +153,7 @@ const appMessages = { }, startdate: { change: "Date d'ouverture", - date: "Date d'ouverture", + // update: "La nouvelle date d'ouverture a été enregistrée" }, // catch errors 'Error while updating AccompanyingPeriod Course.': "Erreur du serveur lors de la mise à jour du parcours d'accompagnement.", diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js index 06a3606ab..57415565b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js @@ -322,7 +322,6 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou } }, updateStartDate(state, date) { - console.log('new state date', date) state.accompanyingCourse.openingDate = date; } }, @@ -814,11 +813,9 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou }) }, updateStartDate({commit}, payload) { - console.log('payload', payload) - const date = ISOToDate(payload); const url = `/api/1.0/person/accompanying-course/${id}.json`; - const body = { type: "accompanying_period", openingDate: { datetime: datetimeToISO(date) }}; - console.log('body', body) + const date = payload === null || payload === '' ? null : { datetime: datetimeToISO(ISOToDate(payload)) } + const body = { type: "accompanying_period", openingDate: date}; return makeFetch('PATCH', url, body) .then((response) => { commit('updateStartDate', response.openingDate); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue index 503a00c59..758cd7e5e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue @@ -227,19 +227,18 @@ export default { }, startDate: { get() { - let d = this.$store.state.startDate; - return dateToISO(d); + return this.$store.state.startDate; }, set(value) { - this.$store.commit('setStartDate', ISOToDate(value)); + this.$store.commit('setStartDate', value); } }, endDate: { get() { - return dateToISO(this.$store.state.endDate); + return this.$store.state.endDate; }, set(value) { - this.$store.commit('setEndDate', ISOToDate(value)); + this.$store.commit('setEndDate', value); } }, setSocialIssue: { diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js index 408115257..3c2e034d7 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js @@ -1,6 +1,6 @@ import { createStore } from 'vuex'; -import { datetimeToISO } from 'ChillMainAssets/chill/js/date.js'; +import { datetimeToISO, dateToISO, ISOToDate, ISOToDatetime } from 'ChillMainAssets/chill/js/date.js'; import { findSocialActionsBySocialIssue } from 'ChillPersonAssets/vuejs/_api/SocialWorkSocialAction.js'; // import { create } from 'ChillPersonAssets/vuejs/_api/AccompanyingCourseWork.js'; import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods'; @@ -20,7 +20,7 @@ const store = createStore({ .map(p => p.person), personsReachables: window.accompanyingCourse.participations.filter(p => p.endDate == null) .map(p => p.person), - startDate: new Date(), + startDate: dateToISO(new Date()), endDate: null, isLoadingSocialActions: false, isPostingWork: false, @@ -47,7 +47,7 @@ const store = createStore({ id: state.socialActionPicked.id }, startDate: { - datetime: datetimeToISO(state.startDate) + datetime: datetimeToISO(ISOToDate(state.startDate)) }, persons: [] }; @@ -61,7 +61,7 @@ const store = createStore({ if (null !== state.endDate) { payload.endDate = { - datetime: datetimeToISO(state.endDate) + datetime: datetimeToISO(ISOToDate(state.endDate)) }; } @@ -111,6 +111,7 @@ const store = createStore({ state.startDate = date; }, setEndDate(state, date) { + console.log(date) state.endDate = date; }, setPersonsPickedIds(state, ids) { diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue index bcfa53e45..bab64018a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue @@ -439,18 +439,18 @@ export default { ]), startDate: { get() { - return dateToISO(this.$store.state.startDate); + return this.$store.state.startDate; }, set(v) { - this.$store.commit('setStartDate', ISOToDate(v)); + this.$store.commit('setStartDate', v); } }, endDate: { get() { - return dateToISO(this.$store.state.endDate); + return this.$store.state.endDate; }, set(v) { - this.$store.commit('setEndDate', ISOToDate(v)); + this.$store.commit('setEndDate', v); } }, note: { @@ -521,12 +521,12 @@ export default { this.$store.commit('removeReferrer', u); }, goToGenerateWorkflow({link}) { - console.log('save before leave to generate workflow') - const callback = (data) => { - window.location.assign(link); - }; + // console.log('save before leave to generate workflow') + const callback = (data) => { + window.location.assign(link); + }; - return this.$store.dispatch('submit', callback) + return this.$store.dispatch('submit', callback) .catch(e => { console.log(e); throw e; }); }, submit() { 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 53e2b72f3..b1e657554 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue @@ -298,38 +298,39 @@ export default { } }, computed: { - ...mapState([ - 'isPosting' - ]), + ...mapState([ + 'isPosting' + ]), getTemplatesAvailables() { - return this.$store.getters.getTemplatesAvailablesForEvaluation(this.evaluation.evaluation); + return this.$store.getters.getTemplatesAvailablesForEvaluation(this.evaluation.evaluation); }, canGenerate() { - return !this.$store.state.isPosting && this.template !== null; + return !this.$store.state.isPosting && this.template !== null; }, startDate: { - get() { - return dateToISO(this.evaluation.startDate); - }, - set(v) { - this.$store.commit('setEvaluationStartDate', { key: this.evaluation.key, date: ISOToDate(v) }); - } + get() { + console.log('evaluation', this.evaluation); + return this.evaluation.startDate; + }, + set(v) { + this.$store.commit('setEvaluationStartDate', { key: this.evaluation.key, date: v }); + } }, endDate: { - get() { - return dateToISO(this.evaluation.endDate); - }, - set(v) { - this.$store.commit('setEvaluationEndDate', { key: this.evaluation.key, date: ISOToDate(v) }); - } + get() { + return this.evaluation.endDate; + }, + set(v) { + this.$store.commit('setEvaluationEndDate', { key: this.evaluation.key, date: v }); + } }, maxDate: { - get() { - return dateToISO(this.evaluation.maxDate); - }, - set(v) { - this.$store.commit('setEvaluationMaxDate', { key: this.evaluation.key, date: ISOToDate(v) }); - } + get() { + return this.evaluation.maxDate; + }, + set(v) { + this.$store.commit('setEvaluationMaxDate', { key: this.evaluation.key, date: v }); + } }, warningInterval: { get() { return this.evaluation.warningInterval; }, @@ -344,7 +345,7 @@ export default { ISOToDatetime, canEditDocument(document) { return 'storedObject' in document ? - this.mime.includes(document.storedObject.type) && document.storedObject.keyInfos.length === 0 : false; + this.mime.includes(document.storedObject.type) : false; }, listAllStatus() { console.log('load all status'); @@ -360,16 +361,16 @@ export default { }, buildEditLink(storedObject) { return `/wopi/edit/${storedObject.uuid}?returnPath=` + encodeURIComponent( - window.location.pathname + window.location.search + window.location.hash); + window.location.pathname + window.location.search + window.location.hash); }, submitBeforeGenerate({template}) { - const callback = (data) => { - let evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key).id; + const callback = (data) => { + let evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key).id; - window.location.assign(buildLink(template, evaluationId, 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation')); - }; + window.location.assign(buildLink(template, evaluationId, 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation')); + }; - return this.$store.dispatch('submit', callback).catch(e => { console.log(e); throw e; }); + return this.$store.dispatch('submit', callback).catch(e => { console.log(e); throw e; }); }, onInputDocumentTitle(event) { const id = Number(event.target.id); @@ -395,20 +396,20 @@ export default { }, removeDocument(document) { if (window.confirm("Êtes-vous sûr·e de vouloir supprimer le document qui a pour titre \"" + document.title +"\" ?")) { - this.$store.commit('removeDocument', {key: this.evaluation.key, document: document}); + this.$store.commit('removeDocument', {key: this.evaluation.key, document: document}); } }, goToGenerateWorkflowEvaluationDocument({event, link, workflowName, payload}) { - const callback = (data) => { - let evaluation = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key); - let updatedDocument = evaluation.documents.find(d => d.key === payload.doc.key); - window.location.assign(buildLinkCreate(workflowName, - 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', updatedDocument.id)); - }; + const callback = (data) => { + let evaluation = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key); + let updatedDocument = evaluation.documents.find(d => d.key === payload.doc.key); + window.location.assign(buildLinkCreate(workflowName, + 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', updatedDocument.id)); + }; - return this.$store.dispatch('submit', callback) - .catch(e => { console.log(e); throw e; }); - }, + return this.$store.dispatch('submit', callback) + .catch(e => { console.log(e); throw e; }); + }, }, } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js index 4ce0b8ebe..d5437b0a7 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js @@ -1,5 +1,5 @@ import { createStore } from 'vuex'; -import { datetimeToISO, ISOToDatetime, intervalDaysToISO, intervalISOToDays } from 'ChillMainAssets/chill/js/date.js'; +import { dateToISO, ISOToDate, 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'; @@ -13,9 +13,9 @@ const store = createStore({ state: { work: window.accompanyingCourseWork, startDate: window.accompanyingCourseWork.startDate !== null ? - ISOToDatetime(window.accompanyingCourseWork.startDate.datetime) : null, + dateToISO(new Date(window.accompanyingCourseWork.startDate.datetime)) : null, endDate: window.accompanyingCourseWork.endDate !== null ? - ISOToDatetime(window.accompanyingCourseWork.endDate.datetime) : null, + dateToISO(new Date(window.accompanyingCourseWork.endDate.datetime)) : null, note: window.accompanyingCourseWork.note, goalsPicked: window.accompanyingCourseWork.goals, goalsForAction: [], @@ -72,11 +72,11 @@ const store = createStore({ return { type: 'accompanying_period_work', id: state.work.id, - startDate: state.startDate === null ? null : { - datetime: datetimeToISO(state.startDate) + startDate: state.startDate === null || state.startDate === '' ? null : { + datetime: datetimeToISO(ISOToDate(state.startDate)) }, - endDate: state.endDate === null ? null : { - datetime: datetimeToISO(state.endDate) + endDate: state.endDate === null || state.endDate === '' ? null : { + datetime: datetimeToISO(ISOToDate(state.endDate)) }, note: state.note, persons: state.personsPicked.map(p => ({id: p.id, type: p.type})), @@ -110,9 +110,9 @@ const store = createStore({ id: e.evaluation.id, type: e.evaluation.type }, - startDate: e.startDate !== null ? { datetime: datetimeToISO(e.startDate) } : null, - endDate: e.endDate !== null ? { datetime: datetimeToISO(e.endDate) } : null, - maxDate: e.maxDate !== null ? { datetime: datetimeToISO(e.maxDate) } : null, + startDate: e.startDate === null || e.startDate === '' ? null : { datetime: datetimeToISO(ISOToDate(e.startDate)) }, + endDate: e.endDate === null || e.endDate === '' ? null : { datetime: datetimeToISO(ISOToDate(e.endDate)) }, + maxDate: e.maxDate === null || e.maxDate === '' ? null : { datetime: datetimeToISO(ISOToDate(e.maxDate)) }, warningInterval: intervalDaysToISO(e.warningInterval), comment: e.comment, documents: e.documents @@ -132,9 +132,9 @@ const store = createStore({ 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, + startDate: e.startDate !== null ? dateToISO(new Date(e.startDate.datetime)) : null, + endDate: e.endDate !== null ? dateToISO(new Date(e.endDate.datetime)) : null, + maxDate: e.maxDate !== null ? dateToISO(new Date(e.maxDate.datetime)) : null, warningInterval: e.warningInterval !== null ? intervalISOToDays(e.warningInterval) : null, documents: e.documents.map((d, docIndex) => { return Object.assign(d, { @@ -244,7 +244,7 @@ const store = createStore({ type: "accompanying_period_work_evaluation", key: state.evaluationsPicked.length + 1, evaluation: evaluation, - startDate: new Date(), + startDate: dateToISO(new Date()), endDate: null, maxDate: null, warningInterval: null, @@ -264,6 +264,7 @@ const store = createStore({ .startDate = date; }, setEvaluationEndDate(state, {key, date}) { + console.log('commit date', date) state.evaluationsPicked.find(e => e.key === key) .endDate = date; }, diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue index 040d316bb..03feb0329 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue @@ -66,18 +66,19 @@ export default { }, startDate: { get() { - return [ - this.$store.state.startDate.getFullYear(), - (this.$store.state.startDate.getMonth() + 1).toString().padStart(2, '0'), - this.$store.state.startDate.getDate().toString().padStart(2, '0') - ].join('-'); + return this.$store.state.startDate; + // return [ + // this.$store.state.startDate.getFullYear(), + // (this.$store.state.startDate.getMonth() + 1).toString().padStart(2, '0'), + // this.$store.state.startDate.getDate().toString().padStart(2, '0') + // ].join('-'); }, set(value) { - let - [year, month, day] = value.split('-'), - dValue = new Date(year, month-1, day); + // let + // [year, month, day] = value.split('-'), + // dValue = new Date(year, month-1, day); - this.$store.dispatch('setStartDate', dValue); + this.$store.dispatch('setStartDate', value); } } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js index f09b19787..f3801576d 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/store/index.js @@ -2,7 +2,7 @@ import { createStore } from 'vuex'; import { householdMove, fetchHouseholdSuggestionByAccompanyingPeriod, fetchAddressSuggestionByPerson} from './../api.js'; import { fetchResults } from 'ChillMainAssets/lib/api/apiMethods.js' import { fetchHouseholdByAddressReference } from 'ChillPersonAssets/lib/household.js'; -import { datetimeToISO } from 'ChillMainAssets/chill/js/date.js'; +import { datetimeToISO, dateToISO, ISOToDate } from 'ChillMainAssets/chill/js/date.js'; const debug = process.env.NODE_ENV !== 'production'; @@ -30,7 +30,7 @@ const store = createStore({ } return 0; }), - startDate: new Date(), + startDate: dateToISO(new Date()), /** * Indicates if the destination is: * @@ -278,7 +278,7 @@ const store = createStore({ type: conc.person.type }, start_date: { - datetime: datetimeToISO(state.startDate) + datetime: state.startDate === null || state.startDate === '' ? null : datetimeToISO(ISOToDate(state.startDate)) } }; @@ -302,7 +302,7 @@ const store = createStore({ }, number_of_children: state.numberOfChildren, start_date: { - datetime: datetimeToISO(state.startDate), + datetime: datetimeToISO(ISOToDate(state.startDate)), }, }; } diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_item.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_item.html.twig index 8ff8a0617..3d4f46425 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_item.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_item.html.twig @@ -25,22 +25,6 @@
    - {% if w.createdBy %} -
    -
    -

    {{ 'Referrers'|trans }}

    -
    -
    -

    - {% for u in w.referrers %} - {{ u|chill_entity_render_box }} - {% if not loop.last %}, {% endif %} - {% endfor %} -

    -
    -
    - {% endif %} - {%- if w.persons -%}
    @@ -78,6 +62,22 @@
    {% endif %} + {%- if w.referrers -%} +
    +
    +

    {{ 'Referrers'|trans }}

    +
    +
    + {% for u in w.referrers %} + + {{ u|chill_entity_render_box }} + {% if not loop.last %}, {% endif %} + + {% endfor %} +
    +
    + {% endif %} + {%- if w.socialAction.issue -%}
    diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_recent_by_accompanying_period.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_recent_by_accompanying_period.html.twig index 7f2fb0177..4621cb97a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_recent_by_accompanying_period.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_recent_by_accompanying_period.html.twig @@ -28,6 +28,15 @@ {{ w.handlingThierParty|chill_entity_render_box }}
  • {% endif %} + {% if w.referrers %} +
  • + {{ 'Referrers'|trans ~ ' : ' }} + {% for u in w.referrers %} + {{ u|chill_entity_render_box }} + {% if not loop.last %}, {% endif %} + {% endfor %} +
  • + {% endif %}
  • {{ 'Participants'|trans ~ ' : ' }} {% for p in w.persons %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list_item.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list_item.html.twig index 62d0682df..cfa616988 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list_item.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list_item.html.twig @@ -20,7 +20,7 @@ {% elseif period.step == 'CONFIRMED' %} {{- 'Confirmed'|trans|upper -}} {% else %} - {{- 'Closed'|trans|upper -}} + {{- 'Closed'|trans|upper -}} {% endif %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Household/accompanying_period.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Household/accompanying_period.html.twig index a793ac37a..acd251735 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Household/accompanying_period.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Household/accompanying_period.html.twig @@ -10,12 +10,6 @@ {% include 'ChillPersonBundle:AccompanyingPeriod:_list.html.twig' %} {% if accompanying_periods_old|length > 0 %} -

    diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Household/summary.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Household/summary.html.twig index a00c8df36..7ba2235a6 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Household/summary.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Household/summary.html.twig @@ -191,12 +191,6 @@ {% endif %} {% if old_members|length > 0 %} -

    diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig index c43806a71..9c7486fc2 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig @@ -10,6 +10,157 @@

  • {% endmacro %} +{% macro accompanying_period(acp, person) %} + {% set app = person.findParticipationForPeriod(acp) %} +
    +
    +
    +
    +

    + + {{ 'Course number'|trans }} {{ acp.id }} +

    +
    +
    +
    + {% if app != null %} +
    + {{ 'Since %date%'|trans({'%date%': app.startDate|format_date('medium') }) }} +
    + {% endif %} + + {% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', acp.id) %} + {% if notif_counter.total > 0 %} + {{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', acp.id) }} + {% endif %} +
    +
    + {% if acp.requestoranonymous == false and acp.requestorPerson is same as(person) %} + + {{ 'Requestor'|trans({'gender': person.gender}) }} + + {% endif %} + + {% if acp.emergency %} + {{- 'Emergency'|trans|upper -}} + {% endif %} + + {% if acp.confidential %} + {{- 'Confidential'|trans|upper -}} + {% endif %} + + {% if acp.step == 'DRAFT' %} + {{ 'course.draft'|trans }} + {% endif %} + + {% if acp.step == 'CLOSED' %} + {{ 'course.closed'|trans }} + {% endif %} +
    +
    +
    + + {% if acp.user is not null %} +
    +
    +

    {{ 'Referrer'|trans }}

    +
    +
    +
    + {{ acp.user|chill_entity_render_box }} +
    +
    +
    + {% endif %} + + {% if acp.socialIssues|length > 0 %} +
    +
    +

    {{ 'Social issues'|trans }}

    +
    +
    + {% for issue in acp.socialIssues %} + {{ issue|chill_entity_render_box }} + {% endfor %} +
    +
    + {% endif %} + + {% if acp.currentParticipations|length > 1 %} +
    +
    +

    + {{ 'Participants'|trans }} +

    +
    +
    + {% set participating = false %} + {% for part in acp.currentParticipations %} + {% if part.person.id != person.id %} + {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { + targetEntity: { name: 'person', id: part.person.id }, + action: 'show', + displayBadge: true, + buttonText: part.person|chill_entity_render_string, + isDead: part.person.deathdate is not null + } %} + {% else %} + {% set participating = true %} + {% endif %} + {% endfor %} + {% if participating %} + {{ 'person.and_himself'|trans({'gender': person.gender}) }} + {% endif %} +
    +
    + {% endif %} + + {% if acp.requestoranonymous == false %} + {% if (acp.requestorPerson is not null and acp.requestorPerson.id != person.id) or acp.requestorThirdParty is not null %} +
    +
    +

    + {% if acp.requestorPerson is not null %} + {{ 'Requestor'|trans({'gender': acp.requestorPerson.gender}) }} + {% else %} + {{ 'Requestor'|trans({'gender': 'other'})}} + {% endif %} +

    +
    +
    + {% if acp.requestorThirdParty is not null %} + {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { + targetEntity: { name: 'thirdparty', id: acp.requestorThirdParty.id }, + action: 'show', + displayBadge: true, + buttonText: acp.requestorThirdParty|chill_entity_render_string + } %} + {% else %} + {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { + targetEntity: { name: 'person', id: acp.requestorPerson.id }, + action: 'show', + displayBadge: true, + buttonText: acp.requestorPerson|chill_entity_render_string, + isDead: acp.requestorPerson.deathdate is not null + } %} + {% endif %} +
    +
    + {% endif %} + {% endif %} + + +
    +
    +{% endmacro %} +

    {{ title|default('Person search results')|trans }}

    @@ -66,167 +217,53 @@ {#- 'acps' is for AcCompanyingPeriodS #} {%- set acps = [] %} + {%- set acpsClosed = [] %} {%- for acp in person.accompanyingPeriodInvolved %} {%- if is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', acp) %} - {%- set acps = acps|merge([acp]) %} + {% if acp.step == 'CLOSED' %} + {%- set acpsClosed = acpsClosed|merge([acp]) %} + {% else %} + {%- set acps = acps|merge([acp]) %} + {% endif %} {%- endif %} {%- endfor %} {# add as requestor #} {% if acps|length > 0 %} {% for acp in acps %} - {% set app = person.findParticipationForPeriod(acp) %} -
    -
    -
    -
    -

    - - {{ 'Course number'|trans }} {{ acp.id }} -

    -
    -
    -
    - {% if app != null %} -
    - {{ 'Since %date%'|trans({'%date%': app.startDate|format_date('medium') }) }} -
    - {% endif %} - - {% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', acp.id) %} - {% if notif_counter.total > 0 %} - {{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', acp.id) }} - {% endif %} -
    -
    - {% if acp.requestoranonymous == false and acp.requestorPerson is same as(person) %} - - {{ 'Requestor'|trans({'gender': person.gender}) }} - - {% endif %} - - {% if acp.emergency %} - {{- 'Emergency'|trans|upper -}} - {% endif %} - - {% if acp.confidential %} - {{- 'Confidential'|trans|upper -}} - {% endif %} - - {% if acp.step == 'DRAFT' %} - {{ 'course.draft'|trans }} - {% endif %} - - {% if acp.step == 'CLOSED' %} - {{ 'course.closed'|trans }} - {% endif %} -
    -
    -
    - - {% if acp.user is not null %} -
    -
    -

    {{ 'Referrer'|trans }}

    -
    -
    -
    - {{ acp.user|chill_entity_render_box }} -
    -
    -
    - {% endif %} - - {% if acp.socialIssues|length > 0 %} -
    -
    -

    {{ 'Social issues'|trans }}

    -
    -
    - {% for issue in acp.socialIssues %} - {{ issue|chill_entity_render_box }} - {% endfor %} -
    -
    - {% endif %} - - {% if acp.currentParticipations|length > 1 %} -
    -
    -

    - {{ 'Participants'|trans }} -

    -
    -
    - {% set participating = false %} - {% for part in acp.currentParticipations %} - {% if part.person.id != person.id %} - {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { - targetEntity: { name: 'person', id: part.person.id }, - action: 'show', - displayBadge: true, - buttonText: part.person|chill_entity_render_string, - isDead: part.person.deathdate is not null - } %} - {% else %} - {% set participating = true %} - {% endif %} - {% endfor %} - {% if participating %} - {{ 'person.and_himself'|trans({'gender': person.gender}) }} - {% endif %} -
    -
    - {% endif %} - - {% if acp.requestoranonymous == false %} - {% if (acp.requestorPerson is not null and acp.requestorPerson.id != person.id) or acp.requestorThirdParty is not null %} -
    -
    -

    - {% if acp.requestorPerson is not null %} - {{ 'Requestor'|trans({'gender': acp.requestorPerson.gender}) }} - {% else %} - {{ 'Requestor'|trans({'gender': 'other'})}} - {% endif %} -

    -
    -
    - {% if acp.requestorThirdParty is not null %} - {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { - targetEntity: { name: 'thirdparty', id: acp.requestorThirdParty.id }, - action: 'show', - displayBadge: true, - buttonText: acp.requestorThirdParty|chill_entity_render_string - } %} - {% else %} - {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { - targetEntity: { name: 'person', id: acp.requestorPerson.id }, - action: 'show', - displayBadge: true, - buttonText: acp.requestorPerson|chill_entity_render_string, - isDead: acp.requestorPerson.deathdate is not null - } %} - {% endif %} -
    -
    - {% endif %} - {% endif %} - - - -
    -
    + {{ _self.accompanying_period(acp, person) }} {% endfor %} {% endif %} + {% if acpsClosed|length > 0 %} +
    +
    +

    + +

    + +
    + {% for acp in acpsClosed %} + {{ _self.accompanying_period(acp, person) }} + {% endfor %} +
    +
    +
    + {% endif %} + +
    {% endfor %} diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialActionNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialActionNormalizer.php index e210e1c4f..52ba07a93 100644 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialActionNormalizer.php +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialActionNormalizer.php @@ -40,6 +40,7 @@ class SocialActionNormalizer implements NormalizerAwareInterface, NormalizerInte 'desactivationDate' => $this->normalizer->normalize($socialAction->getDesactivationDate(), $format, $context), 'title' => $socialAction->getTitle(), 'issue' => $this->normalizer->normalize($socialAction->getIssue(), $format, $context), + 'ordering' => $socialAction->getOrdering(), ]; case 'docgen': diff --git a/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml b/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml index 44f69b4c2..0e825aa72 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml +++ b/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml @@ -114,3 +114,19 @@ household_composition: few {# enfants dans le ménage} other {# enfants dans le ménage} } + +periods: + show closed periods: >- + {nb_items, plural, + =0 {Aucun parcours clôturé} + one {Montrer un parcours clôturé} + many {Montrer # parcours clôturés} + other {Montrer # parcours clôturés} + } + hide closed periods: >- + {nb_items, plural, + =0 {Aucun parcours clôturé} + one {Masquer un parcours clôturé} + many {Masquer # parcours clôturés} + other {Masquer # parcours clôturés} + } diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 2a4077e1c..77ff2bdb2 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -214,7 +214,7 @@ No requestor: Pas de demandeur No resources: "Pas d'interlocuteurs privilégiés" Persons associated: Usagers concernés Referrer: Référent -Referrers: Référents +Referrers: Agents traitants Some peoples does not belong to any household currently. Add them to an household soon: Certaines personnes n'appartiennent à aucun ménage actuellement. Renseignez leur ménage dès que possible. Add to household now: Ajouter à un ménage Any resource for this accompanying course: Aucun interlocuteur privilégié pour ce parcours @@ -465,6 +465,9 @@ fix it: Compléter accompanying_course: administrative_location: Localisation administrative comment is pinned: Le commentaire est épinglé +show: Montrer +hide: Masquer +closed periods: parcours clôturer # Accompanying Course comments Accompanying Course Comment: Commentaire diff --git a/src/Bundle/ChillWopiBundle/src/DependencyInjection/ChillWopiExtension.php b/src/Bundle/ChillWopiBundle/src/DependencyInjection/ChillWopiExtension.php index 9e8596336..37a9e2fa6 100644 --- a/src/Bundle/ChillWopiBundle/src/DependencyInjection/ChillWopiExtension.php +++ b/src/Bundle/ChillWopiBundle/src/DependencyInjection/ChillWopiExtension.php @@ -11,14 +11,12 @@ declare(strict_types=1); namespace Chill\WopiBundle\DependencyInjection; -use Ramsey\Uuid\Doctrine\UuidType; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\HttpKernel\DependencyInjection\Extension; -final class ChillWopiExtension extends Extension implements PrependExtensionInterface +final class ChillWopiExtension extends Extension { public function load(array $configs, ContainerBuilder $container) { @@ -33,18 +31,4 @@ final class ChillWopiExtension extends Extension implements PrependExtensionInte $loader = new PhpFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); $loader->load('services.php'); } - - public function prepend(ContainerBuilder $container) - { - $container->prependExtensionConfig( - 'doctrine', - [ - 'dbal' => [ - 'types' => [ - 'uuid' => UuidType::class, - ], - ], - ] - ); - } } diff --git a/src/Bundle/ChillWopiBundle/src/Resources/config/routes/routes.php b/src/Bundle/ChillWopiBundle/src/Resources/config/routes/routes.php index 81ecb4e55..1dfb5e6af 100644 --- a/src/Bundle/ChillWopiBundle/src/Resources/config/routes/routes.php +++ b/src/Bundle/ChillWopiBundle/src/Resources/config/routes/routes.php @@ -9,11 +9,11 @@ declare(strict_types=1); -use Chill\WopiBundle\Controller\Test; +use Chill\WopiBundle\Controller\Editor; use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; return static function (RoutingConfigurator $routes) { $routes ->add('chill_wopi_file_edit', '/edit/{fileId}') - ->controller(Test::class); + ->controller(Editor::class); }; diff --git a/src/Bundle/ChillWopiBundle/src/Resources/public/page/editor/index.js b/src/Bundle/ChillWopiBundle/src/Resources/public/page/editor/index.js index 99b4ae707..e5fe20618 100644 --- a/src/Bundle/ChillWopiBundle/src/Resources/public/page/editor/index.js +++ b/src/Bundle/ChillWopiBundle/src/Resources/public/page/editor/index.js @@ -14,11 +14,13 @@ window.addEventListener('DOMContentLoaded', function(e) { office_frame.setAttribute('allowfullscreen', 'true'); // The sandbox attribute is needed to allow automatic redirection to the O365 sign-in page in the business user flow - office_frame.setAttribute('sandbox', 'allow-downloads allow-scripts allow-same-origin allow-forms allow-popups allow-top-navigation allow-popups-to-escape-sandbox'); + office_frame.setAttribute( + 'sandbox', + 'allow-downloads allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-top-navigation allow-popups-to-escape-sandbox' + ); frameholder.appendChild(office_frame); document.getElementById('office_form').submit(); - console.log(office_frame); const url = new URL(editor_url); const editor_domain = url.origin; diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php index 19ff3c62e..958f35b4e 100644 --- a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php +++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php @@ -11,12 +11,12 @@ declare(strict_types=1); namespace Chill\WopiBundle\Service\Wopi; -use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface; use ChampsLibres\WopiLib\Contract\Entity\Document; use ChampsLibres\WopiLib\Contract\Service\DocumentLockManagerInterface; use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface; use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Repository\StoredObjectRepository; +use Chill\DocStoreBundle\Service\StoredObjectManagerInterface; use DateTimeInterface; use Doctrine\ORM\EntityManagerInterface; use Error; @@ -28,8 +28,6 @@ use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Mime\MimeTypes; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; -use Symfony\Contracts\HttpClient\HttpClientInterface; -use Throwable; use function strlen; @@ -39,33 +37,29 @@ final class ChillDocumentManager implements DocumentManagerInterface private EntityManagerInterface $entityManager; - private HttpClientInterface $httpClient; - private Psr17Interface $psr17; private RequestInterface $request; - private StoredObjectRepository $storedObjectRepository; + private StoredObjectManagerInterface $storedObjectManager; - private TempUrlGeneratorInterface $tempUrlGenerator; + private StoredObjectRepository $storedObjectRepository; public function __construct( DocumentLockManagerInterface $documentLockManager, EntityManagerInterface $entityManager, - HttpClientInterface $httpClient, - Psr17Interface $psr17, - StoredObjectRepository $storedObjectRepository, - TempUrlGeneratorInterface $tempUrlGenerator, HttpMessageFactoryInterface $httpMessageFactory, - RequestStack $requestStack + Psr17Interface $psr17, + RequestStack $requestStack, + StoredObjectManagerInterface $storedObjectManager, + StoredObjectRepository $storedObjectRepository ) { + $this->documentLockManager = $documentLockManager; $this->entityManager = $entityManager; $this->psr17 = $psr17; - $this->storedObjectRepository = $storedObjectRepository; - $this->documentLockManager = $documentLockManager; - $this->tempUrlGenerator = $tempUrlGenerator; - $this->httpClient = $httpClient; $this->request = $httpMessageFactory->createRequest($requestStack->getCurrentRequest()); + $this->storedObjectManager = $storedObjectManager; + $this->storedObjectRepository = $storedObjectRepository; } public function create(array $data): Document @@ -197,18 +191,7 @@ final class ChillDocumentManager implements DocumentManagerInterface public function remove(Document $document): void { - $entityIsDeleted = false; - - try { - $this->entityManager->remove($document); - $entityIsDeleted = true; - } catch (Throwable $e) { - $entityIsDeleted = false; - } - - if (true === $entityIsDeleted) { - $this->deleteContent($document); - } + // TODO: To implement when we have a clearer view and API. } public function write(Document $document, array $properties = []): void @@ -216,42 +199,13 @@ final class ChillDocumentManager implements DocumentManagerInterface $this->setContent($document, $properties['content']); } - private function deleteContent(StoredObject $storedObject): void - { - /** @var StdClass $object */ - $object = $this->tempUrlGenerator->generate('DELETE', $storedObject->getFilename()); - - $response = $this->httpClient->request('DELETE', $object->url); - - if (200 !== $response->getStatusCode()) { - throw new Error('Unable to delete stored object.'); - } - } - private function getContent(StoredObject $storedObject): string { - /** @var StdClass $object */ - $object = $this->tempUrlGenerator->generate('GET', $storedObject->getFilename()); - - $response = $this->httpClient->request('GET', $object->url); - - if (200 !== $response->getStatusCode()) { - throw new Error('Unable to retrieve stored object.'); - } - - return $response->getContent(); + return $this->storedObjectManager->read($storedObject); } private function setContent(StoredObject $storedObject, string $content): void { - // TODO: Add strict typing in champs-libres/async-uploader-bundle - /** @var StdClass $object */ - $object = $this->tempUrlGenerator->generate('PUT', $storedObject->getFilename()); - - $response = $this->httpClient->request('PUT', $object->url, ['body' => $content]); - - if (201 !== $response->getStatusCode()) { - throw new Error('Unable to save stored object.'); - } + $this->storedObjectManager->write($storedObject, $content); } }