diff --git a/CHANGELOG.md b/CHANGELOG.md index caf9a9249..eb3036107 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ and this project adheres to * [list for accompanying course in person] filter list using ACL * [validation] toasts are displayed for errors when modifying accompanying course (generalization required). * add an endpoint for checking permissions. See https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/232 +* [activity] for a new activity: suggest and create on-the-fly locations based on the accompanying course location + location of the suggested parties +* [calendar] for a new rdv: suggest and create on-the-fly locations based on the accompanying course location + location of the suggested parties ## Test releases diff --git a/src/Bundle/ChillActivityBundle/Controller/ActivityController.php b/src/Bundle/ChillActivityBundle/Controller/ActivityController.php index f4248b5e8..0c43c4d91 100644 --- a/src/Bundle/ChillActivityBundle/Controller/ActivityController.php +++ b/src/Bundle/ChillActivityBundle/Controller/ActivityController.php @@ -137,7 +137,7 @@ final class ActivityController extends AbstractController static fn (ActivityReason $ar): int => $ar->getId() ) ->toArray(), - 'type_id' => $activity->getType()->getId(), + 'type_id' => $activity->getActivityType()->getId(), 'duration' => $activity->getDurationTime() ? $activity->getDurationTime()->format('U') : null, 'date' => $activity->getDate()->format('Y-m-d'), 'attendee' => $activity->getAttendee(), @@ -194,7 +194,7 @@ final class ActivityController extends AbstractController $form = $this->createForm(ActivityType::class, $entity, [ 'center' => $entity->getCenter(), 'role' => new Role('CHILL_ACTIVITY_UPDATE'), - 'activityType' => $entity->getType(), + 'activityType' => $entity->getActivityType(), 'accompanyingPeriod' => $accompanyingPeriod, ])->handleRequest($request); @@ -327,7 +327,7 @@ final class ActivityController extends AbstractController $entity->setAccompanyingPeriod($accompanyingPeriod); } - $entity->setType($activityType); + $entity->setActivityType($activityType); $entity->setDate(new DateTime('now')); if ($request->query->has('activityData')) { @@ -385,7 +385,7 @@ final class ActivityController extends AbstractController $form = $this->createForm(ActivityType::class, $entity, [ 'center' => $entity->getCenter(), 'role' => new Role('CHILL_ACTIVITY_CREATE'), - 'activityType' => $entity->getType(), + 'activityType' => $entity->getActivityType(), 'accompanyingPeriod' => $accompanyingPeriod, ])->handleRequest($request); diff --git a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/api.js b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/api.js index 9dab14ef1..8d4bcac3b 100644 --- a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/api.js +++ b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/api.js @@ -1,4 +1,5 @@ import { getSocialIssues } from 'ChillPersonAssets/vuejs/AccompanyingCourse/api.js'; +import { fetchResults } from 'ChillMainAssets/lib/api/apiMethods'; /* * Load socialActions by socialIssue (id) @@ -12,33 +13,20 @@ const getSocialActionByIssue = (id) => { }); }; -/* -* Load Locations - */ -const getLocations = () => { - const url = `/api/1.0/main/location.json`; - return fetch(url) - .then(response => { - if (response.ok) { return response.json(); } - throw Error('Error with request resource response'); - }); -}; +const getLocations = () => fetchResults('/api/1.0/main/location.json'); + +const getLocationTypes = () => fetchResults('/api/1.0/main/location-type.json'); /* -* Load Location Types +* Load Location Type by defaultFor +* @param {string} entity - can be "person" or "thirdparty" */ -const getLocationTypes = () => { - const url = `/api/1.0/main/location-type.json`; - return fetch(url) - .then(response => { - if (response.ok) { return response.json(); } - throw Error('Error with request resource response'); - }); +const getLocationTypeByDefaultFor = (entity) => { + return getLocationTypes().then(results => + results.filter(t => t.defaultFor === entity)[0] + ); }; -/* -* Post a Location - */ const postLocation = (body) => { const url = `/api/1.0/main/location.json`; return fetch(url, { @@ -59,5 +47,6 @@ export { getSocialActionByIssue, getLocations, getLocationTypes, + getLocationTypeByDefaultFor, postLocation }; diff --git a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/Location.vue b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/Location.vue index 5fa3360b7..9a4b78334 100644 --- a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/Location.vue +++ b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/Location.vue @@ -2,10 +2,9 @@
- + group-values="locations" + group-label="locationGroup" + v-model="location" + > @@ -27,49 +29,146 @@ diff --git a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/Location/NewLocation.vue b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/Location/NewLocation.vue index 2920e15b2..35bf9a065 100644 --- a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/Location/NewLocation.vue +++ b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/Location/NewLocation.vue @@ -214,11 +214,9 @@ export default { return cond; }, getLocationTypesList() { - getLocationTypes().then(response => new Promise(resolve => { - console.log('getLocationTypes', response); - this.locationTypes = response.results.filter(t => t.availableForUsers === true); - resolve(); - })) + getLocationTypes().then(results => { + this.locationTypes = results.filter(t => t.availableForUsers === true); + }) }, openModal() { this.modal.showModal = true; @@ -247,7 +245,6 @@ export default { postLocation(body) .then( location => new Promise(resolve => { - console.log('postLocation', location); this.locations.push(location); this.$store.dispatch('updateLocation', location); resolve(); diff --git a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.js b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.js index ef9cf3b1f..5b6297553 100644 --- a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.js +++ b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.js @@ -1,5 +1,6 @@ import 'es6-promise/auto'; import { createStore } from 'vuex'; +import { postLocation } from './api'; const debug = process.env.NODE_ENV !== 'production'; //console.log('window.activity', window.activity); @@ -27,7 +28,6 @@ const store = createStore({ }, getters: { suggestedEntities(state) { - console.log(state.activity); if (typeof state.activity.accompanyingPeriod === "undefined") { return []; } @@ -303,7 +303,33 @@ const store = createStore({ let hiddenLocation = document.getElementById( "chill_activitybundle_activity_location" ); - hiddenLocation.value = value.id; + if (value.onthefly) { + const body = { + "type": "location", + "name": value.name === '__AccompanyingCourseLocation__' ? null : value.name, + "locationType": { + "id": value.locationType.id, + "type": "location-type" + } + }; + if (value.address.id) { + Object.assign(body, { + "address": { + "id": value.address.id + }, + }) + } + postLocation(body) + .then( + location => hiddenLocation.value = location.id + ).catch( + err => { + console.log(err.message); + } + ); + } else { + hiddenLocation.value = value.id; + } commit("updateLocation", value); }, }, diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig index a30034257..e9782b8e1 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig @@ -67,8 +67,8 @@
{% if entity.location is not null %}

- {{ entity.location.locationType.title|localize_translatable_string }} {{ entity.location.name }} + ({{ entity.location.locationType.title|localize_translatable_string }})

{{ entity.location.address|chill_entity_render_box }} {% else %} diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php index 0165142f7..6e6bce842 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php @@ -332,12 +332,12 @@ class CalendarController extends AbstractController $personsId = array_map( static fn (Person $p): int => $p->getId(), - $entity->getPersons() + $entity->getPersons()->toArray() ); $professionalsId = array_map( static fn (ThirdParty $thirdParty): ?int => $thirdParty->getId(), - $entity->getProfessionals() + $entity->getProfessionals()->toArray() ); $durationTime = $entity->getEndDate()->diff($entity->getStartDate()); diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store.js b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store.js index 457d31799..edfb7f236 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store.js +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store.js @@ -1,5 +1,6 @@ import 'es6-promise/auto'; import { createStore } from 'vuex'; +import { postLocation } from 'ChillActivityAssets/vuejs/Activity/api'; const debug = process.env.NODE_ENV !== 'production'; @@ -33,7 +34,6 @@ const store = createStore({ }, getters: { suggestedEntities(state) { - console.log(state.activity) if (typeof(state.activity.accompanyingPeriod) === 'undefined') { return []; } @@ -189,8 +189,35 @@ const store = createStore({ updateLocation({ commit }, value) { console.log('### action: updateLocation', value); let hiddenLocation = document.getElementById("chill_calendarbundle_calendar_location"); - hiddenLocation.value = value.id; - commit('updateLocation', value); + if (value.onthefly) { + const body = { + "type": "location", + "name": value.name === '__AccompanyingCourseLocation__' ? null : value.name, + "locationType": { + "id": value.locationType.id, + "type": "location-type" + } + }; + if (value.address.id) { + Object.assign(body, { + "address": { + "id": value.address.id + }, + }) + } + postLocation(body) + .then( + location => hiddenLocation.value = location.id + ).catch( + err => { + console.log(err.message); + } + ); + } else { + hiddenLocation.value = value.id; + } + commit("updateLocation", value); + } } diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/show.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/show.html.twig index 5dfde6fc3..7c59996d2 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/show.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/show.html.twig @@ -41,8 +41,8 @@
{% if entity.location is not null %}

- {{ entity.location.locationType.title|localize_translatable_string }} {{ entity.location.name }} + ({{ entity.location.locationType.title|localize_translatable_string }})

{{ entity.location.address|chill_entity_render_box }} {% else %} diff --git a/src/Bundle/ChillMainBundle/Controller/LocationApiController.php b/src/Bundle/ChillMainBundle/Controller/LocationApiController.php index 52b6f1e81..16fc414d9 100644 --- a/src/Bundle/ChillMainBundle/Controller/LocationApiController.php +++ b/src/Bundle/ChillMainBundle/Controller/LocationApiController.php @@ -10,8 +10,6 @@ namespace Chill\MainBundle\Controller; use Chill\MainBundle\CRUD\Controller\ApiController; -use DateInterval; -use DateTime; use Symfony\Component\HttpFoundation\Request; /** @@ -21,22 +19,11 @@ class LocationApiController extends ApiController { public function customizeQuery(string $action, Request $request, $query): void { - $query->andWhere($query->expr()->orX( - $query->expr()->andX( - $query->expr()->eq('e.createdBy', ':user'), - $query->expr()->gte('e.createdAt', ':dateBefore') - ), + $query->andWhere( $query->expr()->andX( $query->expr()->eq('e.availableForUsers', "'TRUE'"), $query->expr()->eq('e.active', "'TRUE'"), - $query->expr()->isNotNull('e.name'), - $query->expr()->neq('e.name', ':emptyString'), ) - )) - ->setParameters([ - 'user' => $this->getUser(), - 'dateBefore' => (new DateTime())->sub(new DateInterval('P6M')), - 'emptyString' => '', - ]); + ); } } diff --git a/src/Bundle/ChillMainBundle/Entity/LocationType.php b/src/Bundle/ChillMainBundle/Entity/LocationType.php index 8b171eb0f..b0d91a4f1 100644 --- a/src/Bundle/ChillMainBundle/Entity/LocationType.php +++ b/src/Bundle/ChillMainBundle/Entity/LocationType.php @@ -11,6 +11,7 @@ namespace Chill\MainBundle\Entity; use Chill\MainBundle\Repository\LocationTypeRepository; use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Serializer\Annotation as Serializer; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; @@ -20,9 +21,14 @@ use Symfony\Component\Serializer\Annotation\DiscriminatorMap; * @DiscriminatorMap(typeProperty="type", mapping={ * "location-type": LocationType::class * }) + * @UniqueEntity({"defaultFor"}) */ class LocationType { + public const DEFAULT_FOR_3PARTY = 'thirdparty'; + + public const DEFAULT_FOR_PERSON = 'person'; + public const STATUS_NEVER = 'never'; public const STATUS_OPTIONAL = 'optional'; @@ -53,6 +59,12 @@ class LocationType */ private string $contactData = self::STATUS_OPTIONAL; + /** + * @ORM\Column(type="string", nullable=true, length=32, unique=true) + * @Serializer\Groups({"read"}) + */ + private ?string $defaultFor = null; + /** * @ORM\Id * @ORM\GeneratedValue @@ -87,6 +99,11 @@ class LocationType return $this->contactData; } + public function getDefaultFor(): ?string + { + return $this->defaultFor; + } + public function getId(): ?int { return $this->id; @@ -125,6 +142,13 @@ class LocationType return $this; } + public function setDefaultFor(?string $defaultFor): self + { + $this->defaultFor = $defaultFor; + + return $this; + } + public function setTitle(array $title): self { $this->title = $title; diff --git a/src/Bundle/ChillMainBundle/Form/LocationTypeType.php b/src/Bundle/ChillMainBundle/Form/LocationTypeType.php index 1e4776f8f..41372b48e 100644 --- a/src/Bundle/ChillMainBundle/Form/LocationTypeType.php +++ b/src/Bundle/ChillMainBundle/Form/LocationTypeType.php @@ -71,6 +71,18 @@ final class LocationTypeType extends AbstractType ], 'expanded' => true, ] + ) + ->add( + 'defaultFor', + ChoiceType::class, + [ + 'choices' => [ + 'none' => null, + 'person' => LocationType::DEFAULT_FOR_PERSON, + 'thirdparty' => LocationType::DEFAULT_FOR_3PARTY, + ], + 'expanded' => true, + ] ); } } diff --git a/src/Bundle/ChillMainBundle/Resources/views/LocationType/index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/LocationType/index.html.twig index ec617c6c0..5402d5e89 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/LocationType/index.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/LocationType/index.html.twig @@ -11,6 +11,7 @@ {{ 'Address required'|trans }} {{ 'Contact data'|trans }} {{ 'Active'|trans }} + {{ 'Default for'|trans }} @@ -33,6 +34,7 @@ {%- endif -%} + {{ entity.defaultFor|trans }}
  • diff --git a/src/Bundle/ChillMainBundle/migrations/Version20211123093355.php b/src/Bundle/ChillMainBundle/migrations/Version20211123093355.php new file mode 100644 index 000000000..b6618d25e --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20211123093355.php @@ -0,0 +1,38 @@ +addSql('DROP INDEX UNIQ_A459B5CADD3E4105'); + $this->addSql('ALTER TABLE chill_main_location_type DROP defaultFor'); + } + + public function getDescription(): string + { + return 'Add defaultFor to LocationType'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_location_type ADD defaultFor VARCHAR(32) DEFAULT NULL'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_A459B5CADD3E4105 ON chill_main_location_type (defaultFor)'); + } +} diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 210227fbe..f8c474042 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -212,6 +212,10 @@ Location type: Type de localisation Phonenumber1: Numéro de téléphone Phonenumber2: Autre numéro de téléphone Configure location and location type: Configuration des localisations +Default for: Type de localisation par défaut pour +none: aucun +person: usager +thirdparty: tiers # circles / scopes Choose the circle: Choisir le cercle