mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Merge remote-tracking branch 'origin/master' into 106_tasks_to_parcours
This commit is contained in:
commit
c4da6a436b
@ -7,6 +7,7 @@ charset = utf-8
|
||||
end_of_line = LF
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_size = 4
|
||||
|
||||
[*.{php,html,twig}]
|
||||
indent_style = space
|
||||
|
108
CHANGELOG.md
Normal file
108
CHANGELOG.md
Normal file
@ -0,0 +1,108 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to
|
||||
|
||||
* [Semantic Versioning](https://semver.org/spec/v2.0.0.html) for stable releases;
|
||||
* date versioning for test releases
|
||||
|
||||
## Unreleased
|
||||
|
||||
<!-- write down unreleased development here -->
|
||||
|
||||
|
||||
|
||||
|
||||
## Test releases
|
||||
|
||||
### Test release 2021-10-18
|
||||
|
||||
* [3party]: french translation of contact and company
|
||||
* [3party]: show parent in list
|
||||
* [3party]: change color for badge "child"
|
||||
* [3party]: fix address creation
|
||||
* [household members editor] finalisation of editor
|
||||
* [AccompanyingCourse banner]: replace translation referrer (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/70)
|
||||
* [Location]: add location system in activity and RV (calendar). User can choose in location list or create a new location.
|
||||
|
||||
### Test release 2021-10-11
|
||||
|
||||
* Address: zoom on postal code geometry + fix origin of manually entered postal code
|
||||
|
||||
* in the Address vue component, order the postal code and street address by alphabetic and numeric order
|
||||
|
||||
* add 3 new fields to PostalCode and adapt postal code command and fixtures
|
||||
|
||||
* [Aside activity] Fixes for aside activity
|
||||
|
||||
* categories with child
|
||||
* fast creation buttons
|
||||
* add ordering for types
|
||||
|
||||
* [AccompanyingCourse Resume page] badge-title for AccompanyingCourseWork and for Activities;
|
||||
* Improve badges behaviour with small screens;
|
||||
|
||||
* [ThirdParty]:
|
||||
|
||||
* third party list
|
||||
* create a kind contact/institution when create a new thirdparty, and set contact embedded as kind=child;
|
||||
* 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
|
||||
* [On-The-Fly] modale works for showing, editing and creating person or thirdparty ;
|
||||
* [AccompanyingCourse Resume page] associated persons list, can see household when hover, and with show on-the-fly modale when clicking person ;
|
||||
|
||||
### test release 2021-10-04
|
||||
|
||||
* [Household editor][UI] Update how household suggestion and addresses are picked;
|
||||
|
||||
See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/80
|
||||
* [AddAddress] Handle address suggestion;
|
||||
* [CenterType][Create a person] when overriding the ACL rules, allow to show a PickCenterType
|
||||
when no centers are reachable by the default ACL.
|
||||
* [Household] Show comment event if no address are associated with the household;
|
||||
* [Person results] Add requestor into search results:
|
||||
|
||||
* a badge "requestor" is shown into search results;
|
||||
* periods where the person is only requestor (without participating) are also shown;
|
||||
|
||||
Issues:
|
||||
|
||||
* https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/13
|
||||
* https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/199
|
||||
* [Person form] "accept sms" not required:
|
||||
|
||||
https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/37
|
||||
https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/221
|
||||
|
||||
* [Household editor] suggest only temporarily addresses;
|
||||
See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/82
|
||||
* On-The-Fly modale works for showing, editing and creating person and thirdparty ;
|
||||
* AccompanyingCourse Resume page: list associated persons by household, see household when hover, and show on-the-fly modale when clicking on person ;
|
||||
* [AddAddress] Handle address suggestion;
|
||||
* [AddAddress][Entity address]: add a link between address and address reference;
|
||||
* [Household editor] suggest household by comparing the temporary addresses from courses;
|
||||
|
||||
See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/81
|
||||
* On-The-Fly modale works for showing, editing and creating person and thirdparty
|
||||
|
||||
|
||||
## Test released
|
||||
|
||||
<!--
|
||||
|
||||
Coming soon...
|
||||
|
||||
DO NOT ADD unreleased items here. Add them under "Unreleased" title
|
||||
|
||||
### Test release yyyy-mm-dd
|
||||
|
||||
-->
|
||||
|
||||
## Stable releases
|
||||
|
||||
No stable releases for v2+
|
||||
|
383
CONVENTIONS.md
Normal file
383
CONVENTIONS.md
Normal file
@ -0,0 +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
|
||||
<div class="bloc bloc-dark mon-bloc">
|
||||
<h3>mon titre</h3>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a class="btn btn-edit"></a>
|
||||
</li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</div>
|
||||
```
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## 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 %}
|
||||
<h1>
|
||||
{{ block('title')}}
|
||||
</h1>
|
||||
{% 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
|
||||
|
||||
```
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ chill_entity_return_path('route_name' { 'route': 'option' } )}}">{{ return_path_label }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<!-- action 1 -->
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
### 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" => ?
|
||||
|
@ -22,6 +22,7 @@
|
||||
"league/csv": "^9.7.1",
|
||||
"nyholm/psr7": "^1.4",
|
||||
"phpoffice/phpspreadsheet": "^1.16",
|
||||
"ramsey/uuid-doctrine": "^1.7",
|
||||
"sensio/framework-extra-bundle": "^5.5",
|
||||
"symfony/asset": "4.*",
|
||||
"symfony/browser-kit": "^5.2",
|
||||
@ -29,6 +30,7 @@
|
||||
"symfony/expression-language": "4.*",
|
||||
"symfony/form": "4.*",
|
||||
"symfony/intl": "4.*",
|
||||
"symfony/mime": "^4 || ^5",
|
||||
"symfony/monolog-bundle": "^3.5",
|
||||
"symfony/security-bundle": "4.*",
|
||||
"symfony/serializer": "^5.2",
|
||||
|
@ -27,3 +27,4 @@ Version 1.5.5
|
||||
=============
|
||||
|
||||
- [activity] replace dropdown for selecting reasons and use chillEntity for reason rendering
|
||||
- fix bug: error when trying to edit activity of which the type has been deactivated
|
||||
|
@ -174,6 +174,10 @@ class ActivityController extends AbstractController
|
||||
$activityType = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityType::class)
|
||||
->find($activityType_id);
|
||||
|
||||
if (isset($activityType) && !$activityType->isActive()) {
|
||||
throw new \InvalidArgumentException('Activity type must be active');
|
||||
}
|
||||
|
||||
$activityData = null;
|
||||
if ($request->query->has('activityData')) {
|
||||
$activityData = $request->query->get('activityData');
|
||||
|
@ -141,7 +141,6 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface, C
|
||||
$ref = 'activity_'.$person->getFullnameCanonical();
|
||||
|
||||
for($i = 0; $i < $activityNbr; $i ++) {
|
||||
print "Creating an activity type for : ".$person." (ref: ".$ref.") \n";
|
||||
$activity = $this->newRandomActivity($person);
|
||||
$manager->persist($activity);
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ namespace Chill\ActivityBundle\Entity;
|
||||
use Chill\DocStoreBundle\Entity\Document;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
|
||||
use Chill\MainBundle\Entity\Location;
|
||||
use Chill\PersonBundle\AccompanyingPeriod\SocialIssueConsistency\AccompanyingPeriodLinkedWithSocialIssuesEntityInterface;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
|
||||
@ -177,6 +178,13 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
|
||||
*/
|
||||
private string $sentReceived = '';
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location")
|
||||
* @groups({"read"})
|
||||
*/
|
||||
private ?Location $location = null;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->reasons = new ArrayCollection();
|
||||
@ -555,4 +563,22 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Location|null
|
||||
*/
|
||||
public function getLocation(): ?Location
|
||||
{
|
||||
return $this->location;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Location|null $location
|
||||
* @return Activity
|
||||
*/
|
||||
public function setLocation(?Location $location): Activity
|
||||
{
|
||||
$this->location = $location;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -248,6 +248,16 @@ class ActivityType
|
||||
*/
|
||||
private string $socialActionsLabel = '';
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="smallint", nullable=false, options={"default"=1})
|
||||
*/
|
||||
private int $locationVisible = self::FIELD_INVISIBLE;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", nullable=false, options={"default"=""})
|
||||
*/
|
||||
private string $locationLabel = '';
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="float", options={"default"="0.0"})
|
||||
*/
|
||||
@ -820,4 +830,29 @@ class ActivityType
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLocationVisible(): ?int
|
||||
{
|
||||
return $this->locationVisible;
|
||||
}
|
||||
|
||||
public function setLocationVisible(int $locationVisible): self
|
||||
{
|
||||
$this->locationVisible = $locationVisible;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLocationLabel(): ?string
|
||||
{
|
||||
return $this->locationLabel;
|
||||
}
|
||||
|
||||
public function setLocationLabel(string $locationLabel): self
|
||||
{
|
||||
$this->locationLabel = $locationLabel;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use Chill\ActivityBundle\Entity\Activity;
|
||||
use Chill\ActivityBundle\Entity\ActivityPresence;
|
||||
use Chill\ActivityBundle\Entity\ActivityReason;
|
||||
use Chill\DocStoreBundle\Form\StoredObjectType;
|
||||
use Chill\MainBundle\Entity\Location;
|
||||
use Chill\MainBundle\Form\Type\ChillCollectionType;
|
||||
use Chill\MainBundle\Form\Type\CommentType;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
@ -93,15 +94,13 @@ class ActivityType extends AbstractType
|
||||
/** @var \Chill\ActivityBundle\Entity\ActivityType $activityType */
|
||||
$activityType = $options['activityType'];
|
||||
|
||||
if (!$activityType->isActive()) {
|
||||
throw new \InvalidArgumentException('Activity type must be active');
|
||||
}
|
||||
|
||||
// TODO revoir la gestion des center au niveau du form des activité.
|
||||
if ($options['center']) {
|
||||
$builder->add('scope', ScopePickerType::class, [
|
||||
'center' => $options['center'],
|
||||
'role' => $options['role']
|
||||
'role' => $options['role'],
|
||||
// TODO make required again once scope and rights are fixed
|
||||
'required' => false
|
||||
]);
|
||||
}
|
||||
|
||||
@ -224,7 +223,7 @@ class ActivityType extends AbstractType
|
||||
|
||||
if ($activityType->isVisible('comment')) {
|
||||
$builder->add('comment', CommentType::class, [
|
||||
'label' => empty($activityType->getLabel('comment'))
|
||||
'label' => empty($activityType->getLabel('comment'))
|
||||
? 'activity.comment' : $activityType->getLabel('comment'),
|
||||
'required' => $activityType->isRequired('comment'),
|
||||
]);
|
||||
@ -304,6 +303,23 @@ class ActivityType extends AbstractType
|
||||
;
|
||||
}
|
||||
|
||||
if ($activityType->isVisible('location')) {
|
||||
$builder->add('location', HiddenType::class)
|
||||
->get('location')
|
||||
->addModelTransformer(new CallbackTransformer(
|
||||
function (?Location $location): string {
|
||||
if (null === $location) {
|
||||
return '';
|
||||
}
|
||||
return $location->getId();
|
||||
},
|
||||
function (?string $id): Location {
|
||||
return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]);
|
||||
}
|
||||
))
|
||||
;
|
||||
}
|
||||
|
||||
if ($activityType->isVisible('emergency')) {
|
||||
$builder->add('emergency', CheckboxType::class, [
|
||||
'label' => $activityType->getLabel('emergency'),
|
||||
|
@ -1,7 +1,9 @@
|
||||
// Access to Bootstrap variables and mixins
|
||||
@import '~ChillMainAssets/module/bootstrap/shared';
|
||||
|
||||
// activity creation first step: select type page
|
||||
//// ACTIVITY CREATION
|
||||
// first step: select type page
|
||||
|
||||
div.new-activity-select-type {
|
||||
div.activity-row {
|
||||
display: flex;
|
||||
@ -21,91 +23,36 @@ div.new-activity-select-type {
|
||||
}
|
||||
}
|
||||
|
||||
// exceptions for flex-table in list-records
|
||||
div.activity-list {
|
||||
div.flex-table {
|
||||
div.item-bloc {
|
||||
div.item-row.main {
|
||||
div.item-col {
|
||||
&:first-child {
|
||||
flex-basis: 15%;
|
||||
}
|
||||
ul.list-content {
|
||||
li.social-issues, li.social-actions {
|
||||
.badge-primary {
|
||||
font-variant: small-caps;
|
||||
font-weight: bold;
|
||||
font-size: 88%;
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
}
|
||||
li.social-issues .badge-primary {
|
||||
background-color: $orange;
|
||||
}
|
||||
li.social-actions .badge-primary {
|
||||
background-color: $green;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div.item-row.comment {
|
||||
margin-left: 15%;
|
||||
blockquote.chill-user-quote {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
}
|
||||
div.item-row.details {
|
||||
margin-left: 15%;
|
||||
//// ACTIVITY LIST PAGE
|
||||
// precise badge-title specific details
|
||||
|
||||
// override flex-bloc to adapt in list
|
||||
// TODO refund this
|
||||
div.accompanyingCourse.flex-bloc.concerned-groups {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
justify-content: space-around;
|
||||
div.item-bloc {
|
||||
box-shadow: unset;
|
||||
padding: 0;
|
||||
flex-basis: 25%;
|
||||
div.item-row {
|
||||
flex-direction: column;
|
||||
div.item-col {
|
||||
&:first-child {
|
||||
width: unset;
|
||||
}
|
||||
&:last-child {
|
||||
border-top: 0;
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
ul.list-content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
h2.badge-title {
|
||||
div.duration {
|
||||
font-size: smaller;
|
||||
padding-left: 1em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
ul.list-content {
|
||||
font-size: 70%;
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
margin: 0;
|
||||
li {
|
||||
margin-bottom: 0.2em;
|
||||
// exception: change bg color for action badges above badge-title
|
||||
.bg-light {
|
||||
background-color: $chill-light-gray !important;
|
||||
}
|
||||
|
||||
}
|
||||
ul.list-content {
|
||||
list-style-type: none;
|
||||
padding-left: 1em;
|
||||
margin: 0 0;
|
||||
li {
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
div.duration {
|
||||
font-size: smaller;
|
||||
padding-left: 1em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div.main {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
// exceptions for flex-bloc in concerned-groups
|
||||
//// ACTIVITY SHOW AND FORM PAGES
|
||||
// Exceptions for flex-bloc in concerned-groups
|
||||
|
||||
div.flex-bloc.concerned-groups {
|
||||
margin-top: 1em;
|
||||
div.item-bloc {
|
||||
@ -130,7 +77,6 @@ div.flex-bloc.concerned-groups {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// CHILL ENTITY RENDER BOX
|
||||
.chill-entity {
|
||||
|
||||
|
@ -1,17 +1,20 @@
|
||||
<template>
|
||||
<concerned-groups></concerned-groups>
|
||||
<social-issues-acc></social-issues-acc>
|
||||
<location></location>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConcernedGroups from './components/ConcernedGroups.vue';
|
||||
import SocialIssuesAcc from './components/SocialIssuesAcc.vue';
|
||||
import Location from './components/Location.vue';
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
ConcernedGroups,
|
||||
SocialIssuesAcc
|
||||
SocialIssuesAcc,
|
||||
Location
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -3,16 +3,61 @@ import { getSocialIssues } from 'ChillPersonAssets/vuejs/AccompanyingCourse/api.
|
||||
/*
|
||||
* Load socialActions by socialIssue (id)
|
||||
*/
|
||||
const getSocialActionByIssue = (id) => {
|
||||
const getSocialActionByIssue = (id) => {
|
||||
const url = `/api/1.0/person/social/social-action/by-social-issue/${id}.json`;
|
||||
return fetch(url)
|
||||
.then(response => {
|
||||
if (response.ok) { return response.json(); }
|
||||
throw Error('Error with request resource response');
|
||||
throw Error('Error with request resource response');
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* 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');
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Load Location Types
|
||||
*/
|
||||
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');
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Post a Location
|
||||
*/
|
||||
const postLocation = (body) => {
|
||||
const url = `/api/1.0/main/location.json`;
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) { return response.json(); }
|
||||
throw Error('Error with request resource response');
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
getSocialIssues,
|
||||
getSocialActionByIssue
|
||||
getSocialActionByIssue,
|
||||
getLocations,
|
||||
getLocationTypes,
|
||||
postLocation
|
||||
};
|
||||
|
@ -73,9 +73,12 @@ export default {
|
||||
addPersons: {
|
||||
key: 'activity',
|
||||
options: {
|
||||
type: ['person', 'thirdparty', 'user'], // TODO add 'user'
|
||||
type: ['person', 'thirdparty', 'user'],
|
||||
priority: null,
|
||||
uniq: false,
|
||||
button: {
|
||||
size: 'btn-sm'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<teleport to="#location">
|
||||
<div class="mb-3 row">
|
||||
<label class="col-form-label col-sm-4">
|
||||
{{ $t('activity.location') }}
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
|
||||
<VueMultiselect
|
||||
name="selectLocation"
|
||||
id="selectLocation"
|
||||
label="name"
|
||||
track-by="id"
|
||||
open-direction="top"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:placeholder="$t('activity.choose_location')"
|
||||
:custom-label="customLabel"
|
||||
:options="locations"
|
||||
v-model="location">
|
||||
</VueMultiselect>
|
||||
|
||||
<new-location @saveNewLocation="saveNewLocation"></new-location>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import VueMultiselect from 'vue-multiselect';
|
||||
import NewLocation from './Location/NewLocation.vue';
|
||||
import { getLocations, postLocation } from '../api.js';
|
||||
|
||||
export default {
|
||||
name: "Location",
|
||||
components: {
|
||||
NewLocation,
|
||||
VueMultiselect
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
locations: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['activity']),
|
||||
location: {
|
||||
get() {
|
||||
return this.activity.location;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.dispatch('updateLocation', value);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getLocationsList();
|
||||
},
|
||||
methods: {
|
||||
getLocationsList() {
|
||||
getLocations().then(response => new Promise(resolve => {
|
||||
console.log('getLocations', response);
|
||||
this.locations = response.results;
|
||||
resolve();
|
||||
}))
|
||||
},
|
||||
customLabel(value) {
|
||||
return `${value.locationType.title.fr} ${value.name}`;
|
||||
},
|
||||
saveNewLocation(selected) {
|
||||
console.log('saveNewLocation', selected);
|
||||
let body = {
|
||||
type: 'location',
|
||||
name: selected.name,
|
||||
address: {
|
||||
id: selected.addressId
|
||||
},
|
||||
locationType: {
|
||||
id: selected.type,
|
||||
type: 'location-type'
|
||||
},
|
||||
phonenumber1: selected.phonenumber1,
|
||||
phonenumber2: selected.phonenumber2,
|
||||
email: selected.email,
|
||||
}
|
||||
postLocation(body).then(location => new Promise(resolve => {
|
||||
console.log('postLocation', location);
|
||||
this.locations.push(location);
|
||||
this.$store.dispatch('updateLocation', location);
|
||||
resolve();
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
@ -0,0 +1,181 @@
|
||||
<template>
|
||||
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a class="btn btn-sm btn-create" @click="openModal">
|
||||
{{ $t('activity.create_new_location') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<teleport to="body">
|
||||
<modal v-if="modal.showModal"
|
||||
:modalDialogClass="modal.modalDialogClass"
|
||||
@close="modal.showModal = false">
|
||||
|
||||
<template v-slot:header>
|
||||
<h3 class="modal-title">{{ $t('activity.create_new_location') }}</h3>
|
||||
</template>
|
||||
<template v-slot:body>
|
||||
|
||||
<add-address
|
||||
:context="addAddress.context"
|
||||
:options="addAddress.options"
|
||||
:addressChangedCallback="submitNewAddress"
|
||||
ref="addAddress">
|
||||
</add-address>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control form-control-lg" id="name" v-model="inputName" placeholder />
|
||||
<label for="name">{{ $t('activity.location_fields.name') }}</label>
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<select class="form-select form-select-lg" id="type" v-model="selectType">
|
||||
<option selected disabled value="">{{ $t('activity.choose_location_type') }}</option>
|
||||
<option v-for="t in locationTypes" :value="t.id">
|
||||
{{ t.title.fr }}
|
||||
</option>
|
||||
</select>
|
||||
<label>{{ $t('activity.location_fields.type') }}</label>
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control form-control-lg" id="phonenumber1" v-model="inputPhonenumber1" placeholder />
|
||||
<label for="phonenumber1">{{ $t('activity.location_fields.phonenumber1') }}</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3" v-if="hasPhonenumber1">
|
||||
<input class="form-control form-control-lg" id="phonenumber2" v-model="inputPhonenumber2" placeholder />
|
||||
<label for="phonenumber2">{{ $t('activity.location_fields.phonenumber2') }}</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control form-control-lg" id="email" v-model="inputEmail" placeholder />
|
||||
<label for="email">{{ $t('activity.location_fields.email') }}</label>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<button class="btn btn-save"
|
||||
@click.prevent="$emit('saveNewLocation', selected); modal.showModal = false;">
|
||||
{{ $t('action.save') }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
</modal>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Modal from 'ChillMainAssets/vuejs/_components/Modal.vue';
|
||||
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
|
||||
import { mapState } from "vuex";
|
||||
import { getLocationTypes } from "../../api";
|
||||
|
||||
export default {
|
||||
name: "NewLocation",
|
||||
components: {
|
||||
Modal,
|
||||
AddAddress,
|
||||
},
|
||||
emits: ['saveNewLocation'],
|
||||
data() {
|
||||
return {
|
||||
selected: {
|
||||
type: {},
|
||||
name: null,
|
||||
addressId: null,
|
||||
phonenumber1: null,
|
||||
phonenumber2: null,
|
||||
email: null,
|
||||
},
|
||||
locationTypes: [],
|
||||
modal: {
|
||||
showModal: false,
|
||||
modalDialogClass: "modal-dialog-scrollable modal-xl"
|
||||
},
|
||||
addAddress: {
|
||||
options: {
|
||||
button: {
|
||||
text: { create: 'activity.create_address', edit: 'activity.edit_address' },
|
||||
size: 'btn-sm'
|
||||
},
|
||||
title: { create: 'activity.create_address', edit: 'activity.edit_address' },
|
||||
},
|
||||
context: {
|
||||
target: { //name, id
|
||||
},
|
||||
edit: false,
|
||||
addressId: null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['activity']),
|
||||
selectType: {
|
||||
get() {
|
||||
return this.selected.type;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.type = value
|
||||
}
|
||||
},
|
||||
inputName: {
|
||||
get() {
|
||||
return this.selected.name;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.name = value;
|
||||
}
|
||||
},
|
||||
inputEmail: {
|
||||
get() {
|
||||
return this.selected.email;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.email = value;
|
||||
}
|
||||
},
|
||||
inputPhonenumber1: {
|
||||
get() {
|
||||
return this.selected.phonenumber1;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.phonenumber1 = value;
|
||||
}
|
||||
},
|
||||
inputPhonenumber2: {
|
||||
get() {
|
||||
return this.selected.phonenumber2;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.phonenumber2 = value;
|
||||
}
|
||||
},
|
||||
hasPhonenumber1() {
|
||||
return this.selected.phonenumber1 !== null && this.selected.phonenumber1 !== "";
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getLocationTypesList();
|
||||
},
|
||||
methods: {
|
||||
getLocationTypesList() {
|
||||
getLocationTypes().then(response => new Promise(resolve => {
|
||||
console.log('getLocationTypes', response);
|
||||
this.locationTypes = response.results.filter(t => t.availableForUsers === true);
|
||||
resolve();
|
||||
}))
|
||||
},
|
||||
openModal() {
|
||||
this.modal.showModal = true;
|
||||
},
|
||||
submitNewAddress(payload) {
|
||||
console.log('submitNewAddress', payload);
|
||||
this.selected.addressId = payload.addressId;
|
||||
this.addAddress.context.addressId = payload.addressId;
|
||||
this.addAddress.context.edit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<teleport to="#social-issues-acc">
|
||||
|
||||
|
||||
<div class="mb-3 row">
|
||||
<div class="col-4">
|
||||
<label class="col-form-label">{{ $t('activity.social_issues') }}</label>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
|
||||
|
||||
<check-social-issue
|
||||
v-for="issue in socialIssuesList"
|
||||
v-bind:key="issue.id"
|
||||
@ -14,7 +14,7 @@
|
||||
v-bind:selection="socialIssuesSelected"
|
||||
@updateSelected="updateIssuesSelected">
|
||||
</check-social-issue>
|
||||
|
||||
|
||||
<div class="my-3">
|
||||
<VueMultiselect
|
||||
name="otherIssues"
|
||||
@ -28,16 +28,15 @@
|
||||
v-bind:taggable="false"
|
||||
v-bind:multiple="false"
|
||||
v-bind:searchable="true"
|
||||
v-bind:allow-empty="true"
|
||||
v-bind:allow-empty="true"
|
||||
v-bind:show-labels="false"
|
||||
v-bind:loading="issueIsLoading"
|
||||
v-bind:placeholder="$t('activity.choose_other_social_issue')"
|
||||
v-bind:options="socialIssuesOther"
|
||||
v-model="value"
|
||||
@select="addIssueInList">
|
||||
</VueMultiselect>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -46,11 +45,11 @@
|
||||
<label class="col-form-label">{{ $t('activity.social_actions') }}</label>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
|
||||
|
||||
<div v-if="actionIsLoading === true">
|
||||
<i class="chill-green fa fa-circle-o-notch fa-spin fa-lg"></i>
|
||||
</div>
|
||||
|
||||
|
||||
<check-social-action
|
||||
v-if="socialIssuesSelected.length || socialActionsSelected.length"
|
||||
v-for="action in socialActionsList"
|
||||
@ -59,19 +58,18 @@
|
||||
v-bind:selection="socialActionsSelected"
|
||||
@updateSelected="updateActionsSelected">
|
||||
</check-social-action>
|
||||
|
||||
|
||||
<span v-else class="inline-choice chill-no-data-statement mt-3">
|
||||
{{ $t('activity.select_first_a_social_issue') }}
|
||||
</span>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { readonly } from 'vue';
|
||||
import VueMultiselect from 'vue-multiselect';
|
||||
import CheckSocialIssue from './SocialIssuesAcc/CheckSocialIssue.vue';
|
||||
import CheckSocialAction from './SocialIssuesAcc/CheckSocialAction.vue';
|
||||
@ -113,7 +111,7 @@ export default {
|
||||
this.issueIsLoading = true;
|
||||
getSocialIssues().then(response => new Promise((resolve, reject) => {
|
||||
this.$store.commit('updateIssuesOther', response.results);
|
||||
|
||||
|
||||
/* Add in list the issues already associated (if not yet listed)
|
||||
*/
|
||||
this.socialIssuesSelected.forEach(issue => {
|
||||
@ -121,34 +119,34 @@ export default {
|
||||
this.$store.commit('addIssueInList', issue);
|
||||
}
|
||||
}, this);
|
||||
|
||||
|
||||
/* Remove from multiselect the issues that are not yet in checkbox list
|
||||
*/
|
||||
this.socialIssuesList.forEach(issue => {
|
||||
this.$store.commit('removeIssueInOther', issue);
|
||||
}, this);
|
||||
|
||||
|
||||
/* Filter issues
|
||||
*/
|
||||
this.$store.commit('filterList', 'issues');
|
||||
|
||||
|
||||
/* Add in list the actions already associated (if not yet listed)
|
||||
*/
|
||||
this.socialActionsSelected.forEach(action => {
|
||||
this.$store.commit('addActionInList', action);
|
||||
}, this);
|
||||
|
||||
|
||||
/* Filter issues
|
||||
*/
|
||||
this.$store.commit('filterList', 'actions');
|
||||
|
||||
|
||||
this.issueIsLoading = false;
|
||||
resolve();
|
||||
}));
|
||||
}));
|
||||
},
|
||||
methods: {
|
||||
|
||||
/* When choosing an issue in multiselect, add it in checkboxes (as selected),
|
||||
|
||||
/* When choosing an issue in multiselect, add it in checkboxes (as selected),
|
||||
remove it from multiselect, and add socialActions concerned
|
||||
*/
|
||||
addIssueInList(value) {
|
||||
@ -171,30 +169,30 @@ export default {
|
||||
//console.log('updateActionsSelected', actions);
|
||||
this.$store.dispatch('updateActionsSelected', actions);
|
||||
},
|
||||
/* Add socialActions concerned: after reset, loop on each issue selected
|
||||
/* Add socialActions concerned: after reset, loop on each issue selected
|
||||
to get social actions concerned
|
||||
*/
|
||||
updateActionsList() {
|
||||
//console.log('updateActionsList');
|
||||
this.resetActionsList();
|
||||
this.socialIssuesSelected.forEach(item => {
|
||||
|
||||
|
||||
this.actionIsLoading = true;
|
||||
getSocialActionByIssue(item.id)
|
||||
.then(actions => new Promise((resolve, reject) => {
|
||||
|
||||
|
||||
actions.results.forEach(action => {
|
||||
this.$store.commit('addActionInList', action);
|
||||
}, this);
|
||||
|
||||
|
||||
this.$store.commit('filterList', 'actions');
|
||||
|
||||
|
||||
this.actionIsLoading = false;
|
||||
resolve();
|
||||
}));
|
||||
}, this);
|
||||
},
|
||||
/* Reset socialActions List: flush list and restore selected actions
|
||||
/* Reset socialActions List: flush list and restore selected actions
|
||||
*/
|
||||
resetActionsList() {
|
||||
this.$store.commit('resetActionsList');
|
||||
@ -207,7 +205,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
span.multiselect__single {
|
||||
display: none !important;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n'
|
||||
|
||||
const appMessages = {
|
||||
const activityMessages = {
|
||||
fr: {
|
||||
activity: {
|
||||
//
|
||||
@ -8,7 +8,7 @@ const appMessages = {
|
||||
choose_other_social_issue: "Ajouter une autre problématique sociale...",
|
||||
social_actions: "Actions d'accompagnement",
|
||||
select_first_a_social_issue: "Sélectionnez d'abord une problématique sociale",
|
||||
|
||||
|
||||
//
|
||||
add_persons: "Ajouter des personnes concernées",
|
||||
bloc_persons: "Usagers",
|
||||
@ -16,12 +16,28 @@ const appMessages = {
|
||||
bloc_persons_not_associated: "Tiers non-pro.",
|
||||
bloc_thirdparty: "Tiers professionnels",
|
||||
bloc_users: "T(M)S",
|
||||
|
||||
//
|
||||
location: "Localisation",
|
||||
choose_location: "Choisissez une localisation",
|
||||
choose_location_type: "Choisissez un type de localisation",
|
||||
create_new_location: "Créer une nouvelle localisation",
|
||||
location_fields: {
|
||||
name: "Nom",
|
||||
type: "Type",
|
||||
phonenumber1: "Téléphone",
|
||||
phonenumber2: "Autre téléphone",
|
||||
email: "Adresse courriel",
|
||||
},
|
||||
create_address: 'Créer une adresse',
|
||||
edit_address: "Modifier l'adresse"
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(appMessages.fr, personMessages.fr);
|
||||
Object.assign(activityMessages.fr, personMessages.fr);
|
||||
|
||||
export {
|
||||
appMessages
|
||||
activityMessages
|
||||
};
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { createApp } from 'vue';
|
||||
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
|
||||
import { appMessages } from './i18n'
|
||||
import { activityMessages } from './i18n'
|
||||
import store from './store'
|
||||
|
||||
import App from './App.vue';
|
||||
|
||||
const i18n = _createI18n(appMessages);
|
||||
const i18n = _createI18n(activityMessages);
|
||||
|
||||
const app = createApp({
|
||||
template: `<app></app>`,
|
||||
|
@ -114,6 +114,10 @@ const store = createStore({
|
||||
state.activity.users = state.activity.users.filter(user => user !== payload);
|
||||
break;
|
||||
};
|
||||
},
|
||||
updateLocation(state, value) {
|
||||
console.log('### mutation: updateLocation', value);
|
||||
state.activity.location = value;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
@ -173,6 +177,12 @@ const store = createStore({
|
||||
break;
|
||||
};
|
||||
commit('removePersonInvolved', payload);
|
||||
},
|
||||
updateLocation({ commit }, value) {
|
||||
console.log('### action: updateLocation', value);
|
||||
let hiddenLocation = document.getElementById("chill_activitybundle_activity_location");
|
||||
hiddenLocation.value = value.id;
|
||||
commit('updateLocation', value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -0,0 +1,88 @@
|
||||
<h2 class="badge-title">
|
||||
<span class="title_label">
|
||||
|
||||
{% if activity.date %}
|
||||
<h3>{{ activity.date|format_date('short') }}</h3>
|
||||
{% endif %}
|
||||
|
||||
<div class="duration">
|
||||
{% if activity.durationTime and t.durationTimeVisible %}
|
||||
<p>
|
||||
<abbr class="fa fa-fw fa-hourglass-end" title="{{ 'Duration Time'|trans }}"></abbr>
|
||||
{{ activity.durationTime|date('H:i') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if activity.travelTime and t.travelTimeVisible %}
|
||||
<p>
|
||||
<abbr class="fa fa-fw fa-car" title="{{ 'Travel time'|trans }}"></abbr>
|
||||
{{ activity.travelTime|date('H:i') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</span>
|
||||
<span class="title_action">
|
||||
|
||||
{{ activity.type.name | localize_translatable_string }}
|
||||
|
||||
<ul class="small_in_title">
|
||||
<li>
|
||||
<abbr title="{{ 'location'|trans }}">{{ 'location'|trans ~ ': ' }}</abbr>
|
||||
{# TODO {% if activity.location %}{{ activity.location }}{% endif %} #}
|
||||
Domicile de l'usager
|
||||
</li>
|
||||
{% if activity.user and t.userVisible %}
|
||||
<li>
|
||||
<abbr title="{{ 'Referrer'|trans }}">{{ 'Referrer'|trans ~ ': ' }}</abbr>
|
||||
{{ activity.user.usernameCanonical }}
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
<ul class="list-content">
|
||||
{%- if t.reasonsVisible -%}
|
||||
{%- if activity.reasons is not empty -%}
|
||||
<li class="reasons">
|
||||
{% for r in activity.reasons %}
|
||||
{{ r|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
</li>
|
||||
{%- endif -%}
|
||||
{% endif %}
|
||||
{%- if t.socialIssuesVisible %}
|
||||
{%- if activity.socialIssues is not empty -%}
|
||||
<li class="social-issues">
|
||||
{% for r in activity.socialIssues %}
|
||||
{{ r|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
</li>
|
||||
{%- endif -%}
|
||||
{% endif %}
|
||||
{%- if t.socialActionsVisible -%}
|
||||
{%- if activity.socialActions is not empty -%}
|
||||
<li class="social-actions">
|
||||
{% for r in activity.socialActions %}
|
||||
{{ r|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
</li>
|
||||
{%- endif -%}
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
{#
|
||||
{% if context == 'person' and activity.accompanyingPeriod is not empty %}
|
||||
<div class="mt-3">
|
||||
<a class="btn btn-sm btn-outline-primary"
|
||||
title="{{ 'Period number %number%'|trans({'%number%': activity.accompanyingPeriod.id}) }}"
|
||||
href="{{ chill_path_add_return_path(
|
||||
"chill_person_accompanying_course_index",
|
||||
{ 'accompanying_period_id': activity.accompanyingPeriod.id }
|
||||
) }}"><i class="fa fa-random"></i>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
#}
|
@ -5,17 +5,17 @@
|
||||
|
||||
{% if context == 'person' %}
|
||||
{% set blocs = [
|
||||
{ 'title': 'Others persons'|trans,
|
||||
{ 'title': 'Others persons'|trans,
|
||||
'items': entity.persons,
|
||||
'path' : 'chill_person_view',
|
||||
'key' : 'person_id'
|
||||
},
|
||||
{ 'title': 'Third parties'|trans,
|
||||
{ 'title': 'Third parties'|trans,
|
||||
'items': entity.thirdParties,
|
||||
'path' : 'chill_3party_3party_show',
|
||||
'key' : 'thirdparty_id'
|
||||
},
|
||||
{ 'title': 'Users concerned'|trans,
|
||||
{ 'title': 'Users concerned'|trans,
|
||||
'items': entity.users,
|
||||
'path' : 'admin_user_show',
|
||||
'key' : 'id'
|
||||
@ -23,22 +23,22 @@
|
||||
] %}
|
||||
{% else %}
|
||||
{% set blocs = [
|
||||
{ 'title': 'Persons in accompanying course'|trans,
|
||||
{ 'title': 'Persons in accompanying course'|trans,
|
||||
'items': entity.personsAssociated,
|
||||
'path' : 'chill_person_view',
|
||||
'key' : 'person_id'
|
||||
},
|
||||
{ 'title': 'Third persons'|trans,
|
||||
{ 'title': 'Third persons'|trans,
|
||||
'items': entity.personsNotAssociated,
|
||||
'path' : 'chill_person_view',
|
||||
'key' : 'person_id'
|
||||
},
|
||||
{ 'title': 'Third parties'|trans,
|
||||
{ 'title': 'Third parties'|trans,
|
||||
'items': entity.thirdParties,
|
||||
'path' : 'chill_3party_3party_show',
|
||||
'key' : 'thirdparty_id'
|
||||
},
|
||||
{ 'title': 'Users concerned'|trans,
|
||||
{ 'title': 'Users concerned'|trans,
|
||||
'items': entity.users,
|
||||
'path' : 'admin_user_show',
|
||||
'key' : 'id'
|
||||
@ -101,3 +101,31 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if (with_display == 'wrap-list') %}
|
||||
<div class="concerned-groups wrap-list">
|
||||
{% for bloc in blocs %}
|
||||
<div class="wl-row">
|
||||
{% if bloc.items|length > 0 %}
|
||||
<div class="wl-col title">
|
||||
<h4>{{ bloc.title }}</h4>
|
||||
</div>
|
||||
<div class="wl-col list">
|
||||
{% for item in bloc.items %}
|
||||
|
||||
<span class="wl-item {% if (badge_person is defined and badge_person == true) %}badge-person{% else %}badge bg-primary{% endif %}">
|
||||
<a href="{{ _self.href(bloc.path, bloc.key, item.id) }}">
|
||||
{{ item|chill_entity_render_box({
|
||||
'render': 'raw',
|
||||
'addAltNames': false
|
||||
}) }}
|
||||
</a>
|
||||
</span>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -54,7 +54,10 @@
|
||||
{{ form_row(edit_form.date) }}
|
||||
{% endif %}
|
||||
|
||||
{# TODO .. location #}
|
||||
{%- if edit_form.location is defined -%}
|
||||
{{ form_row(edit_form.location) }}
|
||||
<div id="location"></div>
|
||||
{% endif %}
|
||||
|
||||
{%- if edit_form.durationTime is defined -%}
|
||||
{{ form_row(edit_form.durationTime) }}
|
||||
|
@ -6,10 +6,10 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="activity-edit">
|
||||
|
||||
<div id="activity"></div> {# <=== vue component #}
|
||||
{% include 'ChillActivityBundle:Activity:edit.html.twig' %}
|
||||
|
||||
|
||||
<div id="activity"></div> {# <=== vue component #}
|
||||
{% include 'ChillActivityBundle:Activity:edit.html.twig' %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@ -18,10 +18,10 @@
|
||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]',
|
||||
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]',
|
||||
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
|
||||
});
|
||||
window.activity = {{ activity_json|json_encode|raw }};
|
||||
window.activity = {{ activity_json|json_encode|raw }};
|
||||
</script>
|
||||
{{ encore_entry_script_tags('vue_activity') }}
|
||||
{% endblock %}
|
||||
|
@ -22,10 +22,10 @@
|
||||
|
||||
{% block personcontent %}
|
||||
<div class="activity-edit">
|
||||
|
||||
<div id="activity"></div> {# <=== vue component #}
|
||||
{% include 'ChillActivityBundle:Activity:edit.html.twig' %}
|
||||
|
||||
|
||||
<div id="activity"></div> {# <=== vue component #}
|
||||
{% include 'ChillActivityBundle:Activity:edit.html.twig' %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]',
|
||||
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]',
|
||||
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
|
||||
});
|
||||
window.activity = {{ activity_json|json_encode|raw }};
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="activity-list context-{{ context }}">
|
||||
<div class="context-{{ context }}">
|
||||
|
||||
{% if activities|length == 0 %}
|
||||
<p class="chill-no-data-statement">
|
||||
@ -7,176 +7,56 @@
|
||||
</p>
|
||||
|
||||
{% else %}
|
||||
<div class="flex-table">
|
||||
<div class="flex-table activity-list">
|
||||
{% for activity in activities %}
|
||||
{% set t = activity.type %}
|
||||
<div class="item-bloc">
|
||||
<div class="item-row main">
|
||||
<div class="item-col">
|
||||
|
||||
{% if activity.date %}
|
||||
<h3>{{ activity.date|format_date('long') }}</h3>
|
||||
{% endif %}
|
||||
|
||||
<div class="duration">
|
||||
{% if activity.durationTime and t.durationTimeVisible %}
|
||||
<p>
|
||||
<i class="fa fa-fw fa-hourglass-end"></i>
|
||||
{{ activity.durationTime|date('H:i') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if activity.travelTime and t.travelTimeVisible %}
|
||||
<p>
|
||||
<i class="fa fa-fw fa-car"></i>
|
||||
{{ activity.travelTime|date('H:i') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if context == 'person' and activity.accompanyingPeriod is not empty %}
|
||||
<div class="mt-3">
|
||||
<a class="btn btn-sm btn-outline-primary"
|
||||
title="{{ 'Period number %number%'|trans({'%number%': activity.accompanyingPeriod.id}) }}"
|
||||
href="{{ chill_path_add_return_path(
|
||||
"chill_person_accompanying_course_index",
|
||||
{ 'accompanying_period_id': activity.accompanyingPeriod.id }
|
||||
) }}"><i class="fa fa-random"></i>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<div class="item-col">
|
||||
|
||||
<div class="float-button top">
|
||||
<div class="box">
|
||||
|
||||
<div class="action">
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_show', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
||||
class="btn btn-sm btn-show "></a>
|
||||
</li>
|
||||
{% if no_action is not defined or no_action == false %}
|
||||
{# TODO
|
||||
{% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %}
|
||||
#}
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_edit', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
||||
class="btn btn-sm btn-update "></a>
|
||||
</li>
|
||||
{# TODO
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_ACTIVITY_DELETE', activity) %}
|
||||
#}
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_delete', { 'id': activity.id, 'person_id' : person_id, 'accompanying_period_id': accompanying_course_id } ) }}"
|
||||
class="btn btn-sm btn-delete "></a>
|
||||
</li>
|
||||
{# TODO
|
||||
{% endif %}
|
||||
#}
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ul class="list-content">
|
||||
|
||||
{% if activity.user and t.userVisible %}
|
||||
<li>
|
||||
<abbr class="referrer" title="{{ 'Referrer'|trans }}">ref:</abbr>
|
||||
<b>{{ activity.user.usernameCanonical }}</b>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li>
|
||||
<b>{{ activity.type.name | localize_translatable_string }}</b>
|
||||
|
||||
{% if activity.attendee is not null and t.attendeeVisible %}
|
||||
{% if activity.attendee %}
|
||||
{{ '→ ' ~ 'present'|trans|capitalize }}
|
||||
{% else %}
|
||||
{{ '→ ' ~ 'not present'|trans|capitalize }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<b>{{ 'location'|trans ~ ': ' }}</b>
|
||||
Domicile de l'usager
|
||||
|
||||
{# TODO {% if activity.location %}{{ activity.location }}{% endif %}
|
||||
#}
|
||||
|
||||
</li>
|
||||
|
||||
{%- if t.reasonsVisible -%}
|
||||
<li>
|
||||
{%- if activity.reasons is not empty -%}
|
||||
{% for r in activity.reasons %}
|
||||
{{ r|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
{%- endif -%}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{%- if t.socialIssuesVisible %}
|
||||
<li class="social-issues">
|
||||
{%- if activity.socialIssues is not empty -%}
|
||||
{% for r in activity.socialIssues %}
|
||||
{{ r|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
{%- endif -%}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{%- if t.socialActionsVisible -%}
|
||||
<li class="social-actions">
|
||||
{%- if activity.socialActions is not empty -%}
|
||||
{% for r in activity.socialActions %}
|
||||
{{ r|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
{%- endif -%}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="item-row">
|
||||
{% include '@ChillActivity/Activity/activity-badge-title.html.twig' %}
|
||||
</div>
|
||||
|
||||
|
||||
{% if activity.comment.comment is not empty
|
||||
or activity.persons|length > 0
|
||||
or activity.thirdParties|length > 0
|
||||
or activity.users|length > 0
|
||||
%}
|
||||
<div class="item-row comment separator">
|
||||
{% if activity.comment.comment is not empty %}
|
||||
{{ activity.comment|chill_entity_render_box({
|
||||
'disable_markdown': false,
|
||||
'limit_lines': 3,
|
||||
'metadata': false,
|
||||
}) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="item-row details">
|
||||
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
|
||||
'context': context,
|
||||
'with_display': 'bloc',
|
||||
'entity': activity,
|
||||
'badge_person': true
|
||||
} %}
|
||||
</div>
|
||||
<div class="main">
|
||||
{% if activity.comment.comment is not empty %}
|
||||
{{ activity.comment|chill_entity_render_box({
|
||||
'disable_markdown': false,
|
||||
'limit_lines': 3,
|
||||
'metadata': false,
|
||||
}) }}
|
||||
{% endif %}
|
||||
|
||||
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
|
||||
'context': context,
|
||||
'with_display': 'row',
|
||||
'entity': activity,
|
||||
'badge_person': true
|
||||
} %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="item-row separator">
|
||||
<div class="updatedBy"></div>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_show', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
||||
class="btn btn-sm btn-show "></a>
|
||||
</li>
|
||||
{% if no_action is not defined or no_action == false %}
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_edit', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
||||
class="btn btn-sm btn-update "></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_delete', { 'id': activity.id, 'person_id' : person_id, 'accompanying_period_id': accompanying_course_id } ) }}"
|
||||
class="btn btn-sm btn-delete "></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
@ -0,0 +1,10 @@
|
||||
<div class="activity-list">
|
||||
{% for activity in activities | slice(0,5) %}
|
||||
{% set t = activity.type %}
|
||||
|
||||
<a href="{{ path('chill_activity_activity_show', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"></a>
|
||||
|
||||
{% include '@ChillActivity/Activity/activity-badge-title.html.twig' %}
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
@ -55,7 +55,10 @@
|
||||
{{ form_row(form.date) }}
|
||||
{% endif %}
|
||||
|
||||
{# TODO .. location #}
|
||||
{%- if form.location is defined -%}
|
||||
{{ form_row(form.location) }}
|
||||
<div id="location"></div>
|
||||
{% endif %}
|
||||
|
||||
{%- if form.durationTime is defined -%}
|
||||
{{ form_row(form.durationTime) }}
|
||||
|
@ -6,10 +6,10 @@
|
||||
|
||||
{% block personcontent %}
|
||||
<div class="activity-new">
|
||||
|
||||
<div id="activity"></div> {# <=== vue component #}
|
||||
{% include 'ChillActivityBundle:Activity:new.html.twig' with {'context': 'person'} %}
|
||||
|
||||
|
||||
<div id="activity"></div> {# <=== vue component #}
|
||||
{% include 'ChillActivityBundle:Activity:new.html.twig' with {'context': 'person'} %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]',
|
||||
chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]',
|
||||
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
|
||||
});
|
||||
window.activity = {{ activity_json|json_encode|raw }};
|
||||
|
@ -62,6 +62,21 @@
|
||||
<dt class="inline">{{ 'Date'|trans }}</dt>
|
||||
<dd>{{ entity.date|format_date('long') }}</dd>
|
||||
|
||||
{% if t.locationVisible %}
|
||||
<dt class="inline">{{ 'Activity location'|trans }}</dt>
|
||||
<dd>
|
||||
{% if entity.location is not null %}
|
||||
<p>
|
||||
<span>{{ entity.location.locationType.title|localize_translatable_string }}</span>
|
||||
{{ entity.location.name }}
|
||||
</p>
|
||||
{{ entity.location.address|chill_entity_render_box }}
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if t.durationTimeVisible %}
|
||||
<dt class="inline">{{ 'Duration Time'|trans }}</dt>
|
||||
<dd>{% if entity.durationTime is not null %}
|
||||
@ -73,7 +88,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% if t.travelTimeVisible %}
|
||||
<dt class="inline">{{ 'Travel Time'|trans }}</dt>
|
||||
<dt class="inline">{{ 'Travel time'|trans }}</dt>
|
||||
<dd>{% if entity.travelTime is not null %}
|
||||
{{ entity.travelTime|date('H:i') }}
|
||||
{% else %}
|
||||
|
@ -23,6 +23,7 @@ Update: Mettre à jour
|
||||
Update activity: Modifier l'activité
|
||||
Scope: Cercle
|
||||
Activity data: Données de l'activité
|
||||
Activity location: Localisation de l'activité
|
||||
No reason associated: Aucun sujet
|
||||
No social issues associated: Aucune problématique sociale
|
||||
No social actions associated: Aucune action d'accompagnement
|
||||
@ -210,3 +211,5 @@ Group by reasons: Sujet d'activité
|
||||
Aggregate by activity user: Aggréger par utilisateur lié à l'activité
|
||||
Aggregate by activity type: Aggréger par type d'activité
|
||||
Aggregate by activity reason: Aggréger par sujet de l'activité
|
||||
|
||||
Last activities: Les dernières activités
|
||||
|
@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\AsideActivityBundle\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
|
||||
/**
|
||||
* Controller for activity configuration
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
* @author Champs Libres <info@champs-libres.coop>
|
||||
*/
|
||||
class AdminController extends AbstractController
|
||||
{
|
||||
|
||||
public function redirectToAdminIndexAction()
|
||||
{
|
||||
return $this->redirectToRoute('chill_main_admin_central');
|
||||
}
|
||||
}
|
@ -3,6 +3,9 @@
|
||||
namespace Chill\AsideActivityBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\CRUD\Controller\CRUDController;
|
||||
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
|
||||
/**
|
||||
@ -10,5 +13,12 @@ use Chill\MainBundle\CRUD\Controller\CRUDController;
|
||||
*/
|
||||
class AsideActivityCategoryController extends CRUDController
|
||||
{
|
||||
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
|
||||
{
|
||||
/** @var QueryBuilder $query */
|
||||
$query->addOrderBy('e.ordering', 'ASC');
|
||||
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,7 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\AsideActivityBundle\Controller;
|
||||
|
||||
use Chill\AsideActivityBundle\Entity\AsideActivity;
|
||||
use Chill\AsideActivityBundle\Repository\AsideActivityCategoryRepository;
|
||||
use Chill\MainBundle\CRUD\Controller\CRUDController;
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||
@ -12,12 +15,21 @@ use Doctrine\Common\Collections\Criteria;
|
||||
|
||||
final class AsideActivityController extends CRUDController
|
||||
{
|
||||
protected function buildQueryEntities(string $action, Request $request)
|
||||
|
||||
private $categoryRepository;
|
||||
|
||||
public function __construct(AsideActivityCategoryRepository $categoryRepository)
|
||||
{
|
||||
$qb = parent::buildQueryEntities($action, $request);
|
||||
$this->categoryRepository = $categoryRepository;
|
||||
}
|
||||
|
||||
protected function buildQueryEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null)
|
||||
{
|
||||
$qb = parent::buildQueryEntities($action, $request, $filterOrder);
|
||||
|
||||
if ('index' === $action) {
|
||||
$qb->andWhere($qb->expr()->eq('e.agent', ':user'));
|
||||
$qb->where($qb->expr()->eq('e.agent', ':user'));
|
||||
$qb->orWhere($qb->expr()->eq('e.createdBy', ':user'));
|
||||
$qb->setParameter('user', $this->getUser());
|
||||
}
|
||||
|
||||
@ -37,5 +49,24 @@ final class AsideActivityController extends CRUDController
|
||||
return parent::orderQuery($action, $query, $request, $paginator);
|
||||
}
|
||||
|
||||
public function createEntity(string $action, Request $request): object
|
||||
{
|
||||
$asideActivity = new AsideActivity();
|
||||
|
||||
$duration = $request->query->get('duration', '300');
|
||||
$duration = \DateTime::createFromFormat('U', $duration);
|
||||
$asideActivity->setDuration($duration);
|
||||
|
||||
$categoryId = $request->query->get('type', 7);
|
||||
if($categoryId === null){
|
||||
return $this->createNotFoundException('You must give a valid category id');
|
||||
}
|
||||
$category = $this->categoryRepository->find($categoryId);
|
||||
$asideActivity->setType($category);
|
||||
|
||||
$note = $request->query->get('note', null);
|
||||
$asideActivity->setNote($note);
|
||||
|
||||
return $asideActivity;
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,12 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte
|
||||
*/
|
||||
public function load(array $configs, ContainerBuilder $container): void
|
||||
{
|
||||
|
||||
$configuration = $this->getConfiguration($configs, $container);
|
||||
$config = $this->processConfiguration($configuration, $configs);
|
||||
|
||||
$container->setParameter('chill_aside_activity.form.time_duration', $config['form']['time_duration']);
|
||||
|
||||
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
|
||||
$loader->load('services.yaml');
|
||||
$loader->load('services/form.yaml');
|
||||
@ -81,7 +87,7 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte
|
||||
'controller' => \Chill\AsideActivityBundle\Controller\AsideActivityController::class,
|
||||
'actions' => [
|
||||
'index' => [
|
||||
'template' => '@ChillAsideActivity/asideActivity/list.html.twig',
|
||||
'template' => '@ChillAsideActivity/asideActivity/index.html.twig',
|
||||
'role' => 'ROLE_USER'
|
||||
],
|
||||
'new' => [
|
||||
|
@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\AsideActivityBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||
|
||||
class Configuration implements ConfigurationInterface
|
||||
{
|
||||
public function getConfigTreeBuilder()
|
||||
{
|
||||
$treeBuilder = new TreeBuilder('chill_aside_activity');
|
||||
|
||||
$treeBuilder->getRootNode('chill_aside_activity')
|
||||
->children()
|
||||
->arrayNode('form')
|
||||
->canBeEnabled()
|
||||
->children()
|
||||
->arrayNode('time_duration')
|
||||
->isRequired()
|
||||
->defaultValue(
|
||||
[
|
||||
[ 'label' => '5 minutes', 'seconds' => 300],
|
||||
[ 'label' => '10 minutes', 'seconds' => 600],
|
||||
[ 'label' => '15 minutes', 'seconds' => 900],
|
||||
[ 'label' => '20 minutes', 'seconds' => 1200],
|
||||
[ 'label' => '25 minutes', 'seconds' => 1500],
|
||||
[ 'label' => '30 minutes', 'seconds' => 1800],
|
||||
[ 'label' => '45 minutes', 'seconds' => 2700],
|
||||
[ 'label' => '1 hour', 'seconds' => 3600],
|
||||
[ 'label' => '1 hour 15', 'seconds' => 4500],
|
||||
[ 'label' => '1 hour 30', 'seconds' => 5400],
|
||||
[ 'label' => '1 hour 45', 'seconds' => 6300],
|
||||
[ 'label' => '2 hours', 'seconds' => 7200],
|
||||
[ 'label' => '2 hours 30', 'seconds' => 9000],
|
||||
[ 'label' => '3 hours', 'seconds' => 10800],
|
||||
[ 'label' => '3 hours 30', 'seconds' => 12600],
|
||||
[ 'label' => '4 hours', 'seconds' => 14400],
|
||||
[ 'label' => '4 hours 30', 'seconds' => 16200],
|
||||
[ 'label' => '5 hours', 'seconds' => 18000],
|
||||
[ 'label' => '5 hours 30', 'seconds' => 19800],
|
||||
[ 'label' => '6 hours', 'seconds' => 21600],
|
||||
[ 'label' => '6 hours 30', 'seconds' => 23400],
|
||||
[ 'label' => '7 hours', 'seconds' => 25200],
|
||||
[ 'label' => '7 hours 30', 'seconds' => 27000],
|
||||
[ 'label' => '8 hours', 'seconds' => 28800],
|
||||
[ 'label' => '8 hours 30', 'seconds' => 30600],
|
||||
[ 'label' => '9 hours', 'seconds' => 32400],
|
||||
[ 'label' => '9 hours 30', 'seconds' => 34200],
|
||||
[ 'label' => '10 hours', 'seconds' => 36000],
|
||||
[ 'label' => '1/2 day', 'seconds' => 14040],
|
||||
[ 'label' => '1 day', 'seconds' => 28080],
|
||||
[ 'label' => '1 1/2 days', 'seconds' => 42120],
|
||||
[ 'label' => '2 days', 'seconds' => 56160],
|
||||
[ 'label' => '2 1/2 days', 'seconds' => 70200],
|
||||
[ 'label' => '3 days', 'seconds' => 84240],
|
||||
[ 'label' => '3 1/2 days', 'seconds' => 98280],
|
||||
[ 'label' => '4 days', 'seconds' => 112320],
|
||||
[ 'label' => '4 1/2 days', 'seconds' => 126360],
|
||||
[ 'label' => '5 days', 'seconds' => 140400],
|
||||
[ 'label' => '5 1/2 days', 'seconds' => 154440],
|
||||
[ 'label' => '6 days', 'seconds' => 168480],
|
||||
[ 'label' => '6 1/2 days', 'seconds' => 182520],
|
||||
[ 'label' => '7 days', 'seconds' => 196560],
|
||||
[ 'label' => '7 1/2 days', 'seconds' => 210600],
|
||||
[ 'label' => '8 days', 'seconds' => 224640],
|
||||
[ 'label' => '8 1/2 days', 'seconds' => 238680],
|
||||
[ 'label' => '9 days', 'seconds' => 252720],
|
||||
[ 'label' => '9 1/2 days', 'seconds' => 266760],
|
||||
[ 'label' => '10 days', 'seconds' => 280800],
|
||||
[ 'label' => '10 1/2days', 'seconds' => 294840],
|
||||
[ 'label' => '11 days', 'seconds' => 308880],
|
||||
[ 'label' => '11 1/2 days', 'seconds' => 322920],
|
||||
[ 'label' => '12 days', 'seconds' => 336960],
|
||||
[ 'label' => '12 1/2 days', 'seconds' => 351000],
|
||||
[ 'label' => '13 days', 'seconds' => 365040],
|
||||
[ 'label' => '13 1/2 days', 'seconds' => 379080],
|
||||
[ 'label' => '14 days', 'seconds' => 393120],
|
||||
[ 'label' => '14 1/2 days', 'seconds' => 407160],
|
||||
[ 'label' => '15 days', 'seconds' => 421200],
|
||||
[ 'label' => '15 1/2 days', 'seconds' => 435240],
|
||||
[ 'label' => '16 days', 'seconds' => 449280],
|
||||
[ 'label' => '16 1/2 days', 'seconds' => 463320],
|
||||
[ 'label' => '17 days', 'seconds' => 477360],
|
||||
[ 'label' => '17 1/2 days', 'seconds' => 491400],
|
||||
[ 'label' => '18 days', 'seconds' => 505440],
|
||||
[ 'label' => '18 1/2 days', 'seconds' => 519480],
|
||||
[ 'label' => '19 days', 'seconds' => 533520],
|
||||
[ 'label' => '19 1/2 days', 'seconds' => 547560],
|
||||
[ 'label' => '20 days', 'seconds' => 561600],
|
||||
[ 'label' => '20 1/2 days', 'seconds' => 575640],
|
||||
[ 'label' => '21 days', 'seconds' => 580680],
|
||||
[ 'label' => '21 1/2 days', 'seconds' => 603720],
|
||||
[ 'label' => '22 days', 'seconds' => 617760],
|
||||
[ 'label' => '22 1/2 days', 'seconds' => 631800],
|
||||
[ 'label' => '23 days', 'seconds' => 645840],
|
||||
[ 'label' => '23 1/2 days', 'seconds' => 659880],
|
||||
[ 'label' => '24 days', 'seconds' => 673920],
|
||||
[ 'label' => '24 1/2 days', 'seconds' => 687960],
|
||||
[ 'label' => '25 days', 'seconds' => 702000],
|
||||
[ 'label' => '25 1/2 days', 'seconds' => 716040],
|
||||
[ 'label' => '26 days', 'seconds' => 730080],
|
||||
[ 'label' => '26 1/2 days', 'seconds' => 744120],
|
||||
[ 'label' => '27 days', 'seconds' => 758160],
|
||||
[ 'label' => '27 1/2 days', 'seconds' => 772200],
|
||||
[ 'label' => '28 days', 'seconds' => 786240],
|
||||
[ 'label' => '28 1/2 days', 'seconds' => 800280],
|
||||
[ 'label' => '29 days', 'seconds' => 814320],
|
||||
[ 'label' => '29 1/2 days', 'seconds' => 828360],
|
||||
[ 'label' => '30 days', 'seconds' => 842400],
|
||||
]
|
||||
)
|
||||
->info('The intervals of time to show in activity form')
|
||||
|
||||
->prototype('array')
|
||||
->children()
|
||||
->scalarNode('seconds')
|
||||
->info("The number of seconds of this duration. Must be an integer.")
|
||||
->cannotBeEmpty()
|
||||
->validate()
|
||||
->ifTrue(function($data) {
|
||||
return !is_int($data);
|
||||
})->thenInvalid("The value %s is not a valid integer")
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('label')
|
||||
->cannotBeEmpty()
|
||||
->info("The label to show into fields")
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end();
|
||||
|
||||
return $treeBuilder;
|
||||
}
|
||||
}
|
@ -7,6 +7,8 @@ namespace Chill\AsideActivityBundle\Entity;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
@ -27,10 +29,33 @@ class AsideActivityCategory
|
||||
private array $title;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity=AsideActivityCategory::class, mappedBy="parent")
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
private bool $isActive = true;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
* @ORM\Column(type="float", options={"default": 0.00})
|
||||
*/
|
||||
private float $ordering = 0.00;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=AsideActivityCategory::class, inversedBy="children")
|
||||
* @ORM\JoinColumn(nullable=true)
|
||||
*/
|
||||
private $parent;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity=AsideActivityCategory::class, mappedBy="parent")
|
||||
*/
|
||||
private $children;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->children = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
@ -59,4 +84,85 @@ class AsideActivityCategory
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getParent(): ?self
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @Assert\Callback()
|
||||
*/
|
||||
public function preventRecursiveParent(ExecutionContextInterface $context, $payload)
|
||||
{
|
||||
if (!$this->hasParent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->getParent() === $this) {
|
||||
// replace parent with old parent. This prevent recursive loop
|
||||
// when displaying form
|
||||
$this->parent = $this->oldParent;
|
||||
$context->buildViolation('You must not add twice the same category in the parent tree (previous result returned)')
|
||||
->atPath('parent')
|
||||
->addViolation()
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
public function hasParent(): bool
|
||||
{
|
||||
return $this->parent !== null;
|
||||
}
|
||||
|
||||
public function setParent(?self $parent): self
|
||||
{
|
||||
// cache the old result for changing it during validaiton
|
||||
$this->oldParent = $this->parent;
|
||||
$this->parent = $parent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection|self[]
|
||||
*/
|
||||
public function getChildren(): Collection
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
public function addChild(self $child): self
|
||||
{
|
||||
if (!$this->children->contains($child)) {
|
||||
$this->children[] = $child;
|
||||
$child->setParent($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeChild(self $child): self
|
||||
{
|
||||
if ($this->children->removeElement($child)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($child->getParent() === $this) {
|
||||
$child->setParent(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOrdering(): float
|
||||
{
|
||||
return $this->ordering;
|
||||
}
|
||||
|
||||
public function setOrdering(float $ordering): AsideActivityCategory
|
||||
{
|
||||
$this->ordering = $ordering;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -3,21 +3,43 @@
|
||||
namespace Chill\AsideActivityBundle\Form;
|
||||
|
||||
use Chill\AsideActivityBundle\Entity\AsideActivityCategory;
|
||||
use Chill\AsideActivityBundle\Templating\Entity\CategoryRender;
|
||||
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
final class AsideActivityCategoryType extends AbstractType
|
||||
{
|
||||
|
||||
protected $translatableStringHelper;
|
||||
|
||||
public function __construct(TranslatableStringHelper $translatableStringHelper, CategoryRender $categoryRender)
|
||||
{
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
$this->categoryRender = $categoryRender;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->add('title', TranslatableStringFormType::class,
|
||||
[
|
||||
'label' => 'Nom',
|
||||
])
|
||||
->add('isActive', ChoiceType::class,
|
||||
->add('parent', EntityType::class, [
|
||||
'class' => AsideActivityCategory::class,
|
||||
'required' => false,
|
||||
'label' => 'Parent',
|
||||
'choice_label' => function (AsideActivityCategory $category){
|
||||
$options = [];
|
||||
return $this->categoryRender->renderString($category, $options);
|
||||
}
|
||||
])
|
||||
->add('ordering', NumberType::class)
|
||||
->add('isActive', ChoiceType::class,
|
||||
[
|
||||
'choices' => [
|
||||
'Yes' => true,
|
||||
|
@ -4,11 +4,13 @@ namespace Chill\AsideActivityBundle\Form;
|
||||
|
||||
use Chill\AsideActivityBundle\Entity\AsideActivity;
|
||||
use Chill\AsideActivityBundle\Entity\AsideActivityCategory;
|
||||
use Chill\AsideActivityBundle\Templating\Entity\CategoryRender;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Form\Type\ChillDateType;
|
||||
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
@ -16,26 +18,27 @@ use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTra
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Templating\EngineInterface;
|
||||
|
||||
final class AsideActivityFormType extends AbstractType
|
||||
{
|
||||
protected array $timeChoices;
|
||||
private TranslatableStringHelper $translatableStringHelper;
|
||||
private TokenStorageInterface $storage;
|
||||
private CategoryRender $categoryRender;
|
||||
|
||||
public function __construct (
|
||||
TranslatableStringHelper $translatableStringHelper,
|
||||
ParameterBagInterface $parameterBag,
|
||||
TokenStorageInterface $storage
|
||||
TokenStorageInterface $storage,
|
||||
CategoryRender $categoryRender
|
||||
){
|
||||
$this->timeChoices = $parameterBag->get('chill_activity.form.time_duration');
|
||||
$this->timeChoices = $parameterBag->get('chill_aside_activity.form.time_duration');
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
$this->storage = $storage;
|
||||
$this->categoryRender = $categoryRender;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
@ -55,7 +58,7 @@ final class AsideActivityFormType extends AbstractType
|
||||
$builder
|
||||
->add('agent', EntityType::class,
|
||||
[
|
||||
'label' => 'Agent',
|
||||
'label' => 'For agent',
|
||||
'required' => true,
|
||||
'class' => User::class,
|
||||
'data' => $this->storage->getToken()->getUser(),
|
||||
@ -70,11 +73,6 @@ final class AsideActivityFormType extends AbstractType
|
||||
[
|
||||
'label' => 'date',
|
||||
'data' => new \DateTime(),
|
||||
//SETTING RANGE ONLY POSSIBLE WITH WIDGET 'CHOICE' AND NOT 'SINGLE_TEXT'?
|
||||
// 'widget' => 'choice',
|
||||
// 'years' => range(2020, date('Y')),
|
||||
// 'months' => range(1, date('m')),
|
||||
// 'days' => range(1, date('d')),
|
||||
'required' => true
|
||||
])
|
||||
->add('type', EntityType::class,
|
||||
@ -83,9 +81,17 @@ final class AsideActivityFormType extends AbstractType
|
||||
'required' => true,
|
||||
'class' => AsideActivityCategory::class,
|
||||
'placeholder' => 'Choose the activity category',
|
||||
// 'choice_label' => 'title[""]'
|
||||
'query_builder' => function(EntityRepository $er) {
|
||||
$qb = $er->createQueryBuilder('ac');
|
||||
$qb->where($qb->expr()->eq('ac.isActive', 'TRUE'))
|
||||
->addOrderBy('ac.ordering', 'ASC')
|
||||
;
|
||||
|
||||
return $qb;
|
||||
},
|
||||
'choice_label' => function (AsideActivityCategory $asideActivityCategory) {
|
||||
return $this->translatableStringHelper->localize($asideActivityCategory->getTitle());
|
||||
$options = [];
|
||||
return $this->categoryRender->renderString($asideActivityCategory, $options);
|
||||
},
|
||||
])
|
||||
->add('duration', ChoiceType::class, $durationTimeOptions)
|
||||
|
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\AsideActivityBundle\Menu;
|
||||
|
||||
use Knp\Menu\MenuItem;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
final class AdminMenuBuilder implements \Chill\MainBundle\Routing\LocalMenuBuilderInterface
|
||||
{
|
||||
private Security $security;
|
||||
|
||||
public function __construct(Security $security)
|
||||
{
|
||||
$this->security = $security;
|
||||
}
|
||||
|
||||
public static function getMenuIds(): array
|
||||
{
|
||||
return ['admin_index', 'admin_section', 'admin_aside_activity'];
|
||||
}
|
||||
|
||||
public function buildMenu($menuId, MenuItem $menu, array $parameters)
|
||||
{
|
||||
// all the entries below must have ROLE_ADMIN permissions
|
||||
if (!$this->security->isGranted('ROLE_ADMIN')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array($menuId, ['admin_index', 'admin_section'])) {
|
||||
$menu->addChild('Aside activities', [
|
||||
'route' => 'chill_crud_aside_activity_category_index'
|
||||
])
|
||||
->setExtras([
|
||||
'order' => 900,
|
||||
'explain' => "Configure aside activities categories"
|
||||
]);
|
||||
} else {
|
||||
$menu
|
||||
->addChild('Aside activity categories', [
|
||||
'route' => 'chill_crud_aside_activity_category_index'
|
||||
])
|
||||
->setExtras([
|
||||
'order' => '50'
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@ namespace Chill\AsideActivityBundle\Menu;
|
||||
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
@ -14,10 +16,12 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
class SectionMenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
protected TranslatorInterface $translator;
|
||||
public AuthorizationCheckerInterface $authorizationChecker;
|
||||
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
public function __construct(TranslatorInterface $translator, AuthorizationCheckerInterface $authorizationChecker)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
$this->authorizationChecker = $authorizationChecker;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -27,13 +31,26 @@ class SectionMenuBuilder implements LocalMenuBuilderInterface
|
||||
*/
|
||||
public function buildMenu($menuId, MenuItem $menu, array $parameters)
|
||||
{
|
||||
$menu->addChild($this->translator->trans('Create an aside activity'), [
|
||||
'route' => 'chill_crud_aside_activity_new'
|
||||
if ($this->authorizationChecker->isGranted('ROLE_USER')){
|
||||
$menu->addChild($this->translator->trans('Create an aside activity'), [
|
||||
'route' => 'chill_crud_aside_activity_new'
|
||||
])
|
||||
->setExtras([
|
||||
'order' => 11,
|
||||
'icons' => [ 'plus' ]
|
||||
]);
|
||||
$menu->addChild($this->translator->trans('Phonecall'), [
|
||||
'route' => 'chill_crud_aside_activity_new',
|
||||
'routeParameters' => [
|
||||
'type' => 1,
|
||||
'duration' => 900,
|
||||
]
|
||||
])
|
||||
->setExtras([
|
||||
'order' => 11,
|
||||
'icons' => [ 'plus' ]
|
||||
'order' => 12,
|
||||
'icons' => ['plus']
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Chill\AsideActivityBundle\Menu;
|
||||
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Chill\TaskBundle\Templating\UI\CountNotificationTask;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Translation\TranslatorInterface;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class UserMenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
|
||||
/**
|
||||
*
|
||||
* @var CountNotificationTask
|
||||
*/
|
||||
public $counter;
|
||||
|
||||
/*
|
||||
* @var TokenStorageInterface
|
||||
*/
|
||||
public $tokenStorage;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
public $translator;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var AuthorizationCheckerInterface
|
||||
*/
|
||||
public $authorizationChecker;
|
||||
|
||||
public function __construct(
|
||||
CountNotificationTask $counter,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
TranslatorInterface $translator,
|
||||
AuthorizationCheckerInterface $authorizationChecker
|
||||
) {
|
||||
$this->counter = $counter;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->translator = $translator;
|
||||
$this->authorizationChecker = $authorizationChecker;
|
||||
}
|
||||
|
||||
|
||||
public function buildMenu($menuId, MenuItem $menu, array $parameters)
|
||||
{
|
||||
if ($this->authorizationChecker->isGranted('ROLE_USER')){
|
||||
$menu->addChild("My aside activities", [
|
||||
'route' => 'chill_crud_aside_activity_index'
|
||||
])
|
||||
->setExtras([
|
||||
'order' => 10,
|
||||
'icon' => 'tasks'
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static function getMenuIds(): array
|
||||
{
|
||||
return [ 'user' ];
|
||||
}
|
||||
|
||||
}
|
@ -1,14 +1,12 @@
|
||||
{% extends "@ChillMain/Admin/layoutWithVerticalMenu.html.twig" %}
|
||||
|
||||
{% block vertical_menu_content %}
|
||||
{{ chill_menu('admin_aside_activity', {
|
||||
'layout': '@ChillAsideActivity/Admin/menu_asideactivity.html.twig',
|
||||
}) }}
|
||||
{{ chill_menu('admin_aside_activity') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block layout_wvm_content %}
|
||||
{% block admin_content %}
|
||||
<!-- block personcontent empty -->
|
||||
<h1>{{ 'Aside activity configuration' |trans }}</h1>
|
||||
<h1>{{ 'Aside activity configuration'|trans }}</h1>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +0,0 @@
|
||||
{% extends "@ChillMain/Menu/verticalMenu.html.twig" %}
|
||||
{% block v_menu_title %}
|
||||
{{ 'Aside activity configuration menu'|trans }}
|
||||
{% endblock %}
|
@ -0,0 +1,12 @@
|
||||
{% set reversed_parents = parents|reverse %}
|
||||
<span
|
||||
class="chill-entity entity-aside_activity-category">
|
||||
{%- for p in reversed_parents %}
|
||||
<span class="parent-{{ loop.revindex0 }}">
|
||||
{{ p.title|localize_translatable_string }}{{ options['default.separator'] }}
|
||||
</span>
|
||||
{%- endfor -%}
|
||||
<span class="child">
|
||||
{{ asideActivityCategory.title|localize_translatable_string }}
|
||||
</span>
|
||||
</span>
|
@ -1,8 +1,91 @@
|
||||
{% extends '@ChillMain/layout.html.twig' %}
|
||||
{% extends "@ChillMain/layout.html.twig" %}
|
||||
|
||||
{% block title %}{{ ('crud.' ~ crud_name ~ '.index.title')|trans({'%crud_name%': crud_name}) }}{% endblock %}
|
||||
{% block title %}
|
||||
{{ 'Aside activity list'|trans }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
{% embed '@ChillAsideActivity/AsideActivity/_index.html.twig' %}
|
||||
{% endembed %}
|
||||
{% endblock content %}
|
||||
<div class="col-md-10 asideactivity-list">
|
||||
<h2>{{ 'My aside activities'|trans }}</h2>
|
||||
|
||||
{% if entities|length == 0 %}
|
||||
<p class="chill-no-data-statement">
|
||||
{{ "There aren't any aside activities."|trans }}
|
||||
<a href="{{ path('chill_crud_aside_activity_new') }}" class="btn btn-create button-small"></a>
|
||||
</p>
|
||||
{% else %}
|
||||
|
||||
<div class="flex-table">
|
||||
|
||||
{% for entity in entities %}
|
||||
|
||||
<div class="item-bloc">
|
||||
<div class="item-row wrap-header">
|
||||
|
||||
<div class="item-col">
|
||||
<h3>
|
||||
{{ entity.type|chill_entity_render_box }}
|
||||
</h3>
|
||||
<div>
|
||||
{% if entity.date %}
|
||||
<span>{{ entity.date|format_date('long') }}</span>
|
||||
{% endif %}
|
||||
{% if entity.date %}
|
||||
<span class="duration">
|
||||
|
||||
<i class="fa fa-fw fa-hourglass-end"></i>
|
||||
{{ entity.duration|date('H:i') }}
|
||||
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<div class="box">
|
||||
<ul class="list-content fa-ul">
|
||||
<li>
|
||||
<span>
|
||||
<abbr class="referrer" title={{ 'Created by'|trans }}>{{ 'By'|trans }}:</abbr>
|
||||
<b>{{ entity.createdBy|chill_entity_render_box }}</b>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
<abbr class="referrer" title={{ 'Created for'|trans }}>{{ 'For'|trans }}:</abbr>
|
||||
<b>{{ entity.agent|chill_entity_render_box }}</b>
|
||||
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="action">
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_crud_aside_activity_view', { 'id': entity.id } ) }}" class="btn btn-show btn-mini"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_crud_aside_activity_edit', { 'id': entity.id }) }}" class="btn btn-update btn-mini "></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_crud_aside_activity_delete', { 'id': entity.id } ) }}" class="btn btn-delete btn-mini"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{{ chill_pagination(paginator) }}
|
||||
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_crud_aside_activity_new') }}" class="btn btn-create">
|
||||
{{ 'Create'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
@ -1,97 +0,0 @@
|
||||
{% extends "@ChillMain/layout.html.twig" %}
|
||||
|
||||
{% block title %}
|
||||
{{ 'Aside activity list' |trans }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-md-10 col-xxl asideactivity-list">
|
||||
<h2>{{ 'My aside activities' |trans }}</h2>
|
||||
|
||||
{% if entities|length == 0 %}
|
||||
<p class="chill-no-data-statement">
|
||||
{{ "There aren't any aside activities."|trans }}
|
||||
<a href="{{ path('chill_crud_aside_activity_new') }}" class="btn btn-create button-small"></a>
|
||||
</p>
|
||||
{% else %}
|
||||
|
||||
<div
|
||||
class="flex-table my-4 list-records">
|
||||
{# Sort activities according to date in descending order #}
|
||||
{% for entity in entities %}
|
||||
{% set t = entity.type %}
|
||||
|
||||
<div class="item-bloc">
|
||||
<div class="item-row main">
|
||||
<div class="item-col">
|
||||
|
||||
<h3>
|
||||
<b>{{ entity.type.title | localize_translatable_string }}</b>
|
||||
</h3>
|
||||
|
||||
{% if entity.date %}
|
||||
<p>{{ entity.date|format_date('long') }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="duration">
|
||||
<p>
|
||||
<i class="fa fa-fw fa-hourglass-end"></i>
|
||||
{{ entity.duration|date('H:i') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="list-content">
|
||||
{% if entity.createdBy %}
|
||||
<li>
|
||||
<b>{{ 'Created by: '|trans }}{{ entity.createdBy.usernameCanonical }}</b>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# {%
|
||||
if entity.note is not empty
|
||||
or entity.createdBy|length > 0
|
||||
%}
|
||||
<div class="item-row details">
|
||||
{% if entity.note is not empty %}
|
||||
<div class="item-col comment">
|
||||
{{ entity.note|chill_markdown_to_html }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endif %} #}
|
||||
<div class="item-col">
|
||||
<ul class="list-content">
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_crud_aside_activity_edit', { 'id': entity.id }) }}" class="btn btn-update btn-mini "></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_crud_aside_activity_delete', { 'id': entity.id } ) }}" class="btn btn-delete btn-mini"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{{ chill_pagination(paginator) }}
|
||||
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_crud_aside_activity_new') }}" class="btn btn-create">
|
||||
{{ 'Create' | trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
@ -3,13 +3,50 @@
|
||||
{% set activeRouteKey = '' %}
|
||||
|
||||
{% block title %}
|
||||
{% include('@ChillMain/CRUD/_view_title.html.twig') %}
|
||||
{% include('@ChillMain/CRUD/_view_title.html.twig') %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% embed '@ChillMain/CRUD/_view_content.html.twig' %}
|
||||
{% block crud_content_header %}
|
||||
<h1>{{ ('crud.' ~ crud_name ~ '.title_view')|trans }}</h1>
|
||||
{% endblock crud_content_header %}
|
||||
{% endembed %}
|
||||
{% endblock %}
|
||||
{% embed '@ChillMain/CRUD/_view_content.html.twig' %}
|
||||
{% block crud_content_header %}
|
||||
<h1>{{ ('crud.' ~ crud_name ~ '.title_view')|trans }}</h1>
|
||||
{% endblock crud_content_header %}
|
||||
{% block crud_content_view_details %}
|
||||
<dl class="chill_view_data">
|
||||
|
||||
<dt class="inline">{{ 'Type'|trans }}</dt>
|
||||
<dd>{{ entity.type|chill_entity_render_box }}</dd>
|
||||
|
||||
<dt class="inline">{{ 'Created by'|trans }}</dt>
|
||||
<dd>{{ entity.createdBy }}</dd>
|
||||
|
||||
<dt class="inline">{{ 'Created for'|trans }}</dt>
|
||||
<dd>{{ entity.agent }}</dd>
|
||||
|
||||
<h2 class="chill-red">{{ 'Activity data'|trans }}</h2>
|
||||
|
||||
<dt class="inline">{{ 'Date'|trans }}</dt>
|
||||
<dd>{{ entity.date|format_date('long') }}</dd>
|
||||
|
||||
<dt class="inline">{{ 'Duration'|trans }}</dt>
|
||||
<dd>{{ entity.duration|date('H:i') }}</dd>
|
||||
|
||||
<dt class="inline">{{ 'Remark'|trans }}</dt>
|
||||
{%- if entity.note is empty -%}
|
||||
<dd>
|
||||
<span class="chill-no-data-statement">{{ 'No comments'|trans }}</span>
|
||||
</dd>
|
||||
{%- else -%}
|
||||
<dd>
|
||||
<section class="chill-entity entity-comment-embeddable">
|
||||
<blockquote class="chill-user-quote">
|
||||
<p>{{ entity.note }}</p>
|
||||
</blockquote>
|
||||
</section>
|
||||
</dd>
|
||||
{%- endif -%}
|
||||
</dl>
|
||||
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends "@ChillActivity/Admin/layout_activity.html.twig" %}
|
||||
{% extends "@ChillAsideActivity/Admin/layout_asideactivity.html.twig" %}
|
||||
|
||||
{% block title %}
|
||||
{% include('@ChillMain/CRUD/_edit_title.html.twig') %}
|
||||
|
@ -1,11 +1,12 @@
|
||||
{% extends "@ChillAsideActivity/Admin/layout_asideactivity.html.twig" %}
|
||||
|
||||
{% block admin_content %}
|
||||
<h1>{{ 'ActivityType list'|trans }}</h1>
|
||||
<h1>{{ 'Aside Activity Type list'|trans }}</h1>
|
||||
|
||||
<table class="records_list table table-bordered border-dark">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'Ordering'|trans }}</th>
|
||||
<th>{{ 'Name'|trans }}</th>
|
||||
<th>{{ 'Active'|trans }}</th>
|
||||
<th>{{ 'Actions'|trans }}</th>
|
||||
@ -14,7 +15,8 @@
|
||||
<tbody>
|
||||
{% for entity in entities %}
|
||||
<tr>
|
||||
<td>{{ entity.title|localize_translatable_string }}</td>
|
||||
<td>{{ entity.ordering }}</td>
|
||||
<td>{{ entity|chill_entity_render_box }}</td>
|
||||
<td style="text-align:center;">
|
||||
{%- if entity.isActive -%}
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends "@ChillActivity/Admin/layout_activity.html.twig" %}
|
||||
{% extends "@ChillAsideActivity/Admin/layout_asideactivity.html.twig" %}
|
||||
|
||||
{% block title %}
|
||||
{% include('@ChillMain/CRUD/_new_title.html.twig') %}
|
||||
|
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\AsideActivityBundle\Templating\Entity;
|
||||
|
||||
use Chill\AsideActivityBundle\Entity\AsideActivityCategory;
|
||||
use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
use Symfony\Component\Templating\EngineInterface;
|
||||
|
||||
final class CategoryRender implements ChillEntityRenderInterface
|
||||
{
|
||||
private TranslatableStringHelper $translatableStringHelper;
|
||||
private EngineInterface $engine;
|
||||
|
||||
public const SEPERATOR_KEY = 'default.separator';
|
||||
public const DEFAULT_ARGS = [
|
||||
self::SEPERATOR_KEY => ' > '
|
||||
];
|
||||
|
||||
public function __construct(TranslatableStringHelper $translatableStringHelper, EngineInterface $engine)
|
||||
{
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
$this->engine = $engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AsideActivityCategory $asideActivityCategory
|
||||
*/
|
||||
public function renderString($asideActivityCategory, array $options): string
|
||||
{
|
||||
$options = array_merge(self::DEFAULT_ARGS, $options);
|
||||
|
||||
$titles[] = $this->translatableStringHelper->localize($asideActivityCategory->getTitle());
|
||||
|
||||
while ($asideActivityCategory->hasParent()) {
|
||||
$asideActivityCategory = $asideActivityCategory->getParent();
|
||||
$titles[] = $this->translatableStringHelper->localize($asideActivityCategory->getTitle());
|
||||
}
|
||||
|
||||
$titles = array_reverse($titles);
|
||||
|
||||
return implode($options[self::SEPERATOR_KEY], $titles);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AsideActivityCategory $asideActivityCategory
|
||||
*/
|
||||
public function supports($asideActivityCategory, array $options): bool
|
||||
{
|
||||
return $asideActivityCategory instanceof AsideActivityCategory;
|
||||
}
|
||||
|
||||
public function buildParents(AsideActivityCategory $asideActivityCategory)
|
||||
{
|
||||
$parents = [];
|
||||
|
||||
while($asideActivityCategory->hasParent()) {
|
||||
$asideActivityCategory = $parents[] = $asideActivityCategory->getParent();
|
||||
}
|
||||
|
||||
return $parents;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AsideActivityCategory $asideActivityCategory
|
||||
*/
|
||||
public function renderBox($asideActivityCategory, array $options): string
|
||||
{
|
||||
$options = array_merge(self::DEFAULT_ARGS, $options);
|
||||
$parents = $this->buildParents($asideActivityCategory);
|
||||
|
||||
return $this->engine->render('@ChillAsideActivity/Entity/asideActivityCategory.html.twig',
|
||||
[
|
||||
'asideActivityCategory' => $asideActivityCategory,
|
||||
'parents' => $parents,
|
||||
'options' => $options
|
||||
]);
|
||||
}
|
||||
}
|
@ -1,5 +1,22 @@
|
||||
services:
|
||||
Chill\AsideActivityBundle\DataFixtures\:
|
||||
resource: './../DataFixtures'
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
Chill\AsideActivityBundle\DataFixtures\:
|
||||
resource: "./../DataFixtures"
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\AsideActivityBundle\Templating\Entity\:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
resource: "../Templating/Entity"
|
||||
tags:
|
||||
- "chill.render_entity"
|
||||
|
||||
Chill\AsideActivityBundle\Repository\:
|
||||
resource: "../Repository"
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\AsideActivityBundle\Controller\:
|
||||
resource: "../Controller"
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
@ -1,5 +1,5 @@
|
||||
services:
|
||||
Chill\AsideActivityBundle\Menu\:
|
||||
resource: './../../Menu'
|
||||
resource: "./../../Menu"
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
@ -7,14 +7,11 @@ namespace Chill\Migrations\AsideActivity;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20210706124644 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
return 'Aside activity category entity created';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
|
@ -7,14 +7,11 @@ namespace Chill\Migrations\AsideActivity;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20210804082249 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
return 'Aside activity entity created';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
@ -59,12 +56,12 @@ final class Version20210804082249 extends AbstractMigration
|
||||
$this->addSql('CREATE INDEX idx_e9fa219165ff1aec ON asideactivity (updatedby_id)');
|
||||
$this->addSql('CREATE INDEX idx_e9fa21913414710b ON asideactivity (agent_id)');
|
||||
$this->addSql('COMMENT ON COLUMN asideactivity.updatedat IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('CREATE TABLE chill_main_address_legacy (id INT DEFAULT NULL, postcode_id INT DEFAULT NULL, street VARCHAR(255) DEFAULT NULL, streetnumber VARCHAR(255) DEFAULT NULL, validfrom DATE DEFAULT NULL, isnoaddress BOOLEAN DEFAULT NULL, customs JSONB DEFAULT NULL, floor VARCHAR(16) DEFAULT NULL, corridor VARCHAR(16) DEFAULT NULL, steps VARCHAR(16) DEFAULT NULL, buildingname VARCHAR(255) DEFAULT NULL, flat VARCHAR(16) DEFAULT NULL, distribution VARCHAR(255) DEFAULT NULL, extra VARCHAR(255) DEFAULT NULL, validto DATE DEFAULT NULL, point VARCHAR(255) DEFAULT NULL, linkedtothirdparty_id INT DEFAULT NULL)');
|
||||
$this->addSql('ALTER TABLE asideactivity ADD CONSTRAINT fk_e9fa2191c54c8c93 FOREIGN KEY (type_id) REFERENCES asideactivitytype (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE asideactivity ADD CONSTRAINT fk_e9fa21913174800f FOREIGN KEY (createdby_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE asideactivity ADD CONSTRAINT fk_e9fa219165ff1aec FOREIGN KEY (updatedby_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE asideactivity ADD CONSTRAINT fk_e9fa21913414710b FOREIGN KEY (agent_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('DROP TABLE chill_asideactivity.AsideActivity');
|
||||
$this->addSql('DROP TABLE chill_asideactivity.AsideActivityCategory');
|
||||
$this->addSql('DROP SCHEMA chill_asideactivity');
|
||||
}
|
||||
}
|
||||
|
@ -7,14 +7,11 @@ namespace Chill\Migrations\AsideActivity;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20210806140343 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
return 'Duration changed to type integer';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
|
@ -7,14 +7,11 @@ namespace Chill\Migrations\AsideActivity;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20210806140710 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
return 'Duration changed back to timestamp';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
|
@ -7,14 +7,11 @@ namespace Chill\Migrations\AsideActivity;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20210810084456 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
return 'createdat, updatedat and date given a type timestamp';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\AsideActivity;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20210922182907 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Parent and children added to aside activity category entity';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_asideactivity.asideactivitycategory ADD parent_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE chill_asideactivity.asideactivitycategory ADD CONSTRAINT FK_7BF90DBE727ACA70 FOREIGN KEY (parent_id) REFERENCES chill_asideactivity.AsideActivityCategory (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('CREATE INDEX IDX_7BF90DBE727ACA70 ON chill_asideactivity.asideactivitycategory (parent_id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_asideactivity.AsideActivityCategory DROP parent');
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\AsideActivity;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* allow to add an ordering to aside activity categories
|
||||
*/
|
||||
final class Version20211004134012 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'allow to add an ordering to aside activity categories';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_asideactivity.asideactivitycategory ADD ordering DOUBLE PRECISION NOT NULL DEFAULT 0.00');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_asideactivity.asideactivitycategory DROP ordering');
|
||||
}
|
||||
}
|
@ -14,11 +14,8 @@ present: présent
|
||||
not present: absent
|
||||
Delete: Supprimer
|
||||
Update: Mettre à jour
|
||||
Update activity: Modifier l'activité
|
||||
Aside activity data: Données de l'activité annexe
|
||||
No reason associated: Aucun sujet
|
||||
There aren't any aside activities.: Aucune activité annexe enregistrée.
|
||||
type_name: type de l'activité
|
||||
Type: Type
|
||||
Invisible: Invisible
|
||||
Optional: Optionnel
|
||||
@ -43,27 +40,115 @@ crud:
|
||||
title_edit: Edition d'une catégorie de type d'activité
|
||||
|
||||
#forms
|
||||
Activity creation: Nouvelle activité annexe
|
||||
Create a new aside activity type: Nouvelle catégorie d'activité annexe
|
||||
Create: Créer
|
||||
Create a new aside activity type: Nouvelle categorie d'activité annexe
|
||||
Back to the list: Retour à la liste
|
||||
Save activity: Sauver l'activité
|
||||
Reset form: Remise à zéro du formulaire
|
||||
Choose the agent for whom this activity is created: Choisissez l'utilisateur pour qui l'activité est créée.
|
||||
Choose the activity category: Choisissez le type d'activité
|
||||
Choose the duration: Choisir la durée
|
||||
Choose a category: Choisir une catégorie
|
||||
Is active: Actif
|
||||
Agent: Utilisateur
|
||||
For agent: Pour l'utilisateur
|
||||
date: Date
|
||||
Duration: Durée
|
||||
Note: Note
|
||||
Choose the agent for whom this activity is created: Choisissez l'agent pour qui l'activité est créée
|
||||
Choose the activity category: Choisir la catégorie
|
||||
|
||||
#Duration
|
||||
minutes: minutes
|
||||
hour: heure
|
||||
hours: heures
|
||||
day: jour
|
||||
days: jours
|
||||
5 minutes: 5 minutes
|
||||
10 minutes: 10 minutes
|
||||
15 minutes: 15 minutes
|
||||
20 minutes: 20 minutes
|
||||
25 minutes: 25 minutes
|
||||
30 minutes: 30 minutes
|
||||
45 minutes: 45 minutes
|
||||
1 hour: 1 heure
|
||||
1 hour 15: 1 heure 15
|
||||
1 hour 30: 1 heure 30
|
||||
1 hour 45: 1 heure 45
|
||||
2 hours: 2 heures
|
||||
2 hours 30: 2 heure 30
|
||||
3 hours: 3 heures
|
||||
3 hours 30: 3 heure 30
|
||||
4 hours: 4 heures
|
||||
4 hours 30: 4 heure 30
|
||||
5 hours: 5 heures
|
||||
5 hours 30: 5 heure 30
|
||||
6 hours: 6 heures
|
||||
6 hours 30: 6 heure 30
|
||||
7 hours: 7 heures
|
||||
7 hours 30: 7 heure 30
|
||||
8 hours: 8 heures
|
||||
8 hours 30: 8 heure 30
|
||||
9 hours: 9 heures
|
||||
9 hours 30: 9 heure 30
|
||||
10 hours: 10 heures
|
||||
1/2 day: 1/2 jour
|
||||
1 day: 1 jour
|
||||
1 1/2 days: 1 1/2 jours
|
||||
2 days: 2 jours
|
||||
2 1/2 days: 2 1/2 jours
|
||||
3 days: 3 jours
|
||||
3 1/2 days: 3 1/2 jours
|
||||
4 days: 4 jours
|
||||
4 1/2 days: 4 1/2 jours
|
||||
5 days: 5 jours
|
||||
5 1/2 days: 5 1/2 jours
|
||||
6 days: 6 jours
|
||||
6 1/2 days: 6 1/2 jours
|
||||
7 days: 7 jours
|
||||
7 1/2 days: 7 1/2 jours
|
||||
8 days: 8 jours
|
||||
8 1/2 days: 8 1/2 jours
|
||||
9 days: 9 jours
|
||||
9 1/2 days: 9 1/2 jours
|
||||
10 days: 10 jours
|
||||
10 1/2 days: 10 1/2 jours
|
||||
11 days: 11 jours
|
||||
11 1/2 days: 11 1/2 jours
|
||||
12 days: 12 jours
|
||||
12 1/2 days: 12 1/2 jours
|
||||
13 days: 13 jours
|
||||
13 1/2 days: 13 1/2 jours
|
||||
14 days: 14 jours
|
||||
14 1/2 days: 14 1/2 jours
|
||||
15 days: 15 jours
|
||||
15 1/2 days: 15 1/2 jours
|
||||
16 days: 16 jours
|
||||
16 1/2 days: 16 1/2 jours
|
||||
17 days: 17 jours
|
||||
17 1/2 days: 17 1/2 jours
|
||||
18 days: 18 jours
|
||||
18 1/2 days: 18 1/2 jours
|
||||
19 days: 19 jours
|
||||
19 1/2 days: 19 1/2 jours
|
||||
20 days: 20 jours
|
||||
20 1/2 days: 20 1/2 jours
|
||||
21 days: 21 jours
|
||||
21 1/2 days: 21 1/2 jours
|
||||
22 days: 22 jours
|
||||
22 1/2 days: 22 1/2 jours
|
||||
23 days: 23 jours
|
||||
23 1/2 days: 23 1/2 jours
|
||||
24 days: 24 jours
|
||||
24 1/2 days: 24 1/2 jours
|
||||
25 days: 25 jours
|
||||
25 1/2 days: 25 1/2 jours
|
||||
26 days: 26 jours
|
||||
26 1/2 days: 26 1/2 jours
|
||||
27 days: 27 jours
|
||||
27 1/2 days: 27 1/2 jours
|
||||
28 days: 28 jours
|
||||
28 1/2 days: 28 1/2 jours
|
||||
29 days: 29 jours
|
||||
29 1/2 days: 29 1/2 jours
|
||||
30 days: 30 jours
|
||||
|
||||
#list
|
||||
My aside activities: Mes activités annexes
|
||||
Date: Date
|
||||
Created by: Créée par
|
||||
|
||||
#Aside activity delete
|
||||
Delete aside activity: Supprimer une activité annexe
|
||||
Are you sure you want to remove the aside activity concerning "%name%" ?: Êtes-vous sûr de vouloir supprimer une activité annexe qui concerne "%name%" ?
|
||||
@ -73,3 +158,4 @@ The activity has been successfully removed.: L'activité a été supprimée.
|
||||
Create an aside activity: "Créer une activité annexe"
|
||||
Aside activity configuration menu: "Menu de configuration des activités annexes"
|
||||
Aside activity configuration: "Configuration des activités annexes"
|
||||
Phonecall: "Appel téléphonique"
|
||||
|
@ -0,0 +1 @@
|
||||
You must not add twice the same category in the parent tree (previous result returned): Il est interdit d'indiquer la même entité dans l'arbre des parents. Le résultat précédent a été rétabli
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Chill\CalendarBundle\Entity;
|
||||
|
||||
use Chill\MainBundle\Entity\Location;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
@ -136,6 +137,13 @@ class Calendar
|
||||
*/
|
||||
private ?bool $sendSMS;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location")
|
||||
* @groups({"read"})
|
||||
*/
|
||||
private ?Location $location = null;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->comment = new CommentEmbeddable();
|
||||
@ -354,7 +362,7 @@ class Calendar
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPersonsAssociated(): array
|
||||
public function getPersonsAssociated(): array
|
||||
{
|
||||
if (null !== $this->accompanyingPeriod) {
|
||||
$personsAssociated = [];
|
||||
@ -363,11 +371,11 @@ class Calendar
|
||||
$personsAssociated[] = $participation->getPerson();
|
||||
}
|
||||
}
|
||||
return $personsAssociated;
|
||||
return $personsAssociated;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
public function getPersonsNotAssociated(): array
|
||||
{
|
||||
if (null !== $this->accompanyingPeriod) {
|
||||
@ -407,4 +415,22 @@ class Calendar
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Location|null
|
||||
*/
|
||||
public function getLocation(): ?Location
|
||||
{
|
||||
return $this->location;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Location|null $location
|
||||
* @return Calendar
|
||||
*/
|
||||
public function setLocation(?Location $location): Calendar
|
||||
{
|
||||
$this->location = $location;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,17 +29,19 @@ class ListenToActivityCreate
|
||||
$activityData = $request->query->get('activityData');
|
||||
if (array_key_exists('calendarId', $activityData)) {
|
||||
$calendarId = $activityData['calendarId'];
|
||||
|
||||
// Attach the activity to the calendar
|
||||
$em = $event->getObjectManager();
|
||||
|
||||
$calendar = $em->getRepository(\Chill\CalendarBundle\Entity\Calendar::class)->find($calendarId);
|
||||
$calendar->setActivity($activity);
|
||||
|
||||
$em->persist($calendar);
|
||||
$em->flush();
|
||||
}
|
||||
}
|
||||
|
||||
// Attach the activity to the calendar
|
||||
$em = $event->getObjectManager();
|
||||
|
||||
$calendar = $em->getRepository(\Chill\CalendarBundle\Entity\Calendar::class)->find($calendarId);
|
||||
$calendar->setActivity($activity);
|
||||
|
||||
$em->persist($calendar);
|
||||
$em->flush();
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Chill\CalendarBundle\Form;
|
||||
|
||||
use Chill\MainBundle\Entity\Location;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
@ -155,7 +156,7 @@ class CalendarType extends AbstractType
|
||||
}
|
||||
))
|
||||
;
|
||||
|
||||
|
||||
$builder->add('calendarRange', HiddenType::class);
|
||||
$builder->get('calendarRange')
|
||||
->addModelTransformer(new CallbackTransformer(
|
||||
@ -178,6 +179,20 @@ class CalendarType extends AbstractType
|
||||
))
|
||||
;
|
||||
|
||||
$builder->add('location', HiddenType::class)
|
||||
->get('location')
|
||||
->addModelTransformer(new CallbackTransformer(
|
||||
function (?Location $location): string {
|
||||
if (null === $location) {
|
||||
return '';
|
||||
}
|
||||
return $location->getId();
|
||||
},
|
||||
function (?string $id): Location {
|
||||
return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]);
|
||||
}
|
||||
))
|
||||
;
|
||||
|
||||
// $builder->add('invites', HiddenType::class);
|
||||
// $builder->get('invites')
|
||||
|
@ -41,7 +41,7 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
|
||||
'routeParameters' => [
|
||||
'accompanying_period_id' => $period->getId(),
|
||||
]])
|
||||
->setExtras(['order' => 41]);
|
||||
->setExtras(['order' => 35]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<concerned-groups></concerned-groups>
|
||||
<location></location>
|
||||
<teleport to="#calendarControls">
|
||||
<calendar-user-selector
|
||||
<calendar-user-selector
|
||||
v-bind:users="users"
|
||||
v-bind:calendarEvents="calendarEvents"
|
||||
v-bind:calendarEvents="calendarEvents"
|
||||
v-bind:updateEventsSource="updateEventsSource"
|
||||
v-bind:showMyCalendar="showMyCalendar"
|
||||
v-bind:toggleMyCalendar="toggleMyCalendar"
|
||||
@ -22,6 +23,7 @@
|
||||
|
||||
<script>
|
||||
import ConcernedGroups from 'ChillActivityAssets/vuejs/Activity/components/ConcernedGroups.vue';
|
||||
import Location from 'ChillActivityAssets/vuejs/Activity/components/Location.vue';
|
||||
import CalendarUserSelector from '../_components/CalendarUserSelector/CalendarUserSelector.vue';
|
||||
import '@fullcalendar/core/vdom'; // solves problem with Vite
|
||||
import frLocale from '@fullcalendar/core/locales/fr';
|
||||
@ -35,6 +37,7 @@ export default {
|
||||
name: "App",
|
||||
components: {
|
||||
ConcernedGroups,
|
||||
Location,
|
||||
CalendarUserSelector,
|
||||
FullCalendar
|
||||
},
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n'
|
||||
import { calendarUserSelectorMessages } from '../_components/CalendarUserSelector/js/i18n';
|
||||
import { activityMessages } from 'ChillActivityAssets/vuejs/Activity/i18n';
|
||||
|
||||
const appMessages = {
|
||||
fr: {
|
||||
@ -17,6 +18,7 @@ const appMessages = {
|
||||
|
||||
Object.assign(appMessages.fr, personMessages.fr);
|
||||
Object.assign(appMessages.fr, calendarUserSelectorMessages.fr);
|
||||
Object.assign(appMessages.fr, activityMessages.fr);
|
||||
|
||||
export {
|
||||
appMessages
|
||||
|
@ -66,6 +66,11 @@ const store = createStore({
|
||||
setEvents(state, payload) {
|
||||
console.log(payload)
|
||||
state.currentEvent = {start: payload.start, end: payload.end}
|
||||
},
|
||||
// Location
|
||||
updateLocation(state, value) {
|
||||
console.log('### mutation: updateLocation', value);
|
||||
state.activity.location = value;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
@ -130,6 +135,14 @@ const store = createStore({
|
||||
commit('setEvents', payload);
|
||||
},
|
||||
|
||||
// Location
|
||||
updateLocation({ commit }, value) {
|
||||
console.log('### action: updateLocation', value);
|
||||
let hiddenLocation = document.getElementById("chill_calendarbundle_calendar_location");
|
||||
hiddenLocation.value = value.id;
|
||||
commit('updateLocation', value);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -38,7 +38,10 @@
|
||||
{{ form_row(form.calendarRange) }}
|
||||
{% endif %}
|
||||
|
||||
.. location
|
||||
{%- if form.location is defined -%}
|
||||
{{ form_row(form.location) }}
|
||||
<div id="location"></div>
|
||||
{% endif %}
|
||||
|
||||
{%- if form.comment is defined -%}
|
||||
{{ form_row(form.comment) }}
|
||||
@ -56,7 +59,7 @@
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a
|
||||
<a
|
||||
class="btn btn-cancel"
|
||||
{%- if context == 'user' -%}
|
||||
href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'user_id': user.id } )}}"
|
||||
|
@ -6,14 +6,10 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="calendar-edit">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10 col-xxl">
|
||||
|
||||
<div id="calendar"></div> {# <=== vue component #}
|
||||
{% include 'ChillCalendarBundle:Calendar:edit.html.twig' with {'context': 'accompanyingCourse'} %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="calendar"></div> {# <=== vue component #}
|
||||
{% include 'ChillCalendarBundle:Calendar:edit.html.twig' with {'context': 'accompanyingCourse'} %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@ -21,7 +17,7 @@
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ form.vars.form.vars.name }}"]',
|
||||
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ form.vars.form.vars.name }}"]',
|
||||
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
|
||||
});
|
||||
window.entity = {{ entity_json|json_encode|raw }};
|
||||
@ -40,4 +36,4 @@
|
||||
|
||||
{% block block_post_menu %}
|
||||
<div id="calendarControls"></div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
@ -4,14 +4,10 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="calendar-edit">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10 col-xxl">
|
||||
|
||||
<div id="calendar"></div> {# <=== vue component #}
|
||||
{% include 'ChillCalendarBundle:Calendar:edit.html.twig' with {'context': 'user'} %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="calendar"></div> {# <=== vue component #}
|
||||
{% include 'ChillCalendarBundle:Calendar:edit.html.twig' with {'context': 'user'} %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@ -19,7 +15,7 @@
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ form.vars.form.vars.name }}"]',
|
||||
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ form.vars.form.vars.name }}"]',
|
||||
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
|
||||
});
|
||||
window.entity = {{ entity_json|json_encode|raw }};
|
||||
|
@ -34,7 +34,10 @@
|
||||
{{ form_row(form.endDate) }}
|
||||
{% endif %}
|
||||
|
||||
.. location
|
||||
{%- if form.location is defined -%}
|
||||
{{ form_row(form.location) }}
|
||||
<div id="location"></div>
|
||||
{% endif %}
|
||||
|
||||
{%- if form.comment is defined -%}
|
||||
{{ form_row(form.comment) }}
|
||||
@ -48,9 +51,9 @@
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a
|
||||
<a
|
||||
class="btn btn-cancel"
|
||||
{%- if context == 'person' -%}
|
||||
{%- if context == 'person' -%}
|
||||
href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'person_id': person.id } )}}"
|
||||
{%- else -%}
|
||||
href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'accompanying_period_id': accompanyingCourse.id } )}}"
|
||||
|
@ -6,25 +6,21 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="calendar-new">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10 col-xxl">
|
||||
|
||||
<div id="calendar"></div> {# <=== vue component #}
|
||||
{% include 'ChillCalendarBundle:Calendar:new.html.twig' with {'context': 'accompanyingCourse'} %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="calendar"></div> {# <=== vue component #}
|
||||
{% include 'ChillCalendarBundle:Calendar:new.html.twig' with {'context': 'accompanyingCourse'} %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]',
|
||||
chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]',
|
||||
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
|
||||
});
|
||||
window.entity = {{ entity_json|json_encode|raw }};
|
||||
window.entity = {{ entity_json|json_encode|raw }};
|
||||
</script>
|
||||
{{ encore_entry_script_tags('vue_calendar') }}
|
||||
{% endblock %}
|
||||
|
@ -36,6 +36,20 @@
|
||||
<dt class="inline">{{ 'status'|trans }}</dt>
|
||||
<dd>{{ entity.status }}</dd>
|
||||
|
||||
|
||||
<dt class="inline">{{ 'calendar location'|trans }}</dt>
|
||||
<dd>
|
||||
{% if entity.location is not null %}
|
||||
<p>
|
||||
<span>{{ entity.location.locationType.title|localize_translatable_string }}</span>
|
||||
{{ entity.location.name }}
|
||||
</p>
|
||||
{{ entity.location.address|chill_entity_render_box }}
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
{% if not entity.comment.isEmpty %}
|
||||
<dt class="inline">{{ 'calendar comment'|trans }}</dt>
|
||||
<dd>{{ entity.comment|chill_entity_render_box }}</dd>
|
||||
|
@ -6,10 +6,8 @@
|
||||
|
||||
{% block content -%}
|
||||
<div class="calendar-show">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10 col-xxl">
|
||||
{% include 'ChillCalendarBundle:Calendar:show.html.twig' with {'context': 'accompanyingCourse'} %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'ChillCalendarBundle:Calendar:show.html.twig' with {'context': 'accompanyingCourse'} %}
|
||||
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
@ -4,10 +4,8 @@
|
||||
|
||||
{% block content -%}
|
||||
<div class="calendar-show">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10 col-xxl">
|
||||
{% include 'ChillCalendarBundle:Calendar:show.html.twig' with {'context': 'user'} %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'ChillCalendarBundle:Calendar:show.html.twig' with {'context': 'user'} %}
|
||||
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
@ -14,6 +14,7 @@ start date: début du rendez-vous
|
||||
end date: fin du rendez-vous
|
||||
cancel reason: motif d'annulation
|
||||
status: Statut du rendez-vous
|
||||
calendar location: Localistion du rendez-vous
|
||||
calendar comment: Remarque sur le rendez-vous
|
||||
sendSMS: Envoi d'un SMS
|
||||
Send s m s: Envoi d'un SMS ?
|
||||
|
@ -1,16 +1,16 @@
|
||||
{#
|
||||
* Copyright (C) 2014, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
|
||||
*
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
#}
|
||||
@ -33,7 +33,7 @@
|
||||
{# CFChoice : render the different elements in a choice list #}
|
||||
{% block cf_choices_row %}
|
||||
<h3>{{ 'Choices'|trans }}</h3>
|
||||
|
||||
|
||||
<div id="{{ form.vars.id }}" data-prototype="{{- form_row(form.vars.prototype.children.name)
|
||||
~ form_row(form.vars.prototype.children.active)
|
||||
~ form_row(form.vars.prototype.children.slug) -}}">
|
||||
@ -47,8 +47,8 @@
|
||||
{% endfor %}
|
||||
</tbody></table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
{# we use javascrit to add an additional element. All functions are personnalized with the id ( = form.vars.id) #}
|
||||
<script type="text/javascript">
|
||||
function addElementInDiv(div_id) {
|
||||
@ -109,3 +109,13 @@
|
||||
|
||||
{# The choice_with_other_widget widget is defined in the main bundle #}
|
||||
|
||||
{% block pick_address_row %}
|
||||
{{ form_label(form) }}
|
||||
{{ form_errors(form) }}
|
||||
{{ form_widget(form) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block pick_address_widget %}
|
||||
{{ form_widget(form) }}
|
||||
<div data-input-address-container="{{ form.vars.uniqid }}"></div>
|
||||
{% endblock %}
|
||||
|
@ -138,7 +138,7 @@ class DocGeneratorTemplateController extends AbstractController
|
||||
$em->flush();
|
||||
|
||||
return $this->redirectToRoute('chill_wopi_file_edit', [
|
||||
'fileId' => $genDocName,
|
||||
'fileId' => $storedObject->getUuid(),
|
||||
'returnPath' => $request->query->get('returnPath', "/")
|
||||
]);
|
||||
}
|
||||
|
@ -7,7 +7,10 @@ namespace Chill\DocStoreBundle\Entity;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use ChampsLibres\AsyncUploaderBundle\Model\AsyncFileInterface;
|
||||
use ChampsLibres\AsyncUploaderBundle\Validator\Constraints\AsyncFileExists;
|
||||
use ChampsLibres\WopiLib\Contract\Entity\Document;
|
||||
use DateTimeInterface;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
|
||||
/**
|
||||
@ -19,7 +22,7 @@ use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
* message="The file is not stored properly"
|
||||
* )
|
||||
*/
|
||||
class StoredObject implements AsyncFileInterface
|
||||
class StoredObject implements AsyncFileInterface, Document
|
||||
{
|
||||
/**
|
||||
* @ORM\Id()
|
||||
@ -47,6 +50,12 @@ class StoredObject implements AsyncFileInterface
|
||||
*/
|
||||
private array $iv = [];
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="uuid", unique=true)
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private UuidInterface $uuid;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime", name="creation_date")
|
||||
* @Serializer\Groups({"read"})
|
||||
@ -68,6 +77,7 @@ class StoredObject implements AsyncFileInterface
|
||||
public function __construct()
|
||||
{
|
||||
$this->creationDate = new \DateTime();
|
||||
$this->uuid = Uuid::uuid4();
|
||||
}
|
||||
|
||||
public function getId()
|
||||
@ -155,5 +165,13 @@ class StoredObject implements AsyncFileInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUuid(): UuidInterface
|
||||
{
|
||||
return $this->uuid;
|
||||
}
|
||||
|
||||
public function getWopiDocId(): string
|
||||
{
|
||||
return (string) $this->uuid;
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,12 @@
|
||||
"description": "A Chill bundle to store documents",
|
||||
"type": "symfony-bundle",
|
||||
"autoload": {
|
||||
"psr-4": { "Chill\\DocStoreBundle\\" : "" }
|
||||
"psr-4": {
|
||||
"Chill\\DocStoreBundle\\": ""
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"symfony/mime": "^4 || ^5"
|
||||
},
|
||||
"license": "AGPL-3.0"
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\DocStore;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20210928182542 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Create UUID column on StoredObject table.';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"');
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object ADD uuid UUID DEFAULT NULL');
|
||||
$this->addSql('UPDATE chill_doc.stored_object SET uuid=uuid_generate_v4()');
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object ALTER uuid SET NOT NULL');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_49604E36D17F50A6 ON chill_doc.stored_object (uuid)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_doc.stored_object DROP uuid');
|
||||
}
|
||||
}
|
@ -20,6 +20,8 @@
|
||||
|
||||
namespace Chill\MainBundle\CRUD\Controller;
|
||||
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@ -209,22 +211,24 @@ class CRUDController extends AbstractController
|
||||
* This method:
|
||||
*
|
||||
* 1. Launch `onPreIndex`
|
||||
* x. check acl. If it does return a response instance, return it
|
||||
* x. launch `onPostCheckACL`. If it does return a response instance, return it
|
||||
* 1. count the items, using `countEntities`
|
||||
* 2. build a paginator element from the the number of entities ;
|
||||
* 3. Launch `onPreIndexQuery`. If it does return a response instance, return it
|
||||
* 3. build a query, using `queryEntities`
|
||||
* x. fetch the results, using `getQueryResult`
|
||||
* x. Launch `onPostIndexFetchQuery`. If it does return a response instance, return it
|
||||
* 4. create default parameters:
|
||||
* 2. check acl. If it does return a response instance, return it
|
||||
* 3. launch `onPostCheckACL`. If it does return a response instance, return it
|
||||
* 4. count the items, using `countEntities`
|
||||
* 5. build a paginator element from the the number of entities ;
|
||||
* 6. Launch `onPreIndexQuery`. If it does return a response instance, return it
|
||||
* 7. fetch the results, using `getQueryResult`
|
||||
*
|
||||
* Internally, this build a query, using `queryEntities`
|
||||
*
|
||||
* 8. Launch `onPostIndexFetchQuery`. If it does return a response instance, return it
|
||||
* 9. create default parameters:
|
||||
*
|
||||
* The default parameters are:
|
||||
*
|
||||
* * entities: the list en entities ;
|
||||
* * crud_name: the name of the crud ;
|
||||
* * paginator: a paginator element ;
|
||||
* 5. Launch rendering, the parameter is fetch using `getTemplateFor`
|
||||
* 10. Launch rendering, the parameter is fetch using `getTemplateFor`
|
||||
* The parameters may be personnalized using `generateTemplateParameter`.
|
||||
*
|
||||
* @param string $action
|
||||
@ -249,7 +253,8 @@ class CRUDController extends AbstractController
|
||||
return $response;
|
||||
}
|
||||
|
||||
$totalItems = $this->countEntities($action, $request);
|
||||
$filterOrder = $this->buildFilterOrderHelper($action, $request);
|
||||
$totalItems = $this->countEntities($action, $request, $filterOrder);
|
||||
$paginator = $this->getPaginatorFactory()->create($totalItems);
|
||||
|
||||
$response = $this->onPreIndexBuildQuery($action, $request, $totalItems,
|
||||
@ -259,16 +264,7 @@ class CRUDController extends AbstractController
|
||||
return $response;
|
||||
}
|
||||
|
||||
$query = $this->queryEntities($action, $request, $paginator);
|
||||
|
||||
$response = $this->onPostIndexBuildQuery($action, $request, $totalItems,
|
||||
$paginator, $query);
|
||||
|
||||
if ($response instanceof Response) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$entities = $this->getQueryResult($action, $request, $totalItems, $paginator, $query);
|
||||
$entities = $this->getQueryResult($action, $request, $totalItems, $paginator, $filterOrder);
|
||||
|
||||
$response = $this->onPostIndexFetchQuery($action, $request, $totalItems,
|
||||
$paginator, $entities);
|
||||
@ -280,7 +276,8 @@ class CRUDController extends AbstractController
|
||||
$defaultTemplateParameters = [
|
||||
'entities' => $entities,
|
||||
'crud_name' => $this->getCrudName(),
|
||||
'paginator' => $paginator
|
||||
'paginator' => $paginator,
|
||||
'filter_order' => $filterOrder
|
||||
];
|
||||
|
||||
return $this->render(
|
||||
@ -289,6 +286,11 @@ class CRUDController extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $action
|
||||
* @param Request $request
|
||||
@ -361,9 +363,9 @@ class CRUDController extends AbstractController
|
||||
* @param PaginatorInterface $paginator
|
||||
* @return type
|
||||
*/
|
||||
protected function queryEntities(string $action, Request $request, PaginatorInterface $paginator)
|
||||
protected function queryEntities(string $action, Request $request, PaginatorInterface $paginator, ?FilterOrderHelper $filterOrder = null)
|
||||
{
|
||||
$query = $this->buildQueryEntities($action, $request)
|
||||
$query = $this->buildQueryEntities($action, $request, $filterOrder)
|
||||
->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber())
|
||||
->setMaxResults($paginator->getItemsPerPage());
|
||||
|
||||
@ -393,11 +395,13 @@ class CRUDController extends AbstractController
|
||||
* @param Request $request
|
||||
* @param int $totalItems
|
||||
* @param PaginatorInterface $paginator
|
||||
* @param mixed $query
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getQueryResult(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $query)
|
||||
protected function getQueryResult(string $action, Request $request, int $totalItems, PaginatorInterface $paginator,
|
||||
?FilterOrderHelper $filterOrder = null)
|
||||
{
|
||||
$query = $this->queryEntities($action, $request, $paginator, $filterOrder);
|
||||
|
||||
return $query->getQuery()->getResult();
|
||||
}
|
||||
|
||||
@ -408,9 +412,9 @@ class CRUDController extends AbstractController
|
||||
* @param Request $request
|
||||
* @return int
|
||||
*/
|
||||
protected function countEntities(string $action, Request $request): int
|
||||
protected function countEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null): int
|
||||
{
|
||||
return $this->buildQueryEntities($action, $request)
|
||||
return $this->buildQueryEntities($action, $request, $filterOrder)
|
||||
->select('COUNT(e)')
|
||||
->getQuery()
|
||||
->getSingleScalarResult()
|
||||
@ -1183,6 +1187,11 @@ class CRUDController extends AbstractController
|
||||
return $this->get(Resolver::class);
|
||||
}
|
||||
|
||||
protected function getFilterOrderHelperFactory(): FilterOrderHelperFactoryInterface
|
||||
{
|
||||
return $this->get(FilterOrderHelperFactoryInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
@ -1197,6 +1206,7 @@ class CRUDController extends AbstractController
|
||||
EventDispatcherInterface::class => EventDispatcherInterface::class,
|
||||
Resolver::class => Resolver::class,
|
||||
SerializerInterface::class => SerializerInterface::class,
|
||||
FilterOrderHelperFactoryInterface::class => FilterOrderHelperFactoryInterface::class,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
namespace Chill\MainBundle\Command;
|
||||
|
||||
use Chill\MainBundle\Doctrine\Model\Point;
|
||||
use Chill\MainBundle\Entity\Country;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
@ -69,6 +71,9 @@ class LoadPostalCodesCommand extends Command
|
||||
. "using the postal code and name. \n"
|
||||
. "The CSV file must have the following columns: "
|
||||
. "postal code, label, country code."
|
||||
. "Optionally, the csv file can have the following "
|
||||
. "columns after the country code: reference code, latitude, longitude, source. "
|
||||
. "The latitude and longitude columns are supposed to be in WGS84 and expressed in decimal degrees. "
|
||||
. "The CSV file should not have any header row.")
|
||||
->addArgument('csv_file', InputArgument::REQUIRED, "the path to "
|
||||
. "the csv file. See the help for specifications.")
|
||||
@ -163,7 +168,7 @@ class LoadPostalCodesCommand extends Command
|
||||
}
|
||||
$em = $this->entityManager;
|
||||
$country = $em
|
||||
->getRepository('ChillMainBundle:Country')
|
||||
->getRepository(Country::class)
|
||||
->findOneBy(array('countryCode' => $row[2]));
|
||||
|
||||
if ($country === NULL) {
|
||||
@ -173,7 +178,7 @@ class LoadPostalCodesCommand extends Command
|
||||
|
||||
// try to find an existing postal code
|
||||
$existingPC = $em
|
||||
->getRepository('ChillMainBundle:PostalCode')
|
||||
->getRepository(PostalCode::class)
|
||||
->findBy(array('code' => $row[0], 'name' => $row[1]));
|
||||
|
||||
if (count($existingPC) > 0) {
|
||||
@ -187,6 +192,18 @@ class LoadPostalCodesCommand extends Command
|
||||
->setCountry($country)
|
||||
;
|
||||
|
||||
if (NULL != $row[3]){
|
||||
$postalCode->setRefPostalCodeId($row[3]);
|
||||
}
|
||||
|
||||
if (NULL != $row[4] & NULL != $row[5]){
|
||||
$postalCode->setCenter(Point::fromLonLat((float) $row[5], (float) $row[4]));
|
||||
}
|
||||
|
||||
if (NULL != $row[6]){
|
||||
$postalCode->setPostalCodeSource($row[6]);
|
||||
}
|
||||
|
||||
$errors = $this->validator->validate($postalCode);
|
||||
|
||||
if ($errors->count() == 0) {
|
||||
|
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||
use Chill\MainBundle\Entity\Address;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||
|
||||
class AddressApiController extends ApiController
|
||||
{
|
||||
/**
|
||||
* Duplicate an existing address
|
||||
*
|
||||
* @Route("/api/1.0/main/address/{id}/duplicate.json", name="chill_api_main_address_duplicate",
|
||||
* methods={"POST"})
|
||||
*
|
||||
* @param Address $address
|
||||
*/
|
||||
public function duplicate(Address $address): JsonResponse
|
||||
{
|
||||
$this->denyAccessUnlessGranted('ROLE_USER');
|
||||
$new = Address::createFromAddress($address);
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
|
||||
$em->persist($new);
|
||||
$em->flush();
|
||||
|
||||
return $this->json($new, Response::HTTP_OK, [], [
|
||||
AbstractNormalizer::GROUPS => ['read']
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
@ -20,8 +21,17 @@ class AddressReferenceAPIController extends ApiController
|
||||
|
||||
$qb->where('e.postcode = :postal_code')
|
||||
->setParameter('postal_code', $request->query->get('postal_code'));
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format)
|
||||
{
|
||||
$query->addOrderBy('e.street', 'ASC');
|
||||
$query->addOrderBy('e.streetNumber', 'ASC');
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Class LocationApiController
|
||||
*
|
||||
* @package Chill\MainBundle\Controller
|
||||
* @author Champs Libres
|
||||
*/
|
||||
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->expr()->eq('e.availableForUsers', "'TRUE'")
|
||||
))
|
||||
->setParameters([
|
||||
'user' => $this->getUser(),
|
||||
'dateBefore' => (new \DateTime())->sub(new \DateInterval('P6M'))
|
||||
]);
|
||||
}
|
||||
}
|
41
src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCivility.php
Normal file
41
src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCivility.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\DataFixtures\ORM;
|
||||
|
||||
use Chill\MainBundle\Entity\Civility;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
|
||||
class LoadCivility extends Fixture implements FixtureGroupInterface
|
||||
{
|
||||
public static function getGroups(): array
|
||||
{
|
||||
return ['civilities'];
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
$civilities = [
|
||||
['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" ], 'abbrev' => ['fr' => 'Me']],
|
||||
];
|
||||
|
||||
foreach ( $civilities as $val) {
|
||||
$civility = (new Civility())
|
||||
->setName($val['name'])
|
||||
->setAbbreviation($val['abbrev'] ?? [])
|
||||
->setActive(true);
|
||||
$manager->persist($civility);
|
||||
}
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
}
|
@ -22,6 +22,8 @@
|
||||
|
||||
namespace Chill\MainBundle\DataFixtures\ORM;
|
||||
|
||||
use Chill\MainBundle\Doctrine\Model\Point;
|
||||
use Chill\MainBundle\Entity\Country;
|
||||
use Doctrine\Common\DataFixtures\AbstractFixture;
|
||||
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
@ -40,21 +42,41 @@ class LoadPostalCodes extends AbstractFixture implements OrderedFixtureInterface
|
||||
return 50;
|
||||
}
|
||||
|
||||
public static $refs = array();
|
||||
public static $refs = [];
|
||||
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
$lines = str_getcsv(self::$codes, "\n");
|
||||
$belgium = $manager->getRepository('ChillMainBundle:Country')
|
||||
->findOneBy(array('countryCode' => 'BE'));
|
||||
echo "loading postal codes... \n";
|
||||
$this->loadPostalCodeCSV($manager, self::$postalCodeBelgium, 'BE');
|
||||
$this->loadPostalCodeCSV($manager, self::$postalCodeFrance, 'FR');
|
||||
}
|
||||
|
||||
private function loadPostalCodeCSV(ObjectManager $manager, string $csv, string $countryCode) {
|
||||
|
||||
$lines = str_getcsv($csv, "\n");
|
||||
$country = $manager->getRepository(Country::class)
|
||||
->findOneBy(['countryCode' => $countryCode]);
|
||||
|
||||
foreach($lines as $line) {
|
||||
$code = str_getcsv($line);
|
||||
$c = new PostalCode();
|
||||
$c->setCountry($belgium)
|
||||
$c->setCountry($country)
|
||||
->setCode($code[0])
|
||||
->setName(\ucwords(\strtolower($code[2])))
|
||||
->setName(\ucwords(\strtolower($code[1])))
|
||||
;
|
||||
|
||||
if (NULL != $code[3]){
|
||||
$c->setRefPostalCodeId($code[3]);
|
||||
}
|
||||
|
||||
if (NULL != $code[4] & NULL != $code[5]){
|
||||
$c->setCenter(Point::fromLonLat((float) $code[5], (float) $code[4]));
|
||||
}
|
||||
|
||||
if (NULL != $code[6]){
|
||||
$c->setPostalCodeSource($code[6]);
|
||||
}
|
||||
|
||||
$manager->persist($c);
|
||||
$ref = 'postal_code_'.$code[0];
|
||||
|
||||
@ -66,40 +88,305 @@ class LoadPostalCodes extends AbstractFixture implements OrderedFixtureInterface
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
private static $codes = <<<EOF
|
||||
1000,BRUXELLES,BRUXELLES,Bruxelles
|
||||
1020,Laeken,BRUXELLES,Bruxelles
|
||||
1030,SCHAERBEEK,SCHAERBEEK,Bruxelles
|
||||
1040,ETTERBEEK,ETTERBEEK,Bruxelles
|
||||
1050,IXELLES,IXELLES,Bruxelles
|
||||
1060,SAINT-GILLES,SAINT-GILLES,Bruxelles
|
||||
1070,ANDERLECHT,ANDERLECHT,Bruxelles
|
||||
1080,MOLENBEEK-SAINT-JEAN,MOLENBEEK-SAINT-JEAN,Bruxelles
|
||||
1081,KOEKELBERG,KOEKELBERG,Bruxelles
|
||||
1082,BERCHEM-SAINTE-AGATHE,BERCHEM-SAINTE-AGATHE,Bruxelles
|
||||
1083,GANSHOREN,GANSHOREN,Bruxelles
|
||||
1090,JETTE,JETTE,Bruxelles
|
||||
1120,Neder-Over-Heembeek,BRUXELLES,Bruxelles
|
||||
1130,Haren,BRUXELLES,Bruxelles
|
||||
1140,EVERE,EVERE,Bruxelles
|
||||
1150,WOLUWE-SAINT-PIERRE,WOLUWE-SAINT-PIERRE,Bruxelles
|
||||
1160,AUDERGHEM,AUDERGHEM,Bruxelles
|
||||
1170,WATERMAEL-BOITSFORT,WATERMAEL-BOITSFORT,Bruxelles
|
||||
1180,UCCLE,UCCLE,Bruxelles
|
||||
1190,FOREST,FOREST,Bruxelles
|
||||
1200,WOLUWE-SAINT-LAMBERT,WOLUWE-SAINT-LAMBERT,Bruxelles
|
||||
1210,SAINT-JOSSE-TEN-NOODE,SAINT-JOSSE-TEN-NOODE,Bruxelles
|
||||
1300,Limal,WAVRE,Brabant-Wallon
|
||||
1300,WAVRE,WAVRE,Brabant-Wallon
|
||||
1301,Bierges,WAVRE,Brabant-Wallon
|
||||
1310,LA HULPE,LA HULPE,Brabant-Wallon
|
||||
1315,Glimes,INCOURT,Brabant-Wallon
|
||||
1315,INCOURT,INCOURT,Brabant-Wallon
|
||||
1315,Opprebais,INCOURT,Brabant-Wallon
|
||||
1315,Piètrebais,INCOURT,Brabant-Wallon
|
||||
1315,Roux-Miroir,INCOURT,Brabant-Wallon
|
||||
1320,BEAUVECHAIN,BEAUVECHAIN,Brabant-Wallon
|
||||
|
||||
private static $postalCodeBelgium = <<<EOF
|
||||
1000,BRUXELLES,BE
|
||||
1020,BRUXELLES,BE
|
||||
1030,SCHAERBEEK,BE
|
||||
1040,ETTERBEEK,BE
|
||||
1050,IXELLES,BE
|
||||
1060,SAINT-GILLES,BE
|
||||
1070,ANDERLECHT,BE
|
||||
1080,MOLENBEEK-SAINT-JEAN,BE
|
||||
1081,KOEKELBERG,BE
|
||||
1082,BERCHEM-SAINTE-AGATHE,BE
|
||||
1083,GANSHOREN,BE
|
||||
1090,JETTE,BE
|
||||
1120,BRUXELLES,BE
|
||||
1130,BRUXELLES,BE
|
||||
1140,EVERE,BE
|
||||
1150,WOLUWE-SAINT-PIERRE,BE
|
||||
1160,AUDERGHEM,BE
|
||||
1170,WATERMAEL-BOITSFORT,BE
|
||||
1180,UCCLE,BE
|
||||
1190,FOREST,BE
|
||||
1200,WOLUWE-SAINT-LAMBERT,BE
|
||||
1210,SAINT-JOSSE-TEN-NOODE,BE
|
||||
1300,WAVRE,BE
|
||||
1300,WAVRE,BE
|
||||
1301,WAVRE,BE
|
||||
1310,LA HULPE,BE
|
||||
1315,INCOURT,BE
|
||||
1315,INCOURT,BE
|
||||
1315,INCOURT,BE
|
||||
1315,INCOURT,BE
|
||||
1315,INCOURT,BE
|
||||
1320,BEAUVECHAIN,BE
|
||||
EOF;
|
||||
|
||||
private static $postalCodeFrance = <<<EOF
|
||||
85000,LA ROCHE SUR YON,FR,85191,46.6675261644,-1.4077954093,INSEE
|
||||
85000,MOUILLERON LE CAPTIF,FR,85155,46.7104764993,-1.46129661418,INSEE
|
||||
85100,LES SABLES D OLONNE,FR,85194,46.5007612799,-1.79255128677,INSEE
|
||||
85110,STE CECILE,FR,85202,46.7309688326,-1.12107316048,INSEE
|
||||
85110,LA JAUDONNIERE,FR,85115,46.6488761831,-0.962477574588,INSEE
|
||||
85110,ST GERMAIN DE PRINCAY,FR,85220,46.7356314659,-1.04299885081,INSEE
|
||||
85110,MONSIREIGNE,FR,85145,46.7383480028,-0.931953130855,INSEE
|
||||
85110,ST VINCENT STERLANGES,FR,85276,46.7397220689,-1.08371759277,INSEE
|
||||
85110,SIGOURNAIS,FR,85282,46.7140097406,-0.98747730882,INSEE
|
||||
85110,CHANTONNAY,FR,85051,46.6690167793,-1.04372588019,INSEE
|
||||
85110,ST PROUANT,FR,85266,46.7502017421,-0.974504061491,INSEE
|
||||
85120,LA CHAPELLE AUX LYS,FR,85053,46.6221916887,-0.642706103195,INSEE
|
||||
85120,VOUVANT,FR,85305,46.5626835135,-0.764380170382,INSEE
|
||||
85120,ANTIGNY,FR,85005,46.6191770822,-0.767030058653,INSEE
|
||||
85120,ST MAURICE DES NOUES,FR,85251,46.5955876221,-0.724327725888,INSEE
|
||||
85120,LOGE FOUGEREUSE,FR,85125,46.6180854641,-0.6899276733,INSEE
|
||||
85120,LA TARDIERE,FR,85289,46.663737434,-0.727562430214,INSEE
|
||||
85120,LA CHATAIGNERAIE,FR,85059,46.6416143401,-0.739561966419,INSEE
|
||||
85120,ST HILAIRE DE VOUST,FR,85229,46.5914988312,-0.651486245674,INSEE
|
||||
85120,BREUIL BARRET,FR,85037,46.6503266235,-0.671948654426,INSEE
|
||||
85120,ST PIERRE DU CHEMIN,FR,85264,46.6957771744,-0.701777715154,INSEE
|
||||
85130,LA GAUBRETIERE,FR,85097,46.9345007303,-1.05578200702,INSEE
|
||||
85130,ST MARTIN DES TILLEULS,FR,85247,46.9711539531,-1.06282621567,INSEE
|
||||
85130,TIFFAUGES,FR,85293,47.0020573556,-1.09858009203,INSEE
|
||||
85130,CHANVERRIE,FR,85302,46.9634774521,-0.985340006089,INSEE
|
||||
85130,BAZOGES EN PAILLERS,FR,85013,46.9213757643,-1.14415666313,INSEE
|
||||
85130,LES LANDES GENUSSON,FR,85119,46.9663828627,-1.12900644447,INSEE
|
||||
85130,ST AUBIN DES ORMEAUX,FR,85198,46.9958175597,-1.04216568722,INSEE
|
||||
85140,ESSARTS EN BOCAGE,FR,85084,46.7806739038,-1.22925967851,INSEE
|
||||
85140,LA MERLATIERE,FR,85142,46.7557703112,-1.29794577,INSEE
|
||||
85140,ST MARTIN DES NOYERS,FR,85246,46.7239461187,-1.20379080965,INSEE
|
||||
85140,CHAUCHE,FR,85064,46.8282791899,-1.27090860656,INSEE
|
||||
85150,ST MATHURIN,FR,85250,46.5686332748,-1.70787622288,INSEE
|
||||
85150,MARTINET,FR,85138,46.6620680463,-1.6772013304,INSEE
|
||||
85150,STE FLAIVE DES LOUPS,FR,85211,46.611019489,-1.58031627863,INSEE
|
||||
85150,STE FOY,FR,85214,46.5327600427,-1.69243074733,INSEE
|
||||
85150,ST JULIEN DES LANDES,FR,85236,46.6395925444,-1.7159724914,INSEE
|
||||
85150,ST GEORGES DE POINTINDOUX,FR,85218,46.6423470977,-1.62881823574,INSEE
|
||||
85150,LE GIROUARD,FR,85099,46.5726064909,-1.58872487716,INSEE
|
||||
85150,LANDERONDE,FR,85118,46.6549237031,-1.57351777893,INSEE
|
||||
85150,LES ACHARDS,FR,85152,46.6163645636,-1.65038156849,INSEE
|
||||
85150,VAIRE,FR,85298,46.6055340621,-1.74863672042,INSEE
|
||||
85160,ST JEAN DE MONTS,FR,85234,46.8021968737,-2.04839789308,INSEE
|
||||
85170,BELLEVIGNY,FR,85019,46.7756383534,-1.43313700054,INSEE
|
||||
85170,LE POIRE SUR VIE,FR,85178,46.769919754,-1.50488626452,INSEE
|
||||
85170,BEAUFOU,FR,85015,46.8191122027,-1.52479250801,INSEE
|
||||
85170,DOMPIERRE SUR YON,FR,85081,46.7599858068,-1.37275519417,INSEE
|
||||
85170,LES LUCS SUR BOULOGNE,FR,85129,46.8527299002,-1.48398928084,INSEE
|
||||
85170,ST DENIS LA CHEVASSE,FR,85208,46.8325959261,-1.3830312677,INSEE
|
||||
85180,LES SABLES D OLONNE,FR,85194,46.5007612799,-1.79255128677,INSEE
|
||||
85190,AIZENAY,FR,85003,46.7384516809,-1.62702889721,INSEE
|
||||
85190,VENANSAULT,FR,85300,46.685677363,-1.54112129191,INSEE
|
||||
85190,MACHE,FR,85130,46.771364944,-1.69526445062,INSEE
|
||||
85190,BEAULIEU SOUS LA ROCHE,FR,85016,46.6872087211,-1.62355064963,INSEE
|
||||
85190,LA GENETOUZE,FR,85098,46.7244524541,-1.50410719693,INSEE
|
||||
85200,PISSOTTE,FR,85176,46.5010870694,-0.808352236192,INSEE
|
||||
85200,AUCHAY SUR VENDEE,FR,85009,46.4474386161,-0.876574265149,INSEE
|
||||
85200,FONTENAY LE COMTE,FR,85092,46.4563186117,-0.793449510859,INSEE
|
||||
85200,MERVENT,FR,85143,46.5325327351,-0.748519927998,INSEE
|
||||
85200,DOIX LES FONTAINES,FR,85080,46.3849492327,-0.806840287485,INSEE
|
||||
85200,LONGEVES,FR,85126,46.4722105292,-0.858917690239,INSEE
|
||||
85200,ST MARTIN DE FRAIGNEAU,FR,85244,46.4289052087,-0.758948963227,INSEE
|
||||
85200,SERIGNE,FR,85281,46.5054321828,-0.848819460581,INSEE
|
||||
85200,BOURNEAU,FR,85033,46.5476882922,-0.813838020265,INSEE
|
||||
85200,ST MICHEL LE CLOUCQ,FR,85256,46.4861591475,-0.743056336646,INSEE
|
||||
85200,MONTREUIL,FR,85148,46.3973419593,-0.840846860992,INSEE
|
||||
85200,L ORBRIE,FR,85167,46.4997145725,-0.77427886573,INSEE
|
||||
85210,ST JEAN DE BEUGNE,FR,85233,46.5196817523,-1.10826075013,INSEE
|
||||
85210,ST MARTIN LARS EN STE HERMINE,FR,85248,46.5970244335,-0.976384286709,INSEE
|
||||
85210,LA REORTHE,FR,85188,46.6113748938,-1.04881036553,INSEE
|
||||
85210,ST AUBIN LA PLAINE,FR,85199,46.5040293195,-1.06506577005,INSEE
|
||||
85210,ST JUIRE CHAMPGILLON,FR,85235,46.5882648491,-1.00959676911,INSEE
|
||||
85210,LA CHAPELLE THEMER,FR,85056,46.5639307793,-0.960376685588,INSEE
|
||||
85210,ST ETIENNE DE BRILLOUET,FR,85209,46.5138850327,-1.01157660374,INSEE
|
||||
85210,STE HERMINE,FR,85223,46.5572659953,-1.07210861039,INSEE
|
||||
85210,THIRE,FR,85290,46.5433098199,-1.00699777534,INSEE
|
||||
85220,ST MAIXENT SUR VIE,FR,85239,46.7329496925,-1.82595152879,INSEE
|
||||
85220,LA CHAIZE GIRAUD,FR,85045,46.6476375058,-1.81865076161,INSEE
|
||||
85220,LANDEVIEILLE,FR,85120,46.6444349925,-1.7854367847,INSEE
|
||||
85220,L AIGUILLON SUR VIE,FR,85002,46.6706426618,-1.82599992318,INSEE
|
||||
85220,COEX,FR,85070,46.7078707764,-1.75788339462,INSEE
|
||||
85220,ST REVEREND,FR,85268,46.7057741864,-1.83155480996,INSEE
|
||||
85220,APREMONT,FR,85006,46.7572682339,-1.74841313647,INSEE
|
||||
85220,LA CHAPELLE HERMIER,FR,85054,46.6826679204,-1.72083372442,INSEE
|
||||
85220,COMMEQUIERS,FR,85071,46.7674752232,-1.82534079642,INSEE
|
||||
85230,BEAUVOIR SUR MER,FR,85018,46.9086155426,-2.06349351302,INSEE
|
||||
85230,BOUIN,FR,85029,46.9815930867,-2.00423808381,INSEE
|
||||
85230,ST URBAIN,FR,85273,46.8818371328,-2.01607828912,INSEE
|
||||
85230,ST GERVAIS,FR,85221,46.9285711589,-1.98059327519,INSEE
|
||||
85240,MARILLET,FR,85136,46.5667525381,-0.634287713939,INSEE
|
||||
85240,ST HILAIRE DES LOGES,FR,85227,46.4747117878,-0.650611151998,INSEE
|
||||
85240,FAYMOREAU,FR,85087,46.5427361252,-0.624271378946,INSEE
|
||||
85240,XANTON CHASSENON,FR,85306,46.4519408659,-0.706316598666,INSEE
|
||||
85240,FOUSSAIS PAYRE,FR,85094,46.5230750581,-0.687135962627,INSEE
|
||||
85240,RIVES D AUTISE,FR,85162,46.424726987,-0.665995249042,INSEE
|
||||
85240,PUY DE SERRE,FR,85184,46.5650384637,-0.680144631346,INSEE
|
||||
85250,ST ANDRE GOULE D OIE,FR,85196,46.8410224478,-1.19644211396,INSEE
|
||||
85250,LA RABATELIERE,FR,85186,46.8584147661,-1.2569733759,INSEE
|
||||
85250,CHAVAGNES EN PAILLERS,FR,85065,46.8951394423,-1.24054768713,INSEE
|
||||
85250,ST FULGENT,FR,85215,46.8705618525,-1.16246465678,INSEE
|
||||
85250,VENDRENNES,FR,85301,46.8226741756,-1.11650982164,INSEE
|
||||
85260,LA COPECHAGNIERE,FR,85072,46.8523980181,-1.34349746898,INSEE
|
||||
85260,L HERBERGEMENT,FR,85108,46.9166207979,-1.37033557148,INSEE
|
||||
85260,MONTREVERD,FR,85197,46.9277672307,-1.4126154924,INSEE
|
||||
85260,LES BROUZILS,FR,85038,46.8854235235,-1.33186892233,INSEE
|
||||
85270,NOTRE DAME DE RIEZ,FR,85189,46.7532179022,-1.8935292542,INSEE
|
||||
85270,ST HILAIRE DE RIEZ,FR,85226,46.7432732188,-1.96439228965,INSEE
|
||||
85280,LA FERRIERE,FR,85089,46.7215872927,-1.33469332327,INSEE
|
||||
85290,MORTAGNE SUR SEVRE,FR,85151,46.9910941319,-0.946500033344,INSEE
|
||||
85290,ST LAURENT SUR SEVRE,FR,85238,46.9506837971,-0.901123752328,INSEE
|
||||
85300,CHALLANS,FR,85047,46.8354416653,-1.84036683139,INSEE
|
||||
85300,FROIDFOND,FR,85095,46.8789464367,-1.75511438567,INSEE
|
||||
85300,SOULLANS,FR,85284,46.7951288466,-1.91392699457,INSEE
|
||||
85300,LE PERRIER,FR,85172,46.8196487652,-1.97926629071,INSEE
|
||||
85300,SALLERTAINE,FR,85280,46.8659054157,-1.94894081389,INSEE
|
||||
85310,LA CHAIZE LE VICOMTE,FR,85046,46.6729533879,-1.29188591019,INSEE
|
||||
85310,NESMY,FR,85160,46.5921936479,-1.40947698594,INSEE
|
||||
85310,RIVES DE L YON,FR,85213,46.605637391,-1.3354497172,INSEE
|
||||
85310,LE TABLIER,FR,85285,46.5596307281,-1.32788759657,INSEE
|
||||
85320,CHATEAU GUIBERT,FR,85061,46.5741109302,-1.25524886228,INSEE
|
||||
85320,LES PINEAUX,FR,85175,46.599225902,-1.17865799724,INSEE
|
||||
85320,ROSNAY,FR,85193,46.5324344973,-1.3007139449,INSEE
|
||||
85320,BESSAY,FR,85023,46.5397253861,-1.17028433093,INSEE
|
||||
85320,LA BRETONNIERE LA CLAYE,FR,85036,46.4879459421,-1.26773426545,INSEE
|
||||
85320,CORPE,FR,85073,46.5050234767,-1.17034425311,INSEE
|
||||
85320,MAREUIL SUR LAY DISSAIS,FR,85135,46.5335825488,-1.22688907859,INSEE
|
||||
85320,PEAULT,FR,85171,46.502029199,-1.22708559855,INSEE
|
||||
85320,LA COUTURE,FR,85074,46.523938732,-1.26493227292,INSEE
|
||||
85320,MOUTIERS SUR LE LAY,FR,85157,46.5651677306,-1.16826489836,INSEE
|
||||
85320,STE PEXINE,FR,85261,46.5596018797,-1.12406235901,INSEE
|
||||
85330,NOIRMOUTIER EN L ILE,FR,85163,47.0086655085,-2.26243620205,INSEE
|
||||
85340,L ILE D OLONNE,FR,85112,46.570163703,-1.7737502368,INSEE
|
||||
85340,LES SABLES D OLONNE,FR,85194,46.5007612799,-1.79255128677,INSEE
|
||||
85350,L ILE D YEU,FR,85113,46.7093514816,-2.34712702345,INSEE
|
||||
85360,LA TRANCHE SUR MER,FR,85294,46.3564601605,-1.43136322126,INSEE
|
||||
85370,MOUZEUIL ST MARTIN,FR,85158,46.4591118412,-0.984449849889,INSEE
|
||||
85370,NALLIERS,FR,85159,46.4658962703,-1.03958611312,INSEE
|
||||
85370,LE LANGON,FR,85121,46.4393119039,-0.947017086151,INSEE
|
||||
85390,BAZOGES EN PAREDS,FR,85014,46.6602005512,-0.914053446792,INSEE
|
||||
85390,ST MAURICE LE GIRARD,FR,85252,46.6398624578,-0.810875649028,INSEE
|
||||
85390,TALLUD STE GEMME,FR,85287,46.6949386862,-0.886169517112,INSEE
|
||||
85390,CHAVAGNES LES REDOUX,FR,85066,46.7101499475,-0.915900131393,INSEE
|
||||
85390,CHEFFOIS,FR,85067,46.6786935635,-0.782949851125,INSEE
|
||||
85390,MOUILLERON ST GERMAIN,FR,85154,46.6612700667,-0.846784201071,INSEE
|
||||
85400,STE GEMME LA PLAINE,FR,85216,46.4732196212,-1.11103084694,INSEE
|
||||
85400,LAIROUX,FR,85117,46.4496842668,-1.27114022202,INSEE
|
||||
85400,LUCON,FR,85128,46.4510564854,-1.16449285012,INSEE
|
||||
85400,LES MAGNILS REIGNIERS,FR,85131,46.4635045649,-1.210635375,INSEE
|
||||
85400,CHASNAIS,FR,85058,46.4459908481,-1.2385924923,INSEE
|
||||
85410,ST LAURENT DE LA SALLE,FR,85237,46.5854041653,-0.922177315485,INSEE
|
||||
85410,LA CAILLERE ST HILAIRE,FR,85040,46.6293907412,-0.933153931505,INSEE
|
||||
85410,ST CYR DES GATS,FR,85205,46.572397925,-0.86344873853,INSEE
|
||||
85410,THOUARSAIS BOUILDROUX,FR,85292,46.6062740621,-0.873461023865,INSEE
|
||||
85410,CEZAIS,FR,85041,46.5917363748,-0.802618133558,INSEE
|
||||
85410,ST SULPICE EN PAREDS,FR,85271,46.6130038733,-0.8310839288,INSEE
|
||||
85420,LE MAZEAU,FR,85139,46.3298580373,-0.672957405035,INSEE
|
||||
85420,LIEZ,FR,85123,46.3698532376,-0.70502476758,INSEE
|
||||
85420,BOUILLE COURDAULT,FR,85028,46.3847932448,-0.684917815779,INSEE
|
||||
85420,DAMVIX,FR,85078,46.32063079,-0.743504259797,INSEE
|
||||
85420,MAILLE,FR,85132,46.3417503082,-0.787487297301,INSEE
|
||||
85420,ST PIERRE LE VIEUX,FR,85265,46.4009643491,-0.742816267425,INSEE
|
||||
85420,ST SIGISMOND,FR,85269,46.3368577973,-0.707293731101,INSEE
|
||||
85420,MAILLEZAIS,FR,85133,46.3642178261,-0.750260780443,INSEE
|
||||
85420,RIVES D AUTISE,FR,85162,46.424726987,-0.665995249042,INSEE
|
||||
85430,AUBIGNY LES CLOUZEAUX,FR,85008,46.6028241769,-1.46743549114,INSEE
|
||||
85430,NIEUL LE DOLENT,FR,85161,46.5676509922,-1.51560194548,INSEE
|
||||
85430,LA BOISSIERE DES LANDES,FR,85026,46.5581861734,-1.44371985689,INSEE
|
||||
85440,ST HILAIRE LA FORET,FR,85231,46.4551155186,-1.53048160541,INSEE
|
||||
85440,TALMONT ST HILAIRE,FR,85288,46.475786445,-1.62751498166,INSEE
|
||||
85440,POIROUX,FR,85179,46.5107890457,-1.53929317556,INSEE
|
||||
85440,GROSBREUIL,FR,85103,46.5390090882,-1.6072005484,INSEE
|
||||
85440,AVRILLE,FR,85010,46.4744272125,-1.49524360118,INSEE
|
||||
85450,CHAMPAGNE LES MARAIS,FR,85049,46.3735020647,-1.13380723653,INSEE
|
||||
85450,LA TAILLEE,FR,85286,46.3852513569,-0.941017792066,INSEE
|
||||
85450,CHAILLE LES MARAIS,FR,85042,46.3853555319,-1.01044079362,INSEE
|
||||
85450,VOUILLE LES MARAIS,FR,85304,46.3891941167,-0.968106001439,INSEE
|
||||
85450,MOREILLES,FR,85149,46.4218721314,-1.09404530407,INSEE
|
||||
85450,PUYRAVAULT,FR,85185,46.3653834101,-1.09115660367,INSEE
|
||||
85450,STE RADEGONDE DES NOYERS,FR,85267,46.3694246909,-1.06671995264,INSEE
|
||||
85460,LA FAUTE SUR MER,FR,85307,46.3199919131,-1.31487049579,INSEE
|
||||
85460,L AIGUILLON SUR MER,FR,85001,46.304138479,-1.2623239198,INSEE
|
||||
85470,BRETIGNOLLES SUR MER,FR,85035,46.6374826705,-1.86324200464,INSEE
|
||||
85470,BREM SUR MER,FR,85243,46.6118566989,-1.81003917923,INSEE
|
||||
85480,BOURNEZEAU,FR,85034,46.6296975315,-1.14101742229,INSEE
|
||||
85480,FOUGERE,FR,85093,46.6617881911,-1.23612691916,INSEE
|
||||
85480,ST HILAIRE LE VOUHIS,FR,85232,46.6859198669,-1.15222590884,INSEE
|
||||
85480,THORIGNY,FR,85291,46.6179795025,-1.24888057642,INSEE
|
||||
85490,BENET,FR,85020,46.368873213,-0.613959918706,INSEE
|
||||
85500,BEAUREPAIRE,FR,85017,46.9050210355,-1.10144867013,INSEE
|
||||
85500,ST PAUL EN PAREDS,FR,85259,46.8303789022,-0.964515191283,INSEE
|
||||
85500,LES HERBIERS,FR,85109,46.8666125813,-1.02216086186,INSEE
|
||||
85500,MESNARD LA BAROTIERE,FR,85144,46.851716793,-1.10954466033,INSEE
|
||||
85500,CHANVERRIE,FR,85302,46.9634774521,-0.985340006089,INSEE
|
||||
85510,ROCHETREJOUX,FR,85192,46.7852546732,-0.996743019108,INSEE
|
||||
85510,LE BOUPERE,FR,85031,46.7877960262,-0.930897406714,INSEE
|
||||
85520,JARD SUR MER,FR,85114,46.4246376808,-1.60014921643,INSEE
|
||||
85520,ST VINCENT SUR JARD,FR,85278,46.4297504489,-1.54956205778,INSEE
|
||||
85530,LA BRUFFIERE,FR,85039,47.0148006973,-1.18329758318,INSEE
|
||||
85540,LE CHAMP ST PERE,FR,85050,46.5157210212,-1.33946630196,INSEE
|
||||
85540,LE GIVRE,FR,85101,46.4729146043,-1.40256149118,INSEE
|
||||
85540,MOUTIERS LES MAUXFAITS,FR,85156,46.489279204,-1.43622387207,INSEE
|
||||
85540,ST VINCENT SUR GRAON,FR,85277,46.5034756656,-1.382954206,INSEE
|
||||
85540,LA JONCHERE,FR,85116,46.4358647401,-1.38517843312,INSEE
|
||||
85540,ST BENOIST SUR MER,FR,85201,46.4266286403,-1.3399129604,INSEE
|
||||
85540,CURZON,FR,85077,46.4512376923,-1.30138059216,INSEE
|
||||
85540,ST AVAUGOURD DES LANDES,FR,85200,46.5136722903,-1.47528120789,INSEE
|
||||
85540,ST CYR EN TALMONDAIS,FR,85206,46.4597334032,-1.33722144355,INSEE
|
||||
85550,LA BARRE DE MONTS,FR,85012,46.8722784154,-2.09984018879,INSEE
|
||||
85560,LE BERNARD,FR,85022,46.44832528,-1.43979865314,INSEE
|
||||
85560,LONGEVILLE SUR MER,FR,85127,46.4091029013,-1.47711855345,INSEE
|
||||
85570,POUILLE,FR,85181,46.5022256779,-0.955223380119,INSEE
|
||||
85570,ST VALERIEN,FR,85274,46.5348508899,-0.944470507825,INSEE
|
||||
85570,L HERMENAULT,FR,85110,46.5156262822,-0.898430664265,INSEE
|
||||
85570,PETOSSE,FR,85174,46.4796848965,-0.911734176662,INSEE
|
||||
85570,ST MARTIN DES FONTAINES,FR,85245,46.5464637508,-0.907394581139,INSEE
|
||||
85570,MARSAIS STE RADEGONDE,FR,85137,46.5380790745,-0.868868309437,INSEE
|
||||
85580,ST DENIS DU PAYRE,FR,85207,46.4118936776,-1.27222282402,INSEE
|
||||
85580,ST MICHEL EN L HERM,FR,85255,46.3366903175,-1.2483968538,INSEE
|
||||
85580,TRIAIZE,FR,85297,46.3792685111,-1.19928351422,INSEE
|
||||
85580,GRUES,FR,85104,46.3813091348,-1.32364519268,INSEE
|
||||
85590,TREIZE VENTS,FR,85296,46.9168130123,-0.845959158017,INSEE
|
||||
85590,LES EPESSES,FR,85082,46.8917166066,-0.903422756546,INSEE
|
||||
85590,MALLIEVRE,FR,85134,46.9112847287,-0.864977836096,INSEE
|
||||
85590,ST MALO DU BOIS,FR,85240,46.9248120291,-0.914182961099,INSEE
|
||||
85590,ST MARS LA REORTHE,FR,85242,46.8597253005,-0.925777900202,INSEE
|
||||
85600,LA BOISSIERE DE MONTAIGU,FR,85025,46.9451858636,-1.1916392484,INSEE
|
||||
85600,MONTAIGU VENDEE,FR,85146,46.9759800852,-1.31364530268,INSEE
|
||||
85600,TREIZE SEPTIERS,FR,85295,46.9975586143,-1.23193361154,INSEE
|
||||
85610,CUGAND,FR,85076,47.0602388146,-1.25289811103,INSEE
|
||||
85610,LA BERNARDIERE,FR,85021,47.0361828072,-1.27390355206,INSEE
|
||||
85620,ROCHESERVIERE,FR,85190,46.9274273036,-1.50132208111,INSEE
|
||||
85630,BARBATRE,FR,85011,46.9335754783,-2.16743559847,INSEE
|
||||
85640,MOUCHAMPS,FR,85153,46.7870550926,-1.05454102867,INSEE
|
||||
85660,ST PHILBERT DE BOUAINE,FR,85262,46.9927907526,-1.5073882242,INSEE
|
||||
85670,LA CHAPELLE PALLUAU,FR,85055,46.7873638997,-1.62492863273,INSEE
|
||||
85670,FALLERON,FR,85086,46.8623928354,-1.70108938038,INSEE
|
||||
85670,ST ETIENNE DU BOIS,FR,85210,46.8418481774,-1.59617737479,INSEE
|
||||
85670,ST PAUL MONT PENIT,FR,85260,46.8070059547,-1.66964833149,INSEE
|
||||
85670,ST CHRISTOPHE DU LIGNERON,FR,85204,46.8151386519,-1.74035413493,INSEE
|
||||
85670,GRAND LANDES,FR,85102,46.8483283063,-1.64453002578,INSEE
|
||||
85670,PALLUAU,FR,85169,46.8063002019,-1.60225256402,INSEE
|
||||
85680,LA GUERINIERE,FR,85106,46.9669962053,-2.2302799245,INSEE
|
||||
85690,NOTRE DAME DE MONTS,FR,85164,46.8424284611,-2.10928732775,INSEE
|
||||
85700,SEVREMONT,FR,85090,46.8211105864,-0.854584153953,INSEE
|
||||
85700,LA MEILLERAIE TILLAY,FR,85140,46.742582825,-0.85478763606,INSEE
|
||||
85700,MONTOURNAIS,FR,85147,46.7502937556,-0.770013941158,INSEE
|
||||
85700,POUZAUGES,FR,85182,46.7812581702,-0.828778359084,INSEE
|
||||
85700,REAUMUR,FR,85187,46.7145137269,-0.816742537248,INSEE
|
||||
85700,MENOMBLET,FR,85141,46.7301338667,-0.728654955878,INSEE
|
||||
85700,ST MESMIN,FR,85254,46.8005779435,-0.748892533741,INSEE
|
||||
85710,BOIS DE CENE,FR,85024,46.9479643351,-1.89668693466,INSEE
|
||||
85710,CHATEAUNEUF,FR,85062,46.916944435,-1.9261131832,INSEE
|
||||
85710,LA GARNACHE,FR,85096,46.8977541296,-1.82443040539,INSEE
|
||||
85740,L EPINE,FR,85083,46.9843405667,-2.26449527608,INSEE
|
||||
85750,ANGLES,FR,85004,46.3870511077,-1.40049386944,INSEE
|
||||
85770,LES VELLUIRE SUR VENDEE,FR,85177,46.4190919441,-0.910475769222,INSEE
|
||||
85770,VIX,FR,85303,46.3543018169,-0.856628326667,INSEE
|
||||
85770,LE GUE DE VELLUIRE,FR,85105,46.3675950645,-0.905432724485,INSEE
|
||||
85770,L ILE D ELLE,FR,85111,46.3334258655,-0.919100677098,INSEE
|
||||
85800,ST GILLES CROIX DE VIE,FR,85222,46.6904708814,-1.91946363327,INSEE
|
||||
85800,LE FENOUILLER,FR,85088,46.7161264566,-1.89206667498,INSEE
|
||||
85800,GIVRAND,FR,85100,46.6822701061,-1.8787272243,INSEE
|
||||
EOF;
|
||||
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
namespace Chill\MainBundle\DependencyInjection;
|
||||
|
||||
use Chill\MainBundle\Controller\AddressApiController;
|
||||
use Chill\MainBundle\Controller\UserController;
|
||||
use Chill\MainBundle\Doctrine\DQL\STContains;
|
||||
use Chill\MainBundle\Doctrine\DQL\StrictWordSimilarityOPS;
|
||||
@ -319,6 +320,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
|
||||
'apis' => [
|
||||
[
|
||||
'class' => \Chill\MainBundle\Entity\Address::class,
|
||||
'controller' => AddressApiController::class,
|
||||
'name' => 'address',
|
||||
'base_path' => '/api/1.0/main/address',
|
||||
'base_role' => 'ROLE_USER',
|
||||
@ -443,6 +445,50 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
'class' => \Chill\MainBundle\Entity\Location::class,
|
||||
'controller' => \Chill\MainBundle\Controller\LocationApiController::class,
|
||||
'name' => 'location',
|
||||
'base_path' => '/api/1.0/main/location',
|
||||
'base_role' => 'ROLE_USER',
|
||||
'actions' => [
|
||||
'_index' => [
|
||||
'methods' => [
|
||||
Request::METHOD_GET => true,
|
||||
Request::METHOD_HEAD => true
|
||||
],
|
||||
],
|
||||
'_entity' => [
|
||||
'methods' => [
|
||||
Request::METHOD_GET => true,
|
||||
Request::METHOD_HEAD => true,
|
||||
Request::METHOD_POST => true,
|
||||
]
|
||||
],
|
||||
|
||||
]
|
||||
],
|
||||
[
|
||||
'class' => \Chill\MainBundle\Entity\LocationType::class,
|
||||
'name' => 'location_type',
|
||||
'base_path' => '/api/1.0/main/location-type',
|
||||
'base_role' => 'ROLE_USER',
|
||||
'actions' => [
|
||||
'_index' => [
|
||||
'methods' => [
|
||||
Request::METHOD_GET => true,
|
||||
Request::METHOD_HEAD => true
|
||||
],
|
||||
],
|
||||
'_entity' => [
|
||||
'methods' => [
|
||||
Request::METHOD_GET => true,
|
||||
Request::METHOD_HEAD => true,
|
||||
]
|
||||
],
|
||||
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ class Address
|
||||
* @ORM\Id
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
* @groups({"write"})
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private $id;
|
||||
|
||||
@ -31,7 +31,7 @@ class Address
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="string", length=255)
|
||||
* @groups({"write"})
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private $street = '';
|
||||
|
||||
@ -39,7 +39,7 @@ class Address
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="string", length=255)
|
||||
* @groups({"write"})
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private $streetNumber = '';
|
||||
|
||||
@ -47,7 +47,7 @@ class Address
|
||||
* @var PostalCode
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode")
|
||||
* @groups({"write"})
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private $postcode;
|
||||
|
||||
@ -55,7 +55,7 @@ class Address
|
||||
* @var string|null
|
||||
*
|
||||
* @ORM\Column(type="string", length=16, nullable=true)
|
||||
* @groups({"write"})
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private $floor;
|
||||
|
||||
@ -63,7 +63,7 @@ class Address
|
||||
* @var string|null
|
||||
*
|
||||
* @ORM\Column(type="string", length=16, nullable=true)
|
||||
* @groups({"write"})
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private $corridor;
|
||||
|
||||
@ -71,7 +71,7 @@ class Address
|
||||
* @var string|null
|
||||
*
|
||||
* @ORM\Column(type="string", length=16, nullable=true)
|
||||
* @groups({"write"})
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private $steps;
|
||||
|
||||
@ -79,7 +79,7 @@ class Address
|
||||
* @var string|null
|
||||
*
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @groups({"write"})
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private $buildingName;
|
||||
|
||||
@ -87,7 +87,7 @@ class Address
|
||||
* @var string|null
|
||||
*
|
||||
* @ORM\Column(type="string", length=16, nullable=true)
|
||||
* @groups({"write"})
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private $flat;
|
||||
|
||||
@ -95,7 +95,7 @@ class Address
|
||||
* @var string|null
|
||||
*
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @groups({"write"})
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private $distribution;
|
||||
|
||||
@ -103,7 +103,7 @@ class Address
|
||||
* @var string|null
|
||||
*
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @groups({"write"})
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private $extra;
|
||||
|
||||
@ -114,7 +114,7 @@ class Address
|
||||
* @var \DateTime
|
||||
*
|
||||
* @ORM\Column(type="date")
|
||||
* @groups({"write"})
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private \DateTime $validFrom;
|
||||
|
||||
@ -125,13 +125,13 @@ class Address
|
||||
* @var \DateTime|null
|
||||
*
|
||||
* @ORM\Column(type="date", nullable=true)
|
||||
* @groups({"write"})
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private ?\DateTime $validTo = null;
|
||||
|
||||
/**
|
||||
* True if the address is a "no address", aka homeless person, ...
|
||||
* @groups({"write"})
|
||||
* @Groups({"write"})
|
||||
* @ORM\Column(type="boolean")
|
||||
*
|
||||
* @var bool
|
||||
@ -144,7 +144,7 @@ class Address
|
||||
* @var Point|null
|
||||
*
|
||||
* @ORM\Column(type="point", nullable=true)
|
||||
* @groups({"write"})
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private $point;
|
||||
|
||||
@ -154,7 +154,7 @@ class Address
|
||||
* @var ThirdParty|null
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty")
|
||||
* @groups({"write"})
|
||||
* @Groups({"write"})
|
||||
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
|
||||
*/
|
||||
private $linkedToThirdParty;
|
||||
@ -166,6 +166,12 @@ class Address
|
||||
*/
|
||||
private $customs = [];
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=AddressReference::class)
|
||||
* @Groups({"write"})
|
||||
*/
|
||||
private ?AddressReference $addressReference = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->validFrom = new \DateTime();
|
||||
@ -376,10 +382,23 @@ class Address
|
||||
public static function createFromAddress(Address $original) : Address
|
||||
{
|
||||
return (new Address())
|
||||
->setAddressReference($original->getAddressReference())
|
||||
->setBuildingName($original->getBuildingName())
|
||||
->setCorridor($original->getCorridor())
|
||||
->setCustoms($original->getCustoms())
|
||||
->setDistribution($original->getDistribution())
|
||||
->setExtra($original->getExtra())
|
||||
->setFlat($original->getFlat())
|
||||
->setFloor($original->getFloor())
|
||||
->setIsNoAddress($original->getIsNoAddress())
|
||||
->setLinkedToThirdParty($original->getLinkedToThirdParty())
|
||||
->setPoint($original->getPoint())
|
||||
->setPostcode($original->getPostcode())
|
||||
->setStreetAddress1($original->getStreetAddress1())
|
||||
->setStreetAddress2($original->getStreetAddress2())
|
||||
->setSteps($original->getSteps())
|
||||
->setStreet($original->getStreet())
|
||||
->setStreetNumber($original->getStreetNumber())
|
||||
->setValidFrom($original->getValidFrom())
|
||||
->setValidTo($original->getValidTo())
|
||||
;
|
||||
}
|
||||
|
||||
@ -390,6 +409,7 @@ class Address
|
||||
->setPostcode($original->getPostcode())
|
||||
->setStreet($original->getStreet())
|
||||
->setStreetNumber($original->getStreetNumber())
|
||||
->setAddressReference($original)
|
||||
;
|
||||
}
|
||||
|
||||
@ -506,7 +526,7 @@ class Address
|
||||
return $this->validTo;
|
||||
}
|
||||
|
||||
public function setValidTo(\DateTimeInterface $validTo): self
|
||||
public function setValidTo(?\DateTimeInterface $validTo = null): self
|
||||
{
|
||||
$this->validTo = $validTo;
|
||||
|
||||
@ -537,5 +557,22 @@ class Address
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AddressReference|null
|
||||
*/
|
||||
public function getAddressReference(): ?AddressReference
|
||||
{
|
||||
return $this->addressReference;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AddressReference|null $addressReference
|
||||
* @return Address
|
||||
*/
|
||||
public function setAddressReference(?AddressReference $addressReference = null): Address
|
||||
{
|
||||
$this->addressReference = $addressReference;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,33 +20,41 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Chill\ThirdPartyBundle\Entity;
|
||||
namespace Chill\MainBundle\Entity;
|
||||
|
||||
use Chill\ThirdPartyBundle\Repository\ThirdPartyCivilityRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
|
||||
/**
|
||||
* @ORM\Table(name="chill_3party.party_civility")
|
||||
* @ORM\Entity(repositoryClass=ThirdPartyCivilityRepository::class)
|
||||
* @ORM\Table(name="chill_main_civility")
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class ThirdPartyCivility
|
||||
class Civility
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json")
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private $name = [];
|
||||
private array $name = [];
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json")
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private array $abbreviation = [];
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
private $active = true;
|
||||
private bool $active = true;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
@ -76,4 +84,22 @@ class ThirdPartyCivility
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAbbreviation(): array
|
||||
{
|
||||
return $this->abbreviation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $abbreviation
|
||||
* @return Civility
|
||||
*/
|
||||
public function setAbbreviation(array $abbreviation): self
|
||||
{
|
||||
$this->abbreviation = $abbreviation;
|
||||
return $this;
|
||||
}
|
||||
}
|
242
src/Bundle/ChillMainBundle/Entity/Location.php
Normal file
242
src/Bundle/ChillMainBundle/Entity/Location.php
Normal file
@ -0,0 +1,242 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Entity;
|
||||
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||
use Chill\MainBundle\Repository\LocationRepository;
|
||||
use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
||||
|
||||
/**
|
||||
* @ORM\Table(name="chill_main_location")
|
||||
* @ORM\Entity(repositoryClass=LocationRepository::class)
|
||||
* @DiscriminatorMap(typeProperty="type", mapping={
|
||||
* "location"=Location::class
|
||||
* })
|
||||
*/
|
||||
class Location implements TrackCreationInterface, TrackUpdateInterface
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=LocationType::class)
|
||||
* @ORM\JoinColumn(nullable=false)
|
||||
* @Serializer\Groups({"read", "write"})
|
||||
*/
|
||||
private ?LocationType $locationType = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=Address::class, cascade={"persist"})
|
||||
* @ORM\JoinColumn(nullable=true)
|
||||
* @Serializer\Groups({"read", "write"})
|
||||
*/
|
||||
private ?Address $address = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @Serializer\Groups({"read", "write"})
|
||||
*/
|
||||
private ?string $name = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=64, nullable=true)
|
||||
* @Serializer\Groups({"read", "write"})
|
||||
* @Assert\Regex(pattern="/^([\+{1}])([0-9\s*]{4,20})$/")
|
||||
* @PhonenumberConstraint(type="any")
|
||||
*/
|
||||
private ?string $phonenumber1 = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=64, nullable=true)
|
||||
* @Serializer\Groups({"read", "write"})
|
||||
* @Assert\Regex(pattern="/^([\+{1}])([0-9\s*]{4,20})$/")
|
||||
* @PhonenumberConstraint(type="any")
|
||||
*/
|
||||
private ?string $phonenumber2 = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @Serializer\Groups({"read", "write"})
|
||||
*/
|
||||
private ?string $email = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private bool $availableForUsers = false;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=User::class)
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private ?User $createdBy = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime_immutable", nullable=true)
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private ?\DateTimeImmutable $createdAt = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=User::class)
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private ?User $updatedBy = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime_immutable", nullable=true)
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private ?\DateTimeImmutable $updatedAt = null;
|
||||
|
||||
|
||||
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getLocationType(): ?LocationType
|
||||
{
|
||||
return $this->locationType;
|
||||
}
|
||||
|
||||
public function setLocationType(?LocationType $locationType): self
|
||||
{
|
||||
$this->locationType = $locationType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAddress(): ?Address
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
public function setAddress(Address $address): self
|
||||
{
|
||||
$this->address = $address;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(?string $name): self
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPhonenumber1(): ?string
|
||||
{
|
||||
return $this->phonenumber1;
|
||||
}
|
||||
|
||||
public function setPhonenumber1(?string $phonenumber1): self
|
||||
{
|
||||
$this->phonenumber1 = $phonenumber1;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPhonenumber2(): ?string
|
||||
{
|
||||
return $this->phonenumber2;
|
||||
}
|
||||
|
||||
public function setPhonenumber2(?string $phonenumber2): self
|
||||
{
|
||||
$this->phonenumber2 = $phonenumber2;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function setEmail(?string $email): self
|
||||
{
|
||||
$this->email = $email;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAvailableForUsers(): ?bool
|
||||
{
|
||||
return $this->availableForUsers;
|
||||
}
|
||||
|
||||
public function setAvailableForUsers(bool $availableForUsers): self
|
||||
{
|
||||
$this->availableForUsers = $availableForUsers;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedBy(): ?User
|
||||
{
|
||||
return $this->createdBy;
|
||||
}
|
||||
|
||||
public function setCreatedBy(?User $createdBy): self
|
||||
{
|
||||
$this->createdBy = $createdBy;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function setCreatedAt(?\DateTimeInterface $createdAt): self
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUpdatedBy(): ?User
|
||||
{
|
||||
return $this->updatedBy;
|
||||
}
|
||||
|
||||
public function setUpdatedBy(?User $updatedBy): self
|
||||
{
|
||||
$this->updatedBy = $updatedBy;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUpdatedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
public function setUpdatedAt(?\DateTimeInterface $updatedAt): self
|
||||
{
|
||||
$this->updatedAt = $updatedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
108
src/Bundle/ChillMainBundle/Entity/LocationType.php
Normal file
108
src/Bundle/ChillMainBundle/Entity/LocationType.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Entity;
|
||||
|
||||
use Chill\MainBundle\Repository\LocationTypeRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
||||
|
||||
/**
|
||||
* @ORM\Table(name="chill_main_location_type")
|
||||
* @ORM\Entity(repositoryClass=LocationTypeRepository::class)
|
||||
* @DiscriminatorMap(typeProperty="type", mapping={
|
||||
* "location-type"=LocationType::class
|
||||
* })
|
||||
*/
|
||||
class LocationType
|
||||
{
|
||||
const STATUS_OPTIONAL = 'optional';
|
||||
const STATUS_REQUIRED = 'required';
|
||||
const STATUS_NEVER = 'never';
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json")
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private array $title = [];
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private bool $availableForUsers = true;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=32, options={"default"="optional"})
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private string $addressRequired = self::STATUS_OPTIONAL;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=32, options={"default"="optional"})
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private string $contactData = self::STATUS_OPTIONAL;
|
||||
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getTitle(): ?array
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function setTitle(array $title): self
|
||||
{
|
||||
$this->title = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAvailableForUsers(): ?bool
|
||||
{
|
||||
return $this->availableForUsers;
|
||||
}
|
||||
|
||||
public function setAvailableForUsers(bool $availableForUsers): self
|
||||
{
|
||||
$this->availableForUsers = $availableForUsers;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAddressRequired(): ?string
|
||||
{
|
||||
return $this->addressRequired;
|
||||
}
|
||||
|
||||
public function setAddressRequired(string $addressRequired): self
|
||||
{
|
||||
$this->addressRequired = $addressRequired;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getContactData(): ?string
|
||||
{
|
||||
return $this->contactData;
|
||||
}
|
||||
|
||||
public function setContactData(string $contactData): self
|
||||
{
|
||||
$this->contactData = $contactData;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Chill\MainBundle\Entity;
|
||||
|
||||
use Chill\MainBundle\Doctrine\Model\Point;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
@ -62,6 +63,30 @@ class PostalCode
|
||||
*/
|
||||
private $origin = 0;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @groups({"read"})
|
||||
*/
|
||||
private $refPostalCodeId;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @groups({"read"})
|
||||
*/
|
||||
private $postalCodeSource;
|
||||
|
||||
/**
|
||||
* @var Point
|
||||
*
|
||||
* @ORM\Column(type="point", nullable=true)
|
||||
* @groups({"read"})
|
||||
*/
|
||||
private $center;
|
||||
|
||||
/**
|
||||
* Get id
|
||||
*
|
||||
@ -169,5 +194,41 @@ class PostalCode
|
||||
{
|
||||
return $this->country;
|
||||
}
|
||||
|
||||
public function getRefPostalCodeId(): ?string
|
||||
{
|
||||
return $this->refPostalCodeId;
|
||||
}
|
||||
|
||||
public function setRefPostalCodeId(?string $refPostalCodeId): self
|
||||
{
|
||||
$this->refPostalCodeId = $refPostalCodeId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPostalCodeSource(): ?string
|
||||
{
|
||||
return $this->postalCodeSource;
|
||||
}
|
||||
|
||||
public function setPostalCodeSource(?string $postalCodeSource): self
|
||||
{
|
||||
$this->postalCodeSource = $postalCodeSource;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCenter(): ?Point
|
||||
{
|
||||
return $this->center;
|
||||
}
|
||||
|
||||
public function setCenter(?Point $center): self
|
||||
{
|
||||
$this->center = $center;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,139 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class CenterType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* The user linked with this type.
|
||||
*
|
||||
* @var \Chill\MainBundle\Entity\User
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* associative array where keys are center.id and
|
||||
* value are center objects
|
||||
*
|
||||
* @var Center[]
|
||||
*/
|
||||
protected $reachableCenters = array();
|
||||
|
||||
/**
|
||||
*
|
||||
* @var CenterTransformer
|
||||
*/
|
||||
protected $transformer;
|
||||
|
||||
public function __construct(TokenStorageInterface $tokenStorage,
|
||||
CenterTransformer $transformer)
|
||||
{
|
||||
$this->user = $tokenStorage->getToken()->getUser();
|
||||
$this->transformer = $transformer;
|
||||
$this->prepareReachableCenterByUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* return a 'hidden' field if only one center is available.
|
||||
*
|
||||
* Return a 'choice' field if more than one center is available.
|
||||
*
|
||||
* @return string
|
||||
* @throws \RuntimeException if the user is not associated with any center
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
$nbReachableCenters = count($this->reachableCenters);
|
||||
|
||||
if ($nbReachableCenters === 0) {
|
||||
throw new \RuntimeException("The user is not associated with "
|
||||
. "any center. Associate user with a center");
|
||||
} elseif ($nbReachableCenters === 1) {
|
||||
return HiddenType::class;
|
||||
} else {
|
||||
return EntityType::class;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* configure default options, i.e. add choices if user can reach multiple
|
||||
* centers.
|
||||
*
|
||||
* @param OptionsResolver $resolver
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
if (count($this->reachableCenters) > 1) {
|
||||
$resolver->setDefault('class', Center::class)
|
||||
->setDefault('choices', $this->reachableCenters)
|
||||
->setDefault('placeholder', 'Pick a center')
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* add a data transformer if user can reach only one center
|
||||
*
|
||||
* @param FormBuilderInterface $builder
|
||||
* @param array $options
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
if ($this->getParent() === HiddenType::class) {
|
||||
$builder->addModelTransformer($this->transformer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* populate reachableCenters as an associative array where
|
||||
* keys are center.id and value are center entities.
|
||||
*
|
||||
*/
|
||||
private function prepareReachableCenterByUser()
|
||||
{
|
||||
$groupCenters = $this->user->getGroupCenters();
|
||||
|
||||
foreach ($groupCenters as $groupCenter) {
|
||||
|
||||
$center = $groupCenter->getCenter();
|
||||
|
||||
if (!array_key_exists($center->getId(),
|
||||
$this->reachableCenters)) {
|
||||
$this->reachableCenters[$center->getId()] = $center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -24,14 +24,14 @@ use Symfony\Component\Form\FormInterface;
|
||||
|
||||
/**
|
||||
* Available options :
|
||||
*
|
||||
*
|
||||
* - `button_add_label`
|
||||
* - `button_remove_label`
|
||||
* - `identifier`: an identifier to identify the kind of collecton. Useful if some
|
||||
* javascript should be launched associated to `add_entry`, `remove_entry` events.
|
||||
*
|
||||
* - `empty_collection_explain`
|
||||
*
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class ChillCollectionType extends AbstractType
|
||||
{
|
||||
@ -41,10 +41,11 @@ class ChillCollectionType extends AbstractType
|
||||
->setDefaults([
|
||||
'button_add_label' => 'Add an entry',
|
||||
'button_remove_label' => 'Remove entry',
|
||||
'identifier' => ''
|
||||
'identifier' => '',
|
||||
'empty_collection_explain' => '',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
{
|
||||
$view->vars['button_add_label'] = $options['button_add_label'];
|
||||
@ -52,8 +53,9 @@ class ChillCollectionType extends AbstractType
|
||||
$view->vars['allow_delete'] = (int) $options['allow_delete'];
|
||||
$view->vars['allow_add'] = (int) $options['allow_add'];
|
||||
$view->vars['identifier'] = $options['identifier'];
|
||||
$view->vars['empty_collection_explain'] = $options['empty_collection_explain'];
|
||||
}
|
||||
|
||||
|
||||
public function getParent()
|
||||
{
|
||||
return \Symfony\Component\Form\Extension\Core\Type\CollectionType::class;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user