mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Merge remote-tracking branch 'origin/master' into 693-filter-acp-by-user-job
This commit is contained in:
commit
9e63480c70
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,6 +3,7 @@ composer
|
|||||||
composer.phar
|
composer.phar
|
||||||
composer.lock
|
composer.lock
|
||||||
docs/build/
|
docs/build/
|
||||||
|
node_modules/*
|
||||||
.php_cs.cache
|
.php_cs.cache
|
||||||
|
|
||||||
###> symfony/framework-bundle ###
|
###> symfony/framework-bundle ###
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
<?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\Filter\ACPFilters;
|
||||||
|
|
||||||
|
use Chill\ActivityBundle\Export\Declarations;
|
||||||
|
use Chill\MainBundle\Export\FilterInterface;
|
||||||
|
use Chill\MainBundle\Form\Type\PickUserLocationType;
|
||||||
|
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
class LocationFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
private TranslatableStringHelper $translatableStringHelper;
|
||||||
|
|
||||||
|
public function __construct(TranslatableStringHelper $translatableStringHelper)
|
||||||
|
{
|
||||||
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRole(): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function alterQuery(QueryBuilder $qb, $data)
|
||||||
|
{
|
||||||
|
$qb->andWhere(
|
||||||
|
$qb->expr()->in('activity.location', ':location')
|
||||||
|
);
|
||||||
|
|
||||||
|
$qb->setParameter('location', $data['accepted_location']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyOn(): string
|
||||||
|
{
|
||||||
|
return Declarations::ACTIVITY_ACP;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder)
|
||||||
|
{
|
||||||
|
$builder->add('accepted_location', PickUserLocationType::class, [
|
||||||
|
'multiple' => true,
|
||||||
|
'label' => 'pick location'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function describeAction($data, $format = 'string'): array
|
||||||
|
{
|
||||||
|
$locations = [];
|
||||||
|
|
||||||
|
foreach ($data['accepted_location'] as $location) {
|
||||||
|
$locations[] = $location->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['Filtered activity by location: only %locations%', [
|
||||||
|
'%locations%' => implode(', ', $locations),
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return 'Filter activity by location';
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
]);
|
]);
|
||||||
|
@ -36,7 +36,6 @@ final class AdminMenuBuilder implements LocalMenuBuilderInterface
|
|||||||
->setAttribute('class', 'list-group-item-header')
|
->setAttribute('class', 'list-group-item-header')
|
||||||
->setExtras([
|
->setExtras([
|
||||||
'order' => 5000,
|
'order' => 5000,
|
||||||
'icons' => ['exchange'],
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$menu->addChild('Activity Reasons', [
|
$menu->addChild('Activity Reasons', [
|
||||||
|
@ -156,7 +156,7 @@
|
|||||||
<dd>
|
<dd>
|
||||||
<section class="chill-entity entity-comment-embeddable">
|
<section class="chill-entity entity-comment-embeddable">
|
||||||
<blockquote class="chill-user-quote private-quote">
|
<blockquote class="chill-user-quote private-quote">
|
||||||
{{ entity.privateComment.comments[userId] }}
|
{{ entity.privateComment.comments[userId]|chill_markdown_to_html }}
|
||||||
</blockquote>
|
</blockquote>
|
||||||
</section>
|
</section>
|
||||||
</dd>
|
</dd>
|
||||||
@ -168,11 +168,11 @@
|
|||||||
{% if entity.documents|length > 0 %}
|
{% if entity.documents|length > 0 %}
|
||||||
<ul>
|
<ul>
|
||||||
{% for d in entity.documents %}
|
{% for d in entity.documents %}
|
||||||
<li>{{ d.title }}{{ m.download_button(d) }}</li>
|
<li>{{ d.title }} {{ d|chill_document_button_group() }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="chill-no-data-statement">{{ 'Any document found'|trans }}</span>
|
<span class="chill-no-data-statement">{{ 'No document found'|trans }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</dd>
|
</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -8,12 +8,14 @@
|
|||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{{ encore_entry_script_tags('mod_notification_toggle_read_status') }}
|
{{ encore_entry_script_tags('mod_notification_toggle_read_status') }}
|
||||||
{{ encore_entry_script_tags('mod_async_upload') }}
|
{{ encore_entry_script_tags('mod_async_upload') }}
|
||||||
|
{{ encore_entry_script_tags('mod_document_action_buttons_group') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{{ encore_entry_link_tags('mod_notification_toggle_read_status') }}
|
{{ encore_entry_link_tags('mod_notification_toggle_read_status') }}
|
||||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||||
|
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %}
|
{% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %}
|
||||||
|
@ -7,13 +7,13 @@
|
|||||||
{% block js %}
|
{% block js %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{{ encore_entry_script_tags('mod_notification_toggle_read_status') }}
|
{{ encore_entry_script_tags('mod_notification_toggle_read_status') }}
|
||||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
{{ encore_entry_script_tags('mod_document_action_buttons_group') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{{ encore_entry_link_tags('mod_notification_toggle_read_status') }}
|
{{ encore_entry_link_tags('mod_notification_toggle_read_status') }}
|
||||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %}
|
{% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %}
|
||||||
|
@ -79,6 +79,11 @@ services:
|
|||||||
tags:
|
tags:
|
||||||
- { name: chill.export_filter, alias: 'accompanyingcourse_activitytype_filter' }
|
- { name: chill.export_filter, alias: 'accompanyingcourse_activitytype_filter' }
|
||||||
|
|
||||||
|
chill.activity.export.location_filter:
|
||||||
|
class: Chill\ActivityBundle\Export\Filter\ACPFilters\LocationFilter
|
||||||
|
tags:
|
||||||
|
- { name: chill.export_filter, alias: 'activity_location_filter' }
|
||||||
|
|
||||||
chill.activity.export.locationtype_filter:
|
chill.activity.export.locationtype_filter:
|
||||||
class: Chill\ActivityBundle\Export\Filter\ACPFilters\LocationTypeFilter
|
class: Chill\ActivityBundle\Export\Filter\ACPFilters\LocationTypeFilter
|
||||||
tags:
|
tags:
|
||||||
|
@ -252,6 +252,8 @@ Activity reasons for those activities: Sujets de ces activités
|
|||||||
|
|
||||||
Filter by activity type: Filtrer les activités par type
|
Filter by activity type: Filtrer les activités par type
|
||||||
|
|
||||||
|
Filter activity by location: Filtrer les activités par localisation
|
||||||
|
'Filtered activity by location: only %locations%': "Filtré par localisation: uniquement %locations%"
|
||||||
Filter activity by locationtype: Filtrer les activités par type de localisation
|
Filter activity by locationtype: Filtrer les activités par type de localisation
|
||||||
'Filtered activity by locationtype: only %types%': "Filtré par type de localisation: uniquement %types%"
|
'Filtered activity by locationtype: only %types%': "Filtré par type de localisation: uniquement %types%"
|
||||||
Accepted locationtype: Types de localisation
|
Accepted locationtype: Types de localisation
|
||||||
|
@ -53,19 +53,15 @@ class ByActivityTypeAggregator implements AggregatorInterface
|
|||||||
|
|
||||||
public function getLabels($key, array $values, $data)
|
public function getLabels($key, array $values, $data)
|
||||||
{
|
{
|
||||||
$this->asideActivityCategoryRepository->findBy(['id' => $values]);
|
|
||||||
|
|
||||||
return function ($value): string {
|
return function ($value): string {
|
||||||
if ('_header' === $value) {
|
if ('_header' === $value) {
|
||||||
return 'export.aggregator.Aside activity type';
|
return 'export.aggregator.Aside activity type';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null === $value) {
|
if (null === $value || null === $t = $this->asideActivityCategoryRepository->find($value)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$t = $this->asideActivityCategoryRepository->find($value);
|
|
||||||
|
|
||||||
return $this->translatableStringHelper->localize($t->getTitle());
|
return $this->translatableStringHelper->localize($t->getTitle());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
<?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\AsideActivityBundle\Export\Aggregator;
|
||||||
|
|
||||||
|
use Chill\AsideActivityBundle\Export\Declarations;
|
||||||
|
use Chill\MainBundle\Export\AggregatorInterface;
|
||||||
|
use Chill\MainBundle\Repository\UserJobRepositoryInterface;
|
||||||
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
use function in_array;
|
||||||
|
|
||||||
|
class ByUserJobAggregator implements AggregatorInterface
|
||||||
|
{
|
||||||
|
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||||
|
|
||||||
|
private UserJobRepositoryInterface $userJobRepository;
|
||||||
|
|
||||||
|
public function __construct(UserJobRepositoryInterface $userJobRepository, TranslatableStringHelperInterface $translatableStringHelper)
|
||||||
|
{
|
||||||
|
$this->userJobRepository = $userJobRepository;
|
||||||
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRole(): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function alterQuery(QueryBuilder $qb, $data)
|
||||||
|
{
|
||||||
|
if (!in_array('aside_user', $qb->getAllAliases(), true)) {
|
||||||
|
$qb->leftJoin('aside.agent', 'aside_user');
|
||||||
|
}
|
||||||
|
|
||||||
|
$qb
|
||||||
|
->addSelect('IDENTITY(aside_user.userJob) AS aside_activity_user_job_aggregator')
|
||||||
|
->addGroupBy('aside_activity_user_job_aggregator');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyOn()
|
||||||
|
{
|
||||||
|
return Declarations::ASIDE_ACTIVITY_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder)
|
||||||
|
{
|
||||||
|
// nothing to add in the form
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabels($key, array $values, $data)
|
||||||
|
{
|
||||||
|
return function ($value): string {
|
||||||
|
if ('_header' === $value) {
|
||||||
|
return 'Users \'s job';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $value || '' === $value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$j = $this->userJobRepository->find($value);
|
||||||
|
|
||||||
|
return $this->translatableStringHelper->localize(
|
||||||
|
$j->getLabel()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQueryKeys($data): array
|
||||||
|
{
|
||||||
|
return ['aside_activity_user_job_aggregator'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle()
|
||||||
|
{
|
||||||
|
return 'export.aggregator.Aggregate by user job';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
<?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\AsideActivityBundle\Export\Aggregator;
|
||||||
|
|
||||||
|
use Chill\AsideActivityBundle\Export\Declarations;
|
||||||
|
use Chill\MainBundle\Export\AggregatorInterface;
|
||||||
|
use Chill\MainBundle\Repository\ScopeRepositoryInterface;
|
||||||
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
use function in_array;
|
||||||
|
|
||||||
|
class ByUserScopeAggregator implements AggregatorInterface
|
||||||
|
{
|
||||||
|
private ScopeRepositoryInterface $scopeRepository;
|
||||||
|
|
||||||
|
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||||
|
|
||||||
|
public function __construct(ScopeRepositoryInterface $scopeRepository, TranslatableStringHelperInterface $translatableStringHelper)
|
||||||
|
{
|
||||||
|
$this->scopeRepository = $scopeRepository;
|
||||||
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRole(): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function alterQuery(QueryBuilder $qb, $data)
|
||||||
|
{
|
||||||
|
if (!in_array('aside_user', $qb->getAllAliases(), true)) {
|
||||||
|
$qb->leftJoin('aside.agent', 'aside_user');
|
||||||
|
}
|
||||||
|
|
||||||
|
$qb
|
||||||
|
->addSelect('IDENTITY(aside_user.mainScope) AS aside_activity_user_scope_aggregator')
|
||||||
|
->addGroupBy('aside_activity_user_scope_aggregator');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyOn()
|
||||||
|
{
|
||||||
|
return Declarations::ASIDE_ACTIVITY_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder)
|
||||||
|
{
|
||||||
|
// nothing to add in the form
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabels($key, array $values, $data)
|
||||||
|
{
|
||||||
|
return function ($value): string {
|
||||||
|
if ('_header' === $value) {
|
||||||
|
return 'Users \'s scope';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $value || '' === $value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$s = $this->scopeRepository->find($value);
|
||||||
|
|
||||||
|
return $this->translatableStringHelper->localize(
|
||||||
|
$s->getName()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQueryKeys($data): array
|
||||||
|
{
|
||||||
|
return ['aside_activity_user_scope_aggregator'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle()
|
||||||
|
{
|
||||||
|
return 'export.aggregator.Aggregate by user scope';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
<?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\AsideActivityBundle\Export\Export;
|
||||||
|
|
||||||
|
use Chill\AsideActivityBundle\Export\Declarations;
|
||||||
|
use Chill\AsideActivityBundle\Repository\AsideActivityRepository;
|
||||||
|
use Chill\AsideActivityBundle\Security\AsideActivityVoter;
|
||||||
|
use Chill\MainBundle\Export\ExportInterface;
|
||||||
|
use Chill\MainBundle\Export\FormatterInterface;
|
||||||
|
use Chill\MainBundle\Export\GroupedExportInterface;
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
|
use LogicException;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
class AvgAsideActivityDuration implements ExportInterface, GroupedExportInterface
|
||||||
|
{
|
||||||
|
private AsideActivityRepository $repository;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
AsideActivityRepository $repository
|
||||||
|
) {
|
||||||
|
$this->repository = $repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllowedFormattersTypes(): array
|
||||||
|
{
|
||||||
|
return [FormatterInterface::TYPE_TABULAR];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'export.Average aside activities duration';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGroup(): string
|
||||||
|
{
|
||||||
|
return 'export.Exports of aside activities';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabels($key, array $values, $data)
|
||||||
|
{
|
||||||
|
if ('export_avg_aside_activity_duration' !== $key) {
|
||||||
|
throw new LogicException("the key {$key} is not used by this export");
|
||||||
|
}
|
||||||
|
|
||||||
|
return static fn ($value) => '_header' === $value ? 'Average duration aside activities' : $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQueryKeys($data): array
|
||||||
|
{
|
||||||
|
return ['export_avg_aside_activity_duration'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResult($query, $data)
|
||||||
|
{
|
||||||
|
return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return 'export.Average aside activities duration';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return Declarations::ASIDE_ACTIVITY_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
|
||||||
|
{
|
||||||
|
$qb = $this->repository->createQueryBuilder('aside');
|
||||||
|
|
||||||
|
$qb
|
||||||
|
->select('AVG(aside.duration) as export_avg_aside_activity_duration')
|
||||||
|
->andWhere($qb->expr()->isNotNull('aside.duration'));
|
||||||
|
|
||||||
|
return $qb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function requiredRole(): string
|
||||||
|
{
|
||||||
|
return AsideActivityVoter::STATS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsModifiers(): array
|
||||||
|
{
|
||||||
|
return [Declarations::ASIDE_ACTIVITY_TYPE];
|
||||||
|
}
|
||||||
|
}
|
@ -11,12 +11,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\AsideActivityBundle\Export\Export;
|
namespace Chill\AsideActivityBundle\Export\Export;
|
||||||
|
|
||||||
|
use Chill\AsideActivityBundle\Export\Declarations;
|
||||||
use Chill\AsideActivityBundle\Repository\AsideActivityRepository;
|
use Chill\AsideActivityBundle\Repository\AsideActivityRepository;
|
||||||
use Chill\AsideActivityBundle\Security\AsideActivityVoter;
|
use Chill\AsideActivityBundle\Security\AsideActivityVoter;
|
||||||
use Chill\MainBundle\Export\ExportInterface;
|
use Chill\MainBundle\Export\ExportInterface;
|
||||||
use Chill\MainBundle\Export\FormatterInterface;
|
use Chill\MainBundle\Export\FormatterInterface;
|
||||||
use Chill\MainBundle\Export\GroupedExportInterface;
|
use Chill\MainBundle\Export\GroupedExportInterface;
|
||||||
use ChillAsideActivityBundle\Export\Declarations;
|
|
||||||
use Doctrine\ORM\Query;
|
use Doctrine\ORM\Query;
|
||||||
use LogicException;
|
use LogicException;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
@ -100,6 +100,8 @@ class CountAsideActivity implements ExportInterface, GroupedExportInterface
|
|||||||
|
|
||||||
public function supportsModifiers(): array
|
public function supportsModifiers(): array
|
||||||
{
|
{
|
||||||
return [];
|
return [
|
||||||
|
Declarations::ASIDE_ACTIVITY_TYPE,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,236 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\AsideActivityBundle\Export\Export;
|
||||||
|
|
||||||
|
use Chill\AsideActivityBundle\Entity\AsideActivity;
|
||||||
|
use Chill\AsideActivityBundle\Export\Declarations;
|
||||||
|
use Chill\AsideActivityBundle\Form\AsideActivityCategoryType;
|
||||||
|
use Chill\AsideActivityBundle\Repository\AsideActivityCategoryRepository;
|
||||||
|
use Chill\AsideActivityBundle\Security\AsideActivityVoter;
|
||||||
|
use Chill\AsideActivityBundle\Templating\Entity\CategoryRender;
|
||||||
|
use Chill\MainBundle\Entity\Center;
|
||||||
|
use Chill\MainBundle\Export\FormatterInterface;
|
||||||
|
use Chill\MainBundle\Export\GroupedExportInterface;
|
||||||
|
use Chill\MainBundle\Export\Helper\DateTimeHelper;
|
||||||
|
use Chill\MainBundle\Export\Helper\UserHelper;
|
||||||
|
use Chill\MainBundle\Export\ListInterface;
|
||||||
|
use Chill\MainBundle\Repository\CenterRepositoryInterface;
|
||||||
|
use Chill\MainBundle\Repository\ScopeRepositoryInterface;
|
||||||
|
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||||
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
|
use Closure;
|
||||||
|
use Doctrine\ORM\AbstractQuery;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
final class ListAsideActivity implements ListInterface, GroupedExportInterface
|
||||||
|
{
|
||||||
|
private EntityManagerInterface $em;
|
||||||
|
|
||||||
|
private UserHelper $userHelper;
|
||||||
|
|
||||||
|
private DateTimeHelper $dateTimeHelper;
|
||||||
|
|
||||||
|
private ScopeRepositoryInterface $scopeRepository;
|
||||||
|
|
||||||
|
private CenterRepositoryInterface $centerRepository;
|
||||||
|
|
||||||
|
private AsideActivityCategoryRepository $asideActivityCategoryRepository;
|
||||||
|
|
||||||
|
private CategoryRender $categoryRender;
|
||||||
|
|
||||||
|
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
EntityManagerInterface $em,
|
||||||
|
DateTimeHelper $dateTimeHelper,
|
||||||
|
UserHelper $userHelper,
|
||||||
|
ScopeRepositoryInterface $scopeRepository,
|
||||||
|
CenterRepositoryInterface $centerRepository,
|
||||||
|
AsideActivityCategoryRepository $asideActivityCategoryRepository,
|
||||||
|
CategoryRender $categoryRender,
|
||||||
|
TranslatableStringHelperInterface $translatableStringHelper
|
||||||
|
) {
|
||||||
|
$this->em = $em;
|
||||||
|
$this->dateTimeHelper = $dateTimeHelper;
|
||||||
|
$this->userHelper = $userHelper;
|
||||||
|
$this->scopeRepository = $scopeRepository;
|
||||||
|
$this->centerRepository = $centerRepository;
|
||||||
|
$this->asideActivityCategoryRepository = $asideActivityCategoryRepository;
|
||||||
|
$this->categoryRender = $categoryRender;
|
||||||
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllowedFormattersTypes()
|
||||||
|
{
|
||||||
|
return [FormatterInterface::TYPE_LIST];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription()
|
||||||
|
{
|
||||||
|
return 'export.aside_activity.List of aside activities';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle()
|
||||||
|
{
|
||||||
|
return 'export.aside_activity.List of aside activities';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGroup(): string
|
||||||
|
{
|
||||||
|
return 'export.Exports of aside activities';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabels($key, array $values, $data)
|
||||||
|
{
|
||||||
|
switch ($key) {
|
||||||
|
case 'id':
|
||||||
|
case 'note':
|
||||||
|
return function ($value) use ($key) {
|
||||||
|
if ('_header' === $value) {
|
||||||
|
return 'export.aside_activity.' . $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value ?? '';
|
||||||
|
};
|
||||||
|
case 'duration':
|
||||||
|
return function ($value) use ($key) {
|
||||||
|
if ('_header' === $value) {
|
||||||
|
return 'export.aside_activity.' . $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value instanceof \DateTimeInterface) {
|
||||||
|
return $value->format('H:i:s');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
};
|
||||||
|
|
||||||
|
case 'createdAt':
|
||||||
|
case 'updatedAt':
|
||||||
|
case 'date':
|
||||||
|
return $this->dateTimeHelper->getLabel('export.aside_activity.'.$key);
|
||||||
|
|
||||||
|
case 'agent_id':
|
||||||
|
case 'creator_id':
|
||||||
|
return $this->userHelper->getLabel($key, $values, 'export.aside_activity.' . $key);
|
||||||
|
|
||||||
|
case 'aside_activity_type':
|
||||||
|
return function ($value) {
|
||||||
|
if ('_header' === $value) {
|
||||||
|
return 'export.aside_activity.aside_activity_type';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $value || '' === $value || null === $c = $this->asideActivityCategoryRepository->find($value)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->categoryRender->renderString($c, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
case 'main_scope':
|
||||||
|
return function ($value) {
|
||||||
|
if ('_header' === $value) {
|
||||||
|
return 'export.aside_activity.main_scope';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $value || '' === $value || null === $c = $this->scopeRepository->find($value)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->translatableStringHelper->localize($c->getName());
|
||||||
|
};
|
||||||
|
|
||||||
|
case 'main_center':
|
||||||
|
return function ($value) {
|
||||||
|
if ('_header' === $value) {
|
||||||
|
return 'export.aside_activity.main_center';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Center $c */
|
||||||
|
if (null === $value || '' === $value || null === $c = $this->centerRepository->find($value)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $c->getName();
|
||||||
|
};
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new \LogicException('this key is not supported : ' . $key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQueryKeys($data)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id',
|
||||||
|
'createdAt',
|
||||||
|
'updatedAt',
|
||||||
|
'agent_id',
|
||||||
|
'creator_id',
|
||||||
|
'main_scope',
|
||||||
|
'main_center',
|
||||||
|
'aside_activity_type',
|
||||||
|
'date',
|
||||||
|
'duration',
|
||||||
|
'note'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param QueryBuilder $query
|
||||||
|
* @param array $data
|
||||||
|
*/
|
||||||
|
public function getResult($query, $data): array
|
||||||
|
{
|
||||||
|
return $query->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return Declarations::ASIDE_ACTIVITY_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
|
||||||
|
{
|
||||||
|
$qb = $this->em->createQueryBuilder()
|
||||||
|
->from(AsideActivity::class, 'aside')
|
||||||
|
->leftJoin('aside.agent', 'agent')
|
||||||
|
;
|
||||||
|
|
||||||
|
$qb
|
||||||
|
->addSelect('aside.id AS id')
|
||||||
|
->addSelect('aside.createdAt AS createdAt')
|
||||||
|
->addSelect('aside.updatedAt AS updatedAt')
|
||||||
|
->addSelect('IDENTITY(aside.agent) AS agent_id')
|
||||||
|
->addSelect('IDENTITY(aside.createdBy) AS creator_id')
|
||||||
|
->addSelect('IDENTITY(agent.mainScope) AS main_scope')
|
||||||
|
->addSelect('IDENTITY(agent.mainCenter) AS main_center')
|
||||||
|
->addSelect('IDENTITY(aside.type) AS aside_activity_type')
|
||||||
|
->addSelect('aside.date')
|
||||||
|
->addSelect('aside.duration')
|
||||||
|
->addSelect('aside.note')
|
||||||
|
;
|
||||||
|
|
||||||
|
return $qb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function requiredRole(): string
|
||||||
|
{
|
||||||
|
return AsideActivityVoter::STATS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsModifiers()
|
||||||
|
{
|
||||||
|
return [Declarations::ASIDE_ACTIVITY_TYPE];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
<?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\AsideActivityBundle\Export\Export;
|
||||||
|
|
||||||
|
use Chill\AsideActivityBundle\Export\Declarations;
|
||||||
|
use Chill\AsideActivityBundle\Repository\AsideActivityRepository;
|
||||||
|
use Chill\AsideActivityBundle\Security\AsideActivityVoter;
|
||||||
|
use Chill\MainBundle\Export\ExportInterface;
|
||||||
|
use Chill\MainBundle\Export\FormatterInterface;
|
||||||
|
use Chill\MainBundle\Export\GroupedExportInterface;
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
|
use LogicException;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
class SumAsideActivityDuration implements ExportInterface, GroupedExportInterface
|
||||||
|
{
|
||||||
|
private AsideActivityRepository $repository;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
AsideActivityRepository $repository
|
||||||
|
) {
|
||||||
|
$this->repository = $repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllowedFormattersTypes(): array
|
||||||
|
{
|
||||||
|
return [FormatterInterface::TYPE_TABULAR];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'export.Sum aside activities duration';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGroup(): string
|
||||||
|
{
|
||||||
|
return 'export.Exports of aside activities';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabels($key, array $values, $data)
|
||||||
|
{
|
||||||
|
if ('export_sum_aside_activity_duration' !== $key) {
|
||||||
|
throw new LogicException("the key {$key} is not used by this export");
|
||||||
|
}
|
||||||
|
|
||||||
|
return static fn ($value) => '_header' === $value ? 'Sum duration aside activities' : $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQueryKeys($data): array
|
||||||
|
{
|
||||||
|
return ['export_sum_aside_activity_duration'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResult($query, $data)
|
||||||
|
{
|
||||||
|
return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return 'export.Sum aside activities duration';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return Declarations::ASIDE_ACTIVITY_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
|
||||||
|
{
|
||||||
|
$qb = $this->repository
|
||||||
|
->createQueryBuilder('aside');
|
||||||
|
|
||||||
|
$qb->select('SUM(aside.duration) as export_sum_aside_activity_duration')
|
||||||
|
->andWhere($qb->expr()->isNotNull('aside.duration'));
|
||||||
|
|
||||||
|
return $qb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function requiredRole(): string
|
||||||
|
{
|
||||||
|
return AsideActivityVoter::STATS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsModifiers(): array
|
||||||
|
{
|
||||||
|
return [Declarations::ASIDE_ACTIVITY_TYPE];
|
||||||
|
}
|
||||||
|
}
|
@ -80,8 +80,8 @@ class ByActivityTypeFilter implements FilterInterface
|
|||||||
public function describeAction($data, $format = 'string'): array
|
public function describeAction($data, $format = 'string'): array
|
||||||
{
|
{
|
||||||
$types = array_map(
|
$types = array_map(
|
||||||
fn (AsideActivityCategory $t): string => $this->translatableStringHelper->localize($t->getName()),
|
fn (AsideActivityCategory $t): string => $this->translatableStringHelper->localize($t->getTitle()),
|
||||||
$this->asideActivityTypeRepository->findBy(['id' => $data['types']->toArray()])
|
$data['types']->toArray()
|
||||||
);
|
);
|
||||||
|
|
||||||
return ['export.filter.Filtered by aside activity type: only %type%', [
|
return ['export.filter.Filtered by aside activity type: only %type%', [
|
||||||
|
@ -46,25 +46,18 @@ class ByDateFilter implements FilterInterface
|
|||||||
|
|
||||||
public function alterQuery(QueryBuilder $qb, $data)
|
public function alterQuery(QueryBuilder $qb, $data)
|
||||||
{
|
{
|
||||||
$where = $qb->getDQLPart('where');
|
|
||||||
$clause = $qb->expr()->between(
|
$clause = $qb->expr()->between(
|
||||||
'aside.date',
|
'aside.date',
|
||||||
':date_from',
|
':date_from',
|
||||||
':date_to'
|
':date_to'
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($where instanceof Andx) {
|
$qb->andWhere($clause);
|
||||||
$where->add($clause);
|
|
||||||
} else {
|
|
||||||
$where = $qb->expr()->andX($clause);
|
|
||||||
}
|
|
||||||
|
|
||||||
$qb->add('where', $where);
|
|
||||||
$qb->setParameter(
|
$qb->setParameter(
|
||||||
'date_from',
|
'date_from',
|
||||||
$this->rollingDateConverter->convert($data['date_from'])
|
$this->rollingDateConverter->convert($data['date_from'])
|
||||||
);
|
)->setParameter(
|
||||||
$qb->setParameter(
|
|
||||||
'date_to',
|
'date_to',
|
||||||
$this->rollingDateConverter->convert($data['date_to'])
|
$this->rollingDateConverter->convert($data['date_to'])
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
<?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\AsideActivityBundle\Export\Filter;
|
||||||
|
|
||||||
|
use Chill\AsideActivityBundle\Export\Declarations;
|
||||||
|
use Chill\MainBundle\Export\FilterInterface;
|
||||||
|
use Chill\MainBundle\Form\Type\PickUserDynamicType;
|
||||||
|
use Chill\MainBundle\Templating\Entity\UserRender;
|
||||||
|
use Doctrine\ORM\Query\Expr\Andx;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
class ByUserFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
private UserRender $userRender;
|
||||||
|
|
||||||
|
public function __construct(UserRender $userRender)
|
||||||
|
{
|
||||||
|
$this->userRender = $userRender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRole(): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function alterQuery(QueryBuilder $qb, $data)
|
||||||
|
{
|
||||||
|
$clause = $qb->expr()->in('aside.agent', ':users');
|
||||||
|
|
||||||
|
$qb
|
||||||
|
->andWhere($clause)
|
||||||
|
->setParameter('users', $data['accepted_users']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyOn(): string
|
||||||
|
{
|
||||||
|
return Declarations::ASIDE_ACTIVITY_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder)
|
||||||
|
{
|
||||||
|
$builder->add('accepted_users', PickUserDynamicType::class, [
|
||||||
|
'multiple' => true,
|
||||||
|
'label' => 'Creators',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function describeAction($data, $format = 'string'): array
|
||||||
|
{
|
||||||
|
$users = [];
|
||||||
|
|
||||||
|
foreach ($data['accepted_users'] as $u) {
|
||||||
|
$users[] = $this->userRender->renderString($u, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['export.filter.Filtered aside activity by user: only %users%', [
|
||||||
|
'%users%' => implode(', ', $users),
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return 'export.filter.Filter aside activity by user';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
<?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\AsideActivityBundle\Export\Filter;
|
||||||
|
|
||||||
|
use Chill\AsideActivityBundle\Entity\AsideActivity;
|
||||||
|
use Chill\AsideActivityBundle\Export\Declarations;
|
||||||
|
use Chill\MainBundle\Entity\UserJob;
|
||||||
|
use Chill\MainBundle\Export\FilterInterface;
|
||||||
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
class ByUserJobFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||||
|
|
||||||
|
public function __construct(TranslatableStringHelperInterface $translatableStringHelper)
|
||||||
|
{
|
||||||
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRole(): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function alterQuery(QueryBuilder $qb, $data)
|
||||||
|
{
|
||||||
|
$qb
|
||||||
|
->andWhere(
|
||||||
|
$qb->expr()->exists(
|
||||||
|
'SELECT 1 FROM ' . AsideActivity::class . ' aside_activity_user_job_filter_act
|
||||||
|
JOIN aside_activity_user_job_filter_act.agent aside_activity_user_job_filter_user WHERE aside_activity_user_job_filter_user.userJob IN (:aside_activity_user_job_filter_jobs) AND aside_activity_user_job_filter_act = aside'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
->setParameter('aside_activity_user_job_filter_jobs', $data['jobs']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyOn()
|
||||||
|
{
|
||||||
|
return Declarations::ASIDE_ACTIVITY_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder)
|
||||||
|
{
|
||||||
|
$builder->add('jobs', EntityType::class, [
|
||||||
|
'class' => UserJob::class,
|
||||||
|
'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize($j->getLabel()),
|
||||||
|
'multiple' => true,
|
||||||
|
'expanded' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function describeAction($data, $format = 'string')
|
||||||
|
{
|
||||||
|
return ['export.filter.Filtered aside activities by user jobs: only %jobs%', [
|
||||||
|
'%jobs%' => implode(
|
||||||
|
', ',
|
||||||
|
array_map(
|
||||||
|
fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()),
|
||||||
|
$data['jobs']->toArray()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle()
|
||||||
|
{
|
||||||
|
return 'export.filter.Filter by user jobs';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
<?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\AsideActivityBundle\Export\Filter;
|
||||||
|
|
||||||
|
use Chill\AsideActivityBundle\Entity\AsideActivity;
|
||||||
|
use Chill\AsideActivityBundle\Export\Declarations;
|
||||||
|
use Chill\MainBundle\Entity\Scope;
|
||||||
|
use Chill\MainBundle\Export\FilterInterface;
|
||||||
|
use Chill\MainBundle\Repository\ScopeRepositoryInterface;
|
||||||
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
class ByUserScopeFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
private ScopeRepositoryInterface $scopeRepository;
|
||||||
|
|
||||||
|
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
ScopeRepositoryInterface $scopeRepository,
|
||||||
|
TranslatableStringHelperInterface $translatableStringHelper
|
||||||
|
) {
|
||||||
|
$this->scopeRepository = $scopeRepository;
|
||||||
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRole(): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function alterQuery(QueryBuilder $qb, $data)
|
||||||
|
{
|
||||||
|
$qb
|
||||||
|
->andWhere(
|
||||||
|
$qb->expr()->exists(
|
||||||
|
'SELECT 1 FROM ' . AsideActivity::class . ' aside_activity_user_scope_filter_act
|
||||||
|
JOIN aside_activity_user_scope_filter_act.agent aside_activity_user_scope_filter_user WHERE aside_activity_user_scope_filter_user.mainScope IN (:aside_activity_user_scope_filter_scopes) AND aside_activity_user_scope_filter_act = aside '
|
||||||
|
)
|
||||||
|
)
|
||||||
|
->setParameter('aside_activity_user_scope_filter_scopes', $data['scopes']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyOn()
|
||||||
|
{
|
||||||
|
return Declarations::ASIDE_ACTIVITY_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder)
|
||||||
|
{
|
||||||
|
$builder->add('scopes', EntityType::class, [
|
||||||
|
'class' => Scope::class,
|
||||||
|
'choices' => $this->scopeRepository->findAllActive(),
|
||||||
|
'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()),
|
||||||
|
'multiple' => true,
|
||||||
|
'expanded' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function describeAction($data, $format = 'string')
|
||||||
|
{
|
||||||
|
return ['export.filter.Filtered aside activities by user scope: only %scopes%', [
|
||||||
|
'%scopes%' => implode(
|
||||||
|
', ',
|
||||||
|
array_map(
|
||||||
|
fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()),
|
||||||
|
$data['scopes']->toArray()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle()
|
||||||
|
{
|
||||||
|
return 'export.filter.Filter by user scope';
|
||||||
|
}
|
||||||
|
}
|
@ -12,8 +12,7 @@ declare(strict_types=1);
|
|||||||
namespace Chill\AsideActivityBundle\Form;
|
namespace Chill\AsideActivityBundle\Form;
|
||||||
|
|
||||||
use Chill\AsideActivityBundle\Entity\AsideActivity;
|
use Chill\AsideActivityBundle\Entity\AsideActivity;
|
||||||
use Chill\AsideActivityBundle\Entity\AsideActivityCategory;
|
use Chill\AsideActivityBundle\Form\Type\PickAsideActivityCategoryType;
|
||||||
use Chill\AsideActivityBundle\Templating\Entity\CategoryRender;
|
|
||||||
use Chill\MainBundle\Form\Type\ChillDateType;
|
use Chill\MainBundle\Form\Type\ChillDateType;
|
||||||
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
||||||
use Chill\MainBundle\Form\Type\PickUserDynamicType;
|
use Chill\MainBundle\Form\Type\PickUserDynamicType;
|
||||||
@ -21,8 +20,6 @@ use DateInterval;
|
|||||||
use DateTime;
|
use DateTime;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use DateTimeZone;
|
use DateTimeZone;
|
||||||
use Doctrine\ORM\EntityRepository;
|
|
||||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
|
||||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
|
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
|
||||||
@ -37,20 +34,16 @@ use function in_array;
|
|||||||
|
|
||||||
final class AsideActivityFormType extends AbstractType
|
final class AsideActivityFormType extends AbstractType
|
||||||
{
|
{
|
||||||
private CategoryRender $categoryRender;
|
|
||||||
|
|
||||||
private TokenStorageInterface $storage;
|
private TokenStorageInterface $storage;
|
||||||
|
|
||||||
private array $timeChoices;
|
private array $timeChoices;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
ParameterBagInterface $parameterBag,
|
ParameterBagInterface $parameterBag,
|
||||||
TokenStorageInterface $storage,
|
TokenStorageInterface $storage
|
||||||
CategoryRender $categoryRender
|
|
||||||
) {
|
) {
|
||||||
$this->timeChoices = $parameterBag->get('chill_aside_activity.form.time_duration');
|
$this->timeChoices = $parameterBag->get('chill_aside_activity.form.time_duration');
|
||||||
$this->storage = $storage;
|
$this->storage = $storage;
|
||||||
$this->categoryRender = $categoryRender;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
@ -81,28 +74,10 @@ final class AsideActivityFormType extends AbstractType
|
|||||||
'required' => true,
|
'required' => true,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
->add(
|
->add('type', PickAsideActivityCategoryType::class, [
|
||||||
'type',
|
'label' => 'Type',
|
||||||
EntityType::class,
|
'required' => true,
|
||||||
[
|
])
|
||||||
'label' => 'Type',
|
|
||||||
'required' => true,
|
|
||||||
'class' => AsideActivityCategory::class,
|
|
||||||
'placeholder' => 'Choose the activity category',
|
|
||||||
'query_builder' => static 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) {
|
|
||||||
$options = [];
|
|
||||||
|
|
||||||
return $this->categoryRender->renderString($asideActivityCategory, $options);
|
|
||||||
},
|
|
||||||
]
|
|
||||||
)
|
|
||||||
->add('duration', ChoiceType::class, $durationTimeOptions)
|
->add('duration', ChoiceType::class, $durationTimeOptions)
|
||||||
->add('note', ChillTextareaType::class, [
|
->add('note', ChillTextareaType::class, [
|
||||||
'label' => 'Note',
|
'label' => 'Note',
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
<?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\AsideActivityBundle\Form\Type;
|
||||||
|
|
||||||
|
use Chill\AsideActivityBundle\Entity\AsideActivityCategory;
|
||||||
|
use Chill\AsideActivityBundle\Templating\Entity\CategoryRender;
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
final class PickAsideActivityCategoryType extends AbstractType
|
||||||
|
{
|
||||||
|
private CategoryRender $categoryRender;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
CategoryRender $categoryRender
|
||||||
|
) {
|
||||||
|
$this->categoryRender = $categoryRender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
|
{
|
||||||
|
$resolver
|
||||||
|
->setDefaults([
|
||||||
|
'class' => AsideActivityCategory::class,
|
||||||
|
'placeholder' => 'Choose the activity category',
|
||||||
|
'query_builder' => static 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) {
|
||||||
|
$options = [];
|
||||||
|
|
||||||
|
return $this->categoryRender->renderString($asideActivityCategory, $options);
|
||||||
|
},
|
||||||
|
'attr' => ['class' => 'select2'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParent(): string
|
||||||
|
{
|
||||||
|
return EntityType::class;
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
<div class="{% block crud_content_main_div_class %}col-10 centered{% endblock %}">
|
|
||||||
{% block crud_content_header %}
|
{% block crud_content_header %}
|
||||||
<h1>{{ ('crud.'~crud_name~'.title_delete')|trans({ '%as_string%': 'Aside Activity' }) }}</h1>
|
<h1>{{ ('crud.'~crud_name~'.title_delete')|trans({ '%as_string%': 'Aside Activity' }) }}</h1>
|
||||||
{% endblock crud_content_header %}
|
{% endblock crud_content_header %}
|
||||||
@ -27,4 +27,4 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
</div>
|
|
||||||
|
@ -87,5 +87,5 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{% extends '@ChillMain/Admin/layout.html.twig' %}
|
{% extends '@ChillMain/layout.html.twig' %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
@ -14,7 +14,7 @@
|
|||||||
{% include('@ChillMain/CRUD/_new_title.html.twig') %}
|
{% include('@ChillMain/CRUD/_new_title.html.twig') %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block admin_content %}
|
{% block content %}
|
||||||
{% embed '@ChillMain/CRUD/_new_content.html.twig' %}
|
{% embed '@ChillMain/CRUD/_new_content.html.twig' %}
|
||||||
{% block content_form_actions_save_and_show %}{% endblock %}
|
{% block content_form_actions_save_and_show %}{% endblock %}
|
||||||
{% endembed %}
|
{% endembed %}
|
||||||
|
@ -20,33 +20,3 @@ services:
|
|||||||
resource: "../Controller"
|
resource: "../Controller"
|
||||||
autowire: true
|
autowire: true
|
||||||
autoconfigure: true
|
autoconfigure: true
|
||||||
|
|
||||||
|
|
||||||
## Exports
|
|
||||||
|
|
||||||
# indicators
|
|
||||||
Chill\AsideActivityBundle\Export\Export\CountAsideActivity:
|
|
||||||
autowire: true
|
|
||||||
autoconfigure: true
|
|
||||||
tags:
|
|
||||||
- { name: chill.export, alias: count_asideactivity }
|
|
||||||
|
|
||||||
# filters
|
|
||||||
Chill\AsideActivityBundle\Export\Filter\ByDateFilter:
|
|
||||||
autowire: true
|
|
||||||
autoconfigure: true
|
|
||||||
tags:
|
|
||||||
- { name: chill.export_filter, alias: asideactivity_bydate_filter }
|
|
||||||
|
|
||||||
Chill\AsideActivityBundle\Export\Filter\ByActivityTypeFilter:
|
|
||||||
autowire: true
|
|
||||||
autoconfigure: true
|
|
||||||
tags:
|
|
||||||
- { name: chill.export_filter, alias: asideactivity_activitytype_filter }
|
|
||||||
|
|
||||||
# aggregators
|
|
||||||
Chill\AsideActivityBundle\Export\Aggregator\ByActivityTypeAggregator:
|
|
||||||
autowire: true
|
|
||||||
autoconfigure: true
|
|
||||||
tags:
|
|
||||||
- { name: chill.export_aggregator, alias: asideactivity_activitytype_aggregator }
|
|
||||||
|
@ -3,11 +3,23 @@ services:
|
|||||||
autowire: true
|
autowire: true
|
||||||
autoconfigure: true
|
autoconfigure: true
|
||||||
|
|
||||||
|
Chill\AsideActivityBundle\Export\Export\ListAsideActivity:
|
||||||
|
tags:
|
||||||
|
- { name: chill.export, alias: 'list_aside_activity' }
|
||||||
|
|
||||||
## Indicators
|
## Indicators
|
||||||
Chill\AsideActivityBundle\Export\Export\CountAsideActivity:
|
Chill\AsideActivityBundle\Export\Export\CountAsideActivity:
|
||||||
tags:
|
tags:
|
||||||
- { name: chill.export, alias: 'count_aside_activity' }
|
- { name: chill.export, alias: 'count_aside_activity' }
|
||||||
|
|
||||||
|
Chill\AsideActivityBundle\Export\Export\SumAsideActivityDuration:
|
||||||
|
tags:
|
||||||
|
- { name: chill.export, alias: 'sum_aside_activity_duration' }
|
||||||
|
|
||||||
|
Chill\AsideActivityBundle\Export\Export\AvgAsideActivityDuration:
|
||||||
|
tags:
|
||||||
|
- { name: chill.export, alias: 'avg_aside_activity_duration' }
|
||||||
|
|
||||||
## Filters
|
## Filters
|
||||||
chill.aside_activity.export.date_filter:
|
chill.aside_activity.export.date_filter:
|
||||||
class: Chill\AsideActivityBundle\Export\Filter\ByDateFilter
|
class: Chill\AsideActivityBundle\Export\Filter\ByDateFilter
|
||||||
@ -19,9 +31,34 @@ services:
|
|||||||
tags:
|
tags:
|
||||||
- { name: chill.export_filter, alias: 'aside_activity_type_filter' }
|
- { name: chill.export_filter, alias: 'aside_activity_type_filter' }
|
||||||
|
|
||||||
|
chill.aside_activity.export.user_job_filter:
|
||||||
|
class: Chill\AsideActivityBundle\Export\Filter\ByUserJobFilter
|
||||||
|
tags:
|
||||||
|
- { name: chill.export_filter, alias: 'aside_activity_user_job_filter' }
|
||||||
|
|
||||||
|
chill.aside_activity.export.user_scope_filter:
|
||||||
|
class: Chill\AsideActivityBundle\Export\Filter\ByUserScopeFilter
|
||||||
|
tags:
|
||||||
|
- { name: chill.export_filter, alias: 'aside_activity_user_scope_filter' }
|
||||||
|
|
||||||
|
chill.aside_activity.export.user_filter:
|
||||||
|
class: Chill\AsideActivityBundle\Export\Filter\ByUserFilter
|
||||||
|
tags:
|
||||||
|
- { name: chill.export_filter, alias: 'aside_activity_user_filter' }
|
||||||
|
|
||||||
## Aggregators
|
## Aggregators
|
||||||
|
|
||||||
chill.aside_activity.export.type_aggregator:
|
chill.aside_activity.export.type_aggregator:
|
||||||
class: Chill\AsideActivityBundle\Export\Aggregator\ByActivityTypeAggregator
|
class: Chill\AsideActivityBundle\Export\Aggregator\ByActivityTypeAggregator
|
||||||
tags:
|
tags:
|
||||||
- { name: chill.export_aggregator, alias: activity_type_aggregator }
|
- { name: chill.export_aggregator, alias: activity_type_aggregator }
|
||||||
|
|
||||||
|
chill.aside_activity.export.user_job_aggregator:
|
||||||
|
class: Chill\AsideActivityBundle\Export\Aggregator\ByUserJobAggregator
|
||||||
|
tags:
|
||||||
|
- { name: chill.export_aggregator, alias: aside_activity_user_job_aggregator }
|
||||||
|
|
||||||
|
chill.aside_activity.export.user_scope_aggregator:
|
||||||
|
class: Chill\AsideActivityBundle\Export\Aggregator\ByUserScopeAggregator
|
||||||
|
tags:
|
||||||
|
- { name: chill.export_aggregator, alias: aside_activity_user_scope_aggregator }
|
||||||
|
@ -29,16 +29,16 @@ location: Lieu
|
|||||||
|
|
||||||
# Crud
|
# Crud
|
||||||
crud:
|
crud:
|
||||||
aside_activity:
|
aside_activity:
|
||||||
title_view: Détail de l'activité annexe
|
title_view: Détail de l'activité annexe
|
||||||
title_new: Nouvelle activité annexe
|
title_new: Nouvelle activité annexe
|
||||||
title_edit: Édition d'une activité annexe
|
title_edit: Édition d'une activité annexe
|
||||||
title_delete: Supprimer une activité annexe
|
title_delete: Supprimer une activité annexe
|
||||||
button_delete: Supprimer
|
button_delete: Supprimer
|
||||||
confirm_message_delete: Êtes-vous sûr de vouloir supprimer cette activité annexe?
|
confirm_message_delete: Êtes-vous sûr de vouloir supprimer cette activité annexe?
|
||||||
aside_activity_category:
|
aside_activity_category:
|
||||||
title_new: Nouvelle catégorie d'activité annexe
|
title_new: Nouvelle catégorie d'activité annexe
|
||||||
title_edit: Édition d'une catégorie de type d'activité
|
title_edit: Édition d'une catégorie de type d'activité
|
||||||
|
|
||||||
#forms
|
#forms
|
||||||
Create a new aside activity type: Nouvelle categorie d'activité annexe
|
Create a new aside activity type: Nouvelle categorie d'activité annexe
|
||||||
@ -169,18 +169,43 @@ Aside activity configuration: Configuration des activités annexes
|
|||||||
|
|
||||||
# exports
|
# exports
|
||||||
export:
|
export:
|
||||||
Exports of aside activities: Exports des activités annexes
|
aside_activity:
|
||||||
Count aside activities: Nombre d'activités annexes
|
List of aside activities: Liste des activités annexes
|
||||||
Count aside activities by various parameters.: Compte le nombre d'activités annexes selon divers critères
|
createdAt: Création
|
||||||
filter:
|
updatedAt: Dernière mise à jour
|
||||||
Filter by aside activity date: Filtrer les activités annexes par date
|
agent_id: Utilisateur
|
||||||
Filter by aside activity type: Filtrer les activités annexes par type d'activité
|
creator_id: Créateur
|
||||||
'Filtered by aside activity type: only %type%': "Filtré par type d'activité annexe: uniquement %type%"
|
main_scope: Service principal de l'utilisateur
|
||||||
This date should be after the date given in "Implied in an aside activity after this date" field: Cette date devrait être postérieure à la date donnée dans le champ "activités annexes après cette date"
|
main_center: Centre principal de l'utilisteur
|
||||||
Aside activities after this date: Actvitités annexes après cette date
|
aside_activity_type: Catégorie d'activité annexe
|
||||||
Aside activities before this date: Actvitités annexes avant cette date
|
date: Date
|
||||||
aggregator:
|
duration: Durée
|
||||||
Group by aside activity type: Grouper les activités annexes par type d'activité
|
note: Note
|
||||||
Aside activity type: Type d'activité annexe
|
|
||||||
|
|
||||||
|
Exports of aside activities: Exports des activités annexes
|
||||||
|
Count aside activities: Nombre d'activités annexes
|
||||||
|
Count aside activities by various parameters.: Compte le nombre d'activités annexes selon divers critères
|
||||||
|
Average aside activities duration: Durée moyenne des activités annexes
|
||||||
|
Sum aside activities duration: Durée des activités annexes
|
||||||
|
filter:
|
||||||
|
Filter by aside activity date: Filtrer les activités annexes par date
|
||||||
|
Filter by aside activity type: Filtrer les activités annexes par type d'activité
|
||||||
|
'Filtered by aside activity type: only %type%': "Filtré par type d'activité annexe: uniquement %type%"
|
||||||
|
Filtered by aside activities between %dateFrom% and %dateTo%: Filtré par date d'activité annexe, entre %dateFrom% et %dateTo%
|
||||||
|
This date should be after the date given in "Implied in an aside activity after this date" field: Cette date devrait être postérieure à la date donnée dans le champ "activités annexes après cette date"
|
||||||
|
Aside activities after this date: Actvitités annexes après cette date
|
||||||
|
Aside activities before this date: Actvitités annexes avant cette date
|
||||||
|
'Filtered aside activity by user: only %users%': "Filtré par utilisateur: uniquement %users%"
|
||||||
|
Filter aside activity by user: Filtrer par utilisateur
|
||||||
|
'Filtered aside activities by user jobs: only %jobs%': "Filtré par métier des utilisateurs: uniquement %jobs%"
|
||||||
|
Filter by user jobs: Filtrer les activités annexes par métier des utilisateurs
|
||||||
|
'Filtered aside activities by user scope: only %scopes%': "Filtré par service des utilisateur: uniquement %scopes%"
|
||||||
|
Filter by user scope: Filtrer les activités annexes par service d'utilisateur
|
||||||
|
aggregator:
|
||||||
|
Group by aside activity type: Grouper les activités annexes par type d'activité
|
||||||
|
Aside activity type: Type d'activité annexe
|
||||||
|
Aggregate by user job: Grouper les activités annexes par métier des utilisateurs
|
||||||
|
Aggregate by user scope: Grouper les activités annexes par service des utilisateurs
|
||||||
|
|
||||||
|
# ROLES
|
||||||
|
CHILL_ASIDE_ACTIVITY_STATS: Statistiques pour les activités annexes
|
||||||
|
@ -1,102 +0,0 @@
|
|||||||
<?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\BudgetBundle\Config;
|
|
||||||
|
|
||||||
class ConfigRepository
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $charges;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $resources;
|
|
||||||
|
|
||||||
public function __construct($resources, $charges)
|
|
||||||
{
|
|
||||||
$this->resources = $resources;
|
|
||||||
$this->charges = $charges;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getChargesKeys(bool $onlyActive = false): array
|
|
||||||
{
|
|
||||||
return array_map(static function ($element) {
|
|
||||||
return $element['key'];
|
|
||||||
}, $this->getCharges($onlyActive));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array where keys are the resource'key and label the ressource label
|
|
||||||
*/
|
|
||||||
public function getChargesLabels(bool $onlyActive = false)
|
|
||||||
{
|
|
||||||
$charges = [];
|
|
||||||
|
|
||||||
foreach ($this->getCharges($onlyActive) as $definition) {
|
|
||||||
$charges[$definition['key']] = $this->normalizeLabel($definition['labels']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $charges;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getResourcesKeys(bool $onlyActive = false): array
|
|
||||||
{
|
|
||||||
return array_map(static function ($element) {
|
|
||||||
return $element['key'];
|
|
||||||
}, $this->getResources($onlyActive));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array where keys are the resource'key and label the ressource label
|
|
||||||
*/
|
|
||||||
public function getResourcesLabels(bool $onlyActive = false)
|
|
||||||
{
|
|
||||||
$resources = [];
|
|
||||||
|
|
||||||
foreach ($this->getResources($onlyActive) as $definition) {
|
|
||||||
$resources[$definition['key']] = $this->normalizeLabel($definition['labels']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $resources;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getCharges(bool $onlyActive = false): array
|
|
||||||
{
|
|
||||||
return $onlyActive ?
|
|
||||||
array_filter($this->charges, static function ($el) {
|
|
||||||
return $el['active'];
|
|
||||||
})
|
|
||||||
: $this->charges;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getResources(bool $onlyActive = false): array
|
|
||||||
{
|
|
||||||
return $onlyActive ?
|
|
||||||
array_filter($this->resources, static function ($el) {
|
|
||||||
return $el['active'];
|
|
||||||
})
|
|
||||||
: $this->resources;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function normalizeLabel($labels)
|
|
||||||
{
|
|
||||||
$normalizedLabels = [];
|
|
||||||
|
|
||||||
foreach ($labels as $labelDefinition) {
|
|
||||||
$normalizedLabels[$labelDefinition['lang']] = $labelDefinition['label'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $normalizedLabels;
|
|
||||||
}
|
|
||||||
}
|
|
@ -35,7 +35,6 @@ class ChillBudgetExtension extends Extension implements PrependExtensionInterfac
|
|||||||
$config = $this->processConfiguration($configuration, $configs);
|
$config = $this->processConfiguration($configuration, $configs);
|
||||||
|
|
||||||
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../config'));
|
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../config'));
|
||||||
$loader->load('services/config.yaml');
|
|
||||||
$loader->load('services/form.yaml');
|
$loader->load('services/form.yaml');
|
||||||
$loader->load('services/repository.yaml');
|
$loader->load('services/repository.yaml');
|
||||||
$loader->load('services/security.yaml');
|
$loader->load('services/security.yaml');
|
||||||
|
@ -75,7 +75,7 @@ abstract class AbstractElement
|
|||||||
/**
|
/**
|
||||||
* @ORM\Column(name="type", type="string", length=255)
|
* @ORM\Column(name="type", type="string", length=255)
|
||||||
*/
|
*/
|
||||||
private string $type;
|
private string $type = '';
|
||||||
|
|
||||||
/*Getters and Setters */
|
/*Getters and Setters */
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\BudgetBundle\Form;
|
namespace Chill\BudgetBundle\Form;
|
||||||
|
|
||||||
use Chill\BudgetBundle\Config\ConfigRepository;
|
|
||||||
use Chill\BudgetBundle\Entity\Charge;
|
use Chill\BudgetBundle\Entity\Charge;
|
||||||
use Chill\BudgetBundle\Entity\ChargeKind;
|
use Chill\BudgetBundle\Entity\ChargeKind;
|
||||||
use Chill\BudgetBundle\Repository\ChargeKindRepository;
|
use Chill\BudgetBundle\Repository\ChargeKindRepository;
|
||||||
@ -25,13 +24,9 @@ use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
|||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
use function array_flip;
|
|
||||||
use function asort;
|
|
||||||
|
|
||||||
class ChargeType extends AbstractType
|
class ChargeType extends AbstractType
|
||||||
{
|
{
|
||||||
protected ConfigRepository $configRepository;
|
|
||||||
|
|
||||||
protected TranslatableStringHelperInterface $translatableStringHelper;
|
protected TranslatableStringHelperInterface $translatableStringHelper;
|
||||||
|
|
||||||
private ChargeKindRepository $repository;
|
private ChargeKindRepository $repository;
|
||||||
@ -39,12 +34,10 @@ class ChargeType extends AbstractType
|
|||||||
private TranslatorInterface $translator;
|
private TranslatorInterface $translator;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
ConfigRepository $configRepository,
|
|
||||||
TranslatableStringHelperInterface $translatableStringHelper,
|
TranslatableStringHelperInterface $translatableStringHelper,
|
||||||
ChargeKindRepository $repository,
|
ChargeKindRepository $repository,
|
||||||
TranslatorInterface $translator
|
TranslatorInterface $translator
|
||||||
) {
|
) {
|
||||||
$this->configRepository = $configRepository;
|
|
||||||
$this->translatableStringHelper = $translatableStringHelper;
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
$this->repository = $repository;
|
$this->repository = $repository;
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
@ -116,19 +109,4 @@ class ChargeType extends AbstractType
|
|||||||
{
|
{
|
||||||
return 'chill_budgetbundle_charge';
|
return 'chill_budgetbundle_charge';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getTypes()
|
|
||||||
{
|
|
||||||
$charges = $this->configRepository
|
|
||||||
->getChargesLabels(true);
|
|
||||||
|
|
||||||
// rewrite labels to filter in language
|
|
||||||
foreach ($charges as $key => $labels) {
|
|
||||||
$charges[$key] = $this->translatableStringHelper->localize($labels);
|
|
||||||
}
|
|
||||||
|
|
||||||
asort($charges);
|
|
||||||
|
|
||||||
return array_flip($charges);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\BudgetBundle\Form;
|
namespace Chill\BudgetBundle\Form;
|
||||||
|
|
||||||
use Chill\BudgetBundle\Config\ConfigRepository;
|
|
||||||
use Chill\BudgetBundle\Entity\Resource;
|
use Chill\BudgetBundle\Entity\Resource;
|
||||||
use Chill\BudgetBundle\Entity\ResourceKind;
|
use Chill\BudgetBundle\Entity\ResourceKind;
|
||||||
use Chill\BudgetBundle\Repository\ResourceKindRepository;
|
use Chill\BudgetBundle\Repository\ResourceKindRepository;
|
||||||
@ -24,12 +23,9 @@ use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
|||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
use function array_flip;
|
|
||||||
|
|
||||||
class ResourceType extends AbstractType
|
class ResourceType extends AbstractType
|
||||||
{
|
{
|
||||||
protected ConfigRepository $configRepository;
|
|
||||||
|
|
||||||
protected TranslatableStringHelperInterface $translatableStringHelper;
|
protected TranslatableStringHelperInterface $translatableStringHelper;
|
||||||
|
|
||||||
private ResourceKindRepository $repository;
|
private ResourceKindRepository $repository;
|
||||||
@ -37,12 +33,10 @@ class ResourceType extends AbstractType
|
|||||||
private TranslatorInterface $translator;
|
private TranslatorInterface $translator;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
ConfigRepository $configRepository,
|
|
||||||
TranslatableStringHelperInterface $translatableStringHelper,
|
TranslatableStringHelperInterface $translatableStringHelper,
|
||||||
ResourceKindRepository $repository,
|
ResourceKindRepository $repository,
|
||||||
TranslatorInterface $translator
|
TranslatorInterface $translator
|
||||||
) {
|
) {
|
||||||
$this->configRepository = $configRepository;
|
|
||||||
$this->translatableStringHelper = $translatableStringHelper;
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
$this->repository = $repository;
|
$this->repository = $repository;
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
@ -98,19 +92,4 @@ class ResourceType extends AbstractType
|
|||||||
{
|
{
|
||||||
return 'chill_budgetbundle_resource';
|
return 'chill_budgetbundle_resource';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getTypes()
|
|
||||||
{
|
|
||||||
$resources = $this->configRepository
|
|
||||||
->getResourcesLabels(true);
|
|
||||||
|
|
||||||
// rewrite labels to filter in language
|
|
||||||
foreach ($resources as $key => $labels) {
|
|
||||||
$resources[$key] = $this->translatableStringHelper->localize($labels);
|
|
||||||
}
|
|
||||||
|
|
||||||
asort($resources);
|
|
||||||
|
|
||||||
return array_flip($resources);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,8 @@ namespace Chill\BudgetBundle\Repository;
|
|||||||
use Chill\BudgetBundle\Entity\ChargeKind;
|
use Chill\BudgetBundle\Entity\ChargeKind;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Doctrine\Persistence\ObjectRepository;
|
|
||||||
|
|
||||||
class ChargeKindRepository implements ObjectRepository
|
final class ChargeKindRepository implements ChargeKindRepositoryInterface
|
||||||
{
|
{
|
||||||
private EntityRepository $repository;
|
private EntityRepository $repository;
|
||||||
|
|
||||||
@ -50,7 +49,8 @@ class ChargeKindRepository implements ObjectRepository
|
|||||||
->where($qb->expr()->eq('c.isActive', 'true'))
|
->where($qb->expr()->eq('c.isActive', 'true'))
|
||||||
->orderBy('c.ordering', 'ASC')
|
->orderBy('c.ordering', 'ASC')
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->getResult();
|
->getResult()
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,6 +77,11 @@ class ChargeKindRepository implements ObjectRepository
|
|||||||
return $this->repository->findOneBy($criteria);
|
return $this->repository->findOneBy($criteria);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function findOneByKind(string $kind): ?ChargeKind
|
||||||
|
{
|
||||||
|
return $this->repository->findOneBy(['kind' => $kind]);
|
||||||
|
}
|
||||||
|
|
||||||
public function getClassName(): string
|
public function getClassName(): string
|
||||||
{
|
{
|
||||||
return ChargeKind::class;
|
return ChargeKind::class;
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
<?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\BudgetBundle\Repository;
|
||||||
|
|
||||||
|
use Chill\BudgetBundle\Entity\ChargeKind;
|
||||||
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
|
||||||
|
interface ChargeKindRepositoryInterface extends ObjectRepository
|
||||||
|
{
|
||||||
|
public function find($id): ?ChargeKind;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ChargeType[]
|
||||||
|
*/
|
||||||
|
public function findAll(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ChargeType[]
|
||||||
|
*/
|
||||||
|
public function findAllActive(): array;
|
||||||
|
|
||||||
|
public function findOneByKind(string $kind): ?ChargeKind;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ChargeType[]
|
||||||
|
*/
|
||||||
|
public function findAllByType(string $type): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed|null $limit
|
||||||
|
* @param mixed|null $offset
|
||||||
|
*
|
||||||
|
* @return ChargeType[]
|
||||||
|
*/
|
||||||
|
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
|
||||||
|
|
||||||
|
public function findOneBy(array $criteria): ?ChargeKind;
|
||||||
|
|
||||||
|
public function getClassName(): string;
|
||||||
|
}
|
@ -14,9 +14,8 @@ namespace Chill\BudgetBundle\Repository;
|
|||||||
use Chill\BudgetBundle\Entity\ResourceKind;
|
use Chill\BudgetBundle\Entity\ResourceKind;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Doctrine\Persistence\ObjectRepository;
|
|
||||||
|
|
||||||
class ResourceKindRepository implements ObjectRepository
|
final class ResourceKindRepository implements ResourceKindRepositoryInterface
|
||||||
{
|
{
|
||||||
private EntityRepository $repository;
|
private EntityRepository $repository;
|
||||||
|
|
||||||
@ -50,7 +49,8 @@ class ResourceKindRepository implements ObjectRepository
|
|||||||
->where($qb->expr()->eq('r.isActive', 'true'))
|
->where($qb->expr()->eq('r.isActive', 'true'))
|
||||||
->orderBy('r.ordering', 'ASC')
|
->orderBy('r.ordering', 'ASC')
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->getResult();
|
->getResult()
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,6 +77,11 @@ class ResourceKindRepository implements ObjectRepository
|
|||||||
return $this->repository->findOneBy($criteria);
|
return $this->repository->findOneBy($criteria);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function findOneByKind(string $kind): ?ResourceKind
|
||||||
|
{
|
||||||
|
return $this->repository->findOneBy(['kind' => $kind]);
|
||||||
|
}
|
||||||
|
|
||||||
public function getClassName(): string
|
public function getClassName(): string
|
||||||
{
|
{
|
||||||
return ResourceKind::class;
|
return ResourceKind::class;
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
<?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\BudgetBundle\Repository;
|
||||||
|
|
||||||
|
use Chill\BudgetBundle\Entity\ResourceKind;
|
||||||
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
|
||||||
|
interface ResourceKindRepositoryInterface extends ObjectRepository
|
||||||
|
{
|
||||||
|
public function find($id): ?ResourceKind;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ResourceType[]
|
||||||
|
*/
|
||||||
|
public function findAll(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ResourceType[]
|
||||||
|
*/
|
||||||
|
public function findAllActive(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ResourceType[]
|
||||||
|
*/
|
||||||
|
public function findAllByType(string $type): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed|null $limit
|
||||||
|
* @param mixed|null $offset
|
||||||
|
*
|
||||||
|
* @return ResourceType[]
|
||||||
|
*/
|
||||||
|
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
|
||||||
|
|
||||||
|
public function findOneBy(array $criteria): ?ResourceKind;
|
||||||
|
|
||||||
|
public function findOneByKind(string $kind): ?ResourceKind;
|
||||||
|
|
||||||
|
public function getClassName(): string;
|
||||||
|
}
|
@ -34,7 +34,7 @@ class ResourceRepository extends EntityRepository
|
|||||||
//->andWhere('c.startDate < :date')
|
//->andWhere('c.startDate < :date')
|
||||||
// TODO: there is a misconception here, the end date must be lower or null. startDate are never null
|
// TODO: there is a misconception here, the end date must be lower or null. startDate are never null
|
||||||
//->andWhere('c.startDate < :date OR c.startDate IS NULL');
|
//->andWhere('c.startDate < :date OR c.startDate IS NULL');
|
||||||
;
|
;
|
||||||
|
|
||||||
if (null !== $sort) {
|
if (null !== $sort) {
|
||||||
$qb->orderBy($sort);
|
$qb->orderBy($sort);
|
||||||
|
@ -11,45 +11,52 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\BudgetBundle\Service\Summary;
|
namespace Chill\BudgetBundle\Service\Summary;
|
||||||
|
|
||||||
use Chill\BudgetBundle\Config\ConfigRepository;
|
use Chill\BudgetBundle\Entity\ChargeKind;
|
||||||
|
use Chill\BudgetBundle\Entity\ResourceKind;
|
||||||
|
use Chill\BudgetBundle\Repository\ChargeKindRepository;
|
||||||
|
use Chill\BudgetBundle\Repository\ChargeKindRepositoryInterface;
|
||||||
|
use Chill\BudgetBundle\Repository\ResourceKindRepository;
|
||||||
|
use Chill\BudgetBundle\Repository\ResourceKindRepositoryInterface;
|
||||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
use Chill\PersonBundle\Entity\Household\Household;
|
use Chill\PersonBundle\Entity\Household\Household;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\Query\ResultSetMapping;
|
use Doctrine\ORM\Query\ResultSetMapping;
|
||||||
use LogicException;
|
use LogicException;
|
||||||
|
use RuntimeException;
|
||||||
use function count;
|
use function count;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helps to find a summary of the budget: the sum of resources and charges.
|
* Helps to find a summary of the budget: the sum of resources and charges.
|
||||||
*/
|
*/
|
||||||
class SummaryBudget implements SummaryBudgetInterface
|
final class SummaryBudget implements SummaryBudgetInterface
|
||||||
{
|
{
|
||||||
private const QUERY_CHARGE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.charge WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type';
|
private const QUERY_CHARGE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, charge_id AS kind_id FROM chill_budget.charge WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY charge_id';
|
||||||
|
|
||||||
private const QUERY_CHARGE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.charge WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type';
|
private const QUERY_CHARGE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, charge_id AS kind_id FROM chill_budget.charge WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY charge_id';
|
||||||
|
|
||||||
private const QUERY_RESOURCE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.resource WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type';
|
private const QUERY_RESOURCE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, resource_id AS kind_id FROM chill_budget.resource WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY resource_id';
|
||||||
|
|
||||||
private const QUERY_RESOURCE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.resource WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type';
|
private const QUERY_RESOURCE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, resource_id AS kind_id FROM chill_budget.resource WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY resource_id';
|
||||||
|
|
||||||
private array $chargeLabels;
|
private ChargeKindRepositoryInterface $chargeKindRepository;
|
||||||
|
|
||||||
private ConfigRepository $configRepository;
|
|
||||||
|
|
||||||
private EntityManagerInterface $em;
|
private EntityManagerInterface $em;
|
||||||
|
|
||||||
private array $resourcesLabels;
|
private ResourceKindRepositoryInterface $resourceKindRepository;
|
||||||
|
|
||||||
private TranslatableStringHelperInterface $translatableStringHelper;
|
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $em, ConfigRepository $configRepository, TranslatableStringHelperInterface $translatableStringHelper)
|
public function __construct(
|
||||||
{
|
EntityManagerInterface $em,
|
||||||
|
TranslatableStringHelperInterface $translatableStringHelper,
|
||||||
|
ResourceKindRepositoryInterface $resourceKindRepository,
|
||||||
|
ChargeKindRepositoryInterface $chargeKindRepository
|
||||||
|
) {
|
||||||
$this->em = $em;
|
$this->em = $em;
|
||||||
$this->configRepository = $configRepository;
|
|
||||||
$this->chargeLabels = $configRepository->getChargesLabels();
|
|
||||||
$this->resourcesLabels = $configRepository->getResourcesLabels();
|
|
||||||
$this->translatableStringHelper = $translatableStringHelper;
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
|
$this->resourceKindRepository = $resourceKindRepository;
|
||||||
|
$this->chargeKindRepository = $chargeKindRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSummaryForHousehold(?Household $household): array
|
public function getSummaryForHousehold(?Household $household): array
|
||||||
@ -112,7 +119,7 @@ class SummaryBudget implements SummaryBudgetInterface
|
|||||||
$rsm = new ResultSetMapping();
|
$rsm = new ResultSetMapping();
|
||||||
$rsm
|
$rsm
|
||||||
->addScalarResult('sum', 'sum')
|
->addScalarResult('sum', 'sum')
|
||||||
->addScalarResult('type', 'type')
|
->addScalarResult('kind_id', 'kind_id')
|
||||||
->addScalarResult('comment', 'comment');
|
->addScalarResult('comment', 'comment');
|
||||||
|
|
||||||
return $rsm;
|
return $rsm;
|
||||||
@ -120,51 +127,62 @@ class SummaryBudget implements SummaryBudgetInterface
|
|||||||
|
|
||||||
private function getEmptyChargeArray(): array
|
private function getEmptyChargeArray(): array
|
||||||
{
|
{
|
||||||
$keys = $this->configRepository->getChargesKeys();
|
$keys = array_map(static fn (ChargeKind $kind) => $kind->getKind(), $this->chargeKindRepository->findAll());
|
||||||
$labels = $this->chargeLabels;
|
|
||||||
|
|
||||||
return array_combine($keys, array_map(function ($i) use ($labels) {
|
return array_combine($keys, array_map(function ($kind) {
|
||||||
return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i]), 'comment' => ''];
|
return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($this->chargeKindRepository->findOneByKind($kind)->getName()), 'comment' => ''];
|
||||||
}, $keys));
|
}, $keys));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getEmptyResourceArray(): array
|
private function getEmptyResourceArray(): array
|
||||||
{
|
{
|
||||||
$keys = $this->configRepository->getResourcesKeys();
|
$keys = array_map(static fn (ResourceKind $kind) => $kind->getKind(), $this->resourceKindRepository->findAll());
|
||||||
$labels = $this->resourcesLabels;
|
|
||||||
|
|
||||||
return array_combine($keys, array_map(function ($i) use ($labels) {
|
return array_combine($keys, array_map(function ($kind) {
|
||||||
return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i]), 'comment' => ''];
|
return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($this->resourceKindRepository->findOneByKind($kind)->getName()), 'comment' => ''];
|
||||||
}, $keys));
|
}, $keys));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function rowToArray(array $rows, string $kind): array
|
private function rowToArray(array $rows, string $kind): array
|
||||||
{
|
{
|
||||||
|
$result = [];
|
||||||
|
|
||||||
switch ($kind) {
|
switch ($kind) {
|
||||||
case 'charge':
|
case 'charge':
|
||||||
$label = $this->chargeLabels;
|
foreach ($rows as $row) {
|
||||||
|
$chargeKind = $this->chargeKindRepository->find($row['kind_id']);
|
||||||
|
|
||||||
break;
|
if (null === $chargeKind) {
|
||||||
|
throw new RuntimeException('charge kind not found: ' . $row['kind_id']);
|
||||||
|
}
|
||||||
|
$result[$chargeKind->getKind()] = [
|
||||||
|
'sum' => (float) $row['sum'],
|
||||||
|
'label' => $this->translatableStringHelper->localize($chargeKind->getName()),
|
||||||
|
'comment' => (string) $row['comment'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
|
||||||
case 'resource':
|
case 'resource':
|
||||||
$label = $this->resourcesLabels;
|
foreach ($rows as $row) {
|
||||||
|
$resourceKind = $this->resourceKindRepository->find($row['kind_id']);
|
||||||
|
|
||||||
break;
|
if (null === $resourceKind) {
|
||||||
|
throw new RuntimeException('charge kind not found: ' . $row['kind_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result[$resourceKind->getKind()] = [
|
||||||
|
'sum' => (float) $row['sum'],
|
||||||
|
'label' => $this->translatableStringHelper->localize($resourceKind->getName()),
|
||||||
|
'comment' => (string) $row['comment'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new LogicException();
|
throw new LogicException();
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = [];
|
|
||||||
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
$result[$row['type']] = [
|
|
||||||
'sum' => (float) $row['sum'],
|
|
||||||
'label' => $this->translatableStringHelper->localize($label[$row['type']]),
|
|
||||||
'comment' => (string) $row['comment'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
<?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\BudgetBundle\Templating;
|
|
||||||
|
|
||||||
use Chill\BudgetBundle\Config\ConfigRepository;
|
|
||||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
|
||||||
use Twig\Extension\AbstractExtension;
|
|
||||||
use Twig\TwigFilter;
|
|
||||||
use UnexpectedValueException;
|
|
||||||
|
|
||||||
class Twig extends AbstractExtension
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var ConfigRepository
|
|
||||||
*/
|
|
||||||
protected $configRepository;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var TranslatableStringHelper
|
|
||||||
*/
|
|
||||||
protected $translatableStringHelper;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
ConfigRepository $configRepository,
|
|
||||||
TranslatableStringHelper $translatableStringHelper
|
|
||||||
) {
|
|
||||||
$this->configRepository = $configRepository;
|
|
||||||
$this->translatableStringHelper = $translatableStringHelper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function displayLink($link, $family)
|
|
||||||
{
|
|
||||||
switch ($family) {
|
|
||||||
case 'resource':
|
|
||||||
return $this->translatableStringHelper->localize(
|
|
||||||
$this->configRepository->getResourcesLabels()[$link]
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'charge':
|
|
||||||
return $this->translatableStringHelper->localize(
|
|
||||||
$this->configRepository->getChargesLabels()[$link]
|
|
||||||
);
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new UnexpectedValueException("This family of element: {$family} is not "
|
|
||||||
. "supported. Supported families are 'resource', 'charge'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFilters()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
new TwigFilter('budget_element_type_display', [$this, 'displayLink'], ['is_safe' => ['html']]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,158 @@
|
|||||||
|
<?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\BudgetBundle\Tests\Service\Summary;
|
||||||
|
|
||||||
|
use Chill\BudgetBundle\Entity\ChargeKind;
|
||||||
|
use Chill\BudgetBundle\Entity\ResourceKind;
|
||||||
|
use Chill\BudgetBundle\Repository\ChargeKindRepositoryInterface;
|
||||||
|
use Chill\BudgetBundle\Repository\ResourceKindRepositoryInterface;
|
||||||
|
use Chill\BudgetBundle\Service\Summary\SummaryBudget;
|
||||||
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
|
use Chill\PersonBundle\Entity\Household\Household;
|
||||||
|
use Chill\PersonBundle\Entity\Household\HouseholdMember;
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Doctrine\ORM\AbstractQuery;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\Argument;
|
||||||
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class SummaryBudgetTest extends TestCase
|
||||||
|
{
|
||||||
|
use ProphecyTrait;
|
||||||
|
|
||||||
|
public function testGenerateSummaryForPerson(): void
|
||||||
|
{
|
||||||
|
$queryCharges = $this->prophesize(AbstractQuery::class);
|
||||||
|
$queryCharges->getResult()->willReturn([
|
||||||
|
[
|
||||||
|
'sum' => 250.0,
|
||||||
|
'comment' => '',
|
||||||
|
'kind_id' => 1, // kind: rental
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$queryCharges->setParameters(Argument::type('array'))
|
||||||
|
->will(function ($args, $query) {
|
||||||
|
return $query;
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
$queryResources = $this->prophesize(AbstractQuery::class);
|
||||||
|
$queryResources->getResult()->willReturn([
|
||||||
|
[
|
||||||
|
'sum' => 1500.0,
|
||||||
|
'comment' => '',
|
||||||
|
'kind_id' => 2, // kind: 'salary',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$queryResources->setParameters(Argument::type('array'))
|
||||||
|
->will(function ($args, $query) {
|
||||||
|
return $query;
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
$em = $this->prophesize(EntityManagerInterface::class);
|
||||||
|
$em->createNativeQuery(Argument::type('string'), Argument::type(Query\ResultSetMapping::class))
|
||||||
|
->will(function ($args) use ($queryResources, $queryCharges) {
|
||||||
|
if (false !== strpos($args[0], 'chill_budget.resource')) {
|
||||||
|
return $queryResources->reveal();
|
||||||
|
}
|
||||||
|
if (false !== strpos($args[0], 'chill_budget.charge')) {
|
||||||
|
return $queryCharges->reveal();
|
||||||
|
}
|
||||||
|
throw new \RuntimeException('this query does not have a stub counterpart: '.$args[0]);
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
$chargeRepository = $this->prophesize(ChargeKindRepositoryInterface::class);
|
||||||
|
$chargeRepository->findAll()->willReturn([
|
||||||
|
$rental = (new ChargeKind())->setKind('rental')->setName(['fr' => 'Rental']),
|
||||||
|
$other = (new ChargeKind())->setKind('other')->setName(['fr' => 'Other']),
|
||||||
|
]);
|
||||||
|
$chargeRepository->find(1)->willReturn($rental);
|
||||||
|
$chargeRepository->findOneByKind('rental')->willReturn($rental);
|
||||||
|
$chargeRepository->findOneByKind('other')->willReturn($other);
|
||||||
|
|
||||||
|
$resourceRepository = $this->prophesize(ResourceKindRepositoryInterface::class);
|
||||||
|
$resourceRepository->findAll()->willReturn([
|
||||||
|
$salary = (new ResourceKind())->setKind('salary')->setName(['fr' => 'Salary']),
|
||||||
|
$misc = (new ResourceKind())->setKind('misc')->setName(['fr' => 'Misc']),
|
||||||
|
]);
|
||||||
|
$resourceRepository->find(2)->willReturn($salary);
|
||||||
|
$resourceRepository->findOneByKind('salary')->willReturn($salary);
|
||||||
|
$resourceRepository->findOneByKind('misc')->willReturn($misc);
|
||||||
|
|
||||||
|
$translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class);
|
||||||
|
$translatableStringHelper->localize(Argument::type('array'))->will(function ($arg) {
|
||||||
|
return $arg[0]['fr'];
|
||||||
|
});
|
||||||
|
|
||||||
|
$person = new Person();
|
||||||
|
$personReflection = new \ReflectionClass($person);
|
||||||
|
$personIdReflection = $personReflection->getProperty('id');
|
||||||
|
$personIdReflection->setAccessible(true);
|
||||||
|
$personIdReflection->setValue($person, 1);
|
||||||
|
|
||||||
|
$household = new Household();
|
||||||
|
$householdReflection = new \ReflectionClass($household);
|
||||||
|
$householdId = $householdReflection->getProperty('id');
|
||||||
|
$householdId->setAccessible(true);
|
||||||
|
$householdId->setValue($household, 1);
|
||||||
|
$householdMember = (new HouseholdMember())->setPerson($person)
|
||||||
|
->setStartDate(new \DateTimeImmutable('1 month ago'))
|
||||||
|
;
|
||||||
|
$household->addMember($householdMember);
|
||||||
|
|
||||||
|
$summaryBudget = new SummaryBudget(
|
||||||
|
$em->reveal(),
|
||||||
|
$translatableStringHelper->reveal(),
|
||||||
|
$resourceRepository->reveal(),
|
||||||
|
$chargeRepository->reveal()
|
||||||
|
);
|
||||||
|
|
||||||
|
$summary = $summaryBudget->getSummaryForPerson($person);
|
||||||
|
$summaryForHousehold = $summaryBudget->getSummaryForHousehold($household);
|
||||||
|
|
||||||
|
// we check the structure for the summary. The structure is the same for household
|
||||||
|
// and persons
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'charges' => [
|
||||||
|
'rental' => ['sum' => 250.0, 'comment' => '', 'label' => 'Rental'],
|
||||||
|
'other' => ['sum' => 0.0, 'comment' => '', 'label' => 'Other'],
|
||||||
|
],
|
||||||
|
'resources' => [
|
||||||
|
'salary' => ['sum' => 1500.0, 'comment' => '', 'label' => 'Salary'],
|
||||||
|
'misc' => ['sum' => 0.0, 'comment' => '', 'label' => 'Misc'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ([$summaryForHousehold, $summary] as $summary) {
|
||||||
|
$this->assertIsArray($summary);
|
||||||
|
$this->assertEqualsCanonicalizing(['charges', 'resources'], array_keys($summary));
|
||||||
|
$this->assertEqualsCanonicalizing(['rental', 'other'], array_keys($summary['charges']));
|
||||||
|
$this->assertEqualsCanonicalizing(['salary', 'misc'], array_keys($summary['resources']));
|
||||||
|
|
||||||
|
foreach ($expected as $resCha => $contains) {
|
||||||
|
foreach ($contains as $kind => $row) {
|
||||||
|
$this->assertEqualsCanonicalizing($row, $summary[$resCha][$kind]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +0,0 @@
|
|||||||
services:
|
|
||||||
Chill\BudgetBundle\Config\ConfigRepository:
|
|
||||||
arguments:
|
|
||||||
$resources: '%chill_budget.resources%'
|
|
||||||
$charges: '%chill_budget.charges%'
|
|
@ -0,0 +1,37 @@
|
|||||||
|
<?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\Migrations\Budget;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20230201131008 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this shouldn't be undone.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Fix the comment on tags column in resource and charge type';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('COMMENT ON COLUMN chill_budget.resource_type.tags IS \'(DC2Type:json)\'');
|
||||||
|
$this->addSql('COMMENT ON COLUMN chill_budget.charge_type.tags IS \'(DC2Type:json)\'');
|
||||||
|
}
|
||||||
|
}
|
@ -77,6 +77,13 @@ The balance: Différence entre ressources et charges
|
|||||||
Valid since %startDate% until %endDate%: Valide depuis le %startDate% jusqu'au %endDate%
|
Valid since %startDate% until %endDate%: Valide depuis le %startDate% jusqu'au %endDate%
|
||||||
Valid since %startDate%: Valide depuis le %startDate%
|
Valid since %startDate%: Valide depuis le %startDate%
|
||||||
|
|
||||||
|
# ROLES
|
||||||
|
Budget elements: Budget
|
||||||
|
CHILL_BUDGET_ELEMENT_CREATE: Créer une ressource/charge
|
||||||
|
CHILL_BUDGET_ELEMENT_DELETE: Supprimer une ressource/charge
|
||||||
|
CHILL_BUDGET_ELEMENT_SEE: Voir les ressources/charges
|
||||||
|
CHILL_BUDGET_ELEMENT_UPDATE: Modifier une ressource/charge
|
||||||
|
|
||||||
## admin
|
## admin
|
||||||
|
|
||||||
crud:
|
crud:
|
||||||
|
@ -39,7 +39,6 @@ class AdminMenuBuilder implements LocalMenuBuilderInterface
|
|||||||
->setAttribute('class', 'list-group-item-header')
|
->setAttribute('class', 'list-group-item-header')
|
||||||
->setExtras([
|
->setExtras([
|
||||||
'order' => 6000,
|
'order' => 6000,
|
||||||
'icons' => ['calendar'],
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$menu->addChild('Cancel reason', [
|
$menu->addChild('Cancel reason', [
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
||||||
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
||||||
|
|
||||||
<div class="accompanying_course_work-list">
|
<div class="accompanying-course-work">
|
||||||
<table class="obj-res-eval my-3">
|
<table class="obj-res-eval my-3">
|
||||||
<thead>
|
<thead>
|
||||||
<th class="eval">
|
<th class="eval">
|
||||||
|
@ -39,7 +39,6 @@ class AdminMenuBuilder implements LocalMenuBuilderInterface
|
|||||||
->setAttribute('class', 'list-group-item-header')
|
->setAttribute('class', 'list-group-item-header')
|
||||||
->setExtras([
|
->setExtras([
|
||||||
'order' => 4500,
|
'order' => 4500,
|
||||||
'icons' => ['plus'],
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$menu->addChild('Custom fields group', [
|
$menu->addChild('Custom fields group', [
|
||||||
|
@ -272,8 +272,9 @@ final class DocGeneratorTemplateController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($isTest && isset($form) && $form['show_data']->getData()) {
|
if ($isTest && isset($form) && $form['show_data']->getData()) {
|
||||||
// very ugly hack...
|
return $this->render('@ChillDocGenerator/Generator/debug_value.html.twig', [
|
||||||
dd($context->getData($template, $entity, $contextGenerationData));
|
'datas' => json_encode($context->getData($template, $entity, $contextGenerationData), JSON_PRETTY_PRINT)
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{{ 'Doc generator debug'|trans }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<pre>{{ datas }}</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -39,7 +39,6 @@ class AdminMenuBuilder implements LocalMenuBuilderInterface
|
|||||||
->setAttribute('class', 'list-group-item-header')
|
->setAttribute('class', 'list-group-item-header')
|
||||||
->setExtras([
|
->setExtras([
|
||||||
'order' => 4000,
|
'order' => 4000,
|
||||||
'icons' => ['file-pdf-o'],
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$menu->addChild('Document category list', [
|
$menu->addChild('Document category list', [
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
import {_createI18n} from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
|
||||||
|
import DocumentActionButtonsGroup from "../../vuejs/DocumentActionButtonsGroup.vue";
|
||||||
|
import {createApp} from "vue";
|
||||||
|
import {StoredObject} from "../../types";
|
||||||
|
|
||||||
|
const i18n = _createI18n({});
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', function (e) {
|
||||||
|
document.querySelectorAll<HTMLDivElement>('div[data-download-buttons]').forEach((el) => {
|
||||||
|
const app = createApp({
|
||||||
|
components: {DocumentActionButtonsGroup},
|
||||||
|
data() {
|
||||||
|
|
||||||
|
const datasets = el.dataset as {
|
||||||
|
filename: string,
|
||||||
|
canEdit: string,
|
||||||
|
storedObject: string,
|
||||||
|
small: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const
|
||||||
|
storedObject = JSON.parse(datasets.storedObject),
|
||||||
|
filename = datasets.filename,
|
||||||
|
canEdit = datasets.canEdit === '1',
|
||||||
|
small = datasets.small === '1'
|
||||||
|
;
|
||||||
|
|
||||||
|
return { storedObject, filename, canEdit, small };
|
||||||
|
},
|
||||||
|
template: '<document-action-buttons-group :can-edit="canEdit" :filename="filename" :stored-object="storedObject" :small="small"></document-action-buttons-group>',
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use(i18n).mount(el);
|
||||||
|
})
|
||||||
|
});
|
25
src/Bundle/ChillDocStoreBundle/Resources/public/types.ts
Normal file
25
src/Bundle/ChillDocStoreBundle/Resources/public/types.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import {DateTime} from "../../../ChillMainBundle/Resources/public/types";
|
||||||
|
|
||||||
|
export interface StoredObject {
|
||||||
|
id: number,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* filename of the object in the object storage
|
||||||
|
*/
|
||||||
|
filename: string,
|
||||||
|
creationDate: DateTime,
|
||||||
|
datas: object,
|
||||||
|
iv: number[],
|
||||||
|
keyInfos: object,
|
||||||
|
title: string,
|
||||||
|
type: string,
|
||||||
|
uuid: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function executed by the WopiEditButton component.
|
||||||
|
*/
|
||||||
|
export type WopiEditButtonExecutableBeforeLeaveFunction = {
|
||||||
|
(): Promise<void>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dropdown">
|
||||||
|
<button :class="Object.assign({'btn': true, 'btn-outline-primary': true, 'dropdown-toggle': true, small: props.small})" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
Actions
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li v-if="props.canEdit && is_extension_editable(props.storedObject.type)">
|
||||||
|
<wopi-edit-button :stored-object="props.storedObject" :classes="{'dropdown-item': true}" :execute-before-leave="props.executeBeforeLeave"></wopi-edit-button>
|
||||||
|
</li>
|
||||||
|
<li v-if="props.storedObject.type != 'application/pdf' && is_extension_viewable(props.storedObject.type) && props.canConvertPdf">
|
||||||
|
<convert-button :stored-object="props.storedObject" :filename="filename" :classes="{'dropdown-item': true}"></convert-button>
|
||||||
|
</li>
|
||||||
|
<li v-if="props.canDownload">
|
||||||
|
<download-button :stored-object="props.storedObject" :filename="filename" :classes="{'dropdown-item': true}"></download-button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
import ConvertButton from "./StoredObjectButton/ConvertButton.vue";
|
||||||
|
import DownloadButton from "./StoredObjectButton/DownloadButton.vue";
|
||||||
|
import WopiEditButton from "./StoredObjectButton/WopiEditButton.vue";
|
||||||
|
import {is_extension_editable, is_extension_viewable} from "./StoredObjectButton/helpers";
|
||||||
|
import {StoredObject, WopiEditButtonExecutableBeforeLeaveFunction} from "../types";
|
||||||
|
|
||||||
|
interface DocumentActionButtonsGroupConfig {
|
||||||
|
storedObject: StoredObject,
|
||||||
|
small?: boolean,
|
||||||
|
canEdit?: boolean,
|
||||||
|
canDownload?: boolean,
|
||||||
|
canConvertPdf?: boolean,
|
||||||
|
returnPath?: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will be the filename displayed to the user when he·she download the document
|
||||||
|
* (the document will be saved on his disk with this name)
|
||||||
|
*
|
||||||
|
* If not set, 'document' will be used.
|
||||||
|
*/
|
||||||
|
filename?: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, will execute this function before leaving to the editor
|
||||||
|
*/
|
||||||
|
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction,
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<DocumentActionButtonsGroupConfig>(), {
|
||||||
|
small: false,
|
||||||
|
canEdit: true,
|
||||||
|
canDownload: true,
|
||||||
|
canConvertPdf: true,
|
||||||
|
returnPath: window.location.pathname + window.location.search + window.location.hash,
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -0,0 +1,5 @@
|
|||||||
|
# About buttons and components available
|
||||||
|
|
||||||
|
## DocumentActionButtonsGroup
|
||||||
|
|
||||||
|
This is an component to use to render a group of button with actions linked to a document.
|
@ -0,0 +1,46 @@
|
|||||||
|
<template>
|
||||||
|
<a :class="props.classes" @click="download_and_open($event)">
|
||||||
|
<i class="fa fa-file-pdf-o"></i>
|
||||||
|
Télécharger en pdf
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
import {build_convert_link, download_and_decrypt_doc, download_doc} from "./helpers";
|
||||||
|
import mime from "mime";
|
||||||
|
import {reactive} from "vue";
|
||||||
|
import {StoredObject} from "../../types";
|
||||||
|
|
||||||
|
interface ConvertButtonConfig {
|
||||||
|
storedObject: StoredObject,
|
||||||
|
classes: { [key: string]: boolean},
|
||||||
|
filename?: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface DownloadButtonState {
|
||||||
|
content: null|string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<ConvertButtonConfig>();
|
||||||
|
const state: DownloadButtonState = reactive({content: null});
|
||||||
|
|
||||||
|
async function download_and_open(event: Event): Promise<void> {
|
||||||
|
const button = event.target as HTMLAnchorElement;
|
||||||
|
|
||||||
|
if (null === state.content) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const raw = await download_doc(build_convert_link(props.storedObject.uuid));
|
||||||
|
state.content = window.URL.createObjectURL(raw);
|
||||||
|
|
||||||
|
button.href = window.URL.createObjectURL(raw);
|
||||||
|
button.type = 'application/pdf';
|
||||||
|
|
||||||
|
button.download = (props.filename + '.pdf') || 'document.pdf';
|
||||||
|
}
|
||||||
|
|
||||||
|
button.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
@ -0,0 +1,53 @@
|
|||||||
|
<template>
|
||||||
|
<a :class="props.classes" @click="download_and_open($event)">
|
||||||
|
<i class="fa fa-download"></i>
|
||||||
|
Télécharger
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {reactive} from "vue";
|
||||||
|
import {build_download_info_link, download_and_decrypt_doc} from "./helpers";
|
||||||
|
import mime from "mime";
|
||||||
|
import {StoredObject} from "../../types";
|
||||||
|
|
||||||
|
interface DownloadButtonConfig {
|
||||||
|
storedObject: StoredObject,
|
||||||
|
classes: {[k: string]: boolean},
|
||||||
|
filename?: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DownloadButtonState {
|
||||||
|
content: null|string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<DownloadButtonConfig>();
|
||||||
|
const state: DownloadButtonState = reactive({content: null});
|
||||||
|
|
||||||
|
async function download_and_open(event: Event): Promise<void> {
|
||||||
|
const button = event.target as HTMLAnchorElement;
|
||||||
|
|
||||||
|
if (null === state.content) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const urlInfo = build_download_info_link(props.storedObject.filename);
|
||||||
|
|
||||||
|
const raw = await download_and_decrypt_doc(urlInfo, props.storedObject.keyInfos, new Uint8Array(props.storedObject.iv));
|
||||||
|
state.content = window.URL.createObjectURL(raw);
|
||||||
|
|
||||||
|
button.href = window.URL.createObjectURL(raw);
|
||||||
|
button.type = props.storedObject.type;
|
||||||
|
|
||||||
|
if (props.filename !== undefined) {
|
||||||
|
button.download = props.filename || 'document';
|
||||||
|
|
||||||
|
const ext = mime.getExtension(props.storedObject.type);
|
||||||
|
if (null !== ext) {
|
||||||
|
button.download = button.download + '.' + ext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button.click();
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<a :class="Object.assign(props.classes, {'btn': true})" @click="beforeLeave($event)" :href="build_wopi_editor_link(props.storedObject.uuid, props.returnPath)">
|
||||||
|
<i class="fa fa-paragraph"></i>
|
||||||
|
Editer en ligne
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import WopiEditButton from "./WopiEditButton.vue";
|
||||||
|
import {build_wopi_editor_link} from "./helpers";
|
||||||
|
import {StoredObject, WopiEditButtonExecutableBeforeLeaveFunction} from "../../types";
|
||||||
|
|
||||||
|
interface WopiEditButtonConfig {
|
||||||
|
storedObject: StoredObject,
|
||||||
|
returnPath?: string,
|
||||||
|
classes: {[k: string] : boolean},
|
||||||
|
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction,
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<WopiEditButtonConfig>();
|
||||||
|
|
||||||
|
let executed = false;
|
||||||
|
|
||||||
|
async function beforeLeave(event: Event): Promise<true> {
|
||||||
|
console.log(executed);
|
||||||
|
if (props.executeBeforeLeave === undefined || executed === true) {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
await props.executeBeforeLeave();
|
||||||
|
executed = true;
|
||||||
|
|
||||||
|
const link = event.target as HTMLAnchorElement;
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="sass">
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,179 @@
|
|||||||
|
|
||||||
|
const MIMES_EDIT = new Set([
|
||||||
|
'application/vnd.ms-powerpoint',
|
||||||
|
'application/vnd.ms-excel',
|
||||||
|
'application/vnd.oasis.opendocument.text',
|
||||||
|
'application/vnd.oasis.opendocument.text-flat-xml',
|
||||||
|
'application/vnd.oasis.opendocument.spreadsheet',
|
||||||
|
'application/vnd.oasis.opendocument.spreadsheet-flat-xml',
|
||||||
|
'application/vnd.oasis.opendocument.presentation',
|
||||||
|
'application/vnd.oasis.opendocument.presentation-flat-xml',
|
||||||
|
'application/vnd.oasis.opendocument.graphics',
|
||||||
|
'application/vnd.oasis.opendocument.graphics-flat-xml',
|
||||||
|
'application/vnd.oasis.opendocument.chart',
|
||||||
|
'application/msword',
|
||||||
|
'application/vnd.ms-excel',
|
||||||
|
'application/vnd.ms-powerpoint',
|
||||||
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
|
'application/vnd.ms-word.document.macroEnabled.12',
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
|
||||||
|
'application/vnd.ms-excel.sheet.macroEnabled.12',
|
||||||
|
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||||
|
'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
|
||||||
|
'application/x-dif-document',
|
||||||
|
'text/spreadsheet',
|
||||||
|
'text/csv',
|
||||||
|
'application/x-dbase',
|
||||||
|
'text/rtf',
|
||||||
|
'text/plain',
|
||||||
|
'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const MIMES_VIEW = new Set([
|
||||||
|
...MIMES_EDIT,
|
||||||
|
[
|
||||||
|
'image/svg+xml',
|
||||||
|
'application/vnd.sun.xml.writer',
|
||||||
|
'application/vnd.sun.xml.calc',
|
||||||
|
'application/vnd.sun.xml.impress',
|
||||||
|
'application/vnd.sun.xml.draw',
|
||||||
|
'application/vnd.sun.xml.writer.global',
|
||||||
|
'application/vnd.sun.xml.writer.template',
|
||||||
|
'application/vnd.sun.xml.calc.template',
|
||||||
|
'application/vnd.sun.xml.impress.template',
|
||||||
|
'application/vnd.sun.xml.draw.template',
|
||||||
|
'application/vnd.oasis.opendocument.text-master',
|
||||||
|
'application/vnd.oasis.opendocument.text-template',
|
||||||
|
'application/vnd.oasis.opendocument.text-master-template',
|
||||||
|
'application/vnd.oasis.opendocument.spreadsheet-template',
|
||||||
|
'application/vnd.oasis.opendocument.presentation-template',
|
||||||
|
'application/vnd.oasis.opendocument.graphics-template',
|
||||||
|
'application/vnd.ms-word.template.macroEnabled.12',
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
|
||||||
|
'application/vnd.ms-excel.template.macroEnabled.12',
|
||||||
|
'application/vnd.openxmlformats-officedocument.presentationml.template',
|
||||||
|
'application/vnd.ms-powerpoint.template.macroEnabled.12',
|
||||||
|
'application/vnd.wordperfect',
|
||||||
|
'application/x-aportisdoc',
|
||||||
|
'application/x-hwp',
|
||||||
|
'application/vnd.ms-works',
|
||||||
|
'application/x-mswrite',
|
||||||
|
'application/vnd.lotus-1-2-3',
|
||||||
|
'image/cgm',
|
||||||
|
'image/vnd.dxf',
|
||||||
|
'image/x-emf',
|
||||||
|
'image/x-wmf',
|
||||||
|
'application/coreldraw',
|
||||||
|
'application/vnd.visio2013',
|
||||||
|
'application/vnd.visio',
|
||||||
|
'application/vnd.ms-visio.drawing',
|
||||||
|
'application/x-mspublisher',
|
||||||
|
'application/x-sony-bbeb',
|
||||||
|
'application/x-gnumeric',
|
||||||
|
'application/macwriteii',
|
||||||
|
'application/x-iwork-numbers-sffnumbers',
|
||||||
|
'application/vnd.oasis.opendocument.text-web',
|
||||||
|
'application/x-pagemaker',
|
||||||
|
'application/x-fictionbook+xml',
|
||||||
|
'application/clarisworks',
|
||||||
|
'image/x-wpg',
|
||||||
|
'application/x-iwork-pages-sffpages',
|
||||||
|
'application/x-iwork-keynote-sffkey',
|
||||||
|
'application/x-abiword',
|
||||||
|
'image/x-freehand',
|
||||||
|
'application/vnd.sun.xml.chart',
|
||||||
|
'application/x-t602',
|
||||||
|
'image/bmp',
|
||||||
|
'image/png',
|
||||||
|
'image/gif',
|
||||||
|
'image/tiff',
|
||||||
|
'image/jpg',
|
||||||
|
'image/jpeg',
|
||||||
|
'application/pdf',
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
function is_extension_editable(mimeType: string): boolean {
|
||||||
|
return MIMES_EDIT.has(mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
function is_extension_viewable(mimeType: string): boolean {
|
||||||
|
return MIMES_VIEW.has(mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_convert_link(uuid: string) {
|
||||||
|
return `/chill/wopi/convert/${uuid}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_download_info_link(object_name: string) {
|
||||||
|
return `/asyncupload/temp_url/generate/GET?object_name=${object_name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_wopi_editor_link(uuid: string, returnPath?: string) {
|
||||||
|
if (returnPath === undefined) {
|
||||||
|
returnPath = window.location.pathname + window.location.search + window.location.hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `/chill/wopi/edit/${uuid}?returnPath=` + encodeURIComponent(returnPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
function download_doc(url: string): Promise<Blob> {
|
||||||
|
return window.fetch(url).then(r => {
|
||||||
|
if (r.ok) {
|
||||||
|
return r.blob()
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Could not download document');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function download_and_decrypt_doc(urlGenerator: string, keyData: JsonWebKey, iv: Uint8Array): Promise<Blob>
|
||||||
|
{
|
||||||
|
const algo = 'AES-CBC';
|
||||||
|
// get an url to download the object
|
||||||
|
const downloadInfoResponse = await window.fetch(urlGenerator);
|
||||||
|
|
||||||
|
if (!downloadInfoResponse.ok) {
|
||||||
|
throw new Error("error while downloading url " + downloadInfoResponse.status + " " + downloadInfoResponse.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadInfo = await downloadInfoResponse.json() as {url: string};
|
||||||
|
const rawResponse = await window.fetch(downloadInfo.url);
|
||||||
|
|
||||||
|
if (!rawResponse.ok) {
|
||||||
|
throw new Error("error while downloading raw file " + rawResponse.status + " " + rawResponse.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iv.length === 0) {
|
||||||
|
return rawResponse.blob();
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawBuffer = await rawResponse.arrayBuffer();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const key = await window.crypto.subtle
|
||||||
|
.importKey('jwk', keyData, { name: algo }, false, ['decrypt']);
|
||||||
|
const decrypted = await window.crypto.subtle
|
||||||
|
.decrypt({ name: algo, iv: iv }, key, rawBuffer);
|
||||||
|
|
||||||
|
return Promise.resolve(new Blob([decrypted]));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('get error while keys and decrypt operations');
|
||||||
|
console.error(e);
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
build_convert_link,
|
||||||
|
build_download_info_link,
|
||||||
|
build_wopi_editor_link,
|
||||||
|
download_and_decrypt_doc,
|
||||||
|
download_doc,
|
||||||
|
is_extension_editable,
|
||||||
|
is_extension_viewable,
|
||||||
|
};
|
@ -6,7 +6,7 @@
|
|||||||
{{ 'workflow.Document deleted'|trans }}
|
{{ 'workflow.Document deleted'|trans }}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="flex-table accompanying_course_work-list">
|
<div class="flex-table accompanying-course-work">
|
||||||
<div class="item-bloc document-item bg-chill-llight-gray">
|
<div class="item-bloc document-item bg-chill-llight-gray">
|
||||||
<div class="row justify-content-center my-4">
|
<div class="row justify-content-center my-4">
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
@ -22,7 +22,6 @@
|
|||||||
{{ document.description }}
|
{{ document.description }}
|
||||||
</blockquote>
|
</blockquote>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -47,21 +46,8 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
{{ m.download_button(document.object, document.title) }}
|
{{ document.object|chill_document_button_group(document.title, not freezed) }}
|
||||||
</li>
|
</li>
|
||||||
{% if chill_document_is_editable(document.object) %}
|
|
||||||
{% if not freezed %}
|
|
||||||
<li>
|
|
||||||
{{ document.object|chill_document_edit_button({'title': document.title|e('html') }) }}
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li>
|
|
||||||
<a class="btn btn-wopilink disabled" href="#" title="{{ 'workflow.freezed document'|trans }}">
|
|
||||||
{{ 'Update document'|trans }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE', document) and document.course != null %}
|
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE', document) and document.course != null %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ chill_path_add_return_path('accompanying_course_document_show', {'course': document.course.id, 'id': document.id}) }}" class="btn btn-show"></a>
|
<a href="{{ chill_path_add_return_path('accompanying_course_document_show', {'course': document.course.id, 'id': document.id}) }}" class="btn btn-show"></a>
|
||||||
|
@ -8,16 +8,16 @@
|
|||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{{ encore_entry_script_tags('mod_async_upload') }}
|
|
||||||
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
|
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
|
||||||
{{ encore_entry_script_tags('mod_entity_workflow_pick') }}
|
{{ encore_entry_script_tags('mod_entity_workflow_pick') }}
|
||||||
|
{{ encore_entry_script_tags('mod_document_action_buttons_group') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
|
||||||
{{ encore_entry_link_tags('mod_docgen_picktemplate') }}
|
{{ encore_entry_link_tags('mod_docgen_picktemplate') }}
|
||||||
{{ encore_entry_link_tags('mod_entity_workflow_pick') }}
|
{{ encore_entry_link_tags('mod_entity_workflow_pick') }}
|
||||||
|
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||||
{{ encore_entry_link_tags('mod_entity_workflow_pick') }}
|
{{ encore_entry_link_tags('mod_entity_workflow_pick') }}
|
||||||
|
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@ -61,13 +62,8 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
{{ m.download_button(document.object, document.title) }}
|
{{ document.object|chill_document_button_group(document.title) }}
|
||||||
</li>
|
</li>
|
||||||
{% if chill_document_is_editable(document.object) %}
|
|
||||||
<li>
|
|
||||||
{{ document.object|chill_document_edit_button }}
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% set workflows_frame = chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) %}
|
{% set workflows_frame = chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) %}
|
||||||
{% if workflows_frame is not empty %}
|
{% if workflows_frame is not empty %}
|
||||||
<li>
|
<li>
|
||||||
@ -86,4 +82,5 @@
|
|||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{{ encore_entry_script_tags('mod_async_upload') }}
|
{{ encore_entry_script_tags('mod_async_upload') }}
|
||||||
{{ encore_entry_script_tags('mod_entity_workflow_pick') }}
|
{{ encore_entry_script_tags('mod_entity_workflow_pick') }}
|
||||||
|
{{ encore_entry_script_tags('mod_document_action_buttons_group') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
{%- import "@ChillDocStore/Macro/macro.html.twig" as m -%}
|
||||||
|
<div
|
||||||
|
data-download-buttons
|
||||||
|
data-stored-object="{{ document_json|json_encode|escape('html_attr') }}"
|
||||||
|
data-can-edit="{{ can_edit ? '1' : '0' }}"
|
||||||
|
{% if options['small'] is defined %}data-button-small="{{ options['small'] ? '1' : '0' }}"{% endif %}
|
||||||
|
{% if title|default(document.title)|default(null) is not null %}data-filename="{{ title|default(document.title)|escape('html_attr') }}"{% endif %}></div>
|
@ -53,15 +53,10 @@
|
|||||||
<li>
|
<li>
|
||||||
<a href="{{ path('accompanying_course_document_edit', {'course': accompanyingCourse.id, 'id': document.id }) }}" class="btn btn-update"></a>
|
<a href="{{ path('accompanying_course_document_edit', {'course': accompanyingCourse.id, 'id': document.id }) }}" class="btn btn-update"></a>
|
||||||
</li>
|
</li>
|
||||||
{% if chill_document_is_editable(document.object) %}
|
|
||||||
<li>
|
|
||||||
{{ document.object|chill_document_edit_button }}
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
|
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
|
||||||
<li>
|
<li>
|
||||||
{{ m.download_button(document.object, document.title) }}
|
{{ document.object|chill_document_button_group(document.title, is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document)) }}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ chill_path_add_return_path('accompanying_course_document_show', {'course': accompanyingCourse.id, 'id': document.id}) }}" class="btn btn-show"></a>
|
<a href="{{ chill_path_add_return_path('accompanying_course_document_show', {'course': accompanyingCourse.id, 'id': document.id}) }}" class="btn btn-show"></a>
|
||||||
@ -80,15 +75,10 @@
|
|||||||
<li>
|
<li>
|
||||||
<a href="{{ path('person_document_edit', {'person': person.id, 'id': document.id}) }}" class="btn btn-update"></a>
|
<a href="{{ path('person_document_edit', {'person': person.id, 'id': document.id}) }}" class="btn btn-update"></a>
|
||||||
</li>
|
</li>
|
||||||
{% if chill_document_is_editable(document.object) %}
|
|
||||||
<li>
|
|
||||||
{{ document.object|chill_document_edit_button }}
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_granted('CHILL_PERSON_DOCUMENT_SEE_DETAILS', document) %}
|
{% if is_granted('CHILL_PERSON_DOCUMENT_SEE_DETAILS', document) %}
|
||||||
<li>
|
<li>
|
||||||
{{ m.download_button(document.object, document.title) }}
|
{{ document.object|chill_document_button_group(document.title, is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document)) }}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('person_document_show', {'person': person.id, 'id': document.id}) }}" class="btn btn-show"></a>
|
<a href="{{ path('person_document_show', {'person': person.id, 'id': document.id}) }}" class="btn btn-show"></a>
|
||||||
|
@ -13,3 +13,36 @@
|
|||||||
{{ 'Download'|trans }}</a>
|
{{ 'Download'|trans }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro download_button_small(storedObject, filename = null) %}
|
||||||
|
{% if storedObject is null %}
|
||||||
|
<!-- No document to download -->
|
||||||
|
{% else %}
|
||||||
|
<a class="btn btn-sm btn-download"
|
||||||
|
data-label-preparing="{{ ('Preparing'|trans ~ '...')|escape('html_attr') }}"
|
||||||
|
data-label-ready="{{ 'Ready to show'|trans|escape('html_attr') }}"
|
||||||
|
data-download-button
|
||||||
|
data-key="{{ storedObject.keyInfos|json_encode|escape('html_attr') }}"
|
||||||
|
data-iv="{{ storedObject.iv|json_encode|escape('html_attr') }}"
|
||||||
|
data-temp-url-get-generator="{{ storedObject|generate_url|escape('html_attr') }}"
|
||||||
|
data-mime-type="{{ storedObject.type|escape('html_attr') }}" {% if filename is not null %}data-filename="{{ filename|escape('html_attr') }}"{% endif %}>
|
||||||
|
{{ 'Download'|trans }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro download_button_group(storedObject, canEdit = true, filename = null, options = {}) %}
|
||||||
|
{% if storedObject is null %}
|
||||||
|
<!-- No document to download -->
|
||||||
|
{% else %}
|
||||||
|
<div
|
||||||
|
data-download-buttons
|
||||||
|
data-uuid="{{ storedObject.uuid|escape('html_attr') }}"
|
||||||
|
data-key="{{ storedObject.keyInfos|json_encode|escape('html_attr') }}"
|
||||||
|
data-iv="{{ storedObject.iv|json_encode|escape('html_attr') }}"
|
||||||
|
data-temp-url-generator="{{ storedObject|generate_url|escape('html_attr') }}"
|
||||||
|
data-mime-type="{{ storedObject.type|escape('html_attr') }}"
|
||||||
|
data-can-edit="{{ canEdit ? '1' : '0' }}"
|
||||||
|
{% if options['small'] is defined %}data-button-small="{{ options['small'] ? '1' : '0' }}"{% endif %}
|
||||||
|
{% if filename|default(storedObject.title)|default(null) is not null %}data-filename="{{ filename|default(storedObject.title)|escape('html_attr') }}"{% endif %}></div>
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro %}
|
||||||
|
@ -48,8 +48,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<div class="metadata">
|
<span class="metadata">
|
||||||
<i class="fa {{ icon }} fa-lg me-1"></i>
|
<i class="fa {{ icon }} fa-lg me-1"></i>
|
||||||
{{ label|capitalize }}
|
{{ label|capitalize }}
|
||||||
</div>
|
</span>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
@ -27,16 +27,16 @@
|
|||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{{ encore_entry_script_tags('mod_async_upload') }}
|
|
||||||
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
|
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
|
||||||
{{ encore_entry_script_tags('mod_entity_workflow_pick') }}
|
{{ encore_entry_script_tags('mod_entity_workflow_pick') }}
|
||||||
|
{{ encore_entry_script_tags('mod_document_action_buttons_group') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
|
||||||
{{ encore_entry_link_tags('mod_docgen_picktemplate') }}
|
{{ encore_entry_link_tags('mod_docgen_picktemplate') }}
|
||||||
{{ encore_entry_link_tags('mod_entity_workflow_pick') }}
|
{{ encore_entry_link_tags('mod_entity_workflow_pick') }}
|
||||||
|
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
@ -24,7 +24,11 @@
|
|||||||
{% block title %}{{ 'Detail of document of %name%'|trans({ '%name%': person|chill_entity_render_string } ) }}{% endblock %}
|
{% block title %}{{ 'Detail of document of %name%'|trans({ '%name%': person|chill_entity_render_string } ) }}{% endblock %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
{{ encore_entry_script_tags('mod_async_upload') }}
|
{{ encore_entry_script_tags('mod_document_action_buttons_group') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@ -70,6 +74,10 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<li>
|
||||||
|
{{ document.object|chill_document_button_group(document.title, is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document)) }}
|
||||||
|
</li>
|
||||||
|
|
||||||
{% if is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document) %}
|
{% if is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document) %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('person_document_edit', {'id': document.id, 'person': person.id}) }}" class="btn btn-edit">
|
<a href="{{ path('person_document_edit', {'id': document.id, 'person': person.id}) }}" class="btn btn-edit">
|
||||||
@ -77,16 +85,4 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<li>
|
|
||||||
{{ m.download_button(document.object, document.title) }}
|
|
||||||
</li>
|
|
||||||
|
|
||||||
{% if chill_document_is_editable(document.object) %}
|
|
||||||
<li>
|
|
||||||
{{ document.object|chill_document_edit_button }}
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# {{ include('ChillDocStoreBundle:PersonDocument:_delete_form.html.twig') }} #}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -24,6 +24,10 @@ class WopiEditTwigExtension extends AbstractExtension
|
|||||||
'needs_environment' => true,
|
'needs_environment' => true,
|
||||||
'is_safe' => ['html'],
|
'is_safe' => ['html'],
|
||||||
]),
|
]),
|
||||||
|
new TwigFilter('chill_document_button_group', [WopiEditTwigExtensionRuntime::class, 'renderButtonGroup'], [
|
||||||
|
'needs_environment' => true,
|
||||||
|
'is_safe' => ['html'],
|
||||||
|
]),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ namespace Chill\DocStoreBundle\Templating;
|
|||||||
|
|
||||||
use ChampsLibres\WopiLib\Contract\Service\Discovery\DiscoveryInterface;
|
use ChampsLibres\WopiLib\Contract\Service\Discovery\DiscoveryInterface;
|
||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
use Twig\Environment;
|
use Twig\Environment;
|
||||||
use Twig\Extension\RuntimeExtensionInterface;
|
use Twig\Extension\RuntimeExtensionInterface;
|
||||||
|
|
||||||
@ -112,20 +114,53 @@ final class WopiEditTwigExtensionRuntime implements RuntimeExtensionInterface
|
|||||||
'application/pdf',
|
'application/pdf',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private const DEFAULT_OPTIONS_TEMPLATE_BUTTON_GROUP = [
|
||||||
|
'small' => false,
|
||||||
|
];
|
||||||
|
|
||||||
private const TEMPLATE = '@ChillDocStore/Button/wopi_edit_document.html.twig';
|
private const TEMPLATE = '@ChillDocStore/Button/wopi_edit_document.html.twig';
|
||||||
|
|
||||||
|
private const TEMPLATE_BUTTON_GROUP = '@ChillDocStore/Button/button_group.html.twig';
|
||||||
|
|
||||||
private DiscoveryInterface $discovery;
|
private DiscoveryInterface $discovery;
|
||||||
|
|
||||||
public function __construct(DiscoveryInterface $discovery)
|
private NormalizerInterface $normalizer;
|
||||||
|
|
||||||
|
public function __construct(DiscoveryInterface $discovery, NormalizerInterface $normalizer)
|
||||||
{
|
{
|
||||||
$this->discovery = $discovery;
|
$this->discovery = $discovery;
|
||||||
|
$this->normalizer = $normalizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return true if the document is editable.
|
||||||
|
*
|
||||||
|
* **NOTE**: as the Vue button does have similar test, this is not required if in use with
|
||||||
|
* the dedicated Vue component (GroupDownloadButton.vue, WopiEditButton.vue)
|
||||||
|
*/
|
||||||
public function isEditable(StoredObject $document): bool
|
public function isEditable(StoredObject $document): bool
|
||||||
{
|
{
|
||||||
return in_array($document->getType(), self::SUPPORTED_MIMES, true);
|
return in_array($document->getType(), self::SUPPORTED_MIMES, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{small: boolean} $options
|
||||||
|
*
|
||||||
|
* @throws \Twig\Error\LoaderError
|
||||||
|
* @throws \Twig\Error\RuntimeError
|
||||||
|
* @throws \Twig\Error\SyntaxError
|
||||||
|
*/
|
||||||
|
public function renderButtonGroup(Environment $environment, StoredObject $document, ?string $title = null, bool $canEdit = true, array $options = []): string
|
||||||
|
{
|
||||||
|
return $environment->render(self::TEMPLATE_BUTTON_GROUP, [
|
||||||
|
'document' => $document,
|
||||||
|
'document_json' => $this->normalizer->normalize($document, 'json', [AbstractNormalizer::GROUPS => ['read']]),
|
||||||
|
'title' => $title,
|
||||||
|
'can_edit' => $canEdit,
|
||||||
|
'options' => array_merge($options, self::DEFAULT_OPTIONS_TEMPLATE_BUTTON_GROUP),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function renderEditButton(Environment $environment, StoredObject $document, ?array $options = null): string
|
public function renderEditButton(Environment $environment, StoredObject $document, ?array $options = null): string
|
||||||
{
|
{
|
||||||
return $environment->render(self::TEMPLATE, [
|
return $environment->render(self::TEMPLATE, [
|
||||||
|
@ -4,4 +4,5 @@ module.exports = function(encore)
|
|||||||
ChillDocStoreAssets: __dirname + '/Resources/public'
|
ChillDocStoreAssets: __dirname + '/Resources/public'
|
||||||
});
|
});
|
||||||
encore.addEntry('mod_async_upload', __dirname + '/Resources/public/module/async_upload/index.js');
|
encore.addEntry('mod_async_upload', __dirname + '/Resources/public/module/async_upload/index.js');
|
||||||
|
encore.addEntry('mod_document_action_buttons_group', __dirname + '/Resources/public/module/document_action_buttons_group/index');
|
||||||
};
|
};
|
||||||
|
@ -14,7 +14,7 @@ Edit attributes: Modifier les propriétés du document
|
|||||||
Existing document: Document existant
|
Existing document: Document existant
|
||||||
No document to download: Aucun document à télécharger
|
No document to download: Aucun document à télécharger
|
||||||
'Choose a document category': Choisissez une catégorie de document
|
'Choose a document category': Choisissez une catégorie de document
|
||||||
Any document found: Aucun document trouvé
|
No document found: Aucun document trouvé
|
||||||
The document is successfully registered: Le document est enregistré
|
The document is successfully registered: Le document est enregistré
|
||||||
The document is successfully updated: Le document est mis à jour
|
The document is successfully updated: Le document est mis à jour
|
||||||
Any description: Aucune description
|
Any description: Aucune description
|
||||||
@ -66,3 +66,11 @@ online_edit_document: Éditer en ligne
|
|||||||
|
|
||||||
workflow:
|
workflow:
|
||||||
Document deleted: Document supprimé
|
Document deleted: Document supprimé
|
||||||
|
|
||||||
|
# ROLES
|
||||||
|
accompanyingCourseDocument: Documents dans les parcours d'accompagnement
|
||||||
|
CHILL_ACCOMPANYING_COURSE_DOCUMENT_CREATE: Créer un document
|
||||||
|
CHILL_ACCOMPANYING_COURSE_DOCUMENT_DELETE: Supprimer un document
|
||||||
|
CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE: Voir les documents
|
||||||
|
CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS: Voir les détails d'un document
|
||||||
|
CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE: Modifier un document
|
@ -28,7 +28,7 @@ class LocationController extends CRUDController
|
|||||||
|
|
||||||
protected function customizeQuery(string $action, Request $request, $query): void
|
protected function customizeQuery(string $action, Request $request, $query): void
|
||||||
{
|
{
|
||||||
$query->where('e.availableForUsers = true'); //TODO not working
|
$query->where('e.availableForUsers = "TRUE"');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
|
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
|
||||||
|
25
src/Bundle/ChillMainBundle/Controller/ScopeApiController.php
Normal file
25
src/Bundle/ChillMainBundle/Controller/ScopeApiController.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?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\Controller;
|
||||||
|
|
||||||
|
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
class ScopeApiController extends ApiController
|
||||||
|
{
|
||||||
|
protected function customizeQuery(string $action, Request $request, $query): void
|
||||||
|
{
|
||||||
|
if ('_index' === $action) {
|
||||||
|
$query->andWhere($query->expr()->eq('e.active', "'TRUE'"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||||||
namespace Chill\MainBundle\Controller;
|
namespace Chill\MainBundle\Controller;
|
||||||
|
|
||||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
@ -70,4 +71,13 @@ class UserApiController extends ApiController
|
|||||||
$query->andWhere($query->expr()->eq('e.enabled', "'TRUE'"));
|
$query->andWhere($query->expr()->eq('e.enabled', "'TRUE'"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $query
|
||||||
|
* @param mixed $_format
|
||||||
|
*/
|
||||||
|
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format)
|
||||||
|
{
|
||||||
|
return $query->orderBy('e.label', 'ASC');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -655,6 +655,7 @@ class ChillMainExtension extends Extension implements
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
'class' => \Chill\MainBundle\Entity\Scope::class,
|
'class' => \Chill\MainBundle\Entity\Scope::class,
|
||||||
|
'controller' => \Chill\MainBundle\Controller\ScopeApiController::class,
|
||||||
'name' => 'scope',
|
'name' => 'scope',
|
||||||
'base_path' => '/api/1.0/main/scope',
|
'base_path' => '/api/1.0/main/scope',
|
||||||
'base_role' => 'ROLE_USER',
|
'base_role' => 'ROLE_USER',
|
||||||
|
@ -83,9 +83,9 @@ interface ExportInterface extends ExportElementInterface
|
|||||||
*
|
*
|
||||||
* @param string $key The column key, as added in the query
|
* @param string $key The column key, as added in the query
|
||||||
* @param mixed[] $values The values from the result. if there are duplicates, those might be given twice. Example: array('FR', 'BE', 'CZ', 'FR', 'BE', 'FR')
|
* @param mixed[] $values The values from the result. if there are duplicates, those might be given twice. Example: array('FR', 'BE', 'CZ', 'FR', 'BE', 'FR')
|
||||||
* @param mixed $data The data from the export's form (as defined in `buildForm`
|
* @param mixed $data The data from the export's form (as defined in `buildForm`)
|
||||||
*
|
*
|
||||||
* @return Closure where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }`
|
* @return pure-callable(null|string|int|float|'_header' $value):string|int|\DateTimeInterface where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }`
|
||||||
*/
|
*/
|
||||||
public function getLabels($key, array $values, $data);
|
public function getLabels($key, array $values, $data);
|
||||||
|
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides support to transform aggregates datas in correct format string into column of exports.
|
||||||
|
*/
|
||||||
|
class AggregateStringHelper
|
||||||
|
{
|
||||||
|
public function getLabelMulti(string $key, array $values, string $header)
|
||||||
|
{
|
||||||
|
return static function ($value) use ($header) {
|
||||||
|
if ('_header' === $value) {
|
||||||
|
return $header;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $value || '' === $value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(
|
||||||
|
'|',
|
||||||
|
json_decode($value, true)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -35,6 +35,10 @@ class DateTimeHelper
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($value instanceof \DateTimeInterface) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
// warning: won't work with DateTimeImmutable as we reset time a few lines later
|
// warning: won't work with DateTimeImmutable as we reset time a few lines later
|
||||||
$date = DateTime::createFromFormat('Y-m-d', $value);
|
$date = DateTime::createFromFormat('Y-m-d', $value);
|
||||||
$hasTime = false;
|
$hasTime = false;
|
||||||
|
@ -45,9 +45,9 @@ class UserHelper
|
|||||||
|
|
||||||
public function getLabelMulti($key, array $values, string $header): callable
|
public function getLabelMulti($key, array $values, string $header): callable
|
||||||
{
|
{
|
||||||
return function ($value) {
|
return function ($value) use ($header) {
|
||||||
if ('_header' === $value) {
|
if ('_header' === $value) {
|
||||||
return 'users name';
|
return $header;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null === $value) {
|
if (null === $value) {
|
||||||
|
@ -13,6 +13,7 @@ namespace Chill\MainBundle\Form;
|
|||||||
|
|
||||||
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
|
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
@ -21,7 +22,12 @@ class ScopeType extends AbstractType
|
|||||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
{
|
{
|
||||||
$builder
|
$builder
|
||||||
->add('name', TranslatableStringFormType::class);
|
->add('name', TranslatableStringFormType::class)
|
||||||
|
->add('active', ChoiceType::class, [
|
||||||
|
'choices' => [
|
||||||
|
'Active' => true,
|
||||||
|
'Inactive' => false,
|
||||||
|
], ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -221,12 +221,8 @@ footer.footer {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
div.admin {
|
div.admin {
|
||||||
flex-direction: row-reverse;
|
|
||||||
div.vertical-menu {
|
div.vertical-menu {
|
||||||
font-size: 0.9em;
|
.list-group-item {}
|
||||||
.list-group-item {
|
|
||||||
padding: 0.3rem 0.7rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,12 +303,12 @@ table.table-bordered {
|
|||||||
/// meta-data
|
/// meta-data
|
||||||
div.createdBy,
|
div.createdBy,
|
||||||
div.updatedBy,
|
div.updatedBy,
|
||||||
div.metadata {
|
.metadata {
|
||||||
span.user, span.date {
|
span.user, span.date {
|
||||||
text-decoration: underline dotted;
|
text-decoration: underline dotted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div.metadata {
|
.metadata {
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
color: $gray-600;
|
color: $gray-600;
|
||||||
span.user, span.date {
|
span.user, span.date {
|
||||||
@ -368,6 +364,19 @@ div#flashMessages {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// unbullet lists
|
||||||
|
ul.unbullet {
|
||||||
|
list-style-type: none;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
/// libellé
|
||||||
|
span.dt {
|
||||||
|
font-size: 90%;
|
||||||
|
font-weight: bolder;
|
||||||
|
background-color: var(--bs-chill-light-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* SPECIFIC RULES
|
* SPECIFIC RULES
|
||||||
*/
|
*/
|
||||||
|
@ -106,18 +106,5 @@ section.chill-entity {
|
|||||||
// used for comment-embeddable
|
// used for comment-embeddable
|
||||||
&.entity-comment-embeddable {
|
&.entity-comment-embeddable {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
/* already defined !!
|
|
||||||
div.metadata {
|
|
||||||
font-size: smaller;
|
|
||||||
color: $gray-600;
|
|
||||||
span.user, span.date {
|
|
||||||
text-decoration: underline dotted;
|
|
||||||
&:hover {
|
|
||||||
color: $gray-700;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,33 +48,35 @@
|
|||||||
|
|
||||||
</modal>
|
</modal>
|
||||||
</teleport>
|
</teleport>
|
||||||
<div class="mt-4" v-else>
|
<template v-else>
|
||||||
<suggest-pane v-if="flag.suggestPane"
|
<div v-if="flag.suggestPane" class="mt-4 flex-grow-1">
|
||||||
v-bind:context="this.context"
|
<suggest-pane
|
||||||
v-bind:options="this.options"
|
v-bind:context="this.context"
|
||||||
v-bind:defaultz="this.defaultz"
|
v-bind:options="this.options"
|
||||||
v-bind:entity="this.entity"
|
v-bind:defaultz="this.defaultz"
|
||||||
v-bind:flag="this.flag"
|
v-bind:entity="this.entity"
|
||||||
v-bind:insideModal="false"
|
v-bind:flag="this.flag"
|
||||||
@pick-address="this.pickAddress"
|
v-bind:insideModal="false"
|
||||||
ref="suggestAddress">
|
@pick-address="this.pickAddress"
|
||||||
|
ref="suggestAddress">
|
||||||
|
|
||||||
<template v-slot:before v-if="!bypassFirstStep">
|
<template v-slot:before v-if="!bypassFirstStep">
|
||||||
<a class="btn btn-cancel" @click="resetPane">
|
<a class="btn btn-cancel" @click="resetPane">
|
||||||
{{ $t('action.cancel') }}
|
{{ $t('action.cancel') }}
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:action>
|
<template v-slot:action>
|
||||||
<li>
|
<li>
|
||||||
<button @click="openEditPane"
|
<button @click="openEditPane"
|
||||||
class="btn btn-create">
|
class="btn btn-create">
|
||||||
{{ $t('create_a_new_address')}}
|
{{ $t('create_a_new_address')}}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</suggest-pane>
|
</suggest-pane>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- step 2 -->
|
<!-- step 2 -->
|
||||||
<teleport to="body" v-if="inModal">
|
<teleport to="body" v-if="inModal">
|
||||||
@ -118,40 +120,42 @@
|
|||||||
|
|
||||||
</modal>
|
</modal>
|
||||||
</teleport>
|
</teleport>
|
||||||
<div class="mt-4" v-else>
|
<template v-else>
|
||||||
<edit-pane v-if="flag.editPane"
|
<div v-if="flag.editPane" class="mt-4 flex-grow-1">
|
||||||
v-bind:context="this.context"
|
<edit-pane
|
||||||
v-bind:options="this.options"
|
v-bind:context="this.context"
|
||||||
v-bind:defaultz="this.defaultz"
|
v-bind:options="this.options"
|
||||||
v-bind:entity="this.entity"
|
v-bind:defaultz="this.defaultz"
|
||||||
v-bind:flag="this.flag"
|
v-bind:entity="this.entity"
|
||||||
v-bind:errors="this.errors"
|
v-bind:flag="this.flag"
|
||||||
v-bind:checkErrors="this.checkErrors"
|
v-bind:errors="this.errors"
|
||||||
v-bind:insideModal="false"
|
v-bind:checkErrors="this.checkErrors"
|
||||||
@getCities="getCities"
|
v-bind:insideModal="false"
|
||||||
@getReferenceAddresses="getReferenceAddresses">
|
@getCities="getCities"
|
||||||
|
@getReferenceAddresses="getReferenceAddresses">
|
||||||
|
|
||||||
<template v-slot:before>
|
<template v-slot:before>
|
||||||
<a class="btn btn-cancel" @click="resetPane">
|
<a class="btn btn-cancel" @click="resetPane">
|
||||||
{{ $t('action.cancel') }}
|
{{ $t('action.cancel') }}
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:action>
|
<template v-slot:action>
|
||||||
<li v-if="!this.context.edit && this.useDatePane">
|
<li v-if="!this.context.edit && this.useDatePane">
|
||||||
<button class="btn btn-update change-icon" @click="closeEditPane">
|
<button class="btn btn-update change-icon" @click="closeEditPane">
|
||||||
{{ $t('nav.next')}}
|
{{ $t('nav.next')}}
|
||||||
<i class="fa fa-fw fa-arrow-right"></i>
|
<i class="fa fa-fw fa-arrow-right"></i>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li v-else>
|
<li v-else>
|
||||||
<button class="btn btn-save" @click="closeEditPane">
|
<button class="btn btn-save" @click="closeEditPane">
|
||||||
{{ $t('action.save')}}
|
{{ $t('action.save')}}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</edit-pane>
|
</edit-pane>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- step 3 -->
|
<!-- step 3 -->
|
||||||
<teleport to="body" v-if="inModal">
|
<teleport to="body" v-if="inModal">
|
||||||
@ -192,32 +196,34 @@
|
|||||||
|
|
||||||
</modal>
|
</modal>
|
||||||
</teleport>
|
</teleport>
|
||||||
<div class="mt-4" v-else>
|
<template v-else>
|
||||||
<date-pane v-if="flag.datePane"
|
<div v-if="flag.datePane" class="mt-4 flex-grow-1">
|
||||||
v-bind:context="this.context"
|
<date-pane
|
||||||
v-bind:options="this.options"
|
v-bind:context="this.context"
|
||||||
v-bind:defaultz="this.defaultz"
|
v-bind:options="this.options"
|
||||||
v-bind:entity="this.entity"
|
v-bind:defaultz="this.defaultz"
|
||||||
v-bind:flag="this.flag"
|
v-bind:entity="this.entity"
|
||||||
v-bind:insideModal="false"
|
v-bind:flag="this.flag"
|
||||||
ref="dateAddress">
|
v-bind:insideModal="false"
|
||||||
|
ref="dateAddress">
|
||||||
|
|
||||||
<template v-slot:before>
|
<template v-slot:before>
|
||||||
<button class="btn btn-misc" @click="openEditPane">
|
<button class="btn btn-misc" @click="openEditPane">
|
||||||
<i class="fa fa-fw fa-arrow-left"></i>
|
<i class="fa fa-fw fa-arrow-left"></i>
|
||||||
{{ $t('nav.previous')}}
|
{{ $t('nav.previous')}}
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
<template v-slot:action>
|
|
||||||
<li>
|
|
||||||
<button class="btn btn-save" @click="closeDatePane">
|
|
||||||
{{ $t('action.save')}}
|
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</template>
|
||||||
</template>
|
<template v-slot:action>
|
||||||
|
<li>
|
||||||
|
<button class="btn btn-save" @click="closeDatePane">
|
||||||
|
{{ $t('action.save')}}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
</date-pane>
|
</date-pane>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -187,6 +187,7 @@ div.address-form {
|
|||||||
div#address_map {
|
div#address_map {
|
||||||
height: 400px;
|
height: 400px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
|
|
||||||
<div v-if="!onlyButton">
|
<div v-if="!onlyButton" class="mt-4 flex-grow-1">
|
||||||
<div class="loading">
|
<div class="loading">
|
||||||
<i v-if="flag.loading" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"></i>
|
<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>
|
<span class="sr-only">{{ $t('loading') }}</span>
|
||||||
@ -37,12 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="this.context.edit" class="mb-3 row">
|
<address-render-box :address="address" :isMultiline="false" :useDatePane="useDatePane"></address-render-box>
|
||||||
<div class="col-sm-4"></div>
|
|
||||||
<div class="address-container col-sm-8">
|
|
||||||
<address-render-box :address="address" :isMultiline="false" :useDatePane="useDatePane"></address-render-box>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="this.context.target.name === 'household' || this.context.edit">
|
<div v-if="this.context.target.name === 'household' || this.context.edit">
|
||||||
<action-buttons
|
<action-buttons
|
||||||
@ -50,7 +45,7 @@
|
|||||||
:defaultz="this.defaultz">
|
:defaultz="this.defaultz">
|
||||||
<template v-slot:action>
|
<template v-slot:action>
|
||||||
<button @click.prevent="$emit('openEditPane')"
|
<button @click.prevent="$emit('openEditPane')"
|
||||||
class="btn btn-sm" :class="getClassButton"
|
class="btn" :class="getClassButton"
|
||||||
type="button" name="button" :title="$t(getTextButton)">
|
type="button" name="button" :title="$t(getTextButton)">
|
||||||
<span v-if="displayTextButton">{{ $t(getTextButton) }}</span>
|
<span v-if="displayTextButton">{{ $t(getTextButton) }}</span>
|
||||||
</button>
|
</button>
|
||||||
@ -58,10 +53,6 @@
|
|||||||
</action-buttons>
|
</action-buttons>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!this.context.edit">
|
|
||||||
<address-render-box :address="address" :isMultiline="false" :useDatePane="useDatePane"></address-render-box>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="onlyButton">
|
<div v-if="onlyButton">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="accompanying_course_work">
|
<div class="accompanying-course-work">
|
||||||
<div class="alert alert-light">{{ $t('my_evaluations.description') }}</div>
|
<div class="alert alert-light">{{ $t('my_evaluations.description') }}</div>
|
||||||
<span v-if="noResults" class="chill-no-data-statement">{{ $t('no_data') }}</span>
|
<span v-if="noResults" class="chill-no-data-statement">{{ $t('no_data') }}</span>
|
||||||
<tab-table v-else>
|
<tab-table v-else>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// CURRENTLY NOT IN USE
|
// CURRENTLY NOT IN USE
|
||||||
<template>
|
<template>
|
||||||
<div class="accompanying_course_work">
|
<div class="accompanying-course-work">
|
||||||
<div class="alert alert-light">{{ $t('my_works.description') }}</div>
|
<div class="alert alert-light">{{ $t('my_works.description') }}</div>
|
||||||
<span v-if="noResults" class="chill-no-data-statement">{{ $t('no_data') }}</span>
|
<span v-if="noResults" class="chill-no-data-statement">{{ $t('no_data') }}</span>
|
||||||
<tab-table v-else>
|
<tab-table v-else>
|
||||||
|
@ -29,9 +29,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% block admin_content %}
|
<div class="row justify-content-center">
|
||||||
<!-- block admin content empty -->
|
<div class="col-md-10 col-xxl">
|
||||||
{% endblock %}
|
{% block admin_content %}
|
||||||
|
<!-- block admin content empty -->
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
{% block vertical_menu_content %}
|
{% block vertical_menu_content %}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<div class="{% block crud_content_main_div_class %}col-10 centered{% endblock %}">
|
|
||||||
{% block crud_content_header %}
|
{% block crud_content_header %}
|
||||||
<h1>{{ ('crud.'~crud_name~'.title_delete')|trans({ '%as_string%': entity|chill_entity_render_string }) }}</h1>
|
<h1>{{ ('crud.'~crud_name~'.title_delete')|trans({ '%as_string%': entity|chill_entity_render_string }) }}</h1>
|
||||||
{% endblock crud_content_header %}
|
{% endblock crud_content_header %}
|
||||||
@ -34,4 +34,4 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
</div>
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{% set formId = crudMainFormId|default('crud_main_form') %}
|
{% set formId = crudMainFormId|default('crud_main_form') %}
|
||||||
<div class="{% block crud_content_main_div_class %}col-10 centered{% endblock %}">
|
|
||||||
{% block crud_content_header %}
|
{% block crud_content_header %}
|
||||||
<h1>{{ ('crud.'~crud_name~'.title_edit')|trans }}</h1>
|
<h1>{{ ('crud.'~crud_name~'.title_edit')|trans }}</h1>
|
||||||
{% endblock crud_content_header %}
|
{% endblock crud_content_header %}
|
||||||
@ -64,4 +64,4 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
<div class="col-10 centered">
|
|
||||||
|
|
||||||
{% block index_header %}
|
{% block index_header %}
|
||||||
<h1>{{ ('crud.' ~ crud_name ~ '.index.title')|trans({'%crud_name%': crud_name}) }}</h1>
|
<h1>{{ ('crud.' ~ crud_name ~ '.index.title')|trans({'%crud_name%': crud_name}) }}</h1>
|
||||||
{% endblock index_header %}
|
{% endblock index_header %}
|
||||||
@ -16,7 +14,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% block table_entities %}
|
{% block table_entities %}
|
||||||
<table>
|
<table class="table table-bordered border-dark">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{% block table_entities_thead_tr %}
|
{% block table_entities_thead_tr %}
|
||||||
@ -54,4 +52,3 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endblock list_actions %}
|
{% endblock list_actions %}
|
||||||
</div>
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{% set formId = crudMainFormId|default('crud_main_form') %}
|
{% set formId = crudMainFormId|default('crud_main_form') %}
|
||||||
<div class="{% block crud_content_main_div_class %}col-10 centered{% endblock %}">
|
|
||||||
{% block crud_content_header %}
|
{% block crud_content_header %}
|
||||||
<h1>{{ ('crud.' ~ crud_name ~ '.title_new')|trans({'%crud_name%' : crud_name }) }}</h1>
|
<h1>{{ ('crud.' ~ crud_name ~ '.title_new')|trans({'%crud_name%' : crud_name }) }}</h1>
|
||||||
{% endblock crud_content_header %}
|
{% endblock crud_content_header %}
|
||||||
@ -52,4 +52,4 @@
|
|||||||
|
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<div class="{% block crud_content_main_div_class %}col-10 centered{% endblock %}">
|
|
||||||
{% block crud_content_header %}
|
{% block crud_content_header %}
|
||||||
<h1>{{ 'crud.%crud_name%.title_view'|trans({'%crud_name%' : crud_name }) }}</h1>
|
<h1>{{ 'crud.%crud_name%.title_view'|trans({'%crud_name%' : crud_name }) }}</h1>
|
||||||
{% endblock crud_content_header %}
|
{% endblock crud_content_header %}
|
||||||
@ -60,4 +60,4 @@
|
|||||||
{% endblock crud_content_view_actions %}
|
{% endblock crud_content_view_actions %}
|
||||||
|
|
||||||
{% endblock crud_content_view %}
|
{% endblock crud_content_view %}
|
||||||
</div>
|
|
||||||
|
@ -7,8 +7,6 @@
|
|||||||
|
|
||||||
{{ form_start(edit_form) }}
|
{{ form_start(edit_form) }}
|
||||||
{{ form_row(edit_form.name) }}
|
{{ form_row(edit_form.name) }}
|
||||||
{{ form_row(edit_form.submit, { 'attr' : { 'class' : 'btn btn-chill-green' } } ) }}
|
|
||||||
{{ form_end(edit_form) }}
|
|
||||||
|
|
||||||
<ul class="record_actions sticky-form-buttons">
|
<ul class="record_actions sticky-form-buttons">
|
||||||
<li class='cancel'>
|
<li class='cancel'>
|
||||||
@ -16,5 +14,10 @@
|
|||||||
{{ 'Back to the list'|trans }}
|
{{ 'Back to the list'|trans }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
{{ form_widget(edit_form.submit, { 'attr' : { 'class' : 'btn btn-update' }}) }}
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
{{ form_end(edit_form) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,39 +1,50 @@
|
|||||||
{% extends '@ChillMain/Admin/layoutWithVerticalMenu.html.twig' %}
|
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
|
||||||
|
|
||||||
{% block title %}{{ 'Center list'|trans }}{% endblock %}
|
{% block title %}{{ 'Center list'|trans }}{% endblock %}
|
||||||
|
|
||||||
{% block admin_content -%}
|
{% block admin_content -%}
|
||||||
<h1>{{ 'Center list'|trans }}</h1>
|
{% embed '@ChillMain/CRUD/_index.html.twig' %}
|
||||||
|
|
||||||
<table class="records_list">
|
{% block index_header %}
|
||||||
<thead>
|
<h1>{{ 'Center list'|trans }}</h1>
|
||||||
<tr>
|
{% endblock %}
|
||||||
<th>{{ 'Name'|trans }}</th>
|
|
||||||
<th>{{ 'Actions'|trans }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for entity in entities %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ entity.name }}</td>
|
|
||||||
<td>
|
|
||||||
<ul class="record_actions">
|
|
||||||
<li>
|
|
||||||
<a href="{{ path('admin_center_edit', { 'id': entity.id }) }}" class="btn btn-edit">{{ 'edit'|trans }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<ul class="record_actions sticky-form-buttons">
|
{% block filter_order %}{% endblock %}
|
||||||
<li class='cancel'>
|
|
||||||
<a href="{{ path('chill_main_admin_central') }}" class="btn btn-cancel">{{'Back to the admin'|trans}}</a>
|
{% block table_entities_thead_tr %}
|
||||||
</li>
|
<th>id</th>
|
||||||
<li>
|
<th>{{ 'Name'|trans }}</th>
|
||||||
<a href="{{ path('admin_center_new') }}" class="btn btn-create">{{ 'Create a new center'|trans }}</a>
|
<th>{{ 'Actions'|trans }}</th>
|
||||||
</li>
|
{% endblock %}
|
||||||
</ul>
|
|
||||||
|
{% block table_entities_tbody %}
|
||||||
|
{% for entity in entities %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ entity.id }}</td>
|
||||||
|
<td>{{ entity.name }}</td>
|
||||||
|
<td>
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('admin_center_edit', { 'id': entity.id }) }}" class="btn btn-edit">{{ 'edit'|trans }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block pagination %}{% endblock %}
|
||||||
|
|
||||||
|
{% block list_actions %}
|
||||||
|
<ul class="record_actions sticky-form-buttons">
|
||||||
|
<li class='cancel'>
|
||||||
|
<a href="{{ path('chill_main_admin_central') }}" class="btn btn-cancel">{{'Back to the admin'|trans}}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('admin_center_new') }}" class="btn btn-create">{{ 'Create a new center'|trans }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{% endblock list_actions %}
|
||||||
|
|
||||||
|
{% endembed %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -7,8 +7,6 @@
|
|||||||
|
|
||||||
{{ form_start(form) }}
|
{{ form_start(form) }}
|
||||||
{{ form_row(form.name) }}
|
{{ form_row(form.name) }}
|
||||||
{{ form_row(form.submit, { 'attr' : { 'class' : 'btn btn-chill-green' } } ) }}
|
|
||||||
{{ form_end(form) }}
|
|
||||||
|
|
||||||
<ul class="record_actions sticky-form-buttons">
|
<ul class="record_actions sticky-form-buttons">
|
||||||
<li class='cancel'>
|
<li class='cancel'>
|
||||||
@ -16,5 +14,10 @@
|
|||||||
{{ 'Back to the list'|trans }}
|
{{ 'Back to the list'|trans }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
{{ form_widget(form.submit, { 'attr' : { 'class' : 'btn btn-save' }}) }}
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
{{ form_end(form) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -260,11 +260,11 @@
|
|||||||
|
|
||||||
{% block pick_rolling_date_widget %}
|
{% block pick_rolling_date_widget %}
|
||||||
<div data-rolling-date="{{ form.vars['uniqid'] }}" class="row">
|
<div data-rolling-date="{{ form.vars['uniqid'] }}" class="row">
|
||||||
<div class="roll-wrapper col-sm-6">
|
<div class="roll-wrapper">
|
||||||
{{ form_widget(form.roll, { 'attr': { 'data-roll-picker': 'data-roll-picker'}}) }}
|
{{ form_widget(form.roll, { 'attr': { 'data-roll-picker': 'data-roll-picker'}}) }}
|
||||||
{{ form_errors(form.roll) }}
|
{{ form_errors(form.roll) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="fixed-wrapper col-sm-6">
|
<div class="fixed-wrapper">
|
||||||
{{ form_widget(form.fixedDate) }}
|
{{ form_widget(form.fixedDate) }}
|
||||||
{{ form_errors(form.fixedDate) }}
|
{{ form_errors(form.fixedDate) }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,8 +7,7 @@
|
|||||||
|
|
||||||
{{ form_start(edit_form) }}
|
{{ form_start(edit_form) }}
|
||||||
{{ form_row(edit_form.name) }}
|
{{ form_row(edit_form.name) }}
|
||||||
{{ form_row(edit_form.submit, { 'attr' : { 'class' : 'btn btn-chill-green' } } ) }}
|
{{ form_row(edit_form.active) }}
|
||||||
{{ form_end(edit_form) }}
|
|
||||||
|
|
||||||
<ul class="record_actions sticky-form-buttons">
|
<ul class="record_actions sticky-form-buttons">
|
||||||
<li class='cancel'>
|
<li class='cancel'>
|
||||||
@ -16,5 +15,11 @@
|
|||||||
{{ 'Back to the list'|trans }}
|
{{ 'Back to the list'|trans }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
{{ form_widget(edit_form.submit, { 'attr' : { 'class' : 'btn btn-update' }}) }}
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
{{ form_end(edit_form) }}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,39 +1,58 @@
|
|||||||
{% extends '@ChillMain/Admin/layoutWithVerticalMenu.html.twig' %}
|
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
|
||||||
|
|
||||||
{% block title %}{{ 'List circles'|trans }}{% endblock %}
|
{% block title %}{{ 'List circles'|trans }}{% endblock %}
|
||||||
|
|
||||||
{% block admin_content -%}
|
{% block admin_content -%}
|
||||||
<h1>{{ 'List circles'|trans }}</h1>
|
{% embed '@ChillMain/CRUD/_index.html.twig' %}
|
||||||
|
|
||||||
<table class="records_list">
|
{% block index_header %}
|
||||||
<thead>
|
<h1>{{ 'List circles'|trans }}</h1>
|
||||||
<tr>
|
{% endblock %}
|
||||||
<th>{{ 'Name'|trans }}</th>
|
|
||||||
<th>{{ 'Actions'|trans }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for entity in entities %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ entity.name|localize_translatable_string }}</td>
|
|
||||||
<td>
|
|
||||||
<ul class="record_actions">
|
|
||||||
<li>
|
|
||||||
<a href="{{ path('admin_scope_edit', { 'id': entity.id }) }}" class="btn btn-edit">{{ 'edit'|trans }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<ul class="record_actions sticky-form-buttons">
|
{% block filter_order %}{% endblock %}
|
||||||
<li class='cancel'>
|
|
||||||
<a href="{{ path('chill_main_admin_central') }}" class="btn btn-cancel">{{'Back to the admin'|trans}}</a>
|
{% block table_entities_thead_tr %}
|
||||||
</li>
|
<th>id</th>
|
||||||
<li>
|
<th>{{ 'Name'|trans }}</th>
|
||||||
<a href="{{ path('admin_scope_new') }}" class="btn btn-create">{{ 'Create a new circle'|trans }}</a>
|
<th>{{ 'Active'|trans }}</th>
|
||||||
</li>
|
<th>{{ 'Actions'|trans }}</th>
|
||||||
</ul>
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block table_entities_tbody %}
|
||||||
|
{% for entity in entities %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ entity.id }}</td>
|
||||||
|
<td>{{ entity.name|localize_translatable_string }}</td>
|
||||||
|
<td style="text-align:center;">
|
||||||
|
{%- if entity.active -%}
|
||||||
|
<i class="fa fa-check-square-o"></i>
|
||||||
|
{%- else -%}
|
||||||
|
<i class="fa fa-square-o"></i>
|
||||||
|
{%- endif -%}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('admin_scope_edit', { 'id': entity.id }) }}" class="btn btn-edit">{{ 'edit'|trans }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block pagination %}{% endblock %}
|
||||||
|
|
||||||
|
{% block list_actions %}
|
||||||
|
<ul class="record_actions sticky-form-buttons">
|
||||||
|
<li class='cancel'>
|
||||||
|
<a href="{{ path('chill_main_admin_central') }}" class="btn btn-cancel">{{'Back to the admin'|trans}}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('admin_scope_new') }}" class="btn btn-create">{{ 'Create a new circle'|trans }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{% endblock list_actions %}
|
||||||
|
|
||||||
|
{% endembed %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user