diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts index 17ac8879e..24f518ca3 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts @@ -55,11 +55,20 @@ export interface ServerExceptionInterface extends TransportExceptionInterface { body: string; } +export interface ConflictHttpExceptionInterface extends TransportExceptionInterface { + name: 'ConflictHttpException'; + violations: string[]; +} /** * Generic api method that can be adapted to any fetch request */ -export const makeFetch = (method: 'POST'|'GET'|'PUT'|'PATCH'|'DELETE', url: string, body?: body | Input | null, options?: FetchParams): Promise => { +export const makeFetch = ( + method: 'POST'|'GET'|'PUT'|'PATCH'|'DELETE', + url: string, body?: body | Input | null, + options?: FetchParams +): Promise => { + let opts = { method: method, headers: { @@ -67,6 +76,7 @@ export const makeFetch = (method: 'POST'|'GET'|'PUT'|'PATCH'|'DEL }, }; + if (body !== null && typeof body !== 'undefined') { Object.assign(opts, {body: JSON.stringify(body)}) } @@ -90,6 +100,10 @@ export const makeFetch = (method: 'POST'|'GET'|'PUT'|'PATCH'|'DEL throw AccessException(response); } + if (response.status === 409) { + throw ConflictHttpException(response); + } + throw { name: 'Exception', sta: response.status, @@ -220,3 +234,12 @@ const ServerException = (code: number, body: string): ServerExceptionInterface = return error; } + +const ConflictHttpException = (response: Response): ConflictHttpExceptionInterface => { + const error = {} as ConflictHttpExceptionInterface; + + error.name = 'ConflictHttpException'; + error.violations = ['Sorry, but someone else has already changed this entity. Please refresh the page and apply the changes again'] + + return error; +} diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkApiController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkApiController.php index 47e46cb6b..00d2f9026 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkApiController.php @@ -15,15 +15,23 @@ use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\Serializer\Model\Collection; use Chill\MainBundle\Serializer\Model\Counter; use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository; +use Doctrine\DBAL\LockMode; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\OptimisticLockException; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Contracts\Translation\TranslatorInterface; class AccompanyingCourseWorkApiController extends ApiController { - public function __construct(private readonly AccompanyingPeriodWorkRepository $accompanyingPeriodWorkRepository) - { - } + public function __construct( + private readonly AccompanyingPeriodWorkRepository $accompanyingPeriodWorkRepository, + private readonly EntityManagerInterface $em, + private readonly TranslatorInterface $translator, + ) {} /** * @Route("/api/1.0/person/accompanying-period/work/my-near-end") @@ -67,4 +75,18 @@ class AccompanyingCourseWorkApiController extends ApiController return parent::getContextForSerialization($action, $request, $_format, $entity); } + + public function entityPut($action, Request $request, $id, string $_format): Response + { + $entity = $this->accompanyingPeriodWorkRepository->findBy(['id' => $id])[0]; + $expectedVersion = $entity->getVersion(); + + try { + $this->em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion); + + return parent::entityPut($action, $request, $id, $_format); + } catch (OptimisticLockException) { + throw new ConflictHttpException($this->translator->trans('Sorry, but someone else has already changed this entity. Please refresh the page and apply the changes again')); + } + } } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php index be385c664..618918695 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php @@ -245,10 +245,12 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues private ?User $updatedBy = null; /** - * @ORM\Column(type="integer", nullable=false, options={"default": 1}) - * - * @Serializer\Groups({"read", "accompanying_period_work:edit"}) - */ + * @ORM\Column(type="integer", nullable=false, options={"default": 1}) + * + * @Serializer\Groups({"read", "accompanying_period_work:edit"}) + * + * @ORM\Version + */ private int $version = 1; public function __construct() diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue index dd4ddb664..e50b42c2c 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue @@ -599,7 +599,7 @@ export default { }, submit() { this.$store.dispatch('submit').catch((error) => { - if (error.name === 'ValidationException' || error.name === 'AccessException') { + if (error.name === 'ValidationException' || error.name === 'AccessException' || error.name === 'ConflictHttpException') { error.violations.forEach((violation) => this.$toast.open({message: violation})); } else { this.$toast.open({message: 'An error occurred'}); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js index 5175ba70c..bec89d088 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js @@ -75,6 +75,7 @@ const store = createStore({ return { type: 'accompanying_period_work', id: state.work.id, + version: state.version, startDate: state.startDate === null || state.startDate === '' ? null : { datetime: datetimeToISO(ISOToDate(state.startDate)) }, @@ -505,19 +506,22 @@ const store = createStore({ url = `/api/1.0/person/accompanying-course/work/${state.work.id}.json`, errors = [] ; + commit('setIsPosting', true); - console.log('the social action', payload); + // console.log('the social action', payload); return makeFetch('PUT', url, payload) .then(data => { if (typeof(callback) !== 'undefined') { return callback(data); } else { - console.info('nothing to do here, bye bye'); + // console.log('payload', payload.privateComment) + // console.info('nothing to do here, bye bye'); window.location.assign(`/fr/person/accompanying-period/${state.work.accompanyingPeriod.id}/work`); } }).catch(error => { + console.log('error', error) commit('setIsPosting', false); throw error; }); diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20231129113816.php b/src/Bundle/ChillPersonBundle/migrations/Version20231129113816.php index 51731db8a..20bc3b446 100644 --- a/src/Bundle/ChillPersonBundle/migrations/Version20231129113816.php +++ b/src/Bundle/ChillPersonBundle/migrations/Version20231129113816.php @@ -2,6 +2,13 @@ declare(strict_types=1); +/* + * Chill is a software for social workers + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Chill\Migrations\Person; use Doctrine\DBAL\Schema\Schema; diff --git a/src/Bundle/ChillPersonBundle/translations/validators.fr.yml b/src/Bundle/ChillPersonBundle/translations/validators.fr.yml index f005755b1..691fef833 100644 --- a/src/Bundle/ChillPersonBundle/translations/validators.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/validators.fr.yml @@ -21,6 +21,7 @@ The firstname cannot be empty: Le prénom ne peut pas être vide The lastname cannot be empty: Le nom de famille ne peut pas être vide The gender must be set: Le genre doit être renseigné You are not allowed to perform this action: Vous n'avez pas le droit de changer cette valeur. +Sorry, but someone else has already changed this entity. Please refresh the page and apply the changes again: Désolé, mais quelqu'un d'autre a déjà modifié cette entité. Veuillez actualiser la page et appliquer à nouveau les modifications A center is required: Un centre est requis