Compare commits

..

1 Commits

Author SHA1 Message Date
93c0197cdb corrections review 107 2021-09-28 09:33:47 +02:00
249 changed files with 3152 additions and 7553 deletions

View File

@@ -7,7 +7,6 @@ charset = utf-8
end_of_line = LF
insert_final_newline = true
trim_trailing_whitespace = true
indent_size = 4
[*.{php,html,twig}]
indent_style = space

View File

@@ -1,67 +0,0 @@
# 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
* [3party]: french translation of contact and company
* [3party]: show parent in list
* [3party]: change color for badge "child"
## Test releases
### Test release 2021-10-11
* add 3 new fields to PostalCode and adapt postal code command and fixtures
* [Aside activity] Fixes for aside activity
* 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
### test release 2021-10-04
* [Household editor][UI] Update how household suggestion and addresses are picked;
* [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
* [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 yyyy-mm-dd

View File

@@ -1,383 +0,0 @@
# 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 loption 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" => ?

View File

@@ -22,7 +22,6 @@
"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",
@@ -30,7 +29,6 @@
"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",

View File

@@ -27,4 +27,3 @@ 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

View File

@@ -174,10 +174,6 @@ 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');

View File

@@ -141,6 +141,7 @@ 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);
}

View File

@@ -93,13 +93,15 @@ 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'],
// TODO make required again once scope and rights are fixed
'required' => false
'role' => $options['role']
]);
}

View File

@@ -1,9 +1,7 @@
// 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;
@@ -23,36 +21,91 @@ div.new-activity-select-type {
}
}
//// ACTIVITY LIST PAGE
// precise badge-title specific details
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;
// 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.main {
padding: 1em;
}
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%;
// 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;
}
}
}
}
}
}
}
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;
}
}
}
//// ACTIVITY SHOW AND FORM PAGES
// Exceptions for flex-bloc in concerned-groups
// exceptions for flex-bloc in concerned-groups
div.flex-bloc.concerned-groups {
margin-top: 1em;
div.item-bloc {
@@ -77,6 +130,7 @@ div.flex-bloc.concerned-groups {
}
}
/// CHILL ENTITY RENDER BOX
.chill-entity {

View File

@@ -1,88 +0,0 @@
<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 %}
#}

View File

@@ -101,31 +101,3 @@
{% 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 %}

View File

@@ -1,4 +1,4 @@
<div class="context-{{ context }}">
<div class="activity-list context-{{ context }}">
{% if activities|length == 0 %}
<p class="chill-no-data-statement">
@@ -7,56 +7,176 @@
</p>
{% else %}
<div class="flex-table activity-list">
<div class="flex-table">
{% for activity in activities %}
{% set t = activity.type %}
<div class="item-bloc">
<div class="item-row">
{% include '@ChillActivity/Activity/activity-badge-title.html.twig' %}
<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>
{% if activity.comment.comment is not empty
or activity.persons|length > 0
or activity.thirdParties|length > 0
or activity.users|length > 0
%}
<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>
<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>
{% 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 %}

View File

@@ -1,10 +0,0 @@
<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>

View File

@@ -73,7 +73,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 %}

View File

@@ -210,5 +210,3 @@ 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

View File

@@ -0,0 +1,20 @@
<?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');
}
}

View File

@@ -3,9 +3,6 @@
namespace Chill\AsideActivityBundle\Controller;
use Chill\MainBundle\CRUD\Controller\CRUDController;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\HttpFoundation\Request;
/**
@@ -13,12 +10,5 @@ use Symfony\Component\HttpFoundation\Request;
*/
class AsideActivityCategoryController extends CRUDController
{
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
{
/** @var QueryBuilder $query */
$query->addOrderBy('e.ordering', 'ASC');
return $query;
}
}

View File

@@ -4,10 +4,7 @@ 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;
@@ -15,21 +12,12 @@ use Doctrine\Common\Collections\Criteria;
final class AsideActivityController extends CRUDController
{
private $categoryRepository;
public function __construct(AsideActivityCategoryRepository $categoryRepository)
protected function buildQueryEntities(string $action, Request $request)
{
$this->categoryRepository = $categoryRepository;
}
protected function buildQueryEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null)
{
$qb = parent::buildQueryEntities($action, $request, $filterOrder);
$qb = parent::buildQueryEntities($action, $request);
if ('index' === $action) {
$qb->where($qb->expr()->eq('e.agent', ':user'));
$qb->orWhere($qb->expr()->eq('e.createdBy', ':user'));
$qb->andWhere($qb->expr()->eq('e.agent', ':user'));
$qb->setParameter('user', $this->getUser());
}
@@ -49,24 +37,5 @@ 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;
}
}

View File

@@ -24,12 +24,6 @@ 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');
@@ -87,7 +81,7 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte
'controller' => \Chill\AsideActivityBundle\Controller\AsideActivityController::class,
'actions' => [
'index' => [
'template' => '@ChillAsideActivity/asideActivity/index.html.twig',
'template' => '@ChillAsideActivity/asideActivity/list.html.twig',
'role' => 'ROLE_USER'
],
'new' => [

View File

@@ -1,138 +0,0 @@
<?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;
}
}

View File

@@ -7,8 +7,6 @@ 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
@@ -29,33 +27,10 @@ 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;
@@ -84,85 +59,4 @@ 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;
}
}

View File

@@ -3,42 +3,20 @@
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('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' => [

View File

@@ -4,13 +4,11 @@ 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;
@@ -18,27 +16,26 @@ 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\Templating\EngineInterface;
use Symfony\Component\Security\Core\Security;
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,
CategoryRender $categoryRender
TokenStorageInterface $storage
){
$this->timeChoices = $parameterBag->get('chill_aside_activity.form.time_duration');
$this->timeChoices = $parameterBag->get('chill_activity.form.time_duration');
$this->translatableStringHelper = $translatableStringHelper;
$this->storage = $storage;
$this->categoryRender = $categoryRender;
}
public function buildForm(FormBuilderInterface $builder, array $options)
@@ -73,6 +70,11 @@ 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,
@@ -81,17 +83,9 @@ final class AsideActivityFormType extends AbstractType
'required' => true,
'class' => AsideActivityCategory::class,
'placeholder' => 'Choose the activity category',
'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' => 'title[""]'
'choice_label' => function (AsideActivityCategory $asideActivityCategory) {
$options = [];
return $this->categoryRender->renderString($asideActivityCategory, $options);
return $this->translatableStringHelper->localize($asideActivityCategory->getTitle());
},
])
->add('duration', ChoiceType::class, $durationTimeOptions)

View File

@@ -1,48 +0,0 @@
<?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'
]);
}
}
}

View File

@@ -4,8 +4,6 @@ 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;
/**
@@ -16,12 +14,10 @@ use Symfony\Contracts\Translation\TranslatorInterface;
class SectionMenuBuilder implements LocalMenuBuilderInterface
{
protected TranslatorInterface $translator;
public AuthorizationCheckerInterface $authorizationChecker;
public function __construct(TranslatorInterface $translator, AuthorizationCheckerInterface $authorizationChecker)
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
$this->authorizationChecker = $authorizationChecker;
}
/**
@@ -31,26 +27,13 @@ class SectionMenuBuilder implements LocalMenuBuilderInterface
*/
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
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,
]
$menu->addChild($this->translator->trans('Create an aside activity'), [
'route' => 'chill_crud_aside_activity_new'
])
->setExtras([
'order' => 12,
'icons' => ['plus']
'order' => 11,
'icons' => [ 'plus' ]
]);
}
}
/**

View File

@@ -1,92 +0,0 @@
<?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' ];
}
}

View File

@@ -1,12 +1,14 @@
{% extends "@ChillMain/Admin/layoutWithVerticalMenu.html.twig" %}
{% block vertical_menu_content %}
{{ chill_menu('admin_aside_activity') }}
{{ chill_menu('admin_aside_activity', {
'layout': '@ChillAsideActivity/Admin/menu_asideactivity.html.twig',
}) }}
{% 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 %}

View File

@@ -0,0 +1,4 @@
{% extends "@ChillMain/Menu/verticalMenu.html.twig" %}
{% block v_menu_title %}
{{ 'Aside activity configuration menu'|trans }}
{% endblock %}

View File

@@ -1,12 +0,0 @@
{% 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>

View File

@@ -1,91 +1,8 @@
{% extends "@ChillMain/layout.html.twig" %}
{% extends '@ChillMain/layout.html.twig' %}
{% block title %}
{{ 'Aside activity list'|trans }}
{% endblock title %}
{% block title %}{{ ('crud.' ~ crud_name ~ '.index.title')|trans({'%crud_name%': crud_name}) }}{% endblock %}
{% block 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 %}
{% embed '@ChillAsideActivity/AsideActivity/_index.html.twig' %}
{% endembed %}
{% endblock content %}

View File

@@ -0,0 +1,97 @@
{% 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 %}

View File

@@ -3,50 +3,13 @@
{% 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 %}
{% 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 %}
{% 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 %}

View File

@@ -1,4 +1,4 @@
{% extends "@ChillAsideActivity/Admin/layout_asideactivity.html.twig" %}
{% extends "@ChillActivity/Admin/layout_activity.html.twig" %}
{% block title %}
{% include('@ChillMain/CRUD/_edit_title.html.twig') %}

View File

@@ -1,12 +1,11 @@
{% extends "@ChillAsideActivity/Admin/layout_asideactivity.html.twig" %}
{% block admin_content %}
<h1>{{ 'Aside Activity Type list'|trans }}</h1>
<h1>{{ 'ActivityType 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>
@@ -15,8 +14,7 @@
<tbody>
{% for entity in entities %}
<tr>
<td>{{ entity.ordering }}</td>
<td>{{ entity|chill_entity_render_box }}</td>
<td>{{ entity.title|localize_translatable_string }}</td>
<td style="text-align:center;">
{%- if entity.isActive -%}
<i class="fa fa-check-square-o"></i>

View File

@@ -1,4 +1,4 @@
{% extends "@ChillAsideActivity/Admin/layout_asideactivity.html.twig" %}
{% extends "@ChillActivity/Admin/layout_activity.html.twig" %}
{% block title %}
{% include('@ChillMain/CRUD/_new_title.html.twig') %}

View File

@@ -1,80 +0,0 @@
<?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
]);
}
}

View File

@@ -1,22 +1,5 @@
services:
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
Chill\AsideActivityBundle\DataFixtures\:
resource: './../DataFixtures'
autowire: true
autoconfigure: true

View File

@@ -1,5 +1,5 @@
services:
Chill\AsideActivityBundle\Menu\:
resource: "./../../Menu"
resource: './../../Menu'
autowire: true
autoconfigure: true

View File

@@ -7,11 +7,14 @@ 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 'Aside activity category entity created';
return '';
}
public function up(Schema $schema): void

View File

@@ -7,11 +7,14 @@ 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 'Aside activity entity created';
return '';
}
public function up(Schema $schema): void
@@ -56,12 +59,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');
}
}

View File

@@ -7,11 +7,14 @@ 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 'Duration changed to type integer';
return '';
}
public function up(Schema $schema): void

View File

@@ -7,11 +7,14 @@ 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 'Duration changed back to timestamp';
return '';
}
public function up(Schema $schema): void

View File

@@ -7,11 +7,14 @@ 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 'createdat, updatedat and date given a type timestamp';
return '';
}
public function up(Schema $schema): void

View File

@@ -1,28 +0,0 @@
<?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');
}
}

View File

@@ -1,29 +0,0 @@
<?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');
}
}

View File

@@ -14,8 +14,11 @@ 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
@@ -40,8 +43,14 @@ crud:
title_edit: Edition d'une catégorie de type d'activité
#forms
Create a new aside activity type: Nouvelle categorie d'activité annexe
Activity creation: Nouvelle activité annexe
Create a new aside activity type: Nouvelle catégorie d'activité annexe
Create: Créer
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
@@ -49,106 +58,12 @@ Agent: 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%" ?
@@ -158,4 +73,3 @@ 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"

View File

@@ -1 +0,0 @@
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

View File

@@ -29,19 +29,17 @@ 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();
}
}

View File

@@ -41,7 +41,7 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
'routeParameters' => [
'accompanying_period_id' => $period->getId(),
]])
->setExtras(['order' => 35]);
->setExtras(['order' => 41]);
}
}
}

View File

@@ -109,13 +109,3 @@
{# 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 %}

View File

@@ -138,7 +138,7 @@ class DocGeneratorTemplateController extends AbstractController
$em->flush();
return $this->redirectToRoute('chill_wopi_file_edit', [
'fileId' => $storedObject->getUuid(),
'fileId' => $genDocName,
'returnPath' => $request->query->get('returnPath', "/")
]);
}

View File

@@ -73,14 +73,14 @@ class DocumentAccompanyingCourseController extends AbstractController
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $course);
$documents = $em
->getRepository("ChillDocStoreBundle:AccompanyingCourseDocument")
->getRepository(AccompanyingCourseDocument::class)
->findBy(
['course' => $course],
['date' => 'DESC']
);
return $this->render(
'ChillDocStoreBundle:AccompanyingCourseDocument:index.html.twig',
'@ChillDocStore/AccompanyingCourseDocument/index.html.twig',
[
'documents' => $documents,
'accompanyingCourse' => $course
@@ -122,7 +122,7 @@ class DocumentAccompanyingCourseController extends AbstractController
$this->addFlash('error', $this->translator->trans("This form contains errors"));
}
return $this->render('ChillDocStoreBundle:AccompanyingCourseDocument:new.html.twig', [
return $this->render('@ChillDocStore/AccompanyingCourseDocument/new.html.twig', [
'document' => $document,
'form' => $form->createView(),
'accompanyingCourse' => $course,
@@ -138,7 +138,7 @@ class DocumentAccompanyingCourseController extends AbstractController
$this->denyAccessUnlessGranted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE', $document);
return $this->render(
'ChillDocStoreBundle:AccompanyingCourseDocument:show.html.twig',
'@ChillDocStore/AccompanyingCourseDocument/show.html.twig',
['document' => $document, 'accompanyingCourse' => $course]);
}
@@ -171,7 +171,7 @@ class DocumentAccompanyingCourseController extends AbstractController
}
return $this->render(
'ChillDocStoreBundle:AccompanyingCourseDocument:edit.html.twig',
'@ChillDocStore/AccompanyingCourseDocument/edit.html.twig',
[
'document' => $document,
'form' => $form->createView(),

View File

@@ -7,10 +7,7 @@ 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;
/**
@@ -22,7 +19,7 @@ use Symfony\Component\Serializer\Annotation as Serializer;
* message="The file is not stored properly"
* )
*/
class StoredObject implements AsyncFileInterface, Document
class StoredObject implements AsyncFileInterface
{
/**
* @ORM\Id()
@@ -50,12 +47,6 @@ class StoredObject implements AsyncFileInterface, Document
*/
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"})
@@ -77,7 +68,6 @@ class StoredObject implements AsyncFileInterface, Document
public function __construct()
{
$this->creationDate = new \DateTime();
$this->uuid = Uuid::uuid4();
}
public function getId()
@@ -165,13 +155,5 @@ class StoredObject implements AsyncFileInterface, Document
return $this;
}
public function getUuid(): UuidInterface
{
return $this->uuid;
}
public function getWopiDocId(): string
{
return (string) $this->uuid;
}
}

View File

@@ -3,8 +3,9 @@
namespace Chill\DocStoreBundle\Repository;
use App\Entity\AccompanyingCourseDocument;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument as EntityAccompanyingCourseDocument;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ObjectRepository;
/**
* @method AccompanyingCourseDocument|null find($id, $lockMode = null, $lockVersion = null)
@@ -12,39 +13,41 @@ use Doctrine\Persistence\ManagerRegistry;
* @method AccompanyingCourseDocument[] findAll()
* @method AccompanyingCourseDocument[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class AccompanyingCourseDocumentRepository extends ServiceEntityRepository
class AccompanyingCourseDocumentRepository implements ObjectRepository
{
public function __construct(ManagerRegistry $registry)
private EntityRepository $repository;
public function __construct(EntityManagerInterface $entityManager)
{
parent::__construct($registry, AccompanyingCourseDocument::class);
$this->repository = $entityManager->getRepository(EntityAccompanyingCourseDocument::class);
}
// /**
// * @return AccompanyingCourseDocument[] Returns an array of AccompanyingCourseDocument objects
// */
/*
public function findByExampleField($value)
public function getClassName()
{
return $this->createQueryBuilder('a')
->andWhere('a.exampleField = :val')
->setParameter('val', $value)
->orderBy('a.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
return EntityAccompanyingCourseDocument::class;
}
*/
/*
public function findOneBySomeField($value): ?AccompanyingCourseDocument
public function find($id): ?EntityAccompanyingCourseDocument
{
return $this->createQueryBuilder('a')
->andWhere('a.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
return $this->repository->find($id);
}
/**
* @return EntityAccompanyingCourseDocument[]
*/
public function findAll(): array
{
return $this->repository->findAll();
}
public function findBy(array $criteria, $orderBy = null, $limit = null, $offset = null): array
{
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
}
public function findOneBy(array $criteria): ?EntityAccompanyingCourseDocument
{
return $this->repository->findOneBy($criteria);
}
*/
}

View File

@@ -21,8 +21,8 @@
<ul class="record_actions">
<li class="cancel">
<a href="{{ path('accompanying_course_document_index', {'course': accompanyingCourse.id}) }}" class="btn btn-cancel">
{{ 'Back to the list' | trans }}
<a href="{{ chill_return_path_or('accompanying_course_document_index', {'course': accompanyingCourse.id}) }}" class="btn btn-cancel">
{{ 'Cancel' | trans }}
</a>
</li>
<li class="edit">

View File

@@ -3,12 +3,9 @@
"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"
}

View File

@@ -1,30 +0,0 @@
<?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');
}
}

View File

@@ -20,8 +20,6 @@
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;
@@ -211,24 +209,22 @@ class CRUDController extends AbstractController
* This method:
*
* 1. Launch `onPreIndex`
* 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:
* 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:
*
* The default parameters are:
*
* * entities: the list en entities ;
* * crud_name: the name of the crud ;
* * paginator: a paginator element ;
* 10. Launch rendering, the parameter is fetch using `getTemplateFor`
* 5. Launch rendering, the parameter is fetch using `getTemplateFor`
* The parameters may be personnalized using `generateTemplateParameter`.
*
* @param string $action
@@ -253,8 +249,7 @@ class CRUDController extends AbstractController
return $response;
}
$filterOrder = $this->buildFilterOrderHelper($action, $request);
$totalItems = $this->countEntities($action, $request, $filterOrder);
$totalItems = $this->countEntities($action, $request);
$paginator = $this->getPaginatorFactory()->create($totalItems);
$response = $this->onPreIndexBuildQuery($action, $request, $totalItems,
@@ -264,7 +259,16 @@ class CRUDController extends AbstractController
return $response;
}
$entities = $this->getQueryResult($action, $request, $totalItems, $paginator, $filterOrder);
$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);
$response = $this->onPostIndexFetchQuery($action, $request, $totalItems,
$paginator, $entities);
@@ -276,8 +280,7 @@ class CRUDController extends AbstractController
$defaultTemplateParameters = [
'entities' => $entities,
'crud_name' => $this->getCrudName(),
'paginator' => $paginator,
'filter_order' => $filterOrder
'paginator' => $paginator
];
return $this->render(
@@ -286,11 +289,6 @@ class CRUDController extends AbstractController
);
}
protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper
{
return null;
}
/**
* @param string $action
* @param Request $request
@@ -363,9 +361,9 @@ class CRUDController extends AbstractController
* @param PaginatorInterface $paginator
* @return type
*/
protected function queryEntities(string $action, Request $request, PaginatorInterface $paginator, ?FilterOrderHelper $filterOrder = null)
protected function queryEntities(string $action, Request $request, PaginatorInterface $paginator)
{
$query = $this->buildQueryEntities($action, $request, $filterOrder)
$query = $this->buildQueryEntities($action, $request)
->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber())
->setMaxResults($paginator->getItemsPerPage());
@@ -395,13 +393,11 @@ 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,
?FilterOrderHelper $filterOrder = null)
protected function getQueryResult(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $query)
{
$query = $this->queryEntities($action, $request, $paginator, $filterOrder);
return $query->getQuery()->getResult();
}
@@ -412,9 +408,9 @@ class CRUDController extends AbstractController
* @param Request $request
* @return int
*/
protected function countEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null): int
protected function countEntities(string $action, Request $request): int
{
return $this->buildQueryEntities($action, $request, $filterOrder)
return $this->buildQueryEntities($action, $request)
->select('COUNT(e)')
->getQuery()
->getSingleScalarResult()
@@ -1187,11 +1183,6 @@ class CRUDController extends AbstractController
return $this->get(Resolver::class);
}
protected function getFilterOrderHelperFactory(): FilterOrderHelperFactoryInterface
{
return $this->get(FilterOrderHelperFactoryInterface::class);
}
/**
* @return array
*/
@@ -1206,7 +1197,6 @@ class CRUDController extends AbstractController
EventDispatcherInterface::class => EventDispatcherInterface::class,
Resolver::class => Resolver::class,
SerializerInterface::class => SerializerInterface::class,
FilterOrderHelperFactoryInterface::class => FilterOrderHelperFactoryInterface::class,
]
);
}

View File

@@ -19,8 +19,6 @@
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;
@@ -71,9 +69,6 @@ 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.")
@@ -168,7 +163,7 @@ class LoadPostalCodesCommand extends Command
}
$em = $this->entityManager;
$country = $em
->getRepository(Country::class)
->getRepository('ChillMainBundle:Country')
->findOneBy(array('countryCode' => $row[2]));
if ($country === NULL) {
@@ -178,7 +173,7 @@ class LoadPostalCodesCommand extends Command
// try to find an existing postal code
$existingPC = $em
->getRepository(PostalCode::class)
->getRepository('ChillMainBundle:PostalCode')
->findBy(array('code' => $row[0], 'name' => $row[1]));
if (count($existingPC) > 0) {
@@ -192,18 +187,6 @@ 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) {

View File

@@ -1,38 +0,0 @@
<?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']
]);
}
}

View File

@@ -1,41 +0,0 @@
<?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();
}
}

View File

@@ -22,8 +22,6 @@
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;
@@ -42,41 +40,21 @@ class LoadPostalCodes extends AbstractFixture implements OrderedFixtureInterface
return 50;
}
public static $refs = [];
public static $refs = array();
public function load(ObjectManager $manager)
{
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]);
$lines = str_getcsv(self::$codes, "\n");
$belgium = $manager->getRepository('ChillMainBundle:Country')
->findOneBy(array('countryCode' => 'BE'));
foreach($lines as $line) {
$code = str_getcsv($line);
$c = new PostalCode();
$c->setCountry($country)
$c->setCountry($belgium)
->setCode($code[0])
->setName(\ucwords(\strtolower($code[1])))
->setName(\ucwords(\strtolower($code[2])))
;
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];
@@ -89,341 +67,39 @@ class LoadPostalCodes extends AbstractFixture implements OrderedFixtureInterface
$manager->flush();
}
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,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,ESSARTS EN BOCAGE,FR,85084,46.7806739038,-1.22925967851,INSEE
85140,ESSARTS EN BOCAGE,FR,85084,46.7806739038,-1.22925967851,INSEE
85140,ST MARTIN DES NOYERS,FR,85246,46.7239461187,-1.20379080965,INSEE
85140,CHAUCHE,FR,85064,46.8282791899,-1.27090860656,INSEE
85140,ESSARTS EN BOCAGE,FR,85084,46.7806739038,-1.22925967851,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,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,BELLEVIGNY,FR,85019,46.7756383534,-1.43313700054,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,FONTENAY LE COMTE,FR,85092,46.4563186117,-0.793449510859,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,AUCHAY SUR VENDEE,FR,85009,46.4474386161,-0.876574265149,INSEE
85200,DOIX LES FONTAINES,FR,85080,46.3849492327,-0.806840287485,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,MONTREVERD,FR,85197,46.9277672307,-1.4126154924,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
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,LA CHAIZE LE VICOMTE,FR,85046,46.6729533879,-1.29188591019,INSEE
85310,RIVES DE L YON,FR,85213,46.605637391,-1.3354497172,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,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,MAREUIL SUR LAY DISSAIS,FR,85135,46.5335825488,-1.22688907859,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
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
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,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,AUBIGNY LES CLOUZEAUX,FR,85008,46.6028241769,-1.46743549114,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
85440,TALMONT ST HILAIRE,FR,85288,46.475786445,-1.62751498166,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,CHAILLE LES MARAIS,FR,85042,46.3853555319,-1.01044079362,INSEE
85450,VOUILLE LES MARAIS,FR,85304,46.3891941167,-0.968106001439,INSEE
85450,CHAILLE LES MARAIS,FR,85042,46.3853555319,-1.01044079362,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
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
85480,BOURNEZEAU,FR,85034,46.6296975315,-1.14101742229,INSEE
85490,BENET,FR,85020,46.368873213,-0.613959918706,INSEE
85490,BENET,FR,85020,46.368873213,-0.613959918706,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,ST VINCENT SUR GRAON,FR,85277,46.5034756656,-1.382954206,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
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,MONTAIGU VENDEE,FR,85146,46.9759800852,-1.31364530268,INSEE
85600,TREIZE SEPTIERS,FR,85295,46.9975586143,-1.23193361154,INSEE
85600,MONTAIGU VENDEE,FR,85146,46.9759800852,-1.31364530268,INSEE
85600,MONTAIGU VENDEE,FR,85146,46.9759800852,-1.31364530268,INSEE
85600,MONTAIGU VENDEE,FR,85146,46.9759800852,-1.31364530268,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,SEVREMONT,FR,85090,46.8211105864,-0.854584153953,INSEE
85700,MENOMBLET,FR,85141,46.7301338667,-0.728654955878,INSEE
85700,ST MESMIN,FR,85254,46.8005779435,-0.748892533741,INSEE
85700,SEVREMONT,FR,85090,46.8211105864,-0.854584153953,INSEE
85700,SEVREMONT,FR,85090,46.8211105864,-0.854584153953,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
85770,LES VELLUIRE SUR VENDEE,FR,85177,46.4190919441,-0.910475769222,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
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
EOF;
}

View File

@@ -19,7 +19,6 @@
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;
@@ -320,7 +319,6 @@ 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',

View File

@@ -376,22 +376,10 @@ class Address
public static function createFromAddress(Address $original) : Address
{
return (new Address())
->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())
->setSteps($original->getSteps())
->setStreet($original->getStreet())
->setStreetNumber($original->getStreetNumber())
->setStreetAddress1($original->getStreetAddress1())
->setStreetAddress2($original->getStreetAddress2())
->setValidFrom($original->getValidFrom())
->setValidTo($original->getValidTo())
;
}
@@ -518,7 +506,7 @@ class Address
return $this->validTo;
}
public function setValidTo(?\DateTimeInterface $validTo = null): self
public function setValidTo(\DateTimeInterface $validTo): self
{
$this->validTo = $validTo;

View File

@@ -2,7 +2,6 @@
namespace Chill\MainBundle\Entity;
use Chill\MainBundle\Doctrine\Model\Point;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
@@ -63,30 +62,6 @@ 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
*
@@ -194,41 +169,5 @@ 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;
}
}

View File

@@ -0,0 +1,139 @@
<?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;
}
}
}
}

View File

@@ -29,9 +29,9 @@ use Symfony\Component\Form\FormInterface;
* - `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,8 +41,7 @@ class ChillCollectionType extends AbstractType
->setDefaults([
'button_add_label' => 'Add an entry',
'button_remove_label' => 'Remove entry',
'identifier' => '',
'empty_collection_explain' => '',
'identifier' => ''
]);
}
@@ -53,7 +52,6 @@ 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()

View File

@@ -1,45 +0,0 @@
<?php
namespace Chill\MainBundle\Form\Type\DataTransformer;
use Chill\MainBundle\Repository\AddressRepository;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
final class AddressToIdDataTransformer implements DataTransformerInterface
{
private AddressRepository $addressRepository;
public function __construct(AddressRepository $addressRepository)
{
$this->addressRepository = $addressRepository;
}
public function reverseTransform($value)
{
if (NULL === $value || '' === $value) {
return null;
}
$address = $this->addressRepository->find($value);
if (NULL === $address) {
$failure = new TransformationFailedException(sprintf("Address with id %s does not exists", $value));
$failure
->setInvalidMessage("The given {{ value }} is not a valid address id", [ '{{ value }}' => $value]);
throw $failure;
}
return $address;
}
public function transform($value)
{
if (NULL === $value) {
return '';
}
return $value->getId();
}
}

View File

@@ -20,57 +20,36 @@
namespace Chill\MainBundle\Form\Type\DataTransformer;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Repository\CenterRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
class CenterTransformer implements DataTransformerInterface
{
private CenterRepository $centerRepository;
private bool $multiple = false;
private EntityManagerInterface $em;
public function __construct(
CenterRepository $centerRepository,
bool $multiple = false
) {
$this->centerRepository = $centerRepository;
$this->multiple = $multiple;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function reverseTransform($id)
{
if ($id === NULL) {
if ($this->multiple) {
return new ArrayCollection();
} else {
return NULL;
}
return NULL;
}
if ($this->multiple) {
$ids = \explode(',', $id);
} else {
$ids[] = (int) $id;
}
$center = $this
->em
->getRepository(Center::class)
->find($id);
$centers = $this
->centerRepository
->findBy(['id' => $ids ]);
if ([] === $centers || count($ids) > count($centers)) {
if ($center === NULL) {
throw new TransformationFailedException(sprintf(
'No center found for one of those ids: %s', implode(',', $ids)));
'No center found with id %d', $id));
}
if ($this->multiple) {
return new ArrayCollection($centers);
} else {
return $centers[0];
}
return $center;
}
public function transform($center)
@@ -79,21 +58,7 @@ class CenterTransformer implements DataTransformerInterface
return '';
}
if ($this->multiple) {
if (!is_iterable($center)) {
throw new UnexpectedTypeException($center, \Traversable::class);
}
$ids = [];
foreach ($center as $c) {
$ids[] = $c->getId();
}
return implode(',', $ids);
} else {
if (!$center instanceof Center) {
throw new UnexpectedTypeException($center, Center::class);
}
return (string) $center->getId();
}
return $center->getId();
}
}

View File

@@ -1,55 +0,0 @@
<?php
namespace Chill\MainBundle\Form\Type\Listing;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SearchType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\RequestStack;
final class FilterOrderType extends \Symfony\Component\Form\AbstractType
{
private RequestStack $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** @var FilterOrderHelper $helper */
$helper = $options['helper'];
if ($helper->hasSearchBox()) {
$builder->add('q', SearchType::class, [
'label' => false,
'required' => false
]);
}
foreach ($this->requestStack->getCurrentRequest()->query->getIterator() as $key => $value) {
switch($key) {
case 'q':
break;
case 'page':
$builder->add($key, HiddenType::class, [
'data' => 1
]);
break;
default:
$builder->add($key, HiddenType::class, [
'data' => $value
]);
break;
}
}
}
public function configureOptions(\Symfony\Component\OptionsResolver\OptionsResolver $resolver)
{
$resolver->setRequired('helper')
->setAllowedTypes('helper', FilterOrderHelper::class);
}
}

View File

@@ -1,79 +0,0 @@
<?php
namespace Chill\MainBundle\Form\Type;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Form\Type\DataTransformer\AddressToIdDataTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Form type for picking an address.
*
* In the UI, this resolve to a vuejs component which will insert the created address id into the
* hidden's value. It will also allow to edit existing addresses without changing the id.
*
* In every page where this component is shown, you must include the required module:
*
* ```twig
* {% block js %}
* {{ encore_entry_script_tags('mod_input_address') }}
* {% endblock %}
*
* {% block css %}
* {{ encore_entry_link_tags('mod_input_address') }}
* {% endblock %}
* ```
*/
final class PickAddressType extends AbstractType
{
private AddressToIdDataTransformer $addressToIdDataTransformer;
private TranslatorInterface $translator;
public function __construct(
AddressToIdDataTransformer $addressToIdDataTransformer,
TranslatorInterface $translator
) {
$this->addressToIdDataTransformer = $addressToIdDataTransformer;
$this->translator = $translator;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addModelTransformer($this->addressToIdDataTransformer);
}
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['uniqid'] = $view->vars['attr']['data-input-address'] =\uniqid('input_address_');
$view->vars['attr']['data-use-valid-from'] = (int) $options['use_valid_from'];
$view->vars['attr']['data-use-valid-to'] = (int) $options['use_valid_to'];
$view->vars['attr']['data-button-text-create'] = $this->translator->trans($options['button_text_create']);
$view->vars['attr']['data-button-text-update'] = $this->translator->trans($options['button_text_update']);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'class' => Address::class,
'use_valid_to' => false,
'use_valid_from' => false,
'button_text_create' => 'Create an address',
'button_text_update' => 'Update address',
// reset default from hidden type
'required' => true,
'error_bubbling' => false,
]);
}
public function getParent()
{
return HiddenType::class;
}
}

View File

@@ -1,174 +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 Chill\MainBundle\Repository\CenterRepository;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
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;
use Symfony\Component\Security\Core\Security;
/**
* Pick a center
*
* For a given role and, eventually, scopes, show a dropdown (if more than
* one reachable center) or a HiddenType (if one or zero center).
*
*
*/
class PickCenterType extends AbstractType
{
protected AuthorizationHelperInterface $authorizationHelper;
protected Security $security;
protected CenterRepository $centerRepository;
public function __construct(
AuthorizationHelperInterface $authorizationHelper,
Security $security,
CenterRepository $centerRepository
) {
$this->authorizationHelper = $authorizationHelper;
$this->security = $security;
$this->centerRepository = $centerRepository;
}
/**
* 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 <= 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)
{
$resolver
->setDefault('class', Center::class)
->setRequired('role')
->setAllowedTypes('role', [ 'string' ])
->setDefault('scopes', [])
->setAllowedTypes('scopes', ['iterable'])
->setDefault('choice_options', [])
;
/*
->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)
{
$centers = $this->getReachableCenters($options['role'], $options['scopes']);
if (count($centers) <= 1) {
$multiple = $options['choice_options']['multiple'] ?? false;
$builder->add('center', HiddenType::class);
$builder->get('center')->addModelTransformer(
new CenterTransformer($this->centerRepository, $multiple)
);
} else {
$builder->add('center', EntityType::class,
\array_merge(
$options['choice_options'],
[
'class' => Center::class,
'choices' => $centers
]
)
);
}
$builder
->addModelTransformer(new CallbackTransformer(
function($data) {
if (NULL === $data) {
return ['center' => null];
}
return ['center' => $data];
},
function($data) {
return $data['center'];
}
));
}
private function getReachableCenters(string $role, iterable $scopes): array
{
if (0 < count($scopes)) {
$centers = [];
foreach($scopes as $scope) {
foreach ($this->authorizationHelper
->getReachableCenters($this->security->getUser(), $role, $scope) as $center) {
$centers[spl_object_hash($center)] = $center;
}
}
return \array_values($centers);
} else {
return $this->authorizationHelper
->getReachableCenters($this->security->getUser(), $role);
}
}
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['is_hidden'] = count($this->getReachableCenters($options['role'],
$options['scopes'])) <= 1;
}
}

View File

@@ -7,7 +7,6 @@ namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\Address;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ObjectRepository;
final class AddressRepository implements ObjectRepository
@@ -48,9 +47,4 @@ final class AddressRepository implements ObjectRepository
public function getClassName() {
return Address::class;
}
public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder
{
return $this->repository->createQueryBuilder($alias, $indexBy);
}
}

View File

@@ -1,9 +1,6 @@
// Access to Bootstrap variables and mixins
@import '~ChillMainAssets/module/bootstrap/shared';
// Chill variables
@import './scss/chill_variables';
// Chill mixins
@import './scss/mixins';

View File

@@ -1,13 +0,0 @@
/*
* ADDITIONNAL COLORS VARIABLES
*/
// Contexts banners
$chill-person-context: $chill-green;
$chill-accourse-context: #718596;
$chill-household-context: #929d69;
// Badges colors
$social-issue-color: #4bafe8;
$social-action-color: $orange;
$activity-color: yellowgreen;

View File

@@ -115,7 +115,7 @@ div.flex-table {
}
ul.record_actions {
align-self: flex-start; // !! conflict with .float-button.bottom see L297
align-self: flex-start;
}
}
}
@@ -274,35 +274,29 @@ div.float-button {
div.action {
float: right;
position: relative;
z-index: 1;
}
a {
word-break: break-all; // break email address
}
}
&.top {
div.action {
padding: 0 0 1em 1em;
}
// avoid a position relative that make links unclickable
.fa-ul > li {
position: initial;
}
}
&.bottom {
display: flex;
overflow: hidden;
div.action {
height: calc(100% - 0em);
shape-outside: inset(calc(100% - 4em) 0 0);
shape-outside: inset(calc(100% - 2em) 0 0);
display: flex;
align-items: flex-end;
padding: 0 0 0 1em;
* {
align-self: flex-end !important; // neutralize flex-table rule L118
}
ul.record_actions {
margin: 0;
padding: 0;
align-self: flex-end !important;
}
}
}
@@ -310,19 +304,8 @@ div.float-button {
padding: 1em;
border: 1px solid black;
background-color: yellow;
div.box {
div.action {
background-color: transparentize(cyan, 0.4);
}
& > ul li,
& > div,
& > p {
background-color: transparentize(orange, 0.4);
}
& > div {
margin-bottom: 1em;
}
div.action {
background-color: transparentize(#00ffff, 0.4);
}
}
}

View File

@@ -1,3 +1,4 @@
//
// Titles in forms
//
@@ -11,6 +12,7 @@
display: block;
}
// We use box-shadow instead of border
// to avoid to manage border double-width
// Then we can simulate border-collapse: collapse (table)

View File

@@ -4,9 +4,9 @@ const fetchScopes = () => {
return response.json();
}
}).then(data => {
//console.log(data);
console.log(data);
return new Promise((resolve, reject) => {
//console.log(data);
console.log(data);
resolve(data.results);
});
});

View File

@@ -47,7 +47,6 @@ var handleAdd = function(button) {
form_name = button.dataset.collectionAddTarget,
prototype = button.dataset.formPrototype,
collection = document.querySelector('ul[data-collection-name="'+form_name+'"]'),
empty_explain = collection.querySelector('li[data-collection-empty-explain]'),
entry = document.createElement('li'),
event = new CustomEvent('collection-add-entry', { detail: { collection: collection, entry: entry } }),
counter = collection.childNodes.length,
@@ -57,9 +56,6 @@ var handleAdd = function(button) {
entry.innerHTML = content;
entry.classList.add('entry');
initializeRemove(collection, entry);
if (empty_explain !== null) {
empty_explain.remove();
}
collection.appendChild(entry);
collection.dispatchEvent(event);
@@ -110,10 +106,6 @@ window.addEventListener('load', function() {
let entries = collections[i].querySelectorAll(':scope > li');
for (let j = 0; j < entries.length; j ++) {
console.log(entries[j].dataset);
if (entries[j].dataset.collectionEmptyExplain === "1") {
continue;
}
initializeRemove(collections[i], entries[j]);
}
}

View File

@@ -1,16 +0,0 @@
/**
* Submit button(s) are disabled as soon as submit button is clicked and form is submitted.
* ID 'create-form' must be added to submit forms.
*/
var form = document.getElementById('create-form');
var submitButtons = document.querySelectorAll("[type=submit]");
form.addEventListener('submit', function(e){
for(var i=0; i<submitButtons.length; i++){
submitButtons[i].disabled = true;
}
})

View File

@@ -17,8 +17,7 @@ export default {
components: {
AddAddress
},
props: ['addAddress', 'callback'],
emits: ['addressEdited', 'addressCreated'],
props: ['addAddress'],
computed: {
context() {
return this.addAddress.context;
@@ -47,7 +46,6 @@ export default {
// address is already linked, just finish !
this.$refs.addAddress.afterLastPaneAction({});
this.$emit('addressEdited', payload);
// New created address
} else {
@@ -59,8 +57,6 @@ export default {
* Post new created address to targetEntity
*/
postAddressTo(payload) {
this.$emit('addressCreated', payload);
console.log('postAddress', payload.addressId, 'To', payload.target, payload.targetId);
switch (payload.target) {
case 'household':

View File

@@ -1,4 +1,4 @@
/**
/*
* Endpoint chill_api_single_country__index
* method GET, get Country Object
* @returns {Promise} a promise containing all Country object
@@ -14,7 +14,7 @@ const fetchCountries = () => {
});
};
/**
/*
* Endpoint chill_api_single_postal_code__index
* method GET, get Country Object
* @returns {Promise} a promise containing all Postal Code objects filtered with country
@@ -29,7 +29,7 @@ const fetchCities = (country) => {
});
};
/**
/*
* Endpoint chill_api_single_address_reference__index
* method GET, get AddressReference Object
* @returns {Promise} a promise containing all AddressReference objects filtered with postal code
@@ -44,7 +44,7 @@ const fetchReferenceAddresses = (postalCode) => {
});
};
/**
/*
* Endpoint chill_api_single_address_reference__index
* method GET, get AddressReference Object
* @returns {Promise} a promise containing all AddressReference objects filtered with postal code
@@ -60,7 +60,7 @@ const fetchAddresses = () => {
});
};
/**
/*
* Endpoint chill_api_single_address__entity__create
* method POST, post Address Object
* @returns {Promise}
@@ -81,28 +81,8 @@ const postAddress = (address) => {
});
};
/**
*
* @param address
* @returns {Promise<Response>}
*/
const duplicateAddress = (address) => {
const url = `/api/1.0/main/address/${address.address_id}/duplicate.json`;
return fetch(url, {
'method': 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
}).then(response => {
if (response.ok) {
return response.json();
}
throw Error('Error with request resource response');
});
};
/**
/*
* Endpoint chill_api_single_address__entity__create
* method PATCH, patch Address Instance
*
@@ -162,7 +142,6 @@ const getAddress = (id) => {
};
export {
duplicateAddress,
fetchCountries,
fetchCities,
fetchReferenceAddresses,

View File

@@ -1,18 +1,17 @@
<template>
<ul class="record_actions" v-if="!options.onlyButton"
<ul class="record_actions"
:class="{ 'sticky-form-buttons': isStickyForm }">
<li v-if="isStickyForm" class="cancel">
<slot name="before"></slot>
</li>
<li>
<slot name="action"></slot>
</li>
<slot name="action"></slot>
<li v-if="isStickyForm">
<slot name="after"></slot>
</li>
</ul>
<slot v-else name="action"></slot>
</template>
<script>
@@ -24,6 +23,9 @@ export default {
return (typeof this.options.stickyActions !== 'undefined') ?
this.options.stickyActions : this.defaultz.stickyActions;
},
},
methods: {
}
}
</script>

View File

@@ -34,7 +34,6 @@
v-bind:defaultz="this.defaultz"
v-bind:entity="this.entity"
v-bind:flag="this.flag"
@pick-address="this.pickAddress"
ref="suggestAddress">
</suggest-pane>
</template>
@@ -56,7 +55,6 @@
v-bind:entity="this.entity"
v-bind:flag="this.flag"
v-bind:insideModal="false"
@pick-address="this.pickAddress"
ref="suggestAddress">
<template v-slot:before v-if="!bypassFirstStep">
@@ -219,16 +217,7 @@
<script>
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
import {
duplicateAddress,
fetchCountries,
fetchCities,
fetchReferenceAddresses,
getAddress,
patchAddress,
postAddress,
postPostalCode,
} from '../api';
import { getAddress, fetchCountries, fetchCities, fetchReferenceAddresses, patchAddress, postAddress, postPostalCode } from '../api';
import { postAddressToPerson, postAddressToHousehold } from "ChillPersonAssets/vuejs/_api/AddAddress.js";
import ShowPane from './ShowPane.vue';
import SuggestPane from './SuggestPane.vue';
@@ -245,9 +234,6 @@ export default {
EditPane,
DatePane
},
emits: {
pickAddress: null
},
data() {
return {
flag: {
@@ -267,14 +253,11 @@ export default {
title: { create: 'add_an_address_title', edit: 'edit_address' },
openPanesInModal: true,
stickyActions: false,
// show a message when no address.
// if set to undefined, the value will be equivalent to false if stickyActions is false, true otherwise.
showMessageWhenNoAddress: undefined,
useDate: {
validFrom: false,
validTo: false
},
onlyButton: false
hideAddress: false
},
entity: {
address: {}, // <== loaded and returned
@@ -328,10 +311,8 @@ export default {
return (this.validFrom || this.validTo) ? true : false;
},
hasSuggestions() {
if (typeof(this.context.suggestions) !== 'undefined') {
console.log('hasSuggestions', this.context.suggestions);
return this.context.suggestions.length > 0;
}
// TODO
//return addressSuggestions.length > 0
return false;
},
displaySuggestions() {
@@ -356,9 +337,9 @@ export default {
},
mounted() {
//console.log('validFrom', this.validFrom);
//console.log('validTo', this.validTo);
//console.log('useDatePane', this.useDatePane);
console.log('validFrom', this.validFrom);
console.log('validTo', this.validTo);
console.log('useDatePane', this.useDatePane);
console.log('Mounted now !');
if (this.context.edit) {
@@ -666,12 +647,9 @@ export default {
this.flag.loading = false;
this.flag.success = true;
resolve({
address,
targetOrigin: this.context.target,
// for "legacy" use:
target: this.context.target.name,
targetId: this.context.target.id,
addressId: this.entity.address.address_id,
addressId: this.entity.address.address_id
}
);
}))
@@ -717,9 +695,6 @@ export default {
this.flag.loading = false;
this.flag.success = true;
return resolve({
address,
targetOrigin: this.context.target,
// for "legacy" use:
target: this.context.target.name,
targetId: this.context.target.id,
addressId: this.entity.address.address_id
@@ -732,28 +707,18 @@ export default {
});
},
/**
*
* @param address the address selected
*/
pickAddress(address) {
console.log('pickAddress', address);
duplicateAddress(address).then(newAddress => {
this.entity.address = newAddress;
this.flag.loading = false;
this.flag.success = true;
let payload = {
address: newAddress,
targetOrigin: this.context.target,
// for "legacy" use:
target: this.context.target.name,
targetId: this.context.target.id,
addressId: this.entity.address.address_id
};
this.addressChangedCallback(payload);
this.closeSuggestPane();
});
/*
* Method just add closing pane to the callback method
* (get out step1 show pane, submit button)
closePaneAndCallbackSubmit(payload)
{
//this.initForm();
//this.resetPane(); // because parent callback will cast afterLastPaneAction()
console.log('will call parent callback method', payload);
// callback props method from parent
this.addressChangedCallback(payload);
}
*/
}
}
</script>

View File

@@ -1,6 +1,6 @@
<template>
<div v-if="!onlyButton">
<div v-if="!hideAddress">
<div class="loading">
<i v-if="flag.loading" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"></i>
<span class="sr-only">{{ $t('loading') }}</span>
@@ -15,7 +15,7 @@
<span v-if="forceRedirect">{{ $t('wait_redirection') }}</span>
</div>
<div v-if="showMessageWhenNoAddress" class="mt-5">
<div v-if="noAddressWithStickyActions" class="mt-5">
<p class="chill-no-data-statement">
{{ $t('not_yet_address') }}
</p>
@@ -28,11 +28,13 @@
:options="this.options"
:defaultz="this.defaultz">
<template v-slot:action>
<button @click.prevent="$emit('openEditPane')"
class="btn" :class="getClassButton"
type="button" name="button" :title="$t(getTextButton)">
<span v-if="displayTextButton">{{ $t(getTextButton) }}</span>
</button>
<li>
<button @click.prevent="$emit('openEditPane')"
class="btn" :class="getClassButton"
type="button" name="button" :title="$t(getTextButton)">
<span v-if="displayTextButton">{{ $t(getTextButton) }}</span>
</button>
</li>
</template>
</action-buttons>
@@ -50,8 +52,8 @@ export default {
},
props: [
'context',
'defaultz',
'options',
'defaultz',
'flag',
'entity',
'errorMsg',
@@ -84,18 +86,14 @@ export default {
getSuccessText() {
return (this.context.edit) ? 'address_edit_success' : 'address_new_success';
},
onlyButton() {
return (typeof this.options.onlyButton !== 'undefined') ?
this.options.onlyButton : this.defaultz.onlyButton;
hideAddress() {
return (typeof this.options.hideAddress !== 'undefined') ?
this.options.hideAddress : this.defaultz.hideAddress;
},
forceRedirect() {
return (!(this.context.backUrl === null || typeof this.context.backUrl === 'undefined'));
},
showMessageWhenNoAddress() {
let showMessageWhenNoAddress = this.options.showMessageWhenNoAddress === undefined ? this.defaultz.showMessageWhenNoAddress : this.options.showMessageWhenNoAddress;
if (showMessageWhenNoAddress === true || showMessageWhenNoAddress === false) {
return !this.context.edit && !this.address.id && showMessageWhenNoAddress;
}
noAddressWithStickyActions() {
return !this.context.edit && !this.address.id && this.options.stickyActions;
}
}

View File

@@ -10,14 +10,13 @@
<h4 class="h3">{{ $t('address_suggestions') }}</h4>
<div class="flex-table AddressSuggestionList">
<div v-for="a in context.suggestions" class="item-bloc">
<div class="item-bloc">
<div class="float-button bottom">
<div class="box">
<div class="action">
<!-- QUESTION normal que ça vienne avant l'adresse ? pourquoi pas après avoir affiché le address-render-box ? -->
<ul class="record_actions">
<li>
<button class="btn btn-sm btn-choose" @click="this.pickAddress(a)">
<button class="btn btn-sm btn-choose">
{{ $t('use_this_address') }}
</button>
</li>
@@ -26,7 +25,9 @@
<ul class="list-content fa-ul">
<li>
<i class="fa fa-li fa-map-marker"></i>
<address-render-box :address="a"></address-render-box>
<!--
<address-render-box></address-render-box>
-->
</li>
</ul>
</div>
@@ -67,14 +68,9 @@ export default {
'flag',
'entity',
'errorMsg',
'insideModal',
'insideModal'
],
computed: {},
methods: {
pickAddress(address) {
console.log('pickAddress in suggest pane', address);
this.$emit('pickAddress', address);
},
}
methods: {}
}
</script>

View File

@@ -53,7 +53,7 @@ containers.forEach((container) => {
},
/// Don't display show renderbox Address: showPane display only a button
onlyButton: container.dataset.onlyButton === 'true' //boolean, default: false
hideAddress: container.dataset.hideAddress === 'true' //boolean, default: false
}
}
}

View File

@@ -1,86 +0,0 @@
import {createApp} from 'vue';
import {_createI18n} from 'ChillMainAssets/vuejs/_js/i18n';
import {addressMessages} from './i18n';
import App from './App.vue';
const i18n = _createI18n(addressMessages);
let inputs = document.querySelectorAll('input[type="hidden"][data-input-address]');
const isNumeric = function(v) { return !isNaN(v); };
inputs.forEach(el => {
let
addressId = el.value,
uniqid = el.dataset.inputAddress,
container = document.querySelector('div[data-input-address-container="' + uniqid + '"]'),
isEdit = addressId !== '',
addressIdInt = addressId !== '' ? parseInt(addressId) : null
;
if (container === null) {
throw Error("no container");
}
console.log('useValidFrom', el.dataset.useValidFrom === '1');
const app = createApp({
template: `<app v-bind:addAddress="this.addAddress" @address-created="associateToInput"></app>`,
data() {
return {
addAddress: {
context: {
// for legacy ? can be remove ?
target: {
name: 'input-address',
id: addressIdInt,
},
edit: isEdit,
addressId: addressIdInt,
},
options: {
/// Options override default.
/// null value take default component value defined in AddAddress data()
button: {
text: {
create: el.dataset.buttonTextCreate || null,
edit: el.dataset.buttonTextUpdate || null,
},
size: null,
displayText: true
},
/// Modal title text if create or edit address (trans chain, see i18n)
title: {
create: null,
edit: null,
},
/// Display panes in Modal for step123
openPanesInModal: true,
/// Display actions buttons of panes in a sticky-form-button navbar
stickyActions: false,
showMessageWhenNoAddress: true,
/// Use Date fields
useDate: {
validFrom: el.dataset.useValidFrom === '1' || false, //boolean, default: false
validTo: el.dataset.useValidTo === '1' || false, //boolean, default: false
},
/// Don't display show renderbox Address: showPane display only a button
onlyButton: false,
}
}
}
},
methods: {
associateToInput(payload) {
el.value = payload.addressId;
}
}
})
.use(i18n)
.component('app', App)
.mount(container);
});

View File

@@ -1,41 +0,0 @@
<template>
<on-the-fly
:type="context.type"
:id="context.id"
:action="context.action"
:buttonText="options.buttonText"
:displayBadge="options.displayBadge === 'true'"
@saveFormOnTheFly="saveFormOnTheFly">
</on-the-fly>
</template>
<script>
import OnTheFly from './components/OnTheFly.vue';
export default {
name: "App",
components: {
OnTheFly
},
props: ['onTheFly'],
computed: {
context() {
return this.onTheFly.context;
},
options() {
return this.onTheFly.options;
}
},
mounted() {
//console.log('OnTheFly mounted');
//console.log('OnTheFly: data context', this.context);
//console.log('OnTheFly: data options', this.options);
},
methods: {
saveFormOnTheFly(payload) {
console.log('saveFormOnTheFly', payload);
}
}
}
</script>

View File

@@ -1,24 +0,0 @@
const ontheflyMessages = {
fr: {
onthefly: {
show: {
person: "Détails de l'usager",
thirdparty: "Détails du tiers",
file_person: "Ouvrir la fiche de l'usager",
file_thirdparty: "Voir le Tiers",
},
edit: {
person: "Modifier un usager",
thirdparty: "Modifier un tiers"
},
create: {
button: "Créer \"{q}\"",
title: "Création d'un nouvel usager ou d'un tiers professionnel",
person: "un nouvel usager",
thirdparty: "un nouveau tiers professionnel"
},
}
}
}
export { ontheflyMessages };

View File

@@ -1,35 +0,0 @@
import { createApp } from "vue";
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
import { ontheflyMessages } from './i18n.js';
import App from "./App.vue";
const i18n = _createI18n( ontheflyMessages );
let containers = document.querySelectorAll('.onthefly-container');
containers.forEach((container) => {
const app = createApp({
template: `<app :onTheFly="this.onTheFly" ></app>`,
data() {
return {
onTheFly: {
context: {
action: container.dataset.action,
type: container.dataset.targetName,
id: parseInt(container.dataset.targetId),
},
options: {
buttonText: container.dataset.buttonText || null,
displayBadge: container.dataset.displayBadge || false
}
}
}
}
})
.use(i18n)
.component('app', App)
.mount(container);
//console.log('container dataset', container.dataset);
});

View File

@@ -1,11 +1,6 @@
<template>
<a v-if="isDisplayBadge" @click="openModal">
<span class="chill-entity" :class="badgeType">
{{ buttonText }}
</span>
</a>
<a v-else class="btn btn-sm" target="_blank"
<a class="btn btn-sm" target="_blank"
:class="classAction"
:title="$t(titleAction)"
@click="openModal">
@@ -47,16 +42,16 @@
</template>
<template v-slot:footer>
<a v-if="action === 'show'"
:href="buildLocation(id, type)"
<button v-if="action === 'show'"
@click="goToLocation(id, type)"
:title="$t(titleMessage)"
class="btn btn-show">{{ $t(buttonMessage) }}
</a>
<a v-else
</button>
<button v-else
class="btn btn-save"
@click="saveAction">
{{ $t('action.save')}}
</a>
</button>
</template>
</modal>
@@ -66,7 +61,7 @@
<script>
import Modal from 'ChillMainAssets/vuejs/_components/Modal.vue';
import OnTheFlyCreate from './Create.vue';
import OnTheFlyCreate from './OnTheFly/Create.vue';
import OnTheFlyPerson from 'ChillPersonAssets/vuejs/_components/OnTheFly/Person.vue';
import OnTheFlyThirdparty from 'ChillThirdPartyAssets/vuejs/_components/OnTheFly/ThirdParty.vue';
@@ -78,7 +73,7 @@ export default {
OnTheFlyThirdparty,
OnTheFlyCreate
},
props: ['type', 'id', 'action', 'buttonText', 'displayBadge'],
props: ['type', 'id', 'action', 'buttonText'],
emits: ['saveFormOnTheFly'],
data() {
return {
@@ -128,25 +123,17 @@ export default {
return 'action.redirect.' + this.type;
}
},
buttonMessage() {
buttonMessage(){
switch (this.type){
case 'person':
return 'onthefly.show.file_' + this.type;
case 'thirdparty':
return 'onthefly.show.file_' + this.type;
}
},
isDisplayBadge() {
return (this.displayBadge === true && this.buttonText !== null);
},
badgeType() {
return 'entity-' + this.type + ' badge-' + this.type;
}
},
methods: {
openModal() {
console.log('## OPEN ON THE FLY MODAL');
console.log('## type:', this.type, ', action:', this.action);
this.modal.showModal = true;
this.$nextTick(function() {
//this.$refs.search.focus();
@@ -157,6 +144,7 @@ export default {
},
saveAction() {
console.log('saveAction button: create/edit action with', this.type);
let
type = this.type,
data = {} ;
@@ -171,9 +159,10 @@ export default {
break;
default:
if (typeof this.type === 'undefined') { // action=create
if (typeof this.type === 'undefined') {
type = this.$refs.castNew.radioType;
data = this.$refs.castNew.castDataByType();
} else {
throw 'error with object type';
}
@@ -184,12 +173,11 @@ export default {
this.modal.showModal = false;
},
buildLocation(id, type) {
if (type === 'person') {
// TODO i18n
return `/fr/person/${id}/general`;
} else if (type === 'thirdparty') {
return `/fr/thirdparty/thirdparty/${id}/show`;
goToLocation(id, type){
if(type == 'person'){
window.location = `../../person/${id}/general`
} else if(type == 'thirdparty') {
window.location = `../../thirdparty/thirdparty/${id}/show`
}
}
}
@@ -200,4 +188,5 @@ export default {
a {
cursor: pointer;
}
</style>

View File

@@ -3,7 +3,7 @@
<li class="nav-item">
<a class="nav-link" :class="{ active: isActive('person') }">
<label for="person">
<input type="radio" name="person" id="person" v-model="radioType" value="person">
<input type="radio" name="person" v-model="radioType" value="person">
{{ $t('onthefly.create.person') }}
</label>
</a>
@@ -11,7 +11,7 @@
<li class="nav-item">
<a class="nav-link" :class="{ active: isActive('thirdparty') }">
<label for="thirdparty">
<input type="radio" name="thirdparty" id="thirdparty" v-model="radioType" value="thirdparty">
<input type="radio" name="thirdparty" v-model="radioType" value="thirdparty">
{{ $t('onthefly.create.thirdparty') }}
</label>
</a>
@@ -56,7 +56,6 @@ export default {
radioType: {
set(type) {
this.type = type;
console.log('## type:', type, ', action:', this.action);
},
get() {
return this.type;
@@ -72,15 +71,7 @@ export default {
case 'person':
return this.$refs.castPerson.$data.person;
case 'thirdparty':
let data = this.$refs.castThirdparty.$data.thirdparty;
data.name = data.text;
if (data.address !== undefined) {
data.address = { id: data.address.address_id }
} else {
data.address = null;
}
return data;
return this.$refs.castThirdparty.$data.thirdparty;
default:
throw Error('Invalid type of entity')
}

View File

@@ -53,6 +53,24 @@ const messages = {
top: "Haut",
bottom: "Bas",
},
onthefly: {
show: {
person: "Détails de l'usager",
thirdparty: "Détails du tiers",
file_person: "Ouvrir la fiche de l'usager",
file_thirdparty: "Voir le Tiers",
},
edit: {
person: "Modifier un usager",
thirdparty: "Modifier un tiers"
},
create: {
button: "Créer \"{q}\"",
title: "Création d'un nouvel usager ou d'un tiers professionnel",
person: "un nouvel usager",
thirdparty: "un nouveau tiers professionnel"
},
},
renderbox: {
person: "Usager",
birthday: {

View File

@@ -18,7 +18,7 @@
* stickyActions bool (default: false)
* useValidFrom bool (default: false)
* useValidTo bool (default: false)
* onlyButton bool (default: false)
* hideAddress bool (default: false)
#}
<div class="address-container"
@@ -69,7 +69,7 @@
data-use-valid-to="true"
{% endif %}
{% if onlyButton is defined and onlyButton == 1 %}
{% if hideAddress is defined and hideAddress == 1 %}
data-hide-address="true"
{% endif %}
></div>

View File

@@ -1,5 +1,5 @@
{% set formId = crudMainFormId|default('crud_main_form') %}
<div class="{% block crud_content_main_div_class %}col-10 centered{% endblock %}">
<div class="{% block crud_content_main_div_class %}{% endblock %}">
{% block crud_content_header %}
<h1>{{ ('crud.'~crud_name~'.title_edit')|trans }}</h1>
{% endblock crud_content_header %}

View File

@@ -4,12 +4,6 @@
<h1>{{ ('crud.' ~ crud_name ~ '.index.title')|trans({'%crud_name%': crud_name}) }}</h1>
{% endblock index_header %}
{% block filter_order %}
{% if filter_order is not null %}
{{ filter_order|chill_render_filter_order_helper }}
{% endif %}
{% endblock %}
{% if entities|length == 0 %}
{% block no_existing_entities %}
<p>{{ no_existing_entities_sentences|default('No entities')|trans }}</p>
@@ -38,20 +32,17 @@
{% endif %}
{% block pagination %}
<div class="crud_index__pagination">
{{ chill_pagination(paginator) }}
</div>
{% endblock %}
<div class="crud_index__pagination">
{{ chill_pagination(paginator) }}
</div>
{% block list_actions %}
<ul class="record_actions sticky-form-buttons">
{% block actions_before %}{% endblock %}
{% block add_new %}
<li>
<a href="{{ chill_path_add_return_path('chill_crud_' ~ crud_name ~ '_new') }}" class="btn btn-new">{{ ('crud.'~crud_name~'.index.add_new')|trans( {'%crud_name%': crud_name} ) }}</a>
</li>
{% endblock %}
{% block add_new %}
<li>
<a href="{{ chill_path_add_return_path('chill_crud_' ~ crud_name ~ '_new') }}" class="btn btn-new">{{ ('crud.'~crud_name~'.index.add_new')|trans( {'%crud_name%': crud_name} ) }}</a>
</li>
{% endblock %}
</ul>
{% endblock list_actions %}
</div>

View File

@@ -1,11 +1,10 @@
{% set formId = crudMainFormId|default('crud_main_form') %}
<div class="{% block crud_content_main_div_class %}col-10 centered{% endblock %}">
{% block crud_content_header %}
<h1>{{ ('crud.' ~ crud_name ~ '.title_new')|trans({'%crud_name%' : crud_name }) }}</h1>
{% endblock crud_content_header %}
{% block crud_content_form %}
{{ form_start(form, { 'attr' : { 'id': formId } }) }}
{{ form_start(form) }}
{% block crud_content_form_rows %}
{% for f in form %}{% if f.vars.name != 'submit' %}
@@ -15,8 +14,6 @@
{% block crud_content_after_form %}{% endblock %}
{{ form_end(form) }}
{% block crud_content_form_actions %}
<ul class="record_actions sticky-form-buttons">
{% block content_form_actions_back %}
@@ -28,21 +25,21 @@
{% endblock %}
{% block content_form_actions_save_and_close %}
<li class="">
<button type="submit" name="submit" value="save-and-close" class="btn btn-create" form="{{ formId }}">
<button type="submit" name="submit" value="save-and-close" class="btn btn-create">
{{ 'crud.new.save_and_close'|trans }}
</button>
</li>
{% endblock %}
{% block content_form_actions_save_and_show %}
<li class="">
<button type="submit" name="submit" value="save-and-show" class="btn btn-create" form="{{ formId }}">
<button type="submit" name="submit" value="save-and-show" class="btn btn-create">
{{ 'crud.new.save_and_show'|trans }}
</button>
</li>
{% endblock %}
{% block content_form_actions_save_and_new %}
<li class="">
<button type="submit" name="submit" value="save-and-new" class="btn btn-create" form="{{ formId }}">
<button type="submit" name="submit" value="save-and-new" class="btn btn-create">
{{ 'crud.new.save_and_new'|trans }}
</button>
</li>

View File

@@ -1,12 +0,0 @@
{{ form_start(form) }}
<div class="chill_filter_order container">
<div class="row">
<div class="col-md-12">
<div class="input-group mb-3">
{{ form_widget(form.q)}}
<button type="submit" class="btn btn-chill-l-gray"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
</div>
{{ form_end(form) }}

Some files were not shown because too many files have changed in this diff Show More