Compare commits

...

148 Commits

Author SHA1 Message Date
nobohan
2d39fdb8ea update CHANGELOG 2021-10-18 11:22:29 +02:00
nobohan
cae3defedb address: zoom to postal code 2021-10-08 14:50:09 +02:00
nobohan
78f66d4f14 Address: Assign origin = 3 for postalCode created by users 2021-10-08 12:36:48 +02:00
ddbd6a9f23 Merge branch 'improvements_acitivite_annexe' into 'master'
Improvements acitivite annexe

See merge request Chill-Projet/chill-bundles!153
2021-10-04 14:48:37 +00:00
b5eaea0e88 Merge branch 'fixes/aside-activities-before-integration' into 'improvements_acitivite_annexe'
Fixes/aside activities before integration

See merge request Chill-Projet/chill-bundles!165
2021-10-04 14:35:14 +00:00
ed8a569fea remove obsolete test 2021-10-04 16:30:08 +02:00
c41bc7bef9 prevent recursion 2021-10-04 16:16:34 +02:00
27dea97bc6 fixes minor for admin aside activity 2021-10-04 15:55:35 +02:00
27125b838e various minor fixes in aside activities 2021-10-04 15:15:26 +02:00
4c55631297 Merge conflicts in translations.yaml resolved.
# Conflicts:
#   src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml
#   src/Bundle/ChillMainBundle/translations/messages.fr.yml
2021-10-04 11:22:03 +00:00
cd6dec5082 Display of aside activity title styling changed from badge to normal title display 2021-10-04 12:11:27 +02:00
152a7de1dc section menu entries de-activated for role_admin 2021-10-04 12:09:26 +02:00
da19f68440 Title display of aside activities adjusted to display full title (parent > child > ...) Styling to be discussed 2021-10-04 11:20:07 +02:00
e895125d12 view template replaced by CRUD view template 2021-10-04 10:47:09 +02:00
fe9b76a1fb Merge remote-tracking branch 'origin/master' 2021-10-04 10:17:32 +02:00
baf9251000 update changelog 2021-10-04 10:17:13 +02:00
665b38d166 fix details (translations, etc.) 2021-10-04 09:50:05 +02:00
9450d34af0 Merge branch 'fix/center-type-show-hidden-when-no-center-reachable' into 'master'
[CenterType] switch to hidden type with null value when no center is reachable

See merge request Chill-Projet/chill-bundles!162
2021-10-03 19:23:24 +00:00
42301c9df7 Merge branch 'master' into 'fix/center-type-show-hidden-when-no-center-reachable'
# Conflicts:
#   CHANGELOG.md
2021-10-03 19:19:04 +00:00
eb1a7b2374 Merge branch 'fix/household-summary-show-comment-when-no-address' into 'master'
[household summary] show comment form even if no address are registered

See merge request Chill-Projet/chill-bundles!163
2021-10-03 19:17:44 +00:00
7f661f9022 Merge branch 'master' into 'fix/household-summary-show-comment-when-no-address'
# Conflicts:
#   CHANGELOG.md
2021-10-03 19:17:35 +00:00
178ffdf6bf Merge branch 'feature/add-requestor-in-search-results' into 'master'
Feature/add requestor in search results

See merge request Chill-Projet/chill-bundles!161
2021-10-03 19:16:35 +00:00
5f0238f614 Merge branch 'master' into 'feature/add-requestor-in-search-results'
# Conflicts:
#   CHANGELOG.md
2021-10-03 19:16:17 +00:00
cfaa64884d Merge branch 'fix/accept-sms-not-required' into 'master'
[person form] accept sms is not required

See merge request Chill-Projet/chill-bundles!160
2021-10-03 19:14:34 +00:00
faba2b67eb updating changelog 2021-10-01 21:20:19 +02:00
a981661ea5 resume, list associated_persons : improve design, hover effects 2021-10-01 21:14:27 +02:00
3a3baa79c8 resume: list associated persons sorted by household. add method in entity 2021-10-01 20:48:45 +02:00
e35f002f2b improvements based on draft review of MR 153 2021-10-01 19:05:03 +02:00
fbd17a1de6 OnTheFly: adding displayBadge option to replace button by badge
When using displayBadge=true, you need to pass buttonText=string with badge content.

Limitation: onTheFly cannot use directly PersonRenderBox to display badge,
because it don't have person object in props !
2021-10-01 18:46:25 +02:00
77d86d3836 [household summary] show comment form even if no address are registered 2021-10-01 16:53:27 +02:00
eb11597f9c [CenterType] switch to hidden type with null value when no center is
reachable
2021-10-01 16:38:35 +02:00
daa8b03c8b Merge branch 'onTheFly' into resume_accourse 2021-10-01 14:11:32 +02:00
9ee679a3b3 fix vue syntax error 2021-10-01 14:11:03 +02:00
8791c78426 Merge branch 'onTheFly' into resume_accourse 2021-10-01 14:07:37 +02:00
abd60f7ddf resume: include _insert_vue_onthefly template 2021-10-01 14:06:15 +02:00
75348f6ba7 otf: add data attributes and pass them in vue component 2021-10-01 14:03:58 +02:00
75a06d6896 Merge branch 'onTheFly' into resume_accourse 2021-10-01 13:12:03 +02:00
65782d3a6b fix js syntax 2021-10-01 13:11:34 +02:00
1dc01fa8e2 create OnTheFly rootComponent structure, and twig include template to insert it 2021-10-01 13:08:12 +02:00
db9a203df0 otf: prepare to create vue root component: moving files 2021-10-01 12:18:40 +02:00
2a7e3c0334 resume: display associated persons from accourse 2021-10-01 11:55:32 +02:00
ad72904aa9 resume accourse: simplify alerts with simple link to edit page 2021-10-01 11:41:02 +02:00
0b57b3c74d Merge branch 'master' into onTheFly 2021-10-01 11:38:15 +02:00
8c0abbc5cd updating changelog 2021-10-01 11:37:06 +02:00
c42b7c014b udpate changelog [ci-skip] 2021-10-01 11:00:25 +02:00
0d5d4b3f05 add accompanying period where the person is requestor + badge in results 2021-10-01 09:50:13 +02:00
a3e43fcaaa [person form] accept sms is not required 2021-09-30 23:27:56 +02:00
4047399b76 otf: comments to remember things to improve 2021-09-30 21:22:28 +02:00
4c9d352c46 otf: post/patch thirdparty ok, with addAddress creation/edition 2021-09-30 21:11:51 +02:00
a316cb9568 otf: manage address in edit/create form (wip) 2021-09-30 15:46:34 +02:00
14539f6f88 Merge branch 'onTheFly' into thirdparty 2021-09-30 15:13:03 +02:00
92e6506ecb tp: adapt controller/entity/formtype to works with new ACL (TO BE CHECKED)
* rename type by types -> getTypes() getter
* adapt controller to fix centers errors
* remove voteOnAttribute return always true in voter
2021-09-30 15:03:47 +02:00
59fed905e9 AddAddress: in onlyButton case, button is not in ul.record_actions 2021-09-30 15:02:48 +02:00
577d665f09 batch replace hideAddress option by onlyButton 2021-09-30 14:36:01 +02:00
decc3c7463 otf: replace button by link in show modale 2021-09-30 13:44:04 +02:00
1eb8d04a32 fix property into json body with endpoint PATCH thirdparty 2021-09-30 13:23:11 +02:00
2f60ab4d17 fix chill_resolve_center in person banner when n centers > 1 2021-09-30 12:35:36 +02:00
3cf9920718 Merge branch 'master' into onTheFly 2021-09-30 12:08:24 +02:00
Pol Dellaiera
f1f13996f9 fix: Fix missing parameter. 2021-09-30 12:07:58 +02:00
29838a4d8e Merge branch 'onTheFly' of gitlab.com:Chill-Projet/chill-bundles into onTheFly 2021-09-30 12:04:59 +02:00
e9b1f442bc api docs adjusted to patch/post address on thirdparty 2021-09-30 12:02:54 +02:00
e2feea15f9 stash commit 2021-09-30 11:57:20 +02:00
14a13c7bd5 CourseLocation: improve warning alerts
manually cherrypick from 5b0b1441d3c7ef0717f21adac8566e1beb1af52c on local branch resume_accourse
2021-09-30 11:55:28 +02:00
5a37bfd1a0 Merge remote-tracking branch 'origin/master' 2021-09-30 11:51:35 +02:00
bd7f2d997e fix link to edit in wopi 2021-09-30 11:51:26 +02:00
309fe40564 Merge branch 'accourse_impr' into onTheFly 2021-09-30 11:43:49 +02:00
Pol Dellaiera
f8f019cfe6 fix: Fix bug in getSize() method. 2021-09-30 11:09:55 +02:00
460f0daf0a Merge branch 'master' of gitlab.com:Chill-Projet/chill-bundles 2021-09-29 23:53:19 +02:00
5de05c0ee9 Merge branch 'master' into accourse_impr 2021-09-29 23:51:56 +02:00
Pol Dellaiera
7f721da08a fix: Remove obsolete code. 2021-09-29 22:38:48 +02:00
Pol Dellaiera
9a34aa59bb fix: Add missing dependency. 2021-09-29 21:50:44 +02:00
Pol Dellaiera
00259dae20 fix: Update ChillDocumentManager::getSize(). 2021-09-29 21:46:47 +02:00
Pol Dellaiera
fb8b8b354f refactor: Update Wopi::checkFileInfo() so it can authenticate users from access token. 2021-09-29 20:25:32 +02:00
d8c34c3089 otf: thirdparty corrections 2021-09-29 19:53:28 +02:00
74b649fb2c no location button for requestor 2021-09-29 19:30:57 +02:00
bac8e8d0b8 cleaning 2021-09-29 19:27:22 +02:00
008a10dab8 otf: patch person and thirdparty from the store, emit parent method saveFormOnTheFly() 2021-09-29 17:47:21 +02:00
a2df3c8144 rdv avant échanges dans le menu 2021-09-29 16:04:05 +02:00
d8444455c0 Merge branch 'feature/form-move-household-with-checkboxes' 2021-09-29 15:43:40 +02:00
c793159593 quick fix stored boject 2021-09-29 15:27:11 +02:00
26140de7de Merge remote-tracking branch 'origin/master' 2021-09-29 15:15:17 +02:00
2c1d3d08c3 temporary stored object fixes 2021-09-29 15:15:10 +02:00
Pol Dellaiera
86c2a5c3e8 fix: Update ChillDocumentManager. Fix statuscode and return types. 2021-09-29 15:07:27 +02:00
9420c34f58 Merge branch 'onTheFly' of gitlab.com:Chill-Projet/chill-bundles into onTheFly 2021-09-29 15:05:12 +02:00
d94fec3352 Merge branch 'onTheFly' of gitlab.com:Chill-Projet/chill-bundles into onTheFly 2021-09-29 15:03:14 +02:00
ba98283348 fix specs.yaml indentation 2021-09-29 15:02:54 +02:00
bfd95bff39 otf: add patch api endpoint for person and thirdparty 2021-09-29 14:57:41 +02:00
Pol Dellaiera
664fcd07ef fix: Update StoredObject uuid property. 2021-09-29 14:52:22 +02:00
a8df0070e8 Merge branch 'onTheFly' of gitlab.com:Chill-Projet/chill-bundles into onTheFly 2021-09-29 14:47:37 +02:00
440e21a83b Merge branch 'onTheFly' of gitlab.com:Chill-Projet/chill-bundles into onTheFly 2021-09-29 14:40:23 +02:00
8c2d211661 Merge remote-tracking branch 'origin/master' 2021-09-29 14:29:27 +02:00
f2ebb1f33c PATCH added for person endpoint 2021-09-29 14:22:55 +02:00
c53636c40b Merge branch 'refactor/sync-wopi' into 'master'
Refactor and synchronize WOPI packages

See merge request Chill-Projet/chill-bundles!157
2021-09-29 12:05:31 +00:00
0ec095b59a allow to remove / update address in household member editor 2021-09-29 13:57:36 +02:00
86c5376cc2 bypass ACL restriction
fix response 500 when otf create new person
2021-09-29 13:35:33 +02:00
0e54b8cdf0 add a changelog 2021-09-29 13:26:54 +02:00
53e82f1b2a store created address into new household 2021-09-29 12:54:53 +02:00
f4999548ac handle suggestion in addAddress component 2021-09-29 12:48:18 +02:00
302f60e722 addAddress: remove commented old method 2021-09-29 12:14:58 +02:00
5b7484b1ce Merge branch 'onTheFly' of gitlab.com:Chill-Projet/chill-bundles into onTheFly 2021-09-29 11:42:40 +02:00
fc1dd499c4 POST, PUT, PATCH working for thirdparty and added to swagger. Assert for 'centers'(temporarily) removed from thirdparty entity 2021-09-29 11:41:08 +02:00
3824a380ff otf: prepare with debug (wip) 2021-09-29 11:37:53 +02:00
315253589e create api endpoint for duplciating address 2021-09-29 11:32:23 +02:00
af24637dc4 update return type for prepare client trait 2021-09-29 11:31:44 +02:00
0fe248320d OnTheFly (otf): fix clickable radio tab on create modal 2021-09-29 11:21:45 +02:00
a26d278510 comment console.log 2021-09-29 11:21:06 +02:00
9641fd3fcc WIP add address to new household 2021-09-28 23:06:59 +02:00
2b3c4b7af9 fix boundary in getCurrentMembers/getNonCurrentMembers 2021-09-28 23:06:40 +02:00
8853359405 remove dump
(cherry picked from commit e2230409b8)
2021-09-28 19:38:02 +00:00
704ec76ca4 rewrite the destination for household / leave household 2021-09-28 21:35:41 +02:00
Pol Dellaiera
6020ee2cb2 chore: Update .editorconfig file. 2021-09-28 20:35:19 +02:00
Pol Dellaiera
ba53ce40fa Add migration file. 2021-09-28 20:32:43 +02:00
Pol Dellaiera
ab845d4569 Synchronize ChillWopiBundle with latest upstream WOPI packages. 2021-09-28 17:50:53 +02:00
Pol Dellaiera
dcb92b1378 Update StoredObject entity. 2021-09-28 17:49:32 +02:00
Pol Dellaiera
59fcd1e96b Update local ChillDocStore composer.json. 2021-09-28 17:40:49 +02:00
Pol Dellaiera
0aec2fc1d1 Update local ChillWopiBundle composer.json. 2021-09-28 17:39:54 +02:00
Pol Dellaiera
e43ae56e73 Update root composer.json. 2021-09-28 17:39:23 +02:00
3040ed9483 tp onthefly: add add-address (wip) 2021-09-28 16:26:59 +02:00
adc94aaeea javascript syntax correction 2021-09-28 16:26:32 +02:00
c5fa1b883c onthefly: adding tp i18n file 2021-09-28 16:25:38 +02:00
644d0420ce creation of endpoints POST, PUT, PATCH, but still errors 2021-09-28 16:23:24 +02:00
e2230409b8 remove dump 2021-09-28 15:59:31 +02:00
472866ce91 fix search by birthdate 2021-09-28 15:54:09 +02:00
d5d12c4a17 fix tests for user controller 2021-09-28 15:54:08 +02:00
e72ba4c940 bypass ACL restriction 2021-09-28 15:34:15 +02:00
nobohan
ca7ba90717 hotfix: repair creation activity (was an error linked to ListenToActivityCreate) 2021-09-28 14:53:00 +02:00
f3ba3aeed9 Merge branch 'master' into thirdparty 2021-09-28 14:47:52 +02:00
c791c48248 tp: adapt to new ACL interface (wip) 2021-09-28 14:06:52 +02:00
5da332a06e fix error on previous commit, when creating new accourse 2021-09-27 13:14:42 +02:00
eb89fa2cd4 CourseLocation: replace in accourse vue component the old resume _warning_address.html.twig message 2021-09-27 13:03:17 +02:00
82db55cfec CourseLocation: change add-address key, cleaning add-address options 2021-09-27 11:18:11 +02:00
349a0e69f1 accourse: add warning alert box in each bloc with minimum confirm conditions 2021-09-27 10:56:43 +02:00
4de106ec30 accourse service: improve styles of checkboxes list 2021-09-27 10:21:11 +02:00
6301c4c10b optimisation twig template 2021-09-24 14:42:37 +02:00
b87c995d22 fix UserMenuBuilder so that menu entry is not visible for admin menu 2021-09-24 14:42:07 +02:00
960db6aefc fast aside activity creation implemented 2021-09-24 14:41:37 +02:00
a28c996164 stash commit 2 2021-09-23 14:55:54 +02:00
05c5eaa925 stash commit 2021-09-23 14:54:11 +02:00
d4116a4659 Merge branch 'master' into improvements_acitivite_annexe 2021-09-23 14:39:39 +02:00
15897b4f33 asideactivity category entity changed to have parent and children properties 2021-09-22 20:35:46 +02:00
aa1f02ebd4 style of aside activity list adapted 2021-09-22 16:23:31 +02:00
1672e873a3 view page created for aside activity 2021-09-22 16:23:11 +02:00
544af59009 configuration of duration parametres for aside activity 2021-09-22 15:10:09 +02:00
edc528ff92 Fixed migration 2021-09-22 10:06:58 +02:00
62e70c0c36 Fix layout of flash message in main template 2021-09-21 15:12:25 +02:00
f2e945dc0f UserMenu entry added for aside activities list 2021-09-21 11:52:45 +02:00
d2ce4865c0 display all aside activities created for and created by user with temporary badge 2021-09-21 11:42:18 +02:00
601c16ac27 first commit: rendez-vous menu entry deleted from task usermenubuilder. Start sous-types 2021-09-17 17:00:23 +02:00
140 changed files with 3290 additions and 1550 deletions

View File

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

45
CHANGELOG.md Normal file
View File

@@ -0,0 +1,45 @@
# 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
* Address: zoom on postal code geometry + fix origin of manually entered postal code
* [Aside activity] Fixes for aside activity
* categories with child
* fast creation buttons
* add ordering for types
## Test releases
### 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 and thirdparty ;
* AccompanyingCourse Resume page: list associated persons by household, see household when hover, and show on-the-fly modale when clicking on person ;

View File

@@ -22,6 +22,7 @@
"league/csv": "^9.7.1",
"nyholm/psr7": "^1.4",
"phpoffice/phpspreadsheet": "^1.16",
"ramsey/uuid-doctrine": "^1.7",
"sensio/framework-extra-bundle": "^5.5",
"symfony/asset": "4.*",
"symfony/browser-kit": "^5.2",
@@ -29,6 +30,7 @@
"symfony/expression-language": "4.*",
"symfony/form": "4.*",
"symfony/intl": "4.*",
"symfony/mime": "^4 || ^5",
"symfony/monolog-bundle": "^3.5",
"symfony/security-bundle": "4.*",
"symfony/serializer": "^5.2",

View File

@@ -1,20 +0,0 @@
<?php
namespace Chill\AsideActivityBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
/**
* Controller for activity configuration
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
* @author Champs Libres <info@champs-libres.coop>
*/
class AdminController extends AbstractController
{
public function redirectToAdminIndexAction()
{
return $this->redirectToRoute('chill_main_admin_central');
}
}

View File

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

View File

@@ -4,6 +4,8 @@ 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 Doctrine\ORM\QueryBuilder;
use Symfony\Component\HttpFoundation\Request;
@@ -12,12 +14,21 @@ use Doctrine\Common\Collections\Criteria;
final class AsideActivityController extends CRUDController
{
private $categoryRepository;
public function __construct(AsideActivityCategoryRepository $categoryRepository)
{
$this->categoryRepository = $categoryRepository;
}
protected function buildQueryEntities(string $action, Request $request)
{
$qb = parent::buildQueryEntities($action, $request);
if ('index' === $action) {
$qb->andWhere($qb->expr()->eq('e.agent', ':user'));
$qb->where($qb->expr()->eq('e.agent', ':user'));
$qb->orWhere($qb->expr()->eq('e.createdBy', ':user'));
$qb->setParameter('user', $this->getUser());
}
@@ -37,5 +48,24 @@ final class AsideActivityController extends CRUDController
return parent::orderQuery($action, $query, $request, $paginator);
}
public function createEntity(string $action, Request $request): object
{
$asideActivity = new AsideActivity();
$duration = $request->query->get('duration', '300');
$duration = \DateTime::createFromFormat('U', $duration);
$asideActivity->setDuration($duration);
$categoryId = $request->query->get('type', 7);
if($categoryId === null){
return $this->createNotFoundException('You must give a valid category id');
}
$category = $this->categoryRepository->find($categoryId);
$asideActivity->setType($category);
$note = $request->query->get('note', null);
$asideActivity->setNote($note);
return $asideActivity;
}
}

View File

@@ -24,6 +24,12 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte
*/
public function load(array $configs, ContainerBuilder $container): void
{
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('chill_aside_activity.form.time_duration', $config['form']['time_duration']);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
$loader->load('services.yaml');
$loader->load('services/form.yaml');
@@ -81,7 +87,7 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte
'controller' => \Chill\AsideActivityBundle\Controller\AsideActivityController::class,
'actions' => [
'index' => [
'template' => '@ChillAsideActivity/asideActivity/list.html.twig',
'template' => '@ChillAsideActivity/asideActivity/index.html.twig',
'role' => 'ROLE_USER'
],
'new' => [

View File

@@ -0,0 +1,138 @@
<?php
namespace Chill\AsideActivityBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('chill_aside_activity');
$treeBuilder->getRootNode('chill_aside_activity')
->children()
->arrayNode('form')
->canBeEnabled()
->children()
->arrayNode('time_duration')
->isRequired()
->defaultValue(
[
[ 'label' => '5 minutes', 'seconds' => 300],
[ 'label' => '10 minutes', 'seconds' => 600],
[ 'label' => '15 minutes', 'seconds' => 900],
[ 'label' => '20 minutes', 'seconds' => 1200],
[ 'label' => '25 minutes', 'seconds' => 1500],
[ 'label' => '30 minutes', 'seconds' => 1800],
[ 'label' => '45 minutes', 'seconds' => 2700],
[ 'label' => '1 hour', 'seconds' => 3600],
[ 'label' => '1 hour 15', 'seconds' => 4500],
[ 'label' => '1 hour 30', 'seconds' => 5400],
[ 'label' => '1 hour 45', 'seconds' => 6300],
[ 'label' => '2 hours', 'seconds' => 7200],
[ 'label' => '2 hours 30', 'seconds' => 9000],
[ 'label' => '3 hours', 'seconds' => 10800],
[ 'label' => '3 hours 30', 'seconds' => 12600],
[ 'label' => '4 hours', 'seconds' => 14400],
[ 'label' => '4 hours 30', 'seconds' => 16200],
[ 'label' => '5 hours', 'seconds' => 18000],
[ 'label' => '5 hours 30', 'seconds' => 19800],
[ 'label' => '6 hours', 'seconds' => 21600],
[ 'label' => '6 hours 30', 'seconds' => 23400],
[ 'label' => '7 hours', 'seconds' => 25200],
[ 'label' => '7 hours 30', 'seconds' => 27000],
[ 'label' => '8 hours', 'seconds' => 28800],
[ 'label' => '8 hours 30', 'seconds' => 30600],
[ 'label' => '9 hours', 'seconds' => 32400],
[ 'label' => '9 hours 30', 'seconds' => 34200],
[ 'label' => '10 hours', 'seconds' => 36000],
[ 'label' => '1/2 day', 'seconds' => 14040],
[ 'label' => '1 day', 'seconds' => 28080],
[ 'label' => '1 1/2 days', 'seconds' => 42120],
[ 'label' => '2 days', 'seconds' => 56160],
[ 'label' => '2 1/2 days', 'seconds' => 70200],
[ 'label' => '3 days', 'seconds' => 84240],
[ 'label' => '3 1/2 days', 'seconds' => 98280],
[ 'label' => '4 days', 'seconds' => 112320],
[ 'label' => '4 1/2 days', 'seconds' => 126360],
[ 'label' => '5 days', 'seconds' => 140400],
[ 'label' => '5 1/2 days', 'seconds' => 154440],
[ 'label' => '6 days', 'seconds' => 168480],
[ 'label' => '6 1/2 days', 'seconds' => 182520],
[ 'label' => '7 days', 'seconds' => 196560],
[ 'label' => '7 1/2 days', 'seconds' => 210600],
[ 'label' => '8 days', 'seconds' => 224640],
[ 'label' => '8 1/2 days', 'seconds' => 238680],
[ 'label' => '9 days', 'seconds' => 252720],
[ 'label' => '9 1/2 days', 'seconds' => 266760],
[ 'label' => '10 days', 'seconds' => 280800],
[ 'label' => '10 1/2days', 'seconds' => 294840],
[ 'label' => '11 days', 'seconds' => 308880],
[ 'label' => '11 1/2 days', 'seconds' => 322920],
[ 'label' => '12 days', 'seconds' => 336960],
[ 'label' => '12 1/2 days', 'seconds' => 351000],
[ 'label' => '13 days', 'seconds' => 365040],
[ 'label' => '13 1/2 days', 'seconds' => 379080],
[ 'label' => '14 days', 'seconds' => 393120],
[ 'label' => '14 1/2 days', 'seconds' => 407160],
[ 'label' => '15 days', 'seconds' => 421200],
[ 'label' => '15 1/2 days', 'seconds' => 435240],
[ 'label' => '16 days', 'seconds' => 449280],
[ 'label' => '16 1/2 days', 'seconds' => 463320],
[ 'label' => '17 days', 'seconds' => 477360],
[ 'label' => '17 1/2 days', 'seconds' => 491400],
[ 'label' => '18 days', 'seconds' => 505440],
[ 'label' => '18 1/2 days', 'seconds' => 519480],
[ 'label' => '19 days', 'seconds' => 533520],
[ 'label' => '19 1/2 days', 'seconds' => 547560],
[ 'label' => '20 days', 'seconds' => 561600],
[ 'label' => '20 1/2 days', 'seconds' => 575640],
[ 'label' => '21 days', 'seconds' => 580680],
[ 'label' => '21 1/2 days', 'seconds' => 603720],
[ 'label' => '22 days', 'seconds' => 617760],
[ 'label' => '22 1/2 days', 'seconds' => 631800],
[ 'label' => '23 days', 'seconds' => 645840],
[ 'label' => '23 1/2 days', 'seconds' => 659880],
[ 'label' => '24 days', 'seconds' => 673920],
[ 'label' => '24 1/2 days', 'seconds' => 687960],
[ 'label' => '25 days', 'seconds' => 702000],
[ 'label' => '25 1/2 days', 'seconds' => 716040],
[ 'label' => '26 days', 'seconds' => 730080],
[ 'label' => '26 1/2 days', 'seconds' => 744120],
[ 'label' => '27 days', 'seconds' => 758160],
[ 'label' => '27 1/2 days', 'seconds' => 772200],
[ 'label' => '28 days', 'seconds' => 786240],
[ 'label' => '28 1/2 days', 'seconds' => 800280],
[ 'label' => '29 days', 'seconds' => 814320],
[ 'label' => '29 1/2 days', 'seconds' => 828360],
[ 'label' => '30 days', 'seconds' => 842400],
]
)
->info('The intervals of time to show in activity form')
->prototype('array')
->children()
->scalarNode('seconds')
->info("The number of seconds of this duration. Must be an integer.")
->cannotBeEmpty()
->validate()
->ifTrue(function($data) {
return !is_int($data);
})->thenInvalid("The value %s is not a valid integer")
->end()
->end()
->scalarNode('label')
->cannotBeEmpty()
->info("The label to show into fields")
->end()
->end()
->end()
->end()
->end()
->end();
return $treeBuilder;
}
}

View File

@@ -7,6 +7,8 @@ namespace Chill\AsideActivityBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
* @ORM\Entity
@@ -27,10 +29,33 @@ class AsideActivityCategory
private array $title;
/**
* @ORM\OneToMany(targetEntity=AsideActivityCategory::class, mappedBy="parent")
* @ORM\Column(type="boolean")
*/
private bool $isActive = true;
/**
* @var float
* @ORM\Column(type="float", options={"default": 0.00})
*/
private float $ordering = 0.00;
/**
* @ORM\ManyToOne(targetEntity=AsideActivityCategory::class, inversedBy="children")
* @ORM\JoinColumn(nullable=true)
*/
private $parent;
/**
* @ORM\OneToMany(targetEntity=AsideActivityCategory::class, mappedBy="parent")
*/
private $children;
public function __construct()
{
$this->children = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
@@ -59,4 +84,85 @@ class AsideActivityCategory
return $this;
}
public function getParent(): ?self
{
return $this->parent;
}
/**
*
* @Assert\Callback()
*/
public function preventRecursiveParent(ExecutionContextInterface $context, $payload)
{
if (!$this->hasParent()) {
return;
}
if ($this->getParent() === $this) {
// replace parent with old parent. This prevent recursive loop
// when displaying form
$this->parent = $this->oldParent;
$context->buildViolation('You must not add twice the same category in the parent tree (previous result returned)')
->atPath('parent')
->addViolation()
;
}
}
public function hasParent(): bool
{
return $this->parent !== null;
}
public function setParent(?self $parent): self
{
// cache the old result for changing it during validaiton
$this->oldParent = $this->parent;
$this->parent = $parent;
return $this;
}
/**
* @return Collection|self[]
*/
public function getChildren(): Collection
{
return $this->children;
}
public function addChild(self $child): self
{
if (!$this->children->contains($child)) {
$this->children[] = $child;
$child->setParent($this);
}
return $this;
}
public function removeChild(self $child): self
{
if ($this->children->removeElement($child)) {
// set the owning side to null (unless already changed)
if ($child->getParent() === $this) {
$child->setParent(null);
}
}
return $this;
}
public function getOrdering(): float
{
return $this->ordering;
}
public function setOrdering(float $ordering): AsideActivityCategory
{
$this->ordering = $ordering;
return $this;
}
}

View File

@@ -3,21 +3,43 @@
namespace Chill\AsideActivityBundle\Form;
use Chill\AsideActivityBundle\Entity\AsideActivityCategory;
use Chill\AsideActivityBundle\Templating\Entity\CategoryRender;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\FormBuilderInterface;
final class AsideActivityCategoryType extends AbstractType
{
protected $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper, CategoryRender $categoryRender)
{
$this->translatableStringHelper = $translatableStringHelper;
$this->categoryRender = $categoryRender;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title', TranslatableStringFormType::class,
[
'label' => 'Nom',
])
->add('isActive', ChoiceType::class,
->add('parent', EntityType::class, [
'class' => AsideActivityCategory::class,
'required' => false,
'label' => 'Parent',
'choice_label' => function (AsideActivityCategory $category){
$options = [];
return $this->categoryRender->renderString($category, $options);
}
])
->add('ordering', NumberType::class)
->add('isActive', ChoiceType::class,
[
'choices' => [
'Yes' => true,

View File

@@ -4,11 +4,13 @@ namespace Chill\AsideActivityBundle\Form;
use Chill\AsideActivityBundle\Entity\AsideActivity;
use Chill\AsideActivityBundle\Entity\AsideActivityCategory;
use Chill\AsideActivityBundle\Templating\Entity\CategoryRender;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\ChillTextareaType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\AbstractType;
@@ -16,26 +18,27 @@ use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTra
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Templating\EngineInterface;
final class AsideActivityFormType extends AbstractType
{
protected array $timeChoices;
private TranslatableStringHelper $translatableStringHelper;
private TokenStorageInterface $storage;
private CategoryRender $categoryRender;
public function __construct (
TranslatableStringHelper $translatableStringHelper,
ParameterBagInterface $parameterBag,
TokenStorageInterface $storage
TokenStorageInterface $storage,
CategoryRender $categoryRender
){
$this->timeChoices = $parameterBag->get('chill_activity.form.time_duration');
$this->timeChoices = $parameterBag->get('chill_aside_activity.form.time_duration');
$this->translatableStringHelper = $translatableStringHelper;
$this->storage = $storage;
$this->categoryRender = $categoryRender;
}
public function buildForm(FormBuilderInterface $builder, array $options)
@@ -70,11 +73,6 @@ final class AsideActivityFormType extends AbstractType
[
'label' => 'date',
'data' => new \DateTime(),
//SETTING RANGE ONLY POSSIBLE WITH WIDGET 'CHOICE' AND NOT 'SINGLE_TEXT'?
// 'widget' => 'choice',
// 'years' => range(2020, date('Y')),
// 'months' => range(1, date('m')),
// 'days' => range(1, date('d')),
'required' => true
])
->add('type', EntityType::class,
@@ -83,9 +81,17 @@ final class AsideActivityFormType extends AbstractType
'required' => true,
'class' => AsideActivityCategory::class,
'placeholder' => 'Choose the activity category',
// 'choice_label' => 'title[""]'
'query_builder' => function(EntityRepository $er) {
$qb = $er->createQueryBuilder('ac');
$qb->where($qb->expr()->eq('ac.isActive', 'TRUE'))
->addOrderBy('ac.ordering', 'ASC')
;
return $qb;
},
'choice_label' => function (AsideActivityCategory $asideActivityCategory) {
return $this->translatableStringHelper->localize($asideActivityCategory->getTitle());
$options = [];
return $this->categoryRender->renderString($asideActivityCategory, $options);
},
])
->add('duration', ChoiceType::class, $durationTimeOptions)

View File

@@ -0,0 +1,48 @@
<?php
namespace Chill\AsideActivityBundle\Menu;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Security;
final class AdminMenuBuilder implements \Chill\MainBundle\Routing\LocalMenuBuilderInterface
{
private Security $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public static function getMenuIds(): array
{
return ['admin_index', 'admin_section', 'admin_aside_activity'];
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
// all the entries below must have ROLE_ADMIN permissions
if (!$this->security->isGranted('ROLE_ADMIN')) {
return;
}
if (in_array($menuId, ['admin_index', 'admin_section'])) {
$menu->addChild('Aside activities', [
'route' => 'chill_crud_aside_activity_category_index'
])
->setExtras([
'order' => 900,
'explain' => "Configure aside activities categories"
]);
} else {
$menu
->addChild('Aside activity categories', [
'route' => 'chill_crud_aside_activity_category_index'
])
->setExtras([
'order' => '50'
]);
}
}
}

View File

@@ -4,6 +4,8 @@ namespace Chill\AsideActivityBundle\Menu;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
@@ -14,10 +16,12 @@ use Symfony\Contracts\Translation\TranslatorInterface;
class SectionMenuBuilder implements LocalMenuBuilderInterface
{
protected TranslatorInterface $translator;
public AuthorizationCheckerInterface $authorizationChecker;
public function __construct(TranslatorInterface $translator)
public function __construct(TranslatorInterface $translator, AuthorizationCheckerInterface $authorizationChecker)
{
$this->translator = $translator;
$this->authorizationChecker = $authorizationChecker;
}
/**
@@ -27,13 +31,26 @@ class SectionMenuBuilder implements LocalMenuBuilderInterface
*/
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
$menu->addChild($this->translator->trans('Create an aside activity'), [
'route' => 'chill_crud_aside_activity_new'
if ($this->authorizationChecker->isGranted('ROLE_USER')){
$menu->addChild($this->translator->trans('Create an aside activity'), [
'route' => 'chill_crud_aside_activity_new'
])
->setExtras([
'order' => 11,
'icons' => [ 'plus' ]
]);
$menu->addChild($this->translator->trans('Phonecall'), [
'route' => 'chill_crud_aside_activity_new',
'routeParameters' => [
'type' => 1,
'duration' => 900,
]
])
->setExtras([
'order' => 11,
'icons' => [ 'plus' ]
'order' => 12,
'icons' => ['plus']
]);
}
}
/**

View File

@@ -0,0 +1,92 @@
<?php
/*
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Chill\AsideActivityBundle\Menu;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Chill\TaskBundle\Templating\UI\CountNotificationTask;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Chill\MainBundle\Entity\User;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class UserMenuBuilder implements LocalMenuBuilderInterface
{
/**
*
* @var CountNotificationTask
*/
public $counter;
/*
* @var TokenStorageInterface
*/
public $tokenStorage;
/**
*
* @var TranslatorInterface
*/
public $translator;
/**
*
* @var AuthorizationCheckerInterface
*/
public $authorizationChecker;
public function __construct(
CountNotificationTask $counter,
TokenStorageInterface $tokenStorage,
TranslatorInterface $translator,
AuthorizationCheckerInterface $authorizationChecker
) {
$this->counter = $counter;
$this->tokenStorage = $tokenStorage;
$this->translator = $translator;
$this->authorizationChecker = $authorizationChecker;
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
if ($this->authorizationChecker->isGranted('ROLE_USER')){
$menu->addChild("My aside activities", [
'route' => 'chill_crud_aside_activity_index'
])
->setExtras([
'order' => 10,
'icon' => 'tasks'
]);
}
}
public static function getMenuIds(): array
{
return [ 'user' ];
}
}

View File

@@ -1,14 +1,12 @@
{% extends "@ChillMain/Admin/layoutWithVerticalMenu.html.twig" %}
{% block vertical_menu_content %}
{{ chill_menu('admin_aside_activity', {
'layout': '@ChillAsideActivity/Admin/menu_asideactivity.html.twig',
}) }}
{{ chill_menu('admin_aside_activity') }}
{% endblock %}
{% block layout_wvm_content %}
{% block admin_content %}
<!-- block personcontent empty -->
<h1>{{ 'Aside activity configuration' |trans }}</h1>
<h1>{{ 'Aside activity configuration'|trans }}</h1>
{% endblock %}
{% endblock %}

View File

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

View File

@@ -0,0 +1,12 @@
{% set reversed_parents = parents|reverse %}
<span
class="chill-entity entity-aside_activity-category">
{%- for p in reversed_parents %}
<span class="parent-{{ loop.revindex0 }}">
{{ p.title|localize_translatable_string }}{{ options['default.separator'] }}
</span>
{%- endfor -%}
<span class="child">
{{ asideActivityCategory.title|localize_translatable_string }}
</span>
</span>

View File

@@ -1,8 +1,91 @@
{% extends '@ChillMain/layout.html.twig' %}
{% extends "@ChillMain/layout.html.twig" %}
{% block title %}{{ ('crud.' ~ crud_name ~ '.index.title')|trans({'%crud_name%': crud_name}) }}{% endblock %}
{% block title %}
{{ 'Aside activity list'|trans }}
{% endblock title %}
{% block content %}
{% embed '@ChillAsideActivity/AsideActivity/_index.html.twig' %}
{% endembed %}
{% endblock content %}
<div class="col-md-10 asideactivity-list">
<h2>{{ 'My aside activities'|trans }}</h2>
{% if entities|length == 0 %}
<p class="chill-no-data-statement">
{{ "There aren't any aside activities."|trans }}
<a href="{{ path('chill_crud_aside_activity_new') }}" class="btn btn-create button-small"></a>
</p>
{% else %}
<div class="flex-table">
{% for entity in entities %}
<div class="item-bloc">
<div class="item-row wrap-header">
<div class="item-col">
<h3>
{{ entity.type|chill_entity_render_box }}
</h3>
<div>
{% if entity.date %}
<span>{{ entity.date|format_date('long') }}</span>
{% endif %}
{% if entity.date %}
<span class="duration">
<i class="fa fa-fw fa-hourglass-end"></i>
{{ entity.duration|date('H:i') }}
</span>
{% endif %}
</div>
</div>
<div class="item-col">
<div class="box">
<ul class="list-content fa-ul">
<li>
<span>
<abbr class="referrer" title={{ 'Created by'|trans }}>{{ 'By'|trans }}:</abbr>
<b>{{ entity.createdBy|chill_entity_render_box }}</b>
</span>
</li>
<li>
<span>
<abbr class="referrer" title={{ 'Created for'|trans }}>{{ 'For'|trans }}:</abbr>
<b>{{ entity.agent|chill_entity_render_box }}</b>
</span>
</li>
</ul>
<div class="action">
<ul class="record_actions">
<li>
<a href="{{ chill_path_add_return_path('chill_crud_aside_activity_view', { 'id': entity.id } ) }}" class="btn btn-show btn-mini"></a>
</li>
<li>
<a href="{{ chill_path_add_return_path('chill_crud_aside_activity_edit', { 'id': entity.id }) }}" class="btn btn-update btn-mini "></a>
</li>
<li>
<a href="{{ chill_path_add_return_path('chill_crud_aside_activity_delete', { 'id': entity.id } ) }}" class="btn btn-delete btn-mini"></a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{{ chill_pagination(paginator) }}
<ul class="record_actions">
<li>
<a href="{{ chill_path_add_return_path('chill_crud_aside_activity_new') }}" class="btn btn-create">
{{ 'Create'|trans }}
</a>
</li>
</ul>
{% endif %}
{% endblock %}

View File

@@ -1,97 +0,0 @@
{% extends "@ChillMain/layout.html.twig" %}
{% block title %}
{{ 'Aside activity list' |trans }}
{% endblock title %}
{% block content %}
<div class="col-md-10 col-xxl asideactivity-list">
<h2>{{ 'My aside activities' |trans }}</h2>
{% if entities|length == 0 %}
<p class="chill-no-data-statement">
{{ "There aren't any aside activities."|trans }}
<a href="{{ path('chill_crud_aside_activity_new') }}" class="btn btn-create button-small"></a>
</p>
{% else %}
<div
class="flex-table my-4 list-records">
{# Sort activities according to date in descending order #}
{% for entity in entities %}
{% set t = entity.type %}
<div class="item-bloc">
<div class="item-row main">
<div class="item-col">
<h3>
<b>{{ entity.type.title | localize_translatable_string }}</b>
</h3>
{% if entity.date %}
<p>{{ entity.date|format_date('long') }}</p>
{% endif %}
<div class="duration">
<p>
<i class="fa fa-fw fa-hourglass-end"></i>
{{ entity.duration|date('H:i') }}
</p>
</div>
</div>
<div class="item-col">
<ul class="list-content">
{% if entity.createdBy %}
<li>
<b>{{ 'Created by: '|trans }}{{ entity.createdBy.usernameCanonical }}</b>
</li>
{% endif %}
</ul>
</div>
</div>
{# {%
if entity.note is not empty
or entity.createdBy|length > 0
%}
<div class="item-row details">
{% if entity.note is not empty %}
<div class="item-col comment">
{{ entity.note|chill_markdown_to_html }}
</div>
{% endif %}
</div>
{% endif %} #}
<div class="item-col">
<ul class="list-content">
<ul class="record_actions">
<li>
<a href="{{ chill_path_add_return_path('chill_crud_aside_activity_edit', { 'id': entity.id }) }}" class="btn btn-update btn-mini "></a>
</li>
<li>
<a href="{{ chill_path_add_return_path('chill_crud_aside_activity_delete', { 'id': entity.id } ) }}" class="btn btn-delete btn-mini"></a>
</li>
</ul>
</ul>
</div>
</div>
{% endfor %}
</div>
{{ chill_pagination(paginator) }}
<ul class="record_actions">
<li>
<a href="{{ chill_path_add_return_path('chill_crud_aside_activity_new') }}" class="btn btn-create">
{{ 'Create' | trans }}
</a>
</li>
</ul>
</div>
{% endif %}
{% endblock %}

View File

@@ -3,13 +3,50 @@
{% set activeRouteKey = '' %}
{% block title %}
{% include('@ChillMain/CRUD/_view_title.html.twig') %}
{% include('@ChillMain/CRUD/_view_title.html.twig') %}
{% endblock %}
{% block content %}
{% embed '@ChillMain/CRUD/_view_content.html.twig' %}
{% block crud_content_header %}
<h1>{{ ('crud.' ~ crud_name ~ '.title_view')|trans }}</h1>
{% endblock crud_content_header %}
{% endembed %}
{% endblock %}
{% embed '@ChillMain/CRUD/_view_content.html.twig' %}
{% block crud_content_header %}
<h1>{{ ('crud.' ~ crud_name ~ '.title_view')|trans }}</h1>
{% endblock crud_content_header %}
{% block crud_content_view_details %}
<dl class="chill_view_data">
<dt class="inline">{{ 'Type'|trans }}</dt>
<dd>{{ entity.type|chill_entity_render_box }}</dd>
<dt class="inline">{{ 'Created by'|trans }}</dt>
<dd>{{ entity.createdBy }}</dd>
<dt class="inline">{{ 'Created for'|trans }}</dt>
<dd>{{ entity.agent }}</dd>
<h2 class="chill-red">{{ 'Activity data'|trans }}</h2>
<dt class="inline">{{ 'Date'|trans }}</dt>
<dd>{{ entity.date|format_date('long') }}</dd>
<dt class="inline">{{ 'Duration'|trans }}</dt>
<dd>{{ entity.duration|date('H:i') }}</dd>
<dt class="inline">{{ 'Remark'|trans }}</dt>
{%- if entity.note is empty -%}
<dd>
<span class="chill-no-data-statement">{{ 'No comments'|trans }}</span>
</dd>
{%- else -%}
<dd>
<section class="chill-entity entity-comment-embeddable">
<blockquote class="chill-user-quote">
<p>{{ entity.note }}</p>
</blockquote>
</section>
</dd>
{%- endif -%}
</dl>
{% endblock %}
{% endembed %}
{% endblock %}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,80 @@
<?php
namespace Chill\AsideActivityBundle\Templating\Entity;
use Chill\AsideActivityBundle\Entity\AsideActivityCategory;
use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Symfony\Component\Templating\EngineInterface;
final class CategoryRender implements ChillEntityRenderInterface
{
private TranslatableStringHelper $translatableStringHelper;
private EngineInterface $engine;
public const SEPERATOR_KEY = 'default.separator';
public const DEFAULT_ARGS = [
self::SEPERATOR_KEY => ' > '
];
public function __construct(TranslatableStringHelper $translatableStringHelper, EngineInterface $engine)
{
$this->translatableStringHelper = $translatableStringHelper;
$this->engine = $engine;
}
/**
* @param AsideActivityCategory $asideActivityCategory
*/
public function renderString($asideActivityCategory, array $options): string
{
$options = array_merge(self::DEFAULT_ARGS, $options);
$titles[] = $this->translatableStringHelper->localize($asideActivityCategory->getTitle());
while ($asideActivityCategory->hasParent()) {
$asideActivityCategory = $asideActivityCategory->getParent();
$titles[] = $this->translatableStringHelper->localize($asideActivityCategory->getTitle());
}
$titles = array_reverse($titles);
return implode($options[self::SEPERATOR_KEY], $titles);
}
/**
* @param AsideActivityCategory $asideActivityCategory
*/
public function supports($asideActivityCategory, array $options): bool
{
return $asideActivityCategory instanceof AsideActivityCategory;
}
public function buildParents(AsideActivityCategory $asideActivityCategory)
{
$parents = [];
while($asideActivityCategory->hasParent()) {
$asideActivityCategory = $parents[] = $asideActivityCategory->getParent();
}
return $parents;
}
/**
* @param AsideActivityCategory $asideActivityCategory
*/
public function renderBox($asideActivityCategory, array $options): string
{
$options = array_merge(self::DEFAULT_ARGS, $options);
$parents = $this->buildParents($asideActivityCategory);
return $this->engine->render('@ChillAsideActivity/Entity/asideActivityCategory.html.twig',
[
'asideActivityCategory' => $asideActivityCategory,
'parents' => $parents,
'options' => $options
]);
}
}

View File

@@ -1,5 +1,22 @@
services:
Chill\AsideActivityBundle\DataFixtures\:
resource: './../DataFixtures'
autowire: true
autoconfigure: true
Chill\AsideActivityBundle\DataFixtures\:
resource: "./../DataFixtures"
autowire: true
autoconfigure: true
Chill\AsideActivityBundle\Templating\Entity\:
autowire: true
autoconfigure: true
resource: "../Templating/Entity"
tags:
- "chill.render_entity"
Chill\AsideActivityBundle\Repository\:
resource: "../Repository"
autowire: true
autoconfigure: true
Chill\AsideActivityBundle\Controller\:
resource: "../Controller"
autowire: true
autoconfigure: true

View File

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

View File

@@ -7,14 +7,11 @@ namespace Chill\Migrations\AsideActivity;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20210706124644 extends AbstractMigration
{
public function getDescription(): string
{
return '';
return 'Aside activity category entity created';
}
public function up(Schema $schema): void

View File

@@ -7,14 +7,11 @@ namespace Chill\Migrations\AsideActivity;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20210804082249 extends AbstractMigration
{
public function getDescription(): string
{
return '';
return 'Aside activity entity created';
}
public function up(Schema $schema): void
@@ -59,12 +56,12 @@ final class Version20210804082249 extends AbstractMigration
$this->addSql('CREATE INDEX idx_e9fa219165ff1aec ON asideactivity (updatedby_id)');
$this->addSql('CREATE INDEX idx_e9fa21913414710b ON asideactivity (agent_id)');
$this->addSql('COMMENT ON COLUMN asideactivity.updatedat IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('CREATE TABLE chill_main_address_legacy (id INT DEFAULT NULL, postcode_id INT DEFAULT NULL, street VARCHAR(255) DEFAULT NULL, streetnumber VARCHAR(255) DEFAULT NULL, validfrom DATE DEFAULT NULL, isnoaddress BOOLEAN DEFAULT NULL, customs JSONB DEFAULT NULL, floor VARCHAR(16) DEFAULT NULL, corridor VARCHAR(16) DEFAULT NULL, steps VARCHAR(16) DEFAULT NULL, buildingname VARCHAR(255) DEFAULT NULL, flat VARCHAR(16) DEFAULT NULL, distribution VARCHAR(255) DEFAULT NULL, extra VARCHAR(255) DEFAULT NULL, validto DATE DEFAULT NULL, point VARCHAR(255) DEFAULT NULL, linkedtothirdparty_id INT DEFAULT NULL)');
$this->addSql('ALTER TABLE asideactivity ADD CONSTRAINT fk_e9fa2191c54c8c93 FOREIGN KEY (type_id) REFERENCES asideactivitytype (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE asideactivity ADD CONSTRAINT fk_e9fa21913174800f FOREIGN KEY (createdby_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE asideactivity ADD CONSTRAINT fk_e9fa219165ff1aec FOREIGN KEY (updatedby_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE asideactivity ADD CONSTRAINT fk_e9fa21913414710b FOREIGN KEY (agent_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('DROP TABLE chill_asideactivity.AsideActivity');
$this->addSql('DROP TABLE chill_asideactivity.AsideActivityCategory');
$this->addSql('DROP SCHEMA chill_asideactivity');
}
}

View File

@@ -7,14 +7,11 @@ namespace Chill\Migrations\AsideActivity;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20210806140343 extends AbstractMigration
{
public function getDescription(): string
{
return '';
return 'Duration changed to type integer';
}
public function up(Schema $schema): void

View File

@@ -7,14 +7,11 @@ namespace Chill\Migrations\AsideActivity;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20210806140710 extends AbstractMigration
{
public function getDescription(): string
{
return '';
return 'Duration changed back to timestamp';
}
public function up(Schema $schema): void

View File

@@ -7,14 +7,11 @@ namespace Chill\Migrations\AsideActivity;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20210810084456 extends AbstractMigration
{
public function getDescription(): string
{
return '';
return 'createdat, updatedat and date given a type timestamp';
}
public function up(Schema $schema): void

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\AsideActivity;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20210922182907 extends AbstractMigration
{
public function getDescription(): string
{
return 'Parent and children added to aside activity category entity';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_asideactivity.asideactivitycategory ADD parent_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_asideactivity.asideactivitycategory ADD CONSTRAINT FK_7BF90DBE727ACA70 FOREIGN KEY (parent_id) REFERENCES chill_asideactivity.AsideActivityCategory (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_7BF90DBE727ACA70 ON chill_asideactivity.asideactivitycategory (parent_id)');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_asideactivity.AsideActivityCategory DROP parent');
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\AsideActivity;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* allow to add an ordering to aside activity categories
*/
final class Version20211004134012 extends AbstractMigration
{
public function getDescription(): string
{
return 'allow to add an ordering to aside activity categories';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_asideactivity.asideactivitycategory ADD ordering DOUBLE PRECISION NOT NULL DEFAULT 0.00');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_asideactivity.asideactivitycategory DROP ordering');
}
}

View File

@@ -14,11 +14,8 @@ present: présent
not present: absent
Delete: Supprimer
Update: Mettre à jour
Update activity: Modifier l'activité
Aside activity data: Données de l'activité annexe
No reason associated: Aucun sujet
There aren't any aside activities.: Aucune activité annexe enregistrée.
type_name: type de l'activité
Type: Type
Invisible: Invisible
Optional: Optionnel
@@ -43,14 +40,8 @@ crud:
title_edit: Edition d'une catégorie de type d'activité
#forms
Activity creation: Nouvelle activité annexe
Create a new aside activity type: Nouvelle catégorie d'activité annexe
Create: Créer
Create a new aside activity type: Nouvelle categorie d'activité annexe
Back to the list: Retour à la liste
Save activity: Sauver l'activité
Reset form: Remise à zéro du formulaire
Choose the agent for whom this activity is created: Choisissez l'utilisateur pour qui l'activité est créée.
Choose the activity category: Choisissez le type d'activité
Choose the duration: Choisir la durée
Choose a category: Choisir une catégorie
Is active: Actif
@@ -58,12 +49,106 @@ 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%" ?
@@ -73,3 +158,4 @@ The activity has been successfully removed.: L'activité a été supprimée.
Create an aside activity: "Créer une activité annexe"
Aside activity configuration menu: "Menu de configuration des activités annexes"
Aside activity configuration: "Configuration des activités annexes"
Phonecall: "Appel téléphonique"

View File

@@ -0,0 +1 @@
You must not add twice the same category in the parent tree (previous result returned): Il est interdit d'indiquer la même entité dans l'arbre des parents. Le résultat précédent a été rétabli

View File

@@ -29,17 +29,19 @@ class ListenToActivityCreate
$activityData = $request->query->get('activityData');
if (array_key_exists('calendarId', $activityData)) {
$calendarId = $activityData['calendarId'];
// Attach the activity to the calendar
$em = $event->getObjectManager();
$calendar = $em->getRepository(\Chill\CalendarBundle\Entity\Calendar::class)->find($calendarId);
$calendar->setActivity($activity);
$em->persist($calendar);
$em->flush();
}
}
// Attach the activity to the calendar
$em = $event->getObjectManager();
$calendar = $em->getRepository(\Chill\CalendarBundle\Entity\Calendar::class)->find($calendarId);
$calendar->setActivity($activity);
$em->persist($calendar);
$em->flush();
}
}

View File

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

View File

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

View File

@@ -7,7 +7,10 @@ namespace Chill\DocStoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use ChampsLibres\AsyncUploaderBundle\Model\AsyncFileInterface;
use ChampsLibres\AsyncUploaderBundle\Validator\Constraints\AsyncFileExists;
use ChampsLibres\WopiLib\Contract\Entity\Document;
use DateTimeInterface;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Symfony\Component\Serializer\Annotation as Serializer;
/**
@@ -19,7 +22,7 @@ use Symfony\Component\Serializer\Annotation as Serializer;
* message="The file is not stored properly"
* )
*/
class StoredObject implements AsyncFileInterface
class StoredObject implements AsyncFileInterface, Document
{
/**
* @ORM\Id()
@@ -47,6 +50,12 @@ class StoredObject implements AsyncFileInterface
*/
private array $iv = [];
/**
* @ORM\Column(type="uuid", unique=true)
* @Serializer\Groups({"read"})
*/
private UuidInterface $uuid;
/**
* @ORM\Column(type="datetime", name="creation_date")
* @Serializer\Groups({"read"})
@@ -68,6 +77,7 @@ class StoredObject implements AsyncFileInterface
public function __construct()
{
$this->creationDate = new \DateTime();
$this->uuid = Uuid::uuid4();
}
public function getId()
@@ -155,5 +165,13 @@ class StoredObject implements AsyncFileInterface
return $this;
}
public function getUuid(): UuidInterface
{
return $this->uuid;
}
public function getWopiDocId(): string
{
return (string) $this->uuid;
}
}

View File

@@ -3,9 +3,12 @@
"description": "A Chill bundle to store documents",
"type": "symfony-bundle",
"autoload": {
"psr-4": { "Chill\\DocStoreBundle\\" : "" }
"psr-4": {
"Chill\\DocStoreBundle\\": ""
}
},
"require": {
"symfony/mime": "^4 || ^5"
},
"license": "AGPL-3.0"
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\DocStore;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20210928182542 extends AbstractMigration
{
public function getDescription(): string
{
return 'Create UUID column on StoredObject table.';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"');
$this->addSql('ALTER TABLE chill_doc.stored_object ADD uuid UUID DEFAULT NULL');
$this->addSql('UPDATE chill_doc.stored_object SET uuid=uuid_generate_v4()');
$this->addSql('ALTER TABLE chill_doc.stored_object ALTER uuid SET NOT NULL');
$this->addSql('CREATE UNIQUE INDEX UNIQ_49604E36D17F50A6 ON chill_doc.stored_object (uuid)');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_doc.stored_object DROP uuid');
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Entity\Address;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
class AddressApiController extends ApiController
{
/**
* Duplicate an existing address
*
* @Route("/api/1.0/main/address/{id}/duplicate.json", name="chill_api_main_address_duplicate",
* methods={"POST"})
*
* @param Address $address
*/
public function duplicate(Address $address): JsonResponse
{
$this->denyAccessUnlessGranted('ROLE_USER');
$new = Address::createFromAddress($address);
$em = $this->getDoctrine()->getManager();
$em->persist($new);
$em->flush();
return $this->json($new, Response::HTTP_OK, [], [
AbstractNormalizer::GROUPS => ['read']
]);
}
}

View File

@@ -19,6 +19,7 @@
namespace Chill\MainBundle\DependencyInjection;
use Chill\MainBundle\Controller\AddressApiController;
use Chill\MainBundle\Controller\UserController;
use Chill\MainBundle\Doctrine\DQL\STContains;
use Chill\MainBundle\Doctrine\DQL\StrictWordSimilarityOPS;
@@ -319,6 +320,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
'apis' => [
[
'class' => \Chill\MainBundle\Entity\Address::class,
'controller' => AddressApiController::class,
'name' => 'address',
'base_path' => '/api/1.0/main/address',
'base_role' => 'ROLE_USER',

View File

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

View File

@@ -76,10 +76,7 @@ class CenterType extends AbstractType
{
$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) {
if ($nbReachableCenters <= 1) {
return HiddenType::class;
} else {
return EntityType::class;

View File

@@ -7,6 +7,7 @@ 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
@@ -47,4 +48,9 @@ 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

@@ -8,39 +8,45 @@
div.flex-bloc,
div.flex-table {
display: flex;
align-items: stretch;
align-content: stretch;
box-sizing: border-box;
margin: 1.5em 0;
div.item-bloc {
display: flex;
align-items: stretch;
align-content: stretch;
box-sizing: border-box;
margin: 1.5em 0;
@include border-collapse;
padding: 1em;
div.item-bloc {
div.item-row {
display: flex;
div.item-col:last-child {
display: flex;
@include border-collapse;
padding: 1em;
div.item-row {
display: flex;
div.item-col:last-child {
display: flex;
}
}
}
}
}
h2, h3, h4, dl, p {
margin: 0;
}
h2, h3, h4 {
color: $blue;
}
h2,
h3,
h4,
dl,
p {
margin: 0;
}
h2,
h3,
h4 {
color: $blue;
}
ul.record_actions {
margin: 0;
li {
margin-right: 5px;
}
ul.record_actions {
margin: 0;
li {
margin-right: 5px;
}
}
}
/*
@@ -48,36 +54,43 @@ div.flex-table {
*/
div.flex-bloc {
flex-direction: row;
flex-wrap: wrap;
flex-direction: row;
flex-wrap: wrap;
div.item-bloc {
flex-grow: 0; flex-shrink: 1; flex-basis: auto;
flex-direction: column;
margin: 0;
hyphens: auto;
div.item-bloc {
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
flex-direction: column;
margin: 0;
hyphens: auto;
div.item-row {
flex-grow: 1; flex-shrink: 1; flex-basis: auto;
flex-direction: column;
div.item-row {
flex-grow: 1;
flex-shrink: 1;
flex-basis: auto;
flex-direction: column;
div.item-col {
&:first-child {
flex-grow: 0; flex-shrink: 0; flex-basis: auto;
}
&:last-child {
flex-grow: 1; flex-shrink: 1; flex-basis: auto;
@include separator;
ul.record_actions {
align-self: flex-end;
}
}
}
div.item-col {
&:first-child {
flex-grow: 0;
flex-shrink: 0;
flex-basis: auto;
}
&:last-child {
flex-grow: 1;
flex-shrink: 1;
flex-basis: auto;
@include separator;
ul.record_actions {
align-self: flex-end;
}
}
}
}
}
}
/*
@@ -85,53 +98,57 @@ div.flex-bloc {
*/
div.flex-table {
flex-direction: column;
div.item-bloc {
flex-direction: column;
div.item-bloc {
flex-direction: column;
&:nth-child(even) {
background-color: $gray-200;
&:nth-child(even) {
background-color: $gray-200;
.chill-user-quote {
background-color: shade-color($gray-200, 5%)
}
}
div.item-row {
flex-direction: row;
div.item-col {
&:first-child {
flex-grow: 0; flex-shrink: 0; flex-basis: auto;
}
&:last-child {
flex-grow: 1; flex-shrink: 1; flex-basis: auto;
justify-content: flex-end;
@include media-breakpoint-down(md) {
@include separator;
}
ul.record_actions {
align-self: flex-start;
}
}
}
@include media-breakpoint-down(md) {
flex-direction: column;
div.item-col {
&:last-child {
ul.record_actions {
align-self: flex-end;
}
}
}
}
}
.chill-user-quote {
background-color: shade-color($gray-200, 5%);
}
}
div.item-row {
flex-direction: row;
div.item-col {
&:first-child {
flex-grow: 0;
flex-shrink: 0;
flex-basis: auto;
}
&:last-child {
flex-grow: 1;
flex-shrink: 1;
flex-basis: auto;
justify-content: flex-end;
@include media-breakpoint-down(md) {
@include separator;
}
ul.record_actions {
align-self: flex-start;
}
}
}
@include media-breakpoint-down(md) {
flex-direction: column;
div.item-col {
&:last-child {
ul.record_actions {
align-self: flex-end;
}
}
}
}
}
}
}
/*
@@ -140,54 +157,54 @@ div.flex-table {
*/
div.wrap-list {
padding: 0;
width: 100%;
padding: 0;
width: 100%;
div.wl-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
div.wl-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
div.wl-col.title {
width: auto;
flex-shrink: 0;
div.wl-col.title {
width: auto;
flex-shrink: 0;
@include media-breakpoint-up(md) {
//margin-left: 1.5em;
}
@include media-breakpoint-up(md) {
//margin-left: 1.5em;
}
& > * {
padding-right: 1em;
}
}
div.wl-col.list {
width: 75%;
margin: auto 0 0 auto;
.wl-item {
margin: 0.1em;
padding: 0em;
display: inline-block;
}
}
& > * {
padding-right: 1em;
}
}
&.debug .wl-row {
border: 1px solid $black;
div.wl-col.list {
width: 75%;
margin: auto 0 0 auto;
div.wl-col.title {
background-color: yellow;
}
div.wl-col.list {
background-color: cyan;
p.wl-item {
background-color: orange;
}
}
.wl-item {
margin: 0.1em;
padding: 0em;
display: inline-block;
}
}
}
&.debug .wl-row {
border: 1px solid $black;
div.wl-col.title {
background-color: yellow;
}
div.wl-col.list {
background-color: cyan;
p.wl-item {
background-color: orange;
}
}
}
}
/*
@@ -196,43 +213,56 @@ div.wrap-list {
*/
div.wrap-header {
width: 100%;
width: 100%;
div.wh-row {
display: flex;
flex-direction: row;
&:first-child {
align-items: baseline;
}
&:last-child {
}
div.wh-col {
&:first-child {
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
}
&:last-child {
flex-grow: 1;
flex-shrink: 1;
flex-basis: auto;
div.wh-row {
display: flex;
flex-direction: row;
justify-content: flex-end;
}
}
}
&.debug {
border: 1px solid $black;
div.wh-row {
&:first-child div.wh-col {
&:first-child {
align-items: baseline;
background-color: $yellow;
}
&:last-child {}
div.wh-col {
&:first-child {
flex-grow: 0; flex-shrink: 1; flex-basis: auto;
}
&:last-child {
flex-grow: 1; flex-shrink: 1; flex-basis: auto;
display: flex;
justify-content: flex-end;
}
}
}
&.debug {
border: 1px solid $black;
div.wh-row {
&:first-child div.wh-col {
&:first-child { background-color: $yellow; }
&:last-child { background-color: $beige; }
}
&:last-child div.wh-col {
&:first-child { background-color: $orange; }
&:last-child { background-color: $pink; }
}
&:last-child {
background-color: $beige;
}
}
&:last-child div.wh-col {
&:first-child {
background-color: $orange;
}
&:last-child {
background-color: $pink;
}
}
}
}
}
/*
@@ -243,69 +273,67 @@ div.flex-bloc,
div.flex-table,
div.wrap-list,
div.wrap-header {
div.separator {
@include separator;
}
div.separator {
@include separator;
}
ul.record_actions {
flex-grow: 1;
flex-shrink: 0;
flex-basis: auto;
margin: 0;
li {
margin-right: 5px;
}
ul.record_actions {
flex-grow: 1;
flex-shrink: 0;
flex-basis: auto;
margin: 0;
li {
margin-right: 5px;
}
}
}
/*
* FLOATBUTTON
* p-ê pas convaincant: cet asset est toujours en observation
*/
div.float-button {
width: 100%;
div.box {
width: 100%;
div.box {
width: 100%;
div.action {
float: right;
}
}
&.top {
div.action {
padding: 0 0 1em 1em;
}
div.action {
float: right;
}
// avoid a position relative that make links unclickable
.fa-ul > li {
position: initial;
}
&.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% - 2em) 0 0);
display: flex;
align-items: flex-end;
padding: 0 0 0 1em;
* {
align-self: flex-end !important;
}
}
&.bottom {
display: flex;
overflow: hidden;
div.action {
height: calc(100% - 0em);
shape-outside: inset(calc(100% - 2em) 0 0);
display: flex;
align-items: flex-end;
padding: 0 0 0 1em;
* {
align-self: flex-end !important;
}
}
}
&.debug {
padding: 1em;
border: 1px solid black;
background-color: yellow;
div.action {
background-color: transparentize(#00ffff, 0.4);
}
}
&.debug {
padding: 1em;
border: 1px solid black;
background-color: yellow;
div.action {
background-color: transparentize(#00ffff, 0.4);
}
}
}

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

@@ -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,8 +81,28 @@ 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
*
@@ -142,6 +162,7 @@ const getAddress = (id) => {
};
export {
duplicateAddress,
fetchCountries,
fetchCities,
fetchReferenceAddresses,

View File

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

View File

@@ -34,6 +34,7 @@
v-bind:defaultz="this.defaultz"
v-bind:entity="this.entity"
v-bind:flag="this.flag"
@pick-address="this.pickAddress"
ref="suggestAddress">
</suggest-pane>
</template>
@@ -55,6 +56,7 @@
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">
@@ -217,7 +219,16 @@
<script>
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
import { getAddress, fetchCountries, fetchCities, fetchReferenceAddresses, patchAddress, postAddress, postPostalCode } from '../api';
import {
duplicateAddress,
fetchCountries,
fetchCities,
fetchReferenceAddresses,
getAddress,
patchAddress,
postAddress,
postPostalCode,
} from '../api';
import { postAddressToPerson, postAddressToHousehold } from "ChillPersonAssets/vuejs/_api/AddAddress.js";
import ShowPane from './ShowPane.vue';
import SuggestPane from './SuggestPane.vue';
@@ -234,6 +245,9 @@ export default {
EditPane,
DatePane
},
emits: {
pickAddress: null
},
data() {
return {
flag: {
@@ -257,7 +271,7 @@ export default {
validFrom: false,
validTo: false
},
hideAddress: false
onlyButton: false
},
entity: {
address: {}, // <== loaded and returned
@@ -311,8 +325,10 @@ export default {
return (this.validFrom || this.validTo) ? true : false;
},
hasSuggestions() {
// TODO
//return addressSuggestions.length > 0
if (typeof(this.context.suggestions) !== 'undefined') {
console.log('hasSuggestions', this.context.suggestions);
return this.context.suggestions.length > 0;
}
return false;
},
displaySuggestions() {
@@ -337,9 +353,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) {
@@ -590,7 +606,7 @@ export default {
let newPostcode = this.entity.selected.postcode;
newPostcode = Object.assign(newPostcode, {
'country': {'id': this.entity.selected.country.id },
});
});//TODO why not assign postcodeBody here = Object.assign(postcodeBody, {'origin': 3}); ?
console.log('writeNew postcode is true! newPostcode: ', newPostcode);
newAddress = Object.assign(newAddress, {
'newPostcode': newPostcode
@@ -622,9 +638,7 @@ export default {
if ('newPostcode' in payload) {
let postcodeBody = payload.newPostcode;
if (this.context.target.name === 'person') { // !!! maintain here ?
postcodeBody = Object.assign(postcodeBody, {'origin': 3});
}
postcodeBody = Object.assign(postcodeBody, {'origin': 3});
console.log('juste before post new postcode', postcodeBody);
return postPostalCode(postcodeBody)
.then(postalCode => {
@@ -647,9 +661,12 @@ 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,
}
);
}))
@@ -695,6 +712,9 @@ 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
@@ -707,18 +727,28 @@ export default {
});
},
/*
* 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);
}
/**
*
* @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();
});
}
}
}
</script>

View File

@@ -50,7 +50,7 @@ import VueMultiselect from 'vue-multiselect';
export default {
name: 'CitySelection',
components: { VueMultiselect },
props: ['entity', 'focusOnAddress'],
props: ['entity', 'focusOnAddress', 'updateMapCenter'],
emits: ['getReferenceAddresses'],
data() {
return {
@@ -95,6 +95,7 @@ export default {
return (value.code && value.name) ? `${value.code}-${value.name}` : '';
},
selectCity(value) {
console.log(value)
this.entity.selected.city = value;
this.entity.selected.postcode.name = value.name;
this.entity.selected.postcode.code = value.code;
@@ -102,6 +103,7 @@ export default {
console.log('writeNew.postcode false, in selectCity');
this.$emit('getReferenceAddresses', value);
this.focusOnAddress();
this.updateMapCenter(value.center);
},
listenInputSearch(query) {
//console.log('listenInputSearch', query, this.isCitySelectorOpen);

View File

@@ -31,6 +31,7 @@
<city-selection
v-bind:entity="entity"
v-bind:focusOnAddress="focusOnAddress"
v-bind:updateMapCenter="updateMapCenter"
@getReferenceAddresses="$emit('getReferenceAddresses', selected.city)">
</city-selection>
@@ -135,7 +136,7 @@ export default {
}
},
updateMapCenter(point) {
//console.log('point', point);
console.log('point', point);
this.addressMap.center[0] = point.coordinates[1]; // TODO use reverse()
this.addressMap.center[1] = point.coordinates[0];
this.$refs.addressMap.update(); // cast child methods

View File

@@ -1,6 +1,6 @@
<template>
<div v-if="!hideAddress">
<div v-if="!onlyButton">
<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>
@@ -28,13 +28,11 @@
:options="this.options"
:defaultz="this.defaultz">
<template v-slot:action>
<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>
<button @click.prevent="$emit('openEditPane')"
class="btn" :class="getClassButton"
type="button" name="button" :title="$t(getTextButton)">
<span v-if="displayTextButton">{{ $t(getTextButton) }}</span>
</button>
</template>
</action-buttons>
@@ -86,9 +84,9 @@ export default {
getSuccessText() {
return (this.context.edit) ? 'address_edit_success' : 'address_new_success';
},
hideAddress() {
return (typeof this.options.hideAddress !== 'undefined') ?
this.options.hideAddress : this.defaultz.hideAddress;
onlyButton() {
return (typeof this.options.onlyButton !== 'undefined') ?
this.options.onlyButton : this.defaultz.onlyButton;
},
forceRedirect() {
return (!(this.context.backUrl === null || typeof this.context.backUrl === 'undefined'));

View File

@@ -10,13 +10,14 @@
<h4 class="h3">{{ $t('address_suggestions') }}</h4>
<div class="flex-table AddressSuggestionList">
<div class="item-bloc">
<div v-for="a in context.suggestions" 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">
<button class="btn btn-sm btn-choose" @click="this.pickAddress(a)">
{{ $t('use_this_address') }}
</button>
</li>
@@ -25,9 +26,7 @@
<ul class="list-content fa-ul">
<li>
<i class="fa fa-li fa-map-marker"></i>
<!--
<address-render-box></address-render-box>
-->
<address-render-box :address="a"></address-render-box>
</li>
</ul>
</div>
@@ -68,9 +67,14 @@ export default {
'flag',
'entity',
'errorMsg',
'insideModal'
'insideModal',
],
computed: {},
methods: {}
methods: {
pickAddress(address) {
console.log('pickAddress in suggest pane', address);
this.$emit('pickAddress', address);
},
}
}
</script>

View File

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

View File

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

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

View File

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

View File

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

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

@@ -53,24 +53,6 @@ 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)
* hideAddress bool (default: false)
* onlyButton bool (default: false)
#}
<div class="address-container"
@@ -69,7 +69,7 @@
data-use-valid-to="true"
{% endif %}
{% if hideAddress is defined and hideAddress == 1 %}
{% if onlyButton is defined and onlyButton == 1 %}
data-hide-address="true"
{% endif %}
></div>

View File

@@ -1,5 +1,5 @@
{#
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
<info@champs-libres.coop> / <http://www.champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
@@ -17,7 +17,7 @@
#}
<ul>
{% for route in routes %}
<li><a href="{{ path(route.key, args ) }}" class="{%- if activeRouteKey == route.key -%}active{%- endif -%}">{{ route.label }}</a></li>
{% for menu in menus %}
<li><a href="{{ menu.uri }}">{{ menu.label }}</a></li>
{% endfor %}
</ul>
</ul>

View File

@@ -0,0 +1,37 @@
{#
This Twig template include load vue_onthefly component.
It push all variables from context in OnTheFly/App.vue.
OPTIONS
* targetEntity {
name: string 'person', 'thirdparty'
id: integer
}
* action string 'show', 'edit', 'create'
* buttonText string
* displayBadge boolean (default: false) replace button by badge, need to define buttonText for content
#}
<span class="onthefly-container"
data-target-name="{{ targetEntity.name|e('html_attr') }}"
data-target-id="{{ targetEntity.id|e('html_attr') }}"
{% if action is defined %}
data-action="{{ action|e('html_attr') }}"
{% else %}
data-action="show"
{% endif %}
{% if buttonText is defined %}
data-button-text="{{ buttonText|e('html_attr') }}"
{% endif %}
{% if displayBadge is defined and displayBadge == 1 %}
data-display-badge="true"
{% endif %}
></span>
{{ encore_entry_script_tags('vue_onthefly') }}
{{ encore_entry_link_tags('vue_onthefly') }}

View File

@@ -10,7 +10,7 @@
{% endblock %}
{% block table_entities_tbody %}
{% for entity in entities %}
<tr>
<tr data-username="{{ entity.username|e('html_attr') }}">
<td>
{% if entity.isEnabled %}
<i class="fa fa-check chill-green"></i>

View File

@@ -70,6 +70,9 @@
</center>
</form>
</div>
<div class="col-8">
<a href="{{ path('chill_crud_aside_activity_new', {'type' : 7, 'duration' : '600', 'note' : 'Pas des remarques' }) }}"><div class="bloc btn btn-success btn-md btn-block">Appel téléphonique</div></a>
</div>
{{ chill_widget('homepage', {} ) }}
{% endblock %}

View File

@@ -17,32 +17,31 @@
*/
namespace Chill\MainBundle\Test;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
/**
* Prepare a client authenticated with a user
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
* Prepare a client authenticated with a user
*/
trait PrepareClientTrait
{
/**
* Create a new client with authentication information.
*
*
* @param string $username the username (default 'center a_social')
* @param string $password the password (default 'password')
* @return \Symfony\Component\BrowserKit\Client
* @throws \LogicException
*/
public function getClientAuthenticated(
$username = 'center a_social',
$username = 'center a_social',
$password = 'password'
) {
): KernelBrowser {
if (!$this instanceof WebTestCase) {
throw new \LogicException(sprintf("The current class does not "
. "implements %s", WebTestCase::class));
}
return static::createClient(array(), array(
'PHP_AUTH_USER' => $username,
'PHP_AUTH_PW' => $password,

View File

@@ -0,0 +1,49 @@
<?php
namespace Controller;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Repository\AddressRepository;
use Chill\MainBundle\Test\PrepareClientTrait;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
class AddressControllerTest extends \Symfony\Bundle\FrameworkBundle\Test\WebTestCase
{
private KernelBrowser $client;
use PrepareClientTrait;
public function setUp()
{
self::bootKernel();
$this->client = $this->getClientAuthenticated();
}
/**
* @dataProvider generateAddressIds
* @param int $addressId
*/
public function testDuplicate(int $addressId)
{
$this->client->request('POST', "/api/1.0/main/address/$addressId/duplicate.json");
$this->assertResponseIsSuccessful('test that duplicate is successful');
}
public function generateAddressIds()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
$qb = $em->createQueryBuilder();
$addresses = $qb->select('a')->from(Address::class, 'a')
->setMaxResults(2)
->getQuery()
->getResult();
foreach ($addresses as $a) {
yield [ $a->getId() ];
}
}
}

View File

@@ -2,12 +2,17 @@
namespace Chill\MainBundle\Tests\Controller;
use Chill\MainBundle\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class UserControllerTest extends WebTestCase
{
private $client;
private array $toDelete = [];
public function setUp()
{
self::bootKernel();
@@ -22,18 +27,14 @@ class UserControllerTest extends WebTestCase
public function testList()
{
// get the list
$crawler = $this->client->request('GET', '/fr/admin/user/');
$crawler = $this->client->request('GET', '/fr/admin/main/user');
$this->assertEquals(200, $this->client->getResponse()->getStatusCode(),
"Unexpected HTTP status code for GET /admin/user/");
$link = $crawler->selectLink('Ajouter un nouvel utilisateur')->link();
$this->assertInstanceOf('Symfony\Component\DomCrawler\Link', $link);
$this->assertRegExp('|/fr/admin/user/new$|', $link->getUri());
"Unexpected HTTP status code for GET /admin/main/user");
}
public function testNew()
{
$crawler = $this->client->request('GET', '/fr/admin/user/new');
$crawler = $this->client->request('GET', '/fr/admin/main/user/new');
$username = 'Test_user'. uniqid();
$password = 'Password1234!';
@@ -54,22 +55,15 @@ class UserControllerTest extends WebTestCase
$this->assertGreaterThan(0, $crawler->filter('td:contains("Test_user")')->count(),
'Missing element td:contains("Test user")');
$update = $crawler->selectLink('Modifier')->link();
$this->assertInstanceOf('Symfony\Component\DomCrawler\Link', $update);
$this->assertRegExp('|/fr/admin/user/[0-9]{1,}/edit$|', $update->getUri());
//test the auth of the new client
$this->isPasswordValid($username, $password);
return $update;
}
protected function isPasswordValid($username, $password)
{
/* @var $passwordEncoder \Symfony\Component\Security\Core\Encoder\UserPasswordEncoder */
$passwordEncoder = self::$kernel->getContainer()
->get('security.password_encoder');
$passwordEncoder = self::$container
->get(UserPasswordEncoderInterface::class);
$user = self::$kernel->getContainer()
->get('doctrine.orm.entity_manager')
@@ -81,46 +75,33 @@ class UserControllerTest extends WebTestCase
/**
*
* @param \Symfony\Component\DomCrawler\Link $update
* @depends testNew
* @dataProvider dataGenerateUserId
*/
public function testUpdate(\Symfony\Component\DomCrawler\Link $update)
public function testUpdate(int $userId, string $username)
{
$crawler = $this->client->click($update);
$crawler = $this->client->request('GET', "/fr/admin/main/user/$userId/edit");
$username = 'Foo bar '.uniqid();
$form = $crawler->selectButton('Mettre à jour')->form(array(
$form = $crawler->selectButton('Enregistrer & fermer')->form(array(
'chill_mainbundle_user[username]' => $username,
));
$this->client->submit($form);
$crawler = $this->client->followRedirect();
// Check the element contains an attribute with value equals "Foo"
$this->assertGreaterThan(0, $crawler->filter('[value="'.$username.'"]')->count(),
'Missing element [value="Foo bar"]');
$updatePassword = $crawler->selectLink('Modifier le mot de passe')->link();
$this->assertInstanceOf('Symfony\Component\DomCrawler\Link', $updatePassword);
$this->assertRegExp('|/fr/admin/user/[0-9]{1,}/edit_password$|',
$updatePassword->getUri());
return array('link' => $updatePassword, 'username' => $username);
$this->assertGreaterThan(0, $crawler->filter('[data-username="'.$username.'"]')->count(),
'Missing element [data-username="Foo bar"]');
}
/**
*
* @param \Symfony\Component\DomCrawler\Link $updatePassword
* @depends testUpdate
* @dataProvider dataGenerateUserId
*/
public function testUpdatePassword(array $params)
public function testUpdatePassword(int $userId, $username)
{
$link = $params['link'];
$username = $params['username'];
$crawler = $this->client->request('GET', "/fr/admin/user/$userId/edit_password");
$newPassword = '1234Password!';
$crawler = $this->client->click($link);
$form = $crawler->selectButton('Changer le mot de passe')->form(array(
'chill_mainbundle_user_password[new_password][first]' => $newPassword,
'chill_mainbundle_user_password[new_password][second]' => $newPassword,
@@ -130,10 +111,38 @@ class UserControllerTest extends WebTestCase
$this->assertTrue($this->client->getResponse()->isRedirect(),
"the response is a redirection");
$this->client->followRedirect();
$this->isPasswordValid($username, $newPassword);
}
protected function tearDown()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
foreach ($this->toDelete as list($class, $id)) {
$obj = $em->getRepository($class)->find($id);
$em->remove($obj);
}
$em->flush();
}
public function dataGenerateUserId()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
$user = new User();
$user->setUsername('Test_user '.uniqid());
$user->setPassword(self::$container->get(UserPasswordEncoderInterface::class)->encodePassword($user,
'password'));
$em->persist($user);
$em->flush();
$this->toDelete[] = [User::class, $user->getId()];
yield [ $user->getId(), $user->getUsername() ];
}
}

View File

@@ -1,54 +0,0 @@
<?php
/*
* <one line to give the program's name and a brief idea of what it does.>
* Copyright (C) 2014, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Chill\MainBundle\Tests\Services;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\DomCrawler\Crawler;
/**
* Test the Twig function 'chill_menu'
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class ChillMenuTwigFunctionTest extends KernelTestCase
{
private static $templating;
public static function setUpBeforeClass()
{
self::bootKernel(array('environment' => 'test'));
static::$templating = static::$container
->get('templating');
$pathToBundle = static::$container->getParameter('kernel.bundles_metadata')['ChillMainBundle']['path'];
//load templates in Tests/Resources/views
static::$container->get('twig.loader')
->addPath($pathToBundle.'/Resources/test/views/', $namespace = 'tests');
}
public function testNormalMenu()
{
$content = static::$templating->render('@tests/menus/normalMenu.html.twig');
$this->assertContains('ul', $content,
"test that the file contains an ul tag"
);
}
}

View File

@@ -293,6 +293,32 @@ paths:
401:
description: "Unauthorized"
/1.0/main/address/{id}/duplicate.json:
post:
tags:
- address
summary: Duplicate an existing address
parameters:
- name: id
in: path
required: true
description: The address id that will be duplicated
schema:
type: integer
format: integer
minimum: 1
responses:
200:
description: "ok"
content:
application/json:
schema:
$ref: '#/components/schemas/Address'
404:
description: "not found"
401:
description: "Unauthorized"
/1.0/main/address-reference.json:
get:
tags:

View File

@@ -61,5 +61,6 @@ module.exports = function(encore, entries)
// Vue entrypoints
encore.addEntry('vue_address', __dirname + '/Resources/public/vuejs/Address/index.js');
encore.addEntry('vue_onthefly', __dirname + '/Resources/public/vuejs/OnTheFly/index.js');
};

View File

@@ -1,7 +1,7 @@
"This program is free software: you can redistribute it and/or modify it under the terms of the <strong>GNU Affero General Public License</strong>": "Ce programme est un logiciel libre: vous pouvez le redistribuer et/ou le modifier selon les termes de la licence <strong>GNU Affero GPL</strong>"
User manual: Manuel d'utilisation
Search: Rechercher
'Search persons, ...': 'Recherche des personnes, ...'
"Search persons, ...": "Recherche des personnes, ..."
Person name: Nom / Prénom de la personne
Login: Connexion
Logout: Se déconnecter
@@ -33,7 +33,7 @@ Cancel: Annuler
Save: Enregistrer
This form contains errors: Ce formulaire contient des erreurs
Choose an user: Choisir un utilisateur
'You are going to leave a page with unsubmitted data. Are you sure you want to leave ?': "Vous allez quitter la page alors que des données n'ont pas été enregistrées. Êtes vous sûr de vouloir partir ?"
"You are going to leave a page with unsubmitted data. Are you sure you want to leave ?": "Vous allez quitter la page alors que des données n'ont pas été enregistrées. Êtes vous sûr de vouloir partir ?"
No value: Aucune information
Last updated by: Dernière mise à jour par
Last updated on: Dernière mise à jour le
@@ -71,24 +71,24 @@ Postal code: Code postal
Valid from: Valide à partir du
Choose a postal code: Choisir un code postal
address:
address_homeless: L'adresse est-elle celle d'un domicile fixe ?
real address: Adresse d'un domicile
consider homeless: N'est pas l'adresse d'un domicile (SDF)
address_homeless: L'adresse est-elle celle d'un domicile fixe ?
real address: Adresse d'un domicile
consider homeless: N'est pas l'adresse d'un domicile (SDF)
address more:
floor: ét
corridor: coul
steps: esc
flat: appart
buildingName: résidence
extra: ''
distribution: cedex
floor: ét
corridor: coul
steps: esc
flat: appart
buildingName: résidence
extra: ""
distribution: cedex
Create a new address: Créer une nouvelle adresse
#serach
Your search is empty. Please provide search terms.: La recherche est vide. Merci de fournir des termes de recherche.
The domain %domain% is unknow. Please check your search.: Le domaine de recherche "%domain%" est inconnu. Merci de vérifier votre recherche.
Invalid terms : Recherche invalide
You should not have more than one domain. : Vous ne devriez pas avoir plus d'un domaine de recherche.
Invalid terms: Recherche invalide
You should not have more than one domain.: Vous ne devriez pas avoir plus d'un domaine de recherche.
#used for page title
Search %pattern%: Recherche de "%pattern%"
Results %start%-%end% of %total%: Résultats %start%-%end% sur %total%
@@ -113,9 +113,9 @@ Permissions Menu: Gestion des droits
Permissions management of your chill installation: Gestion des permissions de votre instance
#admin section
'Administration interface': Interface d'administration
"Administration interface": Interface d'administration
Welcome to the admin section !: >
Bienvenue dans l'interface d'administration !
Bienvenue dans l'interface d'administration !
#admin section for center's administration
Create a new center: Créer un nouveau centre
@@ -210,7 +210,6 @@ Problem during download: Problème durant le téléchargement
# sans valeur
without data: sans valeur
#CSV List Formatter
Add a number on first column: La première colonne est un numéro
Number: Numéro
@@ -228,9 +227,9 @@ Comma separated values (CSV): Valeurs séparées par des virgules (CSV - tableur
Choose the format: Choisir le format
# select2
'select2.no_results': Aucun résultat
'select2.error_loading': Erreur de chargement des résultats
'select2.searching': Recherche en cours...
"select2.no_results": Aucun résultat
"select2.error_loading": Erreur de chargement des résultats
"select2.searching": Recherche en cours...
# change password
Change my password: Modification du mot de passe
@@ -258,38 +257,35 @@ Impersonate: Incarner l'utilisateur
Impersonate mode: Mode fantôme
crud:
# general items
new:
button_action_form: Créer
link_edit: Modifier
save_and_close: Créer & fermer
save_and_show: Créer & voir
save_and_new: Créer & nouveau
success: Les données ont été créées
edit:
button_action_form: Enregistrer
back_to_view: Voir
save_and_close: Enregistrer & fermer
save_and_show: Enregistrer & voir
success: Les données ont été modifiées
delete:
success: Les données ont été supprimées
link_to_form: Supprimer
default:
success: Les données ont été enregistrées
view:
link_duplicate: Dupliquer
## admin for users
admin_user:
index:
title: Utilisateurs
add_new: "Créer"
is_active: "Actif ?"
usernames: "Identifiants"
mains: "Champs principaux"
title_new: "Nouvel utilisateur"
title_edit: Modifier un utilisateur
# general items
new:
button_action_form: Créer
link_edit: Modifier
save_and_close: Créer & fermer
save_and_show: Créer & voir
save_and_new: Créer & nouveau
success: Les données ont été créées
edit:
button_action_form: Enregistrer
back_to_view: Voir
save_and_close: Enregistrer & fermer
save_and_show: Enregistrer & voir
success: Les données ont été modifiées
delete:
success: Les données ont été supprimées
link_to_form: Supprimer
default:
success: Les données ont été enregistrées
view:
link_duplicate: Dupliquer
No entities: Aucun élément
CHILL_FOO_SEE: Voir un élément
CHILL_FOO_EDIT: Modifier un élément
#Show templates
Date: Date
By: Par
For: Pour
Created for: Créé pour
Created by: Créé par

View File

@@ -118,6 +118,7 @@ class AccompanyingCourseController extends Controller
return $this->render('@ChillPerson/AccompanyingCourse/index.html.twig', [
'accompanyingCourse' => $accompanyingCourse,
'withoutHousehold' => $withoutHousehold,
'participationsByHousehold' => $accompanyingCourse->actualParticipationsByHousehold(),
'works' => $works,
'activities' => $activities
]);

View File

@@ -45,9 +45,9 @@ class PersonApiController extends ApiController
$person = parent::createEntity($action, $request);
// TODO temporary hack to allow creation of person with fake center
$centers = $this->authorizationHelper->getReachableCenters($this->getUser(),
/* $centers = $this->authorizationHelper->getReachableCenters($this->getUser(),
new Role(PersonVoter::CREATE));
$person->setCenter($centers[0]);
$person->setCenter($centers[0]); */
return $person;
}

View File

@@ -247,7 +247,6 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
if (\random_int(0, 10) > 3) {
// always add social scope:
$accompanyingPeriod->addScope($this->getReference('scope_social'));
var_dump(count($accompanyingPeriod->getScopes()));
$accompanyingPeriod->setAddressLocation($this->createAddress());
$manager->persist($accompanyingPeriod->getAddressLocation());

View File

@@ -640,12 +640,13 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
Request::METHOD_GET => true,
Request::METHOD_HEAD => true,
Request::METHOD_POST=> true,
Request::METHOD_PATCH => true
],
'roles' => [
Request::METHOD_GET => \Chill\PersonBundle\Security\Authorization\PersonVoter::SEE,
Request::METHOD_HEAD => \Chill\PersonBundle\Security\Authorization\PersonVoter::SEE,
Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\PersonVoter::CREATE,
Request::METHOD_PATCH => \Chill\PersonBundle\Security\Authorization\PersonVoter::CREATE,
],
],
'address' => [

View File

@@ -213,7 +213,7 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
private $scopes;
/**
* @ORM\ManyToOne(targetEntity=Person::class)
* @ORM\ManyToOne(targetEntity=Person::class, inversedBy="accompanyingPeriodRequested")
* @ORM\JoinColumn(nullable=true)
*/
private $requestorPerson;
@@ -513,6 +513,44 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
);
}
/**
* Return an array with open participations sorted by household
* [
* [
* "household" => Household x,
* "members" => [
* Participation y , Participation z, ...
* ]
* ],
* ]
*
*/
public function actualParticipationsByHousehold(): array
{
$participations = $this->getOPenParticipations()->toArray();
$households = [];
foreach ($participations as $p) {
$households[] = $p->getPerson()->getCurrentHousehold();
}
$households = array_unique($households, SORT_REGULAR);
$array = [];
foreach ($households as $household) {
$members = [];
foreach ($participations as $p) {
if ($household === $p->getPerson()->getCurrentHousehold()) {
$members[] = array_shift($participations);
} else {
$participations[] = array_shift($participations);
}
}
$array[] = [ 'household' => $household, 'members' => $members ];
}
return $array;
}
/**
* Return true if the accompanying period contains a person.
*

View File

@@ -47,86 +47,91 @@ class AccompanyingPeriodParticipation
* @Groups({"read"})
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity=Person::class, inversedBy="accompanyingPeriodParticipations")
* @ORM\JoinColumn(name="person_id", referencedColumnName="id", nullable=false)
* @Groups({"read"})
*/
private $person;
/**
* @ORM\ManyToOne(targetEntity=AccompanyingPeriod::class, inversedBy="participations", cascade={"persist"})
* @ORM\JoinColumn(name="accompanyingperiod_id", referencedColumnName="id", nullable=false)
*/
private $accompanyingPeriod;
/**
* @ORM\Column(type="date", nullable=false)
* @Groups({"read"})
*/
private $startDate;
/**
* @ORM\Column(type="date", nullable=true)
* @Groups({"read"})
*/
private $endDate = null;
public function __construct(AccompanyingPeriod $accompanyingPeriod, Person $person)
{
$this->startDate = new \DateTimeImmutable('now');
$this->accompanyingPeriod = $accompanyingPeriod;
$this->person = $person;
}
public function getId(): ?int
{
return $this->id;
}
public function getPerson(): ?Person
{
return $this->person;
}
public function setPerson(?Person $person): self
{
$this->person = $person;
return $this;
}
public function getAccompanyingPeriod(): ?AccompanyingPeriod
{
return $this->accompanyingPeriod;
}
public function setAccompanyingPeriod(?AccompanyingPeriod $accompanyingPeriod): self
{
$this->accompanyingPeriod = $accompanyingPeriod;
return $this;
}
public function getStartDate(): ?\DateTimeInterface
{
return $this->startDate;
}
/*
* public function setStartDate(\DateTimeInterface $startDate): self { $this->startDate = $startDate; return $this; }
*/
public function getEndDate(): ?\DateTimeInterface
{
return $this->endDate;
}
public function setEndDate(?\DateTimeInterface $endDate): self
{
$this->endDate = $endDate;
return $this;
}
public function isOpen(): bool
{
return $this->endDate === null;
}
}

View File

@@ -229,7 +229,7 @@ class Household
))
->andWhere($expr->orX(
$expr->isNull('endDate'),
$expr->gte('endDate', $date)
$expr->gt('endDate', $date)
));
return $criteria;
@@ -306,7 +306,7 @@ class Household
)
->orWhere(
$expr->andX(
$expr->lt('endDate', $date),
$expr->lte('endDate', $date),
$expr->neq('endDate', null)
)
);

View File

@@ -28,9 +28,9 @@ use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Country;
use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\MaritalStatus;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\Address;
@@ -329,6 +329,15 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
*/
private $accompanyingPeriodParticipations;
/**
* The accompanying period requested by the Person
*
* @ORM\OneToMany(targetEntity=AccompanyingPeriod::class,
* mappedBy="requestorPerson")
* @var Collection|AccompanyingPeriod[]
*/
private Collection $accompanyingPeriodRequested;
/**
* A remark over the person
* @var string
@@ -478,6 +487,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
$this->genderComment = new CommentEmbeddable();
$this->maritalStatusComment = new CommentEmbeddable();
$this->periodLocatedOn = new ArrayCollection();
$this->accompanyingPeriodRequested = new ArrayCollection();
}
/**
@@ -605,6 +615,8 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
/**
* Get AccompanyingPeriodParticipations Collection
*
* @return AccompanyingPeriodParticipation[]|Collection
*/
public function getAccompanyingPeriodParticipations(): Collection
{
@@ -614,6 +626,8 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
/**
* Return a collection of participation, where the participation
* is still opened, not a draft, and the period is still opened
*
* @return AccompanyingPeriodParticipation[]|Collection
*/
public function getOpenedParticipations(): Collection
{
@@ -1650,6 +1664,84 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this;
}
/**
* @return AccompanyingPeriod[]|Collection
*/
public function getAccompanyingPeriodRequested(): Collection
{
return $this->accompanyingPeriodRequested;
}
/**
* Return a list of all accompanying period where the person is involved:
*
* * as requestor;
* * as participant, only for opened participation;
*
* @param bool $asParticipantOpen add participation which are still opened
* @param bool $asRequestor add accompanying period where the person is requestor
* @return AccompanyingPeriod[]|Collection
*/
public function getAccompanyingPeriodInvolved(
bool $asParticipantOpen = true,
bool $asRequestor = true
): Collection
{
$result = new ArrayCollection();
if ($asParticipantOpen) {
foreach ($this->getOpenedParticipations()
->map(fn (AccompanyingPeriodParticipation $app) =>
$app->getAccompanyingPeriod())
as $period
) {
if (!$result->contains($period)) {
$result->add($period);
}
}
}
if ($asRequestor) {
foreach ($this->accompanyingPeriodRequested as $period) {
if (!$result->contains($period)) {
$result->add($period);
}
}
}
return $result;
}
/**
* Handy method to get the AccompanyingPeriodParticipation
* matching a given AccompanyingPeriod.
*
* Used in template, to find the participation when iterating on a list
* of period.
*
* @param \Chill\PersonBundle\Entity\AccompanyingPeriod $period
* @return AccompanyingPeriodParticipation
*/
public function findParticipationForPeriod(AccompanyingPeriod $period): ?AccompanyingPeriodParticipation
{
$closeCandidates = [];
foreach ($this->getAccompanyingPeriodParticipations() as $participation) {
if ($participation->getAccompanyingPeriod() === $period) {
if ($participation->isOpen()) {
return $participation;
}
$closeCandidates[] = $participation;
}
}
if (0 < count($closeCandidates)) {
return $closeCandidates[0];
}
return null;
}
public function getCreatedBy(): ?User
{
return $this->createdBy;

View File

@@ -129,8 +129,7 @@ class PersonType extends AbstractType
$builder
->add('mobilenumber', TelType::class, array('required' => false))
->add('acceptSMS', CheckboxType::class, array(
'value' => false,
'required' => true
'required' => false
));
}

View File

@@ -199,7 +199,7 @@ final class PersonACLAwareRepository implements PersonACLAwareRepositoryInterfac
}
if (NULL !== $birthdate) {
$qb->andWhere($qb->expr()->eq('s.birthdate', ':birthdate'))
$qb->andWhere($qb->expr()->eq('p.birthdate', ':birthdate'))
->setParameter('birthdate', $birthdate);
}

View File

@@ -247,3 +247,28 @@ span.fa-holder {
font-family: "Open Sans Extrabold";
}
}
div.accompanyingcourse-resume {
div.associated-persons {
span.household {
display: inline-block;
border-radius: 8px;
border: 1px solid $white;
&:hover {
border: 1px solid $chill-beige;
i {
display: inline-block;
}
}
&.no-household:hover {
border: 1px solid $white;
}
i {
color: $chill-beige;
display: none;
}
padding: 0.3em;
margin-right: 2px;
}
}
}

View File

@@ -6,7 +6,7 @@ div.accompanying_course_work-list {
flex-direction: row;
width: 100%;
color: $dark;
height: 40px;
//height: 40px;
span.title_label {
border-radius: 0.35rem 0 0 0.35rem;

View File

@@ -87,8 +87,8 @@ export default {
}
padding: 0em 0em;
margin: 1em 0;
border: 1px dotted tint-color($chill-accourse-context, 10%);
border-radius: 5px;
border: 1px dotted tint-color($chill-accourse-context, 10%);
border-left: 1px dotted tint-color($chill-accourse-context, 10%);
border-right: 1px dotted tint-color($chill-accourse-context, 10%);
dd {
@@ -96,10 +96,15 @@ export default {
}
& > div {
margin: 1em 3em 0;
&.flex-table,
&.flex-bloc {
margin: 1em 0 0;
}
&.alert.to-confirm {
margin: 1em 0 0;
padding: 1em 3em;
}
}
div.flex-table {

View File

@@ -91,7 +91,7 @@ export default {
},
scopes: {
msg: 'confirm.set_a_scope',
anchor: '#section-65'
anchor: '#section-60'
}
}
}

View File

@@ -27,19 +27,34 @@
</div>
<div v-if="isTemporaryAddress" class="alert alert-warning separator">
<p>{{ $t('courselocation.temporary_address_must_be_changed') }}</p>
<p>
{{ $t('courselocation.temporary_address_must_be_changed') }}
<i class="fa fa-fw fa-map-marker"></i>
</p>
</div>
</div>
</div>
<div v-if="hasNoPersonLocation" class="alert alert-danger no-person-location">
<i class="fa fa-warning fa-2x"></i>
<div>
<p>
{{ $t('courselocation.associate_at_least_one_person_with_one_household_with_address') }}
<a href="#section-10">
<i class="fa fa-level-up fa-fw"></i>
</a>
</p>
</div>
</div>
<div>
<ul class="record_actions">
<li>
<add-address
v-if="!isPersonLocation"
:key="key"
:context="context"
:key="addAddress.type"
:options="addAddress.options"
:options="options"
:addressChangedCallback="submitTemporaryAddress"
ref="addAddress">
</add-address>
@@ -55,11 +70,15 @@
</ul>
</div>
<div v-if="!isLocationValid" class="alert alert-warning to-confirm">
{{ $t('courselocation.not_valid') }}
</div>
</div>
</template>
<script>
import { mapState } from "vuex";
import {mapGetters, mapState} from "vuex";
import AddAddress from 'ChillMainAssets/vuejs/Address/components/AddAddress.vue';
import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue';
@@ -72,10 +91,7 @@ export default {
data() {
return {
addAddress: {
type: 'accompanying_course_location',
options: {
/// Options override default.
/// null value take default component value
button: {
text: {
create: 'courselocation.add_temporary_address',
@@ -86,13 +102,7 @@ export default {
create: 'courselocation.add_temporary_address',
edit: 'courselocation.edit_temporary_address'
},
/// Display each step in page or Modal
openPanesInModal: true,
// Use Date fields
//useDate: {
// validFrom: true
//},
hideAddress: true
onlyButton: true
}
}
}
@@ -102,6 +112,16 @@ export default {
accompanyingCourse: state => state.accompanyingCourse,
context: state => state.addressContext
}),
...mapGetters([
'isLocationValid'
]),
options() {
return this.addAddress.options;
},
key() {
return (this.context.edit) ? 'address_' + this.context.addressId
: this.accompanyingCourse.type + '_' + this.accompanyingCourse.id ;
},
isTemporaryAddress() {
return this.accompanyingCourse.locationStatus === 'address';
},
@@ -111,11 +131,40 @@ export default {
hasNoLocation() {
return this.accompanyingCourse.locationStatus === 'none';
},
currentParticipations() {
return this.accompanyingCourse.participations.filter(p => p.enddate !== null);
},
hasNoPersonLocation() {
let addressInParticipations_ = []
this.currentParticipations.forEach(p => {
addressInParticipations_.push(this.checkHouseholdAddressForParticipation(p));
});
const booleanReducer = (previousValue, currentValue) => previousValue || currentValue;
let addressInParticipations = (addressInParticipations_.length > 0) ?
addressInParticipations_.reduce(booleanReducer) : false;
//console.log(addressInParticipations_, addressInParticipations);
return (
this.accompanyingCourse.step !== 'DRAFT'
&& this.isTemporaryAddress
&& !addressInParticipations
)
;
},
isContextEdit() {
return this.context.edit;
}
},
methods: {
checkHouseholdAddressForParticipation(participation) {
if (participation.person.current_household_id === null) {
return false;
}
return participation.person.current_household_address !== null;
},
initAddressContext() {
let context = {
target: {
@@ -153,20 +202,44 @@ export default {
created() {
this.initAddressContext();
console.log('ac.locationStatus', this.accompanyingCourse.locationStatus);
console.log('ac.location (temporary location)', this.accompanyingCourse.location);
console.log('ac.personLocation', this.accompanyingCourse.personLocation);
//console.log('ac.locationStatus', this.accompanyingCourse.locationStatus);
//console.log('ac.location (temporary location)', this.accompanyingCourse.location);
//console.log('ac.personLocation', this.accompanyingCourse.personLocation);
}
}
</script>
<style lang="scss" scoped>
div.flex-table {
div.item-bloc {
div.alert {
margin: 0 -0.9em -1em;
div#accompanying-course {
div.vue-component {
& > div.alert.no-person-location {
margin: 1px 0 0;
}
div.no-person-location {
padding-top: 1.5em;
display: flex;
flex-direction: row;
& > i {
flex-basis: 1.5em; flex-grow: 0; flex-shrink: 0;
padding-top: 0.2em;
opacity: 0.75;
}
& > div {
flex-basis: auto;
div.action {
button.btn-update {
margin-right: 2em;
}
}
}
}
div.flex-table {
div.item-bloc {
div.alert {
margin: 0 -0.9em -1em;
}
}
}
}
}
</style>

View File

@@ -26,7 +26,7 @@
:id="p.person.id"
:value="p.person.id"
/>
<label class="form-check-label" for="hasNoHousehold">
<label class="form-check-label">
{{ p.person.text }}
</label>
</div>
@@ -58,11 +58,14 @@
</add-persons>
</div>
<div v-if="!isParticipationValid" class="alert alert-warning to-confirm">
{{ $t('persons_associated.participation_not_valid') }}
</div>
</div>
</template>
<script>
import { mapState } from 'vuex';
import {mapGetters, mapState} from 'vuex';
import ParticipationItem from "./PersonsAssociated/ParticipationItem.vue"
import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue'
@@ -89,6 +92,9 @@ export default {
courseId: state => state.accompanyingCourse.id,
participations: state => state.accompanyingCourse.participations
}),
...mapGetters([
'isParticipationValid'
]),
currentParticipations() {
return this.participations.filter(p => p.endDate === null)
},
@@ -126,7 +132,7 @@ export default {
<style lang="scss" scoped>
div#accompanying-course {
div.vue-component {
& > div.alert {
& > div.alert.no-household {
margin: 0 0 -1em;
}
div.no-household {

View File

@@ -19,20 +19,8 @@
v-if="hasCurrentHouseholdAddress"
v-bind:person="participation.person">
</button-location>
<li>
<on-the-fly
v-bind:type="participation.person.type"
v-bind:id="participation.person.id"
action="show">
</on-the-fly>
</li>
<li>
<on-the-fly
v-bind:type="participation.person.type"
v-bind:id="participation.person.id"
action="edit">
</on-the-fly>
</li>
<li><on-the-fly :type="participation.person.type" :id="participation.person.id" action="show"></on-the-fly></li>
<li><on-the-fly :type="participation.person.type" :id="participation.person.id" action="edit" @saveFormOnTheFly="saveFormOnTheFly"></on-the-fly></li>
<!-- <li>
<button class="btn btn-delete"
:title="$t('action.delete')"
@@ -69,7 +57,7 @@
</template>
<script>
import OnTheFly from 'ChillMainAssets/vuejs/_components/OnTheFly.vue';
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue';
import ButtonLocation from '../ButtonLocation.vue';
import PersonRenderBox from 'ChillPersonAssets/vuejs/_components/Entity/PersonRenderBox.vue';
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
@@ -112,22 +100,13 @@ export default {
getAccompanyingCourseReturnPath() {
return `fr/parcours/${this.$store.state.accompanyingCourse.id}/edit#section-10`;
}
},
methods: {
saveFormOnTheFly(payload) {
console.log('saveFormOnTheFly: type', payload.type, ', data', payload.data);
payload.target = 'participation';
this.$store.dispatch('patchOnTheFly', payload);
}
}
}
/*
* dates of participation
*
*
*
* <tr>
* <td><span v-if="participation.startDate">
* {{ $d(participation.startDate.datetime, 'short') }}</span>
* </td>
* <td><span v-if="participation.endDate">
* {{ $d(participation.endDate.datetime, 'short') }}</span>
* </td>
* </tr>
*
*/
</script>

View File

@@ -10,7 +10,7 @@
{{ $t('requestor.is_anonymous') }}
</label>
<third-party-render-box v-if="accompanyingCourse.requestor.type == 'thirdparty'"
<third-party-render-box v-if="accompanyingCourse.requestor.type === 'thirdparty'"
:thirdparty="accompanyingCourse.requestor"
:options="{
addLink: false,
@@ -23,14 +23,13 @@
>
<template v-slot:record-actions>
<ul class="record_actions">
<button-location v-if="hasCurrentHouseholdAddress" :thirdparty="accompanyingCourse.requestor"></button-location>
<li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="show"></on-the-fly></li>
<li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="edit"></on-the-fly></li>
<li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="edit" @saveFormOnTheFly="saveFormOnTheFly"></on-the-fly></li>
</ul>
</template>
</third-party-render-box>
<person-render-box render="bloc" v-else-if="accompanyingCourse.requestor.type == 'person'"
<person-render-box render="bloc" v-else-if="accompanyingCourse.requestor.type === 'person'"
:person="accompanyingCourse.requestor"
:options="{
addLink: false,
@@ -44,9 +43,8 @@
>
<template v-slot:record-actions>
<ul class="record_actions">
<button-location v-if="hasCurrentHouseholdAddress" :person="accompanyingCourse.requestor"></button-location>
<li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="show"></on-the-fly></li>
<li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="edit"></on-the-fly></li>
<li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="edit" @saveFormOnTheFly="saveFormOnTheFly"></on-the-fly></li>
</ul>
</template>
</person-render-box>
@@ -81,7 +79,7 @@
<script>
import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue';
import OnTheFly from 'ChillMainAssets/vuejs/_components/OnTheFly.vue';
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue';
import PersonRenderBox from '../../_components/Entity/PersonRenderBox.vue';
import ThirdPartyRenderBox from 'ChillThirdPartyAssets/vuejs/_components/Entity/ThirdPartyRenderBox.vue';
@@ -129,6 +127,11 @@ export default {
this.$store.dispatch('addRequestor', selected.shift());
this.$refs.addPersons.resetSearch(); // to cast child method
modal.showModal = false;
},
saveFormOnTheFly(payload) {
console.log('saveFormOnTheFly: type', payload.type, ', data', payload.data);
payload.target = 'requestor';
this.$store.dispatch('patchOnTheFly', payload);
}
}
}

View File

@@ -6,9 +6,11 @@
>
<template v-slot:record-actions>
<ul class="record_actions">
<!--
<button-location v-if="hasCurrentHouseholdAddress" :person="resource.resource"></button-location>
-->
<li><on-the-fly :type="resource.resource.type" :id="resource.resource.id" action="show"></on-the-fly></li>
<li><on-the-fly :type="resource.resource.type" :id="resource.resource.id" action="edit"></on-the-fly></li>
<li><on-the-fly :type="resource.resource.type" :id="resource.resource.id" action="edit" @saveFormOnTheFly="saveFormOnTheFly"></on-the-fly></li>
<li><button class="btn btn-sm btn-remove" :title="$t('action.remove')" @click.prevent="$emit('remove', resource)"></button></li>
</ul>
</template>
@@ -22,7 +24,7 @@
<template v-slot:record-actions>
<ul class="record_actions">
<li><on-the-fly :type="resource.resource.type" :id="resource.resource.id" action="show"></on-the-fly></li>
<li><on-the-fly :type="resource.resource.type" :id="resource.resource.id" action="edit"></on-the-fly></li>
<li><on-the-fly :type="resource.resource.type" :id="resource.resource.id" action="edit" @saveFormOnTheFly="saveFormOnTheFly"></on-the-fly></li>
<li><button class="btn btn-sm btn-remove" :title="$t('action.remove')" @click.prevent="$emit('remove', resource)"></button></li>
</ul>
</template>
@@ -31,7 +33,7 @@
</template>
<script>
import OnTheFly from 'ChillMainAssets/vuejs/_components/OnTheFly.vue';
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue';
import ButtonLocation from '../ButtonLocation.vue';
import PersonRenderBox from 'ChillPersonAssets/vuejs/_components/Entity/PersonRenderBox.vue';
import ThirdPartyRenderBox from 'ChillThirdPartyAssets/vuejs/_components/Entity/ThirdPartyRenderBox.vue';
@@ -53,6 +55,13 @@ export default {
}
return false;
}
},
methods: {
saveFormOnTheFly(payload) {
console.log('saveFormOnTheFly: type', payload.type, ', data', payload.data);
payload.target = 'resource';
this.$store.dispatch('patchOnTheFly', payload);
}
}
}
</script>

View File

@@ -1,18 +1,20 @@
<template>
<div class="vue-component">
<h2><a id="section-60"></a>{{ $t('scopes.title') }}</h2>
<h2><a id="section-60"></a>{{ $t('scopes.title') }}</h2>
<ul>
<li v-for="s in scopes">
<input type="checkbox" v-model="checkedScopes" :value="s" />
{{ s.name.fr }}
</li>
</ul>
<div class="mb-4">
<div class="form-check" v-for="s in scopes">
<input class="form-check-input" type="checkbox" v-model="checkedScopes" :value="s" />
<label class="form-check-label">
{{ s.name.fr }}
</label>
</div>
</div>
<div v-if="!isScopeValid" class="alert alert-warning separator">
<div v-if="!isScopeValid" class="alert alert-warning to-confirm">
{{ $t('scopes.add_at_least_one') }}
</div>
</div>
</template>

View File

@@ -21,13 +21,17 @@
</VueMultiselect>
</div>
<div v-if="!isSocialIssueValid" class="alert alert-warning to-confirm">
{{ $t('social_issue.not_valid') }}
</div>
</div>
</template>
<script>
import VueMultiselect from 'vue-multiselect';
import { getSocialIssues } from '../api';
import { mapState } from 'vuex';
import {mapGetters, mapState} from 'vuex';
export default {
name: "SocialIssue",
@@ -41,6 +45,9 @@ export default {
...mapState({
value: state => state.accompanyingCourse.socialIssues,
}),
...mapGetters([
'isSocialIssueValid'
])
},
mounted() {
this.getOptions();

View File

@@ -1,5 +1,7 @@
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n'
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n';
import { thirdpartyMessages } from 'ChillThirdPartyAssets/vuejs/_js/i18n';
import { addressMessages } from 'ChillMainAssets/vuejs/Address/i18n';
import { ontheflyMessages } from 'ChillMainAssets/vuejs/OnTheFly/i18n';
const appMessages = {
fr: {
@@ -48,8 +50,9 @@ const appMessages = {
ok: "Oui, l'usager quitte le parcours",
show_household_number: "Voir le ménage (n° {id})",
show_household: "Voir le ménage",
person_without_household_warning: "Certaines personnes n'appartiennent à aucun ménage actuellement. Renseignez leur appartenance à un ménage dès que possible.",
person_without_household_warning: "Certaines usagers n'appartiennent actuellement à aucun ménage. Renseignez leur appartenance dès que possible.",
update_household: "Modifier l'appartenance",
participation_not_valid: "Sélectionnez ou créez au minimum 1 usager",
},
requestor: {
title: "Demandeur",
@@ -72,6 +75,7 @@ const appMessages = {
social_issue: {
title: "Problématiques sociales",
label: "Choisir les problématiques sociales",
not_valid: "Sélectionnez au minimum une problématique sociale",
},
courselocation: {
title: "Localisation du parcours",
@@ -79,11 +83,13 @@ const appMessages = {
edit_temporary_address: "Modifier l'adresse temporaire",
assign_course_address: "Désigner comme l'adresse du parcours",
remove_button: "Enlever l'adresse",
temporary_address_must_be_changed: "Cette adresse est temporaire et devrait être remplacée par celle d'un usager de référence.",
temporary_address_must_be_changed: "Cette adresse est temporaire. Le parcours devrait être localisé auprès d'un usager concerné.",
associate_at_least_one_person_with_one_household_with_address: "Commencez d'abord par associer un membre du parcours à un ménage, et indiquez une adresse à ce ménage.",
sure: "Êtes-vous sûr ?",
sure_description: "Voulez-vous faire de cette adresse l'adresse du parcours ?",
ok: "Désigner comme adresse du parcours",
person_locator: "Parcours localisé auprès de {0}",
not_valid: "Indiquez au minimum une localisation temporaire du parcours",
no_address: "Il n'y a pas d'adresse associée au parcours"
},
scopes: {
@@ -137,7 +143,7 @@ const appMessages = {
}
};
Object.assign(appMessages.fr, personMessages.fr, addressMessages.fr);
Object.assign(appMessages.fr, personMessages.fr, thirdpartyMessages.fr, addressMessages.fr, ontheflyMessages.fr);
export {
appMessages

View File

@@ -11,6 +11,8 @@ import { getAccompanyingCourse,
addScope,
removeScope,
} from '../api';
import { patchPerson } from "ChillPersonAssets/vuejs/_api/OnTheFly";
import { patchThirdparty } from "ChillThirdPartyAssets/vuejs/_api/OnTheFly";
const debug = process.env.NODE_ENV !== 'production';
@@ -48,7 +50,7 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
return state.accompanyingCourse.location !== null;
},
isScopeValid(state) {
console.log('is scope valid', state.accompanyingCourse.scopes.length > 0);
//console.log('is scope valid', state.accompanyingCourse.scopes.length > 0);
return state.accompanyingCourse.scopes.length > 0;
},
validationKeys(state, getters) {
@@ -107,6 +109,36 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
//console.log('### mutation: addResource', resource);
state.accompanyingCourse.resources.push(resource);
},
updatePerson(state, payload) {
console.log('### mutation: updatePerson', payload);
let i = null;
switch (payload.target) {
case 'participation':
i = state.accompanyingCourse.participations.findIndex(e => e.person.id === payload.person.id );
state.accompanyingCourse.participations[i].person = payload.person;
break;
case 'requestor':
state.accompanyingCourse.requestor = payload.person;
break;
case 'resource':
i = state.accompanyingCourse.resources.findIndex(e => e.resource.id === payload.person.id );
state.accompanyingCourse.resources[i].resource = payload.person;
break;
}
},
updateThirdparty(state, payload) {
console.log('### mutation: updateThirdparty', payload);
let i = null;
switch (payload.target) {
case 'requestor':
state.accompanyingCourse.requestor = payload.thirdparty;
break;
case 'resource':
i = state.accompanyingCourse.resources.findIndex(e => e.resource.id === payload.thirdparty.id );
state.accompanyingCourse.resources[i].resource = payload.thirdparty;
break;
}
},
toggleIntensity(state, value) {
state.accompanyingCourse.intensity = value;
},
@@ -239,6 +271,38 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
resolve();
})).catch((error) => { commit('catchError', error) });
},
patchOnTheFly({ commit }, payload) {
console.log('## action: patch OnTheFly', payload);
let body = { type: payload.type };
if (payload.type === 'person') {
body.firstName = payload.data.firstName;
body.lastName = payload.data.lastName;
if (payload.data.birthdate !== null) { body.birthdate = payload.data.birthdate; }
body.phonenumber = payload.data.phonenumber;
body.mobilenumber = payload.data.mobilenumber;
body.gender = payload.data.gender;
console.log('id', payload.data.id, 'and body', body);
patchPerson(payload.data.id, body)
.then(person => new Promise((resolve, reject) => {
console.log('patch person', person);
commit('updatePerson', { target: payload.target, person: person });
resolve();
}));
}
else if (payload.type === 'thirdparty') {
body.name = payload.data.text;
body.email = payload.data.email;
body.telephone = payload.data.phonenumber;
body.address = { id: payload.data.address.address_id };
console.log('id', payload.data.id, 'and body', body);
patchThirdparty(payload.data.id, body)
.then(thirdparty => new Promise((resolve, reject) => {
console.log('patch thirdparty', thirdparty);
commit('updateThirdparty', { target: payload.target, thirdparty: thirdparty });
resolve();
}));
}
},
toggleIntensity({ commit }, payload) {
//console.log(payload);
patchAccompanyingCourse(id, { type: "accompanying_period", intensity: payload })

View File

@@ -109,7 +109,7 @@ export default {
this.toggleEditEvaluation();
},
buildEditLink(storedObject) {
return `/fr/chill_wopi/edit/${storedObject.filename}?returnPath=` + encodeURIComponent(
return `/edit/${storedObject.uuid}?returnPath=` + encodeURIComponent(
window.location.pathname + window.location.search + window.location.hash);
},
}

View File

@@ -2,171 +2,126 @@
<h2 class="mt-4">{{ $t('household_members_editor.household_part') }}</h2>
<div v-if="hasHousehold">
<div v-if="mode == null">
<div class="flex-table">
<div class="item-bloc">
<household-render-box :household="household" :isAddressMultiline="true"></household-render-box>
</div>
</div>
<div class="alert alert-info">{{ $t('household_members_editor.household.no_household_choose_one') }}</div>
<div v-if="isHouseholdNew && !hasHouseholdAddress">
<div v-if="hasAddressSuggestion" class="householdAddressSuggestion my-5">
<h4 class="mb-3">
{{ $t('household_members_editor.household.where_live_the_household') }}
</h4>
<div class="accordion" id="addressSuggestions">
<div class="accordion-item">
<h2 class="accordion-header" id="heading_address_suggestions">
<button v-if="!showAddressSuggestion"
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
aria-expanded="false"
@click="toggleAddressSuggestion">
{{ $tc('household_members_editor.show_household_suggestion', countAddressSuggestion) }}
</button>
<button v-if="showAddressSuggestion"
class="accordion-button"
type="button"
data-bs-toggle="collapse"
aria-expanded="true"
@click="toggleAddressSuggestion">
{{ $t('household_members_editor.hide_household_suggestion') }}
</button>
</h2>
<div class="accordion-collapse" id="collapse_address_suggestions"
aria-labelledby="heading_address_suggestions" data-bs-parent="#addressSuggestions">
<div v-if="showAddressSuggestion">
<div class="flex-table householdAddressSuggestionList">
<div v-for="a in filterAddressesSuggestion" class="item-bloc">
<div class="float-button bottom">
<div class="box">
<div class="action">
<ul class="record_actions">
<li>
<button class="btn btn-sm btn-choose" @click="setHouseholdAddress(a)">
{{ $t('household_members_editor.household.household_live_to_this_address') }}
</button>
</li>
</ul>
</div>
<ul class="list-content fa-ul">
<li>
<i class="fa fa-li fa-map-marker"></i>
<address-render-box :address="a"></address-render-box>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="flex-table householdSuggestionList">
<div v-if="isModeNewAllowed" class="item-bloc">
<div>
<section>
<div class="item-row">
<div class="item-col">
<div class="h4">
<i class="fa fa-home"></i> {{ $t('household_members_editor.household.new_household') }}
</div>
</div>
</div>
</div>
</section>
<ul class="record_actions">
<li>
<button @click="setModeNew" class="btn btn-sm btn-create">{{ $t('household_members_editor.household.create_household') }}</button>
</li>
</ul>
</div>
</div>
<!-- if allow leave household -->
<div v-if="isModeLeaveAllowed" class="item-bloc">
<div>
<section>
<div class="item-row">
<div class="item-col">
<div class="h4">
<span class="fa-stack fa-lg">
<i class="fa fa-home fa-stack-1x"></i>
<i class="fa fa-ban fa-stack-2x text-danger"></i>
</span>
{{ $t('household_members_editor.household.leave_without_household') }}
</div>
</div>
</div>
<div class="item-row">
{{ $t('household_members_editor.household.will_leave_any_household_explanation')}}
</div>
</section>
<ul class="record_actions">
<li>
<button @click="setModeLeave" class="btn btn-sm">
<i class="fa fa-sign-out"></i>
{{ $t('household_members_editor.household.leave') }}
</button>
</li>
</ul>
</div>
</div>
<div v-for="item in getSuggestions">
<div class="item-bloc">
<household-render-box :household="item.household"></household-render-box>
<ul class="record_actions">
<li>
<button class="btn btn-sm btn-choose" @click="selectHousehold(item.household)">
{{ $t('household_members_editor.select_household') }}
</button>
</li>
</ul>
</div>
</div>
</div>
</div>
<ul class="record_actions">
<li >
<add-address
:context="addAddress.context"
<div v-else>
<div class="flex-table">
<div class="item-bloc">
<template v-if="isModeLeave">
<section>
<div class="item-row">
<div class="item-col">
<div class="h4">
<span class="fa-stack fa-lg">
<i class="fa fa-home fa-stack-1x"></i>
<i class="fa fa-ban fa-stack-2x text-danger"></i>
</span>
{{ $t('household_members_editor.household.leave_without_household') }}
</div>
</div>
</div>
<div class="item-row">
{{ $t('household_members_editor.household.will_leave_any_household_explanation')}}
</div>
</section>
</template>
<template v-else>
<household-render-box :household="household" :isAddressMultiline="true"></household-render-box>
<ul class="record_actions">
<li>
<add-address
:context="getAddressContext"
:key="addAddress.key"
:options="addAddress.options"
:result="addAddress.result"
@addressChangedCallback="setHouseholdCreatedAddress"
ref="addAddress">
</add-address>
</li>
</ul>
</div>
<div v-if="isHouseholdNew && hasHouseholdAddress">
<ul class="record_actions">
<li >
<button class="btn btn-misc" @click="removeHouseholdAddress">
{{ $t('household_members_editor.household.delete_this_address') }}
</button>
</li>
</ul>
</div>
</div>
<div v-else-if="isForceLeaveWithoutHousehold">
{{ $t('household_members_editor.household.will_leave_any_household') }}
</div>
<div v-else class="alert alert-info">{{ $t('household_members_editor.household.no_household_choose_one') }}</div>
<ul v-if="allowChangeHousehold" class="record_actions">
<li v-if="allowHouseholdCreate">
<button class="btn btn-create" @click="createHousehold">
{{ $t('household_members_editor.household.create_household') }}
</button>
</li>
<li v-if="allowHouseholdSearch">
<button class="btn btn-misc">
<i class="fa fa-search"></i>{{ $t('household_members_editor.household.search_household') }}
</button>
</li>
<li v-if="allowLeaveWithoutHousehold" >
<button @click="forceLeaveWithoutHousehold" class="btn btn-orange">
<i class="fa fa-sign-out"></i>{{ $t('household_members_editor.household.leave_without_household') }}
</button>
</li>
<li v-if="allowRemoveHousehold">
<button @click="removeHousehold" class="btn">
{{ $t('household_members_editor.household.change') }}
</button>
</li>
</ul>
<div v-if="hasHouseholdSuggestion" class="householdSuggestions my-5">
<h4 class="mb-3">
{{ $t('household_members_editor.household_for_participants_accompanying_period') }} :
</h4>
<div class="accordion" id="householdSuggestions">
<div class="accordion-item">
<h2 class="accordion-header" id="heading_household_suggestions">
<button v-if="!showHouseholdSuggestion"
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
aria-expanded="false"
@click="toggleHouseholdSuggestion">
{{ $tc('household_members_editor.show_household_suggestion', countHouseholdSuggestion) }}
</button>
<button v-if="showHouseholdSuggestion"
class="accordion-button"
type="button"
data-bs-toggle="collapse"
aria-expanded="true"
@click="toggleHouseholdSuggestion">
{{ $t('household_members_editor.hide_household_suggestion') }}
</button>
<!-- disabled bootstrap behaviour: data-bs-target="#collapse_household_suggestions" aria-controls="collapse_household_suggestions" -->
</h2>
<div class="accordion-collapse" id="collapse_household_suggestions"
aria-labelledby="heading_household_suggestions" data-bs-parent="#householdSuggestions">
<div v-if="showHouseholdSuggestion">
<div class="flex-table householdSuggestionList">
<div v-for="h in filterHouseholdSuggestionByAccompanyingPeriod" class="item-bloc">
<household-render-box :household="h"></household-render-box>
<ul class="record_actions">
<li>
<button class="btn btn-sm btn-choose" @click="selectHousehold(h)">
{{ $t('household_members_editor.select_household') }}
</button>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
:addressChangedCallback="addressChanged"
></add-address>
</li>
<li v-if="hasHouseholdAddress">
<button class="btn btn-remove"
@click="removeHouseholdAddress">
{{ $t('household_members_editor.household.remove_address') }}
</button>
</li>
</ul>
</template>
</div>
</div>
<ul v-if="isModeNewAllowed || isModeLeaveAllowed || getModeSuggestions.length > 0" class="record_actions">
<li>
<button class="btn btn-sm btn-chill-beige" @click="resetMode">
{{ $t('household_members_editor.household.reset_mode') }}
</button>
</li>
</ul>
</div>
</div>
</template>
<script>
@@ -185,35 +140,32 @@ export default {
data() {
return {
addAddress: {
context: {
target: {
name: 'household_create',
id: 0
},
edit: false,
addressId: null
},
key: 'household_new',
options: {
useDate: {
validFrom: true
validFrom: false,
validTo: false,
},
onlyButton: true,
button: {
text: {
create: 'household_members_editor.household.or_create_new_address',
edit: null,
create: 'household_members_editor.household.set_address',
edit: 'household_members_editor.household.update_address',
}
},
title: {
create: 'household_members_editor.household.create_new_address',
edit: null,
edit: 'household_members_editor.household.update_address_title',
},
}
}
}
};
},
computed: {
...mapGetters([
'isModeNewAllowed',
'isModeLeaveAllowed',
'getSuggestions',
'hasHousehold',
'isHouseholdNew',
'hasHouseholdSuggestion',
@@ -223,68 +175,38 @@ export default {
'countAddressSuggestion',
'filterAddressesSuggestion',
'hasHouseholdAddress',
'isModeLeave',
'getAddressContext',
]),
...mapState([
'showHouseholdSuggestion',
'showAddressSuggestion'
'showAddressSuggestion',
'mode',
]),
household() {
return this.$store.state.household;
},
allowHouseholdCreate() {
return this.$store.state.allowHouseholdCreate && !this.$store.getters.hasHousehold;
},
allowHouseholdSearch() {
return false;
return this.$store.state.allowHouseholdSearch && !this.$store.getters.hasHousehold;
},
allowLeaveWithoutHousehold() {
return this.$store.state.allowLeaveWithoutHousehold && !this.$store.getters.hasHousehold;
},
allowRemoveHousehold() {
return this.$store.getters.hasHousehold &&
(
this.allowHouseholdCreate || this.allowHouseholdSearch ||
this.allowLeaveWithoutHousehold
)
;
},
allowChangeHousehold() {
return this.allowHouseholdCreate || this.allowHouseholdSearch ||
this.allowLeaveWithoutHousehold;
},
isForceLeaveWithoutHousehold() {
return this.$store.state.forceLeaveWithoutHousehold;
}
},
methods: {
createHousehold() {
setModeNew() {
this.$store.dispatch('createHousehold');
},
forceLeaveWithoutHousehold() {
setModeLeave() {
this.$store.dispatch('forceLeaveWithoutHousehold');
},
toggleHouseholdSuggestion() {
this.$store.commit('toggleHouseholdSuggestion');
resetMode() {
this.$store.commit('resetMode');
},
toggleAddressSuggestion() {
this.$store.commit('toggleAddressSuggestion');
addressChanged(payload) {
console.log("addressChanged", payload);
this.$store.dispatch('setHouseholdNewAddress', payload.address);
},
selectHousehold(h) {
this.$store.dispatch('selectHousehold', h);
this.toggleHouseholdSuggestion();
},
removeHousehold() {
this.$store.dispatch('removeHousehold');
},
setHouseholdAddress(a) {
let payload = this.$refs.addAddress.submitNewAddress();
console.log('setHouseholdAddress', a);
this.$store.commit('setHouseholdAddress', a);
},
setHouseholdCreatedAddress(payload) {
console.log('setHouseholdAddress', payload);
this.$store.dispatch('setHouseholdNewAddress', payload);
},
removeHouseholdAddress() {
this.$store.commit('removeHouseholdAddress');
@@ -310,28 +232,4 @@ div.householdSuggestionList {
}
}
}
/*
div.householdAddressSuggestionList {
display: flex;
list-style-type: none;
padding: 0;
& > li {}
}
.householdSuggestionList {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
& > .item {
margin-bottom: 0.8rem;
width: calc(50% - 1rem);
border: 1px solid var(--chill-light-gray);
padding: 0.5rem 0.5rem 0 0.5rem;
ul.record_actions {
margin-bottom: 0;
}
}
}
*/
</style>

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