Merge branch 'master' into issue279_accompanying_period_validation

This commit is contained in:
Julien Fastré 2021-12-07 17:55:00 +01:00
commit f6408af543
92 changed files with 3270 additions and 1063 deletions

View File

@ -12,17 +12,24 @@ and this project adheres to
<!-- write down unreleased development here -->
* [person] add validator for accompanying period with a test on social issues (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/76)
## Test releases
### test release 2021-12-06
* [main] address: use search API end points for getting postal code and reference address (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/316)
* [main] address: in edit mode, select the encoded values in multiselect for address reference and city (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/316)
* [person search] fix bug when using birthdate after and birthdate before
* [person search] increase pertinence when lastname begins with search pattern
* [activity/actions] Améliore la cohérence du design entre
* la page résumé d'un parcours (liste d'actions récentes et liste d'activités récentes)
* la page liste des actions
* la page liste des activités (contexte personne / contexte parcours)
* [household] field to edit wheter person is titulaire of household or not removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/322)
* [activity] create work if a work with same social action is not associated to the activity
* [visgraph] improve and fix bugs on vis-network relationship graph
* [bugfix] posting of birth- and deathdate through api fixed.
## Test releases
### Test release 2021-11-19 - bis
* [household] do not allow to create two addresses on the same date
@ -175,7 +182,7 @@ and this project adheres to
* fast creation buttons
* add ordering for types
* [AccompanyingCourse Resume page] badge-title for AccompanyingCourseWork and for Activities;
* [AccompanyingCourse Resume page] dashboard for AccompanyingCourseWork and for Activities;
* Improve badges behaviour with small screens;
* [ThirdParty]:

View File

@ -24,6 +24,7 @@
"phpoffice/phpspreadsheet": "^1.16",
"ramsey/uuid-doctrine": "^1.7",
"sensio/framework-extra-bundle": "^5.5",
"spomky-labs/base64url": "^2.0",
"symfony/asset": "4.*",
"symfony/browser-kit": "^5.2",
"symfony/css-selector": "^5.2",

View File

@ -10,16 +10,6 @@ parameters:
count: 1
path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php
-
message: "#^Instantiated class PhpOffice\\\\PhpWord\\\\TemplateProcessor not found\\.$#"
count: 1
path: src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorController.php
-
message: "#^Instantiated class PhpOffice\\\\PhpWord\\\\TemplateProcessor not found\\.$#"
count: 1
path: src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php
-
message: "#^Variable \\$participation might not be defined\\.$#"
count: 3

View File

@ -24,14 +24,16 @@ div.new-activity-select-type {
}
//// ACTIVITY LIST PAGE
// precise badge-title specific details
// precise dashboard specific details
p.date-label {
display: inline-block;
margin: 0 0.5em 0 0;
font-weight: 700;
font-size: 18pt;
}
div.dashboard,
h2.badge-title {
div.duration {
font-size: smaller;
padding-left: 1em;
margin-top: 1em;
}
ul.list-content {
font-size: 70%;
list-style-type: none;
@ -39,16 +41,13 @@ h2.badge-title {
margin: 0;
li {
margin-bottom: 0.2em;
// exception: change bg color for action badges above badge-title
// exception: change bg color for action badges above dashboard
.bg-light {
background-color: $chill-light-gray !important;
}
}
}
}
div.main {
padding: 1em;
}
//// ACTIVITY SHOW AND FORM PAGES
// Exceptions for flex-bloc in concerned-groups

View File

@ -1,110 +0,0 @@
<h2 class="badge-title">
<span class="title_label">
{% if activity.date %}
<h3>{{ activity.date|format_date('short') }}</h3>
{% endif %}
<div class="duration">
{% if activity.durationTime and t.durationTimeVisible %}
<p>
<abbr class="fa fa-fw fa-hourglass-end" title="{{ 'Duration Time'|trans }}"></abbr>
{{ activity.durationTime|date('H:i') }}
</p>
{% endif %}
{% if activity.travelTime and t.travelTimeVisible %}
<p>
<abbr class="fa fa-fw fa-car" title="{{ 'Travel time'|trans }}"></abbr>
{{ activity.travelTime|date('H:i') }}
</p>
{% endif %}
</div>
</span>
<span class="title_action">
{{ activity.type.name | localize_translatable_string }}
{% if activity.emergency %}
<span class="badge bg-danger rounded-pill fs-6">{{ 'Emergency'|trans|upper }}</span>
{% endif %}
<ul class="small_in_title mt-3">
{% if activity.sentReceived is not empty and t.sentReceivedVisible %}
<li>
<span class="item-key">{{ 'Sent received'|trans ~ ' : ' }}</span>
<b>{{ activity.sentReceived|capitalize|trans }}</b>
</li>
{% endif %}
{% if activity.location and t.locationVisible %}
<li>
<span class="item-key">{{ 'location'|trans ~ ': ' }}</span>
<b>
<span>{{ activity.location.locationType.title|localize_translatable_string }}</span>
{{ activity.location.name }}
</b>
</li>
{% endif %}
{% if activity.user and t.userVisible %}
<li>
<span class="item-key">{{ 'Referrer'|trans ~ ': ' }}</span>
<b>{{ activity.user.usernameCanonical }}</b>
</li>
{% endif %}
<li class="associated-persons">
<span class="item-key">{{ 'Participants'|trans ~ ' : ' }}</span>
{% for p in activity.personsAssociated %}
<span class="badge-person">{{ p|chill_entity_render_box }}</span>
{% endfor %}
</li>
</ul>
<ul class="list-content my-3">
{%- if t.reasonsVisible -%}
{%- if activity.reasons is not empty -%}
<li class="reasons">
{% for r in activity.reasons %}
{{ r|chill_entity_render_box }}
{% endfor %}
</li>
{%- endif -%}
{% endif %}
{%- if t.socialIssuesVisible %}
{%- if activity.socialIssues is not empty -%}
<li class="social-issues">
{% for r in activity.socialIssues %}
{{ r|chill_entity_render_box }}
{% endfor %}
</li>
{%- endif -%}
{% endif %}
{%- if t.socialActionsVisible -%}
{%- if activity.socialActions is not empty -%}
<li class="social-actions">
{% for r in activity.socialActions %}
{{ r|chill_entity_render_box }}
{% endfor %}
</li>
{%- endif -%}
{% endif %}
</ul>
</span>
</h2>
{% if context == 'person' and activity.accompanyingPeriod is not empty %}
<div class="mt-3">
<a class="btn btn-sm btn-outline-primary"
title="{{ 'Period number %number%'|trans({'%number%': activity.accompanyingPeriod.id}) }}"
href="{{ chill_path_add_return_path(
"chill_person_accompanying_course_index",
{ 'accompanying_period_id': activity.accompanyingPeriod.id }
) }}"><i class="fa fa-random"></i>
</a>
</div>
{% endif %}

View File

@ -126,7 +126,7 @@
<div class="wl-row">
{% if bloc.items|length > 0 %}
<div class="wl-col title">
<h4>{{ bloc.title }}</h4>
<h3>{{ bloc.title }}</h3>
</div>
<div class="wl-col list">
{% for item in bloc.items %}

View File

@ -10,50 +10,175 @@
{% for activity in activities %}
{% set t = activity.type %}
<div class="item-bloc">
<div class="item-row">
{% include '@ChillActivity/Activity/activity-badge-title.html.twig' %}
<div class="wrap-list">
<div class="wl-row">
<div class="wl-col title">
{% if activity.date %}
<p class="date-label">
{{ activity.date|format_date('short') }}
</p>
{% endif %}
</div>
<div class="wl-col list">
<h2 class="badge-title">
<span class="title_label"></span>
<span class="title_action">
{{ activity.type.name | localize_translatable_string }}
{% if activity.emergency %}
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
{% endif %}
</span>
</h2>
</div>
</div>
</div>
</div>
{% if activity.comment.comment is not empty
or activity.persons|length > 0
or activity.thirdParties|length > 0
or activity.users|length > 0
%}
<div class="main">
{% if activity.comment.comment is not empty %}
{{ activity.comment|chill_entity_render_box({
'disable_markdown': false,
'limit_lines': 3,
'metadata': false,
}) }}
<div class="item-row column separator">
<div class="wrap-list">
{% if activity.location and t.locationVisible %}
<div class="wl-row">
<div class="wl-col title"><h3>{{ 'location'|trans }}</h3></div>
<div class="wl-col list">
<p class="wl-item">
<span>{{ activity.location.locationType.title|localize_translatable_string }}</span>
{{ activity.location.name }}
</p>
</div>
</div>
{% endif %}
{% if activity.sentReceived is not empty and t.sentReceivedVisible %}
<div class="wl-row">
<div class="wl-col title"><h3>{{ 'Sent received'|trans }}</h3></div>
<div class="wl-col list">
<p class="wl-item">
{{ activity.sentReceived|capitalize|trans }}
</p>
</div>
</div>
{% endif %}
{% if activity.user and t.userVisible %}
<div class="wl-row">
<div class="wl-col title"><h3>{{ 'Referrer'|trans }}</h3></div>
<div class="wl-col list">
<p class="wl-item">
{{ activity.user.usernameCanonical|chill_entity_render_string|capitalize }}
</p>
</div>
</div>
{% endif %}
</div>
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
'context': context,
'with_display': 'row',
'with_display': 'wrap-list',
'entity': activity,
'badge_person': true
} %}
<div class="wrap-list">
{%- if activity.reasons is not empty and t.reasonsVisible -%}
<div class="wl-row">
<div class="wl-col title">
<h3>{{ 'Reasons'|trans }}</h3>
</div>
<div class="wl-col list">
{% for r in activity.reasons %}
<p class="wl-item reasons">
{{ r|chill_entity_render_box }}
</p>
{% endfor %}
</div>
</div>
{% endif %}
{%- if activity.socialIssues is not empty and t.socialIssuesVisible -%}
<div class="wl-row">
<div class="wl-col title">
<h3>{{ 'Social issues'|trans }}</h3>
</div>
<div class="wl-col list">
{% for r in activity.socialIssues %}
<p class="wl-item social-issues">
{{ r|chill_entity_render_box }}
</p>
{% endfor %}
</div>
</div>
{% endif %}
{%- if activity.socialActions is not empty and t.socialActionsVisible -%}
<div class="wl-row">
<div class="wl-col title">
<h3>{{ 'Social actions'|trans }}</h3>
</div>
<div class="wl-col list">
{% for r in activity.socialActions %}
<p class="wl-item social-actions">
{{ r|chill_entity_render_box }}
</p>
{% endfor %}
</div>
</div>
{% endif %}
{# SEULEMENT SI DÉTAILLÉ
{% if activity.comment.comment is not empty %}
<div class="wl-row">
<div class="wl-col title">
<h3>{{ 'Comment'|trans }}</h3>
</div>
<div class="wl-col list">
{{ activity.comment|chill_entity_render_box({
'disable_markdown': false,
'limit_lines': 3,
'metadata': false
}) }}
</div>
</div>
{% endif %}
#}
</div>
</div>
<div class="item-row separator">
<div class="updatedBy"></div>
<ul class="record_actions">
{% if context == 'person' and activity.accompanyingPeriod is not empty %}
<li>
<a class="btn btn-primary"
title="{{ 'See activity in accompanying course context'|trans }}"
href="{{ chill_path_add_return_path(
"chill_activity_activity_list",
{ 'accompanying_period_id': activity.accompanyingPeriod.id }
) }}"><i class="fa fa-random fa-fw"></i>
{{ 'Period number %number%'|trans({'%number%': activity.accompanyingPeriod.id}) }}
</a>
</li>
{% else %}
<li>
<a href="{{ path('chill_activity_activity_show', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
class="btn btn-sm btn-show "></a>
class="btn btn-show" title="{{ 'Show'|trans }}"></a>
</li>
{% if no_action is not defined or no_action == false %}
<li>
<a href="{{ path('chill_activity_activity_edit', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
class="btn btn-sm btn-update "></a>
class="btn btn-update" title="{{ 'Edit'|trans }}"></a>
</li>
<li>
<a href="{{ path('chill_activity_activity_delete', { 'id': activity.id, 'person_id' : person_id, 'accompanying_period_id': accompanying_course_id } ) }}"
class="btn btn-sm btn-delete "></a>
class="btn btn-delete" title="{{ 'Delete'|trans }}"></a>
</li>
{% endif %}
{% endif %}
</ul>
</div>

View File

@ -23,8 +23,8 @@
{% if is_granted('CHILL_ACTIVITY_CREATE', accompanyingCourse) %}
<ul class="record_actions sticky-form-buttons">
<li>
<a href="{{ path('chill_activity_activity_new', {'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-create">
{{ 'Create'|trans }}
<a href="{{ path('chill_activity_activity_new', {'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}"
class="btn btn-create">{{ 'Create'|trans }}
</a>
</li>
</ul>

View File

@ -3,10 +3,88 @@
{% set t = activity.type %}
<a href="{{ path('chill_activity_activity_show', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
class="badge-link" title="{{ 'Show the activity'|trans }}">
class="dashboard-link" title="{{ 'Show the activity'|trans }}">
{% include '@ChillActivity/Activity/activity-badge-title.html.twig' %}
<div class="dashboard">
<span class="title_label"></span>
<span class="title_action">
{%- if activity.date -%}
<p class="date-label">{{ activity.date|format_date('short') }}</p>
{%- endif -%}
<span class="like-h3">{{ activity.type.name | localize_translatable_string }}</span>
{% if activity.emergency %}
<span class="badge bg-danger rounded-pill fs-6">{{ 'Emergency'|trans|upper }}</span>
{% endif %}
<ul class="small_in_title mt-3">
{% if activity.sentReceived is not empty and t.sentReceivedVisible %}
<li>
<span class="item-key">{{ 'Sent received'|trans ~ ' : ' }}</span>
<b>{{ activity.sentReceived|capitalize|trans }}</b>
</li>
{% endif %}
{% if activity.location and t.locationVisible %}
<li>
<span class="item-key">{{ 'location'|trans ~ ': ' }}</span>
<b>
<span>{{ activity.location.locationType.title|localize_translatable_string }}</span>
{{ activity.location.name }}
</b>
</li>
{% endif %}
{% if activity.user and t.userVisible %}
<li>
<span class="item-key">{{ 'Referrer'|trans ~ ': ' }}</span>
<b>{{ activity.user.usernameCanonical }}</b>
</li>
{% endif %}
<li class="associated-persons">
<span class="item-key">{{ 'Participants'|trans ~ ' : ' }}</span>
{% for p in activity.personsAssociated %}
<span class="badge-person">{{ p|chill_entity_render_box }}</span>
{% endfor %}
</li>
</ul>
<ul class="list-content my-3">
{%- if t.reasonsVisible -%}
{%- if activity.reasons is not empty -%}
<li class="reasons">
{% for r in activity.reasons %}
{{ r|chill_entity_render_box }}
{% endfor %}
</li>
{%- endif -%}
{% endif %}
{%- if t.socialIssuesVisible %}
{%- if activity.socialIssues is not empty -%}
<li class="social-issues">
{% for r in activity.socialIssues %}
{{ r|chill_entity_render_box }}
{% endfor %}
</li>
{%- endif -%}
{% endif %}
{%- if t.socialActionsVisible -%}
{%- if activity.socialActions is not empty -%}
<li class="social-actions">
{% for r in activity.socialActions %}
{{ r|chill_entity_render_box }}
{% endfor %}
</li>
{%- endif -%}
{% endif %}
</ul>
</span>
</div>
</a>
{% endfor %}
</div>

View File

@ -222,3 +222,5 @@ Aggregate by activity type: Aggréger par type d'activité
Aggregate by activity reason: Aggréger par sujet de l'activité
Last activities: Les dernières activités
See activity in accompanying course context: Voir l'activité dans le contexte du parcours d'accompagnement

View File

@ -11,8 +11,17 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class ChillDocGeneratorBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->registerForAutoconfiguration(DocGeneratorContextInterface::class)
->addTag('chill_docgen.context');
}
}

View File

@ -0,0 +1,58 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Context;
use Chill\DocGeneratorBundle\Context\Exception\ContextNotFoundException;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
class ContextManager
{
/**
* @var DocGeneratorContextInterface[]|iterable
*/
private iterable $contexts;
public function __construct(iterable $contexts)
{
$this->contexts = $contexts;
}
/**
* @throw ContextNotFoundException when the context is not found
*/
public function getContextByDocGeneratorTemplate(DocGeneratorTemplate $docGeneratorTemplate): DocGeneratorContextInterface
{
foreach ($this->contexts as $key => $context) {
if ($docGeneratorTemplate->getContext() === $key) {
return $context;
}
}
throw new ContextNotFoundException($docGeneratorTemplate->getContext());
}
public function getContextByKey(string $searchedKey): DocGeneratorContextInterface
{
foreach ($this->contexts as $key => $context) {
if ($searchedKey === $key) {
return $context;
}
}
throw new ContextNotFoundException($searchedKey);
}
public function getContexts(): array
{
return iterator_to_array($this->contexts);
}
}

View File

@ -11,8 +11,11 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Context;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocStoreBundle\Entity\StoredObject;
/**
* Interface for context for for document generation.
* Interface for context for document generation.
*/
interface DocGeneratorContextInterface
{
@ -21,22 +24,15 @@ interface DocGeneratorContextInterface
*
* @param mixed $entity
*/
public function getData($entity): array;
public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array;
/**
* Generate the form that display.
*
* @param mixed $entity
*/
public function getForm($entity);
public function getDescription(): string;
/**
* has form.
*/
public function hasForm(): bool;
public function getEntityClass(): string;
/**
* True of false which entity supports.
*/
public function supports(string $entityClass): bool;
public static function getKey(): string;
public function getName(): string;
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void;
}

View File

@ -0,0 +1,25 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Context;
use Symfony\Component\Form\FormBuilderInterface;
interface DocGeneratorContextWithAdminFormInterface extends DocGeneratorContextInterface
{
public function adminFormReverseTransform(array $data): array;
public function adminFormTransform(array $data): array;
public function buildAdminForm(FormBuilderInterface $builder): void;
public function hasAdminForm(): bool;
}

View File

@ -0,0 +1,34 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Context;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Symfony\Component\Form\FormBuilderInterface;
interface DocGeneratorContextWithPublicFormInterface extends DocGeneratorContextInterface
{
/**
* Generate the form that display.
*
* @param mixed $entity
*/
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void;
public function getFormData(DocGeneratorTemplate $template, $entity): array;
/**
* has form.
*
* @param mixed $entity
*/
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool;
}

View File

@ -0,0 +1,22 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Context\Exception;
use RuntimeException;
class ContextNotFoundException extends RuntimeException
{
public function __construct($contextName)
{
parent::__construct(sprintf('the context with name %s is not found', $contextName));
}
}

View File

@ -0,0 +1,25 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Context\Exception;
use LogicException;
use function get_class;
use function gettype;
use function is_object;
class UnexpectedTypeException extends LogicException
{
public function __construct($value, string $expectedType)
{
parent::__construct(sprintf('Expected argument of type "%s", "%s" given', $expectedType, is_object($value) ? get_class($value) : gettype($value)));
}
}

View File

@ -1,109 +0,0 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Context;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Exception;
use function count;
use function get_class;
/**
* Context that display a form to select a member of a houseHold.
*/
class HouseholdMemberSelectionContext implements DocGeneratorContextInterface
{
/**
* Get the data that will be injected to the generated document.
*
* @param mixed $entity
*/
public function getData($entity): array
{
$datas = [
'setValues' => [],
'cloneRowAndSetValues' => [],
];
$persons = $entity->getAccompanyingPeriodWork()->getPersons();
if (count($persons) > 0) {
$firstPerson = $persons[0];
$datas['setValues'][] = [
'firstPersonFirstName' => $firstPerson->getFirstName(),
'firstPersonLastName' => $firstPerson->getLastName(), ];
}
if (get_class($entity) === AccompanyingPeriodWorkEvaluation::class) {
$values = [];
foreach ($entity->getAccompanyingPeriodWork()->getPersons() as $person) {
$i = 1;
$values[] = [
'personRowId' => $i,
'personFirstName' => $person->getFirstName(),
'personLastName' => $person->getLastName(),
];
}
$datas['cloneRowAndSetValues'][] = [
'personRowId', $values, ];
}
return $datas;
}
/**
* Generate the form that display.
*
* @param mixed $entity
*/
public function getForm($entity)
{
throw new Exception('No implemented yet', 1);
$choices = [];
if (get_class($entity) === AccompanyingPeriodWorkEvaluation::class) {
foreach ($entity->getAccompanyingPeriodWork()->getPersons() as $person) {
$choices[$person->getId()] = $person->getName();
}
}
$builder->add('members', ChoiceType::class, [
'choices' => $choices,
'placeholder' => 'Choose a person',
'label' => 'Person to add',
]);
return $builder;
}
/**
* has form.
*/
public function hasForm(): bool
{
return true;
}
/**
* True of false which entity supports.
*/
public function supports(string $entityClass): bool
{
return
(AccompanyingPeriod::class === $entityClass)
|| (SocialAction::class === $entityClass);
}
}

View File

@ -11,8 +11,77 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Controller;
use Chill\DocGeneratorBundle\Context\ContextManager;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\MainBundle\CRUD\Controller\CRUDController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class AdminDocGeneratorTemplateController extends CRUDController
{
private ContextManager $contextManager;
public function __construct(ContextManager $contextManager)
{
$this->contextManager = $contextManager;
}
public function generateTemplateParameter(string $action, $entity, Request $request, array $defaultTemplateParameters = [])
{
switch ($action) {
case 'new':
$context = $this->contextManager->getContextByKey($request->get('context'));
// no break
case 'edit':
$context = $this->contextManager->getContextByDocGeneratorTemplate($entity);
return array_merge(
$defaultTemplateParameters,
['context' => $context]
);
case 'index':
return array_merge(
$defaultTemplateParameters,
['contextManager' => $this->contextManager]
);
default:
return parent::generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters); // TODO: Change the autogenerated stub
}
}
public function new(Request $request): Response
{
if (!$request->query->has('context')) {
return $this->redirectToRoute('chill_docgen_admin_template_pick-context');
}
return parent::new($request);
}
/**
* @Route("{_locale}/admin/docgen/template/pick-context", name="chill_docgen_admin_template_pick-context")
*/
public function pickContext(Request $request): Response
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
return $this->render('ChillDocGeneratorBundle:Admin/DocGeneratorTemplate:pick-context.html.twig', [
'contexts' => $this->contextManager->getContexts(),
]);
}
protected function createEntity(string $action, Request $request): object
{
/** @var DocGeneratorTemplate $entity */
$entity = parent::createEntity($action, $request);
$key = $request->query->get('context');
$context = $this->contextManager->getContextByKey($key);
$entity->setContext($key)->setEntity($context->getEntityClass());
return $entity;
}
}

View File

@ -1,76 +0,0 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Controller;
use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlOpenstackGenerator;
use PhpOffice\PhpWord\TemplateProcessor;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\HeaderUtils;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* Class DocGeneratorController.
*/
class DocGeneratorController extends AbstractController
{
/**
* @Route(
* "{_locale}/doc/gen/test",
* name="chill_docgenerator_test"
* )
*/
public function getDoc(Request $request, TempUrlOpenstackGenerator $tempUrlGenerator): Response
{
$p = $tempUrlGenerator->generate(
'GET',
'FORMULAIRE_AEB.docx',
$request->query->has('expires_delay') ? $request->query->getInt('expires_delay', 0) : null
);
$tmpfname = tempnam(sys_get_temp_dir(), 'DOC_TEMPLATE');
file_put_contents($tmpfname, file_get_contents($p->url));
$templateProcessor = new TemplateProcessor($tmpfname);
$templateProcessor->setValues(['firstname' => 'John', 'lastname' => 'Doe']);
$tmpfname2 = tempnam(sys_get_temp_dir(), 'DOC_GENERATED');
$templateProcessor->saveAs($tmpfname2);
unlink($tmpfname);
$fileContent = fopen($tmpfname2, 'rb'); // the generated file content
$response = new Response(fread($fileContent, filesize($tmpfname2)));
$disposition = HeaderUtils::makeDisposition(
HeaderUtils::DISPOSITION_ATTACHMENT,
'foo.docx'
);
$response->headers->set('Content-Disposition', $disposition);
unlink($tmpfname2);
return $response;
}
/**
* @Route(
* "{_locale}/doc/gen/test",
* name="chill_docgenerator_test"
* )
*/
public function testAction(): Response
{
return (new Response())->setContent('Test');
}
}

View File

@ -11,28 +11,73 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Controller;
use Base64Url\Base64Url;
use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface;
use Chill\DocGeneratorBundle\Context\HouseholdMemberSelectionContext;
use Chill\DocGeneratorBundle\Context\ContextManager;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
use Chill\DocGeneratorBundle\Context\Exception\ContextNotFoundException;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocGeneratorBundle\GeneratorDriver\DriverInterface;
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Serializer\Model\Collection;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\TransferException;
use PhpOffice\PhpWord\TemplateProcessor;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
// TODO à mettre dans services
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\Annotation\Route;
class DocGeneratorTemplateController extends AbstractController
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Contracts\HttpClient\HttpClientInterface;
final class DocGeneratorTemplateController extends AbstractController
{
private HttpClientInterface $client;
private ContextManager $contextManager;
private DocGeneratorTemplateRepository $docGeneratorTemplateRepository;
private DriverInterface $driver;
private KernelInterface $kernel;
private LoggerInterface $logger;
private PaginatorFactory $paginatorFactory;
private TempUrlGeneratorInterface $tempUrlGenerator;
public function __construct(
ContextManager $contextManager,
DocGeneratorTemplateRepository $docGeneratorTemplateRepository,
DriverInterface $driver,
LoggerInterface $logger,
PaginatorFactory $paginatorFactory,
TempUrlGeneratorInterface $tempUrlGenerator,
KernelInterface $kernel,
HttpClientInterface $client
) {
$this->contextManager = $contextManager;
$this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository;
$this->driver = $driver;
$this->logger = $logger;
$this->paginatorFactory = $paginatorFactory;
$this->tempUrlGenerator = $tempUrlGenerator;
$this->kernel = $kernel;
$this->client = $client;
}
/**
* @Route(
* "{_locale}/doc/gen/generate/from/{template}/for/{entityClassName}/{entityId}",
@ -40,80 +85,112 @@ class DocGeneratorTemplateController extends AbstractController
* )
*/
public function generateDocFromTemplateAction(
TempUrlGeneratorInterface $tempUrlGenerator,
DocGeneratorTemplate $template,
string $entityClassName,
int $entityId,
Request $request
): Response {
$getUrlGen = $tempUrlGenerator->generate(
try {
$context = $this->contextManager->getContextByDocGeneratorTemplate($template);
} catch (ContextNotFoundException $e) {
throw new NotFoundHttpException($e->getMessage(), $e);
}
$entity = $this->getDoctrine()->getRepository($context->getEntityClass())->find($entityId);
if (null === $entity) {
throw new NotFoundHttpException("Entity with classname {$entityClassName} and id {$entityId} is not found");
}
$contextGenerationData = [];
if ($context instanceof DocGeneratorContextWithPublicFormInterface
&& $context->hasPublicForm($template, $entity)) {
$builder = $this->createFormBuilder($context->getFormData($template, $entity));
$context->buildPublicForm($builder, $template, $entity);
$form = $builder->getForm()->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$contextGenerationData = $form->getData();
} elseif (!$form->isSubmitted() || ($form->isSubmitted() && !$form->isValid())) {
$templatePath = '@ChillDocGenerator/Generator/basic_form.html.twig';
$templateOptions = ['entity' => $entity, 'form' => $form->createView(),
'template' => $template, 'context' => $context, ];
return $this->render($templatePath, $templateOptions);
}
}
$getUrlGen = $this->tempUrlGenerator->generate(
'GET',
$template->getFile()
$template->getFile()->getFilename()
);
$tmpfname = tempnam(sys_get_temp_dir(), 'DOC_TEMPLATE');
file_put_contents($tmpfname, file_get_contents($getUrlGen->url));
$data = $this->client->request('GET', $getUrlGen->url);
$entity = $this->getDoctrine()->getRepository($entityClassName)->find($entityId);
$iv = $template->getFile()->getIv(); // iv as an Array
$ivGoodFormat = pack('C*', ...$iv); // iv as a String (ok for openssl_decrypt)
if ($template->getContext() === HouseholdMemberSelectionContext::class) {
$context = new HouseholdMemberSelectionContext();
$datas = $context->getData($entity);
} else {
throw new Exception('Not implemented', 1);
$method = 'AES-256-CBC';
$key = $template->getFile()->getKeyInfos()['k'];
$keyGoodFormat = Base64Url::decode($key);
$dataDecrypted = openssl_decrypt($data->getContent(), $method, $keyGoodFormat, 1, $ivGoodFormat);
if (false === $dataDecrypted) {
throw new Exception('Error during Decrypt ', 1);
}
$templateProcessor = new TemplateProcessor($tmpfname);
if (false === $templateResource = fopen('php://memory', 'r+b')) {
$this->logger->error('Could not write data to memory');
foreach ($datas['setValues'] as $setValuesConf) {
$templateProcessor->setValues($setValuesConf);
throw new HttpException(500);
}
foreach ($datas['cloneRowAndSetValues'] as $cloneRowAndSetValues) {
$templateProcessor->cloneRowAndSetValues($cloneRowAndSetValues[0], $cloneRowAndSetValues[1]);
}
fwrite($templateResource, $dataDecrypted);
rewind($templateResource);
$tmpfname2 = tempnam(sys_get_temp_dir(), 'DOC_GENERATED');
$templateProcessor->saveAs($tmpfname2);
$datas = $context->getData($template, $entity, $contextGenerationData);
dump('datas compiled', $datas);
unlink($tmpfname);
$generatedResource = $this->driver->generateFromResource($templateResource, $template->getFile()->getType(), $datas, $template->getFile()->getFilename());
$fileContent = fopen($tmpfname2, 'rb'); // the generated file content
fclose($templateResource);
$genDocName = 'doc_' . sprintf('%010d', mt_rand()) . '.docx';
$genDocName = 'doc_' . sprintf('%010d', mt_rand()) . 'odt';
$getUrlGen = $tempUrlGenerator->generate(
$getUrlGen = $this->tempUrlGenerator->generate(
'PUT',
$genDocName
);
unlink($tmpfname2);
$client = new Client();
try {
$putResponse = $client->request('PUT', $getUrlGen->url, [
'body' => $fileContent,
'body' => $generatedResource,
]);
if ($putResponse->getStatusCode() === 201) {
$em = $this->getDoctrine()->getManager();
$storedObject = new StoredObject();
$storedObject
// currently, only docx is supported
->setType('application/vnd.openxmlformats-officedocument.wordprocessingml.document')
->setType($template->getFile()->getType())
->setFilename($genDocName);
$em->persist($storedObject);
// Only for evaluation
if ($entity instanceof AccompanyingPeriodWorkEvaluation) {
$doc = new AccompanyingPeriodWorkEvaluationDocument();
$doc
->setStoredObject($storedObject)
->setTemplate($template);
$entity->addDocument($doc);
$em->persist($doc);
try {
$context->storeGenerated($template, $storedObject, $entity, $contextGenerationData);
} catch (Exception $e) {
$this->logger->error('Could not store the associated document to entity', [
'entityClassName' => $entityClassName,
'entityId' => $entityId,
'contextKey' => $context->getName(),
]);
throw $e;
}
$em->flush();
@ -132,26 +209,25 @@ class DocGeneratorTemplateController extends AbstractController
/**
* @Route(
* "{_locale}/doc/gen/templates/for/{entityClassName}",
* "/api/1.0/docgen/templates/by-entity/{entityClassName}",
* name="chill_docgenerator_templates_for_entity_api"
* )
*/
public function listTemplateApiAction(
string $entityClassName,
DocGeneratorTemplateRepository $templateRepository
): Response {
$entities = $templateRepository->findByEntity($entityClassName);
public function listTemplateApiAction(string $entityClassName): Response
{
$nb = $this->docGeneratorTemplateRepository->countByEntity($entityClassName);
$paginator = $this->paginatorFactory->create($nb);
$entities = $this->docGeneratorTemplateRepository->findByEntity(
$entityClassName,
$paginator->getCurrentPageFirstItemNumber(),
$paginator->getItemsPerPage()
);
$ret = [];
foreach ($entities as $entity) {
$ret[] = [
'id' => $entity->getId(),
'name' => $entity->getName(),
'description' => $entity->getDescription(),
];
}
return new JsonResponse(['results' => $ret]);
return $this->json(
new Collection($entities, $paginator),
Response::HTTP_OK,
[],
[AbstractNormalizer::GROUPS => ['read']]
);
}
}

View File

@ -12,9 +12,10 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle\DataFixtures\ORM;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use DateTime;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Persistence\ObjectManager;
/**
@ -28,13 +29,23 @@ class LoadDocGeneratorTemplate extends AbstractFixture
[
'name' => ['fr' => 'FORMULAIRE AEB'],
'desc' => 'stocké sur openstack comedienbe',
'file' => 'FORMULAIRE_AEB_WITH_DATA_INJ.docx',
'file' => [
'filename' => 'pKNlhCrQDCRsAuC8vYHDKa',
'key' => '{"alg":"A256CBC","ext":true,"k":"_VihnD41-VDHlpS-ouwtbMPnu-OXVdtA7ENQWWtAQYM","key_ops":["encrypt","decrypt"],"kty":"oct"}',
'iv' => '[86,231,83,148,117,107,149,173,130,19,105,194,224,145,8,48]',
'type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
],
'context' => 'Chill\DocGeneratorBundle\Context\HouseholdMemberSelectionContext',
'entities' => [AccompanyingPeriodWorkEvaluation::class],
], [
'name' => ['fr' => 'AIDE ALIMENTAIRE'],
'desc' => 'stocké sur openstack comedienbe',
'file' => 'AIDE_ALIMENTAIRE.docx',
'file' => [
'filename' => 'pKNlhCrQDCRsAuC8vYHDKa',
'key' => '{"alg":"A256CBC","ext":true,"k":"_VihnD41-VDHlpS-ouwtbMPnu-OXVdtA7ENQWWtAQYM","key_ops":["encrypt","decrypt"],"kty":"oct"}',
'iv' => '[86,231,83,148,117,107,149,173,130,19,105,194,224,145,8,48]',
'type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
],
'context' => 'Chill\DocGeneratorBundle\Context\HouseholdMemberSelectionContext',
'entities' => ['Chill\PersonBundle\Entity\AccompanyingPeriod', 'Chill\PersonBundle\Entity\SocialWork\SocialAction', AccompanyingPeriodWorkEvaluation::class],
],
@ -43,10 +54,19 @@ class LoadDocGeneratorTemplate extends AbstractFixture
foreach ($templates as $template) {
echo 'Adding doc generator templates ' . $template['file'] . ")\n";
$newStoredObj = (new StoredObject())
->setFilename($template['file']['filename'])
->setKeyInfos(json_decode($template['file']['key'], true))
->setIv(json_decode($template['file']['iv'], true))
->setCreationDate(new DateTime('today'))
->setType($template['file']['type']);
$manager->persist($newStoredObj);
$newTemplate = (new DocGeneratorTemplate())
->setName($template['name'])
->setDescription($template['desc'])
->setFile($template['file'])
->setFile($newStoredObj)
->setContext($template['context'])
->setEntities($template['entities']);

View File

@ -26,6 +26,10 @@ class ChillDocGeneratorExtension extends Extension implements PrependExtensionIn
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('chill_doc_generator', $config);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../config'));
$loader->load('services.yaml');
$loader->load('services/controller.yaml');
@ -37,6 +41,7 @@ class ChillDocGeneratorExtension extends Extension implements PrependExtensionIn
{
$this->preprendRoutes($container);
$this->prependCruds($container);
$this->prependClientConfig($container);
}
protected function prependCruds(ContainerBuilder $container)
@ -78,4 +83,23 @@ class ChillDocGeneratorExtension extends Extension implements PrependExtensionIn
],
]);
}
private function prependClientConfig(ContainerBuilder $container)
{
$configs = $container->getExtensionConfig($this->getAlias());
$resolvingBag = $container->getParameterBag();
$configs = $resolvingBag->resolveValue($configs);
$config = $this->processConfiguration(new Configuration(), $configs);
$container->prependExtensionConfig('framework', [
'http_client' => [
'scoped_clients' => [
'relatorio.client' => [
'scope' => $config['driver']['relatorio']['url'],
],
],
],
]);
}
}

View File

@ -14,21 +14,35 @@ namespace Chill\DocGeneratorBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* This is the class that validates and merges configuration from your app/config files.
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/configuration.html}
*/
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('chill_calendar');
$rootNode = $treeBuilder->getRootNode('chill_calendar');
$treeBuilder = new TreeBuilder('chill_doc_generator');
$rootNode = $treeBuilder->getRootNode();
// Here you should define the parameters that are allowed to
// configure your bundle. See the documentation linked above for
// more information on that topic.
$rootNode
->children()
->arrayNode('driver')
->addDefaultsIfNotSet()
->children()
->enumNode('type')
->isRequired()
->values(['relatorio'])
->defaultValue('relatorio')
->end()
->arrayNode('relatorio')
->addDefaultsIfNotSet()
->children()
->scalarNode('url')
->isRequired()
->defaultValue('http://relatorio:8888/')
->end()
->end()
->end()
->end()
->end()
->end();
return $treeBuilder;
}

View File

@ -11,44 +11,51 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Entity;
use Chill\DocStoreBundle\Entity\StoredObject;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation as Serializer;
/**
* @ORM\Entity
* @ORM\Table(name="chill_docgen_template")
* @Serializer\DiscriminatorMap(typeProperty="type", mapping={
* "docgen_template": DocGeneratorTemplate::class
* })
*/
class DocGeneratorTemplate
{
/**
* @ORM\Column(type="string", length=255)
*
* Class name of the context to use
* @ORM\Column(type="boolean", options={"default": true})
*/
private bool $active = true;
/**
* Class name of the context to use.
*
* so if $context = ''
* this template will use '' as context
*
* @ORM\Column(type="string", length=255)
*/
private string $context;
/**
* @ORM\Column(type="text", nullable=true)
* @Serializer\Groups({"read"})
*/
private string $description;
private ?string $description = null;
/**
* @ORM\Column(type="simple_array")
* Class name of the entity for which this template can be used.
*
* Class name of the entities for which this template can be used
*
* so if $entities = ['Chill\PersonBundle\Entity\AccompanyingPeriod', 'Chill\PersonBundle\Entity\SocialWork\SocialAction']
* this template can be selected for an AccompanyingPeriod or a SocialAction
* @ORM\Column(type="string", options={"default": ""})
*/
private array $entities = [];
private string $entity = '';
/**
* @ORM\Column(type="string", length=255)
* @ORM\ManyToOne(targetEntity=StoredObject::class, cascade={"persist"}))
*/
private string $file;
private ?StoredObject $file = null;
/**
* @ORM\Id
@ -64,6 +71,13 @@ class DocGeneratorTemplate
*/
private array $name = [];
/**
* Options for the template.
*
* @ORM\Column(type="json", name="template_options", options={"default":"[]"})
*/
private array $options = [];
public function getContext(): ?string
{
return $this->context;
@ -74,12 +88,12 @@ class DocGeneratorTemplate
return $this->description;
}
public function getEntities(): ?array
public function getEntity(): string
{
return $this->entities;
return $this->entity;
}
public function getFile(): ?string
public function getFile(): ?StoredObject
{
return $this->file;
}
@ -94,6 +108,23 @@ class DocGeneratorTemplate
return $this->name;
}
public function getOptions(): array
{
return $this->options;
}
public function isActive(): bool
{
return $this->active;
}
public function setActive(bool $active): DocGeneratorTemplate
{
$this->active = $active;
return $this;
}
public function setContext(string $context): self
{
$this->context = $context;
@ -108,14 +139,14 @@ class DocGeneratorTemplate
return $this;
}
public function setEntities(array $entities): self
public function setEntity(string $entity): self
{
$this->entities = $entities;
$this->entity = $entity;
return $this;
}
public function setFile(string $file): self
public function setFile(StoredObject $file): self
{
$this->file = $file;
@ -128,4 +159,11 @@ class DocGeneratorTemplate
return $this;
}
public function setOptions(array $options): self
{
$this->options = $options;
return $this;
}
}

View File

@ -11,22 +11,64 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Form;
use Chill\DocGeneratorBundle\Context\ContextManager;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocStoreBundle\Form\StoredObjectType;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
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\OptionsResolver\OptionsResolver;
class DocGeneratorTemplateType extends AbstractType
{
private ContextManager $contextManager;
public function __construct(ContextManager $contextManager)
{
$this->contextManager = $contextManager;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** @var DocGeneratorTemplate $template */
$template = $options['data'];
$context = $this->contextManager->getContextByKey($template->getContext());
$builder
->add('name', TranslatableStringFormType::class, [
'label' => 'Nom',
])
->add('description')
->add('file');
->add('file', StoredObjectType::class, [
'error_bubbling' => true,
])
->add('active', ChoiceType::class, [
'label' => 'Active',
'choices' => [
'Yes' => true,
'No' => false,
],
'required' => true,
]);
if ($context instanceof DocGeneratorContextWithAdminFormInterface
&& $context->hasAdminForm()) {
$sub = $builder
->create('options', null, ['compound' => true])
->addModelTransformer(new CallbackTransformer(
static function (array $data) use ($context) {
return $context->adminFormTransform($data);
},
static function (array $data) use ($context) {
return $context->adminFormReverseTransform($data);
}
));
$context->buildAdminForm($sub);
$builder->add($sub);
}
}
public function configureOptions(OptionsResolver $resolver)

View File

@ -0,0 +1,22 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\DocGeneratorBundle\GeneratorDriver;
interface DriverInterface
{
/**
* @param resource $template
*
* @return resource
*/
public function generateFromResource($template, string $resourceType, array $data, ?string $templateName = null);
}

View File

@ -0,0 +1,80 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\DocGeneratorBundle\GeneratorDriver;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Mime\Part\DataPart;
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class RelatorioDriver implements DriverInterface
{
private LoggerInterface $logger;
private HttpClientInterface $relatorioClient;
private string $url;
public function __construct(
HttpClientInterface $relatorioClient,
ParameterBagInterface $parameterBag,
LoggerInterface $logger
) {
$this->relatorioClient = $relatorioClient;
$this->logger = $logger;
$this->url = $parameterBag->get('chill_doc_generator')['driver']['relatorio']['url'];
}
public function generateFromResource($template, string $resourceType, array $data, ?string $templateName = null)
{
$formFields = [
'variables' => json_encode($data),
'template' => new DataPart($template, $templateName ?? uniqid('template_'), $resourceType),
];
$form = new FormDataPart($formFields);
try {
$response = $this->relatorioClient->request('POST', $this->url, [
'headers' => $form->getPreparedHeaders()->toArray(),
'body' => $form->bodyToIterable(),
]);
return $response->toStream();
} catch (HttpExceptionInterface $e) {
$this->logger->error('relatorio: error while generating document', [
'msg' => $e->getMessage(),
'response' => $e->getResponse()->getStatusCode(),
'content' => $e->getResponse()->getContent(false),
]);
throw $e;
} catch (TransportExceptionInterface $e) {
$this->logger->error('relatorio: transport exception', [
'msg' => $e->getMessage(),
'e' => $e->getTraceAsString(),
]);
throw $e;
} catch (DecodingExceptionInterface $e) {
$this->logger->error('relatorio: could not decode response', [
'msg' => $e->getMessage(),
'e' => $e->getTraceAsString(),
]);
throw $e;
}
}
}

View File

@ -0,0 +1,50 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Menu;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
use function in_array;
class AdminMenuBuilder implements LocalMenuBuilderInterface
{
private Security $security;
private TranslatorInterface $translator;
public function __construct(TranslatorInterface $translator, Security $security)
{
$this->translator = $translator;
$this->security = $security;
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
if ($this->security->isGranted('ROLE_ADMIN')) {
if (in_array($menuId, ['admin_index', 'admin_section'], true)) {
$menu->addChild($this->translator->trans('docgen.Document generation'), [
'route' => 'chill_crud_docgen_template_index',
])->setExtras([
'order' => 350,
'explain' => 'docgen.Manage templates and document generation',
]);
}
}
}
public static function getMenuIds(): array
{
return ['admin_index', 'admin_section', 'docgen_admin'];
}
}

View File

@ -25,6 +25,18 @@ final class DocGeneratorTemplateRepository implements ObjectRepository
$this->repository = $entityManager->getRepository(DocGeneratorTemplate::class);
}
public function countByEntity(string $entity): int
{
$builder = $this->repository->createQueryBuilder('t');
$builder
->select('count(t)')
->where('t.entity LIKE :entity')
->setParameter('entity', addslashes($entity));
return $builder->getQuery()->getSingleScalarResult();
}
public function find($id, $lockMode = null, $lockVersion = null): ?DocGeneratorTemplate
{
return $this->repository->find($id, $lockMode, $lockVersion);
@ -49,15 +61,22 @@ final class DocGeneratorTemplateRepository implements ObjectRepository
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
}
public function findByEntity($entity)
/**
* @return array|DocGeneratorTemplate[]
*/
public function findByEntity(string $entity, ?int $start = 0, ?int $limit = 50): array
{
$builder = $this->repository->createQueryBuilder('t');
$builder
->where('t.entities LIKE :entity')
->setParameter('entity', '%' . addslashes($entity) . '%');
->where('t.entity LIKE :entity')
->setParameter('entity', addslashes($entity));
return $builder->getQuery()->execute();
return $builder
->getQuery()
->setFirstResult($start)
->setMaxResults($limit)
->getResult();
}
public function findOneBy(array $criteria, ?array $orderBy = null): ?DocGeneratorTemplate

View File

@ -0,0 +1,10 @@
import { fetchResults } from "ChillMainAssets/lib/api/apiMethods.js";
const fetchTemplates = (entityClass) => {
let fqdnEntityClass = encodeURI(entityClass);
return fetchResults(`/api/1.0/docgen/templates/by-entity/${fqdnEntityClass}`);
}
export {
fetchTemplates
};

View File

@ -0,0 +1,27 @@
import {createApp} from 'vue';
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
import {fetchTemplates} from 'ChillDocGeneratorAssets/api/pickTemplate.js';
import {_createI18n} from 'ChillMainAssets/vuejs/_js/i18n';
const i18n = _createI18n({});
document.querySelectorAll('div[data-docgen-template-picker]').forEach(el => {
fetchTemplates(el.dataset.entityClass).then(templates => {
let
picker = {
template: '<pick-template :templates="this.templates" :entityId="this.entityId"></pick-template>',
components: {
PickTemplate,
},
data() {
return {
templates: templates,
entityId: el.dataset.entityId,
}
},
}
;
createApp(picker).use(i18n).mount(el);
})
});

View File

@ -0,0 +1,141 @@
<template>
<div>
<template v-if="templates.length > 0">
<slot name="title">
<h2>{{ $t('generate_document')}}</h2>
</slot>
<div class="container">
<div class="row">
<div class="col-md-4">
<slot name="label">
<label>{{ $t('select_a_template') }}</label>
</slot>
</div>
<div class="col-md-8">
<div class="input-group mb-3">
<select class="form-select" v-model="template">
<option disabled selected value="">{{ $t('choose_a_template') }}</option>
<template v-for="t in templates">
<option v-bind:value="t.id">{{ t.name.fr }}</option>
</template>
</select>
<button v-if="canGenerate" class="btn btn-update btn-sm change-icon" type="button" @click="generateDocument"><i class="fa fa-fw fa-cog"></i></button>
<button v-else class="btn btn-update btn-sm change-icon" type="button" disabled ><i class="fa fa-fw fa-cog"></i></button>
</div>
</div>
</div>
<div class="row" v-if="hasDescription">
<div class="col-md-8 align-self-end">
<p>{{ getDescription }}</p>
</div>
</div>
</div>
</template>
</div>
</template>
<script>
export default {
name: "PickTemplate",
props: {
entityId: [String, Number],
entityClass: {
type: String,
required: true,
},
templates: {
required: true,
},
// beforeMove execute "something" before
beforeMove: {
type: Function,
required: false,
}
},
data() {
return {
template: null,
}
},
computed: {
canGenerate() {
return this.template != null;
},
hasDescription() {
if (this.template == null) {
return false;
}
return true;
},
getDescription() {
return this.templates.find(t => t.id === this.template).description || '';
},
},
methods: {
generateDocument() {
console.log('generateDocument');
console.log('beforeMove', this.beforeMove);
if (this.beforeMove != null) {
console.log('execute before move');
let r = this.beforeMove();
if (r instanceof Promise) {
r.then((obj) => this.goToGenerate(obj));
} else {
this.goToGenerate();
}
} else {
this.goToGenerate();
}
},
goToGenerate(obj) {
console.log('goToGenerate');
console.log('obj', obj);
console.log('entityId', this.entityId);
let
realId = this.entityId,
realEntityClass = this.entityClass
;
if (obj !== undefined) {
if (obj.entityId !== undefined) {
realId = obj.entityId;
}
if (obj.entityClass !== undefined) {
realEntityClass = obj.entityClass;
}
}
console.log('realId', realId);
console.log('realEntityClass', realEntityClass);
const
entityId = encodeURI(realId),
returnPath = encodeURIComponent(window.location.pathname + window.location.search + window.location.hash),
fqdnEntityClass = encodeURI(realEntityClass),
url = `/fr/doc/gen/generate/from/${this.template}/for/${fqdnEntityClass}/${entityId}?returnPath=${returnPath}`
;
console.log('I will generate your doc at', url);
window.location.assign(url);
},
},
i18n: {
messages: {
fr: {
generate_document: 'Générer un document',
select_a_template: 'Choisir un gabarit',
choose_a_template: 'Choisir',
}
}
}
}
</script>
<style scoped>
</style>

View File

@ -1,12 +1,25 @@
{% extends '@ChillPerson/Admin/layout.html.twig' %}
{% extends '@ChillDocGenerator/Admin/layout.html.twig' %}
{% block title %}
{% include('@ChillMain/CRUD/_edit_title.html.twig') %}
{% endblock %}
{% block title 'docgen.Edit template'|trans %}
{% block layout_wvm_content %}
{% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
{% block crud_content_header %}
<h1>{{ 'docgen.Edit template'|trans }}</h1>
<h2>{{ 'docgen.With context %name%'|trans({'%name%': context.name|trans }) }}</h2>
{% endblock crud_content_header %}
{% block content_form_actions_view %}{% endblock %}
{% block content_form_actions_save_and_show %}{% endblock %}
{% endembed %}
{% endblock %}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }}
{% endblock %}
{% block css %}
{{ encore_entry_link_tags('mod_async_upload') }}
{% endblock %}

View File

@ -1,11 +1,12 @@
{% extends '@ChillPerson/Admin/layout.html.twig' %}
{% extends '@ChillDocGenerator/Admin/layout.html.twig' %}
{% block layout_wvm_content %}
{% embed '@ChillMain/CRUD/_index.html.twig' %}
{% block table_entities_thead_tr %}
<th></th>
<th>{{ 'Title'|trans }}</th>
<th>{{ 'File'|trans }}</th>
<th>{{ 'docgen.Context'|trans }}</th>
<th>{{ 'Edit'|trans }}</th>
{% endblock %}
{% block table_entities_tbody %}
@ -13,7 +14,12 @@
<tr>
<td>{{ entity.id }}</td>
<td>{{ entity.name | localize_translatable_string }}</td>
<td>{{ entity.file }}</td>
<td>{{ contextManager.getContextByKey(entity.context).name|trans }}</td>
<td>
<a href="{{ chill_path_add_return_path('chill_crud_docgen_template_edit', { 'id': entity.id }) }}" class="btn btn-edit">
{{ 'Edit'|trans }}
</a>
</td>
</tr>
{% endfor %}
{% endblock %}

View File

@ -1,11 +1,25 @@
{% extends '@ChillPerson/Admin/layout.html.twig' %}
{% extends '@ChillDocGenerator/Admin/layout.html.twig' %}
{% block title %}
{% include('@ChillMain/CRUD/_new_title.html.twig') %}
{% endblock %}
{% block title 'docgen.New template'|trans %}
{% block layout_wvm_content %}
{% embed '@ChillMain/CRUD/_new_content.html.twig' %}
{% block crud_content_header %}
<h1>{{ 'docgen.New template'|trans }}</h1>
<h2>{{ 'docgen.With context %name%'|trans({'%name%': context.name|trans }) }}</h2>
{% endblock crud_content_header %}
{% block content_form_actions_save_and_show %}{% endblock %}
{% endembed %}
{% endblock %}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }}
{% endblock %}
{% block css %}
{{ encore_entry_link_tags('mod_async_upload') }}
{% endblock %}

View File

@ -0,0 +1,27 @@
{% extends '@ChillDocGenerator/Admin/layout.html.twig' %}
{% block title 'docgen.Pick template context'|trans %}
{% block layout_wvm_content %}
<div class="col-md-10 col-xxl">
<h1>{{ block('title') }}</h1>
<div class="container">
{% for key, context in contexts %}
<div class="row">
<div class="col-md-4">
<a
href="{{ path('chill_crud_docgen_template_new', { 'context': key }) }}"
class="btn btn-outline-chill-green-dark">
{{ context.name|trans }}
</a>
</div>
<div class="col-md-8">
{{ context.description|trans|nl2br }}
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,4 @@
{% extends '@ChillPerson/Admin/layout.html.twig' %}
{% block vertical_menu_content %}
{% endblock %}

View File

@ -0,0 +1,22 @@
{% extends 'ChillMainBundle::layout.html.twig' %}
{% block title 'docgen.Generate a document'|trans %}
{% block content %}
<div class="col-md-10 col-xxl">
<h1>{{ block('title') }}</h1>
<h2>{{ template.name|localize_translatable_string }}</h2>
{{ form_start(form, { 'attr': { 'id': 'generate_doc_form' }}) }}
{{ form(form) }}
{{ form_end(form) }}
<ul class="record_actions sticky-form-buttons">
<li>
<button type="submit" class="btn btn-edit change-icon" form="generate_doc_form">
<i class="fa fa-cog"></i> {{ 'docgen.Generate'|trans }}
</button>
</li>
</ul>
</div>
{% endblock %}

View File

@ -86,19 +86,52 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
private function getExpectedType(AttributeMetadata $attribute, ReflectionClass $reflection): string
{
$type = null;
do {
// we have to get the expected content
if ($reflection->hasProperty($attribute->getName())) {
$type = $reflection->getProperty($attribute->getName())->getType();
} elseif ($reflection->hasMethod($attribute->getName())) {
$type = $reflection->getMethod($attribute->getName())->getReturnType();
} else {
if (!$reflection->getProperty($attribute->getName())->hasType()) {
throw new \LogicException(sprintf(
'Could not determine how the content is determined for the attribute %s. Add attribute property only on property or method',
'Could not determine how the content is determined for the attribute %s. Add a type on this property',
$attribute->getName()
));
}
if (null === $type) {
$type = $reflection->getProperty($attribute->getName())->getType();
} elseif ($reflection->hasMethod($method = 'get' . ucfirst($attribute->getName()))) {
if (!$reflection->getMethod($method)->hasReturnType()) {
throw new \LogicException(sprintf(
'Could not determine how the content is determined for the attribute %s. Add a return type on the method',
$attribute->getName()
));
}
$type = $reflection->getMethod($method)->getReturnType();
} elseif ($reflection->hasMethod($method = 'is' . ucfirst($attribute->getName()))) {
if (!$reflection->getMethod($method)->hasReturnType()) {
throw new \LogicException(sprintf(
'Could not determine how the content is determined for the attribute %s. Add a return type on the method',
$attribute->getName()
));
}
$type = $reflection->getMethod($method)->getReturnType();
} elseif ($reflection->hasMethod($attribute->getName())) {
if (!$reflection->getMethod($attribute->getName())->hasReturnType()) {
throw new \LogicException(sprintf(
'Could not determine how the content is determined for the attribute %s. Add a return type on the method',
$attribute->getName()
));
}
$type = $reflection->getMethod($attribute->getName())->getReturnType();
} else {
$reflection = $reflection->getParentClass();
}
} while (null === $type && $reflection instanceof ReflectionClass);
if (null === $type ?? null) {
throw new \LogicException(sprintf(
'Could not determine the type for this attribute: %s. Add a return type to the method or property declaration',
$attribute->getName()
@ -134,11 +167,15 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
switch ($type) {
case 'array':
return [];
case 'bool':
case 'double':
case 'float':
case 'int':
case 'resource':
return null;
case 'string':
return '';
@ -173,7 +210,18 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
$value = $this->propertyAccess->getValue($object, $attribute->getName());
$key = $attribute->getSerializedName() ?? $attribute->getName();
if (is_object($value)) {
if (is_iterable($value)) {
$arr = [];
foreach ($value as $k => $v) {
$arr[$k] =
$this->normalizer->normalize($v, $format, array_merge(
$context,
$attribute->getNormalizationContextForGroups($expectedGroups)
));
}
$data[$key] = $arr;
} elseif (is_object($value)) {
$data[$key] =
$this->normalizer->normalize($value, $format, array_merge(
$context,
@ -182,7 +230,7 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
} elseif (null === $value) {
$data[$key] = $this->normalizeNullOutputValue($format, $context, $attribute, $reflection);
} else {
$data[$key] = (string) $value;
$data[$key] = $value;
}
}

View File

@ -3,4 +3,6 @@ module.exports = function(encore, entries) {
encore.addAliases({
ChillDocGeneratorAssets: __dirname + '/Resources/public'
});
encore.addEntry('mod_docgen_picktemplate', __dirname + '/Resources/public/module/PickTemplate/index.js');
};

View File

@ -9,9 +9,35 @@ services:
autoconfigure: true
resource: '../Repository/'
Chill\DocGeneratorBundle\Menu\:
autoconfigure: true
autowire: true
resource: '../Menu/'
Chill\DocGeneratorBundle\Serializer\Normalizer\:
autowire: true
autoconfigure: true
resource: '../Serializer/Normalizer/'
tags:
- { name: 'serializer.normalizer', priority: -152 }
Chill\DocGeneratorBundle\Controller\:
resource: "../Controller"
autowire: true
autoconfigure: true
Chill\DocGeneratorBundle\Form\:
resource: "../Form/"
autowire: true
autoconfigure: true
Chill\DocGeneratorBundle\GeneratorDriver\:
resource: "../GeneratorDriver/"
autowire: true
autoconfigure: true
Chill\DocGeneratorBundle\Driver\RelatorioDriver: '@Chill\DocGeneratorBundle\Driver\DriverInterface'
Chill\DocGeneratorBundle\Context\ContextManager:
arguments:
$contexts: !tagged_iterator { tag: chill_docgen.context, default_index_method: getKey }

View File

@ -0,0 +1,46 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\Migrations\DocGenerator;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Using DocStore objects inside the DocGenTemplate.
*/
final class Version20211103111010 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_docgen_template DROP CONSTRAINT FK_49A347E893CB796C');
$this->addSql('DROP INDEX IDX_49A347E893CB796C');
$this->addSql('ALTER TABLE chill_docgen_template ADD file VARCHAR(255) NOT NULL');
$this->addSql('ALTER TABLE chill_docgen_template DROP file_id');
$this->addSql('ALTER TABLE chill_docgen_template ALTER entities DROP NOT NULL');
$this->addSql('ALTER TABLE chill_docgen_template ALTER context DROP NOT NULL');
}
public function getDescription(): string
{
return 'Using DocStore objects inside the DocGenTemplate';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_docgen_template ADD file_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_docgen_template DROP file');
$this->addSql('ALTER TABLE chill_docgen_template ALTER entities SET NOT NULL');
$this->addSql('ALTER TABLE chill_docgen_template ALTER context SET NOT NULL');
$this->addSql('ALTER TABLE chill_docgen_template ADD CONSTRAINT FK_49A347E893CB796C FOREIGN KEY (file_id) REFERENCES chill_doc.stored_object (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_49A347E893CB796C ON chill_docgen_template (file_id)');
}
}

View File

@ -0,0 +1,41 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\Migrations\DocGenerator;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20211201191757 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_docgen_template DROP active');
$this->addSql('ALTER TABLE chill_docgen_template DROP entity');
$this->addSql('ALTER TABLE chill_docgen_template DROP template_options');
$this->addSql('ALTER TABLE chill_docgen_template ADD entities TEXT');
$this->addSql('COMMENT ON COLUMN chill_docgen_template.entities IS \'(DC2Type:simple_array)\'');
}
public function getDescription(): string
{
return 'Add options, active and link to entity in docgen_template';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_docgen_template ADD active BOOLEAN DEFAULT true NOT NULL');
$this->addSql('ALTER TABLE chill_docgen_template ADD entity VARCHAR(255) NOT NULL DEFAULT \'\'');
$this->addSql('ALTER TABLE chill_docgen_template ADD template_options JSONB NOT NULL DEFAULT \'[]\'::jsonb ');
$this->addSql('COMMENT ON COLUMN chill_docgen_template.template_options IS \'(DC2Type:json)\'');
$this->addSql('ALTER TABLE chill_docgen_template DROP entities');
}
}

View File

@ -0,0 +1,19 @@
docgen:
Generate a document: Génerer un document
Generate: Génerer
Document generation: Génération de documents
Manage templates and document generation: Gestion des documents générés et de leurs gabarits
Pick template context: Choisir un contexte
Context: Contexte
New template: Nouveau gabarit
Edit template: Modifier gabarit
With context: 'Avec le contexte :'
crud:
docgen_template:
index:
title: Génération de documents
add_new: Créer

View File

@ -15,7 +15,7 @@ use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table("chill_doc.document_category")
* @ORM\Entity(repositoryClass="Chill\DocStoreBundle\EntityRepository\DocumentCategoryRepository")
* @ORM\Entity
*/
class DocumentCategory
{

View File

@ -1,31 +0,0 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\DocStoreBundle\EntityRepository;
use Doctrine\ORM\EntityRepository;
/**
* Get an available idInsideBUndle.
*/
class DocumentCategoryRepository extends EntityRepository
{
public function nextIdInsideBundle()
{
$array_res = $this->getEntityManager()
->createQuery(
'SELECT MAX(c.idInsideBundle) + 1 FROM ChillDocStoreBundle:DocumentCategory c'
)
->getSingleResult();
return reset($array_res);
}
}

View File

@ -0,0 +1,75 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\DocStoreBundle\Repository;
use Chill\DocStoreBundle\Entity\DocumentCategory;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ObjectRepository;
/**
* Get an available idInsideBUndle.
*/
class DocumentCategoryRepository implements ObjectRepository
{
private EntityManagerInterface $em;
private EntityRepository $repository;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->repository = $em->getRepository(DocumentCategory::class);
}
/**
* @param mixed $id
*/
public function find($id): ?DocumentCategory
{
return $this->repository->find($id);
}
/**
* @return array|DocumentCategory[]
*/
public function findAll(): array
{
return $this->repository->findAll();
}
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null)
{
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
}
public function findOneBy(array $criteria): ?DocumentCategory
{
return $this->findOneBy($criteria);
}
public function getClassName()
{
return DocumentCategory::class;
}
public function nextIdInsideBundle()
{
$array_res = $this->em
->createQuery(
'SELECT MAX(c.idInsideBundle) + 1 FROM ChillDocStoreBundle:DocumentCategory c'
)
->getSingleResult();
return reset($array_res);
}
}

View File

@ -34,6 +34,9 @@ var download = (button) => {
key, url
;
console.log('keyData', keyData);
console.log('ivData', ivData);
button.textContent = labelPreparing;
window.fetch(urlGenerator)
@ -47,25 +50,18 @@ var download = (button) => {
.then(data => {
url = data.url;
if (keyData.length > 0) {
return window.crypto.subtle.importKey('jwk', keyData, { name: algo, iv: iv}, false, ['decrypt']);
})
.catch(e => {
console.error("error while importing key");
console.error(e);
button.appendChild(document.createTextNode(decryptError));
}
return Promise.resolve(undefined);
})
.then(nKey => {
key = nKey;
return window.fetch(url);
})
.catch(e => {
console.error("error while fetching data");
console.error(e);
button.textContent = "";
button.appendChild(document.createTextNode(fetchError));
})
.then(r => {
console.log('r', r);
if (r.ok) {
return r.arrayBuffer();
} else {
@ -73,15 +69,15 @@ var download = (button) => {
}
})
.then(buffer => {
console.log('buffer', buffer);
if (keyData.length > 0) {
return window.crypto.subtle.decrypt({ name: algo, iv: iv }, key, buffer);
})
.catch(e => {
console.error("error while importing key");
console.error(e);
button.textContent = "";
button.appendChild(document.createTextNode(decryptError));
}
return Promise.resolve(buffer);
})
.then(decrypted => {
console.log('decrypted', decrypted);
var
blob = new Blob([decrypted], { type: mimeType }),
url = window.URL.createObjectURL(blob)

View File

@ -11,6 +11,13 @@
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }}
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }}
{{ encore_entry_link_tags('mod_docgen_picktemplate') }}
{% endblock %}
{% block content %}
@ -29,7 +36,7 @@
{% for document in documents %}
<tr>
<td>{{ document.title }}</td>
<td>{{ document.category.name|localize_translatable_string }}</td>
<td>{% if document.category %}{{ document.category.name|localize_translatable_string }}{% endif %}</td>
<td>
<ul class="record_actions">
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
@ -58,6 +65,8 @@
</tbody>
</table>
<div data-docgen-template-picker="data-docgen-template-picker" data-entity-class="Chill\PersonBundle\Entity\AccompanyingPeriod" data-entity-id="{{ accompanyingCourse.id }}"></div>
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_CREATE', accompanyingCourse) %}
<ul class="record_actions">
<li class="create">

View File

@ -22,8 +22,10 @@
<dt>{{ 'Title'|trans }}</dt>
<dd>{{ document.title }}</dd>
{% if document.category is not null %}
<dt>{{ 'Category'|trans }}</dt>
<dd>{{ document.category.name|localize_translatable_string }}</dd>
{% endif %}
<dt>{{ 'Description' | trans }}</dt>
<dd>

View File

@ -33,7 +33,7 @@ class Scope
* @ORM\Id
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
*/
private $id;
@ -43,7 +43,7 @@ class Scope
* @var array
*
* @ORM\Column(type="json")
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
*/
private $name = [];

View File

@ -204,7 +204,7 @@ class PhonenumberHelper
return null;
}
$format = json_decode($response->getBody())->national_format;
$format = json_decode($response->getBody()->getContents())->national_format;
$item
->set($format)

View File

@ -101,6 +101,10 @@ div.flex-table {
div.item-row {
flex-direction: row;
&.column { // exception
flex-direction: column
}
div.item-col {
&:first-child {
flex-grow: 0; flex-shrink: 0; flex-basis: auto;
@ -160,6 +164,12 @@ div.wrap-list {
& > * {
padding-right: 1em;
}
h3, h4 {
font-weight: 700;
font-size: 100%;
font-family: 'Open Sans';
}
}
div.wl-col.list {
@ -171,6 +181,10 @@ div.wrap-list {
padding: 0em;
display: inline-block;
}
blockquote.chill-user-quote {
margin: 0.7em 0;
}
}
}

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Security\Resolver;
use Chill\MainBundle\Entity\Scope;
use Doctrine\Common\Collections\Collection;
final class ScopeResolverDispatcher
{
@ -45,7 +46,13 @@ final class ScopeResolverDispatcher
{
foreach ($this->resolvers as $resolver) {
if ($resolver->supports($entity, $options)) {
return $resolver->resolveScope($entity, $options);
$scopes = $resolver->resolveScope($entity, $options);
if ($scopes instanceof Collection) {
return $scopes->toArray();
}
return $scopes;
}
}

View File

@ -45,6 +45,6 @@ class UserNormalizer implements NormalizerAwareInterface, NormalizerInterface
public function supportsNormalization($data, ?string $format = null): bool
{
return 'json' === $format && $data instanceof User;
return $data instanceof User && ('json' === $format || 'docgen' === $format);
}
}

View File

@ -11,20 +11,24 @@ declare(strict_types=1);
namespace Chill\MainBundle\Templating;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Contracts\Translation\TranslatorInterface;
use function array_key_exists;
final class TranslatableStringHelper implements TranslatableStringHelperInterface
{
private string $defaultLocale;
private RequestStack $requestStack;
private TranslatorInterface $translator;
public function __construct(RequestStack $requestStack, TranslatorInterface $translator)
public function __construct(RequestStack $requestStack, TranslatorInterface $translator, ParameterBagInterface $parameterBag)
{
$this->requestStack = $requestStack;
$this->translator = $translator;
$this->defaultLocale = $parameterBag->get('kernel.default_locale');
}
public function localize(array $translatableStrings): ?string
@ -35,11 +39,7 @@ final class TranslatableStringHelper implements TranslatableStringHelperInterfac
$request = $this->requestStack->getCurrentRequest();
if (null === $request) {
return null;
}
$language = $request->getLocale();
$language = null === $request ? $this->defaultLocale : $request->getLocale();
if (array_key_exists($language, $translatableStrings)) {
return $translatableStrings[$language];

View File

@ -0,0 +1,78 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\PersonBundle\Controller;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Serializer\Model\Collection;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\SerializerInterface;
use function count;
use function in_array;
class AccompanyingPeriodWorkEvaluationApiController
{
private DocGeneratorTemplateRepository $docGeneratorTemplateRepository;
private PaginatorFactory $paginatorFactory;
private SerializerInterface $serializer;
public function __construct(
DocGeneratorTemplateRepository $docGeneratorTemplateRepository,
SerializerInterface $serializer,
PaginatorFactory $paginatorFactory
) {
$this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository;
$this->serializer = $serializer;
$this->paginatorFactory = $paginatorFactory;
}
/**
* @Route("/api/1.0/person/docgen/template/by-evaluation/{id}.{_format}",
* requirements={"format": "json"})
*/
public function listTemplateByEvaluation(Evaluation $evaluation, string $_format): JsonResponse
{
if ('json' !== $_format) {
throw new BadRequestHttpException('format not supported');
}
$evaluations =
array_filter(
$this->docGeneratorTemplateRepository
->findByEntity(AccompanyingPeriodWorkEvaluation::class),
static function (DocGeneratorTemplate $t) use ($evaluation) {
$ids = $t->getOptions()['evaluations'] ?? [];
return in_array($evaluation->getId(), $ids, true);
}
);
$paginator = $this->paginatorFactory->create(count($evaluations));
$paginator->setItemsPerPage(count($evaluations));
return new JsonResponse($this->serializer->serialize(
new Collection($evaluations, $paginator),
'json',
[
AbstractNormalizer::GROUPS => ['read'],
]
), JsonResponse::HTTP_OK, [], true);
}
}

View File

@ -120,11 +120,11 @@ class AccompanyingPeriod implements
* @var DateTime
*
* @ORM\Column(type="date", nullable=true)
* @Groups({"read", "write"})
* @Groups({"read", "write", "docgen:read"})
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CLOSED})
* @Assert\GreaterThan(propertyPath="openingDate", groups={AccompanyingPeriod::STEP_CLOSED})
*/
private $closingDate;
private ?DateTime $closingDate = null;
/**
* @var AccompanyingPeriod\ClosingMotive
@ -135,11 +135,9 @@ class AccompanyingPeriod implements
* @Groups({"read", "write"})
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CLOSED})
*/
private $closingMotive;
private ?ClosingMotive $closingMotive = null;
/**
* @var Collection
*
* @ORM\OneToMany(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\Comment",
* mappedBy="accompanyingPeriod",
* cascade={"persist", "remove"},
@ -147,33 +145,32 @@ class AccompanyingPeriod implements
* )
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_DRAFT})
*/
private $comments;
private Collection $comments;
/**
* @var bool
* @ORM\Column(type="boolean", options={"default": false})
* @Groups({"read", "write"})
* @Groups({"read", "write", "docgen:read"})
*/
private $confidential = false;
private bool $confidential = false;
/**
* @ORM\Column(type="datetime", nullable=true, options={"default": NULL})
* @Groups({"docgen:read"})
*/
private DateTimeInterface $createdAt;
private ?DateTimeInterface $createdAt = null;
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=true)
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
*/
private $createdBy;
private ?User $createdBy = null;
/**
* @var bool
* @ORM\Column(type="boolean", options={"default": false})
* @Groups({"read", "write"})
* @Groups({"read", "write", "docgen:read"})
*/
private $emergency = false;
private bool $emergency = false;
/**
* @var int
@ -181,9 +178,9 @@ class AccompanyingPeriod implements
* @ORM\Id
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
*/
private $id;
private ?int $id = null;
/**
* @ORM\ManyToOne(
@ -205,9 +202,9 @@ class AccompanyingPeriod implements
* @var DateTime
*
* @ORM\Column(type="date")
* @Groups({"read", "write"})
* @Groups({"read", "write", "docgen:read"})
*/
private $openingDate;
private ?DateTime $openingDate = null;
/**
* @ORM\ManyToOne(targetEntity=Origin::class)
@ -215,18 +212,16 @@ class AccompanyingPeriod implements
* @Groups({"read", "write"})
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CONFIRMED})
*/
private $origin;
private ?Origin $origin = null;
/**
* @var Collection
*
* @ORM\OneToMany(targetEntity=AccompanyingPeriodParticipation::class,
* mappedBy="accompanyingPeriod", orphanRemoval=true,
* cascade={"persist", "refresh", "remove", "merge", "detach"})
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
* @ParticipationOverlap(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED})
*/
private $participations;
private Collection $participations;
/**
* @ORM\ManyToOne(
@ -237,48 +232,42 @@ class AccompanyingPeriod implements
private ?Person $personLocation = null;
/**
* @var string
*
* @ORM\Column(type="text")
* @Groups({"read", "write"})
*/
private $remark = '';
private string $remark = '';
/**
* @var bool
* @ORM\Column(type="boolean", options={"default": false})
* @Groups({"read", "write"})
* @Groups({"read", "write", "docgen:read"})
*/
private $requestorAnonymous = false;
private bool $requestorAnonymous = false;
/**
* @ORM\ManyToOne(targetEntity=Person::class, inversedBy="accompanyingPeriodRequested")
* @ORM\JoinColumn(nullable=true)
*/
private $requestorPerson;
private ?Person $requestorPerson = null;
/**
* @ORM\ManyToOne(targetEntity=ThirdParty::class)
* @ORM\JoinColumn(nullable=true)
*/
private $requestorThirdParty;
private ?ThirdParty $requestorThirdParty = null;
/**
* @var Collection
*
* @ORM\OneToMany(
* targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\Resource",
* mappedBy="accompanyingPeriod",
* cascade={"persist", "remove"},
* orphanRemoval=true
* )
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
* @ResourceDuplicateCheck(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED, "Default", "default"})
*/
private $resources;
private Collection $resources;
/**
* @var Collection
* @ORM\ManyToMany(
* targetEntity=Scope::class,
* cascade={}
@ -288,10 +277,10 @@ class AccompanyingPeriod implements
* joinColumns={@ORM\JoinColumn(name="accompanying_period_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM\JoinColumn(name="scope_id", referencedColumnName="id")}
* )
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
* @Assert\Count(min=1, groups={AccompanyingPeriod::STEP_CONFIRMED}, minMessage="A course must be associated to at least one scope")
*/
private $scopes;
private Collection $scopes;
/**
* @ORM\ManyToMany(
@ -300,36 +289,35 @@ class AccompanyingPeriod implements
* @ORM\JoinTable(
* name="chill_person_accompanying_period_social_issues"
* )
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
* @Assert\Count(min=1, groups={AccompanyingPeriod::STEP_CONFIRMED}, minMessage="A course must contains at least one social issue")
*/
private Collection $socialIssues;
/**
* @var string
* @ORM\Column(type="string", length=32, nullable=true)
* @Groups({"read"})
*/
private $step = self::STEP_DRAFT;
private string $step = self::STEP_DRAFT;
/**
* @ORM\Column(type="datetime", nullable=true, options={"default": NULL})
*/
private DateTimeInterface $updatedAt;
private ?DateTimeInterface $updatedAt = null;
/**
* @ORM\ManyToOne(
* targetEntity=User::class
* )
*/
private User $updatedBy;
private ?User $updatedBy = null;
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=true)
* @Groups({"read", "write"})
* @Groups({"read", "write", "docgen:read"})
*/
private $user;
private ?User $user = null;
/**
* @ORM\OneToMany(
@ -355,6 +343,7 @@ class AccompanyingPeriod implements
$this->socialIssues = new ArrayCollection();
$this->comments = new ArrayCollection();
$this->works = new ArrayCollection();
$this->resources = new ArrayCollection();
}
/**
@ -580,11 +569,19 @@ class AccompanyingPeriod implements
});
}
public function getCreatedAt(): ?DateTime
{
return $this->createdAt;
}
public function getCreatedBy(): ?User
{
return $this->createdBy;
}
/**
* @Groups({"docgen:read"})
*/
public function getCurrentParticipations(): Collection
{
return $this->getOpenParticipations();
@ -608,7 +605,7 @@ class AccompanyingPeriod implements
*
* @return int
*/
public function getId()
public function getId(): ?int
{
return $this->id;
}
@ -634,7 +631,7 @@ class AccompanyingPeriod implements
public function getLocation(?DateTimeImmutable $at = null): ?Address
{
if ($this->getPersonLocation() instanceof Person) {
return $this->getPersonLocation()->getCurrentHouseholdAddress($at);
return $this->getPersonLocation()->getCurrentPersonAddress();
}
return $this->getAddressLocation();
@ -663,7 +660,7 @@ class AccompanyingPeriod implements
*
* @return DateTime
*/
public function getOpeningDate()
public function getOpeningDate(): ?DateTime
{
return $this->openingDate;
}

View File

@ -53,7 +53,7 @@ use Symfony\Component\Validator\Constraints as Assert;
* cascade={"remove", "persist"},
* orphanRemoval=true
* )
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*
* @internal /!\ the serialization for write evaluations is handled in `AccompanyingPeriodWorkDenormalizer`
*/
@ -61,24 +61,26 @@ use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Column(type="datetime_immutable")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private ?DateTimeImmutable $createdAt = null;
/**
* @ORM\Column(type="boolean")
* @Serializer\Groups({"read", "docgen:read"})
*/
private bool $createdAutomatically = false;
/**
* @ORM\Column(type="text")
* @Serializer\Groups({"read", "docgen:read"})
*/
private string $createdAutomaticallyReason = '';
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=false)
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private ?User $createdBy = null;
@ -86,7 +88,7 @@ use Symfony\Component\Validator\Constraints as Assert;
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
* @Serializer\Groups({"accompanying_period_work:create"})
* @Serializer\Groups({"accompanying_period_work:edit"})
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
* @Assert\GreaterThan(propertyPath="startDate",
* message="accompanying_course_work.The endDate should be greater than the start date"
* )
@ -100,14 +102,14 @@ use Symfony\Component\Validator\Constraints as Assert;
* cascade={"persist"},
* orphanRemoval=true
* )
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"accompanying_period_work:edit"})
*/
private Collection $goals;
/**
* @ORM\ManyToOne(targetEntity=ThirdParty::class)
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"accompanying_period_work:edit"})
*
* In schema : traitant
@ -118,20 +120,20 @@ use Symfony\Component\Validator\Constraints as Assert;
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private ?int $id;
private ?int $id = null;
/**
* @ORM\Column(type="text")
* @Serializer\Groups({"read", "accompanying_period_work:edit"})
* @Serializer\Groups({"read", "accompanying_period_work:edit", "docgen:read"})
*/
private string $note = '';
/**
* @ORM\ManyToMany(targetEntity=Person::class)
* @ORM\JoinTable(name="chill_person_accompanying_period_work_person")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"accompanying_period_work:edit"})
* @Serializer\Groups({"accompanying_period_work:create"})
*/
@ -140,14 +142,14 @@ use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\ManyToMany(targetEntity=Result::class, inversedBy="accompanyingPeriodWorks")
* @ORM\JoinTable(name="chill_person_accompanying_period_work_result")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"accompanying_period_work:edit"})
*/
private Collection $results;
/**
* @ORM\ManyToOne(targetEntity=SocialAction::class)
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"accompanying_period_work:create"})
*/
private ?SocialAction $socialAction = null;
@ -156,30 +158,30 @@ use Symfony\Component\Validator\Constraints as Assert;
* @ORM\Column(type="date_immutable")
* @Serializer\Groups({"accompanying_period_work:create"})
* @Serializer\Groups({"accompanying_period_work:edit"})
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private DateTimeImmutable $startDate;
private ?DateTimeImmutable $startDate = null;
/**
* @ORM\ManyToMany(targetEntity=ThirdParty::class)
* @ORM\JoinTable(name="chill_person_accompanying_period_work_third_party")
*
* In schema : intervenants
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"accompanying_period_work:edit"})
*/
private Collection $thirdParties;
/**
* @ORM\Column(type="datetime_immutable")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private ?DateTimeImmutable $updatedAt = null;
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=false)
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private ?User $updatedBy = null;

View File

@ -44,7 +44,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
/**
* @ORM\Column(type="text", nullable=false, options={"default": ""})
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"write"})
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
*/
@ -52,7 +52,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
/**
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private ?DateTimeImmutable $createdAt = null;
@ -60,7 +60,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
* @ORM\ManyToOne(
* targetEntity=User::class
* )
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private ?User $createdBy = null;
@ -76,7 +76,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
/**
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"write"})
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
*/
@ -86,7 +86,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
* @ORM\ManyToOne(
* targetEntity=Evaluation::class
* )
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
*/
private ?Evaluation $evaluation = null;
@ -95,7 +95,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private ?int $id = null;
@ -116,14 +116,14 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
/**
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
*/
private ?DateTimeImmutable $maxDate = null;
/**
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"write"})
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
*/
@ -131,7 +131,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
/**
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private ?DateTimeImmutable $updatedAt = null;
@ -139,7 +139,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
* @ORM\ManyToOne(
* targetEntity=User::class
* )
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private ?User $updatedBy = null;
@ -239,6 +239,18 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
return $this->updatedBy;
}
/**
* @Serializer\Groups({"docgen:read"})
*/
public function getWarningDate(): ?DateTimeImmutable
{
if (null === $this->getEndDate() || null === $this->getWarningInterval()) {
return null;
}
return $this->getEndDate()->sub($this->getWarningInterval());
}
public function getWarningInterval(): ?DateInterval
{
return $this->warningInterval;

View File

@ -33,7 +33,7 @@ class Origin
* @ORM\Column(type="integer")
* @Groups({"read"})
*/
private $id;
private ?int $id = null;
/**
* @ORM\Column(type="json")
@ -45,7 +45,7 @@ class Origin
* @ORM\Column(type="date_immutable", nullable=true)
* @Groups({"read"})
*/
private $noActiveAfter;
private ?DateTimeImmutable $noActiveAfter = null;
public function getId(): ?int
{
@ -62,7 +62,7 @@ class Origin
return $this->noActiveAfter;
}
public function setLabel(string $label): self
public function setLabel(array $label): self
{
$this->label = $label;

View File

@ -41,11 +41,10 @@ class Resource
* )
* @ORM\JoinColumn(nullable=false)
*/
private $accompanyingPeriod;
private ?AccompanyingPeriod $accompanyingPeriod = null;
/**
* @ORM\ManyToOne(targetEntity=Comment::class)
* @ORM\JoinColumn(nullable=true)
*/
private $comment;
@ -53,21 +52,23 @@ class Resource
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
*/
private $id;
private ?int $id = null;
/**
* @ORM\ManyToOne(targetEntity=Person::class)
* @ORM\JoinColumn(nullable=true)
* @Groups({"docgen:read"})
*/
private $person;
private ?Person $person = null;
/**
* @ORM\ManyToOne(targetEntity=ThirdParty::class)
* @ORM\JoinColumn(nullable=true)
* @Groups({"docgen:read"})
*/
private $thirdParty;
private ?ThirdParty $thirdParty = null;
public function getAccompanyingPeriod(): ?AccompanyingPeriod
{

View File

@ -11,7 +11,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Entity;
use DateTimeImmutable;
use DateTime;
use DateTimeInterface;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
@ -32,38 +32,38 @@ class AccompanyingPeriodParticipation
* @ORM\ManyToOne(targetEntity=AccompanyingPeriod::class, inversedBy="participations", cascade={"persist"})
* @ORM\JoinColumn(name="accompanyingperiod_id", referencedColumnName="id", nullable=false)
*/
private $accompanyingPeriod;
private ?AccompanyingPeriod $accompanyingPeriod = null;
/**
* @ORM\Column(type="date", nullable=true)
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
*/
private $endDate;
private ?DateTime $endDate = null;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
*/
private $id;
private ?int $id = null;
/**
* @ORM\ManyToOne(targetEntity=Person::class, inversedBy="accompanyingPeriodParticipations")
* @ORM\JoinColumn(name="person_id", referencedColumnName="id", nullable=false)
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
*/
private $person;
private ?Person $person = null;
/**
* @ORM\Column(type="date", nullable=false)
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
*/
private $startDate;
private ?DateTime $startDate = null;
public function __construct(AccompanyingPeriod $accompanyingPeriod, Person $person)
{
$this->startDate = new DateTimeImmutable('now');
$this->startDate = new DateTime('now');
$this->accompanyingPeriod = $accompanyingPeriod;
$this->person = $person;
}
@ -73,10 +73,6 @@ class AccompanyingPeriodParticipation
return $this->accompanyingPeriod;
}
/*
* public function setStartDate(\DateTimeInterface $startDate): self { $this->startDate = $startDate; return $this; }
*/
public function getEndDate(): ?DateTimeInterface
{
return $this->endDate;

View File

@ -59,7 +59,7 @@ class Household
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private ?int $id = null;
@ -68,17 +68,19 @@ class Household
* targetEntity=HouseholdMember::class,
* mappedBy="household"
* )
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private Collection $members;
/**
* @ORM\Column(type="boolean", name="waiting_for_birth", options={"default": false})
* @Serializer\Groups({"docgen:read"})
*/
private bool $waitingForBirth = false;
/**
* @ORM\Column(type="date_immutable", name="waiting_for_birth_date", nullable=true, options={"default": null})
* @Serializer\Groups({"docgen:read"})
*/
private ?DateTimeImmutable $waitingForBirthDate = null;
@ -134,7 +136,7 @@ class Household
}
/**
* @Serializer\Groups({ "read" })
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\SerializedName("current_address")
*/
public function getCurrentAddress(?DateTime $at = null): ?Address
@ -154,6 +156,9 @@ class Household
return null;
}
/**
* @Serializer\Groups({"docgen:read"})
*/
public function getCurrentMembers(?DateTimeImmutable $now = null): Collection
{
return $this->getMembers()->matching($this->buildCriteriaCurrentMembers($now));

View File

@ -28,13 +28,13 @@ class HouseholdMember
{
/**
* @ORM\Column(type="string", length=255, nullable=true)
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private ?string $comment = null;
/**
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
* @Assert\GreaterThan(
* propertyPath="startDate",
* message="household_membership.The end date must be after start date",
@ -45,7 +45,7 @@ class HouseholdMember
/**
* @ORM\Column(type="boolean", options={"default": false})
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private bool $holder = false;
@ -63,7 +63,7 @@ class HouseholdMember
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private $id;
@ -72,7 +72,7 @@ class HouseholdMember
* @ORM\ManyToOne(
* targetEntity="\Chill\PersonBundle\Entity\Person"
* )
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
* @Assert\Valid(groups={"household_memberships"})
* @Assert\NotNull(groups={"household_memberships"})
*/
@ -80,7 +80,7 @@ class HouseholdMember
/**
* @ORM\ManyToOne(targetEntity=Position::class)
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
* @Assert\NotNull(groups={"household_memberships_created"})
*/
private ?Position $position = null;
@ -92,7 +92,7 @@ class HouseholdMember
/**
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
* @Assert\NotNull(groups={"household_memberships"})
*/
private ?DateTimeImmutable $startDate = null;

View File

@ -33,25 +33,25 @@ class Position
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Serializer\Groups({ "read" })
* @Serializer\Groups({"read", "docgen:read"})
*/
private ?int $id;
/**
* @ORM\Column(type="json")
* @Serializer\Groups({ "read" })
* @Serializer\Groups({"read", "docgen:read"})
*/
private array $label = [];
/**
* @ORM\Column(type="float")
* @Serializer\Groups({ "read" })
* @Serializer\Groups({"read"})
*/
private float $ordering = 0.00;
/**
* @ORM\Column(type="boolean")
* @Serializer\Groups({ "read" })
* @Serializer\Groups({"read"})
*/
private bool $shareHouseHold = true;

View File

@ -28,21 +28,21 @@ class Evaluation
* @ORM\Column(type="dateinterval", nullable=true, options={"default": null})
* @Serializer\Groups({"read"})
*/
private $delay;
private ?DateInterval $delay = null;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private $id;
private ?int $id = null;
/**
* @ORM\Column(type="dateinterval", nullable=true, options={"default": null})
* @Serializer\Groups({"read"})
*/
private $notificationDelay;
private ?DateInterval $notificationDelay = null;
/**
* @ORM\ManyToOne(
@ -50,13 +50,13 @@ class Evaluation
* inversedBy="evaluations"
* )
*/
private $socialAction;
private ?SocialAction $socialAction = null;
/**
* @ORM\Column(type="json")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private $title = [];
private array $title = [];
public function getDelay(): ?DateInterval
{

View File

@ -38,7 +38,7 @@ class Goal
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private $id;
@ -55,7 +55,7 @@ class Goal
/**
* @ORM\Column(type="json")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private $title = [];

View File

@ -55,7 +55,7 @@ class Result
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private $id;
@ -66,7 +66,7 @@ class Result
/**
* @ORM\Column(type="json")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private $title = [];

View File

@ -114,10 +114,10 @@ class PersonType extends AbstractType
$builder->get('placeOfBirth')->addModelTransformer(new CallbackTransformer(
static function ($string) {
return strtoupper($string);
return strtoupper((string) $string);
},
static function ($string) {
return strtoupper($string);
return strtoupper((string) $string);
}
));
}

View File

@ -1,51 +1,6 @@
/// AccompanyingCourse Work list Page
div.accompanying_course_work-list {
div.timeline {
width: 100%;
ul {
display: flex;
align-items: center;
justify-content: center;
padding: 0;
list-style-type: none;
> li {
flex-grow: 1; flex-shrink: 1; flex-basis: auto;
div {
display: flex;
flex-direction: column;
align-items: center;
&.date {
margin-bottom: 1em;
}
&.label {
border-top: 3px solid $chill-green;
&:before {
content: '';
display: inline-block;
position: relative;
width: 15px;
height: 15px;
top: -9px;
background-color: $white;
border-radius: 12px;
border: 2px solid $chill-green;
}
&.no-label:before {
display: none;
}
}
}
}
}
}
div.objective_results {
width: 100%;
display: grid;
@ -69,8 +24,10 @@ div.accompanying_course_work-list {
//&:nth-child(even) { background-color: $chill-llight-gray; }
&.without-objectives {}
&.with-objectives {}
}
div.objective_results,
div.evaluations {
h4.title_label {
display: block;
margin: 0.4em 0;

View File

@ -39,69 +39,15 @@ span.fa-holder {
}
/*
* BADGE_TITLE
* Display Title like a badge (with background-colored label)
* DASHBOARDS
*/
h2.badge-title {
display: flex;
flex-direction: row;
width: 100%;
color: $dark;
span.title_label {
border-radius: 0.35rem 0 0 0.35rem;
color: $white;
font-size: 80%;
padding: 0.5em;
padding-right: 0;
h3 {
margin-bottom: 0.5rem;
}
//position: relative;
span {
display: none;
//position: absolute;
//top: 0;
//left: 0;
//transform: rotate(270deg);
//transform-origin: 0 0;
}
}
span.title_action {
flex-grow: 1;
margin: 0 0 0 auto;
border-radius: 0 0.35rem 0.35rem 0;
background-color: $chill-llight-gray;
padding: 0.2em 1em;
ul.small_in_title {
margin: 0;
//margin-top: 0.5em;
font-size: 70%;
padding-left: 1rem;
&.evaluations {
@include list_marker_triangle($orange);
}
}
ul.columns { // XS:1 SM:2 MD:1 LG:2 XL:2 XXL:2
@include media-breakpoint-only(sm) {
columns: 2; -webkit-columns: 2; -moz-columns: 2;
}
@include media-breakpoint-up(lg) {
columns: 2; -webkit-columns: 2; -moz-columns: 2;
}
}
}
}
/// Theses links apply on badge as parent tag.
/// Theses links apply on dashboards as parent tag.
/// They don't look like button, picto or simple text links
a.badge-link {
a.dashboard-link {
color: unset;
text-decoration: unset;
& > h2.badge-title {
& > div.dashboard {
&:hover {
//box-shadow: 0 0 7px 0 $chill-gray;
//opacity: 0.8;
@ -114,21 +60,80 @@ a.badge-link {
}
}
/// badge_title in AccompanyingCourse Work list Page
div.dashboard {
font-weight: 700;
font-size: 1.5rem;
margin-bottom: 0.5rem;
line-height: 1.2;
span.like-h3 {
color: #334d5c;
}
}
div.dashboard,
h2.badge-title {
display: flex;
flex-direction: row;
width: 100%;
color: $dark;
span.title_label {
color: $white;
font-size: 80%;
padding: 0.5em;
padding-right: 0;
border-radius: 0.35rem 0 0 0.35rem;
h3 {
margin-bottom: 0.5rem;
}
}
span.title_action {
flex-grow: 1;
margin: 0 0 0 auto;
background-color: $chill-llight-gray;
padding: 0.2em 1em;
border-radius: 0 0.35rem 0.35rem 0;
ul.small_in_title {
font-size: 70%;
}
}
}
ul.small_in_title {
margin: 0;
//margin-top: 0.5em;
padding-left: 1rem;
&.evaluations {
@include list_marker_triangle($orange);
}
}
ul.columns { // XS:1 SM:2 MD:1 LG:2 XL:2 XXL:2
@include media-breakpoint-only(sm) {
columns: 2; -webkit-columns: 2; -moz-columns: 2;
}
@include media-breakpoint-up(lg) {
columns: 2; -webkit-columns: 2; -moz-columns: 2;
}
}
/// dashboard_like_badge in AccompanyingCourse Work list Page
div.accompanying_course_work-list {
div.dashboard,
h2.badge-title {
span.title_label {
// Calculate same color then border:groove
background-color: shade-color($social-action-color, 34%);
}
span.title_action {
@include badge_title($social-action-color);
@include dashboard_like_badge($social-action-color);
}
}
}
/// badge_title in Activities on resume page
/// dashboard_like_badge in Activities on resume page
div.activity-list {
div.dashboard,
h2.badge-title {
span.title_label {
// Calculate same color then border:groove
@ -138,7 +143,7 @@ div.activity-list {
}
}
span.title_action {
@include badge_title($activity-color);
@include dashboard_like_badge($activity-color);
}
span.title_label {
div.duration {

View File

@ -18,12 +18,6 @@ div.accompanyingcourse-list {
//&:nth-child(2) { flex-direction: row; }
//&:last-child { flex-direction: column; }
}
div.title h3 {
font-weight: 700;
font-size: 100%;
font-family: 'Open Sans';
}
div.list {}
}
/// Search Page (list_with_period.html.twig)

View File

@ -27,11 +27,10 @@
}
///
/// Generic mixin for titles like badge
// define visual badge used in title area
/// Mixin for dashboards (with design like badge_social)
///
@mixin badge_title($color) {
@mixin dashboard_like_badge($color) {
@include chill_badge($color);
&:before {
margin: 0 0.3em 0 -1.05em;

View File

@ -195,6 +195,18 @@
</ul>
</div>
<div>
<pick-template
entityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork"
:templates="this.templatesAvailablesForAction"
:entityId="work.id"
:beforeMove="beforeGenerateTemplate">
<template v-slot:title>
<h3>{{ $t('Generate doc') }}</h3>
</template>
</pick-template>
</div>
<div v-if="errors.length > 0" id="errors" class="alert alert-danger flashbag">
<p>{{ $t('fix_these_errors') }}</p>
<ul>
@ -230,6 +242,7 @@ import AddEvaluation from './components/AddEvaluation.vue';
import PersonRenderBox from 'ChillPersonAssets/vuejs/_components/Entity/PersonRenderBox.vue';
import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue';
import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue';
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
const i18n = {
messages: {
@ -276,6 +289,7 @@ export default {
AddPersons,
PersonRenderBox,
AddressRenderBox,
PickTemplate,
},
i18n,
data() {
@ -318,6 +332,7 @@ export default {
'thirdParties',
'isPosting',
'errors',
'templatesAvailablesForAction',
]),
...mapGetters([
'hasResultsForAction',
@ -386,7 +401,7 @@ export default {
this.$store.commit('removeGoal', g);
},
addEvaluation(e) {
this.$store.commit('addEvaluation', e);
this.$store.dispatch('addEvaluation', e);
},
toggleAddEvaluation() {
this.showAddEvaluation = !this.showAddEvaluation;
@ -414,6 +429,10 @@ export default {
submit() {
this.$store.dispatch('submit');
},
beforeGenerateTemplate() {
console.log('before generate');
return Promise.resolve();
}
}
};

View File

@ -62,19 +62,16 @@
</div>
<div class="row mb-3">
<pick-template
entityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation"
:id="evaluation.id"
:templates="getTemplatesAvailables"
:beforeMove="submitBeforeGenerate"
>
<template v-slot:title>
<label class="col-sm-4 col-form-label">{{ $t('evaluation_generate_a_document') }}</label>
<div class="col-sm-8">
<div class="input-group">
<select class="form-select form-select-sm" v-model="template">
<option disabled value="">{{ $t('evaluation_choose_a_template') }}</option>
<template v-for="t in getTemplatesAvailaibleForEvaluation">
<option v-bind:value="t.id">{{ t.name.fr }}</option>
</template>
</select>
<button v-if="canGenerate" class="btn btn-update btn-sm change-icon" type="button" @click="generateDocument"><i class="fa fa-fw fa-cog"></i></button>
<button v-else class="btn btn-update btn-sm change-icon" type="button" disabled ><i class="fa fa-fw fa-cog"></i></button>
</div>
</div>
</pick-template>
</div>
</div>
</div>
@ -85,6 +82,7 @@ import {dateToISO, ISOToDate, ISOToDatetime} from 'ChillMainAssets/chill/js/date
import CKEditor from '@ckeditor/ckeditor5-vue';
import ClassicEditor from 'ChillMainAssets/module/ckeditor5/index.js';
import { mapGetters, mapState } from 'vuex';
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
const i18n = {
messages: {
@ -111,6 +109,7 @@ export default {
props: ['evaluation'],
components: {
ckeditor: CKEditor.component,
PickTemplate,
},
i18n,
data() {
@ -120,12 +119,12 @@ export default {
}
},
computed: {
...mapGetters([
'getTemplatesAvailaibleForEvaluation'
]),
...mapState([
'isPosting'
]),
getTemplatesAvailables() {
return this.$store.getters.getTemplatesAvailablesForEvaluation(this.evaluation.evaluation);
},
canGenerate() {
return !this.$store.state.isPosting && this.template !== null;
},
@ -176,13 +175,14 @@ export default {
})
;
},
generateDocument() {
console.log('template picked', this.template);
this.$store.dispatch('generateDocument', { key: this.evaluation.key, templateId: this.template})
submitBeforeGenerate() {
const callback = (data) => {
let evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key).id;
return Promise.resolve({entityId: evaluationId});
};
return this.$store.dispatch('submit', callback).catch(e => { console.log(e); throw e; });
}
},
mounted() {
//this.listAllStatus();
}
}
</script>

View File

@ -2,6 +2,8 @@ import { createStore } from 'vuex';
import { datetimeToISO, ISOToDatetime, intervalDaysToISO, intervalISOToDays } from 'ChillMainAssets/chill/js/date.js';
import { findSocialActionsBySocialIssue } from 'ChillPersonAssets/vuejs/_api/SocialWorkSocialAction.js';
import { create } from 'ChillPersonAssets/vuejs/_api/AccompanyingCourseWork.js';
import { fetchResults, makeFetch } from 'ChillMainAssets/lib/api/apiMethods.js';
import { fetchTemplates } from 'ChillDocGeneratorAssets/api/pickTemplate.js';
const debug = process.env.NODE_ENV !== 'production';
const evalFQDN = encodeURIComponent("Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation");
@ -20,20 +22,10 @@ const store = createStore({
resultsPicked: window.accompanyingCourseWork.results,
resultsForAction: [],
resultsForGoal: [],
evaluationsPicked: window.accompanyingCourseWork.accompanyingPeriodWorkEvaluations.map((e, index) => {
var k = Object.assign(e, {
key: index,
editEvaluation: false,
startDate: e.startDate !== null ? ISOToDatetime(e.startDate.datetime) : null,
endDate: e.endDate !== null ? ISOToDatetime(e.endDate.datetime) : null,
maxDate: e.maxDate !== null ? ISOToDatetime(e.maxDate.datetime) : null,
warningInterval: e.warningInterval !== null ? intervalISOToDays(e.warningInterval) : null,
});
return k;
}),
evaluationsPicked: [],
evaluationsForAction: [],
templatesAvailableForEvaluation: [],
templatesAvailablesForAction: [],
templatesAvailablesForEvaluation: new Map([]),
personsPicked: window.accompanyingCourseWork.persons,
personsReachables: window.accompanyingCourseWork.accompanyingPeriod.participations.filter(p => p.endDate == null)
.map(p => p.person),
@ -65,8 +57,8 @@ const store = createStore({
hasThirdParties(state) {
return state.thirdParties.length > 0;
},
getTemplatesAvailaibleForEvaluation(state) {
return state.templatesAvailableForEvaluation;
getTemplatesAvailablesForEvaluation: (state) => (evaluation) => {
return state.templatesAvailablesForEvaluation.get(evaluation.id) || [];
},
buildPayload(state) {
return {
@ -125,6 +117,20 @@ const store = createStore({
}
},
mutations: {
setEvaluationsPicked(state, evaluations) {
state.evaluationsPicked = evaluations.map((e, index) => {
var k = Object.assign(e, {
key: index,
editEvaluation: false,
startDate: e.startDate !== null ? ISOToDatetime(e.startDate.datetime) : null,
endDate: e.endDate !== null ? ISOToDatetime(e.endDate.datetime) : null,
maxDate: e.maxDate !== null ? ISOToDatetime(e.maxDate.datetime) : null,
warningInterval: e.warningInterval !== null ? intervalISOToDays(e.warningInterval) : null,
});
return k;
});
},
setStartDate(state, date) {
state.startDate = date;
},
@ -222,10 +228,11 @@ const store = createStore({
let evaluation = state.evaluationsPicked.find(e => e.key === key);
evaluation.editEvaluation = !evaluation.editEvaluation;
},
setTemplatesAvailableForEvaluation(state, templates) {
for (let i in templates) {
state.templatesAvailableForEvaluation.push(templates[i]);
}
setTemplatesForEvaluation(state, {templates, evaluation}) {
state.templatesAvailablesForEvaluation.set(evaluation.id, templates);
},
setTemplatesAvailablesForAction(state, templates) {
state.templatesAvailablesForAction = templates;
},
setPersonsPickedIds(state, ids) {
state.personsPicked = state.personsReachables
@ -328,36 +335,19 @@ const store = createStore({
commit('setEvaluationsForAction', data.results);
});
},
getReachableTemplatesForEvaluation({commit}) {
const
url = `/fr/doc/gen/templates/for/${evalFQDN}`
;
window.fetch(url).then(r => {
if (r.ok) {
return r.json();
}
throw new Error("not possible to load templates for evaluations")
}).then(data => {
commit('setTemplatesAvailableForEvaluation', data.results);
}).catch(e => {
console.error(e);
})
addEvaluation({commit, dispatch}, evaluation) {
commit('addEvaluation', evaluation);
dispatch('fetchTemplatesAvailablesForEvaluation', evaluation);
},
generateDocument({ dispatch }, {key, templateId}) {
const callback = function(data) {
// get the evaluation id from the data
const
evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === key).id,
returnPath = encodeURIComponent(window.location.pathname + window.location.search + window.location.hash),
url = `/fr/doc/gen/generate/from/${templateId}/for/${evalFQDN}/${evaluationId}?returnPath=${returnPath}`
;
//http://localhost:8001/fr/doc/gen/generate/from/12/for/Chill%5CPersonBundle%5CEntity%5CAccompanyingPeriod%5CAccompanyingPeriodWorkEvaluation/41
console.log('I will generate your doc at', url);
window.location.assign(url);
};
dispatch('submit', callback);
fetchTemplatesAvailablesForEvaluation({commit, state}, evaluation) {
if (!state.templatesAvailablesForEvaluation.has(evaluation.id)) {
// commit an empty array to avoid parallel fetching for same evaluation id
commit('setTemplatesForEvaluation', {templates: [], evaluation});
fetchResults(`/api/1.0/person/docgen/template/by-evaluation/${evaluation.id}.json`)
.then(templates => {
commit('setTemplatesForEvaluation', {templates, evaluation});
});
}
},
submit({ getters, state, commit }, callback) {
let
@ -368,47 +358,36 @@ const store = createStore({
commit('setIsPosting', true);
window.fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
}).then(response => {
if (response.ok || response.status === 422) {
return response.json().then(data => ({data, status: response.status}));
}
throw new Error(response.status);
}).then(({data, status}) => {
if (status === 422) {
for (let i in data.violations) {
errors.push(data.violations[i].title);
}
commit('setErrors', errors);
commit('setIsPosting', false);
} else if (typeof(callback) !== 'undefined') {
callback(data);
return makeFetch('PUT', url, payload)
.then(data => {
console.log('data received', data);
if (typeof(callback) !== 'undefined') {
return callback(data);
} else {
console.info('nothing to do here, bye bye');
window.location.assign(`/fr/person/accompanying-period/${state.work.accompanyingPeriod.id}/work`);
console.info('nothing to do here, bye bye');window.location.assign(`/fr/person/accompanying-period/${state.work.accompanyingPeriod.id}/work`);
}
}).catch(e => {
commit('setErrors', [
'Erreur serveur ou réseau: veuillez ré-essayer. Code erreur: ' + e
]);
}).catch(error => {
console.log('error on submit', error);
commit('setIsPosting', false);
commit('setErrors', error.violations);
});
},
initAsync({ dispatch }) {
dispatch('getReachablesResultsForAction');
dispatch('getReachablesGoalsForAction');
dispatch('getReachablesEvaluationsForAction');
dispatch('getReachableTemplatesForEvaluation');
},
}
});
store.dispatch('initAsync');
store.commit('setEvaluationsPicked', window.accompanyingCourseWork.accompanyingPeriodWorkEvaluations);
store.dispatch('getReachablesResultsForAction');
store.dispatch('getReachablesGoalsForAction');
store.dispatch('getReachablesEvaluationsForAction');
store.state.evaluationsPicked.forEach(evaluation => {
store.dispatch('fetchTemplatesAvailablesForEvaluation', evaluation.evaluation)
});
fetchTemplates('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork')
.then(templates => {
store.commit('setTemplatesAvailablesForAction', templates);
}
)
export { store };

View File

@ -103,7 +103,7 @@
</div>
<div class="social-actions my-4">
<h2 class="mb-3 d-none">{{ 'Last social actions'|trans }}</h2>
<h2 class="mb-3 visually-hidden">{{ 'Last social actions'|trans }}</h2>
{% include 'ChillPersonBundle:AccompanyingCourseWork:list_recent_by_accompanying_period.html.twig' with {'buttonText': false } %}
</div>
@ -121,7 +121,7 @@
{% set accompanying_course_id = accompanyingCourse.id %}
{% endif %}
<h2 class="mb-3 d-none">{{ 'Last activities' |trans }}</h2>
<h2 class="mb-3 visually-hidden">{{ 'Last activities' |trans }}</h2>
{% include 'ChillActivityBundle:Activity:list_recent.html.twig' with { 'context': 'accompanyingCourse', 'no_action': true } %}
</div>
{% endblock %}

View File

@ -8,7 +8,7 @@
<div class="accompanying_course_work-list">
<h2 class="badge-title">
<span class="title_label">{{ 'accompanying_course_work.action'|trans }}</span>
<span class="title_label"></span>
<span class="title_action">{{ work.socialAction|chill_entity_render_string }}</span>
</h2>

View File

@ -7,7 +7,217 @@
<h1>{{ block('title') }}</h1>
{% include 'ChillPersonBundle:AccompanyingCourseWork:list_by_accompanying_period.html.twig' %}
{% if works|length == 0 %}
<p class="chill-no-data-statement">{{ 'accompanying_course_work.Any work'|trans }}
<a class="btn btn-sm btn-create"
title="{{ 'accompanying_course_work.create'|trans }}"
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_new', { 'id': accompanyingCourse.id }) }}"
></a>
</p>
{% else %}
<div class="flex-table accompanying_course_work-list">
{% for w in works %}
<div class="item-bloc">
<div class="item-row">
<h2 class="badge-title">
<span class="title_label"></span>
<span class="title_action">{{ w.socialAction|chill_entity_render_string }}
<ul class="small_in_title columns mt-1">
<li>
<span class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
<b>{{ w.startDate|format_date('short') }}</b>
</li>
{% if w.endDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<b>{{ w.endDate|format_date('short') }}</b>
</li>
{% endif %}
</ul>
</span>
</h2>
</div>
<div class="item-row separator">
<div class="wrap-list">
{% if w.createdBy %}
<div class="wl-row">
<div class="wl-col title">
<h3>{{ 'Referrer'|trans }}</h3>
</div>
<div class="wl-col list">
<p class="wl-item">
{{ w.createdBy.usernameCanonical|chill_entity_render_string|capitalize }}
</p>
</div>
</div>
{% endif %}
{%- if w.persons -%}
<div class="wl-row">
<div class="wl-col title">
<h3>{{ 'Persons in accompanying course'|trans }}</h3>
</div>
<div class="wl-col list">
{% for p in w.persons %}
<span class="wl-item badge-person">
{{ p|chill_entity_render_box({
'render': 'raw',
'addAltNames': false
}) }}
</span>
{% endfor %}
</div>
</div>
{% endif %}
{%- if w.handlingThierParty -%}
<div class="wl-row">
<div class="wl-col title">
<h3>{{ 'Thirdparty handling'|trans }}</h3>
</div>
<div class="wl-col list">
<span class="wl-item badge-thirdparty">
{{ w.handlingThierParty|chill_entity_render_box({
'render': 'raw',
'addAltNames': false
}) }}
</span>
</div>
</div>
{% endif %}
{%- if w.socialAction.issue -%}
<div class="wl-row">
<div class="wl-col title">
<h3>{{ 'Social issue'|trans }}</h3>
</div>
<div class="wl-col list">
<p class="wl-item social-issues">
{{ w.socialAction.issue|chill_entity_render_box }}
</p>
</div>
</div>
{% endif %}
{% if w.accompanyingPeriodWorkEvaluations|length > 0 %}
<div class="wl-row">
<div class="wl-col title">
<h3>{{ 'accompanying_course_work.evaluations'|trans }}</h3>
</div>
<div class="wl-col list">
<ul class="small_in_title evaluations mt-1">
{% for e in w.accompanyingPeriodWorkEvaluations %}
<li>
{{ e.evaluation.title|localize_translatable_string }}
<ul class="columns">
<li>
<span class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
<b>{{ e.startDate|format_date('short') }}</b>
</li>
{% if e.endDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<b>{{ e.endDate|format_date('short') }}</b>
</li>
{% endif %}
{% if e.maxDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<b>{{ e.maxDate|format_date('short') }}</b>
</li>
{% endif %}
{% if e.warningInterval and e.warningInterval.d > 0 %}
<li>
{% set days = (e.warningInterval.d + e.warningInterval.m * 30) %}
<span class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
{{ 'accompanying_course_work.%days% days before max_date'|trans({'%days%': days }) }}
</li>
{% endif %}
</ul>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
</div>
</div>
<div class="item-row column">
{# SEULEMENT SI DÉTAILLÉ
{% if w.results|length > 0 %}
<div class="objective_results without-objectives">
<div class="objective">
<h4 class="title_label">{{ 'accompanying_course_work.goal'|trans }}</h4>
<p class="chill-no-data-statement">{{ 'accompanying_course_work.results without objective'|trans }}</p>
</div>
<div class="results">
<h4 class="title_label">{{ 'accompanying_course_work.results'|trans }}</h4>
<ul class="result_list">
{% for r in w.results %}
<li>{{ r.title|localize_translatable_string }}</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
{% if w.goals|length > 0 %}
{% for g in w.goals %}
<div class="objective_results with-objectives">
<div class="objective">
<h4 class="title_label">{{ 'accompanying_course_work.goal'|trans }}</h4>
<ul class="goal_title"><li>{{ g.goal.title|localize_translatable_string }}</li></ul>
</div>
<div class="results">
<h4 class="title_label">{{ 'accompanying_course_work.results'|trans }}</h4>
{% if g.results|length == 0 %}
<p class="chill-no-data-statement">{{ 'accompanying_course_work.no_results'|trans }}</p>
{% else %}
<ul class="result_list">
{% for r in g.results %}
<li>{{ r.title|localize_translatable_string }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% endfor %}
{% endif %}
#}
</div>
<div class="item-row separator">
<div class="updatedBy">
{{ 'Last updated by'|trans}} <b>{{ w.updatedBy|chill_entity_render_box }}</b>,<br>
{{ 'le ' ~ w.updatedAt|format_datetime('long', 'short') }}
</div>
<ul class="record_actions">
<li>
<a class="btn btn-edit" title="{{ 'Edit'|trans }}"
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"
></a>
</li>
<li>
<a class="btn btn-delete" title="{{ 'Delete'|trans }}"
href="{{ path('chill_person_accompanying_period_work_delete', { 'id': w.id } ) }}"
></a>
</li>
</ul>
</div>
</div>
{% endfor %}
</div>
{% endif %}
<ul class="record_actions sticky-form-buttons">
<li>

View File

@ -1,118 +0,0 @@
{% if works|length == 0 %}
<p class="chill-no-data-statement">{{ 'accompanying_course_work.Any work'|trans }}
<a class="btn btn-sm btn-create"
href="" title="TODO"></a>{# TODO link #}
</p>
{% endif %}
<div class="flex-table accompanying_course_work-list">
{% for w in works %}
<div class="item-bloc">
<div class="item-row">
<h2 class="badge-title">
<span class="title_label">{{ 'accompanying_course_work.action'|trans }}</span>
<span class="title_action">{{ w.socialAction|chill_entity_render_string }}</span>
</h2>
</div>
<div class="item-row separator">
<div class="timeline">
<ul>
<li class="completed">
<div class="date">
<span>{{ w.startDate|format_date('long') }}</span>
</div>
<div class="label">
<span>{{ 'accompanying_course_work.start_date'|trans }}</span>
</div>
</li>
{% if w.endDate == null %}
<li>
<div class="label no-label"></div>
</li>
{% else %}
<li class="{%if date(w.endDate) < date('now') %}completed{% endif %}">
<div class="date">
<span>{{ w.endDate|format_date('long') }}</span>
</div>
<div class="label">
<span>{{ 'accompanying_course_work.end_date'|trans }}</span>
</div>
</li>
{% endif %}
</ul>
</div>
</div>
<div class="">
{% if w.results|length > 0 and w.goals|length > 0 %}
{% endif %}
{% if w.results|length > 0 %}
<div class="objective_results without-objectives">
<div class="objective">
<h4 class="title_label">{{ 'accompanying_course_work.goal'|trans }}</h4>
<p class="chill-no-data-statement">{{ 'accompanying_course_work.results without objective'|trans }}</p>
</div>
<div class="results">
<h4 class="title_label">{{ 'accompanying_course_work.results'|trans }}</h4>
<ul class="result_list">
{% for r in w.results %}
<li>{{ r.title|localize_translatable_string }}</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
{% if w.goals|length > 0 %}
{% for g in w.goals %}
<div class="objective_results with-objectives">
<div class="objective">
<h4 class="title_label">{{ 'accompanying_course_work.goal'|trans }}</h4>
<ul class="goal_title"><li>{{ g.goal.title|localize_translatable_string }}</li></ul>
</div>
<div class="results">
<h4 class="title_label">{{ 'accompanying_course_work.results'|trans }}</h4>
{% if g.results|length == 0 %}
<p class="chill-no-data-statement">{{ 'accompanying_course_work.no_results'|trans }}</p>
{% else %}
<ul class="result_list">
{% for r in g.results %}
<li>{{ r.title|localize_translatable_string }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% endfor %}
{% endif %}
</div>
<div class="item-row separator">
<div class="updatedBy">
{{ 'Last updated by'|trans}} <b>{{ w.updatedBy|chill_entity_render_box }}</b>,<br>
{{ 'le ' ~ w.updatedAt|format_datetime('long', 'short') }}
</div>
<ul class="record_actions">
<li>
<a class="btn btn-edit" title="{{ 'Edit'|trans }}"
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"
>{% if buttonText is not defined or buttonText == true %}{{ 'Edit'|trans }}{% endif %}</a>
</li>
<li>
<a class="btn btn-delete" title="{{ 'Delete'|trans }}"
href="{{ path('chill_person_accompanying_period_work_delete', { 'id': w.id } ) }}"
>{% if buttonText is not defined or buttonText == true %}{{ 'Delete'|trans }}{% endif %}</a>
</li>
</ul>
</div>
</div>
{% endfor %}
</div>

View File

@ -2,14 +2,11 @@
{% for w in works | slice(0,5) %}
<a href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"
class="badge-link" title="{{ 'crud.social_action.title_link'|trans }}">
class="dashboard-link" title="{{ 'crud.social_action.title_link'|trans }}">
<h2 class="badge-title">
<span class="title_label">
<span>{{ 'accompanying_course_work.action'|trans }}</span>
</span>
<span class="title_action">
{{ w.socialAction|chill_entity_render_string }}
<div class="dashboard">
<span class="title_label"></span>
<span class="title_action"><span class="like-h3">{{ w.socialAction|chill_entity_render_string }}</span>
<ul class="small_in_title columns mt-3">
<li>
@ -75,7 +72,7 @@
</div>
</span>
</h2>
</div>
</a>{# {{ dump(w) }} #}
{% endfor %}

View File

@ -0,0 +1,160 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\PersonBundle\Serializer\Normalizer;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Templating\Entity\ClosingMotiveRender;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use DateTime;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Contracts\Translation\TranslatorInterface;
use function array_key_exists;
use function in_array;
use function is_array;
class AccompanyingPeriodDocGenNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface
{
use NormalizerAwareTrait;
private const IGNORE_FIRST_PASS_KEY = 'acc_period_ignore_first_pass';
private const PERIOD_NULL = [
'id' => '',
'closingDate' => DateTime::class,
'confidential' => '',
'confidentialText' => '',
'createdAt' => DateTime::class,
'createdBy' => User::class,
'emergency' => '',
'emergencyText' => '',
'openingDate' => DateTime::class,
'originText' => '',
'requestorAnonymous' => false,
'socialIssues' => [],
'intensity' => '',
'step' => '',
'closingMotiveText' => '',
'socialIssuesText' => '',
'scopes' => [],
'scopesText' => '',
'ref' => User::class,
'participations' => [],
];
private ClosingMotiveRender $closingMotiveRender;
private ScopeResolverDispatcher $scopeResolverDispatcher;
private SocialIssueRender $socialIssueRender;
private TranslatableStringHelper $translatableStringHelper;
private TranslatorInterface $translator;
public function __construct(
TranslatorInterface $translator,
TranslatableStringHelper $translatableStringHelper,
SocialIssueRender $socialIssueRender,
ClosingMotiveRender $closingMotiveRender,
ScopeResolverDispatcher $scopeResolverDispatcher
) {
$this->translator = $translator;
$this->translatableStringHelper = $translatableStringHelper;
$this->socialIssueRender = $socialIssueRender;
$this->closingMotiveRender = $closingMotiveRender;
$this->scopeResolverDispatcher = $scopeResolverDispatcher;
}
/**
* @param AccompanyingPeriod|null $period
*/
public function normalize($period, ?string $format = null, array $context = [])
{
if ($period instanceof AccompanyingPeriod) {
$ignored = $context[self::IGNORE_FIRST_PASS_KEY] ?? [];
$ignored[] = spl_object_hash($period);
$initial =
$this->normalizer->normalize($period, $format, array_merge(
$context,
[self::IGNORE_FIRST_PASS_KEY => $ignored, AbstractNormalizer::GROUPS => 'docgen:read']
));
// some transformation
$user = $initial['user'];
unset($initial['user']);
$scopes = $this->scopeResolverDispatcher->isConcerned($period) ? $this->scopeResolverDispatcher->resolveScope($period) : [];
if (!is_array($scopes)) {
$scopes = [$scopes];
}
return array_merge(
// get a first default data
$initial,
// and add data custom
[
'intensity' => $this->translator->trans($period->getIntensity()),
'step' => $this->translator->trans('accompanying_period.' . $period->getStep()),
'emergencyText' => $period->isEmergency() ? $this->translator->trans('accompanying_period.emergency') : '',
'confidentialText' => $period->isConfidential() ? $this->translator->trans('confidential') : '',
//'originText' => null !== $period->getOrigin() ? $this->translatableStringHelper->localize($period->getOrigin()->getLabel()) : '',
'closingMotiveText' => null !== $period->getClosingMotive() ?
$this->closingMotiveRender->renderString($period->getClosingMotive(), []) : '',
'ref' => $user,
'socialIssuesText' => implode(', ', array_map(function (SocialIssue $s) {
return $this->socialIssueRender->renderString($s, []);
}, $period->getSocialIssues()->toArray())),
'scopesText' => implode(', ', array_map(function (Scope $s) {
return $this->translatableStringHelper->localize($s->getName());
}, $scopes)),
'scopes' => $scopes,
]
);
} elseif (null === $period) {
return self::PERIOD_NULL;
}
throw new InvalidArgumentException('this neither an accompanying period or null');
}
public function supportsNormalization($data, ?string $format = null, array $context = []): bool
{
if ('docgen' !== $format) {
return false;
}
if ($data instanceof AccompanyingPeriod) {
if (array_key_exists(self::IGNORE_FIRST_PASS_KEY, $context)
&& in_array(spl_object_hash($data), $context[self::IGNORE_FIRST_PASS_KEY], true)) {
return false;
}
return true;
}
if (null === $data && AccompanyingPeriod::class === ($context['docgen:expects'] ?? null)) {
return true;
}
return false;
}
}

View File

@ -30,6 +30,8 @@ class SocialActionNormalizer implements NormalizerAwareInterface, NormalizerInte
public function normalize($socialAction, ?string $format = null, array $context = [])
{
switch ($format) {
case 'json':
return [
'id' => $socialAction->getId(),
'type' => 'social_work_social_action',
@ -38,10 +40,39 @@ class SocialActionNormalizer implements NormalizerAwareInterface, NormalizerInte
'desactivationDate' => $this->normalizer->normalize($socialAction->getDesactivationDate()),
'title' => $socialAction->getTitle(),
];
case 'docgen':
if (null === $socialAction) {
return ['id' => 0, 'title' => '', 'text' => ''];
}
public function supportsNormalization($data, ?string $format = null)
return [
'id' => $socialAction->getId(),
'text' => $this->render->renderString($socialAction, []),
'title' => $socialAction->getTitle(),
];
default:
throw new \Symfony\Component\Serializer\Exception\RuntimeException('format not supported');
}
}
public function supportsNormalization($data, ?string $format = null, array $context = [])
{
return $data instanceof SocialAction;
if ($data instanceof SocialAction && 'json' === $format) {
return true;
}
if ('docgen' === $format) {
if ($data instanceof SocialAction) {
return true;
}
if (null === $data && SocialAction::class === ($context['docgen:expects'] ?? null)) {
return true;
}
}
return false;
}
}

View File

@ -13,11 +13,11 @@ namespace Chill\PersonBundle\Serializer\Normalizer;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class SocialIssueNormalizer implements NormalizerAwareInterface, NormalizerInterface
class SocialIssueNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface
{
use NormalizerAwareTrait;
@ -31,6 +31,8 @@ class SocialIssueNormalizer implements NormalizerAwareInterface, NormalizerInter
public function normalize($socialIssue, ?string $format = null, array $context = [])
{
/** @var SocialIssue $socialIssue */
switch ($format) {
case 'json':
return [
'type' => 'social_issue',
'id' => $socialIssue->getId(),
@ -39,10 +41,36 @@ class SocialIssueNormalizer implements NormalizerAwareInterface, NormalizerInter
'title' => $socialIssue->getTitle(),
'text' => $this->render->renderString($socialIssue, []),
];
case 'docgen':
if (null === $socialIssue) {
return ['id' => 0, 'title' => '', 'text' => ''];
}
public function supportsNormalization($data, ?string $format = null): bool
return [
'id' => $socialIssue->getId(),
'title' => $socialIssue->getTitle(),
'text' => $this->render->renderString($socialIssue, []),
];
}
}
public function supportsNormalization($data, ?string $format = null, array $context = [])
{
return $data instanceof SocialIssue;
if ($data instanceof SocialIssue && 'json' === $format) {
return true;
}
if ('docgen' === $format) {
if ($data instanceof SocialIssue) {
return true;
}
if (null === $data && SocialIssue::class === ($context['docgen:expects'] ?? null)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,245 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\PersonBundle\Service\DocGenerator;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
use Chill\DocGeneratorBundle\Context\Exception\UnexpectedTypeException;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Templating\Entity\PersonRender;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function array_key_exists;
class AccompanyingPeriodContext implements
DocGeneratorContextWithAdminFormInterface,
DocGeneratorContextWithPublicFormInterface
{
private DocumentCategoryRepository $documentCategoryRepository;
private EntityManagerInterface $em;
private NormalizerInterface $normalizer;
private PersonRender $personRender;
private TranslatableStringHelperInterface $translatableStringHelper;
private TranslatorInterface $translator;
public function __construct(
DocumentCategoryRepository $documentCategoryRepository,
NormalizerInterface $normalizer,
TranslatableStringHelperInterface $translatableStringHelper,
EntityManagerInterface $em,
PersonRender $personRender,
TranslatorInterface $translator
) {
$this->documentCategoryRepository = $documentCategoryRepository;
$this->normalizer = $normalizer;
$this->translatableStringHelper = $translatableStringHelper;
$this->em = $em;
$this->personRender = $personRender;
$this->translator = $translator;
}
public function adminFormReverseTransform(array $data): array
{
if (array_key_exists('category', $data)) {
$data['category'] = [
'idInsideBundle' => $data['category']->getIdInsideBundle(),
'bundleId' => $data['category']->getBundleId(),
];
}
return $data;
}
public function adminFormTransform(array $data): array
{
$data = [
'mainPerson' => $data['mainPerson'] ?? false,
'mainPersonLabel' => $data['mainPersonLabel'] ?? $this->translator->trans('docgen.Main person'),
'person1' => $data['person1'] ?? false,
'person1Label' => $data['person1Label'] ?? $this->translator->trans('docgen.person 1'),
'person2' => $data['person2'] ?? false,
'person2Label' => $data['person2Label'] ?? $this->translator->trans('docgen.person 2'),
];
if (array_key_exists('category', $data)) {
$data['category'] = array_key_exists('category', $data) ?
$this->documentCategoryRepository->find($data['category']) : null;
}
return $data;
}
public function buildAdminForm(FormBuilderInterface $builder): void
{
$builder
->add('mainPerson', CheckboxType::class, [
'required' => false,
'label' => 'docgen.Ask for main person',
])
->add('mainPersonLabel', TextType::class, [
'label' => 'main person label',
'required' => true,
])
->add('person1', CheckboxType::class, [
'required' => false,
'label' => 'docgen.Ask for person 1',
])
->add('person1Label', TextType::class, [
'label' => 'person 1 label',
'required' => true,
])
->add('person2', CheckboxType::class, [
'required' => false,
'label' => 'docgen.Ask for person 2',
])
->add('person2Label', TextType::class, [
'label' => 'person 2 label',
'required' => true,
])
->add('category', EntityType::class, [
'placeholder' => 'Choose a document category',
'class' => 'ChillDocStoreBundle:DocumentCategory',
'query_builder' => static function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->where('c.documentClass = :docClass')
->setParameter('docClass', AccompanyingCourseDocument::class);
},
'choice_label' => function ($entity = null) {
return $entity ? $this->translatableStringHelper->localize($entity->getName()) : '';
},
]);
}
/**
* @param AccompanyingPeriod $entity
*/
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void
{
$options = $template->getOptions();
$persons = $entity->getCurrentParticipations()->map(static function (AccompanyingPeriodParticipation $p) { return $p->getPerson(); })
->toArray();
foreach (['mainPerson', 'person1', 'person2'] as $key) {
if ($options[$key] ?? false) {
$builder->add($key, EntityType::class, [
'class' => Person::class,
'choices' => $persons,
'choice_label' => function (Person $p) { return $this->personRender->renderString($p, []); },
'multiple' => false,
'expanded' => true,
'label' => $options[$key . 'Label'],
]);
}
}
}
public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array
{
if (!$entity instanceof AccompanyingPeriod) {
throw new UnexpectedTypeException($entity, AccompanyingPeriod::class);
}
$options = $template->getOptions();
$data = [];
$data['course'] = $this->normalizer->normalize($entity, 'docgen', ['docgen:expects' => AccompanyingPeriod::class]);
foreach (['mainPerson', 'person1', 'person2'] as $k) {
if ($options[$k]) {
$data[$k] = $this->normalizer->normalize($contextGenerationData[$k], 'docgen', ['docgen:expects' => Person::class]);
}
}
return $data;
}
public function getDescription(): string
{
return 'docgen.A basic context for accompanying period';
}
public function getEntityClass(): string
{
return AccompanyingPeriod::class;
}
public function getFormData(DocGeneratorTemplate $template, $entity): array
{
return [
'course' => $entity,
];
}
public static function getKey(): string
{
return self::class;
}
public function getName(): string
{
return 'Accompanying Period basic';
}
public function hasAdminForm(): bool
{
return true;
}
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool
{
$options = $template->getOptions();
return $options['mainPerson'] || $options['person1'] || $options['person2'];
}
/**
* @param AccompanyingPeriod $entity
*/
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
{
$doc = new AccompanyingCourseDocument();
$doc->setTitle($this->translatableStringHelper->localize($template->getName()))
->setDate(new DateTime())
->setDescription($this->translatableStringHelper->localize($template->getName()))
->setCourse($entity)
->setObject($storedObject);
if (array_key_exists('category', $template->getOptions()['category'])) {
$doc
->setCategory(
$this->documentCategoryRepository->find(
$template->getOptions()['category']
)
);
}
$this->em->persist($doc);
}
}

View File

@ -0,0 +1,122 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\PersonBundle\Service\DocGenerator;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextInterface;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class AccompanyingPeriodWorkContext implements
DocGeneratorContextInterface,
DocGeneratorContextWithAdminFormInterface,
DocGeneratorContextWithPublicFormInterface
{
private NormalizerInterface $normalizer;
private AccompanyingPeriodContext $periodContext;
public function __construct(
AccompanyingPeriodContext $periodContext,
NormalizerInterface $normalizer
) {
$this->periodContext = $periodContext;
$this->normalizer = $normalizer;
}
public function adminFormReverseTransform(array $data): array
{
return $this->periodContext->adminFormReverseTransform($data);
}
public function adminFormTransform(array $data): array
{
return $this->periodContext->adminFormTransform($data);
}
public function buildAdminForm(FormBuilderInterface $builder): void
{
$this->periodContext->buildAdminForm($builder);
$builder->remove('category');
}
/**
* @param AccompanyingPeriodWork $entity
*/
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void
{
$this->periodContext->buildPublicForm($builder, $template, $entity->getAccompanyingPeriod());
}
/**
* @param AccompanyingPeriodWork $entity
*/
public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array
{
$data = $this->periodContext->getData($template, $entity->getAccompanyingPeriod(), $contextGenerationData);
$data['work'] = $this->normalizer->normalize($entity, 'docgen', [
AbstractNormalizer::GROUPS => ['docgen:read'],
'docgen:expects' => AccompanyingPeriodWork::class,
]);
return $data;
}
public function getDescription(): string
{
return 'docgen.A context for accompanying period work';
}
public function getEntityClass(): string
{
return AccompanyingPeriodWork::class;
}
/**
* @param AccompanyingPeriodWork $entity
*/
public function getFormData(DocGeneratorTemplate $template, $entity): array
{
return $this->periodContext->getFormData($template, $entity->getAccompanyingPeriod());
}
public static function getKey(): string
{
return 'accompanying_period_work_regular';
}
public function getName(): string
{
return 'Accompanying period work';
}
public function hasAdminForm(): bool
{
return $this->periodContext->hasAdminForm();
}
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool
{
return $this->periodContext->hasPublicForm($template, $entity);
}
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
{
// TODO: Implement storeGenerated() method.
}
}

View File

@ -0,0 +1,181 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\PersonBundle\Service\DocGenerator;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Chill\PersonBundle\Repository\SocialWork\EvaluationRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class AccompanyingPeriodWorkEvaluationContext implements
DocGeneratorContextWithAdminFormInterface,
DocGeneratorContextWithPublicFormInterface
{
private AccompanyingPeriodWorkContext $accompanyingPeriodWorkContext;
private EntityManagerInterface $em;
private EvaluationRepository $evaluationRepository;
private NormalizerInterface $normalizer;
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
AccompanyingPeriodWorkContext $accompanyingPeriodWorkContext,
EntityManagerInterface $em,
EvaluationRepository $evaluationRepository,
NormalizerInterface $normalizer,
TranslatableStringHelperInterface $translatableStringHelper
) {
$this->accompanyingPeriodWorkContext = $accompanyingPeriodWorkContext;
$this->em = $em;
$this->evaluationRepository = $evaluationRepository;
$this->normalizer = $normalizer;
$this->translatableStringHelper = $translatableStringHelper;
}
public function adminFormReverseTransform(array $data): array
{
return array_merge(
$this->accompanyingPeriodWorkContext->adminFormReverseTransform($data),
[
'evaluations' => array_map(
static function (Evaluation $e) { return $e->getId(); },
$data['evaluations']
),
]
);
}
public function adminFormTransform(array $data): array
{
return array_merge(
$this->accompanyingPeriodWorkContext->adminFormTransform($data),
[
'evaluations' => array_map(
function ($id) { return $this->evaluationRepository->find($id); },
$data['evaluations'] ?? []
),
]
);
}
public function buildAdminForm(FormBuilderInterface $builder): void
{
$this->accompanyingPeriodWorkContext->buildAdminForm($builder);
$builder->remove('category');
$builder->add('evaluations', EntityType::class, [
'class' => Evaluation::class,
'label' => 'Linked evaluations',
'choices' => $this->evaluationRepository->findAll(),
'choice_label' => function (Evaluation $e) {
return $this->translatableStringHelper->localize($e->getTitle());
},
'multiple' => true,
'expanded' => true,
]);
}
/**
* @param AccompanyingPeriodWorkEvaluation $entity
*/
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void
{
$this->accompanyingPeriodWorkContext->buildPublicForm($builder, $template, $entity->getAccompanyingPeriodWork());
}
/**
* @param AccompanyingPeriodWorkEvaluation $entity
*/
public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array
{
$data = $this->accompanyingPeriodWorkContext
->getData($template, $entity->getAccompanyingPeriodWork(), $contextGenerationData);
$data['evaluation'] = $this->normalizer->normalize(
$entity,
'docgen',
[
'docgen:expect' => AccompanyingPeriodWorkEvaluation::class,
AbstractNormalizer::GROUPS => ['docgen:read'],
]
);
return $data;
}
public function getDescription(): string
{
return 'docgen.A context for accompanying period work evaluation';
}
public function getEntityClass(): string
{
return AccompanyingPeriodWorkEvaluation::class;
}
/**
* @param AccompanyingPeriodWorkEvaluation $entity
*/
public function getFormData(DocGeneratorTemplate $template, $entity): array
{
return $this->accompanyingPeriodWorkContext->getFormData(
$template,
$entity->getAccompanyingPeriodWork()
);
}
public static function getKey(): string
{
return 'accompanying_period_work_evaluation_regular';
}
public function getName(): string
{
return 'Accompanying period work context';
}
public function hasAdminForm(): bool
{
return true;
}
/**
* @param AccompanyingPeriodWorkEvaluation $entity
*/
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool
{
return $this->accompanyingPeriodWorkContext
->hasPublicForm($template, $entity->getAccompanyingPeriodWork());
}
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
{
$doc = new AccompanyingPeriodWorkEvaluationDocument();
$doc->setStoredObject($storedObject)
->setAccompanyingPeriodWorkEvaluation($entity)
->setTemplate($template);
$this->em->persist($doc);
}
}

View File

@ -0,0 +1,137 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Serializer\Normalizer;
use Chill\MainBundle\Entity\Scope;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* @internal
* @coversNothing
*/
final class AccompanyingPeriodDocGenNormalizerTest extends KernelTestCase
{
private NormalizerInterface $normalizer;
protected function setUp(): void
{
self::bootKernel();
$this->normalizer = self::$container->get(NormalizerInterface::class);
}
public function testNormalize()
{
$period = new AccompanyingPeriod();
$period->setConfidential(true);
$period->setEmergency(true);
$period->setOrigin((new AccompanyingPeriod\Origin())->setLabel(['fr' => 'origin']));
$period->setClosingMotive((new AccompanyingPeriod\ClosingMotive())->setName(['closing']));
$period->addScope((new Scope())->setName(['fr' => 'scope1']));
$period->addScope((new Scope())->setName(['fr' => 'scope2']));
$period->addSocialIssue((new SocialIssue())->setTitle(['fr' => 'issue1']));
$period->addSocialIssue((new SocialIssue())->setTitle(['fr' => 'issue2']));
$data = $this->normalizer->normalize($period, 'docgen', ['docgen:expects' => AccompanyingPeriod::class]);
$expected = [
'id' => null,
'closingDate' => '@ignored',
'confidential' => true,
'confidentialText' => 'confidentiel',
'createdAt' => '@ignored',
'createdBy' => '@ignored',
'emergency' => true,
'emergencyText' => 'Urgent',
'openingDate' => '@ignored',
'originText' => 'origin',
'requestorAnonymous' => false,
'socialIssues' => '@ignored',
'intensity' => 'ponctuel',
'step' => 'Brouillon',
'closingMotiveText' => 'closing',
'socialIssuesText' => 'issue1, issue2',
'scopes' => '@ignored',
'scopesText' => 'scope1, scope2',
'ref' => '@ignored',
'participations' => '@ignored',
];
$this->assertIsArray($data);
$this->assertEqualsCanonicalizing(array_keys($expected), array_keys($data));
foreach ($expected as $key => $item) {
if ('@ignored' === $item) {
continue;
}
$this->assertEquals($item, $data[$key]);
}
}
public function testNormalizeNull()
{
$data = $this->normalizer->normalize(null, 'docgen', ['docgen:expects' => AccompanyingPeriod::class]);
$expected = [
'id' => '',
'closingDate' => '@ignored',
'confidential' => '',
'confidentialText' => '',
'createdAt' => '@ignored',
'createdBy' => '@ignored',
'emergency' => '',
'emergencyText' => '',
'openingDate' => '@ignored',
'originText' => '',
'requestorAnonymous' => '',
'socialIssues' => '@ignored',
'intensity' => '',
'step' => '',
'closingMotiveText' => '',
'socialIssuesText' => '',
'scopes' => '@ignored',
'scopesText' => '',
'ref' => '@ignored',
'participations' => '@ignored',
];
$this->assertIsArray($data);
$this->assertEqualsCanonicalizing(array_keys($expected), array_keys($data));
foreach ($expected as $key => $item) {
if ('@ignored' === $item) {
continue;
}
$this->assertEquals($item, $data[$key]);
}
}
public function testNormalizeParticipations()
{
$period = new AccompanyingPeriod();
$period->addPerson($person = new Person());
$person->setFirstName('test');
$data = $this->normalizer->normalize($period, 'docgen', ['docgen:expects' => AccompanyingPeriod::class]);
$this->assertIsArray($data);
$this->assertArrayHasKey('participations', $data);
$this->assertCount(1, $data['participations']);
$this->assertArrayHasKey('currentParticipations', $data);
$this->assertCount(1, $data['currentParticipations']);
}
}

View File

@ -0,0 +1,100 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Serializer\Normalizer;
use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkGoal;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\SocialWork\Goal;
use Chill\PersonBundle\Entity\SocialWork\Result;
use DateTimeImmutable;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* @internal
* @coversNothing
*/
final class AccompanyingPeriodWorkDocGenNormalizerTest extends KernelTestCase
{
private NormalizerInterface $normalizer;
protected function setUp()
{
parent::bootKernel();
$this->normalizer = self::$container->get(NormalizerInterface::class);
}
public function testNormalizationNull()
{
$actual = $this->normalizer->normalize(null, 'docgen', [
'docgen:expects' => AccompanyingPeriodWork::class,
AbstractNormalizer::GROUPS => ['docgen:read'],
]);
dump($actual);
$expected = [
'id' => '',
];
$this->assertIsArray($actual);
$this->assertEqualsCanonicalizing(array_keys($expected), array_keys($actual));
foreach ($expected as $key => $item) {
if ('@ignored' === $item) {
continue;
}
$this->assertEquals($item, $actual[$key]);
}
}
public function testNormlalize()
{
$work = new AccompanyingPeriodWork();
$work
->addPerson((new Person())->setFirstName('hello')->setLastName('name'))
->addGoal($g = new AccompanyingPeriodWorkGoal())
->addResult($r = new Result())
->setCreatedAt(new DateTimeImmutable())
->setUpdatedAt(new DateTimeImmutable())
->setCreatedBy($user = new User())
->setUpdatedBy($user);
$g->addResult($r)->setGoal($goal = new Goal());
$goal->addResult($r);
$actual = $this->normalizer->normalize($work, 'docgen', [
'docgen:expects' => AccompanyingPeriodWork::class,
AbstractNormalizer::GROUPS => ['docgen:read'],
]);
var_dump($actual);
$expected = [
'id' => 0,
];
$this->assertIsArray($actual);
$this->assertEqualsCanonicalizing(array_keys($expected), array_keys($actual));
foreach ($expected as $key => $item) {
if ('@ignored' === $item) {
continue;
}
$this->assertEquals($item, $actual[$key]);
}
}
}

View File

@ -198,6 +198,7 @@ Resources: Interlocuteurs privilégiés
Any requestor to this accompanying course: Aucun demandeur pour ce parcours
Social actions: Actions d'accompagnement
Last social actions: Les dernières actions d'accompagnement
Social issue: Problématique sociale
Social issues: Problématiques sociales
Last events on accompanying course: Dernières actions de suivi
Edit & activate accompanying course: Modifier et valider
@ -377,9 +378,14 @@ accompanying_period:
dates: Période
dates_from_%opening_date%: Ouvert depuis le %opening_date%
dates_from_%opening_date%_to_%closing_date%: Ouvert du %opening_date% au %closing_date%
DRAFT: Brouillon
CONFIRMED: Confirmé
CLOSED: Clotûré
emergency: Urgent
occasional: ponctuel
regular: régulier
Confidential: confidentiel
confidential: confidentiel
Draft: brouillon
Confirmed: en file active
Closed: Cloturé
@ -429,10 +435,14 @@ accompanying_course_work:
create_date: Date de création
start_date: Date de début
end_date: Date de fin
max_date: Date d'échéance
warning_interval: Rappel
'%days% days before max_date': "%days% jour(s) avant l'échéance"
results without objective: Aucun objectif - motif - dispositif
no_results: Aucun résultat - orientation
results: Résultats - orientations
goal: Objectif - motif - dispositif
evaluations: Évaluations
Any work: Aucune action d'accompagnement
remove: Supprimer une action d'accompagnement
social_evaluation: Évaluation
@ -443,3 +453,15 @@ Household addresses: Adresses de domicile
Insert an address: Insérer une adresse
see social issues: Voir les problématiques sociales
see persons associated: Voir les usagers concernés
docgen:
Main person: Personne principale
person 1: Première personne
person 2: Seconde personne
Ask for main person: Demander à l'utilisateur de préciser la personne principale
Ask for person 1: Demander à l'utilisateur de préciser la première personne
Ask for person 2: Demander à l'utilisateur de préciser la seconde personne
Accompanying period work: Actions
A basic context for accompanying period: Contexte pour les parcours
A context for accompanying period work: Contexte pour les actions d'accompagnement
A context for accompanying period work evaluation: Contexte pour les évaluations dans les actions d'accompagnement