diff --git a/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php b/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php index 9686a7b98..cab0bcdcb 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php +++ b/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php @@ -359,9 +359,10 @@ class ApiController extends AbstractCRUDController * 6. validate the base entity (not the deserialized one). Groups are fetched from getValidationGroups, validation is perform by `validate` * 7. run onAfterValidation * 8. if errors, return a 422 response with errors - * 9. flush the data - * 10. run onAfterFlush - * 11. return a 202 response for DELETE with empty body, or HTTP 200 for post with serialized posted entity + * 9. if $forcePersist === true, persist the entity + * 10. flush the data + * 11. run onAfterFlush + * 12. return a 202 response for DELETE with empty body, or HTTP 200 for post with serialized posted entity * * @param string action * @param mixed id @@ -370,11 +371,12 @@ class ApiController extends AbstractCRUDController * @param string $property the name of the property. This will be used to make a `add+$property` and `remove+$property` method * @param string $postedDataType the type of the posted data (the content) * @param string $postedDataContext a context to deserialize posted data (the content) + * @param bool $forcePersist force to persist the created element (only for POST request) * @throw BadRequestException if unable to deserialize the posted data * @throw BadRequestException if the method is not POST or DELETE * */ - protected function addRemoveSomething(string $action, $id, Request $request, string $_format, string $property, string $postedDataType, $postedDataContext = []): Response + protected function addRemoveSomething(string $action, $id, Request $request, string $_format, string $property, string $postedDataType, array $postedDataContext = [], bool $forcePersist = false): Response { $entity = $this->getEntity($action, $id, $request); @@ -429,6 +431,10 @@ class ApiController extends AbstractCRUDController return $this->json($errors, 422); } + if ($forcePersist && $request->getMethod() === Request::METHOD_POST) { + $this->getDoctrine()->getManager()->persist($postedData); + } + $this->getDoctrine()->getManager()->flush(); diff --git a/src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php b/src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php index 9d61d6233..d7e19ea9c 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php +++ b/src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php @@ -146,7 +146,7 @@ class CRUDRoutesLoader extends Loader foreach ($crudConfig['actions'] as $name => $action) { // filter only on single actions - $singleCollection = $action['single-collection'] ?? $name === '_entity' ? 'single' : NULL; + $singleCollection = $action['single_collection'] ?? $name === '_entity' ? 'single' : NULL; if ('collection' === $singleCollection) { // continue; } @@ -171,7 +171,7 @@ class CRUDRoutesLoader extends Loader // path are rewritten // if name === 'default', we rewrite it to nothing :-) $localName = \in_array($name, [ '_entity', '_index' ]) ? '' : '/'.$name; - if ('collection' === $action['single-collection'] || '_index' === $name) { + if ('collection' === $action['single_collection'] || '_index' === $name) { $localPath = $action['path'] ?? $localName.'.{_format}'; } else { $localPath = $action['path'] ?? '/{id}'.$localName.'.{_format}'; diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php b/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php index 4c90aaabb..c711f911f 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php @@ -205,7 +205,7 @@ class Configuration implements ConfigurationInterface ->ignoreExtraKeys(false) ->info('the requirements for the route. Will be set to `[ \'id\' => \'\d+\' ]` if left empty.') ->end() - ->enumNode('single-collection') + ->enumNode('single_collection') ->values(['single', 'collection']) ->defaultValue('single') ->info('indicates if the returned object is a single element or a collection. '. diff --git a/src/Bundle/ChillMainBundle/Resources/public/js/date.js b/src/Bundle/ChillMainBundle/Resources/public/js/date.js index 7b9bf88a2..e49a05972 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/js/date.js +++ b/src/Bundle/ChillMainBundle/Resources/public/js/date.js @@ -11,13 +11,16 @@ * * Do not take time into account * - * **Experimental** */ const dateToISO = (date) => { + if (null === date) { + return null; + } + 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') + date.getFullYear(), + (date.getMonth() + 1).toString().padStart(2, '0'), + date.getDate().toString().padStart(2, '0') ].join('-'); }; @@ -36,10 +39,12 @@ const ISOToDate = (str) => { /** * Return a date object from iso string formatted as YYYY-mm-dd:HH:MM:ss+01:00 * - * **Experimental** */ const ISOToDatetime = (str) => { - console.log(str); + if (null === str) { + return null; + } + let [cal, times] = str.split('T'), [year, month, date] = cal.split('-'), diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/CollectionNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/CollectionNormalizer.php index 1ba95d924..d3f513c54 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/CollectionNormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/CollectionNormalizer.php @@ -19,7 +19,6 @@ class CollectionNormalizer implements NormalizerInterface, NormalizerAwareInterf public function normalize($collection, string $format = null, array $context = []) { /** @var $collection Collection */ - /** @var $collection Chill\MainBundle\Pagination\PaginatorInterface */ $paginator = $collection->getPaginator(); $data['count'] = $paginator->getTotalItems(); diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php index 860cdfad7..8201752af 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php @@ -6,6 +6,7 @@ use Chill\MainBundle\CRUD\Controller\ApiController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; @@ -116,6 +117,20 @@ $workflow = $this->registry->get($accompanyingPeriod); return $this->addRemoveSomething('socialissue', $id, $request, $_format, 'socialIssue', SocialIssue::class, [ 'groups' => [ 'read' ] ]); } + public function workApi($id, Request $request, string $_format): Response + { + return $this->addRemoveSomething( + 'work', + $id, + $request, + $_format, + 'work', + AccompanyingPeriodWork::class, + [ 'groups' => [ 'accompanying_period_work:create' ] ], + true // force persist + ); + } + public function requestorApi($id, Request $request, string $_format): Response { /** @var AccompanyingPeriod $accompanyingPeriod */ diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkController.php new file mode 100644 index 000000000..1a5ab3925 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkController.php @@ -0,0 +1,52 @@ +trans = $trans; + $this->serializer = $serializer; + } + + /** + * @Route( + * "{_locale}/person/accompanying-period/{id}/work/new", + * methods={"GET", "POST"} + * ) + */ + public function createWork(AccompanyingPeriod $period) + { + // TODO ACL + + if ($period->getSocialIssues()->count() === 0) { + $this->addFlash('error', $this->trans->trans( + "accompanying_work.You must add at least ". + "one social issue on accompanying period") + ); + + return $this->redirectToRoute('chill_person_accompanying_course_index', [ + 'accompanying_period_id' => $period->getId() + ]); + } + + $json = $this->serializer->normalize($period, 'json', [ "groups" => [ "read" ]]); + + return $this->render('@ChillPerson/AccompanyingCourseWork/create.html.twig', [ + 'accompanyingCourse' => $period, + 'json' => $json + ]); + } +} diff --git a/src/Bundle/ChillPersonBundle/Controller/SocialWorkSocialActionApiController.php b/src/Bundle/ChillPersonBundle/Controller/SocialWorkSocialActionApiController.php new file mode 100644 index 000000000..de87b380a --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Controller/SocialWorkSocialActionApiController.php @@ -0,0 +1,47 @@ +socialIssueRepository = $socialIssueRepository; + $this->paginator = $paginator; + } + + public function listBySocialIssueApi($id, Request $request) + { + $socialIssue = $this->socialIssueRepository + ->find($id); + + if (NULL === $socialIssue) { + throw $this->createNotFoundException("socialIssue not found"); + } + + $socialActions = $socialIssue->getRecursiveSocialActions(); + $pagination = $this->paginator->create(count($socialActions)); + // max one page + $pagination->setItemsPerPage(count($socialActions)); + + $collection = new Collection($socialActions, $pagination); + + + return $this->json($collection, JsonResponse::HTTP_OK, [], [ "groups" => [ "read" ]]); + } + +} diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php index 1c62f361f..79dae3255 100644 --- a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php +++ b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php @@ -438,6 +438,19 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac Request::METHOD_DELETE=> \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE ] ], + 'work' => [ + 'methods' => [ + Request::METHOD_POST => true, + Request::METHOD_DELETE => false, + Request::METHOD_GET => false, + Request::METHOD_HEAD => false, + ], + 'controller_action' => 'workApi', + 'roles' => [ + Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, + Request::METHOD_DELETE => 'ALWAYS_FAILS', + ] + ], 'confirm' => [ 'methods' => [ @@ -573,6 +586,49 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac ], ] ], + [ + 'class' => \Chill\PersonBundle\Entity\SocialWork\SocialAction::class, + 'name' => 'social_action', + 'base_path' => '/api/1.0/person/social/social-action', + 'controller' => \Chill\PersonBundle\Controller\SocialWorkSocialActionApiController::class, + // TODO: acl + 'base_role' => 'ROLE_USER', + 'actions' => [ + '_entity' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true, + ], + 'roles' => [ + Request::METHOD_GET => 'ROLE_USER', + Request::METHOD_HEAD => 'ROLE_USER', + ] + ], + '_index' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true, + ], + 'roles' => [ + Request::METHOD_GET => 'ROLE_USER', + Request::METHOD_HEAD => 'ROLE_USER', + ] + ], + 'listBySocialIssue' => [ + 'single-collection' => 'collection', + 'path' => '/by-social-issue/{id}.{_format}', + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true, + ], + 'roles' => [ + Request::METHOD_GET => 'ROLE_USER', + Request::METHOD_HEAD => 'ROLE_USER', + ] + + ] + ] + ], ] ]); } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index 84f0c264b..47bb3c953 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -25,6 +25,7 @@ namespace Chill\PersonBundle\Entity; use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; use Chill\MainBundle\Entity\Scope; +use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive; use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment; use Chill\PersonBundle\Entity\AccompanyingPeriod\Origin; @@ -39,6 +40,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; use Chill\MainBundle\Entity\User; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Symfony\Component\Validator\Constraints as Assert; /** * AccompanyingPeriod Class @@ -280,6 +282,15 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface */ private \DateTimeInterface $updatedAt; + /** + * @ORM\OneToMany( + * targetEntity=AccompanyingPeriodWork::class, + * mappedBy="accompanyingPeriod" + * ) + * @Assert\Valid(traverse=true) + */ + private Collection $works; + /** * AccompanyingPeriod constructor. * @@ -292,6 +303,7 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface $this->scopes = new ArrayCollection(); $this->socialIssues = new ArrayCollection(); $this->comments = new ArrayCollection(); + $this->works = new ArrayCollection(); } /** @@ -896,4 +908,28 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface return $this; } + + /** + * @return AccompanyingPeriodWork[] + */ + public function getWorks(): Collection + { + return $this->works; + } + + public function addWork(AccompanyingPeriodWork $work): self + { + $this->works[] = $work; + $work->setAccompanyingPeriod($this); + + return $this; + } + + public function removeWork(AccompanyingPeriodWork $work): self + { + $this->work->removeElement($work); + $work->setAccompanyingPeriod(null); + + return $this; + } } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php index 25fbcc342..11570945e 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php @@ -7,86 +7,123 @@ use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\ThirdPartyBundle\Entity\ThirdParty; +use DateTimeInterface; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation as Serializer; +use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; +use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; +use Symfony\Component\Validator\Constraints as Assert; /** * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period_work") + * @Serializer\DiscriminatorMap( + * typeProperty="type", + * mapping={ + * "accompanying_period_work":AccompanyingPeriodWork::class + * } + * ) */ - class AccompanyingPeriodWork + class AccompanyingPeriodWork implements TrackCreationInterface, TrackUpdateInterface { /** * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") + * @Serializer\Groups({"read"}) */ - private $id; + private ?int $id; /** * @ORM\Column(type="text") + * @Serializer\Groups({"read"}) */ - private $note; + private string $note = ""; /** * @ORM\ManyToOne(targetEntity=AccompanyingPeriod::class) + * @Serializer\Groups({"read"}) */ - private $accompanyingPeriod; + private ?AccompanyingPeriod $accompanyingPeriod = null; /** * @ORM\ManyToOne(targetEntity=SocialAction::class) + * @Serializer\Groups({"accompanying_period_work:create", "read"}) */ - private $socialAction; + private ?SocialAction $socialAction = null; /** - * @ORM\Column(type="datetime") + * @ORM\Column(type="datetime_immutable") + * @Serializer\Groups({"read"}) */ - private $createdAt; + private ?\DateTimeImmutable $createdAt = null; /** * @ORM\ManyToOne(targetEntity=User::class) * @ORM\JoinColumn(nullable=false) + * @Serializer\Groups({"read"}) */ - private $createdBy; + private ?User $createdBy = null; /** - * @ORM\Column(type="datetime") + * @ORM\Column(type="datetime_immutable") + * @Serializer\Groups({"read"}) */ - private $startDate; + private ?\DateTimeImmutable $updatedAt = null; /** - * @ORM\Column(type="datetime") + * @ORM\ManyToOne(targetEntity=User::class) + * @ORM\JoinColumn(nullable=false) + * @Serializer\Groups({"read"}) */ - private $endDate; + private ?User $updatedBy = null; + + /** + * @ORM\Column(type="date_immutable") + * @Serializer\Groups({"accompanying_period_work:create"}) + * @Serializer\Groups({"read"}) + */ + private \DateTimeImmutable $startDate; + + /** + * @ORM\Column(type="date_immutable", nullable=true, options={"default":null}) + * @Serializer\Groups({"accompanying_period_work:create", "read"}) + * @Assert\GreaterThan(propertyPath="startDate", + * message="accompanying_course_work.The endDate should be greater than the start date" + * ) + */ + private ?\DateTimeImmutable $endDate = null; /** * @ORM\ManyToOne(targetEntity=ThirdParty::class) + * @Serializer\Groups({"read"}) * * In schema : traitant */ - private $handlingThierParty; + private ?ThirdParty $handlingThierParty = null; /** * @ORM\Column(type="boolean") */ - private $createdAutomatically; + private bool $createdAutomatically = false; /** * @ORM\Column(type="text") */ - private $createdAutomaticallyReason; + private string $createdAutomaticallyReason = ""; /** * @ORM\OneToMany(targetEntity=AccompanyingPeriodWorkGoal::class, mappedBy="accompanyingPeriodWork") */ - private $goals; + private Collection $goals; /** * @ORM\ManyToMany(targetEntity=Result::class, inversedBy="accompanyingPeriodWorks") * @ORM\JoinTable(name="chill_person_accompanying_period_work_result") */ - private $results; + private Collection $results; /** * @ORM\ManyToMany(targetEntity=ThirdParty::class) @@ -94,7 +131,7 @@ use Doctrine\ORM\Mapping as ORM; * * In schema : intervenants */ - private $thirdParties; + private Collection $thirdParties; public function __construct() { @@ -125,8 +162,17 @@ use Doctrine\ORM\Mapping as ORM; return $this->accompanyingPeriod; } + /** + * Internal: you should use `$accompanyingPeriod->removeWork($work);` or + * `$accompanyingPeriod->addWork($work);` + */ public function setAccompanyingPeriod(?AccompanyingPeriod $accompanyingPeriod): self { + if ($this->accompanyingPeriod instanceof AccompanyingPeriod && + $accompanyingPeriod !== $this->accompanyingPeriod) { + throw new \LogicException("A work cannot change accompanyingPeriod"); + } + $this->accompanyingPeriod = $accompanyingPeriod; return $this; @@ -144,7 +190,7 @@ use Doctrine\ORM\Mapping as ORM; return $this; } - public function getCreatedAt(): ?\DateTimeInterface + public function getCreatedAt(): ?\DateTimeImmutable { return $this->createdAt; } @@ -168,6 +214,30 @@ use Doctrine\ORM\Mapping as ORM; return $this; } + public function setUpdatedBy(User $user): TrackUpdateInterface + { + $this->updatedBy = $user; + + return $this; + } + + public function setUpdatedAt(DateTimeInterface $datetime): TrackUpdateInterface + { + $this->updatedAt = $datetime; + + return $this; + } + + public function getUpdatedAt(): ?\DateTimeImmutable + { + return $this->updatedAt; + } + + public function getUpdatedBy(): ?User + { + return $this->updatedBy; + } + public function getStartDate(): ?\DateTimeInterface { return $this->startDate; diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php index 3f5eb14ed..35abe392f 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php @@ -5,10 +5,17 @@ namespace Chill\PersonBundle\Entity\SocialWork; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation as Serializer; /** * @ORM\Entity * @ORM\Table(name="chill_person_social_action") + * @Serializer\DiscriminatorMap( + * typeProperty="type", + * mapping={ + * "social_work_social_action":SocialAction::class + * } + * ) */ class SocialAction { diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php index 1100d8136..4dc5ecc67 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php @@ -6,8 +6,9 @@ use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use Doctrine\Persistence\ObjectRepository; -final class SocialIssueRepository +final class SocialIssueRepository implements ObjectRepository { private EntityRepository $repository; @@ -15,4 +16,44 @@ final class SocialIssueRepository { $this->repository = $entityManager->getRepository(SocialIssue::class); } + + /** + * {@inheritDoc} + */ + public function find($id) + { + return $this->repository->find($id); + } + + /** + * {@inheritDoc} + */ + public function findAll() + { + return $this->repository->findAll(); + } + + /** + * {@inheritDoc} + */ + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null) + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + /** + * {@inheritDoc} + */ + public function findOneBy(array $criteria) + { + return $this->findOneBy($criteria); + } + + /** + * {@inheritDoc} + */ + public function getClassName() + { + return SocialIssue::class; + } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue new file mode 100644 index 000000000..3c1f4b07a --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/index.js new file mode 100644 index 000000000..7e54e2b0b --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/index.js @@ -0,0 +1,15 @@ +import { createApp } from 'vue'; +import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'; +import { store } from './store'; +import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n' +import App from './App.vue'; + +const i18n = _createI18n(personMessages); + +const app = createApp({ + template: ``, +}) +.use(store) +.use(i18n) +.component('app', App) +.mount('#accompanying_course_work_create'); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js new file mode 100644 index 000000000..040af5097 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/store.js @@ -0,0 +1,155 @@ + +import { createStore } from 'vuex'; +import { datetimeToISO } from 'ChillMainAssets/js/date.js'; +import { findSocialActionsBySocialIssue } from 'ChillPersonAssets/vuejs/_api/SocialWorkSocialAction.js'; +import { create } from 'ChillPersonAssets/vuejs/_api/AccompanyingCourseWork.js'; + +const debug = process.env.NODE_ENV !== 'production'; + +const store = createStore({ + strict: debug, + state: { + accompanyingCourse: window.accompanyingCourse, + socialIssues: window.accompanyingCourse.socialIssues, + socialIssuePicked: null, + socialActionsReachables: [], + socialActionPicked: null, + startDate: new Date(), + endDate: null, + isLoadingSocialActions: false, + isPostingWork: false, + errors: [], + }, + getters: { + hasSocialActionPicked(state) { + console.log(state.socialActionPicked); + return null !== state.socialActionPicked; + }, + hasSocialIssuePicked(state) { + console.log(state.socialIssuePicked); + return null !== state.socialIssuePicked; + }, + isLoadingSocialActions(state) { + return state.isLoadingSocialActions; + }, + isPostingWork(state) { + return state.isPostingWork; + }, + buildPayloadCreate(state) { + let payload = { + type: 'accompanying_period_work', + social_action: { + type: 'social_work_social_action', + id: state.socialActionPicked.id + }, + startDate: { + datetime: datetimeToISO(state.startDate) + } + }; + + if (null !== state.endDate) { + payload.endDate = { + datetime: datetimeToISO(state.endDate) + }; + } + + return payload; + }, + hasErrors(state) { + return state.errors.length > 0; + }, + }, + mutations: { + setSocialActionsReachables(state, actions) { + console.log('set social action reachables'); + console.log(actions); + + state.socialActionsReachables = actions; + }, + setSocialAction(state, socialAction) { + console.log('socialAction', socialAction); + state.socialActionPicked = socialAction; + }, + setSocialIssue(state, socialIssueId) { + if (socialIssueId === null) { + state.socialIssuePicked = null; + return; + } + state.socialIssuePicked = state.socialIssues + .find(e => e.id === socialIssueId); + }, + setIsLoadingSocialActions(state, s) { + state.isLoadingSocialActions = s; + }, + setPostingWork(state) { + state.isPostingWork = true; + }, + setStartDate(state, date) { + state.startDate = date; + }, + setEndDate(state, date) { + state.endDate = date; + }, + addErrors(state, { errors, cancel_posting }) { + console.log('add errors', errors); + state.errors = errors; + if (cancel_posting) { + state.isPostingWork = false; + } + }, + }, + actions: { + pickSocialIssue({ commit }, socialIssueId) { + console.log('pick social issue'); + + commit('setIsLoadingSocialActions', true); + commit('setSocialIssue', null); + commit('setSocialActionsReachables', []); + + findSocialActionsBySocialIssue(socialIssueId).then( + (response) => { + console.log(response); + console.log(response.results); + commit('setSocialIssue', socialIssueId); + commit('setSocialActionsReachables', response.results); + commit('setIsLoadingSocialActions', false); + }) + .catch(err => { + console.error(err); + }); + }, + submit({ commit, getters, state }) { + console.log('submit'); + let + payload = getters.buildPayloadCreate, + errors = []; + + commit('setPostingWork'); + + create(state.accompanyingCourse.id, payload) + .then( ({status, data}) => { + console.log('created return', { status, data}); + if (status === 200) { + console.log('created, nothing to do here any more. Bye-bye!'); + window.location.assign(`/fr/person/accompanying-period/work/${data.id}/edit`); + } else if (status === 422) { + console.log(data); + for (let i in data.violations) { + console.log(i); + console.log(data.violations[i].title); + errors.push(data.violations[i].title); + } + console.log('errors after reseponse handling', errors); + console.log({errors, cancel_posting: true}); + commit('addErrors', { errors, cancel_posting: true }); + } + }); + + + + }, + }, + +}); + +export { store }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AccompanyingCourseWork.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AccompanyingCourseWork.js new file mode 100644 index 000000000..7c73d01f8 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AccompanyingCourseWork.js @@ -0,0 +1,30 @@ +const create = (accompanying_period_id, payload) => { + const url = `/api/1.0/person/accompanying-course/${accompanying_period_id}/work.json`; + let status; + console.log('create', payload); + + return fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }) + .then(response => { + if (response.ok || response.status === 422) { + status = response.status; + + return response.json(); + } + + throw new Error("Error while retrieving social actions: " + response.status + + " " + response.statusText); + }) + .then(data => { + return new Promise((resolve, reject) => { + resolve({ status, data }); + }); + }); +}; + +export { create }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/SocialWorkSocialAction.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/SocialWorkSocialAction.js new file mode 100644 index 000000000..9a012664c --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/SocialWorkSocialAction.js @@ -0,0 +1,21 @@ + +const findSocialActionsBySocialIssue = (id) => { + const url = `/api/1.0/person/social/social-action/by-social-issue/${id}.json`; + + return fetch(url) + .then(response => { + if (!response.ok) { + throw new Error("Error while retrieving social actions " + response.status + + " " + response.statusText); + } + return response.json(); + }) + .catch(err => { + throw err + }) + ; +}; + +export { + findSocialActionsBySocialIssue +}; diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/create.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/create.html.twig new file mode 100644 index 000000000..d19c1f93f --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/create.html.twig @@ -0,0 +1,24 @@ +{% extends '@ChillPerson/AccompanyingCourse/layout.html.twig' %} + +{% block title 'accompanying_course_work.Create accompanying course work'|trans %} + + +{% block content %} +

{{ block('title') }}

+ +
+ +{% endblock %} + +{% block js %} + + + {{ encore_entry_script_tags('accompanying_course_work_create') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('accompanying_course_work_create') }} +{% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialActionNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialActionNormalizer.php new file mode 100644 index 000000000..1ced09a1e --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/SocialActionNormalizer.php @@ -0,0 +1,45 @@ +render = $render; + } + + /** + * {@inheritDoc} + */ + public function normalize($socialAction, string $format = null, array $context = []) + { + return [ + 'id' => $socialAction->getId(), + 'type' => 'social_work_social_action', + 'text' => $this->render->renderString($socialAction, []), + 'parent' => $this->normalizer->normalize($socialAction->getParent()), + 'desactivationDate' => $this->normalizer->normalize($socialAction->getDesactivationDate()), + 'title' => $socialAction->getTitle() + ]; + } + + /** + * {@inheritDoc} + */ + public function supportsNormalization($data, string $format = null) + { + return $data instanceof SocialAction; + } +} diff --git a/src/Bundle/ChillPersonBundle/chill.api.specs.yaml b/src/Bundle/ChillPersonBundle/chill.api.specs.yaml index 58618b15a..4f2ed3dd1 100644 --- a/src/Bundle/ChillPersonBundle/chill.api.specs.yaml +++ b/src/Bundle/ChillPersonBundle/chill.api.specs.yaml @@ -798,6 +798,55 @@ paths: 422: description: "object with validation errors" + /1.0/person/accompanying-course/{id}/work.json: + post: + tags: + - person + summary: "Add a work (AccompanyingPeriodwork) to the accompanying course" + parameters: + - name: id + in: path + required: true + description: The accompanying period's id + schema: + type: integer + format: integer + minimum: 1 + requestBody: + description: "A new work" + required: true + content: + application/json: + schema: + type: object + properties: + type: + type: string + enum: + - 'accompanying_period_work' + startDate: + $ref: '#/components/schemas/Date' + endDate: + $ref: '#/components/schemas/Date' + examples: + create a work: + value: + type: accompanying_period_work + social_action: + id: 0 + type: social_work_social_action + startDate: + datetime: 2021-06-20T15:00:00+0200 + responses: + 401: + description: "Unauthorized" + 404: + description: "Not found" + 200: + description: "OK" + 422: + description: "object with validation errors" + /1.0/person/accompanying-course/{id}/confirm.json: post: tags: diff --git a/src/Bundle/ChillPersonBundle/chill.webpack.config.js b/src/Bundle/ChillPersonBundle/chill.webpack.config.js index d70796fe5..b20aa004e 100644 --- a/src/Bundle/ChillPersonBundle/chill.webpack.config.js +++ b/src/Bundle/ChillPersonBundle/chill.webpack.config.js @@ -13,4 +13,5 @@ module.exports = function(encore, entries) encore.addEntry('household_members_editor', __dirname + '/Resources/public/vuejs/HouseholdMembersEditor/index.js'); encore.addEntry('vue_accourse', __dirname + '/Resources/public/vuejs/AccompanyingCourse/index.js'); encore.addEntry('household_edit_metadata', __dirname + '/Resources/public/modules/household_edit_metadata/index.js'); + encore.addEntry('accompanying_course_work_create', __dirname + '/Resources/public/vuejs/AccompanyingCourseWorkCreate/index.js'); }; diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20210620143757.php b/src/Bundle/ChillPersonBundle/migrations/Version20210620143757.php new file mode 100644 index 000000000..17d1e31aa --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20210620143757.php @@ -0,0 +1,51 @@ +addSql('ALTER TABLE chill_person_accompanying_period_work ADD updatedAt TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_work ADD updatedBy_id INT NOT NULL'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_work ALTER createdat TYPE TIMESTAMP(0) WITHOUT TIME ZONE'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_work ALTER createdat DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_work ALTER startdate TYPE DATE'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_work ALTER startdate DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_work ALTER enddate TYPE DATE'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_work ALTER enddate DROP NOT NULL'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_work ALTER enddate SET DEFAULT NULL'); + $this->addSql('COMMENT ON COLUMN chill_person_accompanying_period_work.updatedAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_person_accompanying_period_work.createdAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_person_accompanying_period_work.startDate IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_person_accompanying_period_work.endDate IS \'(DC2Type:datetime_immutable)\''); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_person_accompanying_period_work DROP updatedAt'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_work DROP updatedBy_id'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_work ALTER createdAt TYPE TIMESTAMP(0) WITHOUT TIME ZONE'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_work ALTER createdAt DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_work ALTER startDate TYPE TIMESTAMP(0) WITHOUT TIME ZONE'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_work ALTER startDate DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_work ALTER endDate TYPE TIMESTAMP(0) WITHOUT TIME ZONE'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_work ALTER endDate DROP DEFAULT'); + $this->addSql('COMMENT ON COLUMN chill_person_accompanying_period_work.createdat IS NULL'); + $this->addSql('COMMENT ON COLUMN chill_person_accompanying_period_work.startdate IS NULL'); + $this->addSql('COMMENT ON COLUMN chill_person_accompanying_period_work.enddate IS NULL'); + } +} diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index c258ca807..8bba291cd 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -328,3 +328,8 @@ Members: Membres Addresses: Adresses Move household: Nouveau déménagement Addresses history for household: Historique des adresses + +# accompanying course work +accompanying_course_work: + Create accompanying course work: Créer une action d'accompagnement +