Merge branch 'issue178_affichage_metiers' into 'master'

Display users and jobs at the date that they executed some task

See merge request Chill-Projet/chill-bundles!641
This commit is contained in:
Julien Fastré 2024-06-12 14:41:40 +00:00
commit cd5001ac74
45 changed files with 468 additions and 159 deletions

View File

@ -42,11 +42,13 @@
"@fullcalendar/vue3": "^6.1.4",
"@popperjs/core": "^2.9.2",
"@types/leaflet": "^1.9.3",
"@types/dompurify": "^3.0.5",
"dropzone": "^5.7.6",
"es6-promise": "^4.2.8",
"leaflet": "^1.7.1",
"marked": "^12.0.2",
"masonry-layout": "^4.2.2",
"mime": "^3.0.0",
"mime": "^4.0.0",
"swagger-ui": "^4.15.5",
"vis-network": "^9.1.0",
"vue": "^3.2.37",

View File

@ -68,7 +68,7 @@
<div class="wl-col title"><h3>{{ 'Referrer'|trans }}</h3></div>
<div class="wl-col list">
<p class="wl-item">
<span class="badge-user">{{ activity.user|chill_entity_render_box }}</span>
<span class="badge-user">{{ activity.user|chill_entity_render_box({'at_date': activity.date}) }}</span>
</p>
</div>
</div>

View File

@ -87,7 +87,8 @@
<li>
{% if bloc.type == 'user' %}
<span class="badge-user">
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false }) }}
hello
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false, 'at_date': entity.date }) }}
</span>
{% else %}
{{ _self.insert_onthefly(bloc.type, item) }}
@ -114,7 +115,7 @@
<li>
{% if bloc.type == 'user' %}
<span class="badge-user">
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false }) }}
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false, 'at_date': entity.date }) }}
</span>
{% else %}
{{ _self.insert_onthefly(bloc.type, item) }}
@ -142,7 +143,7 @@
<span class="wl-item">
{% if bloc.type == 'user' %}
<span class="badge-user">
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false }) }}
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false, 'at_date': entity.date }) }}
{%- if context == 'calendar_accompanyingCourse' or context == 'calendar_person' %}
{% set invite = entity.inviteForUser(item) %}
{% if invite is not null %}

View File

@ -41,7 +41,7 @@
{% if activity.user and t.userVisible %}
<li>
<span class="item-key">{{ 'Referrer'|trans ~ ': ' }}</span>
<span class="badge-user">{{ activity.user|chill_entity_render_box }}</span>
<span class="badge-user">{{ activity.user|chill_entity_render_box({'at_date': activity.date}) }}</span>
</li>
{% endif %}

View File

@ -37,7 +37,7 @@
{%- if entity.user is not null %}
<dt class="inline">{{ 'Referrer'|trans|capitalize }}</dt>
<dd>
<span class="badge-user">{{ entity.user|chill_entity_render_box }}</span>
<span class="badge-user">{{ entity.user|chill_entity_render_box({'at_date': entity.date}) }}</span>
</dd>
{% endif %}

View File

@ -49,13 +49,13 @@
<li>
<span>
<abbr class="referrer" title={{ 'Created by'|trans }}>{{ 'By'|trans }}:</abbr>
<b>{{ entity.createdBy|chill_entity_render_box }}</b>
<b>{{ entity.createdBy|chill_entity_render_box({'at_date': entity.date}) }}</b>
</span>
</li>
<li>
<span>
<abbr class="referrer" title={{ 'Created for'|trans }}>{{ 'For'|trans }}:</abbr>
<b>{{ entity.agent|chill_entity_render_box }}</b>
<b>{{ entity.agent|chill_entity_render_box({'at_date': entity.date}) }}</b>
</span>
</li>

View File

@ -18,10 +18,10 @@
<dd>{{ entity.type|chill_entity_render_box }}</dd>
<dt class="inline">{{ 'Created by'|trans }}</dt>
<dd>{{ entity.createdBy }}</dd>
<dd>{{ entity.createdBy|chill_entity_render_box({'at_date': entity.date}) }}</dd>
<dt class="inline">{{ 'Created for'|trans }}</dt>
<dd>{{ entity.agent }}</dd>
<dd>{{ entity.agent|chill_entity_render_box({'at_date': entity.date}) }}</dd>
<dt class="inline">{{ 'Asideactivity location'|trans }}</dt>
{%- if entity.location.name is defined -%}

View File

@ -524,6 +524,16 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente
return $this->startDate;
}
/**
* get the date of the calendar.
*
* Useful for showing the date of the calendar event, required by twig in some places.
*/
public function getDate(): ?\DateTimeImmutable
{
return $this->getStartDate();
}
public function getStatus(): ?string
{
return $this->status;

View File

@ -16,6 +16,7 @@
:removableIfSet="false"
:displayPicked="false"
:suggested="this.suggestedUsers"
:label="'Utilisateur principal'"
@addNewEntity="setMainUser"
></pick-entity>
</div>

View File

@ -55,7 +55,7 @@
<div class="item-col">
<ul class="list-content">
{% if calendar.mainUser is not empty %}
<span class="badge-user">{{ calendar.mainUser|chill_entity_render_box }}</span>
<span class="badge-user">{{ calendar.mainUser|chill_entity_render_box({'at_date': calendar.startDate}) }}</span>
{% endif %}
</ul>
</div>
@ -132,7 +132,7 @@
<li class="cancel">
<span class="createdBy">
{{ 'Created by'|trans }}
<b>{{ calendar.activity.createdBy|chill_entity_render_string }}</b>, {{ 'on'|trans }} {{ calendar.activity.createdAt|format_datetime('short', 'short') }}
<b>{{ calendar.activity.createdBy|chill_entity_render_string({'at_date': calendar.activity.createdAt}) }}</b>, {{ 'on'|trans }} {{ calendar.activity.createdAt|format_datetime('short', 'short') }}
</span>
</li>
{% if is_granted('CHILL_ACTIVITY_SEE', calendar.activity) %}

View File

@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\DocGeneratorBundle\Test;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* @template T of object
*/
abstract class DocGenNormalizerTestAbstract extends KernelTestCase
{
public function testNullValueHasSameKeysAsNull(): void
{
$normalizedObject = $this->getNormalizer()->normalize($this->provideNotNullObject(), 'docgen', [
AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => $this->provideDocGenExpectClass(),
]);
$nullNormalizedObject = $this->getNormalizer()->normalize(null, 'docgen', [
AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => $this->provideDocGenExpectClass(),
]);
self::assertEqualsCanonicalizing(array_keys($normalizedObject), array_keys($nullNormalizedObject));
self::assertArrayHasKey('isNull', $nullNormalizedObject, 'each object must have an "isNull" key');
self::assertTrue($nullNormalizedObject['isNull'], 'isNull key must be true for null objects');
self::assertFalse($normalizedObject['isNull'], 'isNull key must be false for null objects');
foreach ($normalizedObject as $key => $value) {
if (in_array($key, ['isNull', 'type'])) {
continue;
}
if (is_array($value)) {
if (array_is_list($value)) {
self::assertEquals([], $nullNormalizedObject[$key], "list must be serialized as an empty array, in {$key}");
} else {
self::assertEqualsCanonicalizing(array_keys($value), array_keys($nullNormalizedObject[$key]), "sub-object must have the same keys, in {$key}");
}
} elseif (is_string($value)) {
self::assertEquals('', $nullNormalizedObject[$key], 'strings must be ');
}
}
}
/**
* @return T
*/
abstract public function provideNotNullObject(): object;
/**
* @return class-string<T>
*/
abstract public function provideDocGenExpectClass(): string;
abstract public function getNormalizer(): NormalizerInterface;
}

View File

@ -274,13 +274,13 @@ class User implements UserInterface, \Stringable
return $this->mainLocation;
}
public function getMainScope(?\DateTimeImmutable $at = null): ?Scope
public function getMainScope(?\DateTimeImmutable $atDate = null): ?Scope
{
$at ??= new \DateTimeImmutable('now');
$atDate ??= new \DateTimeImmutable('now');
foreach ($this->scopeHistories as $scopeHistory) {
if ($at >= $scopeHistory->getStartDate() && (
null === $scopeHistory->getEndDate() || $at < $scopeHistory->getEndDate()
if ($atDate >= $scopeHistory->getStartDate() && (
null === $scopeHistory->getEndDate() || $atDate < $scopeHistory->getEndDate()
)) {
return $scopeHistory->getScope();
}
@ -326,13 +326,13 @@ class User implements UserInterface, \Stringable
return $this->salt;
}
public function getUserJob(?\DateTimeImmutable $at = null): ?UserJob
public function getUserJob(?\DateTimeImmutable $atDate = null): ?UserJob
{
$at ??= new \DateTimeImmutable('now');
$atDate ??= new \DateTimeImmutable('now');
foreach ($this->jobHistories as $jobHistory) {
if ($at >= $jobHistory->getStartDate() && (
null === $jobHistory->getEndDate() || $at < $jobHistory->getEndDate()
if ($atDate >= $jobHistory->getStartDate() && (
null === $jobHistory->getEndDate() || $atDate < $jobHistory->getEndDate()
)) {
return $jobHistory->getJob();
}
@ -346,6 +346,11 @@ class User implements UserInterface, \Stringable
return $this->jobHistories;
}
public function getUserScopeHistories(): Collection
{
return $this->scopeHistories;
}
/**
* @return ArrayCollection|UserJobHistory[]
*/

View File

@ -43,7 +43,14 @@ var download_report = (url, container) => {
content = URL.createObjectURL(blob);
}
extension = mime.getExtension(type);
const extensions = new Map();
extensions.set('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xlsx');
extensions.set('application/vnd.oasis.opendocument.spreadsheet', 'ods');
extensions.set('application/vnd.ms-excel', 'xlsx');
extensions.set('text/csv', 'csv');
extensions.set('text/csv; charset=utf-8', 'csv');
extension = extensions.get(type);
link.appendChild(document.createTextNode(download_text));
link.classList.add("btn", "btn-action");
@ -55,7 +62,7 @@ var download_report = (url, container) => {
container.innerHTML = "";
container.appendChild(link);
}).catch(function(error) {
console.log(error);
console.error(error);
var problem_text =
document.createTextNode("Problem during download");

View File

@ -139,8 +139,8 @@ const postprocess = (html: string): string => {
}
const convertMarkdownToHtml = (markdown: string): string => {
marked.use({'hooks': {postprocess, preprocess}});
const rawHtml = marked(markdown);
marked.use({'hooks': {postprocess, preprocess}, 'async': false});
const rawHtml = marked(markdown) as string;
return rawHtml;
};

View File

@ -40,7 +40,7 @@
{{ 'by_user'|trans ~ ' ' }}
{% endif %}
<span class="user">
{{ user|chill_entity_render_box(options['user']) }}
{{ user|chill_entity_render_box({'at_date': comment.date}) }}
</span>
{% endif %}
</div>

View File

@ -1,10 +1,10 @@
<span class="chill-entity entity-user">
{{- user.label }}
{%- if opts['user_job'] and user.userJob(opts['at']) is not null %}
<span class="user-job">({{ user.userJob(opts['at']).label|localize_translatable_string }})</span>
{%- if opts['user_job'] and user.userJob(opts['at_date']) is not null %}
<span class="user-job">({{ user.userJob(opts['at_date']).label|localize_translatable_string }})</span>
{%- endif -%}
{%- if opts['main_scope'] and user.mainScope(opts['at']) is not null %}
<span class="main-scope">({{ user.mainScope(opts['at']).name|localize_translatable_string }})</span>
{%- if opts['main_scope'] and user.mainScope(opts['at_date']) is not null %}
<span class="main-scope">({{ user.mainScope(opts['at_date']).name|localize_translatable_string }})</span>
{%- endif -%}
{%- if opts['absence'] and user.isAbsent %}
<span class="badge bg-danger rounded-pill" title="{{ 'absence.Absent'|trans|escape('html_attr') }}">{{ 'absence.A'|trans }}</span>

View File

@ -21,7 +21,7 @@
</span>
{% if not c.notification.isSystem %}
<span class="badge-user">
{{ c.notification.sender|chill_entity_render_string }}
{{ c.notification.sender|chill_entity_render_string({'at_date': c.notification.date}) }}
</span>
{% else %}
<span class="badge-user system">{{ 'notification.is_system'|trans }}</span>
@ -53,7 +53,7 @@
{% endif %}
{% for a in c.notification.addressees %}
<span class="badge-user">
{{ a|chill_entity_render_string }}
{{ a|chill_entity_render_string({'at_date': c.notification.date}) }}
</span>
{% endfor %}
{% for a in c.notification.addressesEmails %}

View File

@ -1,7 +1,7 @@
{% extends "@ChillMain/layout.html.twig" %}
{% block title 'notification.show notification from %sender%'|trans(
{ '%sender%': notification.sender|chill_entity_render_string }
{ '%sender%': notification.sender|chill_entity_render_string({'at_date': notification.date}) }
) ~ ' ' ~ notification.title %}
{% block js %}

View File

@ -31,14 +31,14 @@
<div class="row">
<div class="col-sm-12">
{{ 'By'|trans }}
{{ step.previous.transitionBy|chill_entity_render_box }},
{{ step.previous.transitionBy|chill_entity_render_box({'at_date': step.previous.transitionAt }) }},
{{ step.previous.transitionAt|format_datetime('short', 'short') }}
</div>
</div>
{% else %}
<div class="row">
<div class="col-sm-4">{{ 'workflow.Created by'|trans }}</div>
<div class="col-sm-8">{{ step.entityWorkflow.createdBy|chill_entity_render_box }}</div>
<div class="col-sm-8">{{ step.entityWorkflow.createdBy|chill_entity_render_box({'at_date': step.entityWorkflow.createdAt}) }}</div>
</div>
<div class="row">
<div class="col-sm-4">{{ 'Le'|trans }}</div>
@ -110,8 +110,8 @@
{% if entity_workflow.currentStep.destUserByAccessKey|length > 0 %}
<p><b>{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }}&nbsp;:</b></p>
<ul>
{% for u in entity_workflow.currentStep.destUserByAccessKey %}
<li>{{ u|chill_entity_render_box }}</li>
{% for u in entity_workflow.currentStepChained.destUserByAccessKey %}
<li>{{ u|chill_entity_render_box({'at_date': entity_workflow.currentStepChained.previous.transitionAt }) }}</li>
{% endfor %}
</ul>
{% endif %}

View File

@ -42,7 +42,7 @@
<div class="item-col" style="width: inherit;">
{% if step.transitionBy is not null %}
<div>
{{ step.transitionBy|chill_entity_render_box }}
{{ step.transitionBy|chill_entity_render_box({'at_date': step.transitionAt}) }}
</div>
{% endif %}
<div>
@ -76,7 +76,7 @@
<p><b>{{ 'workflow.Users allowed to apply transition'|trans }}&nbsp;: </b></p>
<ul>
{% for u in step.destUser %}
<li>{{ u|chill_entity_render_box }}</li>
<li>{{ u|chill_entity_render_box({'at_date': step.previous.transitionAt}) }}</li>
{% endfor %}
</ul>
{% endif %}
@ -85,7 +85,7 @@
<p><b>{{ 'workflow.Users put in Cc'|trans }}&nbsp;: </b></p>
<ul>
{% for u in step.ccUser %}
<li>{{ u|chill_entity_render_box }}</li>
<li>{{ u|chill_entity_render_box({'at_date': step.previous.transitionAt}) }}</li>
{% endfor %}
</ul>
{% endif %}
@ -103,7 +103,7 @@
<p><b>{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }}&nbsp;:</b></p>
<ul>
{% for u in step.destUserByAccessKey %}
<li>{{ u|chill_entity_render_box }}</li>
<li>{{ u|chill_entity_render_box({'at_date': step.previous.transitionAt}) }}</li>
{% endfor %}
</ul>
{% endif %}

View File

@ -3,7 +3,7 @@
{% if step.previous is not null %}
<li>
<span class="item-key">{{ 'By'|trans ~ ' : ' }}</span>
<b>{{ step.previous.transitionBy|chill_entity_render_box }}</b>
<b>{{ step.previous.transitionBy|chill_entity_render_box({'at_date': step.previous.transitionAt }) }}</b>
</li>
<li>
<span class="item-key">{{ 'Le'|trans ~ ' : ' }}</span>
@ -12,19 +12,19 @@
<li>
<span class="item-key">{{ 'workflow.For'|trans ~ ' : ' }}</span>
<b>
{% for d in step.destUser %}{{ d|chill_entity_render_string }}{% if not loop.last %}, {% endif %}{% endfor %}
{% for d in step.destUser %}{{ d|chill_entity_render_string({'at_date': step.previous.transitionAt}) }}{% if not loop.last %}, {% endif %}{% endfor %}
</b>
</li>
<li>
<span class="item-key">{{ 'workflow.Cc'|trans ~ ' : ' }}</span>
<b>
{% for u in step.ccUser %}{{ u|chill_entity_render_string }}{% if not loop.last %}, {% endif %}{% endfor %}
{% for u in step.ccUser %}{{ u|chill_entity_render_string({'at_date': step.previous.transitionAt }) }}{% if not loop.last %}, {% endif %}{% endfor %}
</b>
</li>
{% else %}
<li>
<span class="item-key">{{ 'workflow.Created by'|trans ~ ' : ' }}</span>
<b>{{ step.entityWorkflow.createdBy|chill_entity_render_box }}</b>
<b>{{ step.entityWorkflow.createdBy|chill_entity_render_box({'at_date': step.entityWorkflow.createdAt }) }}</b>
</li>
<li>
<span class="item-key">{{ 'Le'|trans ~ ' : ' }}</span>

View File

@ -54,7 +54,7 @@ class EntityWorkflowStepNormalizer implements NormalizerAwareInterface, Normaliz
$data['transitionPreviousBy'] = $this->normalizer->normalize(
$previous->getTransitionBy(),
$format,
$context
[...$context, UserNormalizer::AT_DATE => $previous->getTransitionAt()]
);
$data['transitionPreviousAt'] = $this->normalizer->normalize(
$previous->getTransitionAt(),

View File

@ -47,7 +47,7 @@ class NotificationNormalizer implements NormalizerAwareInterface, NormalizerInte
'message' => $object->getMessage(),
'relatedEntityClass' => $object->getRelatedEntityClass(),
'relatedEntityId' => $object->getRelatedEntityId(),
'sender' => $this->normalizer->normalize($object->getSender(), $format, $context),
'sender' => $this->normalizer->normalize($object->getSender(), $format, [...$context, UserNormalizer::AT_DATE => $object->getDate()]),
'title' => $object->getTitle(),
'entity' => null !== $entity ? $this->normalizer->normalize($entity, $format, $context) : null,
];

View File

@ -19,6 +19,7 @@ use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Templating\Entity\UserRender;
use libphonenumber\PhoneNumber;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
@ -27,6 +28,8 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware
{
use NormalizerAwareTrait;
final public const AT_DATE = 'chill:user:at_date';
final public const NULL_USER = [
'type' => 'user',
'id' => '',
@ -38,12 +41,18 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware
'isAbsent' => false,
];
public function __construct(private readonly UserRender $userRender)
public function __construct(private readonly UserRender $userRender, private readonly ClockInterface $clock)
{
}
/**
* @param mixed|null $format
*
* @throws \Symfony\Component\Serializer\Exception\ExceptionInterface
*/
public function normalize($object, $format = null, array $context = [])
{
/** @var array{"chill:user:at_date"?: \DateTimeImmutable|\DateTime} $context */
/** @var User $object */
$userJobContext = array_merge(
$context,
@ -74,18 +83,23 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware
return [...self::NULL_USER, 'phonenumber' => $this->normalizer->normalize(null, $format, $phonenumberContext), 'civility' => $this->normalizer->normalize(null, $format, $civilityContext), 'user_job' => $this->normalizer->normalize(null, $format, $userJobContext), 'main_center' => $this->normalizer->normalize(null, $format, $centerContext), 'main_scope' => $this->normalizer->normalize(null, $format, $scopeContext), 'current_location' => $this->normalizer->normalize(null, $format, $locationContext), 'main_location' => $this->normalizer->normalize(null, $format, $locationContext)];
}
$at = $context[self::AT_DATE] ?? $this->clock->now();
if ($at instanceof \DateTime) {
$at = \DateTimeImmutable::createFromMutable($at);
}
$data = [
'type' => 'user',
'id' => $object->getId(),
'username' => $object->getUsername(),
'text' => $this->userRender->renderString($object, []),
'text' => $this->userRender->renderString($object, ['at_date' => $at]),
'text_without_absent' => $this->userRender->renderString($object, ['absence' => false]),
'label' => $object->getLabel(),
'email' => (string) $object->getEmail(),
'phonenumber' => $this->normalizer->normalize($object->getPhonenumber(), $format, $phonenumberContext),
'user_job' => $this->normalizer->normalize($object->getUserJob(), $format, $userJobContext),
'user_job' => $this->normalizer->normalize($object->getUserJob($at), $format, $userJobContext),
'main_center' => $this->normalizer->normalize($object->getMainCenter(), $format, $centerContext),
'main_scope' => $this->normalizer->normalize($object->getMainScope(), $format, $scopeContext),
'main_scope' => $this->normalizer->normalize($object->getMainScope($at), $format, $scopeContext),
'isAbsent' => $object->isAbsent(),
];

View File

@ -12,8 +12,14 @@ declare(strict_types=1);
namespace Chill\MainBundle\Templating\Entity;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use DateTime;
use DateTimeImmutable;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
/**
* @implements ChillEntityRenderInterface<User>
@ -24,17 +30,32 @@ class UserRender implements ChillEntityRenderInterface
'main_scope' => true,
'user_job' => true,
'absence' => true,
'at' => null,
'at_date' => null, // instanceof DateTimeInterface
];
public function __construct(private readonly TranslatableStringHelper $translatableStringHelper, private readonly \Twig\Environment $engine, private readonly TranslatorInterface $translator)
{
public function __construct(
private readonly TranslatableStringHelperInterface $translatableStringHelper,
private readonly \Twig\Environment $engine,
private readonly TranslatorInterface $translator,
private readonly ClockInterface $clock,
) {
}
/**
* @throws LoaderError
* @throws RuntimeError
* @throws SyntaxError
*/
public function renderBox($entity, array $options): string
{
$opts = \array_merge(self::DEFAULT_OPTIONS, $options);
if (null === $opts['at_date']) {
$opts['at_date'] = $this->clock->now();
} elseif ($opts['at_date'] instanceof \DateTime) {
$opts['at_date'] = \DateTimeImmutable::createFromMutable($opts['at_date']);
}
return $this->engine->render('@ChillMain/Entity/user.html.twig', [
'user' => $entity,
'opts' => $opts,
@ -45,16 +66,24 @@ class UserRender implements ChillEntityRenderInterface
{
$opts = \array_merge(self::DEFAULT_OPTIONS, $options);
$str = $entity->getLabel();
// $immutableAtDate = $opts['at_date'] instanceOf DateTime ? DateTimeImmutable::createFromMutable($opts['at_date']) : $opts['at_date'];
if (null !== $entity->getUserJob($opts['at']) && $opts['user_job']) {
$str .= ' ('.$this->translatableStringHelper
->localize($entity->getUserJob($opts['at'])->getLabel()).')';
if (null === $opts['at_date']) {
$opts['at_date'] = $this->clock->now();
} elseif ($opts['at_date'] instanceof \DateTime) {
$opts['at_date'] = \DateTimeImmutable::createFromMutable($opts['at_date']);
}
if (null !== $entity->getMainScope($opts['at']) && $opts['main_scope']) {
$str = $entity->getLabel();
if (null !== $entity->getUserJob($opts['at_date']) && $opts['user_job']) {
$str .= ' ('.$this->translatableStringHelper
->localize($entity->getMainScope($opts['at'])->getName()).')';
->localize($entity->getUserJob($opts['at_date'])->getLabel()).')';
}
if (null !== $entity->getMainScope($opts['at_date']) && $opts['main_scope']) {
$str .= ' ('.$this->translatableStringHelper
->localize($entity->getMainScope($opts['at_date'])->getName()).')';
}
if ($entity->isAbsent() && $opts['absence']) {

View File

@ -25,6 +25,7 @@ use libphonenumber\PhoneNumberUtil;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Clock\MockClock;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
@ -122,7 +123,9 @@ final class UserNormalizerTest extends TestCase
$userRender = $this->prophesize(UserRender::class);
$userRender->renderString(Argument::type(User::class), Argument::type('array'))->willReturn($user ? $user->getLabel() : '');
$normalizer = new UserNormalizer($userRender->reveal());
$clock = new MockClock(new \DateTimeImmutable('now'));
$normalizer = new UserNormalizer($userRender->reveal(), $clock);
$normalizer->setNormalizer(new class () implements NormalizerInterface {
public function normalize($object, ?string $format = null, array $context = [])
{

View File

@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Templating\Entity;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Templating\Entity\UserRender;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Clock\MockClock;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Environment;
/**
* @internal
*
* @coversNothing
*/
class UserRenderTest extends TestCase
{
use ProphecyTrait;
public function testRenderUserWithJobAndScopeAtCertainDate(): void
{
// Create a user with a certain user job
$user = new User();
$userJobA = new UserJob();
$scopeA = new Scope();
$userJobA->setLabel(['fr' => 'assistant social'])
->setActive(true);
$scopeA->setName(['fr' => 'service A']);
$user->setLabel('BOB ISLA');
$userJobB = new UserJob();
$scopeB = new Scope();
$userJobB->setLabel(['fr' => 'directrice'])
->setActive(true);
$scopeB->setName(['fr' => 'service B']);
$userJobHistoryA = (new User\UserJobHistory())
->setUser($user)
->setJob($userJobA)
->setStartDate(new \DateTimeImmutable('2023-11-01 12:00:00'))
->setEndDate(new \DateTimeImmutable('2023-11-30 00:00:00'));
$userScopeHistoryA = (new User\UserScopeHistory())
->setUser($user)
->setScope($scopeA)
->setStartDate(new \DateTimeImmutable('2023-11-01 12:00:00'))
->setEndDate(new \DateTimeImmutable('2023-11-30 00:00:00'));
$userJobHistoryB = (new User\UserJobHistory())
->setUser($user)
->setJob($userJobB)
->setStartDate(new \DateTimeImmutable('2023-12-01 12:00:00'));
$userScopeHistoryB = (new User\UserScopeHistory())
->setUser($user)
->setScope($scopeB)
->setStartDate(new \DateTimeImmutable('2023-12-01 12:00:00'));
$user->getUserJobHistories()->add($userJobHistoryA);
$user->getUserScopeHistories()->add($userScopeHistoryA);
$user->getUserJobHistories()->add($userJobHistoryB);
$user->getUserScopeHistories()->add($userScopeHistoryB);
// Create renderer
$translatableStringHelperMock = $this->prophesize(TranslatableStringHelperInterface::class);
$translatableStringHelperMock->localize(Argument::type('array'))->will(fn ($args) => $args[0]['fr']);
$engineMock = $this->createMock(Environment::class);
$translatorMock = $this->createMock(TranslatorInterface::class);
$clock = new MockClock(new \DateTimeImmutable('2023-12-15 12:00:00'));
$renderer = new UserRender($translatableStringHelperMock->reveal(), $engineMock, $translatorMock, $clock);
$optionsNoDate['at_date'] = null;
$options['at_date'] = new \DateTime('2023-11-25 12:00:00');
$optionsTwo['at_date'] = new \DateTime('2024-01-30 12:00:00');
// Check that the user render for the first activity corresponds with the first user job
$expectedStringA = 'BOB ISLA (assistant social) (service A)';
$this->assertEquals($expectedStringA, $renderer->renderString($user, $options));
// Check that the user render for the second activity corresponds with the second user job
$expectedStringB = 'BOB ISLA (directrice) (service B)';
$this->assertEquals($expectedStringB, $renderer->renderString($user, $optionsTwo));
// Check that the user renders the job and scope that is active now, when no date is given
$expectedStringC = 'BOB ISLA (directrice) (service B)';
$this->assertEquals($expectedStringC, $renderer->renderString($user, $optionsNoDate));
}
}

View File

@ -1507,6 +1507,16 @@ class AccompanyingPeriod implements
return $this;
}
public function getUserHistories(): ReadableCollection
{
return $this->userHistories;
}
public function getCurrentUserHistory(): ?UserHistory
{
return $this->getUserHistories()->findFirst(fn (int $key, UserHistory $userHistory) => null === $userHistory->getEndDate());
}
private function addStepHistory(AccompanyingPeriodStepHistory $stepHistory, array $context = []): self
{
if (!$this->stepHistories->contains($stepHistory)) {

View File

@ -60,13 +60,14 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
* orphanRemoval=true
* )
*
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"read"})
*
* @ORM\OrderBy({"startDate": "DESC", "id": "DESC"})
*
* @var Collection<AccompanyingPeriodWorkEvaluation>
*
* @internal /!\ the serialization for write evaluations is handled in `AccompanyingPeriodWorkDenormalizer`
* @internal the serialization for write evaluations is handled in `accompanyingperiodworkdenormalizer`
* @internal the serialization for context docgen:read is handled in `accompanyingperiodworknormalizer`
*/
private Collection $accompanyingPeriodWorkEvaluations;
@ -395,21 +396,21 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
/**
* @return ReadableCollection<int, User>
*
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
* @Serializer\Groups({"accompanying_period_work:edit"})
* @Serializer\Groups({"accompanying_period_work:create"})
*/
public function getReferrers(): ReadableCollection
{
$users = $this->referrersHistory
->filter(fn (AccompanyingPeriodWorkReferrerHistory $h) => null === $h->getEndDate())
->map(fn (AccompanyingPeriodWorkReferrerHistory $h) => $h->getUser())
->getValues()
;
->getValues();
return new ArrayCollection(array_values($users));
}
/**
* @return Collection<int, AccompanyingPeriodWorkReferrerHistory>
*/
public function getReferrersHistory(): Collection
{
return $this->referrersHistory;
@ -577,9 +578,9 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
return $this;
}
public function setCreatedBy(?User $createdBy): self
public function setCreatedBy(?User $user): self
{
$this->createdBy = $createdBy;
$this->createdBy = $user;
return $this;
}
@ -621,14 +622,14 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
public function setStartDate(\DateTimeInterface $startDate): self
{
$this->startDate = $startDate;
$this->startDate = $startDate instanceof \DateTime ? \DateTimeImmutable::createFromMutable($startDate) : $startDate;
return $this;
}
public function setUpdatedAt(\DateTimeInterface $datetime): TrackUpdateInterface
{
$this->updatedAt = $datetime;
$this->updatedAt = $datetime instanceof \DateTime ? \DateTimeImmutable::createFromMutable($datetime) : $datetime;
return $this;
}

View File

@ -167,6 +167,11 @@ class Comment implements TrackCreationInterface, TrackUpdateInterface
return $this;
}
public function getCreatedBy(): ?User
{
return $this->getCreator();
}
public function setUpdatedAt(\DateTimeInterface $updatedAt): self
{
$this->updatedAt = $updatedAt;

View File

@ -1,6 +1,6 @@
<template>
<a class="btn" :class="getClassButton" :title="$t(buttonTitle)" @click="openModal">
<span v-if="displayTextButton">{{ $t(buttonTitle) }}</span>
<a class="btn" :class="getClassButton" :title="$t(buttonTitle || '')" @click="openModal">
<span v-if="displayTextButton">{{ $t(buttonTitle || '') }}</span>
</a>
<teleport to="body">

View File

@ -7,10 +7,8 @@
{% endif %}
<a id="comment-{{ comment.id }}" href="{{ '#comment-' ~ comment.id }}" class="fa fa-pencil-square-o fa-fw"></a>
{% set creator = comment.creator is defined ? comment.creator : comment.createdBy %}
{{ 'by'|trans }}<b>{{ creator }}</b>
{{ ', ' ~ 'on'|trans ~ ' ' ~ comment.createdAt|format_date('long') }}<br>
{{ 'by'|trans }}
<span class="badge-user">{{ comment.createdBy|chill_entity_render_box({'at_date': comment.createdAt }) }}</span>{{ ', ' ~ 'on'|trans ~ ' ' ~ comment.createdAt|format_date('long') }}<br>
<i>{{ 'Last updated on'|trans ~ ' ' ~ comment.updatedAt|format_datetime('long', 'short') }}</i>
</div>
<ul class="record_actions">

View File

@ -99,7 +99,7 @@
<div class="metadata">
{{ 'Last updated by'| trans }}
<span class="user">
{{ accompanyingCourse.pinnedComment.updatedBy|chill_entity_render_box }}
{{ accompanyingCourse.pinnedComment.updatedBy|chill_entity_render_box({'at_date': accompanyingCourse.pinnedComment.updatedAt}) }}
</span>
{{ 'on'|trans ~ ' ' }}
<span class="date">

View File

@ -83,9 +83,9 @@
</div>
<div class="wl-col list">
{%- if w.referrers|length > 0 -%}
{% for u in w.referrers %}
{% for r in w.referrersHistory %}
<span class="wl-item">
<span class="badge-user">{{ u|chill_entity_render_box }}</span>
<span class="badge-user">{{ r.user|chill_entity_render_box({'at_date': r.startDate}) }}</span>
{% if not loop.last %}, {% endif %}
</span>
{% endfor %}

View File

@ -33,8 +33,8 @@
{% if w.referrers %}
<li>
<span class="item-key">{{ 'Referrers'|trans ~ ' : ' }}</span>
{% for u in w.referrers %}
<span class="badge-user">{{ u|chill_entity_render_box }}</span>
{% for rh in w.referrersHistory %}
<span class="badge-user">{{ rh.user|chill_entity_render_box({'at_date': rh.startDate}) }}</span>
{% endfor %}
{% if w.referrers|length == 0 %}
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>

View File

@ -89,7 +89,7 @@
<li>
{% if evaluation.createdBy is not null %}
<span class="item-key">créé par</span>
<b>{{ evaluation.createdBy.username }}</b>
<b>{{ evaluation.createdBy|chill_entity_render_string({'at_date': evaluation.createdAt}) }}</b>
{% endif %}
{% if evaluation.createdAt is not null %}
<span class="item-key">{{ 'le'|trans }}</span>

View File

@ -13,7 +13,7 @@
{{ 'Last updated by'|trans }}
{% endif %}
<span class="user">
{{ entity.updatedBy|chill_entity_render_box }}
{{ entity.updatedBy|chill_entity_render_box({'at_date': entity.updatedAt }) }}
</span>
{% endif %}
</div>
@ -34,7 +34,7 @@
{{ 'Created by'|trans }}
{% endif %}
<span class="user">
{{ entity.createdBy|chill_entity_render_string }}
{{ entity.createdBy|chill_entity_render_string({'at_date': entity.createdAt}) }}
</span>
{% endif %}
</div>

View File

@ -296,7 +296,7 @@ This view should receive those arguments:
<div class="created-updated">
{% if person.createdBy %}
<div class="createdBy">
{{ 'Created by'|trans}}: <b>{{ person.createdBy|chill_entity_render_box }}</b>,<br>
{{ 'Created by'|trans}}: <b>{{ person.createdBy|chill_entity_render_box({'at_date': person.createdAt}) }}</b>,<br>
{{ 'on'|trans ~ person.createdAt|format_datetime('long', 'short') }}
</div>
{% endif %}

View File

@ -64,7 +64,7 @@
<li>
{% if evaluation.createdBy is not null %}
<span class="item-key">créé par</span>
<b>{{ evaluation.createdBy.username }}</b>
<b>{{ evaluation.createdBy|chill_entity_render_string({'at_date': evaluation.createdAt}) }}</b>
{% endif %}
{% if evaluation.createdAt is not null %}
<span class="item-key">{{ 'le'|trans }}</span>

View File

@ -92,7 +92,7 @@
<li>
{% if evaluation.createdBy is not null %}
<span class="item-key">créé par</span>
<b>{{ evaluation.createdBy.username }}</b>
<b>{{ evaluation.createdBy|chill_entity_render_string({'at_date': evaluation.createdAt}) }}</b>
{% endif %}
{% if evaluation.createdAt is not null %}
<span class="item-key">{{ 'le'|trans }}</span>

View File

@ -17,6 +17,7 @@ use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
use Chill\MainBundle\Serializer\Normalizer\UserNormalizer;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
@ -83,6 +84,7 @@ class AccompanyingPeriodDocGenNormalizer implements ContextAwareNormalizerInterf
{
if ($period instanceof AccompanyingPeriod) {
$scopes = $this->scopeResolverDispatcher->isConcerned($period) ? $this->scopeResolverDispatcher->resolveScope($period) : [];
$userHistory = $period->getCurrentUserHistory();
if (!\is_array($scopes)) {
$scopes = [$scopes];
@ -103,7 +105,7 @@ class AccompanyingPeriodDocGenNormalizer implements ContextAwareNormalizerInterf
'closingMotive' => $this->normalizer->normalize($period->getClosingMotive(), $format, array_merge($context, ['docgen:expects' => AccompanyingPeriod\ClosingMotive::class])),
'confidential' => $period->isConfidential(),
'createdAt' => $this->normalizer->normalize($period->getCreatedAt(), $format, $dateContext),
'createdBy' => $this->normalizer->normalize($period->getCreatedBy(), $format, $userContext),
'createdBy' => $this->normalizer->normalize($period->getCreatedBy(), $format, [...$userContext, UserNormalizer::AT_DATE => $period->getCreatedAt()]),
'emergency' => $period->isEmergency(),
'openingDate' => $this->normalizer->normalize($period->getOpeningDate(), $format, $dateContext),
'origin' => $this->normalizer->normalize($period->getOrigin(), $format, array_merge($context, ['docgen:expects' => AccompanyingPeriod\Origin::class])),
@ -125,7 +127,7 @@ class AccompanyingPeriodDocGenNormalizer implements ContextAwareNormalizerInterf
'isClosed' => null !== $period->getClosingDate(),
'closingMotiveText' => null !== $period->getClosingMotive() ?
$this->closingMotiveRender->renderString($period->getClosingMotive(), []) : '',
'ref' => $this->normalizer->normalize($period->getUser(), $format, $userContext),
'ref' => $this->normalizer->normalize($userHistory?->getUser(), $format, [...$userContext, UserNormalizer::AT_DATE => $userHistory?->getStartDate()]),
'hasRef' => null !== $period->getUser(),
'socialIssuesText' => implode(', ', array_map(fn (SocialIssue $s) => $this->socialIssueRender->renderString($s, []), $period->getSocialIssues()->toArray())),
'scopesText' => implode(', ', array_map(fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), $scopes)),
@ -137,7 +139,7 @@ class AccompanyingPeriodDocGenNormalizer implements ContextAwareNormalizerInterf
'locationPerson' => $this->normalizer->normalize($period->getPersonLocation(), $format, array_merge($context, ['docgen:expects' => Person::class])),
'location' => $this->normalizer->normalize($period->getLocation(), $format, $addressContext),
'administrativeLocation' => $this->normalizer->normalize($period->getAdministrativeLocation(), $format, $administrativeLocationContext),
'works' => $this->normalizer->normalize($period->getWorks(), $format, $workContext),
'works' => $this->normalizer->normalize($period->getWorks()->getValues(), $format, $workContext),
'comments' => $this->normalizer->normalize($period->getComments(), $format, array_merge($context, ['docgen:expects' => AccompanyingPeriod\Comment::class])),
'pinnedComment' => $this->normalizer->normalize($period->getPinnedComment(), $format, array_merge($context, ['docgen:expects' => AccompanyingPeriod\Comment::class])),
];

View File

@ -11,12 +11,16 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Serializer\Normalizer;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
use Chill\MainBundle\Serializer\Normalizer\UserNormalizer;
use Chill\MainBundle\Workflow\Helper\MetadataExtractor;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
@ -32,16 +36,48 @@ class AccompanyingPeriodWorkNormalizer implements ContextAwareNormalizerInterfac
{
}
/**
* @param AccompanyingPeriodWork $object
*
* @throws ExceptionInterface
*/
public function normalize($object, ?string $format = null, array $context = []): array|\ArrayObject|bool|float|int|string|null
{
if (!$object instanceof AccompanyingPeriodWork && 'json' === $format) {
throw new UnexpectedValueException('Object cannot be null or empty when format is json');
}
if ('docgen' === $format && !($object instanceof AccompanyingPeriodWork || null === $object)) {
throw new UnexpectedValueException(sprintf('Object must be an instanceof AccompanyingPeriodWork or null when format is docgen, %s given', get_debug_type($object)));
}
$cleanContext = array_filter($context, fn (string|int $key) => !in_array($key, ['docgen:expects', self::IGNORE_WORK], true), ARRAY_FILTER_USE_KEY);
if (null === $object && 'docgen' === $format) {
$dateNull = $this->normalizer->normalize(null, $format, [...$context, 'docgen:expects' => \DateTimeImmutable::class]);
$userNull = $this->normalizer->normalize(null, $format, [...$context, 'docgen:expects' => User::class]);
return [
'isNull' => true,
'type' => 'accompanying_period_work',
'accompanyingPeriodWorkEvaluations' => [],
'referrers' => [],
'createdAt' => $dateNull,
'createdAutomatically' => 'false',
'createdAutomaticallyReason' => '',
'createdBy' => $userNull,
'endDate' => $dateNull,
'goals' => [],
'handlingThierParty' => $this->normalizer->normalize(null, $format, [...$cleanContext, 'docgen:expects' => ThirdParty::class]),
'id' => '',
'note' => '',
'persons' => [],
'results' => [],
'socialAction' => $this->normalizer->normalize(null, $format, [...$cleanContext, 'docgen:expects' => SocialAction::class]),
'startDate' => $dateNull,
'thirdParties' => [],
'updatedAt' => $dateNull,
'updatedBy' => $userNull,
];
}
$initial = $this->normalizer->normalize($object, $format, array_merge(
$context,
[self::IGNORE_WORK => spl_object_hash($object)]
$cleanContext,
[self::IGNORE_WORK => null === $object ? null : spl_object_hash($object)]
));
// due to bug: https://api-platform.com/docs/core/serialization/#collection-relation
@ -50,38 +86,57 @@ class AccompanyingPeriodWorkNormalizer implements ContextAwareNormalizerInterfac
$initial['accompanyingPeriodWorkEvaluations'] = $this->normalizer->normalize(
$object->getAccompanyingPeriodWorkEvaluations()->getValues(),
$format,
$context
[...$cleanContext]
);
// then, we add normalization for things which are not into the entity
// add the referrers
$initial['referrers'] = [];
$initial['workflows_availables'] = $this->metadataExtractor->availableWorkflowFor(
AccompanyingPeriodWork::class,
$object->getId()
);
foreach ($object->getReferrersHistory() as $referrerHistory) {
if (null !== $referrerHistory->getEndDate()) {
continue;
}
$initial['workflows_availables_evaluation'] = $this->metadataExtractor->availableWorkflowFor(
AccompanyingPeriodWorkEvaluation::class
);
$initial['referrers'][] = $this->normalizer->normalize(
$referrerHistory->getUser(),
$format,
[...$cleanContext, UserNormalizer::AT_DATE => $referrerHistory->getStartDate()]
);
}
$initial['workflows_availables_evaluation_documents'] = $this->metadataExtractor->availableWorkflowFor(
AccompanyingPeriodWorkEvaluationDocument::class
);
if ('json' === $format) {
// then, we add normalization for things which are not into the entity
$initial['workflows_availables'] = $this->metadataExtractor->availableWorkflowFor(
AccompanyingPeriodWork::class,
$object->getId()
);
$workflows = $this->entityWorkflowRepository->findBy([
'relatedEntityClass' => AccompanyingPeriodWork::class,
'relatedEntityId' => $object->getId(),
]);
$initial['workflows_availables_evaluation'] = $this->metadataExtractor->availableWorkflowFor(
AccompanyingPeriodWorkEvaluation::class
);
$initial['workflows'] = $this->normalizer->normalize($workflows, 'json', $context);
$initial['workflows_availables_evaluation_documents'] = $this->metadataExtractor->availableWorkflowFor(
AccompanyingPeriodWorkEvaluationDocument::class
);
$workflows = $this->entityWorkflowRepository->findBy([
'relatedEntityClass' => AccompanyingPeriodWork::class,
'relatedEntityId' => $object->getId(),
]);
$initial['workflows'] = $this->normalizer->normalize($workflows, 'json', $context);
}
return $initial;
}
public function supportsNormalization($data, ?string $format = null, array $context = []): bool
{
return 'json' === $format
&& $data instanceof AccompanyingPeriodWork
&& !\array_key_exists(self::IGNORE_WORK, $context);
return match ($format) {
'json' => $data instanceof AccompanyingPeriodWork && ($context[self::IGNORE_WORK] ?? null) !== spl_object_hash($data),
'docgen' => ($data instanceof AccompanyingPeriodWork || (null === $data && ($context['docgen:expects'] ?? null) === AccompanyingPeriodWork::class))
&& !array_key_exists(self::IGNORE_WORK, $context),
default => false,
};
}
}

View File

@ -50,7 +50,7 @@ class SocialActionNormalizer implements NormalizerAwareInterface, NormalizerInte
'type' => 'social_work_social_action',
'text' => $this->render->renderString($socialAction, []),
'title' => $socialAction->getTitle(),
'parent' => $this->normalizer->normalize($socialAction->getParent(), $format, $context),
'parent' => $this->normalizer->normalize($socialAction->getParent(), $format, [...$context, 'docgen:expects' => SocialAction::class]),
'issue' => $this->normalizer->normalize($socialAction->getIssue(), $format, $context),
];

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Serializer\Normalizer;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Serializer\Normalizer\UserNormalizer;
use Chill\MainBundle\Workflow\Helper\MetadataExtractor;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
@ -48,7 +49,7 @@ class WorkflowNormalizer implements ContextAwareNormalizerInterface, NormalizerA
$data['workflow'] = $this->metadataExtractor->buildArrayPresentationForWorkflow($workflow);
$data['current_place'] = $this->metadataExtractor->buildArrayPresentationForPlace($object);
$data['current_place_at'] = $this->normalizer->normalize($object->getCurrentStepCreatedAt(), 'json', ['groups' => ['read']]);
$data['current_place_by'] = $this->normalizer->normalize($object->getCurrentStepCreatedBy(), 'json', ['groups' => ['read']]);
$data['current_place_by'] = $this->normalizer->normalize($object->getCurrentStepCreatedBy(), 'json', ['groups' => ['read'], UserNormalizer::AT_DATE => $object->getCurrentStepCreatedAt()]);
return $data;
}

View File

@ -11,13 +11,13 @@ declare(strict_types=1);
namespace Serializer\Normalizer;
use Chill\DocGeneratorBundle\Test\DocGenNormalizerTestAbstract;
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 Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
@ -25,8 +25,10 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
* @internal
*
* @coversNothing
*
* @template-extends DocGenNormalizerTestAbstract<AccompanyingPeriodWork>
*/
final class AccompanyingPeriodWorkDocGenNormalizerTest extends KernelTestCase
final class AccompanyingPeriodWorkDocGenNormalizerTest extends DocGenNormalizerTestAbstract
{
private NormalizerInterface $normalizer;
@ -36,28 +38,31 @@ final class AccompanyingPeriodWorkDocGenNormalizerTest extends KernelTestCase
$this->normalizer = self::$container->get(NormalizerInterface::class);
}
public function testNormalizationNull()
public function provideNotNullObject(): object
{
$actual = $this->normalizer->normalize(null, 'docgen', [
'docgen:expects' => AccompanyingPeriodWork::class,
AbstractNormalizer::GROUPS => ['docgen:read'],
]);
$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);
$expected = [
'id' => '',
];
return $work;
}
$this->assertIsArray($actual);
$this->markTestSkipped('specification still not finalized');
$this->assertEqualsCanonicalizing(array_keys($expected), array_keys($actual));
public function provideDocGenExpectClass(): string
{
return AccompanyingPeriodWork::class;
}
foreach ($expected as $key => $item) {
if ('@ignored' === $item) {
continue;
}
$this->assertEquals($item, $actual[$key]);
}
public function getNormalizer(): NormalizerInterface
{
return $this->normalizer;
}
public function testNormalize()
@ -79,20 +84,6 @@ final class AccompanyingPeriodWorkDocGenNormalizerTest extends KernelTestCase
AbstractNormalizer::GROUPS => ['docgen:read'],
]);
$expected = [
'id' => 0,
];
$this->assertIsArray($actual);
$this->markTestSkipped('specification still not finalized');
$this->assertEqualsCanonicalizing(array_keys($expected), array_keys($actual));
foreach ($expected as $key => $item) {
if (0 === $item) {
continue;
}
$this->assertEquals($item, $actual[$key]);
}
}
}