Merge add_toasters: handle 422 method and add toaster into course editor

See https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/221

Squashed commit of the following:

commit 4f68f83aba74a88898779037aeb24d45c622759e
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Nov 25 12:07:37 2021 +0100

    scope in course editor: show toast when error, instead of restoring the
    previous state

commit fdca8c1c87a4972bb6107bb16ab76844a28cae72
Merge: 53d6e68f8 b97f49782
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Nov 24 17:39:27 2021 +0100

    Merge remote-tracking branch 'origin/add_toasters' into add_toasters

commit 53d6e68f8c5e158ac1848403d18146690ffeacf2
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Nov 24 17:38:52 2021 +0100

    better validation messages

commit b97f497822fa6f6057e3b1cf3697044192ea9052
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Wed Nov 24 17:36:59 2021 +0100

    methods added to api.js again so they can be imported in other bundles still. To be refactored later with general makeFetch method?

commit e6089f75b4f66669cbd84f496c3b867901de9c8c
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Nov 24 17:20:05 2021 +0100

    fix validator for participation overlaps

commit e47e6741b9f179dae63f39a596b7ba7410b3ac88
Merge: 25a80fcb2 05385509e
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Nov 24 16:31:50 2021 +0100

    Merge remote-tracking branch 'origin/master' into add_toasters

commit 25a80fcb26cfb3ab7afab0abc1fcb6c0d0af7a6d
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Tue Nov 23 18:22:35 2021 +0100

    error object/message changed if not 422 or 403

commit 0c602cee8c127ab7f0723819a691a98c4e0615e2
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Tue Nov 23 17:13:47 2021 +0100

    Toasts displayed with differentiation between different errors

    error objects created accordingly

commit 252e57a06da1101e4ebc025974b247c472b8c2ed
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Tue Nov 23 17:12:55 2021 +0100

    generic makeFetch implemented almost everywhere

    api.js file no longer needs to contain a separate function to make an api call.

commit 8532eeee7656f6f9761168fb5e2102778ee932b7
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Tue Nov 23 17:10:48 2021 +0100

    Moved generic api methods into one file apiMethods.js

     imports adapted throughout bundles

commit c44bd5e75b32d5fbc8951247324ebf5fb85f3b37
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Tue Nov 23 11:27:24 2021 +0100

    migration deleted

commit dbeee090f46b3653c306a1825dcb05637e1f9b31
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Tue Nov 23 11:27:13 2021 +0100

    toast plugin initialized with options in the root

commit ec3919e357ee26f6c5016360d2083d19688217f4
Merge: 1ab401b5d b8889ed58
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Tue Nov 23 10:52:24 2021 +0100

    merge conflicts fixed

commit 1ab401b5d55a28f9c027ee539da58ef1da3d797c
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Tue Nov 23 10:40:02 2021 +0100

    add options when vue toast plugin is loaded

commit ad4e630bdd87c8ec16455bab0c2b053c1d838050
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Mon Nov 22 18:58:44 2021 +0100

    fixes to throw and catch error

commit 1c19748866d738bcb9e336f1035d9d78c6941d34
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Wed Nov 17 20:22:06 2021 +0100

    toast added to banner

commit 35949b90532b60161e14e815a78b3b69b053b62d
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Wed Nov 17 20:16:35 2021 +0100

    few more actions using makeFetch

commit e94c13e396f496565955294aec360db37f92374b
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Wed Nov 17 19:48:58 2021 +0100

    For accompanyingcourse app general makeFetch + displayToast implemented

commit 35eac75edf2c8d39ee2a04f119bb99a946fe3ea9
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Wed Nov 17 17:12:31 2021 +0100

    general makeFetch method implemented + toasts displayed in progress

commit 541bb10547bd7b96815a22a755ecf7f809d8cc87
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Wed Nov 17 12:36:20 2021 +0100

    general fetch method adjusted and tested on addParticipation: works

commit b7f27e8079bc815d88ffc925ec7a4fae3fba5f02
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Wed Nov 17 11:50:09 2021 +0100

    changelog updated

commit 028519ee3e93991aeb3131656e5f17e728ebdb1c
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Wed Nov 17 11:42:55 2021 +0100

    adjustment of error message using templating render

commit 508139b4476a46028a40b0b5300a8c65f79414a9
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Wed Nov 17 11:41:52 2021 +0100

    toast display for 422 errors on participations, requestor, resources + bugfix in requestor component (display of 'remove' btn if non-anonymous

commit 3e5d4862fcbbf5708f52c0038bf030dc0cbae5bb
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Tue Nov 16 15:38:03 2021 +0100

    debugging fetch method

commit 8b971a2357aac952ea6cd9487da20f4064c26a8d
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Tue Nov 16 14:31:45 2021 +0100

    general fetch method in _abstratAPI and testcase implementation; still needs to take care of commit into store

commit 05488740ab138ed279a4626310c5427d9f732793
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Tue Nov 16 11:13:28 2021 +0100

    testcase: toaster working when adding a resource to an accompanying course. Needs to be generalized

commit 5050e8a516107710216fa11aafcf852c52e1eb03
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Tue Nov 16 09:22:36 2021 +0100

    start of toaster popup

commit b8889ed58d3abb68d1c2859b31bf7428addda36e
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Mon Nov 22 18:58:44 2021 +0100

    fixes to throw and catch error

commit 0c017eb908e65919876d3bed69d3daa27699f2cc
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Wed Nov 17 20:22:06 2021 +0100

    toast added to banner

commit 80d8c5f12be4b0cfcded2948f385b4e3627e9afb
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Wed Nov 17 20:16:35 2021 +0100

    few more actions using makeFetch

commit 9cfcc61d995f75651902af97fe640f00f994ce9b
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Wed Nov 17 19:48:58 2021 +0100

    For accompanyingcourse app general makeFetch + displayToast implemented

commit 2855b0293a4a3fef2baf22fa035316bac13abf3f
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Wed Nov 17 17:12:31 2021 +0100

    general makeFetch method implemented + toasts displayed in progress

commit febbc8b9cdd9ec811546ce2948e2e1bded7c2116
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Wed Nov 17 12:36:20 2021 +0100

    general fetch method adjusted and tested on addParticipation: works

commit 16b59681b97cf995689aa464ae2a310c3efbb603
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Wed Nov 17 11:50:09 2021 +0100

    changelog updated

commit 41df29bdef84af010892708bfd0e37ff826f7aee
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Wed Nov 17 11:42:55 2021 +0100

    adjustment of error message using templating render

commit 8c98fe602c26e61285f12f5f7d58d646655f4caa
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Wed Nov 17 11:41:52 2021 +0100

    toast display for 422 errors on participations, requestor, resources + bugfix in requestor component (display of 'remove' btn if non-anonymous

commit 689ca8aa0ac249d577fe2ead5b3fcc0298dbad90
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Tue Nov 16 15:38:03 2021 +0100

    debugging fetch method

commit 07234c5fa996fb9f0352719ee366b34b093807eb
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Tue Nov 16 14:31:45 2021 +0100

    general fetch method in _abstratAPI and testcase implementation; still needs to take care of commit into store

commit 2a482516e952aa720b5aa8b4cb68ab33f93ce47b
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Tue Nov 16 11:13:28 2021 +0100

    testcase: toaster working when adding a resource to an accompanying course. Needs to be generalized

commit b7dcc5e7d4b1489a8edc175a0c2c1b0d649c6c0f
Author: Julie Lenaerts <julielenaerts@gmail.com>
Date:   Tue Nov 16 09:22:36 2021 +0100

    start of toaster popup
This commit is contained in:
Julien Fastré 2021-11-25 12:11:22 +01:00
parent 8a0d16dc53
commit 983cfb646c
29 changed files with 713 additions and 470 deletions

View File

@ -65,6 +65,14 @@ and this project adheres to
* [person suggest] In widget "add person", improve the pertinence of persons when one of the names starts with the pattern; * [person suggest] In widget "add person", improve the pertinence of persons when one of the names starts with the pattern;
* [person] do not ask for center any more on person creation * [person] do not ask for center any more on person creation
* [3party] do not ask for center any more on 3party creation * [3party] do not ask for center any more on 3party creation
<<<<<<< HEAD
<<<<<<< HEAD
=======
* [validation] toasts are displayed for errors when modifying accompanying course (generalization required).
>>>>>>> 16b59681 (changelog updated)
=======
* [validation] toasts are displayed for errors when modifying accompanying course (generalization required).
>>>>>>> b8889ed58d3abb68d1c2859b31bf7428addda36e
## Test releases ## Test releases

View File

@ -0,0 +1,106 @@
/**
* Generic api method that can be adapted to any fetch request
*/
const makeFetch = (method, url, body) => {
return fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: (body !== null) ? JSON.stringify(body) : null
})
.then(response => {
if (response.ok) {
return response.json();
}
if (response.status === 422) {
return response.json().then(response => {
throw ValidationException(response)
});
}
if (response.status === 403) {
return response.json().then(() => {
throw AccessException();
});
}
throw {
name: 'Exception',
sta: response.status,
txt: response.statusText,
err: new Error(),
violations: response.body
};
});
}
/**
* Fetch results with certain parameters
*/
const _fetchAction = (page, uri, params) => {
const item_per_page = 50;
if (params === undefined) {
params = {};
}
let url = uri + '?' + new URLSearchParams({ item_per_page, page, ...params });
return fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
}).then(response => {
if (response.ok) { return response.json(); }
throw Error({ m: response.statusText });
});
};
const fetchResults = async (uri, params) => {
let promises = [],
page = 1;
let firstData = await _fetchAction(page, uri, params);
promises.push(Promise.resolve(firstData.results));
if (firstData.pagination.more) {
do {
page = ++page;
promises.push(_fetchAction(page, uri, params).then(r => Promise.resolve(r.results)));
} while (page * firstData.pagination.items_per_page < firstData.count)
}
return Promise.all(promises).then(values => values.flat());
};
const fetchScopes = () => {
return fetchResults('/api/1.0/main/scope.json');
};
/**
* Error objects to be thrown
*/
const ValidationException = (response) => {
const error = {};
error.name = 'ValidationException';
error.violations = response.violations.map((violation) => `${violation.title}`);
return error;
}
const AccessException = () => {
const error = {};
error.name = 'AccessException';
error.violations = ['You are no longer permitted to perform this action'];
return error;
}
export {
makeFetch,
fetchResults,
fetchScopes
}

View File

@ -1,39 +1,39 @@
const _fetchAction = (page, uri, params) => { // const _fetchAction = (page, uri, params) => {
const item_per_page = 50; // const item_per_page = 50;
if (params === undefined) { // if (params === undefined) {
params = {}; // params = {};
} // }
let url = uri + '?' + new URLSearchParams({ item_per_page, page, ...params }); // let url = uri + '?' + new URLSearchParams({ item_per_page, page, ...params });
return fetch(url, { // return fetch(url, {
method: 'GET', // method: 'GET',
headers: { // headers: {
'Content-Type': 'application/json;charset=utf-8' // 'Content-Type': 'application/json;charset=utf-8'
}, // },
}).then(response => { // }).then(response => {
if (response.ok) { return response.json(); } // if (response.ok) { return response.json(); }
throw Error({ m: response.statusText }); // throw Error({ m: response.statusText });
}); // });
}; // };
const fetchResults = async (uri, params) => { // const fetchResults = async (uri, params) => {
let promises = [], // let promises = [],
page = 1; // page = 1;
let firstData = await _fetchAction(page, uri, params); // let firstData = await _fetchAction(page, uri, params);
promises.push(Promise.resolve(firstData.results)); // promises.push(Promise.resolve(firstData.results));
if (firstData.pagination.more) { // if (firstData.pagination.more) {
do { // do {
page = ++page; // page = ++page;
promises.push(_fetchAction(page, uri, params).then(r => Promise.resolve(r.results))); // promises.push(_fetchAction(page, uri, params).then(r => Promise.resolve(r.results)));
} while (page * firstData.pagination.items_per_page < firstData.count) // } while (page * firstData.pagination.items_per_page < firstData.count)
} // }
return Promise.all(promises).then(values => values.flat()); // return Promise.all(promises).then(values => values.flat());
}; // };
export { // export {
fetchResults // fetchResults
}; // };

View File

@ -1,9 +1,9 @@
import { fetchResults } from 'ChillMainAssets/lib/api/download.js'; // import { fetchResults } from 'ChillMainAssets/lib/api/download.js';
const fetchScopes = () => { // const fetchScopes = () => {
return fetchResults('/api/1.0/main/scope.json'); // return fetchResults('/api/1.0/main/scope.json');
}; // };
export { // export {
fetchScopes // fetchScopes
}; // };

View File

@ -13,7 +13,6 @@ export default {
methods : { methods : {
toggleBlur: function(e){ toggleBlur: function(e){
if(e.target.matches('.toggle')){ if(e.target.matches('.toggle')){
console.log(e);
e.target.previousElementSibling.classList.toggle("blur"); e.target.previousElementSibling.classList.toggle("blur");
e.target.classList.toggle("fa-eye"); e.target.classList.toggle("fa-eye");
e.target.classList.toggle("fa-eye-slash"); e.target.classList.toggle("fa-eye-slash");

View File

@ -277,7 +277,7 @@ class AccompanyingPeriod implements
* inverseJoinColumns={@ORM\JoinColumn(name="scope_id", referencedColumnName="id")} * inverseJoinColumns={@ORM\JoinColumn(name="scope_id", referencedColumnName="id")}
* ) * )
* @Groups({"read"}) * @Groups({"read"})
* @Assert\Count(min=1, groups={AccompanyingPeriod::STEP_CONFIRMED}) * @Assert\Count(min=1, groups={AccompanyingPeriod::STEP_CONFIRMED}, minMessage="A course must be associated to at least one scope")
*/ */
private $scopes; private $scopes;
@ -289,7 +289,7 @@ class AccompanyingPeriod implements
* name="chill_person_accompanying_period_social_issues" * name="chill_person_accompanying_period_social_issues"
* ) * )
* @Groups({"read"}) * @Groups({"read"})
* @Assert\Count(min=1, groups={AccompanyingPeriod::STEP_CONFIRMED}) * @Assert\Count(min=1, groups={AccompanyingPeriod::STEP_CONFIRMED}, minMessage="A course must contains at least one social issue")
*/ */
private Collection $socialIssues; private Collection $socialIssues;

View File

@ -1,4 +1,4 @@
import { fetchResults } from 'ChillMainAssets/lib/api/download.js'; import { fetchResults } from 'ChillMainAssets/lib/api/apiMethods.js';
const fetchHouseholdByAddressReference = async (reference) => { const fetchHouseholdByAddressReference = async (reference) => {
const url = `/api/1.0/person/household/by-address-reference/${reference.id}.json` const url = `/api/1.0/person/household/by-address-reference/${reference.id}.json`

View File

@ -59,7 +59,7 @@ export default {
'accompanyingCourse', 'accompanyingCourse',
'addressContext' 'addressContext'
]), ]),
}, }
}; };
</script> </script>

View File

@ -1,4 +1,4 @@
import { fetchResults } from 'ChillMainAssets/lib/api/download.js'; import { fetchResults } from 'ChillMainAssets/lib/api/apiMethods.js';
/* /*
* Endpoint v.2 chill_api_single_accompanying_course__entity * Endpoint v.2 chill_api_single_accompanying_course__entity
@ -15,44 +15,17 @@ const getAccompanyingCourse = (id) => {
}); });
}; };
/* const getUsers = () => {
* Endpoint v.2 chill_api_single_accompanying_course__entity const url = `/api/1.0/main/user.json`;
* method PATCH, patch AccompanyingCourse Instance
* return fetchResults(url);
* @id integer - id of accompanyingCourse
* @body Object - dictionary with changes to post
*/
const patchAccompanyingCourse = (id, body) => {
//console.log('body', body);
const url = `/api/1.0/person/accompanying-course/${id}.json`;
return fetch(url, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify(body)
})
.then(response => {
if (response.ok) { return response.json(); }
console.log(response);
throw { msg: 'Error while updating AccompanyingPeriod Course.', sta: response.status, txt: response.statusText, err: new Error(), body: response.body };
});
}; };
/* const getReferrersSuggested = (course) => {
* Endpoint to change 'DRAFT' step to 'CONFIRMED' const url = `/api/1.0/person/accompanying-course/${course.id}/referrers-suggested.json`;
*/
const confirmAccompanyingCourse = (id) => { return fetchResults(url);
const url = `/api/1.0/person/accompanying-course/${id}/confirm.json` }
return fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json;charset=utf-8'}
})
.then(response => {
if (response.ok) { return response.json(); }
throw { msg: 'Error while confirming AccompanyingPeriod Course.', sta: response.status, txt: response.statusText, err: new Error(), body: response.body };
});
};
/* /*
* Endpoint * Endpoint
@ -66,115 +39,6 @@ const getSocialIssues = () => {
}); });
}; };
/*
* Endpoint v.2 chill_api_single_accompanying_course_participation,
* method POST/DELETE, add/close a participation to the accompanyingCourse
*
* @id integer - id of accompanyingCourse
* @payload integer - id of person
* @method string - POST or DELETE
*/
const postParticipation = (id, payload, method) => {
const body = { type: payload.type, id: payload.id };
const url = `/api/1.0/person/accompanying-course/${id}/participation.json`;
return fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify(body)
})
.then(response => {
if (response.ok) { return response.json(); }
// TODO: adjust message according to status code? Or how to access the message from the violation array?
throw { msg: 'Error while sending AccompanyingPeriod Course participation', sta: response.status, txt: response.statusText, err: new Error(), body: response.body };
});
};
/*
* Endpoint v.2 chill_api_single_accompanying_course_requestor,
* method POST/DELETE, add/close a requestor to the accompanyingCourse
*
* @id integer - id of accompanyingCourse
* @payload object of type person|thirdparty
* @method string - POST or DELETE
*/
const postRequestor = (id, payload, method) => {
//console.log('payload', payload);
const body = (payload)? { type: payload.type, id: payload.id } : {};
//console.log('body', body);
const url = `/api/1.0/person/accompanying-course/${id}/requestor.json`;
return fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify(body)
})
.then(response => {
if (response.ok) { return response.json(); }
throw { msg: 'Error while sending AccompanyingPeriod Course requestor', sta: response.status, txt: response.statusText, err: new Error(), body: response.body };
});
};
/*
* Endpoint v.2 chill_api_single_accompanying_course_resource,
* method POST/DELETE, add/remove a resource to the accompanyingCourse
*
* @id integer - id of accompanyingCourse
* @payload object of type person|thirdparty
* @method string - POST or DELETE
*/
const postResource = (id, payload, method) => {
//console.log('payload', payload);
const body = { type: "accompanying_period_resource" };
switch (method) {
case 'DELETE':
body['id'] = payload.id;
break;
default:
body['resource'] = { type: payload.type, id: payload.id };
}
//console.log('body', body);
const url = `/api/1.0/person/accompanying-course/${id}/resource.json`;
return fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify(body)
})
.then(response => {
if (response.ok) { return response.json(); }
throw { msg: 'Error while sending AccompanyingPeriod Course resource.', sta: response.status, txt: response.statusText, err: new Error(), body: response.body };
});
};
/*
* Endpoint to Add/remove SocialIssue
*/
const postSocialIssue = (id, body, method) => {
//console.log('api body and method', body, method);
const url = `/api/1.0/person/accompanying-course/${id}/socialissue.json`;
return fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify(body)
})
.then(response => {
if (response.ok) { return response.json(); }
throw { msg: 'Error while updating SocialIssue.', sta: response.status, txt: response.statusText, err: new Error(), body: response.body };
});
};
const getUsers = () => {
const url = `/api/1.0/main/user.json`;
return fetchResults(url);
};
const whoami = () => { const whoami = () => {
const url = `/api/1.0/main/whoami.json`; const url = `/api/1.0/main/whoami.json`;
return fetch(url) return fetch(url)
@ -184,74 +48,10 @@ const whoami = () => {
}); });
}; };
const getListOrigins = () => {
const url = `/api/1.0/person/accompanying-period/origin.json`;
return fetch(url)
.then(response => {
if (response.ok) { return response.json(); }
throw { msg: 'Error while retriving origin\'s list.', sta: response.status, txt: response.statusText, err: new Error(), body: response.body };
});
};
const addScope = (id, scope) => {
const url = `/api/1.0/person/accompanying-course/${id}/scope.json`;
console.log(url);
console.log(scope);
return fetch(url, {
method: 'POST',
body: JSON.stringify({
id: scope.id,
type: scope.type,
}),
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
})
.then(response => {
if (response.ok) { return response.json(); }
throw { msg: 'Error while adding scope', sta: response.status, txt: response.statusText, err: new Error(), body: response.body };
});
};
const removeScope = (id, scope) => {
const url = `/api/1.0/person/accompanying-course/${id}/scope.json`;
return fetch(url, {
method: 'DELETE',
body: JSON.stringify({
id: scope.id,
type: scope.type,
}),
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
})
.then(response => {
if (response.ok) { return response.json(); }
throw { msg: 'Error while adding scope', sta: response.status, txt: response.statusText, err: new Error(), body: response.body };
});
};
const getReferrersSuggested = (course) => {
const url = `/api/1.0/person/accompanying-course/${course.id}/referrers-suggested.json`;
return fetchResults(url);
}
export { export {
getAccompanyingCourse,
patchAccompanyingCourse,
confirmAccompanyingCourse,
getSocialIssues,
postParticipation,
postRequestor,
postResource,
getUsers,
whoami, whoami,
getListOrigins, getSocialIssues,
postSocialIssue, getAccompanyingCourse,
addScope, getUsers,
removeScope,
getReferrersSuggested, getReferrersSuggested,
}; };

View File

@ -53,13 +53,34 @@ export default {
//temporaire (modif backend) //temporaire (modif backend)
value = "occasional"; value = "occasional";
} }
this.$store.dispatch('toggleIntensity', value); this.$store.dispatch('toggleIntensity', value)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
}, },
toggleEmergency() { toggleEmergency() {
this.$store.dispatch('toggleEmergency', (!this.isEmergency)); this.$store.dispatch('toggleEmergency', (!this.isEmergency))
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
}, },
toggleConfidential() { toggleConfidential() {
this.$store.dispatch('toggleConfidential', (!this.isConfidential)); this.$store.dispatch('toggleConfidential', (!this.isConfidential))
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
} }
} }
} }

View File

@ -59,7 +59,15 @@ export default {
locationStatusTo: 'person', locationStatusTo: 'person',
personId: this.person.id personId: this.person.id
}; };
this.$store.dispatch('updateLocation', payload); this.$store.dispatch('updateLocation', payload)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
window.location.assign('#section-20'); window.location.assign('#section-20');
this.modal.showModal = false; this.modal.showModal = false;
} }

View File

@ -83,12 +83,24 @@ export default {
}, },
methods: { methods: {
submitform() { submitform() {
//console.log('submit'); this.$store.dispatch('postFirstComment', this.formdata)
this.$store.dispatch('postFirstComment', this.formdata); .catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
}, },
removeComment() { removeComment() {
//console.log('remove'); this.$store.dispatch('postFirstComment', {})
this.$store.dispatch('postFirstComment', {}); .catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
} }
} }
} }

View File

@ -115,9 +115,14 @@ export default {
}, },
methods: { methods: {
confirmCourse() { confirmCourse() {
//console.log('@@ CLICK confirmCourse'); this.$store.dispatch('confirmAccompanyingCourse')
this.$store.dispatch('confirmAccompanyingCourse'); .catch(({name, violations}) => {
//console.log('confirm last'); if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
} }
} }
} }

View File

@ -187,7 +187,14 @@ export default {
locationStatusTo: 'none' locationStatusTo: 'none'
}; };
//console.log('remove address'); //console.log('remove address');
this.$store.dispatch('updateLocation', payload); this.$store.dispatch('updateLocation', payload)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
}, },
displayErrors() { displayErrors() {
return this.$refs.addAddress.errorMsg; return this.$refs.addAddress.errorMsg;
@ -195,7 +202,15 @@ export default {
submitTemporaryAddress(payload) { submitTemporaryAddress(payload) {
//console.log('@@@ click on Submit Temporary Address Button', payload); //console.log('@@@ click on Submit Temporary Address Button', payload);
payload['locationStatusTo'] = 'address'; // <== temporary, not none, not person payload['locationStatusTo'] = 'address'; // <== temporary, not none, not person
this.$store.dispatch('updateLocation', payload); this.$store.dispatch('updateLocation', payload)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
this.$store.commit('setEditContextTrue', payload); this.$store.commit('setEditContextTrue', payload);
} }
}, },

View File

@ -29,7 +29,7 @@
<script> <script>
import VueMultiselect from 'vue-multiselect'; import VueMultiselect from 'vue-multiselect';
import { getListOrigins } from '../api'; import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods';
import { mapState, mapGetters } from 'vuex'; import { mapState, mapGetters } from 'vuex';
export default { export default {
@ -53,14 +53,26 @@ export default {
}, },
methods: { methods: {
getOptions() { getOptions() {
getListOrigins().then(response => new Promise((resolve, reject) => { const url = `/api/1.0/person/accompanying-period/origin.json`;
this.options = response.results; makeFetch('GET', url)
resolve(); .then(response => {
})); this.options = response.results;
return response;
})
.catch((error) => {
commit('catchError', error);
this.$toast.open({message: error.txt})
})
}, },
updateOrigin(value) { updateOrigin(value) {
console.log('value', value); this.$store.dispatch('updateOrigin', value)
this.$store.dispatch('updateOrigin', value); .catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
}, },
transText ({ text }) { transText ({ text }) {
const parsedText = JSON.parse(text); const parsedText = JSON.parse(text);

View File

@ -17,7 +17,7 @@
<button class="btn btn-update" type="submit">{{ $t('persons_associated.update_household') }}</button> <button class="btn btn-update" type="submit">{{ $t('persons_associated.update_household') }}</button>
</div> </div>
<p class="mb-3">{{ $t('persons_associated.person_without_household_warning') }}</p> <p class="mb-3">{{ $t('persons_associated.person_without_household_warning') }}</p>
<div class="form-check" v-for="p in participationWithoutHousehold"> <div class="form-check" v-for="p in participationWithoutHousehold" :key="p.id">
<input type="checkbox" <input type="checkbox"
class="form-check-input" class="form-check-input"
v-model="hasNoHousehold" v-model="hasNoHousehold"
@ -66,8 +66,8 @@
<script> <script>
import {mapGetters, mapState} from 'vuex'; import {mapGetters, mapState} from 'vuex';
import ParticipationItem from "./PersonsAssociated/ParticipationItem.vue" import ParticipationItem from "./PersonsAssociated/ParticipationItem.vue";
import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue' import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue';
export default { export default {
name: 'PersonsAssociated', name: 'PersonsAssociated',
@ -110,17 +110,35 @@ export default {
}, },
methods: { methods: {
removeParticipation(item) { removeParticipation(item) {
//console.log('@@ CLICK remove participation: item', item); this.$store.dispatch('removeParticipation', item)
this.$store.dispatch('removeParticipation', item); .catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
}, },
closeParticipation(item) { closeParticipation(item) {
//console.log('@@ CLICK close participation: item', item); this.$store.dispatch('closeParticipation', item)
this.$store.dispatch('closeParticipation', item); .catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
}, },
addNewPersons({ selected, modal }) { addNewPersons({ selected, modal }) {
//console.log('@@@ CLICK button addNewPersons', selected);
selected.forEach(function(item) { selected.forEach(function(item) {
this.$store.dispatch('addParticipation', item); this.$store.dispatch('addParticipation', item)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
}, this }, this
); );
this.$refs.addPersons.resetSearch(); // to cast child method this.$refs.addPersons.resetSearch(); // to cast child method

View File

@ -110,7 +110,14 @@ export default {
saveFormOnTheFly(payload) { saveFormOnTheFly(payload) {
console.log('saveFormOnTheFly: type', payload.type, ', data', payload.data); console.log('saveFormOnTheFly: type', payload.type, ', data', payload.data);
payload.target = 'participation'; payload.target = 'participation';
this.$store.dispatch('patchOnTheFly', payload); this.$store.dispatch('patchOnTheFly', payload)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
} }
} }
} }

View File

@ -50,7 +50,7 @@
<script> <script>
import VueMultiselect from 'vue-multiselect'; import VueMultiselect from 'vue-multiselect';
import { getUsers, whoami } from '../api'; import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods';
import { mapState } from 'vuex'; import { mapState } from 'vuex';
import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge"; import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge";
@ -77,14 +77,26 @@ export default {
methods: { methods: {
updateReferrer(value) { updateReferrer(value) {
//console.log('value', value); //console.log('value', value);
this.$store.dispatch('updateReferrer', value); this.$store.dispatch('updateReferrer', value)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
}, },
assignMe() { assignMe() {
//console.log('assign me'); const url = `/api/1.0/main/whoami.json`;
whoami().then(me => new Promise((resolve, reject) => { makeFetch('GET', url)
this.$store.dispatch('updateReferrer', me); .then(response => {
resolve(); this.$store.dispatch('updateReferrer', response);
})); return response;
})
.catch((error) => {
commit('catchError', error);
this.$toast.open({message: error.body})
})
} }
} }
} }

View File

@ -117,6 +117,16 @@
</ul> </ul>
</template> </template>
</person-render-box> </person-render-box>
<ul class="record_actions">
<li>
<button class="btn btn-remove"
:title="$t('action.remove')"
@click="removeRequestor">
{{ $t('action.remove') }}
</button>
</li>
</ul>
</div> </div>
<div v-else> <div v-else>
@ -183,18 +193,40 @@ export default {
methods: { methods: {
removeRequestor() { removeRequestor() {
//console.log('@@ CLICK remove requestor: item'); //console.log('@@ CLICK remove requestor: item');
this.$store.dispatch('removeRequestor'); this.$store.dispatch('removeRequestor')
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
}, },
addNewPersons({ selected, modal }) { addNewPersons({ selected, modal }) {
//console.log('@@@ CLICK button addNewPersons', selected); //console.log('@@@ CLICK button addNewPersons', selected);
this.$store.dispatch('addRequestor', selected.shift()); this.$store.dispatch('addRequestor', selected.shift())
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
this.$refs.addPersons.resetSearch(); // to cast child method this.$refs.addPersons.resetSearch(); // to cast child method
modal.showModal = false; modal.showModal = false;
}, },
saveFormOnTheFly(payload) { saveFormOnTheFly(payload) {
console.log('saveFormOnTheFly: type', payload.type, ', data', payload.data); console.log('saveFormOnTheFly: type', payload.type, ', data', payload.data);
payload.target = 'requestor'; payload.target = 'requestor';
this.$store.dispatch('patchOnTheFly', payload); this.$store.dispatch('patchOnTheFly', payload)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
} }
} }
} }

View File

@ -62,12 +62,26 @@ export default {
methods: { methods: {
removeResource(item) { removeResource(item) {
//console.log('@@ CLICK remove resource: item', item); //console.log('@@ CLICK remove resource: item', item);
this.$store.dispatch('removeResource', item); this.$store.dispatch('removeResource', item)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
}, },
addNewPersons({ selected, modal }) { addNewPersons({ selected, modal }) {
//console.log('@@@ CLICK button addNewPersons', selected); //console.log('@@@ CLICK button addNewPersons', selected);
selected.forEach(function(item) { selected.forEach(function(item) {
this.$store.dispatch('addResource', item); this.$store.dispatch('addResource', item)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: violations})
}
});
}, this }, this
); );
this.$refs.addPersons.resetSearch(); // to cast child method this.$refs.addPersons.resetSearch(); // to cast child method

View File

@ -60,7 +60,14 @@ export default {
saveFormOnTheFly(payload) { saveFormOnTheFly(payload) {
console.log('saveFormOnTheFly: type', payload.type, ', data', payload.data); console.log('saveFormOnTheFly: type', payload.type, ', data', payload.data);
payload.target = 'resource'; payload.target = 'resource';
this.$store.dispatch('patchOnTheFly', payload); this.$store.dispatch('patchOnTheFly', payload)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
} }
} }
} }

View File

@ -3,8 +3,8 @@
<h2><a id="section-60"></a>{{ $t('scopes.title') }}</h2> <h2><a id="section-60"></a>{{ $t('scopes.title') }}</h2>
<div class="mb-4"> <div class="mb-4">
<div class="form-check" v-for="s in scopes"> <div class="form-check" v-for="s in scopes" :key="s.id">
<input class="form-check-input" type="checkbox" v-model="checkedScopes" :value="s" /> <input class="form-check-input" type="checkbox" v-model="checkedScopes" :value="s"/>
<label class="form-check-label"> <label class="form-check-label">
{{ s.name.fr }} {{ s.name.fr }}
</label> </label>
@ -37,10 +37,22 @@ export default {
return this.$store.state.accompanyingCourse.scopes; return this.$store.state.accompanyingCourse.scopes;
}, },
set: function(v) { set: function(v) {
this.$store.dispatch('setScopes', v); this.$store.dispatch('setScopes', v)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
} }
} }
} },
methods: {
restore() {
console.log('restore');
}
},
} }
</script> </script>

View File

@ -30,7 +30,7 @@
<script> <script>
import VueMultiselect from 'vue-multiselect'; import VueMultiselect from 'vue-multiselect';
import { getSocialIssues } from '../api'; import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods';
import {mapGetters, mapState} from 'vuex'; import {mapGetters, mapState} from 'vuex';
export default { export default {
@ -54,14 +54,26 @@ export default {
}, },
methods: { methods: {
getOptions() { getOptions() {
getSocialIssues().then(response => new Promise((resolve, reject) => { const url = `/api/1.0/person/social-work/social-issue.json`;
//console.log('get socialIssues', response.results); makeFetch('GET', url)
this.options = response.results; .then(response => {
resolve(); this.options = response.results;
})).catch(error => this.$store.commit('catchError', error)); return response;
})
.catch((error) => {
commit('catchError', error);
this.$toast.open({message: error.txt})
})
}, },
updateSocialIssues(value) { updateSocialIssues(value) {
this.$store.dispatch('updateSocialIssues', this.transformValue(value)); this.$store.dispatch('updateSocialIssues', this.transformValue(value))
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
}, },
transformValue(updated) { transformValue(updated) {
let stored = this.value; let stored = this.value;
@ -70,8 +82,6 @@ export default {
let method = (typeof removed === 'undefined') ? 'POST' : 'DELETE'; let method = (typeof removed === 'undefined') ? 'POST' : 'DELETE';
let changed = (typeof removed === 'undefined') ? added : removed; let changed = (typeof removed === 'undefined') ? added : removed;
let body = { type: "social_issue", id: changed.id }; let body = { type: "social_issue", id: changed.id };
//console.log('body', body);
//console.log('@@@', method, changed.text);
let payload = updated; let payload = updated;
return { payload, body, method }; return { payload, body, method };
} }

View File

@ -2,7 +2,8 @@ import { createApp } from 'vue'
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n' import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
import { appMessages } from './js/i18n' import { appMessages } from './js/i18n'
import { initPromise } from './store' import { initPromise } from './store'
import VueToast from 'vue-toast-notification';
import 'vue-toast-notification/dist/theme-sugar.css';
import App from './App.vue'; import App from './App.vue';
import Banner from './components/Banner.vue'; import Banner from './components/Banner.vue';
@ -21,8 +22,14 @@ if (root === 'app') {
}) })
.use(store) .use(store)
.use(i18n) .use(i18n)
.use(VueToast, {
position: "top",
type: "error",
duration: 5000,
dismissible: true
})
.component('app', App) .component('app', App)
.mount('#accompanying-course'); .mount('#accompanying-course')
}); });
} }

View File

@ -1,20 +1,13 @@
import 'es6-promise/auto'; import 'es6-promise/auto';
import { createStore } from 'vuex'; import { createStore } from 'vuex';
import { fetchScopes } from 'ChillMainAssets/lib/api/scope.js'; import { fetchScopes } from 'ChillMainAssets/lib/api/apiMethods.js';
import { getAccompanyingCourse, import { getAccompanyingCourse,
patchAccompanyingCourse,
confirmAccompanyingCourse,
postParticipation,
postRequestor,
postResource,
postSocialIssue,
addScope,
removeScope,
getReferrersSuggested, getReferrersSuggested,
getUsers, getUsers,
} from '../api'; } from '../api';
import { patchPerson } from "ChillPersonAssets/vuejs/_api/OnTheFly"; import { patchPerson } from "ChillPersonAssets/vuejs/_api/OnTheFly";
import { patchThirdparty } from "ChillThirdPartyAssets/vuejs/_api/OnTheFly"; import { patchThirdparty } from "ChillThirdPartyAssets/vuejs/_api/OnTheFly";
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods';
const debug = process.env.NODE_ENV !== 'production'; const debug = process.env.NODE_ENV !== 'production';
@ -88,11 +81,11 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
//console.log('### mutation: remove participation', participation.id); //console.log('### mutation: remove participation', participation.id);
state.accompanyingCourse.participations = state.accompanyingCourse.participations.filter(element => element !== participation); state.accompanyingCourse.participations = state.accompanyingCourse.participations.filter(element => element !== participation);
}, },
closeParticipation(state, { participation, payload }) { closeParticipation(state, { response, payload }) {
//console.log('### mutation: close item', { participation, payload }); //console.log('### mutation: close item', { participation, payload });
// find row position and replace by closed participation // find row position and replace by closed participation
state.accompanyingCourse.participations.splice( state.accompanyingCourse.participations.splice(
state.accompanyingCourse.participations.findIndex(element => element === payload), 1, participation state.accompanyingCourse.participations.findIndex(element => element === payload), 1, response
); );
}, },
addParticipation(state, participation) { addParticipation(state, participation) {
@ -240,67 +233,117 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
} }
}, },
actions: { actions: {
//QUESTION: does this serve a purpose? Participations can be closed...
removeParticipation({ commit }, payload) { removeParticipation({ commit }, payload) {
commit('removeParticipation', payload); commit('removeParticipation', payload);
// fetch DELETE request... // fetch DELETE request...
}, },
/**
* Add/close participation
*/
closeParticipation({ commit }, payload) { closeParticipation({ commit }, payload) {
//console.log('## action: fetch delete participation: payload', payload); const body = { type: payload.person.type, id: payload.person.id };
postParticipation(id, payload.person, 'DELETE') const url = `/api/1.0/person/accompanying-course/${id}/participation.json`;
.then(participation => new Promise((resolve, reject) => {
commit('closeParticipation', { participation, payload }); return makeFetch('DELETE', url, body)
resolve(); .then((response) => {
})).catch((error) => { commit('catchError', error) }); commit('closeParticipation', { response, payload });
})
.catch((error) => {
commit('catchError', error);
throw error;
})
}, },
addParticipation({ commit }, payload) { addParticipation({ commit }, payload) {
//console.log('## action: fetch post participation (select item): payload', payload); const body = { type: payload.result.type, id: payload.result.id };
postParticipation(id, payload.result, 'POST') const url = `/api/1.0/person/accompanying-course/${id}/participation.json`;
.then(participation => new Promise((resolve, reject) => {
commit('addParticipation', participation); return makeFetch('POST', url, body)
resolve(); .then((response) => {
})).catch((error) => { commit('catchError', error) }); commit('addParticipation', response);
})
.catch((error) => {
commit('catchError', error);
throw error;
})
}, },
/**
* Add/remove/display anonymous requestor
*/
removeRequestor({ commit, dispatch }) { removeRequestor({ commit, dispatch }) {
//console.log('## action: fetch delete requestor'); const body = {};
postRequestor(id, null, 'DELETE') const url = `/api/1.0/person/accompanying-course/${id}/requestor.json`;
.then(requestor => new Promise((resolve, reject) => {
commit('removeRequestor'); return makeFetch('DELETE', url, body)
dispatch('requestorIsAnonymous', false); .then((response) => {
resolve(); commit('removeRequestor');
})).catch((error) => { commit('catchError', error) }); dispatch('requestorIsAnonymous', false);
})
.catch((error) => {
commit('catchError', error);
throw error;
})
}, },
addRequestor({ commit }, payload) { addRequestor({ commit }, payload) {
//console.log('## action: fetch post requestor: payload', payload); const body = payload ? { type: payload.result.type, id: payload.result.id } : {};
postRequestor(id, payload.result, 'POST') const url = `/api/1.0/person/accompanying-course/${id}/requestor.json`;
.then(requestor => new Promise((resolve, reject) => {
commit('addRequestor', requestor); return makeFetch('POST', url, body)
resolve(); .then((response) => {
})).catch((error) => { commit('catchError', error) }); commit('addRequestor', response);
})
.catch((error) => {
commit('catchError', error);
throw error;
})
}, },
requestorIsAnonymous({ commit }, payload) { requestorIsAnonymous({ commit }, payload) {
//console.log('## action: fetch patch AccompanyingCourse: payload', payload); const url = `/api/1.0/person/accompanying-course/${id}.json`
patchAccompanyingCourse(id, { type: "accompanying_period", requestorAnonymous: payload }) const body = { type: "accompanying_period", requestorAnonymous: payload }
.then(course => new Promise((resolve, reject) => {
commit('requestorIsAnonymous', course.requestorAnonymous) return makeFetch('PATCH', url, body)
resolve(); .then((response) => {
})).catch((error) => { commit('catchError', error) }); commit('requestorIsAnonymous', response.requestorAnonymous);
})
.catch((error) => {
commit('catchError', error);
throw error;
})
}, },
/**
* Add/remove resource
*/
removeResource({ commit }, payload) { removeResource({ commit }, payload) {
//console.log('## action: fetch postResource: payload', payload); const body = { type: "accompanying_period_resource" };
postResource(id, payload, 'DELETE') body['id'] = payload.id;
.then(resource => new Promise((resolve, reject) => { const url = `/api/1.0/person/accompanying-course/${id}/resource.json`;
commit('removeResource', payload) // mieux un retour de l'objet !
resolve(); return makeFetch('DELETE', url, body)
})).catch((error) => { commit('catchError', error) }); .then((response) => {
commit('removeResource', payload)
})
.catch((error) => {
commit('catchError', error);
throw error;
})
}, },
addResource({ commit }, payload) { addResource({ commit }, payload) {
//console.log('## action: fetch postResource: payload', payload); const body = { type: "accompanying_period_resource" };
postResource(id, payload.result, 'POST') body['resource'] = { type: payload.result.type, id: payload.result.id };
.then(resource => new Promise((resolve, reject) => { const url = `/api/1.0/person/accompanying-course/${id}/resource.json`;
commit('addResource', resource)
resolve(); return makeFetch('POST', url, body)
})).catch((error) => { commit('catchError', error) }); .then((response) => {
commit('addResource', response);
})
.catch((error) => {
commit('catchError', error);
throw error;
})
}, },
/**
* On The Fly
*/
patchOnTheFly({ commit }, payload) { patchOnTheFly({ commit }, payload) {
// TODO should be into the dedicated component, no ? JF // TODO should be into the dedicated component, no ? JF
console.log('## action: patch OnTheFly', payload); console.log('## action: patch OnTheFly', payload);
@ -334,29 +377,48 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
})); }));
} }
}, },
/**
* Update accompanying course intensity/emergency/confidentiality
*/
toggleIntensity({ commit }, payload) { toggleIntensity({ commit }, payload) {
//console.log(payload); const url = `/api/1.0/person/accompanying-course/${id}.json`
patchAccompanyingCourse(id, { type: "accompanying_period", intensity: payload }) const body = { type: "accompanying_period", 'intensity': payload }
.then(course => new Promise((resolve, reject) => {
commit('toggleIntensity', course.intensity); return makeFetch('PATCH', url, body)
resolve(); .then((response) => {
})).catch((error) => { commit('catchError', error) }); commit('toggleIntensity', response.intensity);
})
.catch((error) => {
commit('catchError', error);
throw error;
})
}, },
toggleEmergency({ commit }, payload) { toggleEmergency({ commit }, payload) {
patchAccompanyingCourse(id, { type: "accompanying_period", emergency: payload }) const url = `/api/1.0/person/accompanying-course/${id}.json`
.then(course => new Promise((resolve, reject) => { const body = { type: "accompanying_period", emergency: payload }
commit('toggleEmergency', course.emergency);
return dispatch('setRefererresAvailable'); return makeFetch('PATCH', url, body)
})) .then((response) => {
.then(() => Promise.resolve()) commit('toggleEmergency', response.emergency);
.catch((error) => { commit('catchError', error) }); })
.catch((error) => {
commit('catchError', error);
throw error;
})
}, },
toggleConfidential({ commit }, payload) { toggleConfidential({ commit }, payload) {
patchAccompanyingCourse(id, { type: "accompanying_period", confidential: payload }) const url = `/api/1.0/person/accompanying-course/${id}.json`
.then(course => new Promise((resolve, reject) => { const body = { type: "accompanying_period", confidential: payload }
commit('toggleConfidential', course.confidential);
resolve(); return makeFetch('PATCH', url, body)
})).catch((error) => { commit('catchError', error) }); .then((response) => {
commit('toggleConfidential', response.confidential);
})
.catch((error) => {
commit('catchError', error);
throw error;
})
}, },
/** /**
* Handle the checked/unchecked scopes * Handle the checked/unchecked scopes
@ -411,19 +473,24 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
let lengthAfterOperation = currentServerScopesIds.length + addedScopesIds.length let lengthAfterOperation = currentServerScopesIds.length + addedScopesIds.length
- removedScopesIds.length; - removedScopesIds.length;
if (lengthAfterOperation > 0 || (lengthAfterOperation === 0 && state.scopesAtStart.length === 0) ) { return dispatch('updateScopes', {
return dispatch('updateScopes', { addedScopesIds, removedScopesIds
addedScopesIds, removedScopesIds })
}).then(() => { .catch(error => {
// warning: when the operation of dispatch are too slow, the user may check / uncheck before throw error;
// the end of the synchronisation with the server (done by dispatch operation). Then, it leads to })
// check/uncheck in the UI. I do not know of to avoid it. .then(() => {
commit('setScopes', scopes); // warning: when the operation of dispatch are too slow, the user may check / uncheck before
return Promise.resolve(); // the end of the synchronisation with the server (done by dispatch operation). Then, it leads to
}).then(() => { // check/uncheck in the UI. I do not know of to avoid it.
return dispatch('fetchReferrersSuggested'); commit('setScopes', scopes);
}); return Promise.resolve();
} else { })
.then(() => {
return dispatch('fetchReferrersSuggested');
});
/*
else {
return dispatch('setScopes', state.scopesAtStart).then(() => { return dispatch('setScopes', state.scopesAtStart).then(() => {
commit('setScopes', scopes); commit('setScopes', scopes);
return Promise.resolve(); return Promise.resolve();
@ -431,6 +498,7 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
return dispatch('fetchReferrersSuggested'); return dispatch('fetchReferrersSuggested');
}); });
} }
*/
}, },
/** /**
* Internal function for the store to effectively update scopes. * Internal function for the store to effectively update scopes.
@ -446,59 +514,99 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
*/ */
updateScopes({ state, commit }, { addedScopesIds, removedScopesIds }) { updateScopes({ state, commit }, { addedScopesIds, removedScopesIds }) {
let promises = []; let promises = [];
const url = `/api/1.0/person/accompanying-course/${state.accompanyingCourse.id}/scope.json`;
state.scopes.forEach(scope => { state.scopes.forEach(scope => {
const body = {
id: scope.id,
type: scope.type
};
if (addedScopesIds.includes(scope.id)) { if (addedScopesIds.includes(scope.id)) {
promises.push(addScope(state.accompanyingCourse.id, scope).then(() => { promises.push(
commit('addScopeAtBackend', scope); makeFetch('POST', url, body)
return Promise.resolve(); .then((response) => {
})); commit('addScopeAtBackend', scope);
return response;
})
.catch((error) => {
commit('catchError', error);
throw error;
})
)
} }
if (removedScopesIds.includes(scope.id)) { if (removedScopesIds.includes(scope.id)) {
promises.push(removeScope(state.accompanyingCourse.id, scope).then(() => { promises.push(
commit('removeScopeAtBackend', scope); makeFetch('DELETE', url, body)
return Promise.resolve(); .then((response) => {
})); commit('removeScopeAtBackend', scope);
return response;
})
.catch((error) => {
commit('catchError', error);
throw error;
})
);
} }
}); });
return Promise.all(promises); return Promise.all(promises);
}, },
postFirstComment({ commit }, payload) { postFirstComment({ commit }, payload) {
//console.log('## action: postFirstComment: payload', payload); const url = `/api/1.0/person/accompanying-course/${id}.json`
patchAccompanyingCourse(id, { type: "accompanying_period", initialComment: payload }) const body = { type: "accompanying_period", initialComment: payload }
.then(course => new Promise((resolve, reject) => {
commit('postFirstComment', course.initialComment); return makeFetch('PATCH', url, body)
resolve(); .then((response) => {
})).catch((error) => { commit('catchError', error) }); commit('postFirstComment', response.initialComment);
})
.catch((error) => {
commit('catchError', error);
throw error;
})
}, },
updateSocialIssues({ state, commit, dispatch }, { payload, body, method }) { updateSocialIssues({ state, commit, dispatch }, { payload, body, method }) {
//console.log('## action: payload', { payload, body, method }); const url = `/api/1.0/person/accompanying-course/${id}/socialissue.json`;
postSocialIssue(id, body, method) return makeFetch(method, url, body)
.then(response => new Promise((resolve, reject) => { .then((response) => {
//console.log('response', response); commit('updateSocialIssues', payload);
commit('updateSocialIssues', payload); })
resolve(); .then(() => {
})).then(() => { return getAccompanyingCourse(state.accompanyingCourse.id)
return getAccompanyingCourse(state.accompanyingCourse.id); })
}).then(accompanying_course => { .then(accompanying_course => {
commit('refreshSocialIssues', accompanying_course.socialIssues); commit('refreshSocialIssues', accompanying_course.socialIssues);
dispatch('fetchReferrersSuggested'); dispatch('fetchReferrersSuggested');
}) })
.catch((error) => { commit('catchError', error) }); .catch((error) => {
commit('catchError', error);
throw error;
})
}, },
updateOrigin({ commit }, payload) { updateOrigin({ commit }, payload) {
patchAccompanyingCourse(id, { type: "accompanying_period", origin: { id: payload.id, type: payload.type } }) const url = `/api/1.0/person/accompanying-course/${id}.json`
.then(course => new Promise((resolve, reject) => { const body = { type: "accompanying_period", origin: { id: payload.id, type: payload.type }}
commit('updateOrigin', course.origin);
resolve(); return makeFetch('PATCH', url, body)
})).catch((error) => { commit('catchError', error) }); .then((response) => {
commit('updateOrigin', response.origin);
})
.catch((error) => {
commit('catchError', error);
throw error;
})
}, },
updateReferrer({ commit }, payload) { updateReferrer({ commit }, payload) {
patchAccompanyingCourse(id, { type: "accompanying_period", user: { id: payload.id, type: payload.type } }) const url = `/api/1.0/person/accompanying-course/${id}.json`
.then(course => new Promise((resolve, reject) => { const body = { type: "accompanying_period", user: { id: payload.id, type: payload.type }}
commit('updateReferrer', course.user);
resolve(); return makeFetch('PATCH', url, body)
})).catch((error) => { commit('catchError', error) }); .then((response) => {
commit('updateReferrer', response.user);
})
.catch((error) => {
commit('catchError', error);
throw error;
})
}, },
async fetchReferrersSuggested({ state, commit}) { async fetchReferrersSuggested({ state, commit}) {
let users = await getReferrersSuggested(state.accompanyingCourse); let users = await getReferrersSuggested(state.accompanyingCourse);
@ -519,6 +627,7 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
}, },
updateLocation({ commit, dispatch }, payload) { updateLocation({ commit, dispatch }, payload) {
//console.log('## action: updateLocation', payload.locationStatusTo); //console.log('## action: updateLocation', payload.locationStatusTo);
const url = `/api/1.0/person/accompanying-course/${payload.targetId}.json`;
let body = { 'type': payload.target, 'id': payload.targetId }; let body = { 'type': payload.target, 'id': payload.targetId };
let location = {}; let location = {};
if (payload.locationStatusTo === 'person') { // patch for person address (don't remove addressLocation) if (payload.locationStatusTo === 'person') { // patch for person address (don't remove addressLocation)
@ -531,26 +640,31 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
location = { 'personLocation': null }; location = { 'personLocation': null };
} }
Object.assign(body, location); Object.assign(body, location);
patchAccompanyingCourse(payload.targetId, body) makeFetch('PATCH', url, body)
.then(accompanyingCourse => new Promise((resolve, reject) => { .then((response) => {
commit('updateLocation', { commit('updateLocation', {
location: accompanyingCourse.location, location: response.location,
locationStatus: accompanyingCourse.locationStatus, locationStatus: response.locationStatus,
personLocation: accompanyingCourse.personLocation personLocation: response.personLocation
}); });
resolve(); dispatch('fetchReferrersSuggested');
dispatch('fetchReferrersSuggested'); })
})).catch((error) => { commit('catchError', error) }); .catch((error) => {
commit('catchError', error);
throw error;
})
}, },
confirmAccompanyingCourse({ commit }) { confirmAccompanyingCourse({ commit }) {
//console.log('## action: confirmAccompanyingCourse'); const url = `/api/1.0/person/accompanying-course/${id}/confirm.json`
confirmAccompanyingCourse(id) makeFetch('POST', url)
.then(response => new Promise((resolve, reject) => { .then((response) => {
window.location.replace(`/fr/parcours/${id}`); window.location.replace(`/fr/parcours/${id}`);
//console.log('fetch resolve'); commit('confirmAccompanyingCourse', response);
commit('confirmAccompanyingCourse', response); })
resolve(); .catch((error) => {
})).catch((error) => { commit('catchError', error) }); commit('catchError', error);
throw error;
})
} }
} }
}); });

View File

@ -18,5 +18,5 @@ use Symfony\Component\Validator\Constraint;
*/ */
class ParticipationOverlap extends Constraint class ParticipationOverlap extends Constraint
{ {
public $message = 'This participation already exists.'; public $message = '{{ name }} is already associated to this accompanying course.';
} }

View File

@ -13,6 +13,8 @@ namespace Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod;
use Chill\MainBundle\Util\DateRangeCovering; use Chill\MainBundle\Util\DateRangeCovering;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Templating\Entity\PersonRender;
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\ConstraintValidator;
@ -22,6 +24,16 @@ class ParticipationOverlapValidator extends ConstraintValidator
{ {
private const MAX_PARTICIPATION = 1; private const MAX_PARTICIPATION = 1;
private PersonRender $personRender;
private ThirdPartyRender $thirdpartyRender;
public function __construct(PersonRender $personRender, ThirdPartyRender $thirdPartyRender)
{
$this->personRender = $personRender;
$this->thirdpartyRender = $thirdPartyRender;
}
public function validate($participations, Constraint $constraint) public function validate($participations, Constraint $constraint)
{ {
if (!$constraint instanceof ParticipationOverlap) { if (!$constraint instanceof ParticipationOverlap) {
@ -36,7 +48,10 @@ class ParticipationOverlapValidator extends ConstraintValidator
return; return;
} }
$overlaps = new DateRangeCovering(self::MAX_PARTICIPATION, $participations[0]->getStartDate()->getTimezone()); if (0 === count($participations)) {
return;
}
$participationList = []; $participationList = [];
foreach ($participations as $participation) { foreach ($participations as $participation) {
@ -51,26 +66,29 @@ class ParticipationOverlapValidator extends ConstraintValidator
foreach ($participationList as $group) { foreach ($participationList as $group) {
if (count($group) > 1) { if (count($group) > 1) {
$overlaps = new DateRangeCovering(self::MAX_PARTICIPATION, $participations[0]->getStartDate()->getTimezone());
foreach ($group as $p) { foreach ($group as $p) {
$overlaps->add($p->getStartDate(), $p->getEndDate(), $p->getId()); $overlaps->add($p->getStartDate(), $p->getEndDate(), $p->getId());
} }
}
}
$overlaps->compute(); $overlaps->compute();
if ($overlaps->hasIntersections()) { if ($overlaps->hasIntersections()) {
foreach ($overlaps->getIntersections() as [$start, $end, $ids]) { foreach ($overlaps->getIntersections() as [$start, $end, $ids]) {
$msg = null === $end ? $constraint->message : $msg = null === $end ? $constraint->message :
$constraint->message; $constraint->message;
$this->context->buildViolation($msg) $this->context->buildViolation($msg)
->setParameters([ ->setParameters([
'{{ start }}' => $start->format('d-m-Y'), '{{ start }}' => $start->format('d-m-Y'),
'{{ end }}' => null === $end ? null : $end->format('d-m-Y'), '{{ end }}' => null === $end ? null : $end->format('d-m-Y'),
'{{ ids }}' => $ids, '{{ ids }}' => $ids,
]) '{{ name }}' => $this->personRender->renderString($participation->getPerson(), []),
->addViolation(); ])
->addViolation();
}
}
} }
} }
} }

View File

@ -42,6 +42,10 @@ class ResourceDuplicateCheckValidator extends ConstraintValidator
throw new UnexpectedTypeException($resources, Collection::class); throw new UnexpectedTypeException($resources, Collection::class);
} }
if (0 === count($resources)) {
return;
}
$resourceList = []; $resourceList = [];
foreach ($resources as $resource) { foreach ($resources as $resource) {

View File

@ -43,4 +43,6 @@ household_membership:
Person with membership covering: Une personne ne peut pas appartenir à deux ménages simultanément. Or, avec cette modification, %person_name% appartiendrait à %nbHousehold% ménages à partir du %from%. Person with membership covering: Une personne ne peut pas appartenir à deux ménages simultanément. Or, avec cette modification, %person_name% appartiendrait à %nbHousehold% ménages à partir du %from%.
# Accompanying period # Accompanying period
'{{ name }} is already associated to this accompanying course.': '{{ name }} est déjà associé avec ce parcours.' '{{ name }} is already associated to this accompanying course.': '{{ name }} est déjà associé à ce parcours.'
A course must contains at least one social issue: 'Un parcours doit être associé à au moins une problématique sociale'
A course must be associated to at least one scope: 'Un parcours doit être associé à au moins un service'