From 78f66d4f143b90c92521890115c59185afc10de5 Mon Sep 17 00:00:00 2001 From: nobohan Date: Fri, 8 Oct 2021 12:36:48 +0200 Subject: [PATCH 01/39] Address: Assign origin = 3 for postalCode created by users --- .../public/vuejs/Address/components/AddAddress.vue | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue index 55d8a93d1..959c68c14 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue @@ -606,7 +606,7 @@ export default { let newPostcode = this.entity.selected.postcode; newPostcode = Object.assign(newPostcode, { 'country': {'id': this.entity.selected.country.id }, - }); + });//TODO why not assign postcodeBody here = Object.assign(postcodeBody, {'origin': 3}); ? console.log('writeNew postcode is true! newPostcode: ', newPostcode); newAddress = Object.assign(newAddress, { 'newPostcode': newPostcode @@ -638,9 +638,7 @@ export default { if ('newPostcode' in payload) { let postcodeBody = payload.newPostcode; - if (this.context.target.name === 'person') { // !!! maintain here ? - postcodeBody = Object.assign(postcodeBody, {'origin': 3}); - } + postcodeBody = Object.assign(postcodeBody, {'origin': 3}); console.log('juste before post new postcode', postcodeBody); return postPostalCode(postcodeBody) .then(postalCode => { From cae3defedbfb6c53b8b93147723b253772fa1e51 Mon Sep 17 00:00:00 2001 From: nobohan Date: Fri, 8 Oct 2021 14:50:09 +0200 Subject: [PATCH 02/39] address: zoom to postal code --- .../vuejs/Address/components/AddAddress/CitySelection.vue | 4 +++- .../Resources/public/vuejs/Address/components/EditPane.vue | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue index 9b6a7b87f..ef3683ec4 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue @@ -50,7 +50,7 @@ import VueMultiselect from 'vue-multiselect'; export default { name: 'CitySelection', components: { VueMultiselect }, - props: ['entity', 'focusOnAddress'], + props: ['entity', 'focusOnAddress', 'updateMapCenter'], emits: ['getReferenceAddresses'], data() { return { @@ -95,6 +95,7 @@ export default { return (value.code && value.name) ? `${value.code}-${value.name}` : ''; }, selectCity(value) { + console.log(value) this.entity.selected.city = value; this.entity.selected.postcode.name = value.name; this.entity.selected.postcode.code = value.code; @@ -102,6 +103,7 @@ export default { console.log('writeNew.postcode false, in selectCity'); this.$emit('getReferenceAddresses', value); this.focusOnAddress(); + this.updateMapCenter(value.center); }, listenInputSearch(query) { //console.log('listenInputSearch', query, this.isCitySelectorOpen); diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/EditPane.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/EditPane.vue index 9e257fe4a..2129a69d1 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/EditPane.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/EditPane.vue @@ -31,6 +31,7 @@ @@ -135,7 +136,7 @@ export default { } }, updateMapCenter(point) { - //console.log('point', point); + console.log('point', point); this.addressMap.center[0] = point.coordinates[1]; // TODO use reverse() this.addressMap.center[1] = point.coordinates[0]; this.$refs.addressMap.update(); // cast child methods From 2db9a98bfe14ce3a9f03033c525009dd9ec3cf97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Oct 2021 14:49:51 +0200 Subject: [PATCH 03/39] fix bootstrapping listener for person capitalization name --- .../Controller/PersonController.php | 2 +- .../EventListener/PersonEventListener.php | 35 +++++++------------ .../EventListener/PersonCreateEventTest.php | 14 ++------ .../services/doctrineEventListener.yaml | 16 +++++---- 4 files changed, 26 insertions(+), 41 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonController.php b/src/Bundle/ChillPersonBundle/Controller/PersonController.php index a7249dda0..f9190857c 100644 --- a/src/Bundle/ChillPersonBundle/Controller/PersonController.php +++ b/src/Bundle/ChillPersonBundle/Controller/PersonController.php @@ -268,7 +268,7 @@ final class PersonController extends AbstractController ) { $this->em->persist($person); - // $this->em->flush(); + $this->em->flush(); $this->lastPostDataReset(); if ($form->get('createPeriod')->isClicked()) { diff --git a/src/Bundle/ChillPersonBundle/EventListener/PersonEventListener.php b/src/Bundle/ChillPersonBundle/EventListener/PersonEventListener.php index 6c9b746d7..20855a166 100644 --- a/src/Bundle/ChillPersonBundle/EventListener/PersonEventListener.php +++ b/src/Bundle/ChillPersonBundle/EventListener/PersonEventListener.php @@ -12,28 +12,19 @@ use Symfony\Component\Validator\Exception\LogicException as ExceptionLogicExcept class PersonEventListener { - public function onPrePersist(LifecycleEventArgs $event): void + public function prePersistPerson(Person $person): void { - if($event->getObject() instanceof Person){ + $firstnameCaps = mb_convert_case(mb_strtolower($person->getFirstName()), MB_CASE_TITLE, 'UTF-8'); + $firstnameCaps = ucwords(strtolower($firstnameCaps), " \t\r\n\f\v'-"); + $person->setFirstName($firstnameCaps); - $person = $event->getObject(); - $firstnameCaps = mb_convert_case(mb_strtolower($person->getFirstName()), MB_CASE_TITLE, 'UTF-8'); - $firstnameCaps = ucwords(strtolower($firstnameCaps), " \t\r\n\f\v'-"); - $person->setFirstName($firstnameCaps); - - $lastnameCaps = mb_strtoupper($person->getLastName(), 'UTF-8'); - $person->setLastName($lastnameCaps); - - } elseif ($event->getObject() instanceof PersonAltName){ - - $altname = $event->getObject(); - $altnameCaps = mb_strtoupper($altname->getLabel(), 'UTF-8'); - $altname->setLabel($altnameCaps); - - } else { - - throw new LogicException('Entity must be a person or an altname'); - - } + $lastnameCaps = mb_strtoupper($person->getLastName(), 'UTF-8'); + $person->setLastName($lastnameCaps); } -} \ No newline at end of file + + public function prePersistAltName(PersonAltName $altname) + { + $altnameCaps = mb_strtoupper($altname->getLabel(), 'UTF-8'); + $altname->setLabel($altnameCaps); + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/EventListener/PersonCreateEventTest.php b/src/Bundle/ChillPersonBundle/Tests/EventListener/PersonCreateEventTest.php index c8690a237..fb878053f 100644 --- a/src/Bundle/ChillPersonBundle/Tests/EventListener/PersonCreateEventTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/EventListener/PersonCreateEventTest.php @@ -20,11 +20,7 @@ class PersonCreateEventTest extends TestCase $person->setFirstName($firstname); $person->setLastName($lastname); - $args = $this->createMock(LifecycleEventArgs::class); - $args->method('getObject') - ->willReturn($person); - - $listener->onPrePersist($args); + $listener->prePersistPerson($person); $this->assertEquals($firstnameExpected, $person->getFirstName()); $this->assertEquals($lastnameExpected, $person->getLastName()); @@ -41,11 +37,7 @@ class PersonCreateEventTest extends TestCase $personAltname->setLabel($altname); - $args = $this->createMock(LifecycleEventArgs::class); - $args->method('getObject') - ->willReturn($personAltname); - - $listener->onPrePersist($args); + $listener->prePersistAltName($personAltname); $this->assertEquals($altnameExpected, $personAltname->getLabel()); } @@ -66,4 +58,4 @@ class PersonCreateEventTest extends TestCase yield ['fastré', 'FASTRÉ']; yield ['émile', 'ÉMILE']; } -} \ No newline at end of file +} diff --git a/src/Bundle/ChillPersonBundle/config/services/doctrineEventListener.yaml b/src/Bundle/ChillPersonBundle/config/services/doctrineEventListener.yaml index 6d038d7a5..f75243d02 100644 --- a/src/Bundle/ChillPersonBundle/config/services/doctrineEventListener.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/doctrineEventListener.yaml @@ -1,12 +1,14 @@ services: Chill\PersonBundle\EventListener\PersonEventListener: autoconfigure: true - tags: - - - name: 'doctrine.orm.entity_listener' - event: 'onPrePersist' - entity: 'Chill\PersonBundle\Entity\Person' + tags: - name: 'doctrine.orm.entity_listener' - event: 'onPrePersist' - entity: 'Chill\PersonBundle\Entity\PersonAltName' \ No newline at end of file + event: 'prePersist' + entity: 'Chill\PersonBundle\Entity\Person' + method: 'prePersistPerson' + - + name: 'doctrine.orm.entity_listener' + event: 'prePersist' + entity: 'Chill\PersonBundle\Entity\PersonAltName' + method: 'prePersistAltName' From b5394a3de2929a925fa3c067203372aec62d9b7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Oct 2021 14:52:51 +0200 Subject: [PATCH 04/39] use entity_render_string in titles --- .../ChillPersonBundle/Resources/views/Person/edit.html.twig | 4 ++-- .../ChillPersonBundle/Resources/views/Person/view.html.twig | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Person/edit.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Person/edit.html.twig index 4c7fd0c9a..2bb7fa4bb 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Person/edit.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Person/edit.html.twig @@ -18,12 +18,12 @@ {% set activeRouteKey = '' %} -{% block title %}{{ 'Update details for %name%'|trans({ '%name%': person.firstName|capitalize ~ ' ' ~ person.lastName } )|capitalize }}{% endblock %} +{% block title %}{{ 'Update details for %name%'|trans({ '%name%': person|chill_entity_render_string } ) }}{% endblock %} {% block personcontent %}
-

{{ 'Update details for %name%'|trans({ '%name%': person.firstName|capitalize ~ ' ' ~ person.lastName|capitalize } ) }}

+

{{ block('title') }}

{% form_theme form '@ChillMain/Form/fields.html.twig' %} {{ form_start(form) }} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig index c5cc8478c..a67723f27 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig @@ -23,8 +23,7 @@ This view should receive those arguments: - person #} -{% block title %}{{ 'Person details'|trans|capitalize ~ ' ' ~ person.firstName|capitalize ~ - ' ' ~ person.lastName }}{% endblock %} +{% block title %}{{ 'Person details'|trans|capitalize ~ ' ' ~ person|chill_entity_render_string }}{% endblock %} {# we define variables to include an edit form repeated multiple time across the page From 47897b5fe3014a15de804b330037be7dc70db3c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Oct 2021 14:54:08 +0200 Subject: [PATCH 05/39] update changelog [ci-skip] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d796f0f4..1cd4f1e1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to * filter thirdparties in list * [FilterOrder]: add development kit for generating filter and ordering in list +* [Capitalization of names] person names are capitalized on creation, on prePersist event ## Test releases From 992b320820d1a95dcca067991213cddb03f4662c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Oct 2021 15:01:37 +0200 Subject: [PATCH 06/39] replace continue instruction by equivalent break in switch --- .../ChillMainBundle/Form/Type/Listing/FilterOrderType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php b/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php index 1068301b0..3976625a1 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php @@ -32,7 +32,7 @@ final class FilterOrderType extends \Symfony\Component\Form\AbstractType foreach ($this->requestStack->getCurrentRequest()->query->getIterator() as $key => $value) { switch($key) { case 'q': - continue; + break; case 'page': $builder->add($key, HiddenType::class, [ 'data' => 1 From 76f84934d42c0c3162b29fe29e83dd93b8caf1b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Oct 2021 17:36:20 +0200 Subject: [PATCH 07/39] update changelog with today release --- CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cd4f1e1e..118b75fda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ and this project adheres to ## Unreleased + +## Test releases + +### Test release 2021-10-11 + * add 3 new fields to PostalCode and adapt postal code command and fixtures * [Aside activity] Fixes for aside activity @@ -30,8 +35,6 @@ and this project adheres to * [FilterOrder]: add development kit for generating filter and ordering in list * [Capitalization of names] person names are capitalized on creation, on prePersist event -## Test releases - ### test release 2021-10-04 * [Household editor][UI] Update how household suggestion and addresses are picked; From 799ed0de5cdb1669be2a4e5597286a96a780bc97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Oct 2021 18:43:39 +0000 Subject: [PATCH 08/39] Add new file --- CONVENTIONS.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 CONVENTIONS.md diff --git a/CONVENTIONS.md b/CONVENTIONS.md new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/CONVENTIONS.md @@ -0,0 +1 @@ + From cb29e1e8be334848dab48981dd259492cedf36c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Oct 2021 18:44:25 +0000 Subject: [PATCH 09/39] Update CONVENTIONS.md [ci-skip] --- CONVENTIONS.md | 382 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 382 insertions(+) diff --git a/CONVENTIONS.md b/CONVENTIONS.md index 8b1378917..4dcdb08ea 100644 --- a/CONVENTIONS.md +++ b/CONVENTIONS.md @@ -1 +1,383 @@ +# Conventions Chill + +en cours de rédaction + + +## Assets: nommage des entrypoints + +Trois types d'entrypoint: + +* application vue (souvent spécifique à une page) -> préfixé par `vue_`; +* code js/css qui est réutilisé à plusieurs endroits: + * ckeditor + * async_upload (utilisé pour un formulaire) + * bootstrap + * chill.js + * ... + + => on préfixe `mod_` +* code css ou js pour une seule page + * ré-utilise parfois des "foncitionnalités": ShowHide, ... + => on préfixe `page_` + + +Arborescence: + +``` +# Sous Resources/public + +- chill/ => theme (chill) + - chillmain.scss -> push dans l'entrypoint chill +- lib/ => ne vont jamais dans un entrypoint, mais sont ré-utilisés par d'autres + - ShowHide + - Collection + - Select2 +- module/ => termine dans des entrypoints ré-utilisables (mod_) + - bootstrap + - custom.scss + - custom/ + - variables.scss + - .. + - forkawesome + - AsyncUpload +- vue/ => uniquement application vue (vue_) + - _components + - app +- page/ => uniquement pour une seule page (page_) + - login + - person + - personvendee + - household_edit_metadata + - index.js +``` + +## Organisation des feuilles de styles + +Comment s'échaffaudent les styles dans Chill ? + + +1. l'entrypoint **mod_bootstrap** (module bootstrap) est le premier niveau. Toutes les parties(modules) de bootstrap sont convoquées dans le fichier ```bootstrap.js``` situé dans ```ChillMainBundle/Resources/public/module/bootstrap```. + * Au début, ce fichier importe le fichier ```variables.scss``` qui détermine la plupart des réglages bootstrap tels qu'on les a personnalisés. Ce fichier surcharge l'original, et de nombreuses variables y sont adaptées pour Chill. + * On veillera à ce qu'on puisse toujours comparer ce fichier à l'original de bootstrap. En cas de mise à jour de bootstrap, il faudra générer un diff, et adapter ce diff sur le fichier variable de la nouvelle version. + * A la fin on importe le fichier ```custom.scss```, qui comprends des adaptations de bootstrap pour le préparer à notre thème Chill. + * ce ```custom.scss``` peut être splitté en plus petits fichiers avec des ```@import 'custom/...'``` + * L'idée est que cette première couche bootstrap règle un partie importante des styles de l'application, en particulier ce qui touche aux position du layout, aux points de bascules responsive, aux marges et écarts appliqués par défauts aux éléments qu'on manipule. + +2. l'entrypoint **chill** est le second niveau. Il contient le thème Chill qui est reconnaissable à l'application. + * Chaque bundle a un dossier ```Resources/public/chill``` dans lequel on peut trouver une feuille sass principale, qui est éventuellement splittée avec des ```@imports```. Toutes ces feuilles sont compilées dans un unique entrypoint Chill, c'est le thème de l'application. Celui-ci surcharge bootstrap. + * La feuille chillmain.scss devrait contenir les cascades de styles les plus générales, celles qui sont appliquées à de nombreux endroits de l'application. + * La feuille chillperson.scss va aussi retrouver des styles propres aux différents contextes des personnes: person, household et accompanyingcourse. + * Certains bundles plus secondaires ne contiennent que des styles spécifiques à leur fonctionnement. + +3. les entrypoints **vue_** sont utilisés pour des composants vue. Les fichiers vue peuvent contenir un bloc de styles scss. Ce sont des styles qui ne concernent que le composant et son héritage, le tag ```scoped``` précise justement sa portée (voir la doc). + +4. les entrypoints **page_** sont utilisés pour ajouter des assets spécifiques à certaines pages, le plus souvent des scripts et des styles. + + +## Taguer du code html et construire la cascade de styles + +L'exemple suivant montre comment taguer sans excès un élément de code. On remarque que: +* il n'est pas nécessaire de taguer toutes les classes intérieures, +* il ne faut pas répéter la classe parent dans toutes les classes enfants. La cascade sass va permettre de saisir le html avec souplesse sans alourdir la structure des balises. +* souvent la première classe sera déclinée par plusieurs classes qui commencent de la même manière: ```bloc-dark``` ajoute juste la version sombre de ```bloc```, on ne met pas ```bloc dark```, car on ne souhaite pas que la classe ```dark``` de ```bloc``` interagisse avec la même classe ```dark``` de ```table```. On aura donc un élément ```bloc bloc-dark``` et un élément ```table table-dark```. + +```html +
+

mon titre

+
    +
  • + +
  • +
  • +
  • +
+
+``` + +Finalement, il importe ici de définir ce qu'est un bloc, ce qu'est une zone d'actions et ce qu'est un bouton. Ces 3 éléments existent de manière autonome, ce sont les seuls qu'on tagge. + +Par exemple pour mettre un style au titre on précise juste h3 dans la cascade bloc. + +```sass +div.bloc { + // un bloc générique, utilisé à plusieurs endroits + &.bloc-dark { + // la version sombre du bloc + } + h3 {} + ul { + // une liste standard dans bloc + li { + // des items de liste standard dans bloc + } + } +} +div.mon-bloc { + // des exceptions spécifiques à mon-bloc, + // qui sont des adaptations de bloc +} + +ul.record_actions { + // va uniformiser tous les record_actions de l'application + li { + //... + } +} + +.btn { + // les boutons de bootstrap + .btn-edit { + // chill étends les boutons bootstrap pour ses propres besoins + } +} + +``` + +## Render box + + +## URL + +### Nommage des routes + +:::warning +Ces règles n'ont pas toujours été utilisées par le passé. Elles sont souhaitées pour le futur. +::: + +Les routes sont nommées de cette manière: + +`chill_bundle_entite_action` + +1. d'abord chill_ (pour tous les modules chill) +2. ensuite une string qui est identique, par bundle +3. si le point est un point d'api (json), alors ajouter la string `api` +4. ensuite une string qui indique sur quelle entité porte la route, voire également les sous-entités +5. ensuite une action (`list`, `view`, `edit`, `new`, ...) + +Le fait d'indiquer `api` en 3 permet de distinguer les routes d'api qui sont générées par la configuration (qui sont toutes préfixées par `chill_api`, de celles générées manuellement. (Exemple: `chill_api_household__index`, et `chill_person_api_household_members_move`) + +Si les points 4 et 5 sont inexistants, alors ils sont remplacés par d'autres éléments de manière à garantir l'unicité de la route, et sa bonne compréhension. + +### URL + +Les URL respectent également une convention: + +#### Pour les pages html + +:::warning +Ces règles n'ont pas toujours été utilisées par le passé. Elles sont souhaitées pour le futur. +::: + +Syntaxe: + +``` +/{_locale}/bundle/entity/{id}/action +/{_locale}/bundle/entity/sub-entity/{id}/action +``` + +Les éléments suivants devraient se trouver dans la liste: + +1. la locale; +2. un identifiant du bundle +3. l'entité auquel il se rapporte +4. les éventuelles sous-entités auxquelles l'url se rapport +5. l'action + +Ces éléments peuvent être entrecoupés de l'identifiant d'une entité. Dans ce cas, cet identifiant se place juste après l'entité auquel il se rapporte. + +Exemple: + +``` +# liste des échanges pour une personne +/fr/activity/person/25/activity/list + +# nouvelle activité +/fr/activity/activity/new?person_id=25 + +``` + +#### Pour les API + +:::info +Les routes générées automatiquement sont préfixées par chill_api +::: + +Syntaxe: + +``` +/api/1.0/bundle/entity/{id}/action +/api/1.0/bundle/entity/sub-entity/{id}/action +``` + +Les éléments suivants devraient se trouver dans la liste: + +1. la string `/api/` et puis la version (1.0) +2. un identifiant du bundle +3. l'entité auquel il se rapporte +4. les éventuelles sous-entités auxquelles l'url se rapport +5. l'action + +Ces éléments peuvent être entrecoupés de l'identifiant d'une entité. Dans ce cas, cet identifiant se place juste après l'entité auquel il se rapporte. + +## Règles UI chill + +### Titre des pages + +#### Chaque page contient un titre + +Chaque page contient un titre dans la balise head. Ce titre est normalement identique à celui de l'entête de la page. + +Astuce: il est possible d'utiliser la fonction `block` de twig pour cela: + +```htmlmixed= +{% block title "Titre de la page" %} + +{% block content %} +

+ {{ block('title')}} +

+{% endblock %} +``` + + +### Utilisation des `entity_render` + +#### En twig + +Les templates twig doivent toujours utiliser la fonction chill_entity_render_box pour effectuer le rendu des éléments suivants: + +* User +* Person +* SocialIssue +* SocialAction +* Address +* ThirdParty +* ... + +Exemple: + +``` +address|chill_entity_render_box +``` + +Justification: + +* des éléments sont parfois personnalisés par installation (par exemple, le nom de chaque utilisateur sera suivi par le nom du service) +* pour rationaliser et rendre semblable les affichages +* pour simplifier le code twig + +A prevoir: + +* toujours trois positions: + * inline + * block + * item (dans un tableau, une ligne) + +> block et item sont en fait la même option passée au render_box: render: bloc. Il y a aussi ‘raw’ pour le inline, et ‘label’ pour une titraille configurable avec des options. + +> quand on passe l’option render: bloc, on peut placer le render_box dans une boucle for plus large qui fonctionne avec la classe flex-table ou la classe flex-bloc, ce qui donnera un affichage en rangée (table) ou en blocs. [name=Mathieu] + + + +#### En vue + +Il existe systématiquement une "box" équivalente en vue. + +#### Lien vers des sections + +A chaque fois qu'on indique le nom d'une personne, un parcours, un ménage, il y a toujours: + +* un lien pour accéder à son dossier (pour autant que l'utilisateur ait les droits d'accès); +* à moins qu'il ne soit indiqué dans une phrase, l'icône de son dossier avant ou après (donc un bonhomme pour la personne, une maison pour le ménage, un fa-random pour les parcours); + +Ces éléments sont toujours proposé par des `render_box` par défaut. Des options permettent de les désactiver dans des cas particuliers + +> à discuter, quelques réflexion: +> quelle est la logique qui domine pour les boutons ? on a symbolisé les 4 actions du crud par des couleurs: bleu(show) orange(edit) vert(create) et rouge(delete). +> Est-ce que c'est ça qui prime, et comment ça s'articule avec la logique des pictos ? +> Par exemple, il pourrait être logique d'utiliser l'oeil bleu pour voir l'objet, qu'il s'agisse d'une personne ou d'un parcours, ce serait plutôt le contexte, et l'infobulle (title) qui préciserait le contexte. +> Je pense que les pictos de boutons doivent faire référence à l'action, mais pas à l'objet. Autrement dit je n'utiliserais jamais l'icone du ménage ou du parcours dans les boutons. +> Pour représenter les ménages et les parcours, je pense qu'il faudrait trouver autre chose que forkawesome. Si c'est des pictos, trouver un motif différents et de tailles différente. Réfléchir à un couplage picto-couleur-forme différent, qui exprime le contexte et qui se distingue bien des boutons. +> Idem pour les badges, il faut une palette de badge qui couvre tous les besoins: socialIssue, socialActions, socialReason, members, etc. [name=Mathieu] + +### Formulaires + +#### Vocabulaire: + +Utiliser toujours: + +* `Créer` dans un `bt bt-create` pour les **liens** vers le formulairep pour créer une entité (pour parvenir au formulaire); +* `Enregistrer` dans un `bt bt-save` pour les boutons "Enregistrer" (dans un formulaire édition **ou** création); +* `Enregistrer et nouveau` +* `Enregistrer et voir` +* `Modifier` dans un `bt bt-edit` pour les **liens** vers le formulaire d'édition +* `Dupliquer` (préciser là où on peut le voir) +* `Annuler` pour quitter une page d'édition avec un lien vers la liste, ou le `returnPath` + +#### Retour après un enregistrement + +Après avoir cliqué sur "Créer" ou "Sauver", la page devrait revenir: + +* vers le returnPath, s'il existe; +* sinon, vers la page "vue". + + +### Bandeaux contenant les boutons d'actions + +Les boutons sont toujours dans un bandeau "sticky-form" dans le bas du formulaire ou de la page de liste. + +Si pertinent: + +* Le bandeau contient un bouton "Annuler" qui retourne à la page précédente. Il est obligatoire pour les formulaires, optionnel pour les listes ou les pages "résumés" +* Ce bouton "annuler" est toujours à gauche + +``` + +``` + +### Messages flash + +#### A la création d'une entité + +A chaque fois qu'un élément est créé par un formulaire, un message flash doit apparaitre. Il indique: + +> "L'élément a été créé" + +Le nom de l'élément peut être remplacé par quelque chose de plus pertinent: + +> * L'activité a été créée +> * Le rendez-vous a été créé +> * ... + + +#### A l'enregistrement d'une entité + +A chaque fois qu'un élément est enregistré, un message flash doit apparaitre: + +> * Les données ont été modifiées +> + +#### Erreur sur un formulaire (erreur de validation) + +En tête d'un formulaire, un message flash doit indiquer que des validations n'ont pas réussi: + +> Ce formulaire contient des erreurs + +Les erreurs doivent apparaitre attachée au champ qui les concerne. Toutefois, il est acceptable d'afficher les erreurs à la racine du formulaire s'il était complexe, techniquement, d'attacher les erreurs. + +### Liens de retour + +A chaque fois qu'un lien est indiqué, vérifier si on ne doit pas utiliser la fonction `chill_return_path`, `chill_forward_return_path` ou `chill_return_path_or`. + +* depuis la page liste, vers l'ouverture d'un élément, ou le bouton création => utiliser `chill_path_add_return_path` +* dans ces pages d'éditions, + * utiliser `chill_return_path_or` dans le bouton "Cancel"; + * pour les boutons "enregistrer et voir" et "Enregistrer et fermer" => ? From acbb33918f06935eeae777375b7b7fb78493b9ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Oct 2021 17:09:38 +0200 Subject: [PATCH 10/39] improve translation --- .../public/vuejs/_components/OnTheFly/ThirdParty.vue | 4 ++-- src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue index 401cf1fa8..2710cd58e 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue +++ b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue @@ -77,8 +77,8 @@ const i18n = { messages: { fr: { tparty: { - contact: "Contact", - company: "Institution" + contact: "Personne physique", + company: "Personne morale" } } } diff --git a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml index 604eafbad..cee39297b 100644 --- a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml @@ -29,8 +29,8 @@ thirdparty.UpdateBy.short: ' par ' thirdparty.CreatedAt.long: Date de création thirdparty.UpdatedAt.long: Date de la dernière modification thirdparty.UpdateBy.long: Utilisateur qui a effectué la dernière modification -thirdparty.A company: Une institution -thirdparty.company: Institution +thirdparty.A company: Une personne morale +thirdparty.company: Personne morale thirdparty.A contact: Une personne physique thirdparty.contact: Personne physique thirdparty.a_company_explanation: >- From 313c17826e5e83a7e87d7383f7a4a748f89be1a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Oct 2021 17:34:52 +0200 Subject: [PATCH 11/39] translation of company & contact and show parent in list --- .../_components/AddPersons/TypeThirdParty.vue | 8 ++++---- .../ChillThirdPartyBundle/Entity/ThirdParty.php | 4 ++++ .../Resources/views/Entity/thirdparty.html.twig | 14 +++++++++++++- .../Templating/Entity/ThirdPartyRender.php | 3 ++- .../translations/messages.fr.yml | 6 ++++-- 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue index 4bb2e2d1e..2dbb7c8d0 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue @@ -19,8 +19,8 @@
- - {{ $t('thirdparty.contact')}} + + {{ $t('thirdparty.child')}} {{ $t('thirdparty.company')}} @@ -49,8 +49,8 @@ const i18n = { messages: { fr: { thirdparty: { - contact: "Contact", - company: "Institution", + contact: "Personne physique", + company: "Personne morale", child: "Personne de contact" } } diff --git a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php index b309c4542..38124b645 100644 --- a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php +++ b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php @@ -460,6 +460,10 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface */ public function getAddress(): ?Address { + if ($this->isChild()) { + return $this->getParent()->getAddress(); + } + return $this->address; } diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig index f7dcf3bc5..558062271 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig @@ -84,7 +84,7 @@ {% if thirdparty.kind == 'company' %} {{ 'thirdparty.company'|trans }} {% elseif thirdparty.kind == 'child' %} - {{ 'thirdparty.Child'|trans }} + {{ 'thirdparty.Child'|trans }} {% elseif thirdparty.kind == 'contact' %} {{ 'thirdparty.contact'|trans }} {% endif %} @@ -144,4 +144,16 @@ {% endfor %}
{% endif %} + {% if options['showParent'] and thirdparty.isChild %} +
+ {{ 'thirdparty.Contact of'|trans }} : + {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with { + targetEntity: { name: 'thirdparty', id: thirdparty.parent.id }, + action: 'show', + displayBadge: true, + buttonText: thirdparty.parent|chill_entity_render_string + } %} +
+ {% endif %} + {%- endif -%} diff --git a/src/Bundle/ChillThirdPartyBundle/Templating/Entity/ThirdPartyRender.php b/src/Bundle/ChillThirdPartyBundle/Templating/Entity/ThirdPartyRender.php index db53108d4..e239c4a76 100644 --- a/src/Bundle/ChillThirdPartyBundle/Templating/Entity/ThirdPartyRender.php +++ b/src/Bundle/ChillThirdPartyBundle/Templating/Entity/ThirdPartyRender.php @@ -61,7 +61,8 @@ class ThirdPartyRender extends AbstractChillEntityRender 'hLevel' => $options['hLevel'] ?? 3, 'customButtons' => $options['customButtons'] ?? [], 'customArea' => $options['customArea'] ?? [], - 'showContacts' => $options['showContacts'] ?? [], + 'showContacts' => $options['showContacts'] ?? false, + 'showParent' => $options['showParent'] ?? true, ]; return diff --git a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml index cee39297b..e6167840d 100644 --- a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml @@ -33,11 +33,13 @@ thirdparty.A company: Une personne morale thirdparty.company: Personne morale thirdparty.A contact: Une personne physique thirdparty.contact: Personne physique +thirdparty.Contact of: Contact de thirdparty.a_company_explanation: >- - Les institutions peuvent compter un ou plusieurs contacts, interne à l'instution. Il est également possible de + Les personnes morales peuvent compter un ou plusieurs contacts, interne à l'instution. Il est également possible de leur associer un acronyme, et le nom d'un service. thirdparty.a_contact_explanation: >- - Les personnes physiques ne disposent pas d'acronyme, de service, ou de contacts sous-jacents. + Les personnes physiques ne disposent pas d'acronyme, de service, ou de contacts sous-jacents. Il est possible de leur + indiquer une civilité et un métier. thirdparty.Which kind of third party ?: Quel type de tiers souhaitez-vous créer ? thirdparty.Contact data are confidential: Données de contact confidentielles From bc608832c2425326ee294fc148ace9b63fd8afa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Oct 2021 17:37:58 +0200 Subject: [PATCH 12/39] fill changelog [ci-skip] --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 118b75fda..991df9605 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ and this project adheres to ## Unreleased +* [3party]: french translation of contact and company +* [3party]: show parent in list +* [3party]: change color for badge "child" + ## Test releases From f4369553e12f91c4045d65abe695d48e1223c93f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Oct 2021 17:40:31 +0200 Subject: [PATCH 13/39] fix layout for address --- .../Resources/views/Entity/thirdparty.html.twig | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig index 558062271..5700acc0b 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig @@ -91,12 +91,14 @@
    - {{ thirdparty.getAddress|chill_entity_render_box({ - 'render': 'list', - 'with_picto': true, - 'multiline': false, - 'with_valid_from': false - }) }} +
  • + {{ thirdparty.getAddress|chill_entity_render_box({ + 'render': 'list', + 'with_picto': true, + 'multiline': false, + 'with_valid_from': false + }) }} +
  • {% if thirdparty.telephone %} {{ thirdparty.telephone|chill_format_phonenumber }} From e9192c5011fdfadc326bdb44cd689acd9eabaf7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 12 Oct 2021 09:28:53 +0200 Subject: [PATCH 14/39] adaptations for third party search --- .../Search/Entity/SearchUserApiProvider.php | 2 +- .../ChillMainBundle/Search/SearchApiQuery.php | 37 +++++++-- .../Tests/Search/SearchApiQueryTest.php | 40 ++++++++++ .../Search/SearchPersonApiProvider.php | 2 +- .../Search/ThirdPartyApiSearch.php | 76 ++++++++++++++++--- 5 files changed, 138 insertions(+), 19 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php diff --git a/src/Bundle/ChillMainBundle/Search/Entity/SearchUserApiProvider.php b/src/Bundle/ChillMainBundle/Search/Entity/SearchUserApiProvider.php index e4061cea1..6b8139419 100644 --- a/src/Bundle/ChillMainBundle/Search/Entity/SearchUserApiProvider.php +++ b/src/Bundle/ChillMainBundle/Search/Entity/SearchUserApiProvider.php @@ -27,7 +27,7 @@ class SearchUserApiProvider implements SearchApiInterface ->setSelectPertinence("GREATEST(SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical), SIMILARITY(LOWER(UNACCENT(?)), u.emailcanonical))", [ $pattern, $pattern ]) ->setFromClause("users AS u") - ->setWhereClause("SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical) > 0.15 + ->setWhereClauses("SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical) > 0.15 OR SIMILARITY(LOWER(UNACCENT(?)), u.emailcanonical) > 0.15 ", [ $pattern, $pattern ]); diff --git a/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php b/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php index 2c986ee6c..9188c0b5c 100644 --- a/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php +++ b/src/Bundle/ChillMainBundle/Search/SearchApiQuery.php @@ -12,8 +12,8 @@ class SearchApiQuery private array $pertinenceParams = []; private ?string $fromClause = null; private array $fromClauseParams = []; - private ?string $whereClause = null; - private array $whereClauseParams = []; + private array $whereClauses = []; + private array $whereClausesParams = []; public function setSelectKey(string $selectKey, array $params = []): self { @@ -47,16 +47,39 @@ class SearchApiQuery return $this; } - public function setWhereClause(string $whereClause, array $params = []): self + /** + * Set the where clause and replace all existing ones. + * + */ + public function setWhereClauses(string $whereClause, array $params = []): self { - $this->whereClause = $whereClause; - $this->whereClauseParams = $params; + $this->whereClauses = [$whereClause]; + $this->whereClausesParams = [$params]; + + return $this; + } + + /** + * Add a where clause. + * + * This will add to previous where clauses with and `AND` join + * + * @param string $whereClause + * @param array $params + * @return $this + */ + public function andWhereClause(string $whereClause, array $params = []): self + { + $this->whereClauses[] = $whereClause; + $this->whereClausesParams[] = $params; return $this; } public function buildQuery(): string { + $where = \implode(' AND ', $this->whereClauses); + return \strtr("SELECT '{key}' AS key, {metadata} AS metadata, @@ -68,7 +91,7 @@ class SearchApiQuery '{metadata}' => $this->jsonbMetadata, '{pertinence}' => $this->pertinence, '{from}' => $this->fromClause, - '{where}' => $this->whereClause, + '{where}' => $where, ]); } @@ -79,7 +102,7 @@ class SearchApiQuery $this->jsonbMetadataParams, $this->pertinenceParams, $this->fromClauseParams, - $this->whereClauseParams, + \array_merge([], ...$this->whereClausesParams), ); } } diff --git a/src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php b/src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php new file mode 100644 index 000000000..2e63f24e0 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Search/SearchApiQueryTest.php @@ -0,0 +1,40 @@ +setSelectJsonbMetadata('boum') + ->setSelectKey('bim') + ->setSelectPertinence('1') + ->setFromClause('badaboum') + ->andWhereClause('foo', [ 'alpha' ]) + ->andWhereClause('bar', [ 'beta' ]) + ; + + $query = $q->buildQuery(); + + $this->assertStringContainsString('foo AND bar', $query); + $this->assertEquals(['alpha', 'beta'], $q->buildParameters()); + } + + public function testWithoutWhereClause() + { + $q = new SearchApiQuery(); + $q->setSelectJsonbMetadata('boum') + ->setSelectKey('bim') + ->setSelectPertinence('1') + ->setFromClause('badaboum') + ; + + $this->assertTrue(\is_string($q->buildQuery())); + $this->assertEquals([], $q->buildParameters()); + } + +} diff --git a/src/Bundle/ChillPersonBundle/Search/SearchPersonApiProvider.php b/src/Bundle/ChillPersonBundle/Search/SearchPersonApiProvider.php index 4d1b720db..dd0ae67c7 100644 --- a/src/Bundle/ChillPersonBundle/Search/SearchPersonApiProvider.php +++ b/src/Bundle/ChillPersonBundle/Search/SearchPersonApiProvider.php @@ -26,7 +26,7 @@ class SearchPersonApiProvider implements SearchApiInterface "(person.fullnamecanonical LIKE '%' || LOWER(UNACCENT(?)) || '%')::int". ")", [ $pattern, $pattern ]) ->setFromClause("chill_person_person AS person") - ->setWhereClause("LOWER(UNACCENT(?)) <<% person.fullnamecanonical OR ". + ->setWhereClauses("LOWER(UNACCENT(?)) <<% person.fullnamecanonical OR ". "person.fullnamecanonical LIKE '%' || LOWER(UNACCENT(?)) || '%' ", [ $pattern, $pattern ]) ; diff --git a/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php b/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php index 47d5cc6b9..d25b6ac8f 100644 --- a/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php +++ b/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php @@ -5,7 +5,36 @@ namespace Chill\ThirdPartyBundle\Search; use Chill\MainBundle\Search\SearchApiInterface; use Chill\MainBundle\Search\SearchApiQuery; use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository; +use function explode; + +/* + * Internal note: test query for parametrizing / testing: + * +WITH rows AS ( + SELECT 'aide a domicile en milieu rural admr' AS c, 'la roche sur yon' AS l + UNION + SELECT 'aide a domicile en milieu rural admr' AS c, 'fontenay-le-comte' AS l +), searches AS ( + SELECT 'admr roche' AS s, 'admr' AS s1, 'roche' As s2 + UNION + SELECT 'admr font' AS s, 'admr' AS s1, 'font' AS s2 +) +SELECT + c, l, s, s1, s2, + strict_word_similarity(s, c) + + (c LIKE '%' || s1 || '%')::int + + (c LIKE '%' || s2 || '%')::int + + (l LIKE '%' || s1 || '%')::int + + (l LIKE '%' || s2 || '%')::int, + l LIKE '%' || s1 || '%', + l LIKE '%' || s2 || '%' +FROM rows, searches + */ + +/** + * Generate query for searching amongst third parties + */ class ThirdPartyApiSearch implements SearchApiInterface { private ThirdPartyRepository $thirdPartyRepository; @@ -17,18 +46,45 @@ class ThirdPartyApiSearch implements SearchApiInterface public function provideQuery(string $pattern, array $parameters): SearchApiQuery { - return (new SearchApiQuery) + $query = (new SearchApiQuery) ->setSelectKey('tparty') ->setSelectJsonbMetadata("jsonb_build_object('id', tparty.id)") - ->setSelectPertinence("GREATEST(". - "STRICT_WORD_SIMILARITY(LOWER(UNACCENT(?)), tparty.canonicalized),". - "(tparty.canonicalized LIKE '%' || LOWER(UNACCENT(?)) || '%')::int". - ")", [ $pattern, $pattern ]) - ->setFromClause('chill_3party.third_party AS tparty') - ->setWhereClause("tparty.active IS TRUE ". - "AND (LOWER(UNACCENT(?)) <<% tparty.canonicalized OR ". - "tparty.canonicalized LIKE '%' || LOWER(UNACCENT(?)) || '%')", [ $pattern, $pattern ]) - ; + ->setFromClause('chill_3party.third_party AS tparty + LEFT JOIN chill_main_address cma ON cma.id = tparty.address_id + LEFT JOIN chill_main_postal_code cmpc ON cma.postcode_id = cmpc.id + LEFT JOIN chill_3party.third_party AS parent ON tparty.parent_id = parent.id + LEFT JOIN chill_main_address cma_p ON parent.address_id = cma_p.id + LEFT JOIN chill_main_postal_code cmpc_p ON cma_p.postcode_id = cmpc.id') + ->andWhereClause("tparty.active IS TRUE") + ; + + $strs = explode(' ', $pattern); + $wheres = []; + $whereArgs = []; + $pertinence = []; + $pertinenceArgs = []; + + foreach ($strs as $str) { + if (!empty($str)) { + $wheres[] = "(LOWER(UNACCENT(?)) <<% tparty.canonicalized OR + tparty.canonicalized LIKE '%' || LOWER(UNACCENT(?)) || '%')"; + $whereArgs[] = [$str, $str]; + $pertinence[] = "STRICT_WORD_SIMILARITY(LOWER(UNACCENT(?)), tparty.canonicalized) + ". + "(tparty.canonicalized LIKE '%s' || LOWER(UNACCENT(?)) || '%')::int + ". + // take postcode label into account, but lower than the canonicalized field + "COALESCE((LOWER(UNACCENT(cmpc.label)) LIKE '%' || LOWER(UNACCENT(?)) || '%')::int * 0.3, 0) + ". + "COALESCE((LOWER(UNACCENT(cmpc_p.label)) LIKE '%' || LOWER(UNACCENT(?)) || '%')::int * 0.3, 0)"; + $pertinenceArgs[] = [$str, $str, $str, $str]; + } + } + + $query + ->setSelectPertinence(\implode(' + ', $pertinence), \array_merge([], + ...$pertinenceArgs)) + ->andWhereClause(\implode(' OR ', $wheres), \array_merge([], + ...$whereArgs)); + + return $query; } public function supportsTypes(string $pattern, array $types, array $parameters): bool From 9eec15873eb9684f31894e191330296308da273a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 12 Oct 2021 09:44:51 +0200 Subject: [PATCH 15/39] fixtures for civility: add abbreviations --- .../DataFixtures/ORM/LoadCivility.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCivility.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCivility.php index 07f824cd1..b26b5b991 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCivility.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCivility.php @@ -17,20 +17,21 @@ class LoadCivility extends Fixture implements FixtureGroupInterface public function load(ObjectManager $manager): void { $civilities = [ - ['name' => ['fr' => "Monsieur" ]], - ['name' => ['fr' => "Madame" ]], - ['name' => ['fr' => "Docteur" ]], - ['name' => ['fr' => "Professeur" ]], - ['name' => ['fr' => "Madame la Directrice" ]], - ['name' => ['fr' => "Monsieur le Directeur" ]], + ['name' => ['fr' => "Monsieur" ], 'abbrev' => ['fr' => 'M.']], + ['name' => ['fr' => "Madame" ], 'abbrev' => ['fr' => 'Mme']], + ['name' => ['fr' => "Docteur" ], 'abbrev' => ['fr' => 'Dr']], + ['name' => ['fr' => "Professeur" ], 'abbrev' => ['fr' => 'Pr']], + ['name' => ['fr' => "Madame la Directrice" ], 'abbrev' => ['fr' => 'Mme']], + ['name' => ['fr' => "Monsieur le Directeur" ], 'abbrev' => ['fr' => 'M.']], ['name' => ['fr' => "Madame la Maire" ]], ['name' => ['fr' => "Monsieur le Maire" ]], - ['name' => ['fr' => "Maître" ]], + ['name' => ['fr' => "Maître" ], 'abbrev' => ['fr' => 'Me']], ]; foreach ( $civilities as $val) { $civility = (new Civility()) ->setName($val['name']) + ->setAbbreviation($val['abbrev'] ?? []) ->setActive(true); $manager->persist($civility); } From 88d073b9a97e738f6905d2244fe45ebb555cd36c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 12 Oct 2021 15:52:06 +0200 Subject: [PATCH 16/39] handle types and categories in a single select input --- .../ChillThirdPartyBundle.php | 3 + .../Controller/ThirdPartyController.php | 2 + .../Entity/ThirdParty.php | 106 +++++++++++++++++- .../Form/ThirdPartyType.php | 61 ++-------- .../Type/PickThirdPartyTypeCategoryType.php | 100 +++++++++++++++++ .../views/ThirdParty/_form.html.twig | 3 +- .../Resources/views/ThirdParty/view.html.twig | 11 +- .../Tests/Entity/ThirdPartyTest.php | 92 +++++++++++++++ .../config/services/form.yaml | 22 +--- .../translations/messages.fr.yml | 1 + 10 files changed, 323 insertions(+), 78 deletions(-) create mode 100644 src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdPartyTypeCategoryType.php create mode 100644 src/Bundle/ChillThirdPartyBundle/Tests/Entity/ThirdPartyTest.php diff --git a/src/Bundle/ChillThirdPartyBundle/ChillThirdPartyBundle.php b/src/Bundle/ChillThirdPartyBundle/ChillThirdPartyBundle.php index ba184292a..6c6ff08c9 100644 --- a/src/Bundle/ChillThirdPartyBundle/ChillThirdPartyBundle.php +++ b/src/Bundle/ChillThirdPartyBundle/ChillThirdPartyBundle.php @@ -2,6 +2,7 @@ namespace Chill\ThirdPartyBundle; +use Chill\ThirdPartyBundle\ThirdPartyType\ThirdPartyTypeProviderInterface; use Symfony\Component\HttpKernel\Bundle\Bundle; use Chill\ThirdPartyBundle\DependencyInjection\CompilerPass\ThirdPartyTypeCompilerPass; @@ -10,6 +11,8 @@ class ChillThirdPartyBundle extends Bundle public function build(\Symfony\Component\DependencyInjection\ContainerBuilder $container) { parent::build($container); + $container->registerForAutoconfiguration(ThirdPartyTypeProviderInterface::class) + ->addTag('chill_3party.provider'); $container->addCompilerPass(new ThirdPartyTypeCompilerPass()); } diff --git a/src/Bundle/ChillThirdPartyBundle/Controller/ThirdPartyController.php b/src/Bundle/ChillThirdPartyBundle/Controller/ThirdPartyController.php index d6e4c0046..f7d47a32d 100644 --- a/src/Bundle/ChillThirdPartyBundle/Controller/ThirdPartyController.php +++ b/src/Bundle/ChillThirdPartyBundle/Controller/ThirdPartyController.php @@ -127,6 +127,8 @@ final class ThirdPartyController extends CRUDController return $this->getFilterOrderHelperFactory() ->create(self::class) ->addSearchBox(['name', 'company_name', 'acronym']) + //->addToggle('only-active', []) + // ->addOrderBy() ->build(); } } diff --git a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php index 38124b645..69da6cf50 100644 --- a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php +++ b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php @@ -368,7 +368,7 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface public function setTypes(array $type = null) { // remove all keys from the input data - $this->type = \array_values($type); + $this->types = \array_values($type); foreach ($this->children as $child) { $child->setTypes($type); @@ -387,6 +387,40 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface return $this->types; } + public function addType(?string $type): self + { + if (NULL === $type) { + return $this; + } + + if (!\in_array($type, $this->types ?? [])) { + $this->types[] = $type; + } + + foreach ($this->children as $child) { + $child->addType($type); + } + + return $this; + } + + public function removeType(?string $type): self + { + if (NULL === $type) { + return $this; + } + + if (\in_array($type, $this->types ?? [])) { + $this->types = \array_filter($this->types, fn($e) => !\in_array($e, $this->types)); + } + + foreach ($this->children as $child) { + $child->removeType($type); + } + + return $this; + } + /** * @return bool */ @@ -541,7 +575,7 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface } foreach ($this->children as $child) { - $child->addCategory($child); + $child->addCategory($category); } return $this; @@ -556,7 +590,7 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface $this->categories->removeElement($category); foreach ($this->children as $child) { - $child->removeCategory($child); + $child->removeCategory($category); } return $this; @@ -631,6 +665,72 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface return $this; } + public function addTypesAndCategories($typeAndCategory): self + { + if ($typeAndCategory instanceof ThirdPartyCategory) { + $this->addCategory($typeAndCategory); + return $this; + } + + if (is_string($typeAndCategory)) { + $this->addType($typeAndCategory); + return $this; + } + + throw new \UnexpectedValueException(sprintf( + "typeAndCategory should be a string or a %s", ThirdPartyCategory::class)); + } + + public function removeTypesAndCategories($typeAndCategory): self + { + if ($typeAndCategory instanceof ThirdPartyCategory) { + $this->removeCategory($typeAndCategory); + return $this; + } + + if (is_string($typeAndCategory)) { + $this->removeType($typeAndCategory); + return $this; + } + + throw new \UnexpectedValueException(sprintf( + "typeAndCategory should be a string or a %s", ThirdPartyCategory::class)); + } + + public function getTypesAndCategories(): array + { + return \array_merge( + $this->getCategories()->toArray(), + $this->getTypes() ?? [] + ); + } + + public function setTypesAndCategories(array $typesAndCategories): self + { + $types = \array_filter($typesAndCategories, fn($item) => !$item instanceof ThirdPartyCategory); + $this->setTypes($types); + + // handle categories + foreach ($typesAndCategories as $t) { + $this->addTypesAndCategories($t); + } + + $categories = \array_filter($typesAndCategories, fn($item) => $item instanceof ThirdPartyCategory); + $categoriesHashes = \array_map(fn(ThirdPartyCategory $c) => \spl_object_hash($c), $categories); + + foreach ($categories as $c) { + $this->addCategory($c); + } + + foreach ($this->getCategories() as $t) { + if (!\in_array(\spl_object_hash($t), $categoriesHashes)) { + $this->removeCategory($t); + } + } + + return $this; + } + /** * @param ThirdParty $child diff --git a/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php b/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php index 057f848cb..3d30b9e81 100644 --- a/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php +++ b/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php @@ -11,9 +11,12 @@ use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\ThirdPartyBundle\Entity\ThirdParty; use Chill\ThirdPartyBundle\Entity\ThirdPartyCategory; use Chill\ThirdPartyBundle\Entity\ThirdPartyProfession; +use Chill\ThirdPartyBundle\Form\Type\PickThirdPartyType; +use Chill\ThirdPartyBundle\Form\Type\PickThirdPartyTypeCategoryType; +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; -use Doctrine\Persistence\ObjectManager; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; @@ -40,14 +43,14 @@ class ThirdPartyType extends AbstractType protected TranslatableStringHelper $translatableStringHelper; - protected ObjectManager $om; + protected EntityManagerInterface $om; public function __construct( AuthorizationHelper $authorizationHelper, TokenStorageInterface $tokenStorage, ThirdPartyTypeManager $typesManager, TranslatableStringHelper $translatableStringHelper, - ObjectManager $om + EntityManagerInterface $om ) { $this->authorizationHelper = $authorizationHelper; $this->tokenStorage = $tokenStorage; @@ -171,21 +174,9 @@ class ThirdPartyType extends AbstractType } if (ThirdParty::KIND_CHILD !== $options['kind']) { - $builder - ->add('categories', EntityType::class, [ - 'label' => 'thirdparty.Categories', - 'class' => ThirdPartyCategory::class, - 'choice_label' => function (ThirdPartyCategory $category): string { - return $this->translatableStringHelper->localize($category->getName()); - }, - 'query_builder' => function (EntityRepository $er): QueryBuilder { - return $er->createQueryBuilder('c') - ->where('c.active = true'); - }, - 'required' => true, - 'multiple' => true, - 'attr' => ['class' => 'select2'] + ->add('typesAndCategories', PickThirdPartyTypeCategoryType::class, [ + 'label' => 'thirdparty.Categories' ]) ->add('active', ChoiceType::class, [ 'label' => 'thirdparty.Status', @@ -196,42 +187,6 @@ class ThirdPartyType extends AbstractType 'expanded' => true, 'multiple' => false ]); - - // add the types - $types = []; - foreach ($this->typesManager->getProviders() as $key => $provider) { - $types['chill_3party.key_label.'.$key] = $key; - } - if (count($types) === 1) { - $builder - ->add('types', HiddenType::class, [ - 'data' => array_values($types) - ]) - ->get('types') - ->addModelTransformer(new CallbackTransformer( - function (?array $typeArray): ?string { - if (null === $typeArray) { - return null; - } - return implode(',', $typeArray); - }, - function (?string $typeStr): ?array { - if (null === $typeStr) { - return null; - } - return explode(',', $typeStr); - } - )) - ; - } else { - $builder - ->add('types', ChoiceType::class, [ - 'choices' => $types, - 'expanded' => true, - 'multiple' => true, - 'label' => 'thirdparty.Type' - ]); - } } } diff --git a/src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdPartyTypeCategoryType.php b/src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdPartyTypeCategoryType.php new file mode 100644 index 000000000..2af962895 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdPartyTypeCategoryType.php @@ -0,0 +1,100 @@ +thirdPartyCategoryRepository = $thirdPartyCategoryRepository; + $this->thirdPartyTypeManager = $thirdPartyTypeManager; + $this->translatableStringHelper = $translatableStringHelper; + $this->translator = $translator; + } + + public function getParent() + { + return ChoiceType::class; + } + + public function configureOptions(OptionsResolver $resolver) + { + $choices = \array_merge( + $this->thirdPartyCategoryRepository->findBy(['active' => true]), + $this->thirdPartyTypeManager->getTypes() + ); + + \uasort($choices, function ($itemA, $itemB) { + $strA = $itemA instanceof ThirdPartyCategory ? $this->translatableStringHelper + ->localize($itemA->getName()) : $this->translator->trans(self::PREFIX_TYPE.$itemA); + $strB = $itemB instanceof ThirdPartyCategory ? $this->translatableStringHelper + ->localize($itemB->getName()) : $this->translator->trans(self::PREFIX_TYPE.$itemB); + + return $strA <=> $strB; + }); + + + $resolver->setDefaults([ + 'choices' => $choices, + 'attr' => [ 'class' => 'select2' ], + 'multiple' => true, + 'choice_label' => function($item) { + if ($item instanceof ThirdPartyCategory) { + return $this->translatableStringHelper->localize($item->getName()); + } + return self::PREFIX_TYPE.$item; + }, + 'choice_value' => function($item) { + return $this->reverseTransform($item); + } + ]); + } + + public function reverseTransform($value) + { + if ($value === null) { + return null; + } + if (is_array($value)){ + $r = []; + + foreach ($value as $v) { + $r[] = $this->transform($v); + } + + return $r; + } + + if ($value instanceof ThirdPartyCategory) { + return 'category:'.$value->getId(); + } + + if (is_string($value)) { + return 'type:'.$value; + } + + throw new UnexpectedTypeException($value, \implode(' or ', ['array', 'string', ThirdPartyCategory::class])); + } +} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig index ef347eb6d..87d26cc22 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig @@ -14,8 +14,7 @@ {{ form_row(form.profession) }} {% endif %} -{{ form_row(form.types) }} -{{ form_row(form.categories) }} +{{ form_row(form.typesAndCategories) }} {{ form_row(form.telephone) }} {{ form_row(form.email) }} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig index 460468e98..6b6395b19 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig @@ -48,13 +48,20 @@ {% endif %} -
    {{ 'Type'|trans }}
    +
    {{ 'thirdparty.Categories'|trans }}
    {% set types = [] %} {% for t in thirdParty.types %} {% set types = types|merge( [ ('chill_3party.key_label.'~t)|trans ] ) %} {% endfor %} + {% for c in thirdParty.categories %} + {% set types = types|merge([ c.name|localize_translatable_string ]) %} + {% endfor %}
    - {{ types|join(', ') }} + {% if types|length > 0 %} + {{ types|join(', ') }} + {% else %} +

    {{ 'thirdParty.Any categories' }}

    + {% endif %}
    {{ 'Phonenumber'|trans }}
    diff --git a/src/Bundle/ChillThirdPartyBundle/Tests/Entity/ThirdPartyTest.php b/src/Bundle/ChillThirdPartyBundle/Tests/Entity/ThirdPartyTest.php new file mode 100644 index 000000000..ac1758030 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/Tests/Entity/ThirdPartyTest.php @@ -0,0 +1,92 @@ +addTypesAndCategories('type'); + $tp->addTypesAndCategories($cat1); + $tp->addTypesAndCategories($cat2); + + $this->assertTrue($tp->getCategories()->contains($cat1)); + $this->assertTrue($tp->getCategories()->contains($cat2)); + $this->assertCount(2, $tp->getCategories()); + + $this->assertCount(1, $tp->getTypes()); + $this->assertContains('type', $tp->getTypes()); + + $this->assertCount(3, $tp->getTypesAndCategories()); + $this->assertContains($cat1, $tp->getTypesAndCategories()); + $this->assertContains($cat2, $tp->getTypesAndCategories()); + $this->assertContains('type', $tp->getTypesAndCategories()); + + // remove type + $tp->removeTypesAndCategories('type'); + $tp->removeTypesAndCategories($cat2); + + $this->assertTrue($tp->getCategories()->contains($cat1), + "test that cat1 is still present"); + $this->assertFalse($tp->getCategories()->contains($cat2)); + $this->assertCount(1, $tp->getCategories()); + + $this->assertCount(0, $tp->getTypes()); + $this->assertNotContains('type', $tp->getTypes()); + + $this->assertCount(1, $tp->getTypesAndCategories()); + $this->assertContains($cat1, $tp->getTypesAndCategories()); + $this->assertNotContains($cat2, $tp->getTypesAndCategories()); + $this->assertNotContains('type', $tp->getTypesAndCategories()); + } + + public function testSyncingActivityTypes() + { + $tp = new ThirdParty(); + $tp->setTypesAndCategories([ + 'type1', + 'type2', + $cat1 = new ThirdPartyCategory(), + $cat2 = new ThirdPartyCategory() + ]); + + $this->assertTrue($tp->getCategories()->contains($cat1)); + $this->assertTrue($tp->getCategories()->contains($cat2)); + $this->assertCount(2, $tp->getCategories()); + + $this->assertCount(2, $tp->getTypes()); + $this->assertContains('type1', $tp->getTypes()); + $this->assertContains('type2', $tp->getTypes()); + + $this->assertCount(4, $tp->getTypesAndCategories()); + $this->assertContains($cat1, $tp->getTypesAndCategories()); + $this->assertContains($cat2, $tp->getTypesAndCategories()); + $this->assertContains('type1', $tp->getTypesAndCategories()); + $this->assertContains('type2', $tp->getTypesAndCategories()); + + $tp->setTypesAndCategories([$cat1, 'type1']); + + $this->assertTrue($tp->getCategories()->contains($cat1)); + $this->assertFalse($tp->getCategories()->contains($cat2)); + $this->assertCount(1, $tp->getCategories()); + + $this->assertCount(1, $tp->getTypes()); + $this->assertContains('type1', $tp->getTypes()); + $this->assertNotContains('type2', $tp->getTypes()); + + $this->assertCount(2, $tp->getTypesAndCategories()); + $this->assertContains($cat1, $tp->getTypesAndCategories()); + $this->assertNotContains($cat2, $tp->getTypesAndCategories()); + $this->assertContains('type1', $tp->getTypesAndCategories()); + $this->assertNotContains('type2', $tp->getTypesAndCategories()); + } + +} diff --git a/src/Bundle/ChillThirdPartyBundle/config/services/form.yaml b/src/Bundle/ChillThirdPartyBundle/config/services/form.yaml index 8ff604769..0ed703e86 100644 --- a/src/Bundle/ChillThirdPartyBundle/config/services/form.yaml +++ b/src/Bundle/ChillThirdPartyBundle/config/services/form.yaml @@ -1,19 +1,5 @@ services: - Chill\ThirdPartyBundle\Form\ThirdPartyType: - arguments: - $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper' - $tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface' - $typesManager: '@Chill\ThirdPartyBundle\ThirdPartyType\ThirdPartyTypeManager' - $translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper' - $om: '@doctrine.orm.entity_manager' - tags: - - { name: form.type } - - Chill\ThirdPartyBundle\Form\Type\PickThirdPartyType: - arguments: - $em: '@Doctrine\ORM\EntityManagerInterface' - $urlGenerator: '@Symfony\Component\Routing\Generator\UrlGeneratorInterface' - $translator: '@Symfony\Component\Translation\TranslatorInterface' - $typesManager: '@Chill\ThirdPartyBundle\ThirdPartyType\ThirdPartyTypeManager' - tags: - - { name: form.type } + Chill\ThirdPartyBundle\Form\: + resource: '../../Form/' + autowire: true + autoconfigure: true diff --git a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml index e6167840d..ba5847c0b 100644 --- a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml @@ -67,6 +67,7 @@ No nameCompany given: Aucune raison sociale renseignée No acronym given: Aucun sigle renseigné No phone given: Aucun téléphone renseigné No email given: Aucune adresse courriel renseignée +thirdparty.Any categories: Aucune catégorie The party is visible in those centers: Le tiers est visible dans ces centres The party is not visible in any center: Le tiers n'est associé à aucun centre From b84d0100e0e38213bdbb0c6c61663afeaaec954a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 12 Oct 2021 17:32:17 +0200 Subject: [PATCH 17/39] create style for badge 3party and introduce in ThirdPartyRenderBox.vue --- .../_components/AddPersons/TypeThirdParty.vue | 6 ++--- .../public/chill/chillthirdparty.scss | 16 ++++++++++++++ .../Resources/public/chill/index.js | 1 + .../Resources/public/chill/thirdparty.scss | 0 .../Resources/public/page/index/index.js | 2 +- ...chillthirdparty.scss => index_3party.scss} | 0 .../Entity/ThirdPartyRenderBox.vue | 22 +++++++++++++++++++ .../vuejs/_components/OnTheFly/ThirdParty.vue | 8 +++++-- .../views/Entity/thirdparty.html.twig | 8 +------ .../chill.webpack.config.js | 2 ++ .../translations/messages.fr.yml | 2 ++ 11 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 src/Bundle/ChillThirdPartyBundle/Resources/public/chill/chillthirdparty.scss create mode 100644 src/Bundle/ChillThirdPartyBundle/Resources/public/chill/index.js delete mode 100644 src/Bundle/ChillThirdPartyBundle/Resources/public/chill/thirdparty.scss rename src/Bundle/ChillThirdPartyBundle/Resources/public/page/index/{chillthirdparty.scss => index_3party.scss} (100%) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue index 2dbb7c8d0..c68587ed9 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue @@ -19,13 +19,13 @@
- + {{ $t('thirdparty.child')}} - + {{ $t('thirdparty.company')}} - + {{ $t('thirdparty.contact')}} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/chill/chillthirdparty.scss b/src/Bundle/ChillThirdPartyBundle/Resources/public/chill/chillthirdparty.scss new file mode 100644 index 000000000..9762a898a --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/Resources/public/chill/chillthirdparty.scss @@ -0,0 +1,16 @@ +@import 'ChillMainAssets/module/bootstrap/shared'; + +.badge { + &.bg-thirdparty-company { + //@extend .bg-info; + background-color: $yellow; + } + &.bg-thirdparty-child { + //@extend .bg-chill-blue; + background-color: $chill-blue; + } + &.bg-thirdparty-contact { + //@extedn .bg-secondary; + background-color: $secondary; + } +} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/chill/index.js b/src/Bundle/ChillThirdPartyBundle/Resources/public/chill/index.js new file mode 100644 index 000000000..34dd4f71e --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/Resources/public/chill/index.js @@ -0,0 +1 @@ +require('./chillthirdparty.scss'); diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/chill/thirdparty.scss b/src/Bundle/ChillThirdPartyBundle/Resources/public/chill/thirdparty.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/page/index/index.js b/src/Bundle/ChillThirdPartyBundle/Resources/public/page/index/index.js index d85b125aa..10b9e8383 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/public/page/index/index.js +++ b/src/Bundle/ChillThirdPartyBundle/Resources/public/page/index/index.js @@ -1,2 +1,2 @@ -require('./chillthirdparty.scss'); +require('./index_3party.scss'); diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/page/index/chillthirdparty.scss b/src/Bundle/ChillThirdPartyBundle/Resources/public/page/index/index_3party.scss similarity index 100% rename from src/Bundle/ChillThirdPartyBundle/Resources/public/page/index/chillthirdparty.scss rename to src/Bundle/ChillThirdPartyBundle/Resources/public/page/index/index_3party.scss diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue index c2714126a..3ef941c02 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue +++ b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue @@ -13,6 +13,16 @@ {{ thirdparty.text }} + + {{ $t('thirdparty.child')}} + + + {{ $t('thirdparty.company')}} + + + {{ $t('thirdparty.contact')}} + + {{ thirdparty.id }} {{ $t('renderbox.type.thirdparty') }} @@ -57,11 +67,23 @@ import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue'; import {dateToISO} from 'ChillMainAssets/chill/js/date.js'; +const i18n = { + messages: { + fr: { + tparty: { + contact: "Personne physique", + company: "Personne morale" + } + } + } +}; + export default { name: "ThirdPartyRenderBox", components: { AddressRenderBox }, + i18n, props: ['thirdparty', 'options'], computed: { isMultiline: function() { diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue index 2710cd58e..1cb03ec85 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue +++ b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue @@ -24,13 +24,17 @@
diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig index 5700acc0b..af71b9643 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig @@ -81,13 +81,7 @@
{{ _self.label(thirdparty, options) }} - {% if thirdparty.kind == 'company' %} - {{ 'thirdparty.company'|trans }} - {% elseif thirdparty.kind == 'child' %} - {{ 'thirdparty.Child'|trans }} - {% elseif thirdparty.kind == 'contact' %} - {{ 'thirdparty.contact'|trans }} - {% endif %} + {{ ('thirdparty.' ~ thirdparty.kind)|trans }}
    diff --git a/src/Bundle/ChillThirdPartyBundle/chill.webpack.config.js b/src/Bundle/ChillThirdPartyBundle/chill.webpack.config.js index c508cbc0c..8fbeb35a2 100644 --- a/src/Bundle/ChillThirdPartyBundle/chill.webpack.config.js +++ b/src/Bundle/ChillThirdPartyBundle/chill.webpack.config.js @@ -5,6 +5,8 @@ module.exports = function(encore, entries) ChillThirdPartyAssets: __dirname + '/Resources/public' }); + entries.push(__dirname + '/Resources/public/chill/index.js'); + encore.addEntry( 'page_3party_3party_index', __dirname + '/Resources/public/page/index/index.js' diff --git a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml index ba5847c0b..135ef9b47 100644 --- a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml @@ -16,7 +16,9 @@ thirdparty.NameCompany: Service/Département thirdparty.Acronym: Sigle thirdparty.Categories: Catégories thirdparty.Child: Personne de contact +thirdparty.child: Personne de contact thirdparty.Children: Personnes de contact +thirdparty.children: Personnes de contact thirdparty.Parent: Tiers institutionnel thirdparty.Parents: Tiers institutionnels thirdparty.Civility: Civilité From 01ff88074b526346e8da0d21fa2ebf9d9be50d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 12 Oct 2021 17:50:32 +0200 Subject: [PATCH 18/39] add parent in thirdpartyrenderbox.vue --- .../_components/AddPersons/TypeThirdParty.vue | 2 +- .../_components/Entity/ThirdPartyRenderBox.vue | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue index c68587ed9..de24c6fbf 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue @@ -13,7 +13,7 @@
- {{ item.result.parent.text }} + > {{ item.result.parent.text }}
diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue index 3ef941c02..8b8fc6449 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue +++ b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue @@ -28,11 +28,18 @@ +
+ + > {{ thirdparty.parent.text }} + +
+

+
@@ -87,11 +94,14 @@ export default { props: ['thirdparty', 'options'], computed: { isMultiline: function() { - if(this.options.isMultiline){ + if (this.options.isMultiline){ return this.options.isMultiline } else { return false } + }, + hasParent() { + return !(this.$props.thirdparty.parent === null || this.$props.thirdparty.parent === undefined); } } } @@ -102,6 +112,10 @@ export default { &:before{ content: " " } + &.tparty-parent { + font-weight: bold; + font-variant: all-small-caps; + } } From 13b96637bbaf0ecaf78beb5e9c46bd3fc1583718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Oct 2021 22:58:54 +0200 Subject: [PATCH 19/39] Refactor address vue app and create a PickAddressType --- .../Resources/views/Form/fields.html.twig | 22 +++-- .../AddressToIdDataTransformer.php | 45 ++++++++++ .../Form/Type/PickAddressType.php | 52 +++++++++++ .../Resources/public/vuejs/Address/App.vue | 6 +- .../vuejs/Address/mod_input_address_index.js | 87 ++++++++++++++++++ .../ChillMainBundle/chill.webpack.config.js | 1 + .../ChillMainBundle/config/services/form.yaml | 9 ++ .../Entity/ThirdParty.php | 4 +- .../Form/ThirdPartyType.php | 9 +- .../vuejs/_components/OnTheFly/ThirdParty.vue | 49 +++++++--- .../views/ThirdParty/_form.html.twig | 11 ++- .../Resources/views/ThirdParty/new.html.twig | 8 ++ .../views/ThirdParty/update.html.twig | 89 ++----------------- 13 files changed, 282 insertions(+), 110 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Form/Type/DataTransformer/AddressToIdDataTransformer.php create mode 100644 src/Bundle/ChillMainBundle/Form/Type/PickAddressType.php create mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/mod_input_address_index.js diff --git a/src/Bundle/ChillCustomFieldsBundle/Resources/views/Form/fields.html.twig b/src/Bundle/ChillCustomFieldsBundle/Resources/views/Form/fields.html.twig index a19f22e6f..e4e611b8d 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Resources/views/Form/fields.html.twig +++ b/src/Bundle/ChillCustomFieldsBundle/Resources/views/Form/fields.html.twig @@ -1,16 +1,16 @@ {# * Copyright (C) 2014, Champs Libres Cooperative SCRLFS, - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . #} @@ -33,7 +33,7 @@ {# CFChoice : render the different elements in a choice list #} {% block cf_choices_row %}

{{ 'Choices'|trans }}

- +
@@ -47,8 +47,8 @@ {% endfor %}
- - + + {# we use javascrit to add an additional element. All functions are personnalized with the id ( = form.vars.id) #} diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/CurrentHousehold.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/CurrentHousehold.vue new file mode 100644 index 000000000..01667eab7 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/CurrentHousehold.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue index be332523d..c40cc9cbc 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue @@ -1,5 +1,8 @@ From 9cbac89cae9245eb43a7dd94e5364cf8239909e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 15 Oct 2021 17:43:28 +0200 Subject: [PATCH 34/39] handle leaving household --- .../vuejs/HouseholdMembersEditor/App.vue | 27 +++++++- .../components/CurrentHousehold.vue | 25 +++++++- .../components/Household.vue | 62 +++++++++++++++---- .../vuejs/HouseholdMembersEditor/js/i18n.js | 4 +- 4 files changed, 99 insertions(+), 19 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/App.vue index c2112007f..52eac5ea6 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/App.vue @@ -15,7 +15,7 @@
    -
  • +
  • @@ -77,6 +77,11 @@ export default { return s; }, + hasReturnPath() { + let params = new URLSearchParams(window.location.search); + + return params.has('returnPath'); + }, // return true if the next step is allowed isNextAllowed() { switch (this.$data.step) { @@ -105,6 +110,9 @@ export default { if (this.$store.getters.isHouseholdNew) { this.$data.step = 'household_address'; break; + } else if (this.$store.getters.isModeLeave) { + this.$data.step = 'confirm'; + break; } else { this.$data.step = 'positioning'; break; @@ -118,7 +126,22 @@ export default { } }, goToPrevious() { - this.$data.step = 'concerned'; + if (this.$data.step === 'concerned') { + let params = new URLSearchParams(window.location.search); + if (params.has('returnPath')) { + window.location.replace(params.get('returnPath')); + } else { + return; + } + } + + let s = this.steps; + let index = s.indexOf(this.$data.step); + if (s[index - 1] === undefined) { + throw Error("step not found"); + } + + this.$data.step = s[index - 1]; }, confirm() { this.$store.dispatch('confirm'); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/CurrentHousehold.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/CurrentHousehold.vue index 01667eab7..1f76c8a5b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/CurrentHousehold.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/CurrentHousehold.vue @@ -4,10 +4,30 @@
+
+
+
+
+
+
+ + + + + {{ $t('household_members_editor.household.leave_without_household') }} +
+
+
+
+ {{ $t('household_members_editor.household.will_leave_any_household_explanation')}} +
+
+
+