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 issue714_eval_time_spent
This commit is contained in:
commit
23951f1997
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,6 +5,7 @@ composer.lock
|
|||||||
docs/build/
|
docs/build/
|
||||||
node_modules/*
|
node_modules/*
|
||||||
.php_cs.cache
|
.php_cs.cache
|
||||||
|
.cache/*
|
||||||
|
|
||||||
###> symfony/framework-bundle ###
|
###> symfony/framework-bundle ###
|
||||||
/.env.local
|
/.env.local
|
||||||
|
@ -340,11 +340,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Bundle/ChillPersonBundle/Form/Type/PersonPhoneType.php
|
path: src/Bundle/ChillPersonBundle/Form/Type/PersonPhoneType.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
|
|
||||||
count: 3
|
|
||||||
path: src/Bundle/ChillPersonBundle/Search/PersonSearch.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Method Chill\\\\PersonBundle\\\\Search\\\\PersonSearch\\:\\:renderResult\\(\\) should return string but return statement is missing\\.$#"
|
message: "#^Method Chill\\\\PersonBundle\\\\Search\\\\PersonSearch\\:\\:renderResult\\(\\) should return string but return statement is missing\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
@ -257,12 +257,10 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
|
|||||||
/**
|
/**
|
||||||
* Add a social issue.
|
* Add a social issue.
|
||||||
*
|
*
|
||||||
* Note: the social issue consistency (the fact that only yougest social issues
|
* Note: the social issue consistency (the fact that only youngest social issues
|
||||||
* are kept) is processed by an entity listener:
|
* are kept) is processed by an entity listener:
|
||||||
*
|
*
|
||||||
* @see{\Chill\PersonBundle\AccompanyingPeriod\SocialIssueConsistency\AccompanyingPeriodSocialIssueConsistencyEntityListener}
|
* @see{\Chill\PersonBundle\AccompanyingPeriod\SocialIssueConsistency\AccompanyingPeriodSocialIssueConsistencyEntityListener}
|
||||||
*
|
|
||||||
* @return $this
|
|
||||||
*/
|
*/
|
||||||
public function addSocialIssue(SocialIssue $socialIssue): self
|
public function addSocialIssue(SocialIssue $socialIssue): self
|
||||||
{
|
{
|
||||||
@ -270,6 +268,10 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
|
|||||||
$this->socialIssues[] = $socialIssue;
|
$this->socialIssues[] = $socialIssue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->getAccompanyingPeriod() !== null) {
|
||||||
|
$this->getAccompanyingPeriod()->addSocialIssue($socialIssue);
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -550,6 +552,10 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
|
|||||||
{
|
{
|
||||||
$this->accompanyingPeriod = $accompanyingPeriod;
|
$this->accompanyingPeriod = $accompanyingPeriod;
|
||||||
|
|
||||||
|
foreach ($this->getSocialIssues() as $issue) {
|
||||||
|
$this->accompanyingPeriod->addSocialIssue($issue);
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ class LocationFilter implements FilterInterface
|
|||||||
{
|
{
|
||||||
$builder->add('accepted_location', PickUserLocationType::class, [
|
$builder->add('accepted_location', PickUserLocationType::class, [
|
||||||
'multiple' => true,
|
'multiple' => true,
|
||||||
'label' => 'pick location'
|
'label' => 'pick location',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,3 +88,11 @@ div.flex-bloc.concerned-groups {
|
|||||||
font-size: 120%;
|
font-size: 120%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// DOCUMENT LIST IN ACTIVITY ITEM
|
||||||
|
li.document-list-item {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
}
|
||||||
|
@ -68,7 +68,7 @@
|
|||||||
<div class="wl-col title"><h3>{{ 'Referrer'|trans }}</h3></div>
|
<div class="wl-col title"><h3>{{ 'Referrer'|trans }}</h3></div>
|
||||||
<div class="wl-col list">
|
<div class="wl-col list">
|
||||||
<p class="wl-item">
|
<p class="wl-item">
|
||||||
{{ activity.user|chill_entity_render_box }}
|
<span class="badge-user">{{ activity.user|chill_entity_render_box }}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -137,19 +137,42 @@
|
|||||||
{{ activity.comment|chill_entity_render_box({
|
{{ activity.comment|chill_entity_render_box({
|
||||||
'disable_markdown': false,
|
'disable_markdown': false,
|
||||||
'limit_lines': 3,
|
'limit_lines': 3,
|
||||||
'metadata': false
|
'metadata': false,
|
||||||
}) }}
|
}) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Only if ACL SEE_DETAILS AND/OR only on template SHOW ??
|
{% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) and activity.privateComment.hasCommentForUser(app.user) %}
|
||||||
durationTime
|
<div class="wl-row">
|
||||||
travelTime
|
<div class="wl-col title">
|
||||||
comment
|
<h3>{{ 'Private comment'|trans }}</h3>
|
||||||
documents
|
</div>
|
||||||
attendee
|
<div class="wl-col list">
|
||||||
#}
|
<section class="chill-entity entity-comment-embeddable">
|
||||||
|
<blockquote class="chill-user-quote private-quote">
|
||||||
|
{{ activity.privateComment.comments[app.user.id]|chill_markdown_to_html }}
|
||||||
|
</blockquote>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) and activity.documents|length > 0 %}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<h3>{{ 'Documents'|trans }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
<ul>
|
||||||
|
{% for d in activity.documents %}
|
||||||
|
<li class="document-list-item">{{ d.title|chill_print_or_message('document.Any title') }} {{ d|chill_document_button_group(d.title, is_granted('CHILL_ACTIVITY_UPDATE', activity), {small: true}) }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -8,11 +8,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_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_document_action_buttons_group') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
@ -23,11 +23,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_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_document_action_buttons_group') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
{% if activity.user and t.userVisible %}
|
{% if activity.user and t.userVisible %}
|
||||||
<li>
|
<li>
|
||||||
<span class="item-key">{{ 'Referrer'|trans ~ ': ' }}</span>
|
<span class="item-key">{{ 'Referrer'|trans ~ ': ' }}</span>
|
||||||
<b>{{ activity.user|chill_entity_render_box}}</b>
|
<span class="badge-user">{{ activity.user|chill_entity_render_box }}</span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -35,7 +35,9 @@
|
|||||||
<div class="item-row separator">
|
<div class="item-row separator">
|
||||||
<dl class="chill_view_data">
|
<dl class="chill_view_data">
|
||||||
<dt class="inline">{{ 'Referrer'|trans|capitalize }}</dt>
|
<dt class="inline">{{ 'Referrer'|trans|capitalize }}</dt>
|
||||||
<dd>{{ entity.user|chill_entity_render_box }}</dd>
|
<dd>
|
||||||
|
<span class="badge-user">{{ entity.user|chill_entity_render_box }}</span>
|
||||||
|
</dd>
|
||||||
|
|
||||||
{%- if entity.scope -%}
|
{%- if entity.scope -%}
|
||||||
<dt class="inline">{{ 'Scope'|trans }}</dt>
|
<dt class="inline">{{ 'Scope'|trans }}</dt>
|
||||||
@ -168,7 +170,7 @@
|
|||||||
{% 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 }} {{ d|chill_document_button_group() }}</li>
|
<li class="document-list-item">{{ d.title|chill_print_or_message('document.Any title') }} {{ d|chill_document_button_group(d.title, is_granted('CHILL_ACTIVITY_UPDATE', entity), {small: true}) }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -22,6 +22,7 @@ use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
|
|||||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\PersonBundle\Repository\PersonRepository;
|
||||||
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
|
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
@ -45,6 +46,8 @@ class ActivityContext implements
|
|||||||
|
|
||||||
private PersonRenderInterface $personRender;
|
private PersonRenderInterface $personRender;
|
||||||
|
|
||||||
|
private PersonRepository $personRepository;
|
||||||
|
|
||||||
private TranslatableStringHelperInterface $translatableStringHelper;
|
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||||
|
|
||||||
private TranslatorInterface $translator;
|
private TranslatorInterface $translator;
|
||||||
@ -55,6 +58,7 @@ class ActivityContext implements
|
|||||||
TranslatableStringHelperInterface $translatableStringHelper,
|
TranslatableStringHelperInterface $translatableStringHelper,
|
||||||
EntityManagerInterface $em,
|
EntityManagerInterface $em,
|
||||||
PersonRenderInterface $personRender,
|
PersonRenderInterface $personRender,
|
||||||
|
PersonRepository $personRepository,
|
||||||
TranslatorInterface $translator,
|
TranslatorInterface $translator,
|
||||||
BaseContextData $baseContextData
|
BaseContextData $baseContextData
|
||||||
) {
|
) {
|
||||||
@ -63,6 +67,7 @@ class ActivityContext implements
|
|||||||
$this->translatableStringHelper = $translatableStringHelper;
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
$this->em = $em;
|
$this->em = $em;
|
||||||
$this->personRender = $personRender;
|
$this->personRender = $personRender;
|
||||||
|
$this->personRepository = $personRepository;
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
$this->baseContextData = $baseContextData;
|
$this->baseContextData = $baseContextData;
|
||||||
}
|
}
|
||||||
@ -147,7 +152,7 @@ class ActivityContext implements
|
|||||||
$options = $template->getOptions();
|
$options = $template->getOptions();
|
||||||
|
|
||||||
$data = [];
|
$data = [];
|
||||||
$data = array_merge($data, $this->baseContextData->getData());
|
$data = array_merge($data, $this->baseContextData->getData($contextGenerationData['creator'] ?? null));
|
||||||
$data['activity'] = $this->normalizer->normalize($entity, 'docgen', ['docgen:expects' => Activity::class, 'groups' => 'docgen:read']);
|
$data['activity'] = $this->normalizer->normalize($entity, 'docgen', ['docgen:expects' => Activity::class, 'groups' => 'docgen:read']);
|
||||||
|
|
||||||
$data['course'] = $this->normalizer->normalize($entity->getAccompanyingPeriod(), 'docgen', ['docgen:expects' => AccompanyingPeriod::class, 'groups' => 'docgen:read']);
|
$data['course'] = $this->normalizer->normalize($entity->getAccompanyingPeriod(), 'docgen', ['docgen:expects' => AccompanyingPeriod::class, 'groups' => 'docgen:read']);
|
||||||
@ -206,6 +211,32 @@ class ActivityContext implements
|
|||||||
return $options['mainPerson'] || $options['person1'] || $options['person2'];
|
return $options['mainPerson'] || $options['person1'] || $options['person2'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array
|
||||||
|
{
|
||||||
|
$normalized = [];
|
||||||
|
|
||||||
|
foreach (['mainPerson', 'person1', 'person2'] as $k) {
|
||||||
|
$normalized[$k] = null === $data[$k] ? null : $data[$k]->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array
|
||||||
|
{
|
||||||
|
$denormalized = [];
|
||||||
|
|
||||||
|
foreach (['mainPerson', 'person1', 'person2'] as $k) {
|
||||||
|
if (null !== ($id = ($data[$k] ?? null))) {
|
||||||
|
$denormalized[$k] = $this->personRepository->find($id);
|
||||||
|
} else {
|
||||||
|
$denormalized[$k] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $denormalized;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Activity $entity
|
* @param Activity $entity
|
||||||
*/
|
*/
|
||||||
|
@ -146,6 +146,16 @@ class ListActivitiesByAccompanyingPeriodContext implements
|
|||||||
return $this->accompanyingPeriodContext->hasPublicForm($template, $entity);
|
return $this->accompanyingPeriodContext->hasPublicForm($template, $entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array
|
||||||
|
{
|
||||||
|
return $this->accompanyingPeriodContext->contextGenerationDataNormalize($template, $entity, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array
|
||||||
|
{
|
||||||
|
return $this->accompanyingPeriodContext->contextGenerationDataDenormalize($template, $entity, $data);
|
||||||
|
}
|
||||||
|
|
||||||
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
|
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
|
||||||
{
|
{
|
||||||
$this->accompanyingPeriodContext->storeGenerated($template, $storedObject, $entity, $contextGenerationData);
|
$this->accompanyingPeriodContext->storeGenerated($template, $storedObject, $entity, $contextGenerationData);
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
<?php
|
<?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;
|
namespace Chill\AsideActivityBundle\Export\Export;
|
||||||
|
|
||||||
use Chill\AsideActivityBundle\Entity\AsideActivity;
|
use Chill\AsideActivityBundle\Entity\AsideActivity;
|
||||||
use Chill\AsideActivityBundle\Export\Declarations;
|
use Chill\AsideActivityBundle\Export\Declarations;
|
||||||
use Chill\AsideActivityBundle\Form\AsideActivityCategoryType;
|
|
||||||
use Chill\AsideActivityBundle\Repository\AsideActivityCategoryRepository;
|
use Chill\AsideActivityBundle\Repository\AsideActivityCategoryRepository;
|
||||||
use Chill\AsideActivityBundle\Security\AsideActivityVoter;
|
use Chill\AsideActivityBundle\Security\AsideActivityVoter;
|
||||||
use Chill\AsideActivityBundle\Templating\Entity\CategoryRender;
|
use Chill\AsideActivityBundle\Templating\Entity\CategoryRender;
|
||||||
@ -16,32 +24,32 @@ use Chill\MainBundle\Export\Helper\UserHelper;
|
|||||||
use Chill\MainBundle\Export\ListInterface;
|
use Chill\MainBundle\Export\ListInterface;
|
||||||
use Chill\MainBundle\Repository\CenterRepositoryInterface;
|
use Chill\MainBundle\Repository\CenterRepositoryInterface;
|
||||||
use Chill\MainBundle\Repository\ScopeRepositoryInterface;
|
use Chill\MainBundle\Repository\ScopeRepositoryInterface;
|
||||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
|
||||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
use Closure;
|
use DateTimeInterface;
|
||||||
use Doctrine\ORM\AbstractQuery;
|
use Doctrine\ORM\AbstractQuery;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
use LogicException;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
final class ListAsideActivity implements ListInterface, GroupedExportInterface
|
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 AsideActivityCategoryRepository $asideActivityCategoryRepository;
|
||||||
|
|
||||||
private CategoryRender $categoryRender;
|
private CategoryRender $categoryRender;
|
||||||
|
|
||||||
|
private CenterRepositoryInterface $centerRepository;
|
||||||
|
|
||||||
|
private DateTimeHelper $dateTimeHelper;
|
||||||
|
|
||||||
|
private EntityManagerInterface $em;
|
||||||
|
|
||||||
|
private ScopeRepositoryInterface $scopeRepository;
|
||||||
|
|
||||||
private TranslatableStringHelperInterface $translatableStringHelper;
|
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||||
|
|
||||||
|
private UserHelper $userHelper;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
EntityManagerInterface $em,
|
EntityManagerInterface $em,
|
||||||
DateTimeHelper $dateTimeHelper,
|
DateTimeHelper $dateTimeHelper,
|
||||||
@ -76,11 +84,6 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
|
|||||||
return 'export.aside_activity.List of aside activities';
|
return 'export.aside_activity.List of aside activities';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTitle()
|
|
||||||
{
|
|
||||||
return 'export.aside_activity.List of aside activities';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getGroup(): string
|
public function getGroup(): string
|
||||||
{
|
{
|
||||||
return 'export.Exports of aside activities';
|
return 'export.Exports of aside activities';
|
||||||
@ -91,15 +94,16 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
|
|||||||
switch ($key) {
|
switch ($key) {
|
||||||
case 'id':
|
case 'id':
|
||||||
case 'note':
|
case 'note':
|
||||||
return function ($value) use ($key) {
|
return static function ($value) use ($key) {
|
||||||
if ('_header' === $value) {
|
if ('_header' === $value) {
|
||||||
return 'export.aside_activity.' . $key;
|
return 'export.aside_activity.' . $key;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $value ?? '';
|
return $value ?? '';
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'duration':
|
case 'duration':
|
||||||
return function ($value) use ($key) {
|
return static function ($value) use ($key) {
|
||||||
if ('_header' === $value) {
|
if ('_header' === $value) {
|
||||||
return 'export.aside_activity.' . $key;
|
return 'export.aside_activity.' . $key;
|
||||||
}
|
}
|
||||||
@ -108,7 +112,7 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value instanceof \DateTimeInterface) {
|
if ($value instanceof DateTimeInterface) {
|
||||||
return $value->format('H:i:s');
|
return $value->format('H:i:s');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +122,7 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
|
|||||||
case 'createdAt':
|
case 'createdAt':
|
||||||
case 'updatedAt':
|
case 'updatedAt':
|
||||||
case 'date':
|
case 'date':
|
||||||
return $this->dateTimeHelper->getLabel('export.aside_activity.'.$key);
|
return $this->dateTimeHelper->getLabel('export.aside_activity.' . $key);
|
||||||
|
|
||||||
case 'agent_id':
|
case 'agent_id':
|
||||||
case 'creator_id':
|
case 'creator_id':
|
||||||
@ -165,7 +169,7 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
|
|||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new \LogicException('this key is not supported : ' . $key);
|
throw new LogicException('this key is not supported : ' . $key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +186,7 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
|
|||||||
'aside_activity_type',
|
'aside_activity_type',
|
||||||
'date',
|
'date',
|
||||||
'duration',
|
'duration',
|
||||||
'note'
|
'note',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,6 +199,11 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
|
|||||||
return $query->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY);
|
return $query->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getTitle()
|
||||||
|
{
|
||||||
|
return 'export.aside_activity.List of aside activities';
|
||||||
|
}
|
||||||
|
|
||||||
public function getType(): string
|
public function getType(): string
|
||||||
{
|
{
|
||||||
return Declarations::ASIDE_ACTIVITY_TYPE;
|
return Declarations::ASIDE_ACTIVITY_TYPE;
|
||||||
@ -204,8 +213,7 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
|
|||||||
{
|
{
|
||||||
$qb = $this->em->createQueryBuilder()
|
$qb = $this->em->createQueryBuilder()
|
||||||
->from(AsideActivity::class, 'aside')
|
->from(AsideActivity::class, 'aside')
|
||||||
->leftJoin('aside.agent', 'agent')
|
->leftJoin('aside.agent', 'agent');
|
||||||
;
|
|
||||||
|
|
||||||
$qb
|
$qb
|
||||||
->addSelect('aside.id AS id')
|
->addSelect('aside.id AS id')
|
||||||
@ -218,8 +226,7 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
|
|||||||
->addSelect('IDENTITY(aside.type) AS aside_activity_type')
|
->addSelect('IDENTITY(aside.type) AS aside_activity_type')
|
||||||
->addSelect('aside.date')
|
->addSelect('aside.date')
|
||||||
->addSelect('aside.duration')
|
->addSelect('aside.duration')
|
||||||
->addSelect('aside.note')
|
->addSelect('aside.note');
|
||||||
;
|
|
||||||
|
|
||||||
return $qb;
|
return $qb;
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ use Chill\MainBundle\Form\Type\Export\FilterType;
|
|||||||
use Chill\MainBundle\Form\Type\PickRollingDateType;
|
use Chill\MainBundle\Form\Type\PickRollingDateType;
|
||||||
use Chill\MainBundle\Service\RollingDate\RollingDate;
|
use Chill\MainBundle\Service\RollingDate\RollingDate;
|
||||||
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
|
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
|
||||||
use Doctrine\ORM\Query\Expr\Andx;
|
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\Form\FormError;
|
use Symfony\Component\Form\FormError;
|
||||||
|
@ -15,7 +15,6 @@ use Chill\AsideActivityBundle\Export\Declarations;
|
|||||||
use Chill\MainBundle\Export\FilterInterface;
|
use Chill\MainBundle\Export\FilterInterface;
|
||||||
use Chill\MainBundle\Form\Type\PickUserDynamicType;
|
use Chill\MainBundle\Form\Type\PickUserDynamicType;
|
||||||
use Chill\MainBundle\Templating\Entity\UserRender;
|
use Chill\MainBundle\Templating\Entity\UserRender;
|
||||||
use Doctrine\ORM\Query\Expr\Andx;
|
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
@ -29,10 +29,10 @@ class CalculatorManager
|
|||||||
|
|
||||||
public function addCalculator(CalculatorInterface $calculator, bool $default)
|
public function addCalculator(CalculatorInterface $calculator, bool $default)
|
||||||
{
|
{
|
||||||
$this->calculators[$calculator::getAlias()] = $calculator;
|
$this->calculators[$calculator->getAlias()] = $calculator;
|
||||||
|
|
||||||
if ($default) {
|
if ($default) {
|
||||||
$this->defaultCalculator[] = $calculator::getAlias();
|
$this->defaultCalculator[] = $calculator->getAlias();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ class CalculatorManager
|
|||||||
$result = $calculator->calculate($elements);
|
$result = $calculator->calculate($elements);
|
||||||
|
|
||||||
if (null !== $result) {
|
if (null !== $result) {
|
||||||
$results[$calculator::getAlias()] = $result;
|
$results[$calculator->getAlias()] = $result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ class ChargeKindType extends AbstractType
|
|||||||
])
|
])
|
||||||
->add('kind', TextType::class, [
|
->add('kind', TextType::class, [
|
||||||
'label' => 'budget.admin.form.Charge_kind_key',
|
'label' => 'budget.admin.form.Charge_kind_key',
|
||||||
'help' => 'budget.admin.form.This kind must contains only alphabeticals characters, and dashes. This string is in use during document generation. Changes may have side effect on document'
|
'help' => 'budget.admin.form.This kind must contains only alphabeticals characters, and dashes. This string is in use during document generation. Changes may have side effect on document',
|
||||||
])
|
])
|
||||||
->add('ordering', NumberType::class)
|
->add('ordering', NumberType::class)
|
||||||
->add('isActive', CheckboxType::class, [
|
->add('isActive', CheckboxType::class, [
|
||||||
|
@ -30,7 +30,7 @@ class ResourceKindType extends AbstractType
|
|||||||
])
|
])
|
||||||
->add('kind', TextType::class, [
|
->add('kind', TextType::class, [
|
||||||
'label' => 'budget.admin.form.Resource_kind_key',
|
'label' => 'budget.admin.form.Resource_kind_key',
|
||||||
'help' => 'budget.admin.form.This kind must contains only alphabeticals characters, and dashes. This string is in use during document generation. Changes may have side effect on document'
|
'help' => 'budget.admin.form.This kind must contains only alphabeticals characters, and dashes. This string is in use during document generation. Changes may have side effect on document',
|
||||||
])
|
])
|
||||||
->add('ordering', NumberType::class)
|
->add('ordering', NumberType::class)
|
||||||
->add('isActive', CheckboxType::class, [
|
->add('isActive', CheckboxType::class, [
|
||||||
|
@ -49,8 +49,7 @@ final class ChargeKindRepository implements ChargeKindRepositoryInterface
|
|||||||
->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();
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,8 +28,6 @@ interface ChargeKindRepositoryInterface extends ObjectRepository
|
|||||||
*/
|
*/
|
||||||
public function findAllActive(): array;
|
public function findAllActive(): array;
|
||||||
|
|
||||||
public function findOneByKind(string $kind): ?ChargeKind;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return ChargeType[]
|
* @return ChargeType[]
|
||||||
*/
|
*/
|
||||||
@ -45,5 +43,7 @@ interface ChargeKindRepositoryInterface extends ObjectRepository
|
|||||||
|
|
||||||
public function findOneBy(array $criteria): ?ChargeKind;
|
public function findOneBy(array $criteria): ?ChargeKind;
|
||||||
|
|
||||||
|
public function findOneByKind(string $kind): ?ChargeKind;
|
||||||
|
|
||||||
public function getClassName(): string;
|
public function getClassName(): string;
|
||||||
}
|
}
|
||||||
|
@ -49,8 +49,7 @@ final class ResourceKindRepository implements ResourceKindRepositoryInterface
|
|||||||
->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();
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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);
|
||||||
|
@ -13,9 +13,7 @@ namespace Chill\BudgetBundle\Service\Summary;
|
|||||||
|
|
||||||
use Chill\BudgetBundle\Entity\ChargeKind;
|
use Chill\BudgetBundle\Entity\ChargeKind;
|
||||||
use Chill\BudgetBundle\Entity\ResourceKind;
|
use Chill\BudgetBundle\Entity\ResourceKind;
|
||||||
use Chill\BudgetBundle\Repository\ChargeKindRepository;
|
|
||||||
use Chill\BudgetBundle\Repository\ChargeKindRepositoryInterface;
|
use Chill\BudgetBundle\Repository\ChargeKindRepositoryInterface;
|
||||||
use Chill\BudgetBundle\Repository\ResourceKindRepository;
|
|
||||||
use Chill\BudgetBundle\Repository\ResourceKindRepositoryInterface;
|
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;
|
||||||
|
@ -20,12 +20,15 @@ use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
|||||||
use Chill\PersonBundle\Entity\Household\Household;
|
use Chill\PersonBundle\Entity\Household\Household;
|
||||||
use Chill\PersonBundle\Entity\Household\HouseholdMember;
|
use Chill\PersonBundle\Entity\Household\HouseholdMember;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use DateTimeImmutable;
|
||||||
use Doctrine\ORM\AbstractQuery;
|
use Doctrine\ORM\AbstractQuery;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\Query;
|
use Doctrine\ORM\Query;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Argument;
|
use Prophecy\Argument;
|
||||||
use Prophecy\PhpUnit\ProphecyTrait;
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
|
use ReflectionClass;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -47,10 +50,9 @@ final class SummaryBudgetTest extends TestCase
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
$queryCharges->setParameters(Argument::type('array'))
|
$queryCharges->setParameters(Argument::type('array'))
|
||||||
->will(function ($args, $query) {
|
->will(static function ($args, $query) {
|
||||||
return $query;
|
return $query;
|
||||||
})
|
});
|
||||||
;
|
|
||||||
|
|
||||||
$queryResources = $this->prophesize(AbstractQuery::class);
|
$queryResources = $this->prophesize(AbstractQuery::class);
|
||||||
$queryResources->getResult()->willReturn([
|
$queryResources->getResult()->willReturn([
|
||||||
@ -61,23 +63,23 @@ final class SummaryBudgetTest extends TestCase
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
$queryResources->setParameters(Argument::type('array'))
|
$queryResources->setParameters(Argument::type('array'))
|
||||||
->will(function ($args, $query) {
|
->will(static function ($args, $query) {
|
||||||
return $query;
|
return $query;
|
||||||
})
|
});
|
||||||
;
|
|
||||||
|
|
||||||
$em = $this->prophesize(EntityManagerInterface::class);
|
$em = $this->prophesize(EntityManagerInterface::class);
|
||||||
$em->createNativeQuery(Argument::type('string'), Argument::type(Query\ResultSetMapping::class))
|
$em->createNativeQuery(Argument::type('string'), Argument::type(Query\ResultSetMapping::class))
|
||||||
->will(function ($args) use ($queryResources, $queryCharges) {
|
->will(static function ($args) use ($queryResources, $queryCharges) {
|
||||||
if (false !== strpos($args[0], 'chill_budget.resource')) {
|
if (false !== strpos($args[0], 'chill_budget.resource')) {
|
||||||
return $queryResources->reveal();
|
return $queryResources->reveal();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (false !== strpos($args[0], 'chill_budget.charge')) {
|
if (false !== strpos($args[0], 'chill_budget.charge')) {
|
||||||
return $queryCharges->reveal();
|
return $queryCharges->reveal();
|
||||||
}
|
}
|
||||||
throw new \RuntimeException('this query does not have a stub counterpart: '.$args[0]);
|
|
||||||
})
|
throw new RuntimeException('this query does not have a stub counterpart: ' . $args[0]);
|
||||||
;
|
});
|
||||||
|
|
||||||
$chargeRepository = $this->prophesize(ChargeKindRepositoryInterface::class);
|
$chargeRepository = $this->prophesize(ChargeKindRepositoryInterface::class);
|
||||||
$chargeRepository->findAll()->willReturn([
|
$chargeRepository->findAll()->willReturn([
|
||||||
@ -98,24 +100,23 @@ final class SummaryBudgetTest extends TestCase
|
|||||||
$resourceRepository->findOneByKind('misc')->willReturn($misc);
|
$resourceRepository->findOneByKind('misc')->willReturn($misc);
|
||||||
|
|
||||||
$translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class);
|
$translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class);
|
||||||
$translatableStringHelper->localize(Argument::type('array'))->will(function ($arg) {
|
$translatableStringHelper->localize(Argument::type('array'))->will(static function ($arg) {
|
||||||
return $arg[0]['fr'];
|
return $arg[0]['fr'];
|
||||||
});
|
});
|
||||||
|
|
||||||
$person = new Person();
|
$person = new Person();
|
||||||
$personReflection = new \ReflectionClass($person);
|
$personReflection = new ReflectionClass($person);
|
||||||
$personIdReflection = $personReflection->getProperty('id');
|
$personIdReflection = $personReflection->getProperty('id');
|
||||||
$personIdReflection->setAccessible(true);
|
$personIdReflection->setAccessible(true);
|
||||||
$personIdReflection->setValue($person, 1);
|
$personIdReflection->setValue($person, 1);
|
||||||
|
|
||||||
$household = new Household();
|
$household = new Household();
|
||||||
$householdReflection = new \ReflectionClass($household);
|
$householdReflection = new ReflectionClass($household);
|
||||||
$householdId = $householdReflection->getProperty('id');
|
$householdId = $householdReflection->getProperty('id');
|
||||||
$householdId->setAccessible(true);
|
$householdId->setAccessible(true);
|
||||||
$householdId->setValue($household, 1);
|
$householdId->setValue($household, 1);
|
||||||
$householdMember = (new HouseholdMember())->setPerson($person)
|
$householdMember = (new HouseholdMember())->setPerson($person)
|
||||||
->setStartDate(new \DateTimeImmutable('1 month ago'))
|
->setStartDate(new DateTimeImmutable('1 month ago'));
|
||||||
;
|
|
||||||
$household->addMember($householdMember);
|
$household->addMember($householdMember);
|
||||||
|
|
||||||
$summaryBudget = new SummaryBudget(
|
$summaryBudget = new SummaryBudget(
|
||||||
|
@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
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;
|
namespace Chill\Migrations\Budget;
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
@ -9,6 +16,12 @@ use Doctrine\Migrations\AbstractMigration;
|
|||||||
|
|
||||||
final class Version20230209161546 extends AbstractMigration
|
final class Version20230209161546 extends AbstractMigration
|
||||||
{
|
{
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('DROP INDEX resource_kind_unique_type_idx');
|
||||||
|
$this->addSql('DROP INDEX charge_kind_unique_type_idx');
|
||||||
|
}
|
||||||
|
|
||||||
public function getDescription(): string
|
public function getDescription(): string
|
||||||
{
|
{
|
||||||
return 'Budget: add unique constraint on kind for charge_kind and resource_kind';
|
return 'Budget: add unique constraint on kind for charge_kind and resource_kind';
|
||||||
@ -21,10 +34,4 @@ final class Version20230209161546 extends AbstractMigration
|
|||||||
$this->addSql('CREATE UNIQUE INDEX resource_kind_unique_type_idx ON chill_budget.resource_type (kind);');
|
$this->addSql('CREATE UNIQUE INDEX resource_kind_unique_type_idx ON chill_budget.resource_type (kind);');
|
||||||
$this->addSql('CREATE UNIQUE INDEX charge_kind_unique_type_idx ON chill_budget.charge_type (kind);');
|
$this->addSql('CREATE UNIQUE INDEX charge_kind_unique_type_idx ON chill_budget.charge_type (kind);');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
$this->addSql('DROP INDEX resource_kind_unique_type_idx');
|
|
||||||
$this->addSql('DROP INDEX charge_kind_unique_type_idx');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@ Budget: Budget
|
|||||||
Resource: Inkomsten
|
Resource: Inkomsten
|
||||||
Charge: Onkosten
|
Charge: Onkosten
|
||||||
Budget for %name%: Budget van %name%
|
Budget for %name%: Budget van %name%
|
||||||
Budget for household %household%: Budget van gezin
|
Budget for household %household%: Budget van huishouden
|
||||||
Current budget household members: Actuele budget van gezinsleden
|
Current budget household members: Actuele budget van leden huishouden
|
||||||
Show budget of %name%: Toon budget van %name%
|
Show budget of %name%: Toon budget van %name%
|
||||||
See complete budget: Toon volledige budget
|
See complete budget: Toon volledige budget
|
||||||
Hide budget: Verbergen
|
Hide budget: Verbergen
|
||||||
|
@ -61,7 +61,7 @@ class LoadCalendarRange extends Fixture implements FixtureGroupInterface, Ordere
|
|||||||
->setEmail('centreA@test.chill.social')
|
->setEmail('centreA@test.chill.social')
|
||||||
->setLocationType($type = new LocationType())
|
->setLocationType($type = new LocationType())
|
||||||
->setPhonenumber1(PhoneNumberUtil::getInstance()->parse('+3287653812'));
|
->setPhonenumber1(PhoneNumberUtil::getInstance()->parse('+3287653812'));
|
||||||
$type->setTitle('Service');
|
$type->setTitle(['fr' => 'Service']);
|
||||||
$address->setStreet('Rue des Épaules')->setStreetNumber('14')
|
$address->setStreet('Rue des Épaules')->setStreetNumber('14')
|
||||||
->setPostcode($postCode = new PostalCode());
|
->setPostcode($postCode = new PostalCode());
|
||||||
$postCode->setCode('4145')->setName('Houte-Si-Plout')->setCountry(
|
$postCode->setCode('4145')->setName('Houte-Si-Plout')->setCountry(
|
||||||
|
@ -12,6 +12,8 @@ declare(strict_types=1);
|
|||||||
namespace Chill\CalendarBundle\DataFixtures\ORM;
|
namespace Chill\CalendarBundle\DataFixtures\ORM;
|
||||||
|
|
||||||
use Chill\CalendarBundle\Entity\Invite;
|
use Chill\CalendarBundle\Entity\Invite;
|
||||||
|
use Chill\MainBundle\DataFixtures\ORM\LoadUsers;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||||
use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface;
|
use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface;
|
||||||
use Doctrine\Persistence\ObjectManager;
|
use Doctrine\Persistence\ObjectManager;
|
||||||
@ -33,14 +35,21 @@ class LoadInvite extends Fixture implements FixtureGroupInterface
|
|||||||
public function load(ObjectManager $manager): void
|
public function load(ObjectManager $manager): void
|
||||||
{
|
{
|
||||||
$arr = [
|
$arr = [
|
||||||
['name' => ['fr' => 'Rendez-vous décliné']],
|
[
|
||||||
['name' => ['fr' => 'Rendez-vous accepté']],
|
'name' => ['fr' => 'Rendez-vous décliné'],
|
||||||
|
'status' => Invite::DECLINED,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => ['fr' => 'Rendez-vous accepté'],
|
||||||
|
'status' => Invite::ACCEPTED,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($arr as $a) {
|
foreach ($arr as $a) {
|
||||||
echo 'Creating calendar invite : ' . $a['name']['fr'] . "\n";
|
echo 'Creating calendar invite : ' . $a['name']['fr'] . "\n";
|
||||||
$invite = (new Invite())
|
$invite = (new Invite())
|
||||||
->setStatus($a['name']);
|
->setStatus($a['status'])
|
||||||
|
->setUser($this->getRandomUser());
|
||||||
$manager->persist($invite);
|
$manager->persist($invite);
|
||||||
$reference = 'Invite_' . $a['name']['fr'];
|
$reference = 'Invite_' . $a['name']['fr'];
|
||||||
$this->addReference($reference, $invite);
|
$this->addReference($reference, $invite);
|
||||||
@ -49,4 +58,11 @@ class LoadInvite extends Fixture implements FixtureGroupInterface
|
|||||||
|
|
||||||
$manager->flush();
|
$manager->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getRandomUser(): User
|
||||||
|
{
|
||||||
|
$userRef = array_rand(LoadUsers::$refs);
|
||||||
|
|
||||||
|
return $this->getReference($userRef);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ use Chill\MainBundle\Entity\User;
|
|||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use LogicException;
|
use LogicException;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
use function array_key_exists;
|
use function array_key_exists;
|
||||||
|
|
||||||
@ -74,9 +75,18 @@ class MapCalendarToUser
|
|||||||
|
|
||||||
public function getDefaultUserCalendar(string $idOrUserPrincipalName): ?array
|
public function getDefaultUserCalendar(string $idOrUserPrincipalName): ?array
|
||||||
{
|
{
|
||||||
$value = $this->machineHttpClient->request('GET', "users/{$idOrUserPrincipalName}/calendars", [
|
try {
|
||||||
'query' => ['$filter' => 'isDefaultCalendar eq true'],
|
$value = $this->machineHttpClient->request('GET', "users/{$idOrUserPrincipalName}/calendars", [
|
||||||
])->toArray()['value'];
|
'query' => ['$filter' => 'isDefaultCalendar eq true'],
|
||||||
|
])->toArray()['value'];
|
||||||
|
} catch (ClientExceptionInterface $e) {
|
||||||
|
$this->logger->error('[MapCalendarToUser] Error while listing calendars for a user', [
|
||||||
|
'http_status_code' => $e->getResponse()->getStatusCode(),
|
||||||
|
'id_user' => $idOrUserPrincipalName,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return $value[0] ?? null;
|
return $value[0] ?? null;
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ use Psr\Log\LoggerInterface;
|
|||||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
@ -64,6 +65,8 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface
|
|||||||
|
|
||||||
private OnBehalfOfUserHttpClient $userHttpClient;
|
private OnBehalfOfUserHttpClient $userHttpClient;
|
||||||
|
|
||||||
|
private Security $security;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
CalendarRepository $calendarRepository,
|
CalendarRepository $calendarRepository,
|
||||||
CalendarRangeRepository $calendarRangeRepository,
|
CalendarRangeRepository $calendarRangeRepository,
|
||||||
@ -74,7 +77,8 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface
|
|||||||
OnBehalfOfUserHttpClient $userHttpClient,
|
OnBehalfOfUserHttpClient $userHttpClient,
|
||||||
RemoteEventConverter $remoteEventConverter,
|
RemoteEventConverter $remoteEventConverter,
|
||||||
TranslatorInterface $translator,
|
TranslatorInterface $translator,
|
||||||
UrlGeneratorInterface $urlGenerator
|
UrlGeneratorInterface $urlGenerator,
|
||||||
|
Security $security
|
||||||
) {
|
) {
|
||||||
$this->calendarRepository = $calendarRepository;
|
$this->calendarRepository = $calendarRepository;
|
||||||
$this->calendarRangeRepository = $calendarRangeRepository;
|
$this->calendarRangeRepository = $calendarRangeRepository;
|
||||||
@ -86,6 +90,7 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface
|
|||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
$this->urlGenerator = $urlGenerator;
|
$this->urlGenerator = $urlGenerator;
|
||||||
$this->userHttpClient = $userHttpClient;
|
$this->userHttpClient = $userHttpClient;
|
||||||
|
$this->security = $security;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function countEventsForUser(User $user, DateTimeImmutable $startDate, DateTimeImmutable $endDate): int
|
public function countEventsForUser(User $user, DateTimeImmutable $startDate, DateTimeImmutable $endDate): int
|
||||||
@ -133,6 +138,24 @@ class MSGraphRemoteCalendarConnector implements RemoteCalendarConnectorInterface
|
|||||||
|
|
||||||
public function isReady(): bool
|
public function isReady(): bool
|
||||||
{
|
{
|
||||||
|
$user = $this->security->getUser();
|
||||||
|
|
||||||
|
if (!$user instanceof User) {
|
||||||
|
// this is not a user from chill. This is not the role of this class to
|
||||||
|
// restrict access, so we will just say that we do not have to do anything more
|
||||||
|
// here...
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $this->mapCalendarToUser->getUserId($user)) {
|
||||||
|
// this user is not mapped with remote calendar. The user will have to wait for
|
||||||
|
// the next calendar subscription iteration
|
||||||
|
$this->logger->debug('mark user ready for msgraph calendar as he does not have any mapping', [
|
||||||
|
'userId' => $user->getId(),
|
||||||
|
]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return $this->tokenStorage->hasToken();
|
return $this->tokenStorage->hasToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,30 +17,20 @@
|
|||||||
<td class="eval">
|
<td class="eval">
|
||||||
<ul class="eval_title">
|
<ul class="eval_title">
|
||||||
<li>
|
<li>
|
||||||
{{ mm.mimeIcon(d.storedObject.type) }}
|
<div class="row">
|
||||||
{{ d.storedObject.title }}
|
<div class="col text-start">
|
||||||
{% if d.dateTimeVersion < d.calendar.dateTimeVersion %}
|
{{ d.storedObject.title }}
|
||||||
<span class="badge bg-danger">{{ 'chill_calendar.Document outdated'|trans }}</span>
|
{% if d.dateTimeVersion < d.calendar.dateTimeVersion %}
|
||||||
{% endif %}
|
<span class="badge bg-danger">{{ 'chill_calendar.Document outdated'|trans }}</span>
|
||||||
|
{% endif %}
|
||||||
<ul class="record_actions small inline">
|
</div>
|
||||||
{% if chill_document_is_editable(d.storedObject) and is_granted('CHILL_CALENDAR_DOC_EDIT', d) %}
|
<div class="col-md-auto text-center">
|
||||||
<li>
|
{{ mm.mimeIcon(d.storedObject.type) }}
|
||||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendardoc_delete', {'id': d.id})}}" class="btn btn-delete"></a>
|
</div>
|
||||||
</li>
|
<div class="col col-lg-4 text-end">
|
||||||
<li>
|
{{ d.storedObject|chill_document_button_group(d.storedObject.title, is_granted('CHILL_CALENDAR_DOC_EDIT', d), {'small': true}) }}
|
||||||
{{ d.storedObject|chill_document_edit_button }}
|
</div>
|
||||||
</li>
|
</div>
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_CALENDAR_DOC_EDIT', d) %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendardoc_edit', {'id': d.id})}}" class="btn btn-edit"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
<li>
|
|
||||||
{{ m.download_button(d.storedObject, d.storedObject.title) }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
|
@ -10,13 +10,13 @@
|
|||||||
{% block js %}
|
{% block js %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{{ encore_entry_script_tags('mod_answer') }}
|
{{ encore_entry_script_tags('mod_answer') }}
|
||||||
{{ 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_answer') }}
|
{{ encore_entry_link_tags('mod_answer') }}
|
||||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
@ -18,8 +18,10 @@ use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
|
|||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\PersonBundle\Repository\PersonRepository;
|
||||||
use Chill\PersonBundle\Templating\Entity\PersonRender;
|
use Chill\PersonBundle\Templating\Entity\PersonRender;
|
||||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
|
||||||
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
|
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
@ -39,6 +41,10 @@ final class CalendarContext implements CalendarContextInterface
|
|||||||
|
|
||||||
private PersonRender $personRender;
|
private PersonRender $personRender;
|
||||||
|
|
||||||
|
private PersonRepository $personRepository;
|
||||||
|
|
||||||
|
private ThirdPartyRepository $thirdPartyRepository;
|
||||||
|
|
||||||
private ThirdPartyRender $thirdPartyRender;
|
private ThirdPartyRender $thirdPartyRender;
|
||||||
|
|
||||||
private TranslatableStringHelperInterface $translatableStringHelper;
|
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||||
@ -48,14 +54,18 @@ final class CalendarContext implements CalendarContextInterface
|
|||||||
EntityManagerInterface $entityManager,
|
EntityManagerInterface $entityManager,
|
||||||
NormalizerInterface $normalizer,
|
NormalizerInterface $normalizer,
|
||||||
PersonRender $personRender,
|
PersonRender $personRender,
|
||||||
|
PersonRepository $personRepository,
|
||||||
ThirdPartyRender $thirdPartyRender,
|
ThirdPartyRender $thirdPartyRender,
|
||||||
|
ThirdPartyRepository $thirdPartyRepository,
|
||||||
TranslatableStringHelperInterface $translatableStringHelper
|
TranslatableStringHelperInterface $translatableStringHelper
|
||||||
) {
|
) {
|
||||||
$this->baseContextData = $baseContextData;
|
$this->baseContextData = $baseContextData;
|
||||||
$this->entityManager = $entityManager;
|
$this->entityManager = $entityManager;
|
||||||
$this->normalizer = $normalizer;
|
$this->normalizer = $normalizer;
|
||||||
$this->personRender = $personRender;
|
$this->personRender = $personRender;
|
||||||
|
$this->personRepository = $personRepository;
|
||||||
$this->thirdPartyRender = $thirdPartyRender;
|
$this->thirdPartyRender = $thirdPartyRender;
|
||||||
|
$this->thirdPartyRepository = $thirdPartyRepository;
|
||||||
$this->translatableStringHelper = $translatableStringHelper;
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +156,7 @@ final class CalendarContext implements CalendarContextInterface
|
|||||||
$options = $this->getOptions($template);
|
$options = $this->getOptions($template);
|
||||||
|
|
||||||
$data = array_merge(
|
$data = array_merge(
|
||||||
$this->baseContextData->getData(),
|
$this->baseContextData->getData($contextGenerationData['creator'] ?? null),
|
||||||
[
|
[
|
||||||
'calendar' => $this->normalizer->normalize($entity, 'docgen', ['docgen:expects' => Calendar::class, 'groups' => ['docgen:read']]),
|
'calendar' => $this->normalizer->normalize($entity, 'docgen', ['docgen:expects' => Calendar::class, 'groups' => ['docgen:read']]),
|
||||||
]
|
]
|
||||||
@ -226,8 +236,44 @@ final class CalendarContext implements CalendarContextInterface
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array
|
||||||
|
{
|
||||||
|
$normalized = [];
|
||||||
|
$normalized['title'] = $data['title'] ?? '';
|
||||||
|
|
||||||
|
foreach (['mainPerson', 'thirdParty'] as $k) {
|
||||||
|
if (isset($data[$k])) {
|
||||||
|
$normalized[$k] = $data[$k]->getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array
|
||||||
|
{
|
||||||
|
$denormalized = [];
|
||||||
|
$denormalized['title'] = $data['title'];
|
||||||
|
|
||||||
|
if (null !== ($data['mainPerson'] ?? null)) {
|
||||||
|
if (null === $person = $this->personRepository->find($data['mainPerson'])) {
|
||||||
|
throw new \RuntimeException('person not found');
|
||||||
|
}
|
||||||
|
$denormalized['mainPerson'] = $person;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== ($data['thirdParty'] ?? null)) {
|
||||||
|
if (null === $thirdParty = $this->thirdPartyRepository->find($data['thirdParty'])) {
|
||||||
|
throw new \RuntimeException('third party not found');
|
||||||
|
}
|
||||||
|
$denormalized['thirdParty'] = $thirdParty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $denormalized;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array{mainPerson?: Person, thirdParty?: ThirdParty, title: string} $contextGenerationData
|
* param array{mainPerson?: Person, thirdParty?: ThirdParty, title: string} $contextGenerationData
|
||||||
*/
|
*/
|
||||||
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
|
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
|
||||||
{
|
{
|
||||||
|
@ -56,6 +56,10 @@ interface CalendarContextInterface extends DocGeneratorContextWithPublicFormInte
|
|||||||
*/
|
*/
|
||||||
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool;
|
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool;
|
||||||
|
|
||||||
|
public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array;
|
||||||
|
|
||||||
|
public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Calendar $entity
|
* @param Calendar $entity
|
||||||
*/
|
*/
|
||||||
|
@ -205,7 +205,7 @@ final class CalendarContextTest extends TestCase
|
|||||||
?NormalizerInterface $normalizer = null
|
?NormalizerInterface $normalizer = null
|
||||||
): CalendarContext {
|
): CalendarContext {
|
||||||
$baseContext = $this->prophesize(BaseContextData::class);
|
$baseContext = $this->prophesize(BaseContextData::class);
|
||||||
$baseContext->getData()->willReturn(['base_context' => 'data']);
|
$baseContext->getData(null)->willReturn(['base_context' => 'data']);
|
||||||
|
|
||||||
$personRender = $this->prophesize(PersonRender::class);
|
$personRender = $this->prophesize(PersonRender::class);
|
||||||
$personRender->renderString(Argument::type(Person::class), [])->willReturn('person name');
|
$personRender->renderString(Argument::type(Person::class), [])->willReturn('person name');
|
||||||
|
@ -23,6 +23,9 @@ interface DocGeneratorContextWithPublicFormInterface extends DocGeneratorContext
|
|||||||
*/
|
*/
|
||||||
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void;
|
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill the form with initial data
|
||||||
|
*/
|
||||||
public function getFormData(DocGeneratorTemplate $template, $entity): array;
|
public function getFormData(DocGeneratorTemplate $template, $entity): array;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,4 +34,14 @@ interface DocGeneratorContextWithPublicFormInterface extends DocGeneratorContext
|
|||||||
* @param mixed $entity
|
* @param mixed $entity
|
||||||
*/
|
*/
|
||||||
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool;
|
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the data from the form into serializable data, storable into messenger's message
|
||||||
|
*/
|
||||||
|
public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the data from the messenger's message into data usable for doc's generation
|
||||||
|
*/
|
||||||
|
public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array;
|
||||||
}
|
}
|
||||||
|
@ -16,67 +16,58 @@ use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
|
|||||||
use Chill\DocGeneratorBundle\Context\Exception\ContextNotFoundException;
|
use Chill\DocGeneratorBundle\Context\Exception\ContextNotFoundException;
|
||||||
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||||
use Chill\DocGeneratorBundle\GeneratorDriver\DriverInterface;
|
use Chill\DocGeneratorBundle\GeneratorDriver\DriverInterface;
|
||||||
use Chill\DocGeneratorBundle\GeneratorDriver\Exception\TemplateException;
|
|
||||||
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository;
|
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository;
|
||||||
|
use Chill\DocGeneratorBundle\Service\Generator\GeneratorInterface;
|
||||||
|
use Chill\DocGeneratorBundle\Service\Messenger\RequestGenerationMessage;
|
||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
use Chill\MainBundle\Serializer\Model\Collection;
|
use Chill\MainBundle\Serializer\Model\Collection;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Exception;
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||||
use Symfony\Component\HttpFoundation\File\File;
|
|
||||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
// TODO à mettre dans services
|
// TODO à mettre dans services
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
use Throwable;
|
|
||||||
use function strlen;
|
use function strlen;
|
||||||
|
use const JSON_PRETTY_PRINT;
|
||||||
|
|
||||||
final class DocGeneratorTemplateController extends AbstractController
|
final class DocGeneratorTemplateController extends AbstractController
|
||||||
{
|
{
|
||||||
private HttpClientInterface $client;
|
|
||||||
|
|
||||||
private ContextManager $contextManager;
|
private ContextManager $contextManager;
|
||||||
|
|
||||||
private DocGeneratorTemplateRepository $docGeneratorTemplateRepository;
|
private DocGeneratorTemplateRepository $docGeneratorTemplateRepository;
|
||||||
|
|
||||||
private DriverInterface $driver;
|
|
||||||
|
|
||||||
private EntityManagerInterface $entityManager;
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
private LoggerInterface $logger;
|
private GeneratorInterface $generator;
|
||||||
|
|
||||||
|
private MessageBusInterface $messageBus;
|
||||||
|
|
||||||
private PaginatorFactory $paginatorFactory;
|
private PaginatorFactory $paginatorFactory;
|
||||||
|
|
||||||
private StoredObjectManagerInterface $storedObjectManager;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
ContextManager $contextManager,
|
ContextManager $contextManager,
|
||||||
DocGeneratorTemplateRepository $docGeneratorTemplateRepository,
|
DocGeneratorTemplateRepository $docGeneratorTemplateRepository,
|
||||||
DriverInterface $driver,
|
GeneratorInterface $generator,
|
||||||
LoggerInterface $logger,
|
MessageBusInterface $messageBus,
|
||||||
PaginatorFactory $paginatorFactory,
|
PaginatorFactory $paginatorFactory,
|
||||||
HttpClientInterface $client,
|
|
||||||
StoredObjectManagerInterface $storedObjectManager,
|
|
||||||
EntityManagerInterface $entityManager
|
EntityManagerInterface $entityManager
|
||||||
) {
|
) {
|
||||||
$this->contextManager = $contextManager;
|
$this->contextManager = $contextManager;
|
||||||
$this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository;
|
$this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository;
|
||||||
$this->driver = $driver;
|
$this->generator = $generator;
|
||||||
$this->logger = $logger;
|
$this->messageBus = $messageBus;
|
||||||
$this->paginatorFactory = $paginatorFactory;
|
$this->paginatorFactory = $paginatorFactory;
|
||||||
$this->client = $client;
|
|
||||||
$this->storedObjectManager = $storedObjectManager;
|
|
||||||
$this->entityManager = $entityManager;
|
$this->entityManager = $entityManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +85,6 @@ final class DocGeneratorTemplateController extends AbstractController
|
|||||||
): Response {
|
): Response {
|
||||||
return $this->generateDocFromTemplate(
|
return $this->generateDocFromTemplate(
|
||||||
$template,
|
$template,
|
||||||
$entityClassName,
|
|
||||||
$entityId,
|
$entityId,
|
||||||
$request,
|
$request,
|
||||||
true
|
true
|
||||||
@ -115,7 +105,6 @@ final class DocGeneratorTemplateController extends AbstractController
|
|||||||
): Response {
|
): Response {
|
||||||
return $this->generateDocFromTemplate(
|
return $this->generateDocFromTemplate(
|
||||||
$template,
|
$template,
|
||||||
$entityClassName,
|
|
||||||
$entityId,
|
$entityId,
|
||||||
$request,
|
$request,
|
||||||
false
|
false
|
||||||
@ -185,7 +174,6 @@ final class DocGeneratorTemplateController extends AbstractController
|
|||||||
|
|
||||||
private function generateDocFromTemplate(
|
private function generateDocFromTemplate(
|
||||||
DocGeneratorTemplate $template,
|
DocGeneratorTemplate $template,
|
||||||
string $entityClassName,
|
|
||||||
int $entityId,
|
int $entityId,
|
||||||
Request $request,
|
Request $request,
|
||||||
bool $isTest
|
bool $isTest
|
||||||
@ -206,7 +194,7 @@ final class DocGeneratorTemplateController extends AbstractController
|
|||||||
|
|
||||||
if (null === $entity) {
|
if (null === $entity) {
|
||||||
throw new NotFoundHttpException(
|
throw new NotFoundHttpException(
|
||||||
sprintf('Entity with classname %s and id %s is not found', $entityClassName, $entityId)
|
sprintf('Entity with classname %s and id %s is not found', $context->getEntityClass(), $entityId)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,99 +247,68 @@ final class DocGeneratorTemplateController extends AbstractController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$document = $template->getFile();
|
// transform context generation data
|
||||||
|
$contextGenerationDataSanitized =
|
||||||
if ($isTest && ($contextGenerationData['test_file'] instanceof File)) {
|
$context instanceof DocGeneratorContextWithPublicFormInterface ?
|
||||||
$dataDecrypted = file_get_contents($contextGenerationData['test_file']->getPathname());
|
$context->contextGenerationDataNormalize($template, $entity, $contextGenerationData)
|
||||||
} else {
|
: [];
|
||||||
try {
|
|
||||||
$dataDecrypted = $this->storedObjectManager->read($document);
|
|
||||||
} catch (Throwable $exception) {
|
|
||||||
throw $exception;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// if is test, render the data or generate the doc
|
||||||
if ($isTest && isset($form) && $form['show_data']->getData()) {
|
if ($isTest && isset($form) && $form['show_data']->getData()) {
|
||||||
return $this->render('@ChillDocGenerator/Generator/debug_value.html.twig', [
|
return $this->render('@ChillDocGenerator/Generator/debug_value.html.twig', [
|
||||||
'datas' => json_encode($context->getData($template, $entity, $contextGenerationData), JSON_PRETTY_PRINT)
|
'datas' => json_encode($context->getData($template, $entity, $contextGenerationData), JSON_PRETTY_PRINT),
|
||||||
]);
|
]);
|
||||||
}
|
} elseif ($isTest) {
|
||||||
|
$generated = $this->generator->generateDocFromTemplate(
|
||||||
try {
|
$template,
|
||||||
$generatedResource = $this
|
$entityId,
|
||||||
->driver
|
$contextGenerationDataSanitized,
|
||||||
->generateFromString(
|
null,
|
||||||
$dataDecrypted,
|
true,
|
||||||
$template->getFile()->getType(),
|
isset($form) ? $form['test_file']->getData() : null
|
||||||
$context->getData($template, $entity, $contextGenerationData),
|
|
||||||
$template->getFile()->getFilename()
|
|
||||||
);
|
|
||||||
} catch (TemplateException $e) {
|
|
||||||
return new Response(
|
|
||||||
implode("\n", $e->getErrors()),
|
|
||||||
400,
|
|
||||||
[
|
|
||||||
'Content-Type' => 'text/plain',
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if ($isTest) {
|
|
||||||
return new Response(
|
return new Response(
|
||||||
$generatedResource,
|
$generated,
|
||||||
Response::HTTP_OK,
|
Response::HTTP_OK,
|
||||||
[
|
[
|
||||||
'Content-Transfer-Encoding', 'binary',
|
'Content-Transfer-Encoding', 'binary',
|
||||||
'Content-Type' => 'application/vnd.oasis.opendocument.text',
|
'Content-Type' => 'application/vnd.oasis.opendocument.text',
|
||||||
'Content-Disposition' => 'attachment; filename="generated.odt"',
|
'Content-Disposition' => 'attachment; filename="generated.odt"',
|
||||||
'Content-Length' => strlen($generatedResource),
|
'Content-Length' => strlen($generated),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var StoredObject $storedObject */
|
// this is not a test
|
||||||
$storedObject = (new ObjectNormalizer())
|
// we prepare the object to store the document
|
||||||
->denormalize(
|
$storedObject = (new StoredObject())
|
||||||
[
|
->setStatus(StoredObject::STATUS_PENDING)
|
||||||
'type' => $template->getFile()->getType(),
|
;
|
||||||
'filename' => sprintf('%s_odt', uniqid('doc_', true)),
|
|
||||||
],
|
|
||||||
StoredObject::class
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->storedObjectManager->write($storedObject, $generatedResource);
|
|
||||||
} catch (Throwable $exception) {
|
|
||||||
throw $exception;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->entityManager->persist($storedObject);
|
$this->entityManager->persist($storedObject);
|
||||||
|
|
||||||
try {
|
// we store the generated document
|
||||||
$context
|
$context
|
||||||
->storeGenerated(
|
->storeGenerated(
|
||||||
$template,
|
$template,
|
||||||
$storedObject,
|
$storedObject,
|
||||||
$entity,
|
$entity,
|
||||||
$contextGenerationData
|
$contextGenerationData
|
||||||
);
|
);
|
||||||
} catch (Exception $e) {
|
|
||||||
$this
|
|
||||||
->logger
|
|
||||||
->error(
|
|
||||||
'Unable to store the associated document to entity',
|
|
||||||
[
|
|
||||||
'entityClassName' => $entityClassName,
|
|
||||||
'entityId' => $entityId,
|
|
||||||
'contextKey' => $context->getName(),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
$this->messageBus->dispatch(
|
||||||
|
new RequestGenerationMessage(
|
||||||
|
$this->getUser(),
|
||||||
|
$template,
|
||||||
|
$entityId,
|
||||||
|
$storedObject,
|
||||||
|
$contextGenerationDataSanitized,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
return $this
|
return $this
|
||||||
->redirectToRoute(
|
->redirectToRoute(
|
||||||
'chill_wopi_file_edit',
|
'chill_wopi_file_edit',
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
{{ creator.label }},
|
||||||
|
|
||||||
|
{{ 'docgen.failure_email.The generation of the document {template_name} failed'|trans({'{template_name}': template.name|localize_translatable_string}) }}
|
||||||
|
|
||||||
|
{{ 'docgen.failure_email.Forward this email to your administrator for solving'|trans }}
|
||||||
|
|
||||||
|
{{ 'docgen.failure_email.References'|trans }}:
|
||||||
|
{% if errors|length > 0 %}
|
||||||
|
{{ 'docgen.failure_email.The following errors were encoutered'|trans }}:
|
||||||
|
|
||||||
|
{% for error in errors %}
|
||||||
|
- {{ error }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
- template_id: {{ template.id }}
|
||||||
|
- stored_object_destination_id: {{ stored_object_id }}
|
@ -21,18 +21,14 @@ class BaseContextData
|
|||||||
{
|
{
|
||||||
private NormalizerInterface $normalizer;
|
private NormalizerInterface $normalizer;
|
||||||
|
|
||||||
private Security $security;
|
public function __construct(NormalizerInterface $normalizer)
|
||||||
|
|
||||||
public function __construct(Security $security, NormalizerInterface $normalizer)
|
|
||||||
{
|
{
|
||||||
$this->security = $security;
|
|
||||||
$this->normalizer = $normalizer;
|
$this->normalizer = $normalizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getData(): array
|
public function getData(?User $user = null): array
|
||||||
{
|
{
|
||||||
$data = [];
|
$data = [];
|
||||||
$user = $this->security->getUser();
|
|
||||||
|
|
||||||
$data['creator'] = $this->normalizer->normalize(
|
$data['creator'] = $this->normalizer->normalize(
|
||||||
$user instanceof User ? $user : null,
|
$user instanceof User ? $user : null,
|
||||||
|
@ -3,16 +3,18 @@
|
|||||||
namespace Chill\DocGeneratorBundle\Service\Generator;
|
namespace Chill\DocGeneratorBundle\Service\Generator;
|
||||||
|
|
||||||
use Chill\DocGeneratorBundle\Context\ContextManagerInterface;
|
use Chill\DocGeneratorBundle\Context\ContextManagerInterface;
|
||||||
|
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
|
||||||
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||||
use Chill\DocGeneratorBundle\GeneratorDriver\DriverInterface;
|
use Chill\DocGeneratorBundle\GeneratorDriver\DriverInterface;
|
||||||
use Chill\DocGeneratorBundle\GeneratorDriver\Exception\TemplateException;
|
use Chill\DocGeneratorBundle\GeneratorDriver\Exception\TemplateException;
|
||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\HttpFoundation\File\File;
|
use Symfony\Component\HttpFoundation\File\File;
|
||||||
|
|
||||||
class Generator
|
class Generator implements GeneratorInterface
|
||||||
{
|
{
|
||||||
private ContextManagerInterface $contextManager;
|
private ContextManagerInterface $contextManager;
|
||||||
|
|
||||||
@ -24,6 +26,8 @@ class Generator
|
|||||||
|
|
||||||
private StoredObjectManagerInterface $storedObjectManager;
|
private StoredObjectManagerInterface $storedObjectManager;
|
||||||
|
|
||||||
|
private const LOG_PREFIX = '[docgen generator] ';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
ContextManagerInterface $contextManager,
|
ContextManagerInterface $contextManager,
|
||||||
DriverInterface $driver,
|
DriverInterface $driver,
|
||||||
@ -48,18 +52,24 @@ class Generator
|
|||||||
*/
|
*/
|
||||||
public function generateDocFromTemplate(
|
public function generateDocFromTemplate(
|
||||||
DocGeneratorTemplate $template,
|
DocGeneratorTemplate $template,
|
||||||
string $entityClassName,
|
|
||||||
int $entityId,
|
int $entityId,
|
||||||
|
array $contextGenerationDataNormalized,
|
||||||
?StoredObject $destinationStoredObject = null,
|
?StoredObject $destinationStoredObject = null,
|
||||||
bool $isTest = false,
|
bool $isTest = false,
|
||||||
?File $testFile = null
|
?File $testFile = null,
|
||||||
|
?User $creator = null
|
||||||
): ?string {
|
): ?string {
|
||||||
if ($destinationStoredObject instanceof StoredObject && StoredObject::STATUS_PENDING !== $destinationStoredObject->getStatus()) {
|
if ($destinationStoredObject instanceof StoredObject && StoredObject::STATUS_PENDING !== $destinationStoredObject->getStatus()) {
|
||||||
|
$this->logger->info(self::LOG_PREFIX.'Aborting generation of an already generated document');
|
||||||
throw new ObjectReadyException();
|
throw new ObjectReadyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->logger->info(self::LOG_PREFIX.'Starting generation of a document', [
|
||||||
|
'entity_id' => $entityId,
|
||||||
|
'destination_stored_object' => $destinationStoredObject === null ? null : $destinationStoredObject->getId()
|
||||||
|
]);
|
||||||
|
|
||||||
$context = $this->contextManager->getContextByDocGeneratorTemplate($template);
|
$context = $this->contextManager->getContextByDocGeneratorTemplate($template);
|
||||||
$contextGenerationData = ['test_file' => $testFile];
|
|
||||||
|
|
||||||
$entity = $this
|
$entity = $this
|
||||||
->entityManager
|
->entityManager
|
||||||
@ -67,22 +77,39 @@ class Generator
|
|||||||
;
|
;
|
||||||
|
|
||||||
if (null === $entity) {
|
if (null === $entity) {
|
||||||
throw new RelatedEntityNotFoundException($entityClassName, $entityId);
|
throw new RelatedEntityNotFoundException($template->getEntity(), $entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$contextGenerationDataNormalized = array_merge(
|
||||||
|
$contextGenerationDataNormalized,
|
||||||
|
['creator' => $creator],
|
||||||
|
$context instanceof DocGeneratorContextWithPublicFormInterface ?
|
||||||
|
$context->contextGenerationDataDenormalize($template, $entity, $contextGenerationDataNormalized)
|
||||||
|
: []
|
||||||
|
);
|
||||||
|
|
||||||
|
$data = $context->getData($template, $entity, $contextGenerationDataNormalized);
|
||||||
|
|
||||||
|
$destinationStoredObjectId = $destinationStoredObject instanceof StoredObject ? $destinationStoredObject->getId() : null;
|
||||||
|
$this->entityManager->clear();
|
||||||
|
gc_collect_cycles();
|
||||||
|
if (null !== $destinationStoredObjectId) {
|
||||||
|
$destinationStoredObject = $this->entityManager->find(StoredObject::class, $destinationStoredObjectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($isTest && ($testFile instanceof File)) {
|
if ($isTest && ($testFile instanceof File)) {
|
||||||
$dataDecrypted = file_get_contents($testFile->getPathname());
|
$templateDecrypted = file_get_contents($testFile->getPathname());
|
||||||
} else {
|
} else {
|
||||||
$dataDecrypted = $this->storedObjectManager->read($template->getFile());
|
$templateDecrypted = $this->storedObjectManager->read($template->getFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$generatedResource = $this
|
$generatedResource = $this
|
||||||
->driver
|
->driver
|
||||||
->generateFromString(
|
->generateFromString(
|
||||||
$dataDecrypted,
|
$templateDecrypted,
|
||||||
$template->getFile()->getType(),
|
$template->getFile()->getType(),
|
||||||
$context->getData($template, $entity, $contextGenerationData),
|
$data,
|
||||||
$template->getFile()->getFilename()
|
$template->getFile()->getFilename()
|
||||||
);
|
);
|
||||||
} catch (TemplateException $e) {
|
} catch (TemplateException $e) {
|
||||||
@ -90,6 +117,11 @@ class Generator
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($isTest) {
|
if ($isTest) {
|
||||||
|
$this->logger->info(self::LOG_PREFIX.'Finished generation of a document', [
|
||||||
|
'is_test' => true,
|
||||||
|
'entity_id' => $entityId,
|
||||||
|
'destination_stored_object' => $destinationStoredObject === null ? null : $destinationStoredObject->getId()
|
||||||
|
]);
|
||||||
return $generatedResource;
|
return $generatedResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,31 +134,13 @@ class Generator
|
|||||||
|
|
||||||
$this->storedObjectManager->write($destinationStoredObject, $generatedResource);
|
$this->storedObjectManager->write($destinationStoredObject, $generatedResource);
|
||||||
|
|
||||||
try {
|
|
||||||
$context
|
|
||||||
->storeGenerated(
|
|
||||||
$template,
|
|
||||||
$destinationStoredObject,
|
|
||||||
$entity,
|
|
||||||
$contextGenerationData
|
|
||||||
);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this
|
|
||||||
->logger
|
|
||||||
->error(
|
|
||||||
'Unable to store the associated document to entity',
|
|
||||||
[
|
|
||||||
'entityClassName' => $entityClassName,
|
|
||||||
'entityId' => $entityId,
|
|
||||||
'contextKey' => $context->getName(),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
$this->logger->info(self::LOG_PREFIX.'Finished generation of a document', [
|
||||||
|
'entity_id' => $entityId,
|
||||||
|
'destination_stored_object' => $destinationStoredObject->getId(),
|
||||||
|
]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,41 @@
|
|||||||
<?php
|
<?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\DocGeneratorBundle\Service\Generator;
|
namespace Chill\DocGeneratorBundle\Service\Generator;
|
||||||
|
|
||||||
class GeneratorException extends \RuntimeException
|
use RuntimeException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class GeneratorException extends RuntimeException
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var list<string>
|
* @var list<string>
|
||||||
*/
|
*/
|
||||||
private array $errors;
|
private array $errors;
|
||||||
|
|
||||||
public function __construct(array $errors = [], \Throwable $previous = null)
|
public function __construct(array $errors = [], ?Throwable $previous = null)
|
||||||
{
|
{
|
||||||
$this->errors = $errors;
|
$this->errors = $errors;
|
||||||
parent::__construct("Could not generate the document", 15252,
|
parent::__construct(
|
||||||
$previous);
|
'Could not generate the document',
|
||||||
|
15252,
|
||||||
|
$previous
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getErrors(): array
|
||||||
|
{
|
||||||
|
return $this->errors;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\DocGeneratorBundle\Service\Generator;
|
||||||
|
|
||||||
|
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Symfony\Component\HttpFoundation\File\File;
|
||||||
|
|
||||||
|
interface GeneratorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @template T of File|null
|
||||||
|
* @template B of bool
|
||||||
|
* @param B $isTest
|
||||||
|
* @param (B is true ? T : null) $testFile
|
||||||
|
* @psalm-return (B is true ? string : null)
|
||||||
|
* @throws \Symfony\Component\Serializer\Exception\ExceptionInterface|\Throwable
|
||||||
|
*/
|
||||||
|
public function generateDocFromTemplate(
|
||||||
|
DocGeneratorTemplate $template,
|
||||||
|
int $entityId,
|
||||||
|
array $contextGenerationDataNormalized,
|
||||||
|
?StoredObject $destinationStoredObject = null,
|
||||||
|
bool $isTest = false,
|
||||||
|
?File $testFile = null,
|
||||||
|
?User $creator = null
|
||||||
|
): ?string;
|
||||||
|
}
|
@ -1,11 +1,22 @@
|
|||||||
<?php
|
<?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\DocGeneratorBundle\Service\Generator;
|
namespace Chill\DocGeneratorBundle\Service\Generator;
|
||||||
|
|
||||||
class ObjectReadyException extends \RuntimeException
|
use RuntimeException;
|
||||||
|
|
||||||
|
class ObjectReadyException extends RuntimeException
|
||||||
{
|
{
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct("object is already ready", 6698856);
|
parent::__construct('object is already ready', 6698856);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,26 @@
|
|||||||
<?php
|
<?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\DocGeneratorBundle\Service\Generator;
|
namespace Chill\DocGeneratorBundle\Service\Generator;
|
||||||
|
|
||||||
class RelatedEntityNotFoundException extends \RuntimeException
|
use RuntimeException;
|
||||||
|
|
||||||
|
class RelatedEntityNotFoundException extends RuntimeException
|
||||||
{
|
{
|
||||||
public function __construct(string $relatedEntityClass, int $relatedEntityId, Throwable $previous = null)
|
public function __construct(string $relatedEntityClass, int $relatedEntityId, ?Throwable $previous = null)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
sprintf("Related entity not found: %s, %s", $relatedEntityClass, $relatedEntityId),
|
sprintf('Related entity not found: %s, %s', $relatedEntityClass, $relatedEntityId),
|
||||||
99876652,
|
99876652,
|
||||||
$previous);
|
$previous
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,156 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\DocGeneratorBundle\Service\Messenger;
|
||||||
|
|
||||||
|
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository;
|
||||||
|
use Chill\DocGeneratorBundle\Service\Generator\GeneratorException;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
|
||||||
|
use Chill\MainBundle\Repository\UserRepositoryInterface;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Symfony\Component\Mailer\MailerInterface;
|
||||||
|
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
|
final class OnGenerationFails implements EventSubscriberInterface
|
||||||
|
{
|
||||||
|
private DocGeneratorTemplateRepository $docGeneratorTemplateRepository;
|
||||||
|
|
||||||
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
|
private LoggerInterface $logger;
|
||||||
|
|
||||||
|
private MailerInterface $mailer;
|
||||||
|
|
||||||
|
private StoredObjectRepository $storedObjectRepository;
|
||||||
|
|
||||||
|
private TranslatorInterface $translator;
|
||||||
|
|
||||||
|
private UserRepositoryInterface $userRepository;
|
||||||
|
|
||||||
|
const LOG_PREFIX = '[docgen failed] ';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param DocGeneratorTemplateRepository $docGeneratorTemplateRepository
|
||||||
|
* @param EntityManagerInterface $entityManager
|
||||||
|
* @param LoggerInterface $logger
|
||||||
|
* @param MailerInterface $mailer
|
||||||
|
* @param StoredObjectRepository $storedObjectRepository
|
||||||
|
* @param TranslatorInterface $translator
|
||||||
|
* @param UserRepositoryInterface $userRepository
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
DocGeneratorTemplateRepository $docGeneratorTemplateRepository,
|
||||||
|
EntityManagerInterface $entityManager,
|
||||||
|
LoggerInterface $logger,
|
||||||
|
MailerInterface $mailer,
|
||||||
|
StoredObjectRepository $storedObjectRepository,
|
||||||
|
TranslatorInterface $translator,
|
||||||
|
UserRepositoryInterface $userRepository
|
||||||
|
) {
|
||||||
|
$this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository;
|
||||||
|
$this->entityManager = $entityManager;
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->mailer = $mailer;
|
||||||
|
$this->storedObjectRepository = $storedObjectRepository;
|
||||||
|
$this->translator = $translator;
|
||||||
|
$this->userRepository = $userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getSubscribedEvents()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
WorkerMessageFailedEvent::class => 'onMessageFailed'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onMessageFailed(WorkerMessageFailedEvent $event): void
|
||||||
|
{
|
||||||
|
if ($event->willRetry()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$event->getEnvelope()->getMessage() instanceof RequestGenerationMessage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var \Chill\DocGeneratorBundle\Service\Messenger\RequestGenerationMessage $message */
|
||||||
|
$message = $event->getEnvelope()->getMessage();
|
||||||
|
|
||||||
|
$this->logger->error(self::LOG_PREFIX.'Docgen failed', [
|
||||||
|
'stored_object_id' => $message->getDestinationStoredObjectId(),
|
||||||
|
'entity_id' => $message->getEntityId(),
|
||||||
|
'template_id' => $message->getTemplateId(),
|
||||||
|
'creator_id' => $message->getCreatorId(),
|
||||||
|
'throwable_class' => get_class($event->getThrowable()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->markObjectAsFailed($message);
|
||||||
|
$this->warnCreator($message, $event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function markObjectAsFailed(RequestGenerationMessage $message): void
|
||||||
|
{
|
||||||
|
$object = $this->storedObjectRepository->find($message->getDestinationStoredObjectId());
|
||||||
|
|
||||||
|
if (null === $object) {
|
||||||
|
$this->logger->error(self::LOG_PREFIX.'Stored object not found', ['stored_object_id', $message->getDestinationStoredObjectId()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$object->setStatus(StoredObject::STATUS_FAILURE);
|
||||||
|
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function warnCreator(RequestGenerationMessage $message, WorkerMessageFailedEvent $event): void
|
||||||
|
{
|
||||||
|
if (null === $creatorId = $message->getCreatorId()) {
|
||||||
|
$this->logger->info(self::LOG_PREFIX.'creator id is null');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $creator = $this->userRepository->find($creatorId)) {
|
||||||
|
$this->logger->error(self::LOG_PREFIX.'Creator not found with given id', ['creator_id', $creatorId]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $creator->getEmail() || '' === $creator->getEmail()) {
|
||||||
|
$this->logger->info(self::LOG_PREFIX.'Creator does not have any email', ['user' => $creator->getUsernameCanonical()]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the exception is not a GeneratorException, we try the previous one...
|
||||||
|
$throwable = $event->getThrowable();
|
||||||
|
if (!$throwable instanceof GeneratorException) {
|
||||||
|
$throwable = $throwable->getPrevious();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($throwable instanceof GeneratorException) {
|
||||||
|
$errors = $throwable->getErrors();
|
||||||
|
} else {
|
||||||
|
$errors = [$throwable->getTraceAsString()];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $template = $this->docGeneratorTemplateRepository->find($message->getTemplateId())) {
|
||||||
|
$this->logger->info(self::LOG_PREFIX.'Template not found', ['template_id' => $message->getTemplateId()]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$email = (new TemplatedEmail())
|
||||||
|
->to($creator->getEmail())
|
||||||
|
->subject($this->translator->trans('docgen.failure_email.The generation of a document failed'))
|
||||||
|
->textTemplate('@ChillDocGenerator/Email/on_generation_failed_email.txt.twig')
|
||||||
|
->context([
|
||||||
|
'errors' => $errors,
|
||||||
|
'template' => $template,
|
||||||
|
'creator' => $creator,
|
||||||
|
'stored_object_id' => $message->getDestinationStoredObjectId(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->mailer->send($email);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\DocGeneratorBundle\Service\Messenger;
|
||||||
|
|
||||||
|
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository;
|
||||||
|
use Chill\DocGeneratorBundle\Service\Generator\Generator;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
|
||||||
|
use Chill\MainBundle\Repository\UserRepositoryInterface;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
|
||||||
|
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the request of document generation
|
||||||
|
*/
|
||||||
|
class RequestGenerationHandler implements MessageHandlerInterface
|
||||||
|
{
|
||||||
|
private StoredObjectRepository $storedObjectRepository;
|
||||||
|
|
||||||
|
private DocGeneratorTemplateRepository $docGeneratorTemplateRepository;
|
||||||
|
|
||||||
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
|
private Generator $generator;
|
||||||
|
|
||||||
|
private LoggerInterface $logger;
|
||||||
|
|
||||||
|
private UserRepositoryInterface $userRepository;
|
||||||
|
|
||||||
|
public const AUTHORIZED_TRIALS = 5;
|
||||||
|
|
||||||
|
private const LOG_PREFIX = '[docgen message handler] ';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
DocGeneratorTemplateRepository $docGeneratorTemplateRepository,
|
||||||
|
EntityManagerInterface $entityManager,
|
||||||
|
Generator $generator,
|
||||||
|
LoggerInterface $logger,
|
||||||
|
StoredObjectRepository $storedObjectRepository,
|
||||||
|
UserRepositoryInterface $userRepository
|
||||||
|
) {
|
||||||
|
$this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository;
|
||||||
|
$this->entityManager = $entityManager;
|
||||||
|
$this->generator = $generator;
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->storedObjectRepository = $storedObjectRepository;
|
||||||
|
$this->userRepository = $userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(RequestGenerationMessage $message)
|
||||||
|
{
|
||||||
|
if (null === $template = $this->docGeneratorTemplateRepository->find($message->getTemplateId())) {
|
||||||
|
throw new \RuntimeException('template not found: ' . $message->getTemplateId());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $destinationStoredObject = $this->storedObjectRepository->find($message->getDestinationStoredObjectId())) {
|
||||||
|
throw new \RuntimeException('destination stored object not found : ' . $message->getDestinationStoredObjectId());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($destinationStoredObject->getGenerationTrialsCounter() >= self::AUTHORIZED_TRIALS) {
|
||||||
|
throw new UnrecoverableMessageHandlingException('maximum number of retry reached');
|
||||||
|
}
|
||||||
|
|
||||||
|
$creator = $this->userRepository->find($message->getCreatorId());
|
||||||
|
|
||||||
|
$destinationStoredObject->addGenerationTrial();
|
||||||
|
$this->entityManager->createQuery('UPDATE '.StoredObject::class.' s SET s.generationTrialsCounter = s.generationTrialsCounter + 1 WHERE s.id = :id')
|
||||||
|
->setParameter('id', $destinationStoredObject->getId())
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$this->generator->generateDocFromTemplate(
|
||||||
|
$template,
|
||||||
|
$message->getEntityId(),
|
||||||
|
$message->getContextGenerationData(),
|
||||||
|
$destinationStoredObject,
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
$creator
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->logger->info(self::LOG_PREFIX.'Request generation finished', [
|
||||||
|
'template_id' => $message->getTemplateId(),
|
||||||
|
'destination_stored_object' => $message->getDestinationStoredObjectId(),
|
||||||
|
'duration_int' => (new \DateTimeImmutable('now'))->getTimestamp() - $message->getCreatedAt()->getTimestamp(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
namespace Chill\DocGeneratorBundle\Service\Messenger;
|
namespace Chill\DocGeneratorBundle\Service\Messenger;
|
||||||
|
|
||||||
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
|
|
||||||
class RequestGenerationMessage
|
class RequestGenerationMessage
|
||||||
@ -13,14 +14,25 @@ class RequestGenerationMessage
|
|||||||
|
|
||||||
private int $entityId;
|
private int $entityId;
|
||||||
|
|
||||||
private string $entityClassName;
|
private int $destinationStoredObjectId;
|
||||||
|
|
||||||
public function __construct(User $creator, DocGeneratorTemplate $template, int $entityId, string $entityClassName)
|
private array $contextGenerationData;
|
||||||
{
|
|
||||||
|
private \DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
User $creator,
|
||||||
|
DocGeneratorTemplate $template,
|
||||||
|
int $entityId,
|
||||||
|
StoredObject $destinationStoredObject,
|
||||||
|
array $contextGenerationData
|
||||||
|
) {
|
||||||
$this->creatorId = $creator->getId();
|
$this->creatorId = $creator->getId();
|
||||||
$this->templateId = $template->getId();
|
$this->templateId = $template->getId();
|
||||||
$this->entityId = $entityId;
|
$this->entityId = $entityId;
|
||||||
$this->entityClassName = $entityClassName;
|
$this->destinationStoredObjectId = $destinationStoredObject->getId();
|
||||||
|
$this->contextGenerationData = $contextGenerationData;
|
||||||
|
$this->createdAt = new \DateTimeImmutable('now');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCreatorId(): int
|
public function getCreatorId(): int
|
||||||
@ -28,6 +40,11 @@ class RequestGenerationMessage
|
|||||||
return $this->creatorId;
|
return $this->creatorId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDestinationStoredObjectId(): int
|
||||||
|
{
|
||||||
|
return $this->destinationStoredObjectId;
|
||||||
|
}
|
||||||
|
|
||||||
public function getTemplateId(): int
|
public function getTemplateId(): int
|
||||||
{
|
{
|
||||||
return $this->templateId;
|
return $this->templateId;
|
||||||
@ -38,8 +55,13 @@ class RequestGenerationMessage
|
|||||||
return $this->entityId;
|
return $this->entityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEntityClassName(): string
|
public function getContextGenerationData(): array
|
||||||
{
|
{
|
||||||
return $this->entityClassName;
|
return $this->contextGenerationData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt(): \DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->createdAt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,14 @@ services:
|
|||||||
resource: '../Serializer/Normalizer/'
|
resource: '../Serializer/Normalizer/'
|
||||||
tags:
|
tags:
|
||||||
- { name: 'serializer.normalizer', priority: -152 }
|
- { name: 'serializer.normalizer', priority: -152 }
|
||||||
|
|
||||||
Chill\DocGeneratorBundle\Serializer\Normalizer\CollectionDocGenNormalizer:
|
Chill\DocGeneratorBundle\Serializer\Normalizer\CollectionDocGenNormalizer:
|
||||||
tags:
|
tags:
|
||||||
- { name: 'serializer.normalizer', priority: -126 }
|
- { name: 'serializer.normalizer', priority: -126 }
|
||||||
|
|
||||||
|
Chill\DocGeneratorBundle\Service\Context\:
|
||||||
|
resource: "../Service/Context"
|
||||||
|
|
||||||
Chill\DocGeneratorBundle\Controller\:
|
Chill\DocGeneratorBundle\Controller\:
|
||||||
resource: "../Controller"
|
resource: "../Controller"
|
||||||
autowire: true
|
autowire: true
|
||||||
@ -34,18 +38,20 @@ services:
|
|||||||
autowire: true
|
autowire: true
|
||||||
autoconfigure: true
|
autoconfigure: true
|
||||||
|
|
||||||
Chill\DocGeneratorBundle\Service\Context\:
|
|
||||||
resource: "../Service/Context/"
|
|
||||||
autowire: true
|
|
||||||
autoconfigure: true
|
|
||||||
|
|
||||||
Chill\DocGeneratorBundle\GeneratorDriver\:
|
Chill\DocGeneratorBundle\GeneratorDriver\:
|
||||||
resource: "../GeneratorDriver/"
|
resource: "../GeneratorDriver/"
|
||||||
autowire: true
|
autowire: true
|
||||||
autoconfigure: true
|
autoconfigure: true
|
||||||
|
|
||||||
|
Chill\DocGeneratorBundle\Service\Messenger\:
|
||||||
|
resource: "../Service/Messenger/"
|
||||||
|
|
||||||
|
Chill\DocGeneratorBundle\Service\Generator\Generator: ~
|
||||||
|
Chill\DocGeneratorBundle\Service\Generator\GeneratorInterface: '@Chill\DocGeneratorBundle\Service\Generator\Generator'
|
||||||
|
|
||||||
Chill\DocGeneratorBundle\Driver\RelatorioDriver: '@Chill\DocGeneratorBundle\Driver\DriverInterface'
|
Chill\DocGeneratorBundle\Driver\RelatorioDriver: '@Chill\DocGeneratorBundle\Driver\DriverInterface'
|
||||||
|
|
||||||
Chill\DocGeneratorBundle\Context\ContextManager:
|
Chill\DocGeneratorBundle\Context\ContextManager:
|
||||||
arguments:
|
arguments:
|
||||||
$contexts: !tagged_iterator { tag: chill_docgen.context, default_index_method: getKey }
|
$contexts: !tagged_iterator { tag: chill_docgen.context, default_index_method: getKey }
|
||||||
|
Chill\DocGeneratorBundle\Context\ContextManagerInterface: '@Chill\DocGeneratorBundle\Context\ContextManager'
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\Migrations\DocGenerator;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20230214192558 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add status, template_id and fix defaults on chill_doc.stored_object';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object ADD template_id INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object ADD status TEXT DEFAULT \'ready\' NOT NULL');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object ADD createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
|
||||||
|
$this->addSql('UPDATE chill_doc.stored_object SET createdAt = creation_date');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object ADD createdBy_id INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object DROP creation_date;');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object ALTER type SET DEFAULT \'\'');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object ALTER title DROP DEFAULT');
|
||||||
|
$this->addSql('COMMENT ON COLUMN chill_doc.stored_object.createdAt IS \'(DC2Type:datetime_immutable)\'');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object ADD CONSTRAINT FK_49604E365DA0FB8 FOREIGN KEY (template_id) REFERENCES chill_docgen_template (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object ADD CONSTRAINT FK_49604E363174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('CREATE INDEX IDX_49604E365DA0FB8 ON chill_doc.stored_object (template_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_49604E363174800F ON chill_doc.stored_object (createdBy_id)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object DROP CONSTRAINT FK_49604E365DA0FB8');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object DROP CONSTRAINT FK_49604E363174800F');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object DROP template_id');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object DROP status');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object ADD creation_date TIMESTAMP(0) DEFAULT NOW()');
|
||||||
|
$this->addSql('UPDATE chill_doc.stored_object SET creation_date = createdAt');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object DROP createdAt');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object DROP createdBy_id');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object ALTER title SET DEFAULT \'\'');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object ALTER type DROP DEFAULT');
|
||||||
|
}
|
||||||
|
}
|
@ -26,13 +26,14 @@ class GeneratorTest extends TestCase
|
|||||||
$template = (new DocGeneratorTemplate())->setFile($templateStoredObject = (new StoredObject())
|
$template = (new DocGeneratorTemplate())->setFile($templateStoredObject = (new StoredObject())
|
||||||
->setType('application/test'));
|
->setType('application/test'));
|
||||||
$destinationStoredObject = (new StoredObject())->setStatus(StoredObject::STATUS_PENDING);
|
$destinationStoredObject = (new StoredObject())->setStatus(StoredObject::STATUS_PENDING);
|
||||||
|
$reflection = new \ReflectionClass($destinationStoredObject);
|
||||||
|
$reflection->getProperty('id')->setAccessible(true);
|
||||||
|
$reflection->getProperty('id')->setValue($destinationStoredObject, 1);
|
||||||
$entity = new class {};
|
$entity = new class {};
|
||||||
$data = [];
|
$data = [];
|
||||||
|
|
||||||
$context = $this->prophesize(DocGeneratorContextInterface::class);
|
$context = $this->prophesize(DocGeneratorContextInterface::class);
|
||||||
$context->getData($template, $entity, Argument::type('array'))->willReturn($data);
|
$context->getData($template, $entity, Argument::type('array'))->willReturn($data);
|
||||||
$context->storeGenerated($template, $destinationStoredObject, $entity, Argument::type('array'))
|
|
||||||
->shouldBeCalled();
|
|
||||||
$context->getName()->willReturn('dummy_context');
|
$context->getName()->willReturn('dummy_context');
|
||||||
$context->getEntityClass()->willReturn('DummyClass');
|
$context->getEntityClass()->willReturn('DummyClass');
|
||||||
$context = $context->reveal();
|
$context = $context->reveal();
|
||||||
@ -46,8 +47,11 @@ class GeneratorTest extends TestCase
|
|||||||
->willReturn('generated');
|
->willReturn('generated');
|
||||||
|
|
||||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||||
$entityManager->find(Argument::type('string'), Argument::type('int'))
|
$entityManager->find(StoredObject::class, 1)
|
||||||
|
->willReturn($destinationStoredObject);
|
||||||
|
$entityManager->find('DummyClass', Argument::type('int'))
|
||||||
->willReturn($entity);
|
->willReturn($entity);
|
||||||
|
$entityManager->clear()->shouldBeCalled();
|
||||||
$entityManager->flush()->shouldBeCalled();
|
$entityManager->flush()->shouldBeCalled();
|
||||||
|
|
||||||
$storedObjectManager = $this->prophesize(StoredObjectManagerInterface::class);
|
$storedObjectManager = $this->prophesize(StoredObjectManagerInterface::class);
|
||||||
@ -65,8 +69,8 @@ class GeneratorTest extends TestCase
|
|||||||
|
|
||||||
$generator->generateDocFromTemplate(
|
$generator->generateDocFromTemplate(
|
||||||
$template,
|
$template,
|
||||||
'DummyEntity',
|
|
||||||
1,
|
1,
|
||||||
|
[],
|
||||||
$destinationStoredObject
|
$destinationStoredObject
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -89,8 +93,8 @@ class GeneratorTest extends TestCase
|
|||||||
|
|
||||||
$generator->generateDocFromTemplate(
|
$generator->generateDocFromTemplate(
|
||||||
$template,
|
$template,
|
||||||
'DummyEntity',
|
|
||||||
1,
|
1,
|
||||||
|
[],
|
||||||
$destinationStoredObject
|
$destinationStoredObject
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -102,6 +106,9 @@ class GeneratorTest extends TestCase
|
|||||||
$template = (new DocGeneratorTemplate())->setFile($templateStoredObject = (new StoredObject())
|
$template = (new DocGeneratorTemplate())->setFile($templateStoredObject = (new StoredObject())
|
||||||
->setType('application/test'));
|
->setType('application/test'));
|
||||||
$destinationStoredObject = (new StoredObject())->setStatus(StoredObject::STATUS_PENDING);
|
$destinationStoredObject = (new StoredObject())->setStatus(StoredObject::STATUS_PENDING);
|
||||||
|
$reflection = new \ReflectionClass($destinationStoredObject);
|
||||||
|
$reflection->getProperty('id')->setAccessible(true);
|
||||||
|
$reflection->getProperty('id')->setValue($destinationStoredObject, 1);
|
||||||
|
|
||||||
$context = $this->prophesize(DocGeneratorContextInterface::class);
|
$context = $this->prophesize(DocGeneratorContextInterface::class);
|
||||||
$context->getName()->willReturn('dummy_context');
|
$context->getName()->willReturn('dummy_context');
|
||||||
@ -126,8 +133,8 @@ class GeneratorTest extends TestCase
|
|||||||
|
|
||||||
$generator->generateDocFromTemplate(
|
$generator->generateDocFromTemplate(
|
||||||
$template,
|
$template,
|
||||||
'DummyEntity',
|
|
||||||
1,
|
1,
|
||||||
|
[],
|
||||||
$destinationStoredObject
|
$destinationStoredObject
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,16 @@ docgen:
|
|||||||
test generate: Tester la génération
|
test generate: Tester la génération
|
||||||
With context %name%: 'Avec le contexte "%name%"'
|
With context %name%: 'Avec le contexte "%name%"'
|
||||||
|
|
||||||
|
Doc generation failed: La génération de ce document a échoué
|
||||||
|
Doc generation is pending: La génération de ce document est en cours
|
||||||
|
Come back later: Revenir plus tard
|
||||||
|
|
||||||
|
failure_email:
|
||||||
|
The generation of a document failed: La génération d'un document a échoué
|
||||||
|
The generation of the document {template_name} failed: La génération d'un document à partir du modèle {{ template_name }} a échoué.
|
||||||
|
The following errors were encoutered: Les erreurs suivantes ont été rencontrées
|
||||||
|
Forward this email to your administrator for solving: Faites suivre ce message vers votre administrateur pour la résolution du problème.
|
||||||
|
References: Références
|
||||||
|
|
||||||
crud:
|
crud:
|
||||||
docgen_template:
|
docgen_template:
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\DocStoreBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
|
||||||
|
class StoredObjectApiController
|
||||||
|
{
|
||||||
|
private Security $security;
|
||||||
|
|
||||||
|
public function __construct(Security $security)
|
||||||
|
{
|
||||||
|
$this->security = $security;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/api/1.0/doc-store/stored-object/{uuid}/is-ready")
|
||||||
|
*/
|
||||||
|
public function isDocumentReady(StoredObject $storedObject): Response
|
||||||
|
{
|
||||||
|
if (!$this->security->isGranted('ROLE_USER')) {
|
||||||
|
throw new AccessDeniedHttpException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonResponse(
|
||||||
|
[
|
||||||
|
'id' => $storedObject->getId(),
|
||||||
|
'filename' => $storedObject->getFilename(),
|
||||||
|
'status' => $storedObject->getStatus(),
|
||||||
|
'type' => $storedObject->getType(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -41,12 +41,6 @@ class StoredObject implements AsyncFileInterface, Document, TrackCreationInterfa
|
|||||||
|
|
||||||
use TrackCreationTrait;
|
use TrackCreationTrait;
|
||||||
|
|
||||||
/**
|
|
||||||
* @ORM\Column(type="datetime", name="creation_date")
|
|
||||||
* @Serializer\Groups({"read", "write"})
|
|
||||||
*/
|
|
||||||
private DateTimeInterface $creationDate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="json", name="datas")
|
* @ORM\Column(type="json", name="datas")
|
||||||
* @Serializer\Groups({"read", "write"})
|
* @Serializer\Groups({"read", "write"})
|
||||||
@ -87,7 +81,7 @@ class StoredObject implements AsyncFileInterface, Document, TrackCreationInterfa
|
|||||||
private string $title = '';
|
private string $title = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="text", name="type")
|
* @ORM\Column(type="text", name="type", options={"default": ""})
|
||||||
* @Serializer\Groups({"read", "write"})
|
* @Serializer\Groups({"read", "write"})
|
||||||
*/
|
*/
|
||||||
private string $type = '';
|
private string $type = '';
|
||||||
@ -105,9 +99,20 @@ class StoredObject implements AsyncFileInterface, Document, TrackCreationInterfa
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="text", options={"default": "ready"})
|
* @ORM\Column(type="text", options={"default": "ready"})
|
||||||
|
* @Serializer\Groups({"read"})
|
||||||
*/
|
*/
|
||||||
private string $status;
|
private string $status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the number of times a generation has been tryied for this StoredObject.
|
||||||
|
*
|
||||||
|
* This is a workaround, as generation consume lot of memory, and out-of-memory errors
|
||||||
|
* are not handled by messenger.
|
||||||
|
*
|
||||||
|
* @ORM\Column(type="integer", options={"default": 0})
|
||||||
|
*/
|
||||||
|
private int $generationTrialsCounter = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param StoredObject::STATUS_* $status
|
* @param StoredObject::STATUS_* $status
|
||||||
*/
|
*/
|
||||||
@ -117,8 +122,16 @@ class StoredObject implements AsyncFileInterface, Document, TrackCreationInterfa
|
|||||||
$this->status = $status;
|
$this->status = $status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function addGenerationTrial(): self
|
||||||
|
{
|
||||||
|
$this->generationTrialsCounter++;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Serializer\Groups({"read", "write"})
|
* @Serializer\Groups({"read", "write"})
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
public function getCreationDate(): DateTime
|
public function getCreationDate(): DateTime
|
||||||
{
|
{
|
||||||
@ -135,6 +148,11 @@ class StoredObject implements AsyncFileInterface, Document, TrackCreationInterfa
|
|||||||
return $this->filename;
|
return $this->filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getGenerationTrialsCounter(): int
|
||||||
|
{
|
||||||
|
return $this->generationTrialsCounter;
|
||||||
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
@ -158,6 +176,9 @@ class StoredObject implements AsyncFileInterface, Document, TrackCreationInterfa
|
|||||||
return $this->getFilename();
|
return $this->getFilename();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return StoredObject::STATUS_*
|
||||||
|
*/
|
||||||
public function getStatus(): string
|
public function getStatus(): string
|
||||||
{
|
{
|
||||||
return $this->status;
|
return $this->status;
|
||||||
@ -185,6 +206,7 @@ class StoredObject implements AsyncFileInterface, Document, TrackCreationInterfa
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @Serializer\Groups({"write"})
|
* @Serializer\Groups({"write"})
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
public function setCreationDate(DateTime $creationDate): self
|
public function setCreationDate(DateTime $creationDate): self
|
||||||
{
|
{
|
||||||
@ -244,4 +266,30 @@ class StoredObject implements AsyncFileInterface, Document, TrackCreationInterfa
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getTemplate(): ?DocGeneratorTemplate
|
||||||
|
{
|
||||||
|
return $this->template;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasTemplate(): bool
|
||||||
|
{
|
||||||
|
return null !== $this->template;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTemplate(?DocGeneratorTemplate $template): StoredObject
|
||||||
|
{
|
||||||
|
$this->template = $template;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isPending(): bool
|
||||||
|
{
|
||||||
|
return self::STATUS_PENDING === $this->getStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isFailure(): bool
|
||||||
|
{
|
||||||
|
return self::STATUS_FAILURE === $this->getStatus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import {_createI18n} from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
|
import {_createI18n} from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
|
||||||
import DocumentActionButtonsGroup from "../../vuejs/DocumentActionButtonsGroup.vue";
|
import DocumentActionButtonsGroup from "../../vuejs/DocumentActionButtonsGroup.vue";
|
||||||
import {createApp} from "vue";
|
import {createApp} from "vue";
|
||||||
import {StoredObject} from "../../types";
|
import {StoredObject, StoredObjectStatusChange} from "../../types";
|
||||||
|
import {is_object_ready} from "../../vuejs/StoredObjectButton/helpers";
|
||||||
|
|
||||||
const i18n = _createI18n({});
|
const i18n = _createI18n({});
|
||||||
|
|
||||||
@ -15,19 +16,32 @@ window.addEventListener('DOMContentLoaded', function (e) {
|
|||||||
filename: string,
|
filename: string,
|
||||||
canEdit: string,
|
canEdit: string,
|
||||||
storedObject: string,
|
storedObject: string,
|
||||||
small: string,
|
buttonSmall: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
const
|
const
|
||||||
storedObject = JSON.parse(datasets.storedObject),
|
storedObject = JSON.parse(datasets.storedObject) as StoredObject,
|
||||||
filename = datasets.filename,
|
filename = datasets.filename,
|
||||||
canEdit = datasets.canEdit === '1',
|
canEdit = datasets.canEdit === '1',
|
||||||
small = datasets.small === '1'
|
small = datasets.buttonSmall === '1'
|
||||||
;
|
;
|
||||||
|
|
||||||
return { storedObject, filename, canEdit, small };
|
return { storedObject, filename, canEdit, small };
|
||||||
},
|
},
|
||||||
template: '<document-action-buttons-group :can-edit="canEdit" :filename="filename" :stored-object="storedObject" :small="small"></document-action-buttons-group>',
|
template: '<document-action-buttons-group :can-edit="canEdit" :filename="filename" :stored-object="storedObject" :small="small" @on-stored-object-status-change="onStoredObjectStatusChange"></document-action-buttons-group>',
|
||||||
|
methods: {
|
||||||
|
onStoredObjectStatusChange: function(newStatus: StoredObjectStatusChange): void {
|
||||||
|
this.$data.storedObject.status = newStatus.status;
|
||||||
|
this.$data.storedObject.filename = newStatus.filename;
|
||||||
|
this.$data.storedObject.type = newStatus.type;
|
||||||
|
|
||||||
|
// remove eventual div which inform pending status
|
||||||
|
document.querySelectorAll(`[data-docgen-is-pending="${this.$data.storedObject.id}"]`)
|
||||||
|
.forEach(function(el) {
|
||||||
|
el.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(i18n).mount(el);
|
app.use(i18n).mount(el);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import {DateTime} from "../../../ChillMainBundle/Resources/public/types";
|
import {DateTime} from "../../../ChillMainBundle/Resources/public/types";
|
||||||
|
|
||||||
|
export type StoredObjectStatus = "ready"|"failure"|"pending";
|
||||||
|
|
||||||
export interface StoredObject {
|
export interface StoredObject {
|
||||||
id: number,
|
id: number,
|
||||||
|
|
||||||
@ -13,7 +15,15 @@ export interface StoredObject {
|
|||||||
keyInfos: object,
|
keyInfos: object,
|
||||||
title: string,
|
title: string,
|
||||||
type: string,
|
type: string,
|
||||||
uuid: string
|
uuid: string,
|
||||||
|
status: StoredObjectStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StoredObjectStatusChange {
|
||||||
|
id: number,
|
||||||
|
filename: string,
|
||||||
|
status: StoredObjectStatus,
|
||||||
|
type: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dropdown">
|
<div v-if="'ready' === props.storedObject.status" class="btn-group">
|
||||||
<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">
|
<button :class="Object.assign({'btn': true, 'btn-outline-primary': true, 'dropdown-toggle': true, 'btn-sm': props.small})" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
Actions
|
Actions
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
@ -15,16 +15,27 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="'pending' === props.storedObject.status">
|
||||||
|
<div class="btn btn-outline-info">Génération en cours</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="'failure' === props.storedObject.status">
|
||||||
|
<div class="btn btn-outline-danger">La génération a échoué</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
import {onMounted} from "vue";
|
||||||
import ConvertButton from "./StoredObjectButton/ConvertButton.vue";
|
import ConvertButton from "./StoredObjectButton/ConvertButton.vue";
|
||||||
import DownloadButton from "./StoredObjectButton/DownloadButton.vue";
|
import DownloadButton from "./StoredObjectButton/DownloadButton.vue";
|
||||||
import WopiEditButton from "./StoredObjectButton/WopiEditButton.vue";
|
import WopiEditButton from "./StoredObjectButton/WopiEditButton.vue";
|
||||||
import {is_extension_editable, is_extension_viewable} from "./StoredObjectButton/helpers";
|
import {is_extension_editable, is_extension_viewable, is_object_ready} from "./StoredObjectButton/helpers";
|
||||||
import {StoredObject, WopiEditButtonExecutableBeforeLeaveFunction} from "../types";
|
import {
|
||||||
|
StoredObject,
|
||||||
|
StoredObjectStatusChange,
|
||||||
|
WopiEditButtonExecutableBeforeLeaveFunction
|
||||||
|
} from "../types";
|
||||||
|
|
||||||
interface DocumentActionButtonsGroupConfig {
|
interface DocumentActionButtonsGroupConfig {
|
||||||
storedObject: StoredObject,
|
storedObject: StoredObject,
|
||||||
@ -48,6 +59,10 @@ interface DocumentActionButtonsGroupConfig {
|
|||||||
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction,
|
executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'onStoredObjectStatusChange', newStatus: StoredObjectStatusChange): void
|
||||||
|
}>();
|
||||||
|
|
||||||
const props = withDefaults(defineProps<DocumentActionButtonsGroupConfig>(), {
|
const props = withDefaults(defineProps<DocumentActionButtonsGroupConfig>(), {
|
||||||
small: false,
|
small: false,
|
||||||
canEdit: true,
|
canEdit: true,
|
||||||
@ -56,6 +71,51 @@ const props = withDefaults(defineProps<DocumentActionButtonsGroupConfig>(), {
|
|||||||
returnPath: window.location.pathname + window.location.search + window.location.hash,
|
returnPath: window.location.pathname + window.location.search + window.location.hash,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* counter for the number of times that we check for a new status
|
||||||
|
*/
|
||||||
|
let tryiesForReady = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* how many times we may check for a new status, once loaded
|
||||||
|
*/
|
||||||
|
const maxTryiesForReady = 120;
|
||||||
|
|
||||||
|
const checkForReady = function(): void {
|
||||||
|
if (
|
||||||
|
'ready' === props.storedObject.status
|
||||||
|
|| 'failure' === props.storedObject.status
|
||||||
|
// stop reloading if the page stays opened for a long time
|
||||||
|
|| tryiesForReady > maxTryiesForReady
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tryiesForReady = tryiesForReady + 1;
|
||||||
|
|
||||||
|
setTimeout(onObjectNewStatusCallback, 5000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onObjectNewStatusCallback = async function(): Promise<void> {
|
||||||
|
const new_status = await is_object_ready(props.storedObject);
|
||||||
|
if (props.storedObject.status !== new_status.status) {
|
||||||
|
emit('onStoredObjectStatusChange', new_status);
|
||||||
|
return Promise.resolve();
|
||||||
|
} else if ('failure' === new_status.status) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('ready' !== new_status.status) {
|
||||||
|
// we check for new status, unless it is ready
|
||||||
|
checkForReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
checkForReady();
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -38,13 +38,11 @@ async function download_and_open(event: Event): Promise<void> {
|
|||||||
button.href = window.URL.createObjectURL(raw);
|
button.href = window.URL.createObjectURL(raw);
|
||||||
button.type = props.storedObject.type;
|
button.type = props.storedObject.type;
|
||||||
|
|
||||||
if (props.filename !== undefined) {
|
button.download = props.filename || 'document';
|
||||||
button.download = props.filename || 'document';
|
|
||||||
|
|
||||||
const ext = mime.getExtension(props.storedObject.type);
|
const ext = mime.getExtension(props.storedObject.type);
|
||||||
if (null !== ext) {
|
if (null !== ext) {
|
||||||
button.download = button.download + '.' + ext;
|
button.download = button.download + '.' + ext;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import {StoredObject, StoredObjectStatus, StoredObjectStatusChange} from "../../types";
|
||||||
|
|
||||||
const MIMES_EDIT = new Set([
|
const MIMES_EDIT = new Set([
|
||||||
'application/vnd.ms-powerpoint',
|
'application/vnd.ms-powerpoint',
|
||||||
@ -168,6 +169,18 @@ async function download_and_decrypt_doc(urlGenerator: string, keyData: JsonWebKe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function is_object_ready(storedObject: StoredObject): Promise<StoredObjectStatusChange>
|
||||||
|
{
|
||||||
|
const new_status_response = await window
|
||||||
|
.fetch( `/api/1.0/doc-store/stored-object/${storedObject.uuid}/is-ready`);
|
||||||
|
|
||||||
|
if (!new_status_response.ok) {
|
||||||
|
throw new Error("could not fetch the new status");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await new_status_response.json();
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
build_convert_link,
|
build_convert_link,
|
||||||
build_download_info_link,
|
build_download_info_link,
|
||||||
@ -176,4 +189,5 @@ export {
|
|||||||
download_doc,
|
download_doc,
|
||||||
is_extension_editable,
|
is_extension_editable,
|
||||||
is_extension_viewable,
|
is_extension_viewable,
|
||||||
|
is_object_ready,
|
||||||
};
|
};
|
||||||
|
@ -9,11 +9,6 @@
|
|||||||
<dt>{{ 'Title'|trans }}</dt>
|
<dt>{{ 'Title'|trans }}</dt>
|
||||||
<dd>{{ document.title }}</dd>
|
<dd>{{ document.title }}</dd>
|
||||||
|
|
||||||
{% if document.scope is not null %}
|
|
||||||
<dt>{{ 'Scope' | trans }}</dt>
|
|
||||||
<dd>{{ document.scope.name | localize_translatable_string }}</dd>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<dt>{{ 'Category'|trans }}</dt>
|
<dt>{{ 'Category'|trans }}</dt>
|
||||||
<dd>{{ document.category.name|localize_translatable_string }}</dd>
|
<dd>{{ document.category.name|localize_translatable_string }}</dd>
|
||||||
|
|
||||||
|
@ -5,18 +5,25 @@
|
|||||||
<div class="item-bloc">
|
<div class="item-bloc">
|
||||||
<div class="item-row">
|
<div class="item-row">
|
||||||
<div class="item-col" style="width: unset">
|
<div class="item-col" style="width: unset">
|
||||||
|
{% if document.object.isPending %}
|
||||||
|
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.object.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
||||||
|
{% elseif document.object.isFailure %}
|
||||||
|
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
||||||
|
{% endif %}
|
||||||
<div class="denomination h2">
|
<div class="denomination h2">
|
||||||
{{ document.title }}
|
{{ document.title }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
{% if document.object.type is not empty %}
|
||||||
{{ mm.mimeIcon(document.object.type) }}
|
<div>
|
||||||
</div>
|
{{ mm.mimeIcon(document.object.type) }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div>
|
<div>
|
||||||
<p>{{ document.category.name|localize_translatable_string }}</p>
|
<p>{{ document.category.name|localize_translatable_string }}</p>
|
||||||
</div>
|
</div>
|
||||||
{% if document.template is not null %}
|
{% if document.object.hasTemplate %}
|
||||||
<div>
|
<div>
|
||||||
<p>{{ document.template.name.fr }}</p>
|
<p>{{ document.object.template.name|localize_translatable_string }}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -157,7 +157,7 @@ final class WopiEditTwigExtensionRuntime implements RuntimeExtensionInterface
|
|||||||
'document_json' => $this->normalizer->normalize($document, 'json', [AbstractNormalizer::GROUPS => ['read']]),
|
'document_json' => $this->normalizer->normalize($document, 'json', [AbstractNormalizer::GROUPS => ['read']]),
|
||||||
'title' => $title,
|
'title' => $title,
|
||||||
'can_edit' => $canEdit,
|
'can_edit' => $canEdit,
|
||||||
'options' => array_merge($options, self::DEFAULT_OPTIONS_TEMPLATE_BUTTON_GROUP),
|
'options' => array_merge(self::DEFAULT_OPTIONS_TEMPLATE_BUTTON_GROUP, $options),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +95,16 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSuggestedUsers(EntityWorkflow $entityWorkflow): array
|
||||||
|
{
|
||||||
|
$suggestedUsers = $entityWorkflow->getUsersInvolved();
|
||||||
|
|
||||||
|
$referrer = $this->getRelatedEntity($entityWorkflow)->getCourse()->getUser();
|
||||||
|
$suggestedUsers[spl_object_hash($referrer)] = $referrer;
|
||||||
|
|
||||||
|
return $suggestedUsers;
|
||||||
|
}
|
||||||
|
|
||||||
public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string
|
public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string
|
||||||
{
|
{
|
||||||
return '@ChillDocStore/AccompanyingCourseDocument/_workflow.html.twig';
|
return '@ChillDocStore/AccompanyingCourseDocument/_workflow.html.twig';
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\Migrations\DocStore;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20230227161327 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add a generation counter on doc store';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object ADD generationTrialsCounter INT DEFAULT 0 NOT NULL;');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.stored_object DROP generationTrialsCounter');
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,9 @@ 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
|
||||||
|
|
||||||
|
document:
|
||||||
|
Any title: Aucun titre
|
||||||
|
|
||||||
# delete
|
# delete
|
||||||
Delete document ?: Supprimer le document ?
|
Delete document ?: Supprimer le document ?
|
||||||
Are you sure you want to remove this document ?: Êtes-vous sûr·e de vouloir supprimer ce document ?
|
Are you sure you want to remove this document ?: Êtes-vous sûr·e de vouloir supprimer ce document ?
|
||||||
|
@ -72,7 +72,6 @@ class ChillMainBundle extends Bundle
|
|||||||
$container->addCompilerPass(new NotificationCounterCompilerPass());
|
$container->addCompilerPass(new NotificationCounterCompilerPass());
|
||||||
$container->addCompilerPass(new MenuCompilerPass());
|
$container->addCompilerPass(new MenuCompilerPass());
|
||||||
$container->addCompilerPass(new ACLFlagsCompilerPass());
|
$container->addCompilerPass(new ACLFlagsCompilerPass());
|
||||||
$container->addCompilerPass(new GroupingCenterCompilerPass());
|
|
||||||
$container->addCompilerPass(new CRUDControllerCompilerPass());
|
$container->addCompilerPass(new CRUDControllerCompilerPass());
|
||||||
$container->addCompilerPass(new ShortMessageCompilerPass());
|
$container->addCompilerPass(new ShortMessageCompilerPass());
|
||||||
}
|
}
|
||||||
|
65
src/Bundle/ChillMainBundle/Controller/AbsenceController.php
Normal file
65
src/Bundle/ChillMainBundle/Controller/AbsenceController.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?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\Form\AbsenceType;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
class AbsenceController extends AbstractController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Route(
|
||||||
|
* "/{_locale}/absence",
|
||||||
|
* name="chill_main_user_absence_index",
|
||||||
|
* methods={"GET", "POST"}
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function setAbsence(Request $request)
|
||||||
|
{
|
||||||
|
$user = $this->getUser();
|
||||||
|
$form = $this->createForm(AbsenceType::class, $user);
|
||||||
|
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$em = $this->getDoctrine()->getManager();
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
return $this->redirect($this->generateUrl('chill_main_user_absence_index'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('@ChillMain/Menu/absence.html.twig', [
|
||||||
|
'user' => $user,
|
||||||
|
'form' => $form->createView(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route(
|
||||||
|
* "/{_locale}/absence/unset",
|
||||||
|
* name="chill_main_user_absence_unset",
|
||||||
|
* methods={"GET", "POST"}
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function unsetAbsence(Request $request)
|
||||||
|
{
|
||||||
|
$user = $this->getUser();
|
||||||
|
|
||||||
|
$user->setAbsenceStart(null);
|
||||||
|
$em = $this->getDoctrine()->getManager();
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
return $this->redirect($this->generateUrl('chill_main_user_absence_index'));
|
||||||
|
}
|
||||||
|
}
|
@ -298,6 +298,8 @@ class ExportController extends AbstractController
|
|||||||
'csrf_protection' => $isGenerate ? false : true,
|
'csrf_protection' => $isGenerate ? false : true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// TODO: add a condition to be able to select a regroupment of centers?
|
||||||
|
|
||||||
if ('centers' === $step || 'generate_centers' === $step) {
|
if ('centers' === $step || 'generate_centers' === $step) {
|
||||||
$builder->add('centers', PickCenterType::class, [
|
$builder->add('centers', PickCenterType::class, [
|
||||||
'export_alias' => $alias,
|
'export_alias' => $alias,
|
||||||
|
@ -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"');
|
$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)
|
||||||
|
@ -30,6 +30,7 @@ use Symfony\Component\HttpFoundation\Response;
|
|||||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
use Symfony\Component\Workflow\Registry;
|
use Symfony\Component\Workflow\Registry;
|
||||||
use Symfony\Component\Workflow\TransitionBlocker;
|
use Symfony\Component\Workflow\TransitionBlocker;
|
||||||
@ -48,11 +49,13 @@ class WorkflowController extends AbstractController
|
|||||||
|
|
||||||
private Registry $registry;
|
private Registry $registry;
|
||||||
|
|
||||||
|
private Security $security;
|
||||||
|
|
||||||
private TranslatorInterface $translator;
|
private TranslatorInterface $translator;
|
||||||
|
|
||||||
private ValidatorInterface $validator;
|
private ValidatorInterface $validator;
|
||||||
|
|
||||||
public function __construct(EntityWorkflowManager $entityWorkflowManager, EntityWorkflowRepository $entityWorkflowRepository, ValidatorInterface $validator, PaginatorFactory $paginatorFactory, Registry $registry, EntityManagerInterface $entityManager, TranslatorInterface $translator)
|
public function __construct(EntityWorkflowManager $entityWorkflowManager, EntityWorkflowRepository $entityWorkflowRepository, ValidatorInterface $validator, PaginatorFactory $paginatorFactory, Registry $registry, EntityManagerInterface $entityManager, TranslatorInterface $translator, Security $security)
|
||||||
{
|
{
|
||||||
$this->entityWorkflowManager = $entityWorkflowManager;
|
$this->entityWorkflowManager = $entityWorkflowManager;
|
||||||
$this->entityWorkflowRepository = $entityWorkflowRepository;
|
$this->entityWorkflowRepository = $entityWorkflowRepository;
|
||||||
@ -61,6 +64,7 @@ class WorkflowController extends AbstractController
|
|||||||
$this->registry = $registry;
|
$this->registry = $registry;
|
||||||
$this->entityManager = $entityManager;
|
$this->entityManager = $entityManager;
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
|
$this->security = $security;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -291,10 +295,18 @@ class WorkflowController extends AbstractController
|
|||||||
|
|
||||||
if (count($workflow->getEnabledTransitions($entityWorkflow)) > 0) {
|
if (count($workflow->getEnabledTransitions($entityWorkflow)) > 0) {
|
||||||
// possible transition
|
// possible transition
|
||||||
|
|
||||||
|
$usersInvolved = $entityWorkflow->getUsersInvolved();
|
||||||
|
$currentUserFound = array_search($this->security->getUser(), $usersInvolved, true);
|
||||||
|
|
||||||
|
if (false !== $currentUserFound) {
|
||||||
|
unset($usersInvolved[$currentUserFound]);
|
||||||
|
}
|
||||||
|
|
||||||
$transitionForm = $this->createForm(
|
$transitionForm = $this->createForm(
|
||||||
WorkflowStepType::class,
|
WorkflowStepType::class,
|
||||||
$entityWorkflow->getCurrentStep(),
|
$entityWorkflow->getCurrentStep(),
|
||||||
['transition' => true, 'entity_workflow' => $entityWorkflow]
|
['transition' => true, 'entity_workflow' => $entityWorkflow, 'suggested_users' => $usersInvolved]
|
||||||
);
|
);
|
||||||
|
|
||||||
$transitionForm->handleRequest($request);
|
$transitionForm->handleRequest($request);
|
||||||
|
@ -1,37 +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\MainBundle\DependencyInjection\CompilerPass;
|
|
||||||
|
|
||||||
use LogicException;
|
|
||||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
|
||||||
use Symfony\Component\DependencyInjection\Reference;
|
|
||||||
|
|
||||||
class GroupingCenterCompilerPass implements CompilerPassInterface
|
|
||||||
{
|
|
||||||
public function process(ContainerBuilder $container)
|
|
||||||
{
|
|
||||||
if (false === $container->hasDefinition('chill.main.form.pick_centers_type')) {
|
|
||||||
throw new LogicException('The service chill.main.form.pick_centers_type does '
|
|
||||||
. 'not exists in container');
|
|
||||||
}
|
|
||||||
|
|
||||||
$pickCenterType = $container->getDefinition('chill.main.form.pick_centers_type');
|
|
||||||
|
|
||||||
foreach ($container->findTaggedServiceIds('chill.grouping_center') as $serviceId => $tagged) {
|
|
||||||
$pickCenterType->addMethodCall(
|
|
||||||
'addGroupingCenter',
|
|
||||||
[new Reference($serviceId)]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -24,7 +24,7 @@ class Regroupment
|
|||||||
/**
|
/**
|
||||||
* @var Center
|
* @var Center
|
||||||
* @ORM\ManyToMany(
|
* @ORM\ManyToMany(
|
||||||
* targetEntity="Chill\MainBundle\Entity\Center"
|
* targetEntity=Center::class
|
||||||
* )
|
* )
|
||||||
* @ORM\Id
|
* @ORM\Id
|
||||||
*/
|
*/
|
||||||
@ -43,7 +43,7 @@ class Regroupment
|
|||||||
private bool $isActive = true;
|
private bool $isActive = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="string", length=15, options={"default": ""}, nullable=false)
|
* @ORM\Column(type="text", options={"default": ""}, nullable=false)
|
||||||
*/
|
*/
|
||||||
private string $name = '';
|
private string $name = '';
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ class Regroupment
|
|||||||
$this->centers = new ArrayCollection();
|
$this->centers = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCenters(): ?Collection
|
public function getCenters(): Collection
|
||||||
{
|
{
|
||||||
return $this->centers;
|
return $this->centers;
|
||||||
}
|
}
|
||||||
|
@ -11,14 +11,15 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Entity;
|
namespace Chill\MainBundle\Entity;
|
||||||
|
|
||||||
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Symfony\Component\Security\Core\User\UserInterface;
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|
||||||
|
|
||||||
|
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||||
use function in_array;
|
use function in_array;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,6 +41,11 @@ class User implements UserInterface
|
|||||||
*/
|
*/
|
||||||
protected ?int $id = null;
|
protected ?int $id = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="datetime_immutable", nullable=true)
|
||||||
|
*/
|
||||||
|
private ?DateTimeImmutable $absenceStart = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array where SAML attributes's data are stored.
|
* Array where SAML attributes's data are stored.
|
||||||
*
|
*
|
||||||
@ -173,6 +179,11 @@ class User implements UserInterface
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getAbsenceStart(): ?DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->absenceStart;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get attributes.
|
* Get attributes.
|
||||||
*
|
*
|
||||||
@ -291,6 +302,11 @@ class User implements UserInterface
|
|||||||
return $this->usernameCanonical;
|
return $this->usernameCanonical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isAbsent(): bool
|
||||||
|
{
|
||||||
|
return null !== $this->getAbsenceStart() && $this->getAbsenceStart() <= new DateTimeImmutable('now');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
@ -355,6 +371,11 @@ class User implements UserInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setAbsenceStart(?DateTimeImmutable $absenceStart): void
|
||||||
|
{
|
||||||
|
$this->absenceStart = $absenceStart;
|
||||||
|
}
|
||||||
|
|
||||||
public function setAttributeByDomain(string $domain, string $key, $value): self
|
public function setAttributeByDomain(string $domain, string $key, $value): self
|
||||||
{
|
{
|
||||||
$this->attributes[$domain][$key] = $value;
|
$this->attributes[$domain][$key] = $value;
|
||||||
|
@ -348,6 +348,23 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
return $this->transitionningStep;
|
return $this->transitionningStep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return User[]
|
||||||
|
*/
|
||||||
|
public function getUsersInvolved(): array
|
||||||
|
{
|
||||||
|
$usersInvolved = [];
|
||||||
|
$usersInvolved[spl_object_hash($this->getCreatedBy())] = $this->getCreatedBy();
|
||||||
|
|
||||||
|
foreach ($this->steps as $step) {
|
||||||
|
foreach ($step->getDestUser() as $u) {
|
||||||
|
$usersInvolved[spl_object_hash($u)] = $u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $usersInvolved;
|
||||||
|
}
|
||||||
|
|
||||||
public function getWorkflowName(): string
|
public function getWorkflowName(): string
|
||||||
{
|
{
|
||||||
return $this->workflowName;
|
return $this->workflowName;
|
||||||
|
@ -11,7 +11,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Export;
|
namespace Chill\MainBundle\Export;
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||||||
namespace Chill\MainBundle\Export\Helper;
|
namespace Chill\MainBundle\Export\Helper;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
|
use DateTimeInterface;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
@ -35,7 +36,7 @@ class DateTimeHelper
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value instanceof \DateTimeInterface) {
|
if ($value instanceof DateTimeInterface) {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
38
src/Bundle/ChillMainBundle/Form/AbsenceType.php
Normal file
38
src/Bundle/ChillMainBundle/Form/AbsenceType.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?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\Form;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Form\Type\ChillDateType;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class AbsenceType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('absenceStart', ChillDateType::class, [
|
||||||
|
'required' => true,
|
||||||
|
'input' => 'datetime_immutable',
|
||||||
|
'label' => 'absence.Absence start',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => User::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
<?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\Form\DataMapper;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Center;
|
||||||
|
use Chill\MainBundle\Entity\Regroupment;
|
||||||
|
use Chill\MainBundle\Repository\RegroupmentRepository;
|
||||||
|
use Exception;
|
||||||
|
use Symfony\Component\Form\DataMapperInterface;
|
||||||
|
use Symfony\Component\Form\FormInterface;
|
||||||
|
use function array_key_exists;
|
||||||
|
use function count;
|
||||||
|
|
||||||
|
class ExportPickCenterDataMapper implements DataMapperInterface
|
||||||
|
{
|
||||||
|
protected RegroupmentRepository $regroupmentRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array|Center[] $data
|
||||||
|
* @param $forms
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function mapDataToForms($data, $forms)
|
||||||
|
{
|
||||||
|
if (null === $data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var array<string, FormInterface> $form */
|
||||||
|
$form = iterator_to_array($forms);
|
||||||
|
|
||||||
|
$pickedRegroupment = [];
|
||||||
|
|
||||||
|
foreach ($this->regroupmentRepository->findAll() as $regroupment) {
|
||||||
|
[$contained, $notContained] = $regroupment->getCenters()->partition(static function (Center $center) {
|
||||||
|
});
|
||||||
|
|
||||||
|
if (0 === count($notContained)) {
|
||||||
|
$pickedRegroupment[] = $regroupment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$form['regroupment']->setData($pickedRegroupment);
|
||||||
|
$form['centers']->setData($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param iterable $forms
|
||||||
|
* @param array $data
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function mapFormsToData($forms, &$data)
|
||||||
|
{
|
||||||
|
/** @var array<string, FormInterface> $forms */
|
||||||
|
$forms = iterator_to_array($forms);
|
||||||
|
|
||||||
|
$centers = [];
|
||||||
|
|
||||||
|
foreach ($forms['center']->getData() as $center) {
|
||||||
|
$centers[spl_object_hash($center)] = $center;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('regroupment', $forms)) {
|
||||||
|
foreach ($forms['regroupment']->getData() as $regroupment) {
|
||||||
|
/** @var Regroupment $regroupment */
|
||||||
|
foreach ($regroupment->getCenters() as $center) {
|
||||||
|
$centers[spl_object_hash($center)] = $center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = array_values($centers);
|
||||||
|
}
|
||||||
|
}
|
@ -11,57 +11,46 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Form\Type\Export;
|
namespace Chill\MainBundle\Form\Type\Export;
|
||||||
|
|
||||||
use Chill\MainBundle\Center\GroupingCenterInterface;
|
|
||||||
use Chill\MainBundle\Entity\Center;
|
use Chill\MainBundle\Entity\Center;
|
||||||
|
use Chill\MainBundle\Entity\Regroupment;
|
||||||
use Chill\MainBundle\Export\ExportManager;
|
use Chill\MainBundle\Export\ExportManager;
|
||||||
|
use Chill\MainBundle\Form\DataMapper\ExportPickCenterDataMapper;
|
||||||
|
use Chill\MainBundle\Repository\RegroupmentRepository;
|
||||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
|
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
|
||||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\CallbackTransformer;
|
|
||||||
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;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||||
|
|
||||||
use Symfony\Component\Security\Core\User\UserInterface;
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
use function array_intersect;
|
|
||||||
use function array_key_exists;
|
|
||||||
use function array_merge;
|
|
||||||
use function array_unique;
|
|
||||||
use function count;
|
use function count;
|
||||||
use function in_array;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pick centers amongst available centers for the user.
|
* Pick centers amongst available centers for the user.
|
||||||
*/
|
*/
|
||||||
class PickCenterType extends AbstractType
|
final class PickCenterType extends AbstractType
|
||||||
{
|
{
|
||||||
public const CENTERS_IDENTIFIERS = 'c';
|
public const CENTERS_IDENTIFIERS = 'c';
|
||||||
|
|
||||||
protected AuthorizationHelperInterface $authorizationHelper;
|
private AuthorizationHelperInterface $authorizationHelper;
|
||||||
|
|
||||||
protected ExportManager $exportManager;
|
private ExportManager $exportManager;
|
||||||
|
|
||||||
/**
|
private RegroupmentRepository $regroupmentRepository;
|
||||||
* @var array|GroupingCenterInterface[]
|
|
||||||
*/
|
|
||||||
protected array $groupingCenters = [];
|
|
||||||
|
|
||||||
protected UserInterface $user;
|
private UserInterface $user;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
TokenStorageInterface $tokenStorage,
|
TokenStorageInterface $tokenStorage,
|
||||||
ExportManager $exportManager,
|
ExportManager $exportManager,
|
||||||
|
RegroupmentRepository $regroupmentRepository,
|
||||||
AuthorizationHelperInterface $authorizationHelper
|
AuthorizationHelperInterface $authorizationHelper
|
||||||
) {
|
) {
|
||||||
$this->exportManager = $exportManager;
|
$this->exportManager = $exportManager;
|
||||||
$this->user = $tokenStorage->getToken()->getUser();
|
$this->user = $tokenStorage->getToken()->getUser();
|
||||||
$this->authorizationHelper = $authorizationHelper;
|
$this->authorizationHelper = $authorizationHelper;
|
||||||
}
|
$this->regroupmentRepository = $regroupmentRepository;
|
||||||
|
|
||||||
public function addGroupingCenter(GroupingCenterInterface $grouping)
|
|
||||||
{
|
|
||||||
$this->groupingCenters[md5($grouping->getName())] = $grouping;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
@ -72,97 +61,36 @@ class PickCenterType extends AbstractType
|
|||||||
$export->requiredRole()
|
$export->requiredRole()
|
||||||
);
|
);
|
||||||
|
|
||||||
$builder->add(self::CENTERS_IDENTIFIERS, EntityType::class, [
|
$builder->add('center', EntityType::class, [
|
||||||
'class' => Center::class,
|
'class' => Center::class,
|
||||||
|
'label' => 'center',
|
||||||
'choices' => $centers,
|
'choices' => $centers,
|
||||||
'multiple' => true,
|
'multiple' => true,
|
||||||
'expanded' => true,
|
'expanded' => true,
|
||||||
'choice_label' => static function (Center $c) {
|
'choice_label' => static function (Center $c) {
|
||||||
return $c->getName();
|
return $c->getName();
|
||||||
},
|
},
|
||||||
'data' => count($this->groupingCenters) > 0 ? null : $centers,
|
'data' => $centers,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (count($this->groupingCenters) > 0) {
|
if (count($this->regroupmentRepository->findAllActive()) > 0) {
|
||||||
$groupingBuilder = $builder->create('g', null, [
|
$builder->add('regroupment', EntityType::class, [
|
||||||
'compound' => true,
|
'class' => Regroupment::class,
|
||||||
|
'label' => 'regroupment',
|
||||||
|
'multiple' => true,
|
||||||
|
'expanded' => true,
|
||||||
|
'choices' => $this->regroupmentRepository->findAllActive(),
|
||||||
|
'choice_label' => static function (Regroupment $r) {
|
||||||
|
return $r->getName();
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
foreach ($this->groupingCenters as $key => $gc) {
|
|
||||||
$choices = $this->buildChoices($centers, $gc);
|
|
||||||
|
|
||||||
if (count($choices) > 0) {
|
|
||||||
$groupingBuilder->add($key, ChoiceType::class, [
|
|
||||||
'choices' => $choices,
|
|
||||||
'multiple' => true,
|
|
||||||
'expanded' => true,
|
|
||||||
'label' => $gc->getName(),
|
|
||||||
'required' => false,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($groupingBuilder->count() > 0) {
|
|
||||||
$builder->add($groupingBuilder);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$builder->addModelTransformer(new CallbackTransformer(
|
$builder->setDataMapper(new ExportPickCenterDataMapper());
|
||||||
function ($data) use ($centers) {
|
|
||||||
return $this->transform($data, $centers);
|
|
||||||
},
|
|
||||||
function ($data) use ($centers) {
|
|
||||||
return $this->reverseTransform($data, $centers);
|
|
||||||
}
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configureOptions(OptionsResolver $resolver)
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
{
|
{
|
||||||
$resolver->setRequired('export_alias');
|
$resolver->setRequired('export_alias');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function buildChoices($reachablesCenters, GroupingCenterInterface $gc)
|
|
||||||
{
|
|
||||||
$result = [];
|
|
||||||
|
|
||||||
foreach ($gc->getGroups() as $group) {
|
|
||||||
foreach ($gc->getCentersForGroup($group) as $center) {
|
|
||||||
if (in_array($center, $reachablesCenters, true)) {
|
|
||||||
$result[$group] = $group;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function reverseTransform($data, $centers)
|
|
||||||
{
|
|
||||||
$picked = $data[self::CENTERS_IDENTIFIERS]
|
|
||||||
instanceof \Doctrine\Common\Collections\Collection ?
|
|
||||||
$data[self::CENTERS_IDENTIFIERS]->toArray()
|
|
||||||
:
|
|
||||||
$data[self::CENTERS_IDENTIFIERS];
|
|
||||||
|
|
||||||
if (array_key_exists('g', $data)) {
|
|
||||||
foreach ($data['g'] as $gcid => $group) {
|
|
||||||
$picked =
|
|
||||||
array_merge(
|
|
||||||
array_intersect(
|
|
||||||
$this->groupingCenters[$gcid]->getCentersForGroup($group),
|
|
||||||
$centers
|
|
||||||
),
|
|
||||||
$picked
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_unique($picked);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function transform($data, $centers)
|
|
||||||
{
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ use Symfony\Component\Form\FormInterface;
|
|||||||
use Symfony\Component\Form\FormView;
|
use Symfony\Component\Form\FormView;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
use Symfony\Component\Serializer\SerializerInterface;
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,12 +29,15 @@ class PickUserDynamicType extends AbstractType
|
|||||||
{
|
{
|
||||||
private DenormalizerInterface $denormalizer;
|
private DenormalizerInterface $denormalizer;
|
||||||
|
|
||||||
|
private NormalizerInterface $normalizer;
|
||||||
|
|
||||||
private SerializerInterface $serializer;
|
private SerializerInterface $serializer;
|
||||||
|
|
||||||
public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer)
|
public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer, NormalizerInterface $normalizer)
|
||||||
{
|
{
|
||||||
$this->denormalizer = $denormalizer;
|
$this->denormalizer = $denormalizer;
|
||||||
$this->serializer = $serializer;
|
$this->serializer = $serializer;
|
||||||
|
$this->normalizer = $normalizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
@ -46,6 +50,11 @@ class PickUserDynamicType extends AbstractType
|
|||||||
$view->vars['multiple'] = $options['multiple'];
|
$view->vars['multiple'] = $options['multiple'];
|
||||||
$view->vars['types'] = ['user'];
|
$view->vars['types'] = ['user'];
|
||||||
$view->vars['uniqid'] = uniqid('pick_user_dyn');
|
$view->vars['uniqid'] = uniqid('pick_user_dyn');
|
||||||
|
$view->vars['suggested'] = [];
|
||||||
|
|
||||||
|
foreach ($options['suggested'] as $user) {
|
||||||
|
$view->vars['suggested'][] = $this->normalizer->normalize($user, 'json', ['groups' => 'read']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configureOptions(OptionsResolver $resolver)
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
@ -53,7 +62,8 @@ class PickUserDynamicType extends AbstractType
|
|||||||
$resolver
|
$resolver
|
||||||
->setDefault('multiple', false)
|
->setDefault('multiple', false)
|
||||||
->setAllowedTypes('multiple', ['bool'])
|
->setAllowedTypes('multiple', ['bool'])
|
||||||
->setDefault('compound', false);
|
->setDefault('compound', false)
|
||||||
|
->setDefault('suggested', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBlockPrefix()
|
public function getBlockPrefix()
|
||||||
|
@ -15,6 +15,7 @@ use Chill\MainBundle\Entity\Center;
|
|||||||
use Chill\MainBundle\Entity\Location;
|
use Chill\MainBundle\Entity\Location;
|
||||||
use Chill\MainBundle\Entity\Scope;
|
use Chill\MainBundle\Entity\Scope;
|
||||||
use Chill\MainBundle\Entity\UserJob;
|
use Chill\MainBundle\Entity\UserJob;
|
||||||
|
use Chill\MainBundle\Form\Type\ChillDateType;
|
||||||
use Chill\MainBundle\Form\Type\PickCivilityType;
|
use Chill\MainBundle\Form\Type\PickCivilityType;
|
||||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
@ -110,6 +111,11 @@ class UserType extends AbstractType
|
|||||||
|
|
||||||
return $qb;
|
return $qb;
|
||||||
},
|
},
|
||||||
|
])
|
||||||
|
->add('absenceStart', ChillDateType::class, [
|
||||||
|
'required' => false,
|
||||||
|
'input' => 'datetime_immutable',
|
||||||
|
'label' => 'absence.Absence start',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// @phpstan-ignore-next-line
|
// @phpstan-ignore-next-line
|
||||||
|
@ -154,6 +154,7 @@ class WorkflowStepType extends AbstractType
|
|||||||
'label' => 'workflow.dest for next steps',
|
'label' => 'workflow.dest for next steps',
|
||||||
'multiple' => true,
|
'multiple' => true,
|
||||||
'mapped' => false,
|
'mapped' => false,
|
||||||
|
'suggested' => $options['suggested_users'],
|
||||||
])
|
])
|
||||||
->add('future_dest_emails', ChillCollectionType::class, [
|
->add('future_dest_emails', ChillCollectionType::class, [
|
||||||
'label' => 'workflow.dest by email',
|
'label' => 'workflow.dest by email',
|
||||||
@ -200,6 +201,7 @@ class WorkflowStepType extends AbstractType
|
|||||||
->setAllowedTypes('transition', 'bool')
|
->setAllowedTypes('transition', 'bool')
|
||||||
->setRequired('entity_workflow')
|
->setRequired('entity_workflow')
|
||||||
->setAllowedTypes('entity_workflow', EntityWorkflow::class)
|
->setAllowedTypes('entity_workflow', EntityWorkflow::class)
|
||||||
|
->setDefault('suggested_users', [])
|
||||||
->setDefault('constraints', [
|
->setDefault('constraints', [
|
||||||
new Callback(
|
new Callback(
|
||||||
function ($step, ExecutionContextInterface $context, $payload) {
|
function ($step, ExecutionContextInterface $context, $payload) {
|
||||||
|
@ -118,7 +118,7 @@ final class PhonenumberHelper implements PhoneNumberHelperInterface
|
|||||||
* Return true if the phonenumber is a landline or voip phone. Return always true
|
* Return true if the phonenumber is a landline or voip phone. Return always true
|
||||||
* if the validation is not configured.
|
* if the validation is not configured.
|
||||||
*
|
*
|
||||||
* @param string $phonenumber
|
* @param string|PhoneNumber $phonenumber
|
||||||
*/
|
*/
|
||||||
public function isValidPhonenumberAny($phonenumber): bool
|
public function isValidPhonenumberAny($phonenumber): bool
|
||||||
{
|
{
|
||||||
@ -138,7 +138,7 @@ final class PhonenumberHelper implements PhoneNumberHelperInterface
|
|||||||
* Return true if the phonenumber is a landline or voip phone. Return always true
|
* Return true if the phonenumber is a landline or voip phone. Return always true
|
||||||
* if the validation is not configured.
|
* if the validation is not configured.
|
||||||
*
|
*
|
||||||
* @param string $phonenumber
|
* @param string|PhoneNumber $phonenumber
|
||||||
*/
|
*/
|
||||||
public function isValidPhonenumberLandOrVoip($phonenumber): bool
|
public function isValidPhonenumberLandOrVoip($phonenumber): bool
|
||||||
{
|
{
|
||||||
@ -159,7 +159,7 @@ final class PhonenumberHelper implements PhoneNumberHelperInterface
|
|||||||
* REturn true if the phonenumber is a mobile phone. Return always true
|
* REturn true if the phonenumber is a mobile phone. Return always true
|
||||||
* if the validation is not configured.
|
* if the validation is not configured.
|
||||||
*
|
*
|
||||||
* @param string $phonenumber
|
* @param string|PhoneNumber $phonenumber
|
||||||
*/
|
*/
|
||||||
public function isValidPhonenumberMobile($phonenumber): bool
|
public function isValidPhonenumberMobile($phonenumber): bool
|
||||||
{
|
{
|
||||||
@ -182,6 +182,10 @@ final class PhonenumberHelper implements PhoneNumberHelperInterface
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($phonenumber instanceof PhoneNumber) {
|
||||||
|
$phonenumber = (string) $phonenumber;
|
||||||
|
}
|
||||||
|
|
||||||
// filter only number
|
// filter only number
|
||||||
$filtered = preg_replace('/[^0-9]/', '', $phonenumber);
|
$filtered = preg_replace('/[^0-9]/', '', $phonenumber);
|
||||||
|
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
<?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\Repository;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Regroupment;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
|
||||||
|
final class RegroupmentRepository implements ObjectRepository
|
||||||
|
{
|
||||||
|
private EntityRepository $repository;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $entityManager)
|
||||||
|
{
|
||||||
|
$this->repository = $entityManager->getRepository(Regroupment::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find($id, $lockMode = null, $lockVersion = null): ?Regroupment
|
||||||
|
{
|
||||||
|
return $this->repository->find($id, $lockMode, $lockVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Regroupment[]
|
||||||
|
*/
|
||||||
|
public function findAll(): array
|
||||||
|
{
|
||||||
|
return $this->repository->findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findAllActive(): array
|
||||||
|
{
|
||||||
|
return $this->repository->findBy(['isActive' => true], ['name' => 'ASC']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed|null $limit
|
||||||
|
* @param mixed|null $offset
|
||||||
|
*
|
||||||
|
* @return Regroupment[]
|
||||||
|
*/
|
||||||
|
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
|
||||||
|
{
|
||||||
|
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findOneBy(array $criteria, ?array $orderBy = null): ?Regroupment
|
||||||
|
{
|
||||||
|
return $this->repository->findOneBy($criteria, $orderBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClassName()
|
||||||
|
{
|
||||||
|
return Regroupment::class;
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,7 @@ function loadDynamicPicker(element) {
|
|||||||
(input.value === '[]' || input.value === '') ?
|
(input.value === '[]' || input.value === '') ?
|
||||||
null : [ JSON.parse(input.value) ]
|
null : [ JSON.parse(input.value) ]
|
||||||
)
|
)
|
||||||
;
|
suggested = JSON.parse(el.dataset.suggested)
|
||||||
|
|
||||||
if (!isMultiple) {
|
if (!isMultiple) {
|
||||||
if (input.value === '[]'){
|
if (input.value === '[]'){
|
||||||
@ -37,6 +37,7 @@ function loadDynamicPicker(element) {
|
|||||||
':types="types" ' +
|
':types="types" ' +
|
||||||
':picked="picked" ' +
|
':picked="picked" ' +
|
||||||
':uniqid="uniqid" ' +
|
':uniqid="uniqid" ' +
|
||||||
|
':suggested="notPickedSuggested" ' +
|
||||||
'@addNewEntity="addNewEntity" ' +
|
'@addNewEntity="addNewEntity" ' +
|
||||||
'@removeEntity="removeEntity"></pick-entity>',
|
'@removeEntity="removeEntity"></pick-entity>',
|
||||||
components: {
|
components: {
|
||||||
@ -48,16 +49,31 @@ function loadDynamicPicker(element) {
|
|||||||
types: JSON.parse(el.dataset.types),
|
types: JSON.parse(el.dataset.types),
|
||||||
picked: picked === null ? [] : picked,
|
picked: picked === null ? [] : picked,
|
||||||
uniqid: el.dataset.uniqid,
|
uniqid: el.dataset.uniqid,
|
||||||
|
suggested: suggested
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
notPickedSuggested() {
|
||||||
|
if (this.multiple) {
|
||||||
|
const pickedIds = new Set();
|
||||||
|
for (const p of this.picked) {
|
||||||
|
pickedIds.add(`${p.type}${p.id}`);
|
||||||
|
}
|
||||||
|
return this.suggested.filter(e => !pickedIds.has(`${e.type}${e.id}`))
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.suggested.filter(e => e.type !== this.picked.type && e.id !== e.picked.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addNewEntity(entity) {
|
addNewEntity({entity}) {
|
||||||
if (this.multiple) {
|
if (this.multiple) {
|
||||||
if (!this.picked.some(el => {
|
if (!this.picked.some(el => {
|
||||||
return el.type === entity.type && el.id === entity.id;
|
return el.type === entity.type && el.id === entity.id;
|
||||||
})) {
|
})) {
|
||||||
this.picked.push(entity);
|
this.picked.push(entity);
|
||||||
input.value = JSON.stringify(this.picked);
|
input.value = JSON.stringify(this.picked);
|
||||||
|
console.log(entity)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!this.picked.some(el => {
|
if (!this.picked.some(el => {
|
||||||
@ -69,7 +85,10 @@ function loadDynamicPicker(element) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeEntity(entity) {
|
removeEntity({entity}) {
|
||||||
|
if (-1 === this.suggested.findIndex(e => e.type === entity.type && e.id === entity.id)) {
|
||||||
|
this.suggested.push(entity);
|
||||||
|
}
|
||||||
this.picked = this.picked.filter(e => !(e.type === entity.type && e.id === entity.id));
|
this.picked = this.picked.filter(e => !(e.type === entity.type && e.id === entity.id));
|
||||||
input.value = JSON.stringify(this.picked);
|
input.value = JSON.stringify(this.picked);
|
||||||
},
|
},
|
||||||
|
@ -35,6 +35,7 @@ export interface User {
|
|||||||
id: number;
|
id: number;
|
||||||
username: string;
|
username: string;
|
||||||
text: string;
|
text: string;
|
||||||
|
text_without_absence: string;
|
||||||
email: string;
|
email: string;
|
||||||
user_job: Job;
|
user_job: Job;
|
||||||
label: string;
|
label: string;
|
||||||
|
@ -233,7 +233,7 @@ export default {
|
|||||||
// console.log('data original', data);
|
// console.log('data original', data);
|
||||||
data.parent = {type: "thirdparty", id: this.parent.id};
|
data.parent = {type: "thirdparty", id: this.parent.id};
|
||||||
data.civility = data.civility !== null ? {type: 'chill_main_civility', id: data.civility.id} : null;
|
data.civility = data.civility !== null ? {type: 'chill_main_civility', id: data.civility.id} : null;
|
||||||
data.profession = data.profession !== null ? {type: 'third_party_profession', id: data.profession.id} : null;
|
data.profession = data.profession !== '' ? data.profession : '';
|
||||||
} else {
|
} else {
|
||||||
type = this.$refs.castNew.radioType;
|
type = this.$refs.castNew.radioType;
|
||||||
data = this.$refs.castNew.castDataByType();
|
data = this.$refs.castNew.castDataByType();
|
||||||
@ -241,8 +241,8 @@ export default {
|
|||||||
if (typeof data.civility !== 'undefined' && null !== data.civility) {
|
if (typeof data.civility !== 'undefined' && null !== data.civility) {
|
||||||
data.civility = data.civility !== null ? {type: 'chill_main_civility', id: data.civility.id} : null;
|
data.civility = data.civility !== null ? {type: 'chill_main_civility', id: data.civility.id} : null;
|
||||||
}
|
}
|
||||||
if (typeof data.profession !== 'undefined' && null !== data.profession) {
|
if (typeof data.profession !== 'undefined' && '' !== data.profession) {
|
||||||
data.profession = data.profession !== null ? {type: 'third_party_profession', id: data.profession.id} : null;
|
data.profession = data.profession !== '' ? data.profession : '';
|
||||||
}
|
}
|
||||||
// console.log('onthefly data', data);
|
// console.log('onthefly data', data);
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,9 @@
|
|||||||
</add-persons>
|
</add-persons>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<ul class="list-suggest add-items inline">
|
||||||
|
<li v-for="s in suggested" :key="s.id" @click="addNewSuggested(s)"><span>{{ s.text }}</span></li>
|
||||||
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -49,6 +52,10 @@ export default {
|
|||||||
// display picked entities.
|
// display picked entities.
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
|
},
|
||||||
|
suggested: {
|
||||||
|
type: Array,
|
||||||
|
default: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['addNewEntity', 'removeEntity'],
|
emits: ['addNewEntity', 'removeEntity'],
|
||||||
@ -61,55 +68,58 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
addPersonsOptions() {
|
addPersonsOptions() {
|
||||||
return {
|
return {
|
||||||
uniq: !this.multiple,
|
uniq: !this.multiple,
|
||||||
type: this.types,
|
type: this.types,
|
||||||
priority: null,
|
priority: null,
|
||||||
button: {
|
button: {
|
||||||
size: 'btn-sm',
|
size: 'btn-sm',
|
||||||
class: 'btn-submit',
|
class: 'btn-submit',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
translatedListOfTypes() {
|
translatedListOfTypes() {
|
||||||
let trans = [];
|
let trans = [];
|
||||||
this.types.forEach(t => {
|
this.types.forEach(t => {
|
||||||
if (this.$props.multiple) {
|
if (this.$props.multiple) {
|
||||||
trans.push(appMessages.fr.pick_entity[t].toLowerCase());
|
trans.push(appMessages.fr.pick_entity[t].toLowerCase());
|
||||||
} else {
|
} else {
|
||||||
trans.push(appMessages.fr.pick_entity[t + '_one'].toLowerCase());
|
trans.push(appMessages.fr.pick_entity[t + '_one'].toLowerCase());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (this.$props.multiple) {
|
if (this.$props.multiple) {
|
||||||
return appMessages.fr.pick_entity.modal_title + trans.join(', ');
|
return appMessages.fr.pick_entity.modal_title + trans.join(', ');
|
||||||
} else {
|
} else {
|
||||||
return appMessages.fr.pick_entity.modal_title_one + trans.join(', ');
|
return appMessages.fr.pick_entity.modal_title_one + trans.join(', ');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
listClasses() {
|
listClasses() {
|
||||||
return {
|
return {
|
||||||
'list-suggest': true,
|
'list-suggest': true,
|
||||||
'remove-items': this.$props.removableIfSet,
|
'remove-items': this.$props.removableIfSet,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addNewEntity({ selected, modal }) {
|
addNewSuggested(entity) {
|
||||||
selected.forEach((item) => {
|
this.$emit('addNewEntity', {entity: entity});
|
||||||
this.$emit('addNewEntity', item.result);
|
},
|
||||||
}, this
|
addNewEntity({ selected, modal }) {
|
||||||
);
|
selected.forEach((item) => {
|
||||||
this.$refs.addPersons.resetSearch(); // to cast child method
|
this.$emit('addNewEntity', { entity: item.result});
|
||||||
modal.showModal = false;
|
}, this
|
||||||
},
|
);
|
||||||
removeEntity(entity) {
|
this.$refs.addPersons.resetSearch(); // to cast child method
|
||||||
if (!this.$props.removableIfSet) {
|
modal.showModal = false;
|
||||||
return;
|
},
|
||||||
}
|
removeEntity(entity) {
|
||||||
this.$emit('removeEntity', entity);
|
if (!this.$props.removableIfSet) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
this.$emit('removeEntity',{ entity: entity });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="chill-entity entity-user">
|
<span class="chill-entity entity-user">
|
||||||
{{ user.label }}
|
{{ user.label }}
|
||||||
<span class="user-job" v-if="user.user_job !== null">({{ user.user_job.label.fr }})</span>
|
<span class="user-job" v-if="user.user_job !== null">({{ user.user_job.label.fr }})</span> <span class="main-scope" v-if="user.main_scope !== null">({{ user.main_scope.name.fr }})</span> <span v-if="user.isAbsent" class="badge bg-danger rounded-pill" :title="Absent">A</span>
|
||||||
<span class="main-scope" v-if="user.main_scope !== null">({{ user.main_scope.name.fr }})</span>
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -6,4 +6,7 @@
|
|||||||
{%- if opts['main_scope'] and user.mainScope is not null %}
|
{%- if opts['main_scope'] and user.mainScope is not null %}
|
||||||
<span class="main-scope">({{ user.mainScope.name|localize_translatable_string }})</span>
|
<span class="main-scope">({{ user.mainScope.name|localize_translatable_string }})</span>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
{%- if opts['absence'] and user.isAbsent %}
|
||||||
|
<span class="badge bg-danger rounded-pill" title="{{ 'absence.Absent'|trans|escape('html_attr') }}">{{ 'absence.A'|trans }}</span>
|
||||||
|
{%- endif -%}
|
||||||
</span>
|
</span>
|
||||||
|
@ -38,18 +38,13 @@
|
|||||||
<p>{{ 'The export will contains only data from the picked centers.'|trans }}
|
<p>{{ 'The export will contains only data from the picked centers.'|trans }}
|
||||||
{{ 'This will eventually restrict your possibilities in filtering the data.'|trans }}</p>
|
{{ 'This will eventually restrict your possibilities in filtering the data.'|trans }}</p>
|
||||||
|
|
||||||
{{ form_widget(form.centers.c) }}
|
<h3 class="m-3">{{ 'Center'|trans }}</h3>
|
||||||
|
{{ form_widget(form.centers.center) }}
|
||||||
{% if form.centers.children.g is defined %}
|
|
||||||
|
|
||||||
<h3>{{ 'Pick aggregated centers'|trans }}</h3>
|
|
||||||
|
|
||||||
{% for f in form.centers.children.g.children %}
|
|
||||||
{{ form_row(f) }}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
|
{% if form.centers.regroupment is defined %}
|
||||||
|
<h3 class="m-3">{{ 'Pick aggregated centers'|trans }}</h3>
|
||||||
|
{{ form_widget(form.centers.regroupment) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<p>{{ form_widget(form.submit, { 'attr' : { 'class' : 'btn btn-action btn-create' }, 'label' : 'Go to export options' } ) }}</p>
|
<p>{{ form_widget(form.submit, { 'attr' : { 'class' : 'btn btn-action btn-create' }, 'label' : 'Go to export options' } ) }}</p>
|
||||||
|
@ -249,7 +249,11 @@
|
|||||||
|
|
||||||
{% block pick_entity_dynamic_widget %}
|
{% block pick_entity_dynamic_widget %}
|
||||||
<input type="hidden" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value|escape('html_attr') }}" {% endif %} data-input-uniqid="{{ form.vars['uniqid'] }}"/>
|
<input type="hidden" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value|escape('html_attr') }}" {% endif %} data-input-uniqid="{{ form.vars['uniqid'] }}"/>
|
||||||
<div data-module="pick-dynamic" data-types="{{ form.vars['types']|json_encode }}" data-multiple="{{ form.vars['multiple'] }}" data-uniqid="{{ form.vars['uniqid'] }}"></div>
|
<div data-module="pick-dynamic"
|
||||||
|
data-types="{{ form.vars['types']|json_encode }}"
|
||||||
|
data-multiple="{{ form.vars['multiple'] }}"
|
||||||
|
data-uniqid="{{ form.vars['uniqid'] }}"
|
||||||
|
data-suggested="{{ form.vars['suggested']|json_encode|escape('html_attr') }}"></div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block pick_postal_code_widget %}
|
{% block pick_postal_code_widget %}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<div class="col-10 mt-5">
|
<div class="col-10 mt-5">
|
||||||
|
|
||||||
{# vue component #}
|
{# vue component #}
|
||||||
<div id="homepage_widget"></div>
|
<div id="homepage_widget"></div>
|
||||||
|
|
||||||
{% include '@ChillMain/Homepage/fast_actions.html.twig' %}
|
{% include '@ChillMain/Homepage/fast_actions.html.twig' %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{{ encore_entry_link_tags('page_homepage_widget') }}
|
{{ encore_entry_link_tags('page_homepage_widget') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
{% extends '@ChillMain/Admin/layout.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{{ 'absence.My absence'|trans }}
|
||||||
|
{% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="col-md-10">
|
||||||
|
<h2>{{ 'absence.My absence'|trans }}</h2>
|
||||||
|
|
||||||
|
{% if user.absenceStart is not null %}
|
||||||
|
<div>
|
||||||
|
<p>{{ 'absence.You are listed as absent, as of'|trans }} {{ user.absenceStart|format_date('long') }}</p>
|
||||||
|
<ul class="record_actions sticky-form-buttons">
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('chill_main_user_absence_unset') }}"
|
||||||
|
class="btn btn-delete">{{ 'absence.Unset absence'|trans }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div>
|
||||||
|
<p class="chill-no-data-statement">{{ 'absence.No absence listed'|trans }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ form_start(form) }}
|
||||||
|
{{ form_row(form.absenceStart) }}
|
||||||
|
|
||||||
|
<ul class="record_actions sticky-form-buttons">
|
||||||
|
<li>
|
||||||
|
<button class="btn btn-save" type="submit">
|
||||||
|
{{ 'Save'|trans }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{{ form_end(form) }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
{% block table_entities_thead_tr %}
|
{% block table_entities_thead_tr %}
|
||||||
<th>{{ 'Active'|trans }}</th>
|
<th>{{ 'Active'|trans }}</th>
|
||||||
|
<th>{{ 'absence.Is absent'|trans }}</th>
|
||||||
<th>{{ 'Username'|trans }}</th>
|
<th>{{ 'Username'|trans }}</th>
|
||||||
<th>{{ 'Datas'|trans }}</th>
|
<th>{{ 'Datas'|trans }}</th>
|
||||||
<th>{{ 'Actions'|trans }}</th>
|
<th>{{ 'Actions'|trans }}</th>
|
||||||
@ -26,6 +27,13 @@
|
|||||||
<i class="fa fa-square-o"></i>
|
<i class="fa fa-square-o"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if entity.isAbsent %}
|
||||||
|
<i class="fa fa-check-square-o"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="fa fa-square-o"></i>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{#
|
{#
|
||||||
{% if entity.civility is not null %}
|
{% if entity.civility is not null %}
|
||||||
|
@ -72,7 +72,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-row column">
|
<div class="item-row column">
|
||||||
<table class="obj-res-eval my-3">
|
<table class="obj-res-eval smallfont my-3">
|
||||||
<thead>
|
<thead>
|
||||||
<tr><th class="obj"><h4 class="title_label">Objectif - motif - dispositif</h4></th>
|
<tr><th class="obj"><h4 class="title_label">Objectif - motif - dispositif</h4></th>
|
||||||
<th class="res"><h4 class="title_label">Résultats - orientations</h4></th>
|
<th class="res"><h4 class="title_label">Résultats - orientations</h4></th>
|
||||||
|
@ -69,18 +69,26 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-8 main_search">
|
<div class="col-8 main_search">
|
||||||
|
{% if app.user.isAbsent %}
|
||||||
|
<div class="d-flex flex-row mb-5 alert alert-warning" role="alert">
|
||||||
|
<p class="m-2">{{'absence.You are marked as being absent'|trans }}</p>
|
||||||
|
<span class="ms-auto">
|
||||||
|
<a class="btn btn-remove" title="Modifier" href="{{ path('chill_main_user_absence_index') }}">{{ 'absence.Unset absence'|trans }}</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<h2>{{ 'Search'|trans }}</h2>
|
<h2>{{ 'Search'|trans }}</h2>
|
||||||
|
|
||||||
<form action="{{ path('chill_main_search') }}" method="get">
|
<form action="{{ path('chill_main_search') }}" method="get">
|
||||||
<input class="form-control form-control-lg" name="q" type="search" placeholder="{{ 'Search persons, ...'|trans }}" />
|
<input class="form-control form-control-lg" name="q" type="search" placeholder="{{ 'Search persons, ...'|trans }}" />
|
||||||
<center>
|
<div class="text-center">
|
||||||
<button type="submit" class="btn btn-lg btn-warning mt-3">
|
<button type="submit" class="btn btn-lg btn-warning mt-3">
|
||||||
<i class="fa fa-fw fa-search"></i> {{ 'Search'|trans }}
|
<i class="fa fa-fw fa-search"></i> {{ 'Search'|trans }}
|
||||||
</button>
|
</button>
|
||||||
<a class="btn btn-lg btn-misc mt-3" href="{{ path('chill_main_advanced_search_list') }}">
|
<a class="btn btn-lg btn-misc mt-3" href="{{ path('chill_main_advanced_search_list') }}">
|
||||||
<i class="fa fa-fw fa-search"></i> {{ 'Advanced search'|trans }}
|
<i class="fa fa-fw fa-search"></i> {{ 'Advanced search'|trans }}
|
||||||
</a>
|
</a>
|
||||||
</center>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -78,6 +78,15 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
|
|||||||
|
|
||||||
$nbNotifications = $this->notificationByUserCounter->countUnreadByUser($user);
|
$nbNotifications = $this->notificationByUserCounter->countUnreadByUser($user);
|
||||||
|
|
||||||
|
//TODO add an icon? How exactly? For example a clock icon...
|
||||||
|
$menu
|
||||||
|
->addChild($this->translator->trans('absence.Set absence date'), [
|
||||||
|
'route' => 'chill_main_user_absence_index',
|
||||||
|
])
|
||||||
|
->setExtras([
|
||||||
|
'order' => -8888888,
|
||||||
|
]);
|
||||||
|
|
||||||
$menu
|
$menu
|
||||||
->addChild(
|
->addChild(
|
||||||
$this->translator->trans('notification.My notifications with counter', ['nb' => $nbNotifications]),
|
$this->translator->trans('notification.My notifications with counter', ['nb' => $nbNotifications]),
|
||||||
|
@ -31,6 +31,7 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware
|
|||||||
'id' => '',
|
'id' => '',
|
||||||
'username' => '',
|
'username' => '',
|
||||||
'text' => '',
|
'text' => '',
|
||||||
|
'text_without_absent' => '',
|
||||||
'label' => '',
|
'label' => '',
|
||||||
'email' => '',
|
'email' => '',
|
||||||
];
|
];
|
||||||
@ -82,11 +83,13 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware
|
|||||||
'id' => $object->getId(),
|
'id' => $object->getId(),
|
||||||
'username' => $object->getUsername(),
|
'username' => $object->getUsername(),
|
||||||
'text' => $this->userRender->renderString($object, []),
|
'text' => $this->userRender->renderString($object, []),
|
||||||
|
'text_without_absent' => $this->userRender->renderString($object, ['absence' => false]),
|
||||||
'label' => $object->getLabel(),
|
'label' => $object->getLabel(),
|
||||||
'email' => (string) $object->getEmail(),
|
'email' => (string) $object->getEmail(),
|
||||||
'user_job' => $this->normalizer->normalize($object->getUserJob(), $format, $userJobContext),
|
'user_job' => $this->normalizer->normalize($object->getUserJob(), $format, $userJobContext),
|
||||||
'main_center' => $this->normalizer->normalize($object->getMainCenter(), $format, $centerContext),
|
'main_center' => $this->normalizer->normalize($object->getMainCenter(), $format, $centerContext),
|
||||||
'main_scope' => $this->normalizer->normalize($object->getMainScope(), $format, $scopeContext),
|
'main_scope' => $this->normalizer->normalize($object->getMainScope(), $format, $scopeContext),
|
||||||
|
'isAbsent' => $object->isAbsent(),
|
||||||
];
|
];
|
||||||
|
|
||||||
if ('docgen' === $format) {
|
if ('docgen' === $format) {
|
||||||
|
@ -13,8 +13,10 @@ namespace Chill\MainBundle\Templating\Entity;
|
|||||||
|
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||||
use Symfony\Component\Templating\EngineInterface;
|
use DateTimeImmutable;
|
||||||
|
|
||||||
|
use Symfony\Component\Templating\EngineInterface;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
use function array_merge;
|
use function array_merge;
|
||||||
|
|
||||||
class UserRender implements ChillEntityRenderInterface
|
class UserRender implements ChillEntityRenderInterface
|
||||||
@ -22,16 +24,20 @@ class UserRender implements ChillEntityRenderInterface
|
|||||||
public const DEFAULT_OPTIONS = [
|
public const DEFAULT_OPTIONS = [
|
||||||
'main_scope' => true,
|
'main_scope' => true,
|
||||||
'user_job' => true,
|
'user_job' => true,
|
||||||
|
'absence' => true,
|
||||||
];
|
];
|
||||||
|
|
||||||
private EngineInterface $engine;
|
private EngineInterface $engine;
|
||||||
|
|
||||||
private TranslatableStringHelper $translatableStringHelper;
|
private TranslatableStringHelper $translatableStringHelper;
|
||||||
|
|
||||||
public function __construct(TranslatableStringHelper $translatableStringHelper, EngineInterface $engine)
|
private TranslatorInterface $translator;
|
||||||
|
|
||||||
|
public function __construct(TranslatableStringHelper $translatableStringHelper, EngineInterface $engine, TranslatorInterface $translator)
|
||||||
{
|
{
|
||||||
$this->translatableStringHelper = $translatableStringHelper;
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
$this->engine = $engine;
|
$this->engine = $engine;
|
||||||
|
$this->translator = $translator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function renderBox($entity, array $options): string
|
public function renderBox($entity, array $options): string
|
||||||
@ -63,6 +69,10 @@ class UserRender implements ChillEntityRenderInterface
|
|||||||
->localize($entity->getMainScope()->getName()) . ')';
|
->localize($entity->getMainScope()->getName()) . ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($entity->isAbsent() && $opts['absence']) {
|
||||||
|
$str .= ' (' . $this->translator->trans('absence.Absent') . ')';
|
||||||
|
}
|
||||||
|
|
||||||
return $str;
|
return $str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +36,11 @@ interface EntityWorkflowHandlerInterface
|
|||||||
*/
|
*/
|
||||||
public function getRoleShow(EntityWorkflow $entityWorkflow): ?string;
|
public function getRoleShow(EntityWorkflow $entityWorkflow): ?string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return User[]
|
||||||
|
*/
|
||||||
|
public function getSuggestedUsers(EntityWorkflow $entityWorkflow): array;
|
||||||
|
|
||||||
public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string;
|
public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string;
|
||||||
|
|
||||||
public function getTemplateData(EntityWorkflow $entityWorkflow, array $options = []): array;
|
public function getTemplateData(EntityWorkflow $entityWorkflow, array $options = []): array;
|
||||||
|
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