diff --git a/CHANGELOG.md b/CHANGELOG.md
index 64574ff5a..6a3d24a1d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,10 +12,18 @@ and this project adheres to
* [person] prevent duplicate relationship in filiation/household graph (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/560)
+* [Documents] Validate storedObject and allow for null data (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/565)
+
+## 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
-* [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
* [Documents] Change wording 'créer' to 'enregistrer' (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/634)
@@ -26,11 +34,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 +114,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 +142,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/phpstan-critical.neon b/phpstan-critical.neon
index 262d3012f..1dc516834 100644
--- a/phpstan-critical.neon
+++ b/phpstan-critical.neon
@@ -5,11 +5,6 @@ parameters:
count: 1
path: src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php
- -
- message: "#^Access to an undefined property Chill\\\\PersonBundle\\\\Entity\\\\Person\\:\\:\\$currentHouseholdParticipationAt\\.$#"
- count: 3
- path: src/Bundle/ChillPersonBundle/Entity/Person.php
-
-
message: "#^Access to an undefined property Chill\\\\PersonBundle\\\\Entity\\\\Household\\\\PersonHouseholdAddress\\:\\:\\$relation\\.$#"
count: 1
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index edec00554..3322f9539 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -3,6 +3,7 @@ parameters:
paths:
- src/
excludePaths:
+ - .php_cs*
- docs/
- src/Bundle/*/Tests/*
- src/Bundle/*/tests/*
diff --git a/src/Bundle/ChillActivityBundle/Entity/Activity.php b/src/Bundle/ChillActivityBundle/Entity/Activity.php
index 0a344236c..0c0632722 100644
--- a/src/Bundle/ChillActivityBundle/Entity/Activity.php
+++ b/src/Bundle/ChillActivityBundle/Entity/Activity.php
@@ -35,6 +35,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
+use Symfony\Component\Validator\Constraints as Assert;
/**
* Class Activity.
@@ -93,6 +94,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
/**
* @ORM\ManyToMany(targetEntity="Chill\DocStoreBundle\Entity\StoredObject", cascade={"persist"})
+ * @Assert\Valid(traverse=true)
*/
private Collection $documents;
@@ -231,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)) {
@@ -631,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 78387e7fe..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;
}
@@ -149,37 +149,37 @@ class StoredObject implements AsyncFileInterface, Document
return $this;
}
- public function setDatas(array $datas)
+ public function setDatas(?array $datas)
{
- $this->datas = $datas;
+ $this->datas = (array) $datas;
return $this;
}
- public function setFilename($filename)
+ public function setFilename(?string $filename)
{
- $this->filename = $filename;
+ $this->filename = (string) $filename;
return $this;
}
- public function setIv($iv)
+ public function setIv(?array $iv)
{
- $this->iv = $iv;
+ $this->iv = (array) $iv;
return $this;
}
- public function setKeyInfos($keyInfos)
+ public function setKeyInfos(?array $keyInfos)
{
- $this->keyInfos = $keyInfos;
+ $this->keyInfos = (array) $keyInfos;
return $this;
}
- public function setType($type)
+ public function setType(?string $type)
{
- $this->type = $type;
+ $this->type = (string) $type;
return $this;
}
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/ChillEventBundle/Controller/ParticipationController.php b/src/Bundle/ChillEventBundle/Controller/ParticipationController.php
index 63f40df32..1ef545616 100644
--- a/src/Bundle/ChillEventBundle/Controller/ParticipationController.php
+++ b/src/Bundle/ChillEventBundle/Controller/ParticipationController.php
@@ -235,7 +235,7 @@ class ParticipationController extends AbstractController
'The participation was created'
));
- if ($request->query->get('return_path')) {
+ if ($request->query->has('return_path')) {
return $this->redirect($request->query->get('return_path'));
}
@@ -716,7 +716,7 @@ class ParticipationController extends AbstractController
*/
protected function newSingle(Request $request)
{
- $returnPath = $request->query->get('return_path') ?
+ $returnPath = $request->query->has('return_path') ?
$request->query->get('return_path') : null;
$participation = $this->handleRequest($request, new Participation(), false);
diff --git a/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php b/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php
index a780af78a..9bf43b37e 100644
--- a/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php
+++ b/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php
@@ -16,9 +16,9 @@ use Chill\MainBundle\Serializer\Model\Collection;
use Exception;
use LogicException;
use RuntimeException;
-use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\SerializerInterface;
@@ -55,7 +55,7 @@ class ApiController extends AbstractCRUDController
return $this->entityDelete('_entity', $request, $id, $_format);
default:
- throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException('This method is not implemented');
+ throw new BadRequestHttpException('This method is not implemented');
}
}
@@ -120,7 +120,7 @@ class ApiController extends AbstractCRUDController
return $this->entityPostAction('_entity', $request, $_format);
default:
- throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException('This method is not implemented');
+ throw new BadRequestHttpException('This method is not implemented');
}
}
@@ -160,7 +160,7 @@ class ApiController extends AbstractCRUDController
try {
$entity = $this->deserialize($action, $request, $_format, $entity);
} catch (NotEncodableValueException $e) {
- throw new BadRequestException('invalid json', 400, $e);
+ throw new BadRequestHttpException('invalid json', 400, $e);
}
$errors = $this->validate($action, $request, $_format, $entity);
@@ -273,7 +273,7 @@ class ApiController extends AbstractCRUDController
try {
$postedData = $this->getSerializer()->deserialize($request->getContent(), $postedDataType, $_format, $postedDataContext);
} catch (\Symfony\Component\Serializer\Exception\UnexpectedValueException $e) {
- throw new BadRequestException(sprintf('Unable to deserialize posted ' .
+ throw new BadRequestHttpException(sprintf('Unable to deserialize posted ' .
'data: %s', $e->getMessage()), 0, $e);
}
@@ -290,7 +290,7 @@ class ApiController extends AbstractCRUDController
break;
default:
- throw new BadRequestException('this method is not supported');
+ throw new BadRequestHttpException('this method is not supported');
}
$errors = $this->validate($action, $request, $_format, $entity, [$postedData]);
@@ -408,7 +408,7 @@ class ApiController extends AbstractCRUDController
return $this->json($entity, Response::HTTP_OK, [], $context);
}
- throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException('This format is not implemented');
+ throw new BadRequestHttpException('This format is not implemented');
}
protected function entityPostAction($action, Request $request, string $_format): Response
@@ -418,7 +418,7 @@ class ApiController extends AbstractCRUDController
try {
$entity = $this->deserialize($action, $request, $_format, $entity);
} catch (NotEncodableValueException $e) {
- throw new BadRequestException('invalid json', 400, $e);
+ throw new BadRequestHttpException('invalid json', 400, $e);
}
$errors = $this->validate($action, $request, $_format, $entity);
diff --git a/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php b/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php
index 62d587ab3..edca07b41 100644
--- a/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php
+++ b/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php
@@ -25,6 +25,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
@@ -1140,6 +1141,6 @@ class CRUDController extends AbstractController
return $this->json($entity, Response::HTTP_OK, [], $context);
}
- throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException('This format is not implemented');
+ throw new BadRequestHttpException('This format is not implemented');
}
}
diff --git a/src/Bundle/ChillMainBundle/Controller/SearchController.php b/src/Bundle/ChillMainBundle/Controller/SearchController.php
index 41349336a..496538665 100644
--- a/src/Bundle/ChillMainBundle/Controller/SearchController.php
+++ b/src/Bundle/ChillMainBundle/Controller/SearchController.php
@@ -22,7 +22,6 @@ use Chill\MainBundle\Search\UnknowSearchNameException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
-use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
@@ -216,7 +215,7 @@ class SearchController extends AbstractController
$types = $request->query->get('type', []);
if (count($types) === 0) {
- throw new BadRequestException('The request must contains at '
+ throw new BadRequestHttpException('The request must contains at '
. ' one type');
}
diff --git a/src/Bundle/ChillMainBundle/Controller/WorkflowController.php b/src/Bundle/ChillMainBundle/Controller/WorkflowController.php
index 103de6d84..2803ff254 100644
--- a/src/Bundle/ChillMainBundle/Controller/WorkflowController.php
+++ b/src/Bundle/ChillMainBundle/Controller/WorkflowController.php
@@ -25,7 +25,6 @@ use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
-use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
@@ -144,7 +143,7 @@ class WorkflowController extends AbstractController
public function getAccessByAccessKey(EntityWorkflowStep $entityWorkflowStep, Request $request): Response
{
if (null === $accessKey = $request->query->get('accessKey', null)) {
- throw new BadRequestException('accessKey is missing');
+ throw new BadRequestHttpException('accessKey is missing');
}
if (!$this->getUser() instanceof User) {
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/Controller/AccompanyingCourseApiController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php
index f1306640b..21db6b00c 100644
--- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php
+++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php
@@ -33,10 +33,10 @@ use DateInterval;
use DateTimeImmutable;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
-use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Serializer\Exception\RuntimeException;
@@ -185,7 +185,7 @@ final class AccompanyingCourseApiController extends ApiController
->deserialize($request->getContent(), Person::class, $_format, []);
if (null === $person) {
- throw new BadRequestException('person id not found');
+ throw new BadRequestHttpException('person id not found');
}
// TODO add acl
@@ -204,7 +204,7 @@ final class AccompanyingCourseApiController extends ApiController
break;
default:
- throw new BadRequestException('This method is not supported');
+ throw new BadRequestHttpException('This method is not supported');
}
$errors = $this->validator->validate($accompanyingPeriod);
@@ -247,12 +247,12 @@ final class AccompanyingCourseApiController extends ApiController
}
if (null === $requestor) {
- throw new BadRequestException('Could not find any person or thirdparty', 0, null);
+ throw new BadRequestHttpException('Could not find any person or thirdparty', 0, null);
}
$accompanyingPeriod->setRequestor($requestor);
} else {
- throw new BadRequestException('method not supported');
+ throw new BadRequestHttpException('method not supported');
}
$errors = $this->validator->validate($accompanyingPeriod);
diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php
index b15f8adb9..4d78bb42b 100644
--- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php
+++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php
@@ -21,9 +21,9 @@ use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
-use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\ConstraintViolationInterface;
@@ -255,7 +255,7 @@ class AccompanyingCourseController extends Controller
$personIds = $request->query->get('person_id');
if (false === is_array($personIds)) {
- throw new BadRequestException('person_id parameter should be an array');
+ throw new BadRequestHttpException('person_id parameter should be an array');
}
foreach ($personIds as $personId) {
diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php
index 7b6dc201e..503867a3e 100644
--- a/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php
+++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdController.php
@@ -21,8 +21,8 @@ use Chill\PersonBundle\Security\Authorization\HouseholdVoter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormInterface;
-use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
@@ -202,7 +202,7 @@ class HouseholdController extends AbstractController
$this->denyAccessUnlessGranted(HouseholdVoter::EDIT, $household);
if (!$request->query->has('address_id')) {
- throw new BadRequestException('parameter address_id is missing');
+ throw new BadRequestHttpException('parameter address_id is missing');
}
$address_id = $request->query->getInt('address_id');
@@ -218,7 +218,7 @@ class HouseholdController extends AbstractController
}
if (null === $address) {
- throw new BadRequestException('The edited address does not belongs to the household');
+ throw new BadRequestHttpException('The edited address does not belongs to the household');
}
$form = $this->createForm(AddressDateType::class, $address, []);
diff --git a/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php b/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php
index 2ec72a786..cacba0630 100644
--- a/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php
+++ b/src/Bundle/ChillPersonBundle/Controller/HouseholdMemberController.php
@@ -20,9 +20,9 @@ use Chill\PersonBundle\Form\HouseholdMemberType;
use Chill\PersonBundle\Household\MembersEditor;
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
-use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Serializer\Exception;
@@ -105,7 +105,7 @@ class HouseholdMemberController extends ApiController
$ids = $request->query->get('persons', []);
if (0 === count($ids)) {
- throw new BadRequestException('parameters persons in query ' .
+ throw new BadRequestHttpException('parameters persons in query ' .
'is not an array or empty');
}
@@ -121,7 +121,8 @@ class HouseholdMemberController extends ApiController
}
}
- if ($householdId = $request->query->get('household', false)) {
+ if ($request->query->has('household')) {
+ $householdId = $request->query->get('household', false);
$household = $em->getRepository(Household::class)
->find($householdId);
$allowHouseholdCreate = false;
@@ -189,7 +190,7 @@ class HouseholdMemberController extends ApiController
['groups' => ['read']]
);
} catch (Exception\InvalidArgumentException|Exception\UnexpectedValueException $e) {
- throw new BadRequestException("Deserialization error: {$e->getMessage()}", 45896, $e);
+ throw new BadRequestHttpException("Deserialization error: {$e->getMessage()}", 45896, $e);
}
// TODO ACL
diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationDocument.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationDocument.php
index b31225df1..cf3fb8a9f 100644
--- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationDocument.php
+++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationDocument.php
@@ -20,6 +20,7 @@ use DateTimeInterface;
use Doctrine\ORM\Mapping as ORM;
use RuntimeException;
use Symfony\Component\Serializer\Annotation as Serializer;
+use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity
@@ -88,6 +89,7 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
* @Serializer\Groups({"read"})
* @Serializer\Groups({"write"})
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
+ * @Assert\Valid
*/
private ?StoredObject $storedObject = null;
diff --git a/src/Bundle/ChillPersonBundle/Entity/Person.php b/src/Bundle/ChillPersonBundle/Entity/Person.php
index ef522f068..7f2630a1e 100644
--- a/src/Bundle/ChillPersonBundle/Entity/Person.php
+++ b/src/Bundle/ChillPersonBundle/Entity/Person.php
@@ -240,6 +240,11 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
*/
private array $currentHouseholdAt = [];
+ /**
+ * Cache for the computation of current household participation.
+ */
+ private array $currentHouseholdParticipationAt = [];
+
/**
* The current person address.
*
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 @@
-
{{ $t('startdate.date') }}
-
@@ -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 %}
-