Merge branch 'testing' into VSR-issues

This commit is contained in:
Mathieu Jaumotte 2022-12-15 14:46:30 +01:00
commit 5211d092e3
28 changed files with 931 additions and 48 deletions

2
.gitignore vendored
View File

@ -25,3 +25,5 @@ node_modules/*
/.php-cs-fixer.cache /.php-cs-fixer.cache
/.idea/ /.idea/
/.psalm/ /.psalm/
node_modules/*

67
package.json Normal file
View File

@ -0,0 +1,67 @@
{
"name": "chill",
"version": "2.0.0",
"devDependencies": {
"@alexlafroscia/yaml-merge": "^4.0.0",
"@apidevtools/swagger-cli": "^4.0.4",
"@babel/core": "^7.20.5",
"@babel/preset-env": "^7.20.2",
"@ckeditor/ckeditor5-build-classic": "^35.3.2",
"@ckeditor/ckeditor5-dev-utils": "^31.1.13",
"@ckeditor/ckeditor5-dev-webpack-plugin": "^31.1.13",
"@ckeditor/ckeditor5-markdown-gfm": "^35.3.2",
"@ckeditor/ckeditor5-theme-lark": "^35.3.2",
"@ckeditor/ckeditor5-vue": "^4.0.1",
"@symfony/webpack-encore": "^4.1.0",
"@tsconfig/node14": "^1.0.1",
"bindings": "^1.5.0",
"bootstrap": "^5.0.1",
"chokidar": "^3.5.1",
"fork-awesome": "^1.1.7",
"jquery": "^3.6.0",
"node-sass": "^8.0.0",
"popper.js": "^1.16.1",
"postcss-loader": "^7.0.2",
"raw-loader": "^4.0.2",
"sass-loader": "^13.0.0",
"select2": "^4.0.13",
"select2-bootstrap-theme": "0.1.0-beta.10",
"style-loader": "^3.3.1",
"ts-loader": "^9.3.1",
"typescript": "^4.7.2",
"vue-loader": "^17.0.0",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
},
"dependencies": {
"@fullcalendar/core": "^5.11.0",
"@fullcalendar/daygrid": "^5.11.0",
"@fullcalendar/interaction": "^5.11.0",
"@fullcalendar/list": "^5.11.0",
"@fullcalendar/timegrid": "^5.11.0",
"@fullcalendar/vue3": "^5.11.1",
"@popperjs/core": "^2.9.2",
"dropzone": "^5.7.6",
"es6-promise": "^4.2.8",
"leaflet": "^1.7.1",
"masonry-layout": "^4.2.2",
"mime": "^3.0.0",
"swagger-ui": "^4.15.5",
"vis-network": "^9.1.0",
"vue": "^3.2.37",
"vue-i18n": "^9.1.6",
"vue-multiselect": "3.0.0-alpha.2",
"vue-toast-notification": "^2.0",
"vuex": "^4.0.0"
},
"browserslist": [
"Firefox ESR"
],
"scripts": {
"dev-server": "encore dev-server",
"dev": "encore dev",
"watch": "encore dev --watch",
"build": "encore production --progress"
},
"private": true
}

View File

@ -44,7 +44,7 @@ class ActivityTypeAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data) public function alterQuery(QueryBuilder $qb, $data)
{ {
if (!in_array('acttype', $qb->getAllAliases(), true)) { if (!in_array('acttype', $qb->getAllAliases(), true)) {
$qb->join('activity.activityType', 'acttype'); $qb->leftJoin('activity.activityType', 'acttype');
} }
$qb->addSelect(sprintf('IDENTITY(activity.activityType) AS %s', self::KEY)); $qb->addSelect(sprintf('IDENTITY(activity.activityType) AS %s', self::KEY));

View File

@ -0,0 +1,165 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Export\ListActivityHelper;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\MainBundle\Export\Helper\TranslatableStringExportLabelHelper;
use Chill\MainBundle\Export\ListInterface;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Form\FormBuilderInterface;
class ListActivity implements ListInterface, GroupedExportInterface
{
private EntityManagerInterface $entityManager;
private ListActivityHelper $helper;
private TranslatableStringExportLabelHelper $translatableStringExportLabelHelper;
public function __construct(
ListActivityHelper $helper,
EntityManagerInterface $entityManager,
TranslatableStringExportLabelHelper $translatableStringExportLabelHelper
) {
$this->helper = $helper;
$this->entityManager = $entityManager;
$this->translatableStringExportLabelHelper = $translatableStringExportLabelHelper;
}
public function buildForm(FormBuilderInterface $builder)
{
$this->helper->buildForm($builder);
}
public function getAllowedFormattersTypes()
{
return $this->helper->getAllowedFormattersTypes();
}
public function getDescription()
{
return ListActivityHelper::MSG_KEY . 'List activities linked to an accompanying course';
}
public function getGroup(): string
{
return 'Exports of activities linked to an accompanying period';
}
public function getLabels($key, array $values, $data)
{
switch ($key) {
case 'acpId':
return static function ($value) {
if ('_header' === $value) {
return ListActivityHelper::MSG_KEY . 'accompanying course id';
}
return $value ?? '';
};
case 'scopesNames':
return $this->translatableStringExportLabelHelper->getLabelMulti($key, $values, ListActivityHelper::MSG_KEY . 'course circles');
default:
return $this->helper->getLabels($key, $values, $data);
}
}
public function getQueryKeys($data)
{
return
array_merge(
$this->helper->getQueryKeys($data),
[
'acpId',
'scopesNames',
]
);
}
public function getResult($query, $data)
{
return $this->helper->getResult($query, $data);
}
public function getTitle()
{
return ListActivityHelper::MSG_KEY . 'List activity linked to a course';
}
public function getType()
{
return $this->helper->getType();
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$qb = $this->entityManager->createQueryBuilder();
$qb
->distinct()
->from(Activity::class, 'activity')
->join('activity.accompanyingPeriod', 'acp')
->leftJoin('acp.participations', 'acppart')
->leftJoin('acppart.person', 'person')
->andWhere('acppart.startDate != acppart.endDate OR acppart.endDate IS NULL')
->andWhere(
$qb->expr()->exists(
'SELECT 1
FROM ' . PersonCenterHistory::class . ' acl_count_person_history
WHERE acl_count_person_history.person = person
AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
// some grouping are necessary
->addGroupBy('acp.id')
->addOrderBy('activity.date')
->addOrderBy('activity.id')
->setParameter('authorized_centers', $centers);
$this->helper->addSelect($qb);
// add select for this step
$qb
->addSelect('acp.id AS acpId')
->addSelect('(SELECT AGGREGATE(acpScope.name) FROM ' . Scope::class . ' acpScope WHERE acpScope MEMBER OF acp.scopes) AS scopesNames')
->addGroupBy('scopesNames');
return $qb;
}
public function requiredRole(): string
{
return ActivityStatsVoter::LISTS;
}
public function supportsModifiers()
{
return array_merge(
$this->helper->supportsModifiers(),
[
\Chill\PersonBundle\Export\Declarations::ACP_TYPE,
]
);
}
}

View File

@ -0,0 +1,269 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Export;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityPresenceRepositoryInterface;
use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\Helper\DateTimeHelper;
use Chill\MainBundle\Export\Helper\TranslatableStringExportLabelHelper;
use Chill\MainBundle\Export\Helper\UserHelper;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Export\Helper\LabelPersonHelper;
use Chill\ThirdPartyBundle\Export\Helper\LabelThirdPartyHelper;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\QueryBuilder;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use const SORT_NUMERIC;
class ListActivityHelper
{
public const MSG_KEY = 'export.list.activity.';
private ActivityPresenceRepositoryInterface $activityPresenceRepository;
private ActivityTypeRepositoryInterface $activityTypeRepository;
private DateTimeHelper $dateTimeHelper;
private LabelPersonHelper $labelPersonHelper;
private LabelThirdPartyHelper $labelThirdPartyHelper;
private TranslatableStringHelperInterface $translatableStringHelper;
private TranslatableStringExportLabelHelper $translatableStringLabelHelper;
private TranslatorInterface $translator;
private UserHelper $userHelper;
public function __construct(
ActivityPresenceRepositoryInterface $activityPresenceRepository,
ActivityTypeRepositoryInterface $activityTypeRepository,
DateTimeHelper $dateTimeHelper,
LabelPersonHelper $labelPersonHelper,
LabelThirdPartyHelper $labelThirdPartyHelper,
TranslatorInterface $translator,
TranslatableStringHelperInterface $translatableStringHelper,
TranslatableStringExportLabelHelper $translatableStringLabelHelper,
UserHelper $userHelper
) {
$this->activityPresenceRepository = $activityPresenceRepository;
$this->activityTypeRepository = $activityTypeRepository;
$this->dateTimeHelper = $dateTimeHelper;
$this->labelPersonHelper = $labelPersonHelper;
$this->labelThirdPartyHelper = $labelThirdPartyHelper;
$this->translator = $translator;
$this->translatableStringHelper = $translatableStringHelper;
$this->translatableStringLabelHelper = $translatableStringLabelHelper;
$this->userHelper = $userHelper;
}
public function addSelect(QueryBuilder $qb): void
{
$qb
->addSelect('activity.id AS id')
->addSelect('activity.date')
->addSelect('IDENTITY(activity.activityType) AS typeName')
->leftJoin('activity.reasons', 'reasons')
->addSelect('AGGREGATE(reasons.name) AS listReasons')
->leftJoin('activity.persons', 'actPerson')
->addSelect('AGGREGATE(actPerson.id) AS personsIds')
->addSelect('AGGREGATE(actPerson.id) AS personsNames')
->leftJoin('activity.users', 'users_u')
->addSelect('AGGREGATE(users_u.id) AS usersIds')
->addSelect('AGGREGATE(users_u.id) AS usersNames')
->leftJoin('activity.thirdParties', 'thirdparty')
->addSelect('AGGREGATE(thirdparty.id) AS thirdPartiesIds')
->addSelect('AGGREGATE(thirdparty.id) AS thirdPartiesNames')
->addSelect('IDENTITY(activity.attendee) AS attendeeName')
->addSelect('activity.durationTime')
->addSelect('activity.travelTime')
->addSelect('activity.emergency')
->leftJoin('activity.location', 'location')
->addSelect('location.name AS locationName')
->addSelect('activity.sentReceived')
->addSelect('IDENTITY(activity.createdBy) AS createdBy')
->addSelect('activity.createdAt')
->addSelect('IDENTITY(activity.updatedBy) AS updatedBy')
->addSelect('activity.updatedAt')
->addGroupBy('activity.id')
->addGroupBy('location.id');
}
public function buildForm(FormBuilderInterface $builder)
{
}
public function getAllowedFormattersTypes()
{
return [FormatterInterface::TYPE_LIST];
}
public function getLabels($key, array $values, $data)
{
switch ($key) {
case 'createdAt':
case 'updatedAt':
return $this->dateTimeHelper->getLabel($key);
case 'createdBy':
case 'updatedBy':
return $this->userHelper->getLabel($key, $values, $key);
case 'date':
return $this->dateTimeHelper->getLabel(self::MSG_KEY . $key);
case 'attendeeName':
return function ($value) {
if ('_header' === $value) {
return 'Attendee';
}
if (null === $value || null === $presence = $this->activityPresenceRepository->find($value)) {
return '';
}
return $this->translatableStringHelper->localize($presence->getName());
};
case 'listReasons':
return $this->translatableStringLabelHelper->getLabelMulti($key, $values, 'Activity Reasons');
case 'typeName':
return function ($value) {
if ('_header' === $value) {
return 'Activity type';
}
if (null === $value || null === $type = $this->activityTypeRepository->find($value)) {
return '';
}
return $this->translatableStringHelper->localize($type->getName());
};
case 'usersNames':
return $this->userHelper->getLabelMulti($key, $values, self::MSG_KEY . 'users name');
case 'usersIds':
case 'thirdPartiesIds':
case 'personsIds':
return static function ($value) use ($key) {
if ('_header' === $value) {
switch ($key) {
case 'usersIds':
return self::MSG_KEY . 'users ids';
case 'thirdPartiesIds':
return self::MSG_KEY . 'third parties ids';
case 'personsIds':
return self::MSG_KEY . 'persons ids';
default:
throw new LogicException('key not supported');
}
}
$decoded = json_decode($value);
return implode(
'|',
array_unique(
array_filter($decoded, static fn (?int $id) => null !== $id),
SORT_NUMERIC
)
);
};
case 'personsNames':
return $this->labelPersonHelper->getLabelMulti($key, $values, self::MSG_KEY . 'persons name');
case 'thirdPartiesNames':
return $this->labelThirdPartyHelper->getLabelMulti($key, $values, self::MSG_KEY . 'thirds parties');
case 'sentReceived':
return function ($value) {
if ('_header' === $value) {
return self::MSG_KEY . 'sent received';
}
if (null === $value) {
return '';
}
return $this->translator->trans($value);
};
default:
return function ($value) use ($key) {
if ('_header' === $value) {
return self::MSG_KEY . $key;
}
if (null === $value) {
return '';
}
return $this->translator->trans($value);
};
}
}
public function getQueryKeys($data)
{
return [
'id',
'date',
'typeName',
'listReasons',
'attendeeName',
'durationTime',
'travelTime',
'emergency',
'locationName',
'sentReceived',
'personsIds',
'personsNames',
'usersIds',
'usersNames',
'thirdPartiesIds',
'thirdPartiesNames',
'createdBy',
'createdAt',
'updatedBy',
'updatedAt',
];
}
public function getResult($query, $data)
{
return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR);
}
public function getType(): string
{
return Declarations::ACTIVITY;
}
public function supportsModifiers()
{
return [
Declarations::ACTIVITY,
];
}
}

View File

@ -50,7 +50,7 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
{ {
$where = $qb->getDQLPart('where'); $where = $qb->getDQLPart('where');
$join = $qb->getDQLPart('join'); $join = $qb->getDQLPart('join');
$clause = $qb->expr()->in('reasons', ':selected_activity_reasons'); $clause = $qb->expr()->in('actreasons', ':selected_activity_reasons');
if (!in_array('actreasons', $qb->getAllAliases(), true)) { if (!in_array('actreasons', $qb->getAllAliases(), true)) {
$qb->join('activity.reasons', 'actreasons'); $qb->join('activity.reasons', 'actreasons');
@ -77,6 +77,7 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
'class' => ActivityReason::class, 'class' => ActivityReason::class,
'choice_label' => fn (ActivityReason $reason) => $this->translatableStringHelper->localize($reason->getName()), 'choice_label' => fn (ActivityReason $reason) => $this->translatableStringHelper->localize($reason->getName()),
'group_by' => fn (ActivityReason $reason) => $this->translatableStringHelper->localize($reason->getCategory()->getName()), 'group_by' => fn (ActivityReason $reason) => $this->translatableStringHelper->localize($reason->getCategory()->getName()),
'attr' => ['class' => 'select2 '],
'multiple' => true, 'multiple' => true,
'expanded' => false, 'expanded' => false,
]); ]);

View File

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Repository;
use Chill\ActivityBundle\Entity\ActivityPresence;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
class ActivityPresenceRepository implements ActivityPresenceRepositoryInterface
{
private EntityRepository $repository;
public function __construct(EntityManagerInterface $entityManager)
{
$this->repository = $entityManager->getRepository($this->getClassName());
}
public function find($id): ?ActivityPresence
{
return $this->repository->find($id);
}
public function findAll(): array
{
return $this->repository->findAll();
}
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
{
return $this->findBy($criteria, $orderBy, $limit, $offset);
}
public function findOneBy(array $criteria): ?ActivityPresence
{
return $this->findOneBy($criteria);
}
public function getClassName(): string
{
return ActivityPresence::class;
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Repository;
use Chill\ActivityBundle\Entity\ActivityPresence;
interface ActivityPresenceRepositoryInterface
{
public function find($id): ?ActivityPresence;
/**
* @return array|ActivityPresence[]
*/
public function findAll(): array;
/**
* @return array|ActivityPresence[]
*/
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
public function findOneBy(array $criteria): ?ActivityPresence;
public function getClassName(): string;
}

View File

@ -41,6 +41,12 @@ services:
tags: tags:
- { name: chill.export, alias: 'avg_activity_visit_duration_linked_to_acp' } - { name: chill.export, alias: 'avg_activity_visit_duration_linked_to_acp' }
Chill\ActivityBundle\Export\Export\LinkedToACP\ListActivity:
tags:
- { name: chill.export, alias: 'list_activity_acp'}
Chill\ActivityBundle\Export\Export\ListActivityHelper: ~
## Filters ## Filters
chill.activity.export.type_filter: chill.activity.export.type_filter:
class: Chill\ActivityBundle\Export\Filter\ActivityTypeFilter class: Chill\ActivityBundle\Export\Filter\ActivityTypeFilter
@ -129,10 +135,9 @@ services:
tags: tags:
- { name: chill.export_aggregator, alias: activity_reason_aggregator } - { name: chill.export_aggregator, alias: activity_reason_aggregator }
chill.activity.export.type_aggregator: Chill\ActivityBundle\Export\Aggregator\ActivityTypeAggregator:
class: Chill\ActivityBundle\Export\Aggregator\ActivityTypeAggregator
tags: tags:
- { name: chill.export_aggregator, alias: activity_type_aggregator } - { name: chill.export_aggregator, alias: activity_common_type_aggregator }
chill.activity.export.user_aggregator: chill.activity.export.user_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ActivityUserAggregator class: Chill\ActivityBundle\Export\Aggregator\ActivityUserAggregator

View File

@ -45,6 +45,8 @@ by: 'Par '
location: Lieu location: Lieu
Reasons: Sujets Reasons: Sujets
Private comment: Commentaire privé Private comment: Commentaire privé
sent: Envoyé
received: Reçu
#forms #forms
@ -326,6 +328,27 @@ docgen:
Accompanying period with a list of activities description: Ce contexte reprend les informations du parcours, et tous les activités pour un parcours. Les activités ne sont pas filtrés. Accompanying period with a list of activities description: Ce contexte reprend les informations du parcours, et tous les activités pour un parcours. Les activités ne sont pas filtrés.
export: export:
list:
activity:
users name: Nom des utilisateurs
users ids: Identifiant des utilisateurs
third parties ids: Identifiant des tiers
persons ids: Identifiant des personnes
persons name: Nom des personnes
thirds parties: Tiers
date: Date de l'activité
locationName: Localisation
sent received: Envoyé ou reçu
emergency: Urgence
accompanying course id: Identifiant du parcours
course circles: Cercles du parcours
travelTime: Durée de déplacement
durationTime: Durée
id: Identifiant
List activities linked to an accompanying course: Liste les activités liées à un parcours en fonction de différents filtres.
List activity linked to a course: Liste des activités liées à un parcours
filter: filter:
activity: activity:
by_usersjob: by_usersjob:

View File

@ -16,7 +16,7 @@ For this type of activity, document is required: Pour ce type d'activité, un do
For this type of activity, emergency is required: Pour ce type d'activité, le champ "Urgent" est requis For this type of activity, emergency is required: Pour ce type d'activité, le champ "Urgent" est requis
For this type of activity, accompanying period is required: Pour ce type d'activité, le parcours d'accompagnement est requis For this type of activity, accompanying period is required: Pour ce type d'activité, le parcours d'accompagnement est requis
For this type of activity, you must add at least one social issue: Pour ce type d'activité, vous devez ajouter au moins une problématique sociale For this type of activity, you must add at least one social issue: Pour ce type d'activité, vous devez ajouter au moins une problématique sociale
For this type of activity, you must add at least one social action: Pour ce type d'activité, vous devez indiquez au moins une action sociale For this type of activity, you must add at least one social action: Pour ce type d'activité, vous devez indiquer au moins une action sociale
# admin # admin
This parameter must be equal to social issue parameter: Ce paramètre doit être égal au paramètre "Visibilité du champs Problématiques sociales" This parameter must be equal to social issue parameter: Ce paramètre doit être égal au paramètre "Visibilité du champs Problématiques sociales"

View File

@ -7,7 +7,7 @@ import App2 from './App2.vue';
import {useI18n} from "vue-i18n"; import {useI18n} from "vue-i18n";
futureStore().then((store) => { futureStore().then((store) => {
const i18n = _createI18n(appMessages, true); const i18n = _createI18n(appMessages, false);
const app = createApp({ const app = createApp({
template: `<app></app>`, template: `<app></app>`,

View File

@ -37,8 +37,9 @@ class LoadAddressesBEFromBestAddressCommand extends Command
{ {
$this $this
->setName('chill:main:address-ref-from-best-addresses') ->setName('chill:main:address-ref-from-best-addresses')
->addArgument('lang', InputArgument::REQUIRED) ->addArgument('lang', InputArgument::REQUIRED, "Language code, for example 'fr'")
->addArgument('list', InputArgument::IS_ARRAY, 'The list to add'); ->addArgument('list', InputArgument::IS_ARRAY, "The list to add, for example 'full', or 'extract' (dev) or '1xxx' (brussel CP)")
->setDescription('Import BE addresses from BeST Address (see https://osoc19.github.io/best/)');
} }
protected function execute(InputInterface $input, OutputInterface $output): int protected function execute(InputInterface $input, OutputInterface $output): int

View File

@ -31,7 +31,7 @@ class LoadAddressesFRFromBANOCommand extends Command
{ {
$this->setName('chill:main:address-ref-from-bano') $this->setName('chill:main:address-ref-from-bano')
->addArgument('departementNo', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'a list of departement numbers') ->addArgument('departementNo', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'a list of departement numbers')
->setDescription('Import addresses from bano (see https://bano.openstreetmap.fr'); ->setDescription('Import FR addresses from bano (see https://bano.openstreetmap.fr');
} }
protected function execute(InputInterface $input, OutputInterface $output): int protected function execute(InputInterface $input, OutputInterface $output): int

View File

@ -133,8 +133,7 @@ class EntityWorkflowStep
if (!$this->destUser->contains($user)) { if (!$this->destUser->contains($user)) {
$this->destUser[] = $user; $this->destUser[] = $user;
$this->getEntityWorkflow() $this->getEntityWorkflow()
->addSubscriberToFinal($user) ->addSubscriberToFinal($user);
->addSubscriberToStep($user);
} }
return $this; return $this;
@ -145,8 +144,7 @@ class EntityWorkflowStep
if (!$this->destUserByAccessKey->contains($user) && !$this->destUser->contains($user)) { if (!$this->destUserByAccessKey->contains($user) && !$this->destUser->contains($user)) {
$this->destUserByAccessKey[] = $user; $this->destUserByAccessKey[] = $user;
$this->getEntityWorkflow() $this->getEntityWorkflow()
->addSubscriberToFinal($user) ->addSubscriberToFinal($user);
->addSubscriberToStep($user);
} }
return $this; return $this;

View File

@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Export\Helper;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
/**
* This class provides support for showing translatable string into list or exports.
*
* (note: the name of the class contains "ExportLabelHelper" to give a distinction
* with TranslatableStringHelper.)
*/
class TranslatableStringExportLabelHelper
{
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(TranslatableStringHelperInterface $translatableStringHelper)
{
$this->translatableStringHelper = $translatableStringHelper;
}
public function getLabel(string $key, array $values, string $header)
{
return function ($value) use ($header) {
if ('_header' === $value) {
return $header;
}
if (null === $value) {
return '';
}
return $this->translatableStringHelper->localize(json_decode($value, true));
};
}
public function getLabelMulti(string $key, array $values, string $header)
{
return function ($value) use ($header) {
if ('_header' === $value) {
return $header;
}
if (null === $value) {
return '';
}
$decoded = json_decode($value, true);
return implode(
'|',
array_unique(
array_map(
fn (array $translatableString) => $this->translatableStringHelper->localize($translatableString),
array_filter($decoded, static fn ($elem) => null !== $elem)
)
)
);
};
}
}

View File

@ -13,6 +13,8 @@ namespace Chill\MainBundle\Export\Helper;
use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Repository\UserRepositoryInterface;
use Chill\MainBundle\Templating\Entity\UserRender; use Chill\MainBundle\Templating\Entity\UserRender;
use function count;
use const SORT_NUMERIC;
class UserHelper class UserHelper
{ {
@ -40,4 +42,43 @@ class UserHelper
return $this->userRender->renderString($user, []); return $this->userRender->renderString($user, []);
}; };
} }
public function getLabelMulti($key, array $values, string $header): callable
{
return function ($value) {
if ('_header' === $value) {
return 'users name';
}
if (null === $value) {
return '';
}
$decoded = json_decode($value);
if (0 === count($decoded)) {
return '';
}
return
implode(
'|',
array_map(
function (int $userId) {
$user = $this->userRepository->find($userId);
if (null === $user) {
return '';
}
return $this->userRender->renderString($user, []);
},
array_unique(
array_filter($decoded, static fn (?int $userId) => null !== $userId),
SORT_NUMERIC
)
)
);
};
}
} }

View File

@ -82,6 +82,7 @@ header {
border-radius: 0; border-radius: 0;
z-index: 1500; z-index: 1500;
a.dropdown-item { a.dropdown-item {
padding: 0.5rem 1rem;
width: 120%; width: 120%;
border: 0; border: 0;
border-bottom: 1px solid $gray-200; border-bottom: 1px solid $gray-200;

View File

@ -14,9 +14,11 @@
// 4. Include any default map overrides here // 4. Include any default map overrides here
@import "custom/_maps"; @import "custom/_maps";
@import "bootstrap/scss/maps";
// 5. Include remainder of required parts // 5. Include remainder of required parts
@import "bootstrap/scss/mixins"; @import "bootstrap/scss/mixins";
@import "bootstrap/scss/utilities";
@import "bootstrap/scss/root"; @import "bootstrap/scss/root";

View File

@ -2,25 +2,27 @@
<transition name="modal"> <transition name="modal">
<div class="modal-mask"> <div class="modal-mask">
<!-- :: styles bootstrap :: --> <!-- :: styles bootstrap :: -->
<div class="modal-dialog" :class="modalDialogClass"> <div class="modal fade show" style="display: block" aria-modal="true" role="dialog">
<div class="modal-dialog" :class="modalDialogClass">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<slot name="header"></slot> <slot name="header"></slot>
<button class="close btn" @click="$emit('close')"> <button class="close btn" @click="$emit('close')">
<i class="fa fa-times" aria-hidden="true"></i></button> <i class="fa fa-times" aria-hidden="true"></i></button>
</div> </div>
<div class="body-head"> <div class="modal-body">
<div class="body-head">
<slot name="body-head"></slot> <slot name="body-head"></slot>
</div> </div>
<div class="modal-body"> <slot name="body"></slot>
<slot name="body"></slot> </div>
</div> <div class="modal-footer" v-if="!hideFooter">
<div class="modal-footer" v-if="!hideFooter"> <button class="btn btn-cancel" @click="$emit('close')">{{ $t('action.close') }}</button>
<button class="btn btn-cancel" @click="$emit('close')">{{ $t('action.close') }}</button> <slot name="footer"></slot>
<slot name="footer"></slot> </div>
</div>
</div> </div>
</div> </div>
</div>
<!-- :: end styles bootstrap :: --> <!-- :: end styles bootstrap :: -->
</div> </div>
</transition> </transition>
@ -33,7 +35,7 @@ import {defineComponent} from "vue";
* [+] with 'v-if:showModal' directive:parameter, html scope is added/removed not just shown/hidden * [+] with 'v-if:showModal' directive:parameter, html scope is added/removed not just shown/hidden
* [+] with slot we can pass content from parent component * [+] with slot we can pass content from parent component
* [+] some classes are passed from parent component * [+] some classes are passed from parent component
* and Bootstrap 4.6 _modal.scss module * and Bootstrap 5 _modal.scss module
* [+] using bootstrap css classes, the modal have a responsive behaviour, * [+] using bootstrap css classes, the modal have a responsive behaviour,
* [+] modal design can be configured using css classes (size, scroll) * [+] modal design can be configured using css classes (size, scroll)
*/ */
@ -56,6 +58,9 @@ export default defineComponent({
</script> </script>
<style lang="scss"> <style lang="scss">
/**
* This is a mask behind the modal.
*/
.modal-mask { .modal-mask {
position: fixed; position: fixed;
z-index: 9998; z-index: 9998;

View File

@ -97,8 +97,8 @@ class PostalCodeBEFromBestAddress
trim($record['postal_info_objectid']), trim($record['postal_info_objectid']),
$record['municipality_objectid'], $record['municipality_objectid'],
'bestaddress', 'bestaddress',
$record['Y'], (float) $record['Y'],
$record['X'], (float) $record['X'],
3812 3812
); );
} }

View File

@ -26,12 +26,15 @@ buildCKEditor = function(encore)
.addLoader({ .addLoader({
test: /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/, test: /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/,
loader: 'postcss-loader', loader: 'postcss-loader',
options: styles.getPostCssConfig( { options:
themeImporter: { {
postcssOptions: styles.getPostCssConfig( {
themeImporter: {
themePath: require.resolve('@ckeditor/ckeditor5-theme-lark') themePath: require.resolve('@ckeditor/ckeditor5-theme-lark')
}, },
minify: true minify: true
} ) } )
}
} ) } )
; ;
}; };

View File

@ -56,6 +56,12 @@ Until %date%: Jusqu'au %date%
until %date%: jusqu'au %date% until %date%: jusqu'au %date%
Since: Depuis le Since: Depuis le
Until: Jusqu'au Until: Jusqu'au
updatedAt: Mise à jour le
updatedBy: Mise à jour par
createdAt: Créé le
createdBy: Créé par
#elements used in software #elements used in software
centers: centres centers: centres
Centers: Centres Centers: Centres
@ -472,8 +478,8 @@ workflow:
Previous workflow transitionned help: Workflows où vous avez exécuté une action. Previous workflow transitionned help: Workflows où vous avez exécuté une action.
For: Pour For: Pour
You must select a next step, pick another decision if no next steps are available: Il faut une prochaine étape. Choissisez une autre décision si nécessaire. You must select a next step, pick another decision if no next steps are available: Il faut une prochaine étape. Choissisez une autre décision si nécessaire.
An access key was also sent to those addresses: Un lien d'accès a été envoyé à ces addresses An access key was also sent to those addresses: Un lien d'accès a été envoyé à ces adresses
Those users are also granted to apply a transition by using an access key: Ces utilisateurs ont obtennu l'accès grâce au lien reçu par email Those users are also granted to apply a transition by using an access key: Ces utilisateurs ont obtenu l'accès grâce au lien reçu par email
Access link copied: Lien d'accès copié Access link copied: Lien d'accès copié
This link grant any user to apply a transition: Le lien d'accès suivant permet d'appliquer une transition This link grant any user to apply a transition: Le lien d'accès suivant permet d'appliquer une transition
The workflow may be accssed through this link: Une transition peut être appliquée sur ce workflow grâce au lien d'accès suivant The workflow may be accssed through this link: Une transition peut être appliquée sur ce workflow grâce au lien d'accès suivant

View File

@ -46,7 +46,7 @@ class SocialAction
private $desactivationDate; private $desactivationDate;
/** /**
* @ORM\ManyToMany(targetEntity=Evaluation::class, mappedBy="socialActions") * @ORM\ManyToMany(targetEntity=Evaluation::class, inversedBy="socialActions")
* @ORM\JoinTable(name="chill_person_social_work_evaluation_action") * @ORM\JoinTable(name="chill_person_social_work_evaluation_action")
*/ */
private Collection $evaluations; private Collection $evaluations;
@ -62,7 +62,7 @@ class SocialAction
* @ORM\GeneratedValue * @ORM\GeneratedValue
* @ORM\Column(type="integer") * @ORM\Column(type="integer")
*/ */
private $id; private ?int $id = null;
/** /**
* @ORM\ManyToOne(targetEntity=SocialIssue::class, inversedBy="socialActions") * @ORM\ManyToOne(targetEntity=SocialIssue::class, inversedBy="socialActions")

View File

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Export\Helper;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
use function count;
use const SORT_NUMERIC;
class LabelPersonHelper
{
private PersonRenderInterface $personRender;
private PersonRepository $personRepository;
public function __construct(PersonRepository $personRepository, PersonRenderInterface $personRender)
{
$this->personRepository = $personRepository;
$this->personRender = $personRender;
}
public function getLabelMulti(string $key, array $values, string $header): callable
{
return function ($value) use ($header) {
if ('_header' === $value) {
return $header;
}
if (null === $value) {
return '';
}
$decoded = json_decode($value);
if (0 === count($decoded)) {
return '';
}
return
implode(
'|',
array_map(
function (int $personId) {
$person = $this->personRepository->find($personId);
if (null === $person) {
return '';
}
return $this->personRender->renderString($person, []);
},
array_unique(
array_filter($decoded, static fn (?int $id) => null !== $id),
SORT_NUMERIC
)
)
);
};
}
}

View File

@ -5,15 +5,12 @@
<teleport to="#visgraph-legend"> <teleport to="#visgraph-legend">
<div class="post-menu"> <div class="post-menu">
<div class="list-group mt-4"> <div class="list-group mt-4">
<button type="button" class="list-group-item list-group-item-action btn btn-create" @click="createRelationship"> <button type="button" class="list-group-item list-group-item-action btn btn-misc" @click="createRelationship">
{{ $t('visgraph.add_link') }} <i class="fa fa-plus"></i> {{ $t('visgraph.add_link') }}
</button> </button>
<a type="button" class="list-group-item list-group-item-action btn btn-misc" id="exportCanvasBtn" @click="exportCanvasAsImage"> <a type="button" class="list-group-item list-group-item-action btn btn-misc" id="exportCanvasBtn" @click="exportCanvasAsImage">
<i class="fa fa-camera fa-fw"></i> {{ $t('visgraph.screenshot') }} <i class="fa fa-camera fa-fw"></i> {{ $t('visgraph.screenshot') }}
</a> </a>
<button type="button" class="list-group-item list-group-item-action btn btn-light" @click="refreshNetwork">
<i class="fa fa-refresh fa-fw"></i> {{ $t('visgraph.refresh') }}
</button>
</div> </div>
<div v-if="displayHelpMessage" class="alert alert-info mt-3"> <div v-if="displayHelpMessage" class="alert alert-info mt-3">

View File

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ThirdPartyBundle\Export\Helper;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
use function count;
use const SORT_NUMERIC;
class LabelThirdPartyHelper
{
private ThirdPartyRender $thirdPartyRender;
private ThirdPartyRepository $thirdPartyRepository;
public function __construct(ThirdPartyRender $thirdPartyRender, ThirdPartyRepository $thirdPartyRepository)
{
$this->thirdPartyRender = $thirdPartyRender;
$this->thirdPartyRepository = $thirdPartyRepository;
}
public function getLabelMulti(string $key, array $values, string $header): callable
{
return function ($value) use ($header) {
if ('_header' === $value) {
return $header;
}
if (null === $value) {
return '';
}
$decoded = json_decode($value);
if (0 === count($decoded)) {
return '';
}
return
implode(
'|',
array_map(
function (int $tpId) {
$tp = $this->thirdPartyRepository->find($tpId);
if (null === $tp) {
return '';
}
return $this->thirdPartyRender->renderString($tp, []);
},
array_unique(
array_filter($decoded, static fn (?int $id) => null !== $id),
SORT_NUMERIC
)
)
);
};
}
}

View File

@ -6,3 +6,8 @@ services:
tags: tags:
- { name: 'serializer.normalizer', priority: 64 } - { name: 'serializer.normalizer', priority: 64 }
Chill\ThirdPartyBundle\Export\:
autowire: true
autoconfigure: true
resource: '../Export/'