mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Merge branch '331-manage-attachments-to-workflow' into 'master'
Add attachments to workflow Closes #331 See merge request Chill-Projet/chill-bundles!764
This commit is contained in:
commit
7285e5c2b0
19
config/routes/chill_assets_dev.yaml
Normal file
19
config/routes/chill_assets_dev.yaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
when@dev:
|
||||||
|
sass_assets:
|
||||||
|
path: /_dev/assets
|
||||||
|
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
|
||||||
|
defaults:
|
||||||
|
template: '@ChillMain/Dev/dev.assets.html.twig'
|
||||||
|
|
||||||
|
sass_assets_test1:
|
||||||
|
path: /_dev/assets_test1
|
||||||
|
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
|
||||||
|
defaults:
|
||||||
|
template: '@ChillMain/Dev/dev.assets.test1.html.twig'
|
||||||
|
|
||||||
|
sass_assets_test2:
|
||||||
|
path: /_dev/assets_test2
|
||||||
|
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
|
||||||
|
defaults:
|
||||||
|
template: '@ChillMain/Dev/dev.assets.test2.html.twig'
|
||||||
|
|
12
config/routes/chill_swagger.yaml
Normal file
12
config/routes/chill_swagger.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
when@dev:
|
||||||
|
swagger_ui:
|
||||||
|
path: /_dev/swagger
|
||||||
|
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
|
||||||
|
defaults:
|
||||||
|
template: '@ChillMain/Dev/swagger-ui/index.html.twig'
|
||||||
|
|
||||||
|
swagger_specs:
|
||||||
|
path: /_dev/specs.yaml
|
||||||
|
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
|
||||||
|
defaults:
|
||||||
|
template: api/specs.yaml
|
@ -16,7 +16,7 @@
|
|||||||
"@eslint/js": "^9.14.0",
|
"@eslint/js": "^9.14.0",
|
||||||
"@luminateone/eslint-baseline": "^1.0.9",
|
"@luminateone/eslint-baseline": "^1.0.9",
|
||||||
"@symfony/webpack-encore": "^4.1.0",
|
"@symfony/webpack-encore": "^4.1.0",
|
||||||
"@tsconfig/node14": "^1.0.1",
|
"@tsconfig/node20": "^20.1.4",
|
||||||
"@types/dompurify": "^3.0.5",
|
"@types/dompurify": "^3.0.5",
|
||||||
"@types/eslint__js": "^8.42.3",
|
"@types/eslint__js": "^8.42.3",
|
||||||
"@typescript-eslint/parser": "^8.12.2",
|
"@typescript-eslint/parser": "^8.12.2",
|
||||||
@ -30,7 +30,6 @@
|
|||||||
"eslint-plugin-vue": "^9.30.0",
|
"eslint-plugin-vue": "^9.30.0",
|
||||||
"fork-awesome": "^1.1.7",
|
"fork-awesome": "^1.1.7",
|
||||||
"jquery": "^3.6.0",
|
"jquery": "^3.6.0",
|
||||||
"marked": "^12.0.1",
|
|
||||||
"node-sass": "^8.0.0",
|
"node-sass": "^8.0.0",
|
||||||
"popper.js": "^1.16.1",
|
"popper.js": "^1.16.1",
|
||||||
"postcss-loader": "^7.0.2",
|
"postcss-loader": "^7.0.2",
|
||||||
@ -80,6 +79,11 @@
|
|||||||
"dev": "encore dev",
|
"dev": "encore dev",
|
||||||
"watch": "encore dev --watch",
|
"watch": "encore dev --watch",
|
||||||
"build": "encore production --progress",
|
"build": "encore production --progress",
|
||||||
|
"specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml> templates/api/specs.yaml",
|
||||||
|
"specs-validate": "swagger-cli validate templates/api/specs.yaml",
|
||||||
|
"specs-create-dir": "mkdir -p templates/api",
|
||||||
|
"specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate",
|
||||||
|
"version": "node --version",
|
||||||
"eslint": "npx eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
|
"eslint": "npx eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
|
@ -1,83 +1,3 @@
|
|||||||
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
|
||||||
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
|
||||||
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
|
|
||||||
|
|
||||||
{% set person_id = null %}
|
|
||||||
{% if activity.person %}
|
|
||||||
{% set person_id = activity.person.id %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% set accompanying_course_id = null %}
|
|
||||||
{% if activity.accompanyingPeriod %}
|
|
||||||
{% set accompanying_course_id = activity.accompanyingPeriod.id %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="item-bloc activity-item{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
|
<div class="item-bloc activity-item{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
|
||||||
<div class="item-row">
|
{{ include('@ChillActivity/GenericDoc/activity_document_row.html.twig') }}
|
||||||
<div class="item-col" style="width: unset">
|
|
||||||
{% if document.isPending %}
|
|
||||||
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
|
||||||
{% elseif document.isFailure %}
|
|
||||||
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{% if activity.accompanyingPeriod is not null and context == 'person' %}
|
|
||||||
<span class="badge bg-primary">
|
|
||||||
<i class="fa fa-random"></i> {{ activity.accompanyingPeriod.id }}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
<div class="badge-activity-type">
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="denomination h2">
|
|
||||||
{{ document.title|chill_print_or_message("No title") }}
|
|
||||||
</div>
|
|
||||||
{% if document.hasTemplate %}
|
|
||||||
<div>
|
|
||||||
<p>{{ document.template.name|localize_translatable_string }}</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-col">
|
|
||||||
<div class="container">
|
|
||||||
<div class="dates row text-end">
|
|
||||||
<span>{{ document.createdAt|format_date('short') }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="item-row separator">
|
|
||||||
<div class="item-col item-meta">
|
|
||||||
{{ mmm.createdBy(document) }}
|
|
||||||
</div>
|
|
||||||
<ul class="item-col record_actions flex-shrink-1">
|
|
||||||
{% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %}
|
|
||||||
<li>
|
|
||||||
{{ document|chill_document_button_group(document.title, is_granted('CHILL_ACTIVITY_UPDATE', activity), {small: false}) }}
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_ACTIVITY_SEE', activity)%}
|
|
||||||
<li>
|
|
||||||
<a href="{{ chill_path_add_return_path('chill_activity_activity_show', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-show"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ chill_path_add_return_path('chill_activity_activity_edit', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}" class="btn btn-edit"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
||||||
|
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
||||||
|
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
|
||||||
|
|
||||||
|
{% set person_id = null %}
|
||||||
|
{% if activity.person %}
|
||||||
|
{% set person_id = activity.person.id %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% set accompanying_course_id = null %}
|
||||||
|
{% if activity.accompanyingPeriod %}
|
||||||
|
{% set accompanying_course_id = activity.accompanyingPeriod.id %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="item-row">
|
||||||
|
<div class="item-col" style="width: unset">
|
||||||
|
{% if document.isPending %}
|
||||||
|
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
||||||
|
{% elseif document.isFailure %}
|
||||||
|
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% if activity.accompanyingPeriod is not null and context == 'person' %}
|
||||||
|
<span class="badge bg-primary">
|
||||||
|
<i class="fa fa-random"></i> {{ activity.accompanyingPeriod.id }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
<div class="badge-activity-type">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="denomination h2">
|
||||||
|
{{ document.title|chill_print_or_message("No title") }}
|
||||||
|
</div>
|
||||||
|
{% if document.hasTemplate %}
|
||||||
|
<div>
|
||||||
|
<p>{{ document.template.name|localize_translatable_string }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-col">
|
||||||
|
<div class="container">
|
||||||
|
<div class="dates row text-end">
|
||||||
|
<span>{{ document.createdAt|format_date('short') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if show_actions %}
|
||||||
|
<div class="item-row separator">
|
||||||
|
<div class="item-col item-meta">
|
||||||
|
{{ mmm.createdBy(document) }}
|
||||||
|
</div>
|
||||||
|
<ul class="item-col record_actions flex-shrink-1">
|
||||||
|
{% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %}
|
||||||
|
<li>
|
||||||
|
{{ document|chill_document_button_group(document.title, is_granted('CHILL_ACTIVITY_UPDATE', activity), {small: false}) }}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_ACTIVITY_SEE', activity)%}
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_activity_activity_show', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-show"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_activity_activity_edit', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}" class="btn btn-edit"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\ActivityBundle\Service\GenericDoc\Normalizer;
|
||||||
|
|
||||||
|
use Chill\ActivityBundle\Service\GenericDoc\Providers\AccompanyingPeriodActivityGenericDocProvider;
|
||||||
|
use Chill\ActivityBundle\Service\GenericDoc\Renderers\AccompanyingPeriodActivityGenericDocRenderer;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
|
||||||
|
use Chill\DocStoreBundle\Repository\StoredObjectRepositoryInterface;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
|
final readonly class AccompanyingPeriodActivityGenericDocNormalizer implements GenericDocNormalizerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private StoredObjectRepositoryInterface $storedObjectRepository,
|
||||||
|
private AccompanyingPeriodActivityGenericDocRenderer $renderer,
|
||||||
|
private Environment $twig,
|
||||||
|
private TranslatorInterface $translator,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
|
||||||
|
{
|
||||||
|
return AccompanyingPeriodActivityGenericDocProvider::KEY === $genericDocDTO->key
|
||||||
|
&& 'json' == $format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
|
||||||
|
{
|
||||||
|
$storedObject = $this->storedObjectRepository->find($genericDocDTO->identifiers['id']);
|
||||||
|
|
||||||
|
if (null === $storedObject) {
|
||||||
|
return ['title' => $this->translator->trans('generic_doc.document removed'), 'isPresent' => false];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'isPresent' => true,
|
||||||
|
'title' => $storedObject->getTitle(),
|
||||||
|
'html' => $this->twig->render(
|
||||||
|
$this->renderer->getTemplate($genericDocDTO, ['show-actions' => false, 'row-only' => true]),
|
||||||
|
$this->renderer->getTemplateData($genericDocDTO, ['show-actions' => false, 'row-only' => true]),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -13,10 +13,12 @@ namespace Chill\ActivityBundle\Service\GenericDoc\Providers;
|
|||||||
|
|
||||||
use Chill\ActivityBundle\Entity\Activity;
|
use Chill\ActivityBundle\Entity\Activity;
|
||||||
use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepositoryInterface;
|
use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepositoryInterface;
|
||||||
|
use Chill\ActivityBundle\Repository\ActivityRepository;
|
||||||
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
|
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
|
||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
@ -34,8 +36,47 @@ final readonly class AccompanyingPeriodActivityGenericDocProvider implements Gen
|
|||||||
private EntityManagerInterface $em,
|
private EntityManagerInterface $em,
|
||||||
private Security $security,
|
private Security $security,
|
||||||
private ActivityDocumentACLAwareRepositoryInterface $activityDocumentACLAwareRepository,
|
private ActivityDocumentACLAwareRepositoryInterface $activityDocumentACLAwareRepository,
|
||||||
|
private ActivityRepository $activityRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject
|
||||||
|
{
|
||||||
|
if (null === $activity = $this->getRelatedEntity($genericDocDTO->key, $genericDocDTO->identifiers)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $activity->getDocuments()->findFirst(fn (int $key, StoredObject $storedObject) => $storedObject->getId() === $genericDocDTO->identifiers['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool
|
||||||
|
{
|
||||||
|
return $this->supportsKeyAndIdentifiers($genericDocDTO->key, $genericDocDTO->identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool
|
||||||
|
{
|
||||||
|
return self::KEY === $key && array_key_exists('activity_id', $identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getRelatedEntity(string $key, array $identifiers): ?Activity
|
||||||
|
{
|
||||||
|
return $this->activityRepository->find($identifiers['activity_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
|
||||||
|
{
|
||||||
|
if (null === $activity = $this->getRelatedEntity($key, $identifiers)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GenericDocDTO(
|
||||||
|
self::KEY,
|
||||||
|
$identifiers,
|
||||||
|
\DateTimeImmutable::createFromInterface($activity->getDate()),
|
||||||
|
$activity->getAccompanyingPeriod(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||||
{
|
{
|
||||||
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
|
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
|
||||||
|
@ -18,6 +18,9 @@ use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
|||||||
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
||||||
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
|
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implements GenericDocRendererInterface<array{row-only?: bool, show-actions?: bool}>
|
||||||
|
*/
|
||||||
final readonly class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRendererInterface
|
final readonly class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRendererInterface
|
||||||
{
|
{
|
||||||
public function __construct(private StoredObjectRepository $objectRepository, private ActivityRepository $activityRepository) {}
|
public function __construct(private StoredObjectRepository $objectRepository, private ActivityRepository $activityRepository) {}
|
||||||
@ -29,7 +32,8 @@ final readonly class AccompanyingPeriodActivityGenericDocRenderer implements Gen
|
|||||||
|
|
||||||
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
|
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
|
||||||
{
|
{
|
||||||
return '@ChillActivity/GenericDoc/activity_document.html.twig';
|
return ($options['row-only'] ?? false) ? '@ChillActivity/GenericDoc/activity_document_row.html.twig' :
|
||||||
|
'@ChillActivity/GenericDoc/activity_document.html.twig';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
|
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
|
||||||
@ -38,6 +42,7 @@ final readonly class AccompanyingPeriodActivityGenericDocRenderer implements Gen
|
|||||||
'activity' => $this->activityRepository->find($genericDocDTO->identifiers['activity_id']),
|
'activity' => $this->activityRepository->find($genericDocDTO->identifiers['activity_id']),
|
||||||
'document' => $this->objectRepository->find($genericDocDTO->identifiers['id']),
|
'document' => $this->objectRepository->find($genericDocDTO->identifiers['id']),
|
||||||
'context' => $genericDocDTO->getContext(),
|
'context' => $genericDocDTO->getContext(),
|
||||||
|
'show_actions' => $options['show-actions'] ?? true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||||||
namespace Chill\CalendarBundle\Repository;
|
namespace Chill\CalendarBundle\Repository;
|
||||||
|
|
||||||
use Chill\CalendarBundle\Entity\CalendarDoc;
|
use Chill\CalendarBundle\Entity\CalendarDoc;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Doctrine\Persistence\ObjectRepository;
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
@ -49,4 +50,21 @@ class CalendarDocRepository implements ObjectRepository, CalendarDocRepositoryIn
|
|||||||
{
|
{
|
||||||
return CalendarDoc::class;
|
return CalendarDoc::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param StoredObject|int $storedObject the StoredObject instance, or the id of the stored object
|
||||||
|
*/
|
||||||
|
public function findOneByStoredObject(StoredObject|int $storedObject): ?CalendarDoc
|
||||||
|
{
|
||||||
|
$storedObjectId = $storedObject instanceof StoredObject ? $storedObject->getId() : $storedObject;
|
||||||
|
|
||||||
|
$qb = $this->repository->createQueryBuilder('c');
|
||||||
|
$qb->where(
|
||||||
|
$qb->expr()->eq(':storedObject', 'c.storedObject')
|
||||||
|
);
|
||||||
|
|
||||||
|
$qb->setParameter('storedObject', $storedObjectId);
|
||||||
|
|
||||||
|
return $qb->getQuery()->getOneOrNullResult();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||||||
namespace Chill\CalendarBundle\Repository;
|
namespace Chill\CalendarBundle\Repository;
|
||||||
|
|
||||||
use Chill\CalendarBundle\Entity\CalendarDoc;
|
use Chill\CalendarBundle\Entity\CalendarDoc;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
|
||||||
interface CalendarDocRepositoryInterface
|
interface CalendarDocRepositoryInterface
|
||||||
{
|
{
|
||||||
@ -29,5 +30,7 @@ interface CalendarDocRepositoryInterface
|
|||||||
|
|
||||||
public function findOneBy(array $criteria): ?CalendarDoc;
|
public function findOneBy(array $criteria): ?CalendarDoc;
|
||||||
|
|
||||||
|
public function findOneByStoredObject(StoredObject|int $storedObject): ?CalendarDoc;
|
||||||
|
|
||||||
public function getClassName();
|
public function getClassName();
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,10 @@ export default {
|
|||||||
});
|
});
|
||||||
state.key = state.key + toAdd.length;
|
state.key = state.key + toAdd.length;
|
||||||
},
|
},
|
||||||
addExternals(state, externalEvents: (EventInput & { id: string })[]) {
|
addExternals(
|
||||||
|
state: CalendarRangesState,
|
||||||
|
externalEvents: (EventInput & { id: string })[],
|
||||||
|
) {
|
||||||
const toAdd = externalEvents.filter(
|
const toAdd = externalEvents.filter(
|
||||||
(r) => !state.rangesIndex.has(r.id),
|
(r) => !state.rangesIndex.has(r.id),
|
||||||
);
|
);
|
||||||
@ -160,7 +163,7 @@ export default {
|
|||||||
state.key = state.key + 1;
|
state.key = state.key + 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateRange(state, range: CalendarRange) {
|
updateRange(state: CalendarRangesState, range: CalendarRange) {
|
||||||
const found = state.ranges.find(
|
const found = state.ranges.find(
|
||||||
(r) => r.calendarRangeId === range.id && r.is === "range",
|
(r) => r.calendarRangeId === range.id && r.is === "range",
|
||||||
);
|
);
|
||||||
@ -207,7 +210,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
createRange(
|
createRange(
|
||||||
ctx,
|
ctx: Context,
|
||||||
{
|
{
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
@ -253,10 +256,10 @@ export default {
|
|||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
deleteRange(ctx, calendarRangeId: number) {
|
deleteRange(ctx: Context, calendarRangeId: number) {
|
||||||
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
|
||||||
|
|
||||||
makeFetch<undefined, never>("DELETE", url).then((_) => {
|
makeFetch<undefined, never>("DELETE", url).then(() => {
|
||||||
ctx.commit("removeRange", calendarRangeId);
|
ctx.commit("removeRange", calendarRangeId);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -347,10 +350,10 @@ export default {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises).then((_) => Promise.resolve(null));
|
return Promise.all(promises).then(() => Promise.resolve(null));
|
||||||
},
|
},
|
||||||
copyFromWeekToAnotherWeek(
|
copyFromWeekToAnotherWeek(
|
||||||
ctx,
|
ctx: Context,
|
||||||
{ fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
|
{ fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
|
||||||
): Promise<null> {
|
): Promise<null> {
|
||||||
const rangesToCopy: EventInputCalendarRange[] =
|
const rangesToCopy: EventInputCalendarRange[] =
|
||||||
@ -371,7 +374,7 @@ export default {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises).then((_) => Promise.resolve(null));
|
return Promise.all(promises).then(() => Promise.resolve(null));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as Module<CalendarRangesState, State>;
|
} as Module<CalendarRangesState, State>;
|
||||||
|
@ -5,71 +5,5 @@
|
|||||||
{% set c = document.calendar %}
|
{% set c = document.calendar %}
|
||||||
|
|
||||||
<div class="item-bloc">
|
<div class="item-bloc">
|
||||||
<div class="item-row">
|
{{ include('@ChillCalendar/GenericDoc/calendar_document_row.html.twig') }}
|
||||||
<div class="item-col" style="width: unset">
|
|
||||||
{% if document.storedObject.isPending %}
|
|
||||||
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.storedObject.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
|
||||||
{% elseif document.storedObject.isFailure %}
|
|
||||||
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{% if c.accompanyingPeriod is not null and context == 'person' %}
|
|
||||||
<span class="badge bg-primary">
|
|
||||||
<i class="fa fa-random"></i> {{ c.accompanyingPeriod.id }}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<span class="badge-calendar">
|
|
||||||
<span class="title_label"></span>
|
|
||||||
<span class="title_action">
|
|
||||||
{{ 'Calendar'|trans }}
|
|
||||||
{% if c.endDate.diff(c.startDate).days >= 1 %}
|
|
||||||
{{ c.startDate|format_datetime('short', 'short') }}
|
|
||||||
- {{ c.endDate|format_datetime('short', 'short') }}
|
|
||||||
{% else %}
|
|
||||||
{{ c.startDate|format_datetime('short', 'short') }}
|
|
||||||
- {{ c.endDate|format_datetime('none', 'short') }}
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="denomination h2">
|
|
||||||
{{ document.storedObject.title|chill_print_or_message("No title") }}
|
|
||||||
</div>
|
|
||||||
{% if document.storedObject.hasTemplate %}
|
|
||||||
<div>
|
|
||||||
<p>{{ document.storedObject.template.name|localize_translatable_string }}</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-col">
|
|
||||||
<div class="container">
|
|
||||||
<div class="dates row text-end">
|
|
||||||
<span>{{ document.storedObject.createdAt|format_date('short') }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-row separator">
|
|
||||||
<div class="item-col item-meta">
|
|
||||||
{{ mmm.createdBy(document) }}
|
|
||||||
</div>
|
|
||||||
<ul class="item-col record_actions flex-shrink-1">
|
|
||||||
{% if is_granted('CHILL_CALENDAR_DOC_SEE', document) %}
|
|
||||||
<li>
|
|
||||||
{{ document.storedObject|chill_document_button_group(document.storedObject.title, is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c)) }}
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c) %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', {'id': c.id, 'docId': document.id}) }}" class="btn btn-edit"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
||||||
|
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
||||||
|
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
|
||||||
|
|
||||||
|
{% set c = document.calendar %}
|
||||||
|
|
||||||
|
|
||||||
|
<div class="item-row">
|
||||||
|
<div class="item-col" style="width: unset">
|
||||||
|
{% if document.storedObject.isPending %}
|
||||||
|
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.storedObject.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
||||||
|
{% elseif document.storedObject.isFailure %}
|
||||||
|
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% if c.accompanyingPeriod is not null and context == 'person' %}
|
||||||
|
<span class="badge bg-primary">
|
||||||
|
<i class="fa fa-random"></i> {{ c.accompanyingPeriod.id }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<span class="badge-calendar">
|
||||||
|
<span class="title_label"></span>
|
||||||
|
<span class="title_action">
|
||||||
|
{{ 'Calendar'|trans }}
|
||||||
|
{% if c.endDate.diff(c.startDate).days >= 1 %}
|
||||||
|
{{ c.startDate|format_datetime('short', 'short') }}
|
||||||
|
- {{ c.endDate|format_datetime('short', 'short') }}
|
||||||
|
{% else %}
|
||||||
|
{{ c.startDate|format_datetime('short', 'short') }}
|
||||||
|
- {{ c.endDate|format_datetime('none', 'short') }}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="denomination h2">
|
||||||
|
{{ document.storedObject.title|chill_print_or_message("No title") }}
|
||||||
|
</div>
|
||||||
|
{% if document.storedObject.hasTemplate %}
|
||||||
|
<div>
|
||||||
|
<p>{{ document.storedObject.template.name|localize_translatable_string }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-col">
|
||||||
|
<div class="container">
|
||||||
|
<div class="dates row text-end">
|
||||||
|
<span>{{ document.storedObject.createdAt|format_date('short') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if show_actions %}
|
||||||
|
<div class="item-row separator">
|
||||||
|
<div class="item-col item-meta">
|
||||||
|
{{ mmm.createdBy(document) }}
|
||||||
|
</div>
|
||||||
|
<ul class="item-col record_actions flex-shrink-1">
|
||||||
|
{% if is_granted('CHILL_CALENDAR_DOC_SEE', document) %}
|
||||||
|
<li>
|
||||||
|
{{ document.storedObject|chill_document_button_group(document.storedObject.title, is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c)) }}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', {'id': c.id, 'docId': document.id}) }}" class="btn btn-edit"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\CalendarBundle\Service\GenericDoc\Normalizer;
|
||||||
|
|
||||||
|
use Chill\CalendarBundle\Repository\CalendarDocRepositoryInterface;
|
||||||
|
use Chill\CalendarBundle\Service\GenericDoc\Providers\AccompanyingPeriodCalendarGenericDocProvider;
|
||||||
|
use Chill\CalendarBundle\Service\GenericDoc\Renderers\AccompanyingPeriodCalendarGenericDocRenderer;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
|
final readonly class AccompanyingPeriodCalendarGenericDocNormalizer implements GenericDocNormalizerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private AccompanyingPeriodCalendarGenericDocRenderer $renderer,
|
||||||
|
private CalendarDocRepositoryInterface $calendarDocRepository,
|
||||||
|
private Environment $twig,
|
||||||
|
private TranslatorInterface $translator,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
|
||||||
|
{
|
||||||
|
return AccompanyingPeriodCalendarGenericDocProvider::KEY === $genericDocDTO->key && 'json' === $format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
|
||||||
|
{
|
||||||
|
if (null === $calendarDoc = $this->calendarDocRepository->find($genericDocDTO->identifiers['id'])) {
|
||||||
|
return ['title' => $this->translator->trans('generic_doc.document removed'), 'isPresent' => false];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'isPresent' => true,
|
||||||
|
'title' => $calendarDoc->getStoredObject()->getTitle(),
|
||||||
|
'html' => $this->twig->render(
|
||||||
|
$this->renderer->getTemplate($genericDocDTO, ['show-actions' => false, 'row-only' => true]),
|
||||||
|
$this->renderer->getTemplateData($genericDocDTO, ['show-actions' => false, 'row-only' => true])
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -13,10 +13,12 @@ namespace Chill\CalendarBundle\Service\GenericDoc\Providers;
|
|||||||
|
|
||||||
use Chill\CalendarBundle\Entity\Calendar;
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
use Chill\CalendarBundle\Entity\CalendarDoc;
|
use Chill\CalendarBundle\Entity\CalendarDoc;
|
||||||
|
use Chill\CalendarBundle\Repository\CalendarDocRepositoryInterface;
|
||||||
use Chill\CalendarBundle\Security\Voter\CalendarVoter;
|
use Chill\CalendarBundle\Security\Voter\CalendarVoter;
|
||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
@ -38,8 +40,38 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private Security $security,
|
private Security $security,
|
||||||
private EntityManagerInterface $em,
|
private EntityManagerInterface $em,
|
||||||
|
private CalendarDocRepositoryInterface $calendarRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject
|
||||||
|
{
|
||||||
|
return $this->calendarRepository->find($genericDocDTO->identifiers['id'])?->getStoredObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool
|
||||||
|
{
|
||||||
|
return $this->supportsKeyAndIdentifiers($genericDocDTO->key, $genericDocDTO->identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool
|
||||||
|
{
|
||||||
|
return self::KEY === $key && array_key_exists('id', $identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
|
||||||
|
{
|
||||||
|
if (null === $calendarDoc = $this->calendarRepository->find($identifiers['id'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GenericDocDTO(
|
||||||
|
self::KEY,
|
||||||
|
$identifiers,
|
||||||
|
\DateTimeImmutable::createFromInterface($calendarDoc->getCreatedAt() ?? new \DateTimeImmutable('now')),
|
||||||
|
$calendarDoc->getCalendar()->getAccompanyingPeriod() ?? $calendarDoc->getCalendar()->getPerson()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws MappingException
|
* @throws MappingException
|
||||||
*/
|
*/
|
||||||
@ -82,7 +114,7 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen
|
|||||||
[Types::INTEGER]
|
[Types::INTEGER]
|
||||||
);
|
);
|
||||||
|
|
||||||
return $query;
|
return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool
|
public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool
|
||||||
|
@ -17,6 +17,9 @@ use Chill\CalendarBundle\Service\GenericDoc\Providers\PersonCalendarGenericDocPr
|
|||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implements GenericDocRendererInterface<array{row-only?: bool, show-actions?: bool}>
|
||||||
|
*/
|
||||||
final readonly class AccompanyingPeriodCalendarGenericDocRenderer implements GenericDocRendererInterface
|
final readonly class AccompanyingPeriodCalendarGenericDocRenderer implements GenericDocRendererInterface
|
||||||
{
|
{
|
||||||
public function __construct(private CalendarDocRepository $repository) {}
|
public function __construct(private CalendarDocRepository $repository) {}
|
||||||
@ -28,7 +31,8 @@ final readonly class AccompanyingPeriodCalendarGenericDocRenderer implements Gen
|
|||||||
|
|
||||||
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
|
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
|
||||||
{
|
{
|
||||||
return '@ChillCalendar/GenericDoc/calendar_document.html.twig';
|
return $options['row-only'] ?? false ? '@ChillCalendar/GenericDoc/calendar_document_row.html.twig'
|
||||||
|
: '@ChillCalendar/GenericDoc/calendar_document.html.twig';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
|
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
|
||||||
@ -36,6 +40,7 @@ final readonly class AccompanyingPeriodCalendarGenericDocRenderer implements Gen
|
|||||||
return [
|
return [
|
||||||
'document' => $this->repository->find($genericDocDTO->identifiers['id']),
|
'document' => $this->repository->find($genericDocDTO->identifiers['id']),
|
||||||
'context' => $genericDocDTO->getContext(),
|
'context' => $genericDocDTO->getContext(),
|
||||||
|
'show_actions' => $options['show-actions'] ?? true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
<?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\Tests\Controller;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Controller\GenericDocForAccompanyingPeriodListApiController;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\ManagerInterface;
|
||||||
|
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
||||||
|
use Chill\MainBundle\Pagination\Paginator;
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorFactoryInterface;
|
||||||
|
use Chill\MainBundle\Serializer\Model\Collection;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class GenericDocForAccompanyingPeriodListApiControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testSmokeTest(): void
|
||||||
|
{
|
||||||
|
$accompanyingPeriod = new AccompanyingPeriod();
|
||||||
|
|
||||||
|
$docs = [
|
||||||
|
new GenericDocDTO('dummy', ['id' => 9], new \DateTimeImmutable('2024-08-01'), $accompanyingPeriod),
|
||||||
|
new GenericDocDTO('dummy', ['id' => 1], new \DateTimeImmutable('2024-09-01'), $accompanyingPeriod),
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
$manager = $this->createMock(ManagerInterface::class);
|
||||||
|
$manager->method('findDocForAccompanyingPeriod')->with($accompanyingPeriod)->willReturn($docs);
|
||||||
|
$manager->method('countDocForAccompanyingPeriod')->with($accompanyingPeriod)->willReturn(2);
|
||||||
|
|
||||||
|
$paginatorFactory = $this->createMock(PaginatorFactoryInterface::class);
|
||||||
|
$paginatorFactory->method('create')->with(2)->willReturn(new Paginator(
|
||||||
|
2,
|
||||||
|
20,
|
||||||
|
1,
|
||||||
|
'/route',
|
||||||
|
[],
|
||||||
|
$this->createMock(UrlGeneratorInterface::class),
|
||||||
|
'page',
|
||||||
|
'item-per-page'
|
||||||
|
));
|
||||||
|
|
||||||
|
$serializer = $this->createMock(SerializerInterface::class);
|
||||||
|
$serializer->method('serialize')->with($this->isInstanceOf(Collection::class))->willReturn(
|
||||||
|
json_encode(['docs' => []])
|
||||||
|
);
|
||||||
|
|
||||||
|
$security = $this->createMock(Security::class);
|
||||||
|
$security->expects($this->once())->method('isGranted')
|
||||||
|
->with(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod)->willReturn(true);
|
||||||
|
|
||||||
|
$controller = new GenericDocForAccompanyingPeriodListApiController(
|
||||||
|
$manager,
|
||||||
|
$security,
|
||||||
|
$paginatorFactory,
|
||||||
|
$serializer,
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = $controller($accompanyingPeriod);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(JsonResponse::class, $response);
|
||||||
|
$this->assertEquals('{"docs":[]}', $response->getContent());
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ namespace Chill\DocStoreBundle;
|
|||||||
use Chill\DocStoreBundle\DependencyInjection\Compiler\StorageConfigurationCompilerPass;
|
use Chill\DocStoreBundle\DependencyInjection\Compiler\StorageConfigurationCompilerPass;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||||
@ -28,6 +29,8 @@ class ChillDocStoreBundle extends Bundle
|
|||||||
->addTag('chill_doc_store.generic_doc_person_provider');
|
->addTag('chill_doc_store.generic_doc_person_provider');
|
||||||
$container->registerForAutoconfiguration(GenericDocRendererInterface::class)
|
$container->registerForAutoconfiguration(GenericDocRendererInterface::class)
|
||||||
->addTag('chill_doc_store.generic_doc_renderer');
|
->addTag('chill_doc_store.generic_doc_renderer');
|
||||||
|
$container->registerForAutoconfiguration(GenericDocNormalizerInterface::class)
|
||||||
|
->addTag('chill_doc_store.generic_doc_metadata_normalizer');
|
||||||
|
|
||||||
$container->addCompilerPass(new StorageConfigurationCompilerPass());
|
$container->addCompilerPass(new StorageConfigurationCompilerPass());
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\DocStoreBundle\Controller;
|
namespace Chill\DocStoreBundle\Controller;
|
||||||
|
|
||||||
use Chill\DocStoreBundle\GenericDoc\Manager;
|
use Chill\DocStoreBundle\GenericDoc\ManagerInterface;
|
||||||
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
||||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
|
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
|
||||||
@ -25,7 +25,7 @@ final readonly class GenericDocForAccompanyingPeriodController
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private FilterOrderHelperFactory $filterOrderHelperFactory,
|
private FilterOrderHelperFactory $filterOrderHelperFactory,
|
||||||
private Manager $manager,
|
private ManagerInterface $manager,
|
||||||
private PaginatorFactory $paginator,
|
private PaginatorFactory $paginator,
|
||||||
private Security $security,
|
private Security $security,
|
||||||
private \Twig\Environment $twig,
|
private \Twig\Environment $twig,
|
||||||
@ -68,6 +68,9 @@ final readonly class GenericDocForAccompanyingPeriodController
|
|||||||
);
|
);
|
||||||
$paginator = $this->paginator->create($nb);
|
$paginator = $this->paginator->create($nb);
|
||||||
|
|
||||||
|
// restrict the number of items for performance reasons
|
||||||
|
$paginator->setItemsPerPage(20);
|
||||||
|
|
||||||
$documents = $this->manager->findDocForAccompanyingPeriod(
|
$documents = $this->manager->findDocForAccompanyingPeriod(
|
||||||
$accompanyingPeriod,
|
$accompanyingPeriod,
|
||||||
$paginator->getCurrentPageFirstItemNumber(),
|
$paginator->getCurrentPageFirstItemNumber(),
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\DocStoreBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\ManagerInterface;
|
||||||
|
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorFactoryInterface;
|
||||||
|
use Chill\MainBundle\Serializer\Model\Collection;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||||
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide the list of GenericDoc for an accompanying period.
|
||||||
|
*/
|
||||||
|
final readonly class GenericDocForAccompanyingPeriodListApiController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ManagerInterface $manager,
|
||||||
|
private Security $security,
|
||||||
|
private PaginatorFactoryInterface $paginator,
|
||||||
|
private SerializerInterface $serializer,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Route('/api/1.0/doc-store/generic-doc/by-period/{id}/index', methods: ['GET'])]
|
||||||
|
public function __invoke(AccompanyingPeriod $accompanyingPeriod): JsonResponse
|
||||||
|
{
|
||||||
|
if (!$this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod)) {
|
||||||
|
throw new AccessDeniedHttpException('not allowed to see the documents for accompanying period');
|
||||||
|
}
|
||||||
|
|
||||||
|
$nb = $this->manager->countDocForAccompanyingPeriod($accompanyingPeriod);
|
||||||
|
$paginator = $this->paginator->create($nb);
|
||||||
|
|
||||||
|
$docs = $this->manager->findDocForAccompanyingPeriod($accompanyingPeriod, $paginator->getCurrentPageFirstItemNumber(), $paginator->getItemsPerPage());
|
||||||
|
|
||||||
|
$collection = new Collection($docs, $paginator);
|
||||||
|
|
||||||
|
return new JsonResponse(
|
||||||
|
$this->serializer->serialize($collection, 'json', [AbstractNormalizer::GROUPS => ['read']]),
|
||||||
|
json: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\DocStoreBundle\Controller;
|
namespace Chill\DocStoreBundle\Controller;
|
||||||
|
|
||||||
use Chill\DocStoreBundle\GenericDoc\Manager;
|
use Chill\DocStoreBundle\GenericDoc\ManagerInterface;
|
||||||
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
|
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
|
||||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
|
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
|
||||||
@ -25,7 +25,7 @@ final readonly class GenericDocForPerson
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private FilterOrderHelperFactory $filterOrderHelperFactory,
|
private FilterOrderHelperFactory $filterOrderHelperFactory,
|
||||||
private Manager $manager,
|
private ManagerInterface $manager,
|
||||||
private PaginatorFactory $paginator,
|
private PaginatorFactory $paginator,
|
||||||
private Security $security,
|
private Security $security,
|
||||||
private \Twig\Environment $twig,
|
private \Twig\Environment $twig,
|
||||||
|
@ -46,9 +46,10 @@ class Document implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
#[ORM\ManyToOne(targetEntity: DocGeneratorTemplate::class)]
|
#[ORM\ManyToOne(targetEntity: DocGeneratorTemplate::class)]
|
||||||
private ?DocGeneratorTemplate $template = null;
|
private ?DocGeneratorTemplate $template = null;
|
||||||
|
|
||||||
#[Assert\Length(min: 2, max: 250)]
|
/**
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)]
|
* Store the title of the document, if the title is set before the document.
|
||||||
private string $title = '';
|
*/
|
||||||
|
private string $proxyTitle = '';
|
||||||
|
|
||||||
#[ORM\ManyToOne(targetEntity: \Chill\MainBundle\Entity\User::class)]
|
#[ORM\ManyToOne(targetEntity: \Chill\MainBundle\Entity\User::class)]
|
||||||
private ?\Chill\MainBundle\Entity\User $user = null;
|
private ?\Chill\MainBundle\Entity\User $user = null;
|
||||||
@ -78,9 +79,10 @@ class Document implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
return $this->template;
|
return $this->template;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Assert\Length(min: 2, max: 250)]
|
||||||
public function getTitle(): string
|
public function getTitle(): string
|
||||||
{
|
{
|
||||||
return $this->title;
|
return (string) $this->getObject()?->getTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUser()
|
public function getUser()
|
||||||
@ -113,6 +115,10 @@ class Document implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
{
|
{
|
||||||
$this->object = $object;
|
$this->object = $object;
|
||||||
|
|
||||||
|
if ('' !== $this->proxyTitle) {
|
||||||
|
$this->object->setTitle($this->proxyTitle);
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +131,11 @@ class Document implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
|
|
||||||
public function setTitle(string $title): self
|
public function setTitle(string $title): self
|
||||||
{
|
{
|
||||||
$this->title = $title;
|
if (null !== $this->getObject()) {
|
||||||
|
$this->getObject()->setTitle($title);
|
||||||
|
} else {
|
||||||
|
$this->proxyTitle = $title;
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
<?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\DocStoreBundle\GenericDoc\Exception;
|
||||||
|
|
||||||
|
class AssociatedStoredObjectNotFound extends \RuntimeException
|
||||||
|
{
|
||||||
|
public function __construct(string $key, array $identifiers, int $code = 0, ?\Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct(sprintf('No stored object found for generic doc with key "%s" and identifiers "%s"', $key, json_encode($identifiers)), $code, $previous);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
<?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\DocStoreBundle\GenericDoc\Exception;
|
||||||
|
|
||||||
|
class NotNormalizableGenericDocException extends \LogicException {}
|
@ -0,0 +1,14 @@
|
|||||||
|
<?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\DocStoreBundle\GenericDoc\Exception;
|
||||||
|
|
||||||
|
class UnexpectedValueException extends \UnexpectedValueException {}
|
@ -13,7 +13,7 @@ namespace Chill\DocStoreBundle\GenericDoc;
|
|||||||
|
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
|
||||||
interface GenericDocForAccompanyingPeriodProviderInterface
|
interface GenericDocForAccompanyingPeriodProviderInterface extends GenericDocProviderInterface
|
||||||
{
|
{
|
||||||
public function buildFetchQueryForAccompanyingPeriod(
|
public function buildFetchQueryForAccompanyingPeriod(
|
||||||
AccompanyingPeriod $accompanyingPeriod,
|
AccompanyingPeriod $accompanyingPeriod,
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
<?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\DocStoreBundle\GenericDoc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a Generic Doc.
|
||||||
|
*/
|
||||||
|
interface GenericDocNormalizerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Return true if a generic doc can be normalized by this implementation.
|
||||||
|
*/
|
||||||
|
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a generic doc into an array.
|
||||||
|
*
|
||||||
|
* @return array{title: string, html?: string, isPresent: bool}
|
||||||
|
*/
|
||||||
|
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array;
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\DocStoreBundle\GenericDoc;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
|
||||||
|
interface GenericDocProviderInterface
|
||||||
|
{
|
||||||
|
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if this provider supports the given Generic doc for various informations.
|
||||||
|
*
|
||||||
|
* Concerned:
|
||||||
|
*
|
||||||
|
* - @see{self::fetchAssociatedStoredObject}
|
||||||
|
*/
|
||||||
|
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return true if the implementation supports key and identifiers.
|
||||||
|
*/
|
||||||
|
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a GenericDocDTO, given the key and identifiers.
|
||||||
|
*/
|
||||||
|
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO;
|
||||||
|
}
|
@ -11,13 +11,16 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\DocStoreBundle\GenericDoc;
|
namespace Chill\DocStoreBundle\GenericDoc;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Exception\AssociatedStoredObjectNotFound;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Exception\NotNormalizableGenericDocException;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Doctrine\DBAL\Connection;
|
use Doctrine\DBAL\Connection;
|
||||||
use Doctrine\DBAL\Exception;
|
use Doctrine\DBAL\Exception;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
|
||||||
final readonly class Manager
|
final readonly class Manager implements ManagerInterface
|
||||||
{
|
{
|
||||||
private FetchQueryToSqlBuilder $builder;
|
private FetchQueryToSqlBuilder $builder;
|
||||||
|
|
||||||
@ -31,16 +34,16 @@ final readonly class Manager
|
|||||||
* @var iterable<GenericDocForPersonProviderInterface>
|
* @var iterable<GenericDocForPersonProviderInterface>
|
||||||
*/
|
*/
|
||||||
private iterable $providersForPerson,
|
private iterable $providersForPerson,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var iterable<GenericDocNormalizerInterface>
|
||||||
|
*/
|
||||||
|
private iterable $genericDocNormalizers,
|
||||||
private Connection $connection,
|
private Connection $connection,
|
||||||
) {
|
) {
|
||||||
$this->builder = new FetchQueryToSqlBuilder();
|
$this->builder = new FetchQueryToSqlBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param list<string> $places
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function countDocForAccompanyingPeriod(
|
public function countDocForAccompanyingPeriod(
|
||||||
AccompanyingPeriod $accompanyingPeriod,
|
AccompanyingPeriod $accompanyingPeriod,
|
||||||
?\DateTimeImmutable $startDate = null,
|
?\DateTimeImmutable $startDate = null,
|
||||||
@ -83,13 +86,6 @@ final readonly class Manager
|
|||||||
return $this->countDoc($sql, $params, $types);
|
return $this->countDoc($sql, $params, $types);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param list<string> $places places to search. When empty, search in all places
|
|
||||||
*
|
|
||||||
* @return iterable<GenericDocDTO>
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function findDocForAccompanyingPeriod(
|
public function findDocForAccompanyingPeriod(
|
||||||
AccompanyingPeriod $accompanyingPeriod,
|
AccompanyingPeriod $accompanyingPeriod,
|
||||||
int $offset = 0,
|
int $offset = 0,
|
||||||
@ -129,10 +125,35 @@ final readonly class Manager
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param list<string> $places places to search. When empty, search in all places
|
* Fetch a generic doc, if it does exists.
|
||||||
*
|
*
|
||||||
* @return iterable<GenericDocDTO>
|
* Currently implemented only on generic docs linked with accompanying period
|
||||||
*/
|
*/
|
||||||
|
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
|
||||||
|
{
|
||||||
|
foreach ($this->providersForAccompanyingPeriod as $provider) {
|
||||||
|
if ($provider->supportsKeyAndIdentifiers($key, $identifiers)) {
|
||||||
|
return $provider->buildOneGenericDoc($key, $identifiers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws AssociatedStoredObjectNotFound if no stored object can be found
|
||||||
|
*/
|
||||||
|
public function fetchStoredObject(GenericDocDTO $genericDocDTO): StoredObject
|
||||||
|
{
|
||||||
|
foreach ($this->providersForAccompanyingPeriod as $provider) {
|
||||||
|
if ($provider->supportsGenericDoc($genericDocDTO)) {
|
||||||
|
return $provider->fetchAssociatedStoredObject($genericDocDTO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AssociatedStoredObjectNotFound($genericDocDTO->key, $genericDocDTO->identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
public function findDocForPerson(
|
public function findDocForPerson(
|
||||||
Person $person,
|
Person $person,
|
||||||
int $offset = 0,
|
int $offset = 0,
|
||||||
@ -161,6 +182,28 @@ final readonly class Manager
|
|||||||
return $this->places($sql, $params, $types);
|
return $this->places($sql, $params, $types);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isGenericDocNormalizable(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
|
||||||
|
{
|
||||||
|
foreach ($this->genericDocNormalizers as $genericDocNormalizer) {
|
||||||
|
if ($genericDocNormalizer->supportsNormalization($genericDocDTO, $format, $context)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function normalizeGenericDoc(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
|
||||||
|
{
|
||||||
|
foreach ($this->genericDocNormalizers as $genericDocNormalizer) {
|
||||||
|
if ($genericDocNormalizer->supportsNormalization($genericDocDTO, $format, $context)) {
|
||||||
|
return $genericDocNormalizer->normalize($genericDocDTO, $format, $context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new NotNormalizableGenericDocException();
|
||||||
|
}
|
||||||
|
|
||||||
private function places(string $sql, array $params, array $types): array
|
private function places(string $sql, array $params, array $types): array
|
||||||
{
|
{
|
||||||
if ('' === $sql) {
|
if ('' === $sql) {
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
<?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\DocStoreBundle\GenericDoc;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Exception\AssociatedStoredObjectNotFound;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Doctrine\DBAL\Exception;
|
||||||
|
|
||||||
|
interface ManagerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param list<string> $places
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function countDocForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, array $places = []): int;
|
||||||
|
|
||||||
|
public function countDocForPerson(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, array $places = []): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<string> $places places to search. When empty, search in all places
|
||||||
|
*
|
||||||
|
* @return iterable<GenericDocDTO>
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function findDocForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, int $offset = 0, int $limit = 20, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, array $places = []): iterable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<string> $places places to search. When empty, search in all places
|
||||||
|
*
|
||||||
|
* @return iterable<GenericDocDTO>
|
||||||
|
*/
|
||||||
|
public function findDocForPerson(Person $person, int $offset = 0, int $limit = 20, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, array $places = []): iterable;
|
||||||
|
|
||||||
|
public function placesForPerson(Person $person): array;
|
||||||
|
|
||||||
|
public function placesForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): array;
|
||||||
|
|
||||||
|
public function isGenericDocNormalizable(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{title: string, html?: string}
|
||||||
|
*/
|
||||||
|
public function normalizeGenericDoc(GenericDocDTO $genericDocDTO, string $format, array $context = []): array;
|
||||||
|
|
||||||
|
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws AssociatedStoredObjectNotFound if no stored object can be found
|
||||||
|
*/
|
||||||
|
public function fetchStoredObject(GenericDocDTO $genericDocDTO): StoredObject;
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
<?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\DocStoreBundle\GenericDoc\Normalizer;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Exception\UnexpectedValueException;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingCourseDocumentGenericDocProvider;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Renderer\AccompanyingCourseDocumentGenericDocRenderer;
|
||||||
|
use Chill\DocStoreBundle\Repository\AccompanyingCourseDocumentRepository;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
|
class AccompanyingCourseDocumentGenericDocNormalizer implements GenericDocNormalizerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly AccompanyingCourseDocumentRepository $repository,
|
||||||
|
private readonly Environment $twig,
|
||||||
|
private readonly AccompanyingCourseDocumentGenericDocRenderer $renderer,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
|
||||||
|
{
|
||||||
|
return AccompanyingCourseDocumentGenericDocProvider::KEY === $genericDocDTO->key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
|
||||||
|
{
|
||||||
|
if (!array_key_exists('id', $genericDocDTO->identifiers)) {
|
||||||
|
throw new UnexpectedValueException('key id not found in identifier');
|
||||||
|
}
|
||||||
|
|
||||||
|
$document = $this->repository->find($genericDocDTO->identifiers['id']);
|
||||||
|
|
||||||
|
if (null === $document) {
|
||||||
|
throw new UnexpectedValueException('document not found with id '.$genericDocDTO->identifiers['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'isPresent' => true,
|
||||||
|
'title' => $document->getTitle(),
|
||||||
|
'html' => $this->twig->render(
|
||||||
|
$this->renderer->getTemplate($genericDocDTO, ['show-actions' => false, 'row-only' => true]),
|
||||||
|
$this->renderer->getTemplateData($genericDocDTO, ['show-actions' => false, 'row-only' => true])
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\DocStoreBundle\GenericDoc\Normalizer;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Renderer\AccompanyingCourseDocumentGenericDocRenderer;
|
||||||
|
use Chill\DocStoreBundle\Repository\PersonDocumentRepository;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
|
final readonly class PersonDocumentGenericDocNormalizer implements GenericDocNormalizerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private PersonDocumentRepository $personDocumentRepository,
|
||||||
|
private AccompanyingCourseDocumentGenericDocRenderer $renderer,
|
||||||
|
private Environment $twig,
|
||||||
|
private TranslatorInterface $translator,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
|
||||||
|
{
|
||||||
|
return PersonDocumentGenericDocProvider::KEY === $genericDocDTO->key && 'json' === $format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
|
||||||
|
{
|
||||||
|
if (null === $personDocument = $this->personDocumentRepository->find($genericDocDTO->identifiers['id'])) {
|
||||||
|
return ['title' => $this->translator->trans('generic_doc.document removed'), 'isPresent' => false];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'isPresent' => true,
|
||||||
|
'title' => $personDocument->getTitle(),
|
||||||
|
'html' => $this->twig->render(
|
||||||
|
$this->renderer->getTemplate($genericDocDTO, ['show-actions' => false, 'row-only' => true]),
|
||||||
|
$this->renderer->getTemplateData($genericDocDTO, ['show-actions' => false, 'row-only' => true])
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -12,10 +12,13 @@ declare(strict_types=1);
|
|||||||
namespace Chill\DocStoreBundle\GenericDoc\Providers;
|
namespace Chill\DocStoreBundle\GenericDoc\Providers;
|
||||||
|
|
||||||
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
|
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||||
|
use Chill\DocStoreBundle\Repository\AccompanyingCourseDocumentRepository;
|
||||||
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
@ -31,17 +34,47 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private Security $security,
|
private Security $security,
|
||||||
private EntityManagerInterface $entityManager,
|
private EntityManagerInterface $entityManager,
|
||||||
|
private AccompanyingCourseDocumentRepository $accompanyingCourseDocumentRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject
|
||||||
|
{
|
||||||
|
return $this->accompanyingCourseDocumentRepository->find($genericDocDTO->identifiers['id'])?->getObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool
|
||||||
|
{
|
||||||
|
return $this->supportsKeyAndIdentifiers($genericDocDTO->key, $genericDocDTO->identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool
|
||||||
|
{
|
||||||
|
return self::KEY === $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
|
||||||
|
{
|
||||||
|
if (null === $accompanyingCourseDocument = $this->accompanyingCourseDocumentRepository->find($identifiers['id'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GenericDocDTO(
|
||||||
|
self::KEY,
|
||||||
|
$identifiers,
|
||||||
|
\DateTimeImmutable::createFromInterface($accompanyingCourseDocument->getDate()),
|
||||||
|
$accompanyingCourseDocument->getCourse(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||||
{
|
{
|
||||||
$classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class);
|
$classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class);
|
||||||
|
|
||||||
$query = new FetchQuery(
|
$query = new FetchQuery(
|
||||||
self::KEY,
|
self::KEY,
|
||||||
sprintf('jsonb_build_object(\'id\', %s)', $classMetadata->getIdentifierColumnNames()[0]),
|
sprintf('jsonb_build_object(\'id\', acc_course_document.%s)', $classMetadata->getIdentifierColumnNames()[0]),
|
||||||
$classMetadata->getColumnName('date'),
|
$classMetadata->getColumnName('date'),
|
||||||
$classMetadata->getSchemaName().'.'.$classMetadata->getTableName()
|
$classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS acc_course_document'
|
||||||
);
|
);
|
||||||
|
|
||||||
$query->addWhereClause(
|
$query->addWhereClause(
|
||||||
@ -64,7 +97,7 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen
|
|||||||
|
|
||||||
$query = new FetchQuery(
|
$query = new FetchQuery(
|
||||||
self::KEY,
|
self::KEY,
|
||||||
sprintf('jsonb_build_object(\'id\', %s)', $classMetadata->getIdentifierColumnNames()[0]),
|
sprintf('jsonb_build_object(\'id\', acc_course_document.%s)', $classMetadata->getIdentifierColumnNames()[0]),
|
||||||
$classMetadata->getColumnName('date'),
|
$classMetadata->getColumnName('date'),
|
||||||
$classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS acc_course_document'
|
$classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS acc_course_document'
|
||||||
);
|
);
|
||||||
@ -110,6 +143,7 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen
|
|||||||
private function addWhereClause(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
|
private function addWhereClause(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
|
||||||
{
|
{
|
||||||
$classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class);
|
$classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class);
|
||||||
|
$storedObjectMetadata = $this->entityManager->getClassMetadata(StoredObject::class);
|
||||||
|
|
||||||
if (null !== $startDate) {
|
if (null !== $startDate) {
|
||||||
$query->addWhereClause(
|
$query->addWhereClause(
|
||||||
@ -128,9 +162,19 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (null !== $content and '' !== $content) {
|
if (null !== $content and '' !== $content) {
|
||||||
|
// add join clause to stored_object table
|
||||||
|
$query->addJoinClause(
|
||||||
|
sprintf(
|
||||||
|
'JOIN %s AS doc_store ON doc_store.%s = acc_course_document.%s',
|
||||||
|
$storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(),
|
||||||
|
$storedObjectMetadata->getSingleIdentifierColumnName(),
|
||||||
|
$classMetadata->getSingleAssociationJoinColumnName('object')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
$query->addWhereClause(
|
$query->addWhereClause(
|
||||||
sprintf(
|
sprintf(
|
||||||
'(%s ilike ? OR %s ilike ?)',
|
'(doc_store.%s ilike ? OR acc_course_document.%s ilike ?)',
|
||||||
$classMetadata->getColumnName('title'),
|
$classMetadata->getColumnName('title'),
|
||||||
$classMetadata->getColumnName('description')
|
$classMetadata->getColumnName('description')
|
||||||
),
|
),
|
||||||
|
@ -11,10 +11,13 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\DocStoreBundle\GenericDoc\Providers;
|
namespace Chill\DocStoreBundle\GenericDoc\Providers;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||||
use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface;
|
use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface;
|
||||||
|
use Chill\DocStoreBundle\Repository\PersonDocumentRepository;
|
||||||
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
|
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
@ -27,8 +30,38 @@ final readonly class PersonDocumentGenericDocProvider implements GenericDocForPe
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private Security $security,
|
private Security $security,
|
||||||
private PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository,
|
private PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository,
|
||||||
|
private PersonDocumentRepository $personDocumentRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject
|
||||||
|
{
|
||||||
|
return $this->personDocumentRepository->find($genericDocDTO->identifiers['id'])?->getObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool
|
||||||
|
{
|
||||||
|
return $this->supportsKeyAndIdentifiers($genericDocDTO->key, $genericDocDTO->identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool
|
||||||
|
{
|
||||||
|
return self::KEY === $key && array_key_exists('id', $identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
|
||||||
|
{
|
||||||
|
if (null === $document = $this->personDocumentRepository->find($identifiers['id'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GenericDocDTO(
|
||||||
|
self::KEY,
|
||||||
|
$identifiers,
|
||||||
|
\DateTimeImmutable::createFromInterface($document->getDate()),
|
||||||
|
$document->getPerson()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function buildFetchQueryForPerson(
|
public function buildFetchQueryForPerson(
|
||||||
Person $person,
|
Person $person,
|
||||||
?\DateTimeImmutable $startDate = null,
|
?\DateTimeImmutable $startDate = null,
|
||||||
|
@ -18,6 +18,9 @@ use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingCourseDocumentGenericD
|
|||||||
use Chill\DocStoreBundle\Repository\AccompanyingCourseDocumentRepository;
|
use Chill\DocStoreBundle\Repository\AccompanyingCourseDocumentRepository;
|
||||||
use Chill\DocStoreBundle\Repository\PersonDocumentRepository;
|
use Chill\DocStoreBundle\Repository\PersonDocumentRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implements GenericDocRendererInterface<array{row-only?: bool, show-actions?: bool}>
|
||||||
|
*/
|
||||||
final readonly class AccompanyingCourseDocumentGenericDocRenderer implements GenericDocRendererInterface
|
final readonly class AccompanyingCourseDocumentGenericDocRenderer implements GenericDocRendererInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@ -33,6 +36,10 @@ final readonly class AccompanyingCourseDocumentGenericDocRenderer implements Gen
|
|||||||
|
|
||||||
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
|
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
|
||||||
{
|
{
|
||||||
|
if ($options['row-only'] ?? false) {
|
||||||
|
return '@ChillDocStore/List/list_item_row.html.twig';
|
||||||
|
}
|
||||||
|
|
||||||
return '@ChillDocStore/List/list_item.html.twig';
|
return '@ChillDocStore/List/list_item.html.twig';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,6 +51,7 @@ final readonly class AccompanyingCourseDocumentGenericDocRenderer implements Gen
|
|||||||
'accompanyingCourse' => $doc->getCourse(),
|
'accompanyingCourse' => $doc->getCourse(),
|
||||||
'options' => $options,
|
'options' => $options,
|
||||||
'context' => $genericDocDTO->getContext(),
|
'context' => $genericDocDTO->getContext(),
|
||||||
|
'show_actions' => $options['show-actions'] ?? true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +61,7 @@ final readonly class AccompanyingCourseDocumentGenericDocRenderer implements Gen
|
|||||||
'person' => $doc->getPerson(),
|
'person' => $doc->getPerson(),
|
||||||
'options' => $options,
|
'options' => $options,
|
||||||
'context' => $genericDocDTO->getContext(),
|
'context' => $genericDocDTO->getContext(),
|
||||||
|
'show_actions' => $options['show-actions'] ?? true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,11 +13,25 @@ namespace Chill\DocStoreBundle\GenericDoc\Twig;
|
|||||||
|
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a generic doc, to display it into a page.
|
||||||
|
*
|
||||||
|
* @template T of array
|
||||||
|
*/
|
||||||
interface GenericDocRendererInterface
|
interface GenericDocRendererInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @param T $options the options defined by the renderer
|
||||||
|
*/
|
||||||
public function supports(GenericDocDTO $genericDocDTO, $options = []): bool;
|
public function supports(GenericDocDTO $genericDocDTO, $options = []): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param T $options the options defined by the renderer
|
||||||
|
*/
|
||||||
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string;
|
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param T $options the options defined by the renderer
|
||||||
|
*/
|
||||||
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array;
|
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||||||
namespace Chill\DocStoreBundle\Repository;
|
namespace Chill\DocStoreBundle\Repository;
|
||||||
|
|
||||||
use Chill\DocStoreBundle\Entity\PersonDocument;
|
use Chill\DocStoreBundle\Entity\PersonDocument;
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider;
|
use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider;
|
||||||
@ -136,6 +137,7 @@ final readonly class PersonDocumentACLAwareRepository implements PersonDocumentA
|
|||||||
private function addFilterClauses(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
|
private function addFilterClauses(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
|
||||||
{
|
{
|
||||||
$personDocMetadata = $this->em->getClassMetadata(PersonDocument::class);
|
$personDocMetadata = $this->em->getClassMetadata(PersonDocument::class);
|
||||||
|
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
|
||||||
|
|
||||||
if (null !== $startDate) {
|
if (null !== $startDate) {
|
||||||
$query->addWhereClause(
|
$query->addWhereClause(
|
||||||
@ -154,10 +156,20 @@ final readonly class PersonDocumentACLAwareRepository implements PersonDocumentA
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (null !== $content and '' !== $content) {
|
if (null !== $content and '' !== $content) {
|
||||||
|
|
||||||
|
$query->addJoinClause(
|
||||||
|
sprintf(
|
||||||
|
'JOIN %s AS doc_store ON doc_store.%s = person_document.%s',
|
||||||
|
$storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(),
|
||||||
|
$storedObjectMetadata->getSingleIdentifierColumnName(),
|
||||||
|
$personDocMetadata->getSingleAssociationJoinColumnName('object')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
$query->addWhereClause(
|
$query->addWhereClause(
|
||||||
sprintf(
|
sprintf(
|
||||||
'(%s ilike ? OR %s ilike ?)',
|
'(doc_store.%s ilike ? OR person_document.%s ilike ?)',
|
||||||
$personDocMetadata->getColumnName('title'),
|
$storedObjectMetadata->getColumnName('title'),
|
||||||
$personDocMetadata->getColumnName('description')
|
$personDocMetadata->getColumnName('description')
|
||||||
),
|
),
|
||||||
['%'.$content.'%', '%'.$content.'%'],
|
['%'.$content.'%', '%'.$content.'%'],
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
import { fetchResults } from "ChillMainAssets/lib/api/apiMethods";
|
||||||
|
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
|
||||||
|
|
||||||
|
export function fetch_generic_docs_by_accompanying_period(
|
||||||
|
periodId: number,
|
||||||
|
): Promise<GenericDocForAccompanyingPeriod[]> {
|
||||||
|
return fetchResults(
|
||||||
|
`/api/1.0/doc-store/generic-doc/by-period/${periodId}/index`,
|
||||||
|
);
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { _createI18n } from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
|
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
|
||||||
import DocumentActionButtonsGroup from "../../vuejs/DocumentActionButtonsGroup.vue";
|
import DocumentActionButtonsGroup from "../../vuejs/DocumentActionButtonsGroup.vue";
|
||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
import { StoredObject, StoredObjectStatusChange } from "../../types";
|
import { StoredObject, StoredObjectStatusChange } from "../../types";
|
||||||
|
@ -0,0 +1,71 @@
|
|||||||
|
import { DateTime } from "ChillMainAssets/types";
|
||||||
|
import { StoredObject } from "ChillDocStoreAssets/types/index";
|
||||||
|
|
||||||
|
export interface GenericDocMetadata {
|
||||||
|
isPresent: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty metadata for a GenericDoc
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||||
|
export interface EmptyMetadata extends GenericDocMetadata {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimal Metadata for a GenericDoc with a normalizer
|
||||||
|
*/
|
||||||
|
export interface BaseMetadata extends GenericDocMetadata {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic doc is a document attached to a Person or an AccompanyingPeriod.
|
||||||
|
*/
|
||||||
|
export interface GenericDoc {
|
||||||
|
type: "doc_store_generic_doc";
|
||||||
|
uniqueKey: string;
|
||||||
|
key: string;
|
||||||
|
identifiers: object;
|
||||||
|
context: "person" | "accompanying-period";
|
||||||
|
doc_date: DateTime;
|
||||||
|
metadata: GenericDocMetadata;
|
||||||
|
storedObject: StoredObject | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenericDocForAccompanyingPeriod extends GenericDoc {
|
||||||
|
context: "accompanying-period";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BaseMetadataWithHtml extends BaseMetadata {
|
||||||
|
html: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenericDocForAccompanyingCourseDocument
|
||||||
|
extends GenericDocForAccompanyingPeriod {
|
||||||
|
key: "accompanying_course_document";
|
||||||
|
metadata: BaseMetadataWithHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenericDocForAccompanyingCourseActivityDocument
|
||||||
|
extends GenericDocForAccompanyingPeriod {
|
||||||
|
key: "accompanying_course_activity_document";
|
||||||
|
metadata: BaseMetadataWithHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenericDocForAccompanyingCourseCalendarDocument
|
||||||
|
extends GenericDocForAccompanyingPeriod {
|
||||||
|
key: "accompanying_course_calendar_document";
|
||||||
|
metadata: BaseMetadataWithHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenericDocForAccompanyingCoursePersonDocument
|
||||||
|
extends GenericDocForAccompanyingPeriod {
|
||||||
|
key: "person_document";
|
||||||
|
metadata: BaseMetadataWithHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenericDocForAccompanyingCourseWorkEvaluationDocument
|
||||||
|
extends GenericDocForAccompanyingPeriod {
|
||||||
|
key: "accompanying_period_work_evaluation_document";
|
||||||
|
metadata: BaseMetadataWithHtml;
|
||||||
|
}
|
@ -1,8 +1,5 @@
|
|||||||
import {
|
import { DateTime, User } from "ChillMainAssets/types";
|
||||||
DateTime,
|
import { SignedUrlGet } from "ChillDocStoreAssets/vuejs/StoredObjectButton/helpers";
|
||||||
User,
|
|
||||||
} from "../../../ChillMainBundle/Resources/public/types";
|
|
||||||
import { SignedUrlGet } from "./vuejs/StoredObjectButton/helpers";
|
|
||||||
|
|
||||||
export type StoredObjectStatus = "empty" | "ready" | "failure" | "pending";
|
export type StoredObjectStatus = "empty" | "ready" | "failure" | "pending";
|
||||||
|
|
||||||
@ -138,3 +135,10 @@ export interface ZoomLevel {
|
|||||||
nl?: string;
|
nl?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GenericDoc {
|
||||||
|
type: "doc_store_generic_doc";
|
||||||
|
key: string;
|
||||||
|
context: "person" | "accompanying-period";
|
||||||
|
doc_date: DateTime;
|
||||||
|
}
|
@ -66,7 +66,7 @@ const open_button = ref<HTMLAnchorElement | null>(null);
|
|||||||
function buildDocumentName(): string {
|
function buildDocumentName(): string {
|
||||||
let document_name = props.filename ?? props.storedObject.title;
|
let document_name = props.filename ?? props.storedObject.title;
|
||||||
|
|
||||||
if ("" === document_name) {
|
if ("" === document_name || null === document_name) {
|
||||||
document_name = "document";
|
document_name = "document";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,120 +1,3 @@
|
|||||||
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
|
||||||
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
|
||||||
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
|
|
||||||
|
|
||||||
<div class="item-bloc">
|
<div class="item-bloc">
|
||||||
<div class="item-row">
|
{% include '@ChillDocStore/List/list_item_row.html.twig'%}
|
||||||
<div class="item-col" style="width: unset">
|
|
||||||
{% if document.object.isPending %}
|
|
||||||
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.object.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
|
||||||
{% elseif document.object.isFailure %}
|
|
||||||
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if context == 'person' and accompanyingCourse is defined %}
|
|
||||||
<div>
|
|
||||||
<span class="badge bg-primary">
|
|
||||||
<i class="fa fa-random"></i> {{ accompanyingCourse.id }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{% elseif context == 'accompanying-period' and person is defined %}
|
|
||||||
<div>
|
|
||||||
<span class="badge bg-primary">
|
|
||||||
{{ 'Document from person %name%'|trans({ '%name%': document.person|chill_entity_render_string }) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
<div class="denomination h2">
|
|
||||||
{{ document.title|chill_print_or_message("No title") }}
|
|
||||||
</div>
|
|
||||||
{% if document.object.type is not empty %}
|
|
||||||
<div>
|
|
||||||
{{ mm.mimeIcon(document.object.type) }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div>
|
|
||||||
<p>{{ document.category.name|localize_translatable_string }}</p>
|
|
||||||
</div>
|
|
||||||
{% if document.object.hasTemplate %}
|
|
||||||
<div>
|
|
||||||
<p>{{ document.object.template.name|localize_translatable_string }}</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-col">
|
|
||||||
<div class="container">
|
|
||||||
{% if document.date is not null %}
|
|
||||||
<div class="dates row text-end">
|
|
||||||
<span>{{ document.date|format_date('short') }}</span>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if document.description is not empty %}
|
|
||||||
<div class="item-row">
|
|
||||||
<blockquote class="chill-user-quote col">
|
|
||||||
{{ document.description|chill_markdown_to_html }}
|
|
||||||
</blockquote>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="item-row separator">
|
|
||||||
<div class="item-col item-meta">
|
|
||||||
{{ mmm.createdBy(document) }}
|
|
||||||
</div>
|
|
||||||
<ul class="item-col record_actions flex-shrink-1">
|
|
||||||
{% if document.course is defined %}
|
|
||||||
<li>
|
|
||||||
{{ chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) }}
|
|
||||||
</li>
|
|
||||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
|
|
||||||
<li>
|
|
||||||
{{ document.object|chill_document_button_group(document.title) }}
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_DELETE', document) %}
|
|
||||||
<li class="delete">
|
|
||||||
<a href="{{ chill_return_path_or('chill_docstore_accompanying_course_document_delete', {'course': accompanyingCourse.id, 'id': document.id}) }}" class="btn btn-delete"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_CREATE', document.course) %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ chill_path_add_return_path('chill_doc_store_accompanying_course_document_duplicate', {'id': document.id}) }}" class="btn btn-duplicate" title="{{ 'Duplicate'|trans|e('html_attr') }}"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document) %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ path('accompanying_course_document_edit', {'course': accompanyingCourse.id, 'id': document.id }) }}" class="btn btn-update"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ chill_path_add_return_path('accompanying_course_document_show', {'course': accompanyingCourse.id, 'id': document.id}) }}" class="btn btn-show"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
{% if is_granted('CHILL_PERSON_DOCUMENT_SEE_DETAILS', document) %}
|
|
||||||
<li>
|
|
||||||
{{ document.object|chill_document_button_group(document.title) }}
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{{ path('person_document_show', {'person': person.id, 'id': document.id}) }}" class="btn btn-show"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document) %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ path('person_document_edit', {'person': person.id, 'id': document.id}) }}" class="btn btn-update"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_PERSON_DOCUMENT_DELETE', document) %}
|
|
||||||
<li class="delete">
|
|
||||||
<a href="{{ chill_return_path_or('chill_docstore_person_document_delete', {'person': person.id, 'id': document.id}) }}" class="btn btn-delete"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,119 @@
|
|||||||
|
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
||||||
|
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
||||||
|
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
|
||||||
|
|
||||||
|
<div class="item-row">
|
||||||
|
<div class="item-col" style="width: unset">
|
||||||
|
{% if document.object.isPending %}
|
||||||
|
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.object.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
||||||
|
{% elseif document.object.isFailure %}
|
||||||
|
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if context == 'person' and accompanyingCourse is defined %}
|
||||||
|
<div>
|
||||||
|
<span class="badge bg-primary">
|
||||||
|
<i class="fa fa-random"></i> {{ accompanyingCourse.id }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% elseif context == 'accompanying-period' and person is defined %}
|
||||||
|
<div>
|
||||||
|
<span class="badge bg-primary">
|
||||||
|
{{ 'Document from person %name%'|trans({ '%name%': document.person|chill_entity_render_string }) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
<div class="denomination h2">
|
||||||
|
{{ document.title|chill_print_or_message("No title") }}
|
||||||
|
</div>
|
||||||
|
{% if document.object.type is not empty %}
|
||||||
|
<div>
|
||||||
|
{{ mm.mimeIcon(document.object.type) }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
<p>{{ document.category.name|localize_translatable_string }}</p>
|
||||||
|
</div>
|
||||||
|
{% if document.object.hasTemplate %}
|
||||||
|
<div>
|
||||||
|
<p>{{ document.object.template.name|localize_translatable_string }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-col">
|
||||||
|
<div class="container">
|
||||||
|
{% if document.date is not null %}
|
||||||
|
<div class="dates row text-end">
|
||||||
|
<span>{{ document.date|format_date('short') }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if document.description is not empty %}
|
||||||
|
<div class="item-row">
|
||||||
|
<blockquote class="chill-user-quote col">
|
||||||
|
{{ document.description|chill_markdown_to_html }}
|
||||||
|
</blockquote>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if show_actions %}
|
||||||
|
<div class="item-row separator">
|
||||||
|
<div class="item-col item-meta">
|
||||||
|
{{ mmm.createdBy(document) }}
|
||||||
|
</div>
|
||||||
|
<ul class="item-col record_actions flex-shrink-1">
|
||||||
|
{% if document.course is defined %}
|
||||||
|
<li>
|
||||||
|
{{ chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) }}
|
||||||
|
</li>
|
||||||
|
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
|
||||||
|
<li>
|
||||||
|
{{ document.object|chill_document_button_group(document.title) }}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_DELETE', document) %}
|
||||||
|
<li class="delete">
|
||||||
|
<a href="{{ chill_return_path_or('chill_docstore_accompanying_course_document_delete', {'course': accompanyingCourse.id, 'id': document.id}) }}" class="btn btn-delete"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_CREATE', document.course) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_doc_store_accompanying_course_document_duplicate', {'id': document.id}) }}" class="btn btn-duplicate" title="{{ 'Duplicate'|trans|e('html_attr') }}"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('accompanying_course_document_edit', {'course': accompanyingCourse.id, 'id': document.id }) }}" class="btn btn-update"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('accompanying_course_document_show', {'course': accompanyingCourse.id, 'id': document.id}) }}" class="btn btn-show"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{% if is_granted('CHILL_PERSON_DOCUMENT_SEE_DETAILS', document) %}
|
||||||
|
<li>
|
||||||
|
{{ document.object|chill_document_button_group(document.title) }}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('person_document_show', {'person': person.id, 'id': document.id}) }}" class="btn btn-show"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('person_document_edit', {'person': person.id, 'id': document.id}) }}" class="btn btn-update"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_PERSON_DOCUMENT_DELETE', document) %}
|
||||||
|
<li class="delete">
|
||||||
|
<a href="{{ chill_return_path_or('chill_docstore_person_document_delete', {'person': person.id, 'id': document.id}) }}" class="btn btn-delete"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
@ -24,9 +24,9 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="row">
|
<div class="row g-3">
|
||||||
<div class="col-xs-12 col-sm-6 col-md-4">
|
<div class="col-xs-12 col-sm-6 col-md-4">
|
||||||
<div class="card"">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title">{{ title }}</h2>
|
<h2 class="card-title">{{ title }}</h2>
|
||||||
<h3>{{ 'workflow.public_link.main_document'|trans }}</h3>
|
<h3>{{ 'workflow.public_link.main_document'|trans }}</h3>
|
||||||
@ -39,5 +39,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% for attachment in attachments %}
|
||||||
|
<div class="col-xs-12 col-sm-6 col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title">{{ attachment.proxyStoredObject.title }}</h2>
|
||||||
|
<h3>{{ 'workflow.public_link.attachment'|trans }}</h3>
|
||||||
|
|
||||||
|
<ul class="record_actions slim small">
|
||||||
|
<li>
|
||||||
|
{{ attachment.proxyStoredObject|chill_document_download_only_button(storedObject.title(), false) }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -12,6 +12,8 @@ declare(strict_types=1);
|
|||||||
namespace Chill\DocStoreBundle\Security\Authorization;
|
namespace Chill\DocStoreBundle\Security\Authorization;
|
||||||
|
|
||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Repository\Workflow\EntityWorkflowAttachmentRepository;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||||
@ -26,7 +28,12 @@ class StoredObjectVoter extends Voter
|
|||||||
{
|
{
|
||||||
public const LOG_PREFIX = '[stored object voter] ';
|
public const LOG_PREFIX = '[stored object voter] ';
|
||||||
|
|
||||||
public function __construct(private readonly Security $security, private readonly iterable $storedObjectVoters, private readonly LoggerInterface $logger) {}
|
public function __construct(
|
||||||
|
private readonly Security $security,
|
||||||
|
private readonly iterable $storedObjectVoters,
|
||||||
|
private readonly LoggerInterface $logger,
|
||||||
|
private readonly EntityWorkflowAttachmentRepository $entityWorkflowAttachmentRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
protected function supports($attribute, $subject): bool
|
protected function supports($attribute, $subject): bool
|
||||||
{
|
{
|
||||||
@ -39,6 +46,16 @@ class StoredObjectVoter extends Voter
|
|||||||
/** @var StoredObject $subject */
|
/** @var StoredObject $subject */
|
||||||
$attributeAsEnum = StoredObjectRoleEnum::from($attribute);
|
$attributeAsEnum = StoredObjectRoleEnum::from($attribute);
|
||||||
|
|
||||||
|
// check if the stored object is attached to any workflow
|
||||||
|
$user = $token->getUser();
|
||||||
|
if ($user instanceof User && StoredObjectRoleEnum::SEE === $attributeAsEnum) {
|
||||||
|
foreach ($this->entityWorkflowAttachmentRepository->findByStoredObject($subject) as $workflowAttachment) {
|
||||||
|
if ($workflowAttachment->getEntityWorkflow()->isUserInvolved($user)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Loop through context-specific voters
|
// Loop through context-specific voters
|
||||||
foreach ($this->storedObjectVoters as $storedObjectVoter) {
|
foreach ($this->storedObjectVoters as $storedObjectVoter) {
|
||||||
if ($storedObjectVoter->supports($attributeAsEnum, $subject)) {
|
if ($storedObjectVoter->supports($attributeAsEnum, $subject)) {
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
<?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\DocStoreBundle\Serializer\Normalizer;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Exception\AssociatedStoredObjectNotFound;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\ManagerInterface;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
|
class GenericDocNormalizer implements NormalizerInterface, NormalizerAwareInterface
|
||||||
|
{
|
||||||
|
use NormalizerAwareTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special key to attach a stored object to the generic doc.
|
||||||
|
*
|
||||||
|
* This is present for performance reason: if any other part of the application "knows" about the stored object
|
||||||
|
* related to the GenericDoc, this stored object is use instead of adding costly sql queries.
|
||||||
|
*/
|
||||||
|
public const ATTACHED_STORED_OBJECT_PROXY = 'attached-stored-object-proxy';
|
||||||
|
|
||||||
|
public function __construct(private readonly ManagerInterface $manager) {}
|
||||||
|
|
||||||
|
public function normalize($object, ?string $format = null, array $context = []): array
|
||||||
|
{
|
||||||
|
/* @var GenericDocDTO $object */
|
||||||
|
|
||||||
|
try {
|
||||||
|
$storedObject = $context[self::ATTACHED_STORED_OBJECT_PROXY] ?? $this->manager->fetchStoredObject($object);
|
||||||
|
} catch (AssociatedStoredObjectNotFound) {
|
||||||
|
$storedObject = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'type' => 'doc_store_generic_doc',
|
||||||
|
'key' => $object->key,
|
||||||
|
'uniqueKey' => $object->key.implode('', array_keys($object->identifiers)).implode('', array_values($object->identifiers)),
|
||||||
|
'identifiers' => $object->identifiers,
|
||||||
|
'context' => $object->getContext(),
|
||||||
|
'doc_date' => $this->normalizer->normalize($object->docDate, $format, $context),
|
||||||
|
'metadata' => [],
|
||||||
|
'storedObject' => $this->normalizer->normalize($storedObject, $format, $context),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->manager->isGenericDocNormalizable($object, $format, $context)) {
|
||||||
|
$data['metadata'] = $this->manager->normalizeGenericDoc($object, $format, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsNormalization($data, ?string $format = null): bool
|
||||||
|
{
|
||||||
|
return 'json' === $format && $data instanceof GenericDocDTO;
|
||||||
|
}
|
||||||
|
}
|
@ -11,10 +11,13 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\DocStoreBundle\Tests\GenericDoc;
|
namespace Chill\DocStoreBundle\Tests\GenericDoc;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Exception\NotNormalizableGenericDocException;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\Manager;
|
use Chill\DocStoreBundle\GenericDoc\Manager;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
@ -58,6 +61,7 @@ class ManagerTest extends KernelTestCase
|
|||||||
$manager = new Manager(
|
$manager = new Manager(
|
||||||
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
||||||
[new SimpleGenericDocPersonProvider()],
|
[new SimpleGenericDocPersonProvider()],
|
||||||
|
[],
|
||||||
$this->connection,
|
$this->connection,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -79,6 +83,7 @@ class ManagerTest extends KernelTestCase
|
|||||||
$manager = new Manager(
|
$manager = new Manager(
|
||||||
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
||||||
[new SimpleGenericDocPersonProvider()],
|
[new SimpleGenericDocPersonProvider()],
|
||||||
|
[],
|
||||||
$this->connection,
|
$this->connection,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -100,6 +105,7 @@ class ManagerTest extends KernelTestCase
|
|||||||
$manager = new Manager(
|
$manager = new Manager(
|
||||||
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
||||||
[new SimpleGenericDocPersonProvider()],
|
[new SimpleGenericDocPersonProvider()],
|
||||||
|
[],
|
||||||
$this->connection,
|
$this->connection,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -121,6 +127,7 @@ class ManagerTest extends KernelTestCase
|
|||||||
$manager = new Manager(
|
$manager = new Manager(
|
||||||
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
||||||
[new SimpleGenericDocPersonProvider()],
|
[new SimpleGenericDocPersonProvider()],
|
||||||
|
[],
|
||||||
$this->connection,
|
$this->connection,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -142,6 +149,7 @@ class ManagerTest extends KernelTestCase
|
|||||||
$manager = new Manager(
|
$manager = new Manager(
|
||||||
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
||||||
[new SimpleGenericDocPersonProvider()],
|
[new SimpleGenericDocPersonProvider()],
|
||||||
|
[],
|
||||||
$this->connection,
|
$this->connection,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -163,6 +171,7 @@ class ManagerTest extends KernelTestCase
|
|||||||
$manager = new Manager(
|
$manager = new Manager(
|
||||||
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
||||||
[new SimpleGenericDocPersonProvider()],
|
[new SimpleGenericDocPersonProvider()],
|
||||||
|
[],
|
||||||
$this->connection,
|
$this->connection,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -170,10 +179,77 @@ class ManagerTest extends KernelTestCase
|
|||||||
|
|
||||||
self::assertEquals(['accompanying_course_document_dummy'], $places);
|
self::assertEquals(['accompanying_course_document_dummy'], $places);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testIsGenericDocNormalizable(): void
|
||||||
|
{
|
||||||
|
$genericDoc = new GenericDocDTO('test', ['id' => 1], new \DateTimeImmutable(), new AccompanyingPeriod());
|
||||||
|
|
||||||
|
$manager = new Manager([], [], [$this->buildNormalizer(true)], $this->connection);
|
||||||
|
self::assertTrue($manager->isGenericDocNormalizable($genericDoc, 'json'));
|
||||||
|
|
||||||
|
$manager = new Manager([], [], [$this->buildNormalizer(false)], $this->connection);
|
||||||
|
self::assertFalse($manager->isGenericDocNormalizable($genericDoc, 'json'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNormalizeGenericDocMetadata(): void
|
||||||
|
{
|
||||||
|
$genericDoc = new GenericDocDTO('test', ['id' => 1], new \DateTimeImmutable(), new AccompanyingPeriod());
|
||||||
|
|
||||||
|
$manager = new Manager([], [], [$this->buildNormalizer(false), $this->buildNormalizer(true)], $this->connection);
|
||||||
|
self::assertEquals(['title' => 'Some title'], $manager->normalizeGenericDoc($genericDoc, 'json'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNormalizeGenericDocMetadataNoNormalizer(): void
|
||||||
|
{
|
||||||
|
$genericDoc = new GenericDocDTO('test', ['id' => 1], new \DateTimeImmutable(), new AccompanyingPeriod());
|
||||||
|
|
||||||
|
$manager = new Manager([], [], [$this->buildNormalizer(false)], $this->connection);
|
||||||
|
|
||||||
|
$this->expectException(NotNormalizableGenericDocException::class);
|
||||||
|
|
||||||
|
self::assertEquals(['title' => 'Some title'], $manager->normalizeGenericDoc($genericDoc, 'json'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildNormalizer(bool $supports): GenericDocNormalizerInterface
|
||||||
|
{
|
||||||
|
return new class ($supports) implements GenericDocNormalizerInterface {
|
||||||
|
public function __construct(private readonly bool $supports) {}
|
||||||
|
|
||||||
|
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
|
||||||
|
{
|
||||||
|
return $this->supports;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
|
||||||
|
{
|
||||||
|
return ['title' => 'Some title'];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final readonly class SimpleGenericDocAccompanyingPeriodProvider implements GenericDocForAccompanyingPeriodProviderInterface
|
final readonly class SimpleGenericDocAccompanyingPeriodProvider implements GenericDocForAccompanyingPeriodProviderInterface
|
||||||
{
|
{
|
||||||
|
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject
|
||||||
|
{
|
||||||
|
throw new \BadMethodCallException('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool
|
||||||
|
{
|
||||||
|
throw new \BadMethodCallException('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool
|
||||||
|
{
|
||||||
|
return 'accompanying_course_document_dummy' === $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
|
||||||
|
{
|
||||||
|
return new GenericDocDTO('accompanying_course_document_dummy', $identifiers, new \DateTimeImmutable(), new AccompanyingPeriod());
|
||||||
|
}
|
||||||
|
|
||||||
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||||
{
|
{
|
||||||
$query = new FetchQuery(
|
$query = new FetchQuery(
|
||||||
|
@ -13,6 +13,7 @@ namespace Chill\DocStoreBundle\Tests\GenericDoc\Providers;
|
|||||||
|
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder;
|
||||||
use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingCourseDocumentGenericDocProvider;
|
use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingCourseDocumentGenericDocProvider;
|
||||||
|
use Chill\DocStoreBundle\Repository\AccompanyingCourseDocumentRepository;
|
||||||
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
@ -56,7 +57,8 @@ class AccompanyingCourseDocumentGenericDocProviderTest extends KernelTestCase
|
|||||||
|
|
||||||
$provider = new AccompanyingCourseDocumentGenericDocProvider(
|
$provider = new AccompanyingCourseDocumentGenericDocProvider(
|
||||||
$security->reveal(),
|
$security->reveal(),
|
||||||
$this->entityManager
|
$this->entityManager,
|
||||||
|
$this->prophesize(AccompanyingCourseDocumentRepository::class)->reveal()
|
||||||
);
|
);
|
||||||
|
|
||||||
$query = $provider->buildFetchQueryForAccompanyingPeriod($period, $startDate, $endDate, $content);
|
$query = $provider->buildFetchQueryForAccompanyingPeriod($period, $startDate, $endDate, $content);
|
||||||
|
@ -14,6 +14,7 @@ namespace Chill\DocStoreBundle\Tests\GenericDoc\Providers;
|
|||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder;
|
||||||
use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider;
|
use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider;
|
||||||
use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface;
|
use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface;
|
||||||
|
use Chill\DocStoreBundle\Repository\PersonDocumentRepository;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Prophecy\PhpUnit\ProphecyTrait;
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
@ -33,11 +34,14 @@ class PersonDocumentGenericDocProviderTest extends KernelTestCase
|
|||||||
|
|
||||||
private PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository;
|
private PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository;
|
||||||
|
|
||||||
|
private PersonDocumentRepository $personDocumentRepository;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
self::bootKernel();
|
self::bootKernel();
|
||||||
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
|
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
|
||||||
$this->personDocumentACLAwareRepository = self::getContainer()->get(PersonDocumentACLAwareRepositoryInterface::class);
|
$this->personDocumentACLAwareRepository = self::getContainer()->get(PersonDocumentACLAwareRepositoryInterface::class);
|
||||||
|
$this->personDocumentRepository = self::getContainer()->get(PersonDocumentRepository::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,7 +64,8 @@ class PersonDocumentGenericDocProviderTest extends KernelTestCase
|
|||||||
|
|
||||||
$provider = new PersonDocumentGenericDocProvider(
|
$provider = new PersonDocumentGenericDocProvider(
|
||||||
$security->reveal(),
|
$security->reveal(),
|
||||||
$this->personDocumentACLAwareRepository
|
$this->personDocumentACLAwareRepository,
|
||||||
|
$this->personDocumentRepository,
|
||||||
);
|
);
|
||||||
|
|
||||||
$query = $provider->buildFetchQueryForPerson($person, $startDate, $endDate, $content);
|
$query = $provider->buildFetchQueryForPerson($person, $startDate, $endDate, $content);
|
||||||
|
@ -16,6 +16,7 @@ use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum;
|
|||||||
use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoter;
|
use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoter;
|
||||||
use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoterInterface;
|
use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoterInterface;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Repository\Workflow\EntityWorkflowAttachmentRepository;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Psr\Log\NullLogger;
|
use Psr\Log\NullLogger;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
@ -44,7 +45,7 @@ class StoredObjectVoterTest extends TestCase
|
|||||||
->with($this->logicalOr($this->identicalTo('ROLE_USER'), $this->identicalTo('ROLE_ADMIN')))
|
->with($this->logicalOr($this->identicalTo('ROLE_USER'), $this->identicalTo('ROLE_ADMIN')))
|
||||||
->willReturn($securityIsGrantedResult);
|
->willReturn($securityIsGrantedResult);
|
||||||
|
|
||||||
$voter = new StoredObjectVoter($security, $storedObjectVoters, new NullLogger());
|
$voter = new StoredObjectVoter($security, $storedObjectVoters, new NullLogger(), $this->createMock(EntityWorkflowAttachmentRepository::class));
|
||||||
|
|
||||||
self::assertEquals($expected, $voter->vote($token, $subject, [$attribute]));
|
self::assertEquals($expected, $voter->vote($token, $subject, [$attribute]));
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\DocGeneratorBundle\Tests\Serializer\Normalizer;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\ManagerInterface;
|
||||||
|
use Chill\DocStoreBundle\Serializer\Normalizer\GenericDocNormalizer;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class GenericDocNormalizerTest extends TestCase
|
||||||
|
{
|
||||||
|
private $normalizer;
|
||||||
|
|
||||||
|
private ManagerInterface $manager;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->manager = $this->createMock(ManagerInterface::class);
|
||||||
|
|
||||||
|
$this->normalizer = new GenericDocNormalizer($this->manager);
|
||||||
|
|
||||||
|
$innerNormalizer = $this->createMock(NormalizerInterface::class);
|
||||||
|
$innerNormalizer->method('normalize')
|
||||||
|
->willReturnCallback(fn ($date) => $date instanceof \DateTimeImmutable ? $date->format(DATE_ATOM) : null);
|
||||||
|
|
||||||
|
$this->normalizer->setNormalizer($innerNormalizer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNormalize()
|
||||||
|
{
|
||||||
|
$docDate = new \DateTimeImmutable('2023-10-01T15:03:01.012345Z');
|
||||||
|
|
||||||
|
$object = new GenericDocDTO(
|
||||||
|
'some_key',
|
||||||
|
['id' => 'id1', 'other_id' => 'id2'],
|
||||||
|
$docDate,
|
||||||
|
new AccompanyingPeriod(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'type' => 'doc_store_generic_doc',
|
||||||
|
'key' => 'some_key',
|
||||||
|
'identifiers' => ['id' => 'id1', 'other_id' => 'id2'],
|
||||||
|
'context' => 'accompanying-period',
|
||||||
|
'doc_date' => $docDate->format(DATE_ATOM),
|
||||||
|
'uniqueKey' => 'some_keyidother_idid1id2',
|
||||||
|
'metadata' => [],
|
||||||
|
'storedObject' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->manager->expects($this->once())->method('isGenericDocNormalizable')
|
||||||
|
->with($object, 'json', [])
|
||||||
|
->willReturn(true);
|
||||||
|
|
||||||
|
$actual = $this->normalizer->normalize($object, 'json', []);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $actual);
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@ use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
|
|||||||
use Chill\MainBundle\Workflow\EntityWorkflowWithPublicViewInterface;
|
use Chill\MainBundle\Workflow\EntityWorkflowWithPublicViewInterface;
|
||||||
use Chill\MainBundle\Workflow\EntityWorkflowWithStoredObjectHandlerInterface;
|
use Chill\MainBundle\Workflow\EntityWorkflowWithStoredObjectHandlerInterface;
|
||||||
use Chill\MainBundle\Workflow\Templating\EntityWorkflowViewMetadataDTO;
|
use Chill\MainBundle\Workflow\Templating\EntityWorkflowViewMetadataDTO;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
|
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
|
||||||
use Chill\PersonBundle\Service\AccompanyingPeriod\ProvideThirdPartiesAssociated;
|
use Chill\PersonBundle\Service\AccompanyingPeriod\ProvideThirdPartiesAssociated;
|
||||||
use Chill\PersonBundle\Service\AccompanyingPeriod\ProvidePersonsAssociated;
|
use Chill\PersonBundle\Service\AccompanyingPeriod\ProvidePersonsAssociated;
|
||||||
@ -78,6 +79,15 @@ final readonly class AccompanyingCourseDocumentWorkflowHandler implements Entity
|
|||||||
return $this->repository->find($entityWorkflow->getRelatedEntityId());
|
return $this->repository->find($entityWorkflow->getRelatedEntityId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRelatedAccompanyingPeriod(EntityWorkflow $entityWorkflow): ?AccompanyingPeriod
|
||||||
|
{
|
||||||
|
if (null === $document = $this->getRelatedEntity($entityWorkflow)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $document->getCourse();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array[]
|
* @return array[]
|
||||||
*/
|
*/
|
||||||
|
@ -39,6 +39,7 @@ class WorkflowWithPublicViewDocumentHelper
|
|||||||
'storedObject' => $storedObject,
|
'storedObject' => $storedObject,
|
||||||
'send' => $send,
|
'send' => $send,
|
||||||
'metadata' => $metadata,
|
'metadata' => $metadata,
|
||||||
|
'attachments' => $entityWorkflow->getAttachments(),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,22 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
|
GenericDoc:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- doc_store_generic_doc
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
context:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- person
|
||||||
|
- accompanying-period
|
||||||
|
doc_date:
|
||||||
|
$ref: '#/components/schemas/Date'
|
||||||
|
|
||||||
paths:
|
paths:
|
||||||
/1.0/doc-store/stored-object/create:
|
/1.0/doc-store/stored-object/create:
|
||||||
@ -69,30 +85,30 @@ paths:
|
|||||||
- storedobject
|
- storedobject
|
||||||
summary: Get a signed route to get a stored object
|
summary: Get a signed route to get a stored object
|
||||||
parameters:
|
parameters:
|
||||||
- in: path
|
- in: path
|
||||||
name: uuid
|
name: uuid
|
||||||
required: true
|
required: true
|
||||||
allowEmptyValue: false
|
allowEmptyValue: false
|
||||||
description: The UUID of the storedObjeect
|
description: The UUID of the storedObjeect
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
- in: path
|
- in: path
|
||||||
name: method
|
name: method
|
||||||
required: true
|
required: true
|
||||||
allowEmptyValue: false
|
allowEmptyValue: false
|
||||||
description: the method of the signed url (get or head)
|
description: the method of the signed url (get or head)
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
enum: [get, head]
|
enum: [ get, head ]
|
||||||
- in: query
|
- in: query
|
||||||
name: version
|
name: version
|
||||||
required: false
|
required: false
|
||||||
allowEmptyValue: false
|
allowEmptyValue: false
|
||||||
description: the version's filename of the stored object
|
description: the version's filename of the stored object
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
minLength: 2
|
minLength: 2
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: "OK"
|
description: "OK"
|
||||||
@ -111,14 +127,14 @@ paths:
|
|||||||
- storedobject
|
- storedobject
|
||||||
summary: Get a signed route to post stored object
|
summary: Get a signed route to post stored object
|
||||||
parameters:
|
parameters:
|
||||||
- in: path
|
- in: path
|
||||||
name: uuid
|
name: uuid
|
||||||
required: true
|
required: true
|
||||||
allowEmptyValue: false
|
allowEmptyValue: false
|
||||||
description: The UUID of the storedObjeect
|
description: The UUID of the storedObjeect
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: "OK"
|
description: "OK"
|
||||||
@ -137,13 +153,13 @@ paths:
|
|||||||
- storedobject
|
- storedobject
|
||||||
summary: Restore an old version of a stored object
|
summary: Restore an old version of a stored object
|
||||||
parameters:
|
parameters:
|
||||||
- in: path
|
- in: path
|
||||||
name: id
|
name: id
|
||||||
required: true
|
required: true
|
||||||
allowEmptyValue: false
|
allowEmptyValue: false
|
||||||
description: The id of the stored object version
|
description: The id of the stored object version
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: "OK"
|
description: "OK"
|
||||||
@ -151,4 +167,32 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
|
/1.0/doc-store/generic-doc/by-period/{id}/index:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- storedobject
|
||||||
|
summary: A list of generic doc associated with the accompanying period
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
allowEmptyValue: false
|
||||||
|
description: The id of the accompanying period
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/PaginatedResult'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/GenericDoc'
|
||||||
|
|
||||||
|
type: object
|
||||||
|
@ -31,6 +31,10 @@ services:
|
|||||||
arguments:
|
arguments:
|
||||||
$providersForAccompanyingPeriod: !tagged_iterator chill_doc_store.generic_doc_accompanying_period_provider
|
$providersForAccompanyingPeriod: !tagged_iterator chill_doc_store.generic_doc_accompanying_period_provider
|
||||||
$providersForPerson: !tagged_iterator chill_doc_store.generic_doc_person_provider
|
$providersForPerson: !tagged_iterator chill_doc_store.generic_doc_person_provider
|
||||||
|
$genericDocNormalizers: !tagged_iterator chill_doc_store.generic_doc_metadata_normalizer
|
||||||
|
|
||||||
|
Chill\DocStoreBundle\GenericDoc\ManagerInterface:
|
||||||
|
alias: Chill\DocStoreBundle\GenericDoc\Manager
|
||||||
|
|
||||||
Chill\DocStoreBundle\GenericDoc\Twig\GenericDocExtension: ~
|
Chill\DocStoreBundle\GenericDoc\Twig\GenericDocExtension: ~
|
||||||
|
|
||||||
@ -44,6 +48,9 @@ services:
|
|||||||
Chill\DocStoreBundle\GenericDoc\Renderer\:
|
Chill\DocStoreBundle\GenericDoc\Renderer\:
|
||||||
resource: '../GenericDoc/Renderer/'
|
resource: '../GenericDoc/Renderer/'
|
||||||
|
|
||||||
|
Chill\DocStoreBundle\GenericDoc\Normalizer\:
|
||||||
|
resource: '../GenericDoc/Normalizer/'
|
||||||
|
|
||||||
Chill\DocStoreBundle\Validator\:
|
Chill\DocStoreBundle\Validator\:
|
||||||
resource: '../Validator'
|
resource: '../Validator'
|
||||||
|
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\Migrations\DocStore;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20241212112733 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Move the title of PersonDocument and AccompanyingCourseDocument to stored object';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('UPDATE chill_doc.stored_object SET title = ac_doc.title FROM chill_doc.accompanyingcourse_document ac_doc WHERE ac_doc.object_id = stored_object.id');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document DROP scope_id');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document DROP title');
|
||||||
|
$this->addSql('UPDATE chill_doc.stored_object SET title = p_doc.title FROM chill_doc.person_document p_doc WHERE p_doc.object_id = stored_object.id');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.person_document DROP title');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document ADD scope_id INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document ADD title TEXT DEFAULT \'\' NOT NULL');
|
||||||
|
$this->addSql('UPDATE chill_doc.accompanyingcourse_document SET title = so.title FROM chill_doc.stored_object so WHERE object_id = so.id');
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document ADD CONSTRAINT fk_a45098f6682b5931 FOREIGN KEY (scope_id) REFERENCES scopes (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('CREATE INDEX idx_a45098f6682b5931 ON chill_doc.accompanyingcourse_document (scope_id)');
|
||||||
|
|
||||||
|
|
||||||
|
$this->addSql('ALTER TABLE chill_doc.person_document ADD title TEXT DEFAULT \'\' NOT NULL');
|
||||||
|
$this->addSql('UPDATE chill_doc.person_document SET title = so.title FROM chill_doc.stored_object so WHERE object_id = so.id');
|
||||||
|
}
|
||||||
|
}
|
@ -86,6 +86,7 @@ workflow:
|
|||||||
shared_doc: Document partagé
|
shared_doc: Document partagé
|
||||||
title: Document partagé
|
title: Document partagé
|
||||||
main_document: Document principal
|
main_document: Document principal
|
||||||
|
attachment: Pièce jointe
|
||||||
|
|
||||||
# ROLES
|
# ROLES
|
||||||
accompanyingCourseDocument: Documents dans les parcours d'accompagnement
|
accompanyingCourseDocument: Documents dans les parcours d'accompagnement
|
||||||
|
@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowAttachment;
|
||||||
|
use Chill\MainBundle\Security\Authorization\EntityWorkflowVoter;
|
||||||
|
use Chill\MainBundle\Workflow\Attachment\AddAttachmentAction;
|
||||||
|
use Chill\MainBundle\Workflow\Attachment\AddAttachmentRequestDTO;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||||
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
|
|
||||||
|
class WorkflowAttachmentController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly Security $security,
|
||||||
|
private readonly SerializerInterface $serializer,
|
||||||
|
private readonly ValidatorInterface $validator,
|
||||||
|
private readonly EntityManagerInterface $entityManager,
|
||||||
|
private readonly AddAttachmentAction $addAttachmentAction,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Route('/api/1.0/main/workflow/{id}/attachment', methods: ['POST'])]
|
||||||
|
public function addAttachment(EntityWorkflow $entityWorkflow, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
if (!$this->security->isGranted(EntityWorkflowVoter::SEE, $entityWorkflow)) {
|
||||||
|
throw new AccessDeniedHttpException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$dto = new AddAttachmentRequestDTO($entityWorkflow);
|
||||||
|
$this->serializer->deserialize($request->getContent(), AddAttachmentRequestDTO::class, 'json', [
|
||||||
|
AbstractNormalizer::OBJECT_TO_POPULATE => $dto, AbstractNormalizer::GROUPS => ['write'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$errors = $this->validator->validate($dto);
|
||||||
|
|
||||||
|
if (count($errors) > 0) {
|
||||||
|
return new JsonResponse(
|
||||||
|
$this->serializer->serialize($errors, 'json'),
|
||||||
|
Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||||
|
json: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$attachment = ($this->addAttachmentAction)($dto);
|
||||||
|
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return new JsonResponse(
|
||||||
|
$this->serializer->serialize($attachment, 'json', [AbstractNormalizer::GROUPS => ['read']]),
|
||||||
|
json: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/1.0/main/workflow/attachment/{id}', methods: ['DELETE'])]
|
||||||
|
public function removeAttachment(EntityWorkflowAttachment $attachment): Response
|
||||||
|
{
|
||||||
|
if (!$this->security->isGranted(EntityWorkflowVoter::SEE, $attachment->getEntityWorkflow())) {
|
||||||
|
throw new AccessDeniedHttpException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->entityManager->remove($attachment);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return new Response(null, Response::HTTP_NO_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/1.0/main/workflow/{id}/attachment', methods: ['GET'])]
|
||||||
|
public function listAttachmentsForEntityWorkflow(EntityWorkflow $entityWorkflow): JsonResponse
|
||||||
|
{
|
||||||
|
if (!$this->security->isGranted(EntityWorkflowVoter::SEE, $entityWorkflow)) {
|
||||||
|
throw new AccessDeniedHttpException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonResponse(
|
||||||
|
$this->serializer->serialize(
|
||||||
|
$entityWorkflow->getAttachments(),
|
||||||
|
'json',
|
||||||
|
[AbstractNormalizer::GROUPS => ['read']]
|
||||||
|
),
|
||||||
|
json: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -351,6 +351,7 @@ class WorkflowController extends AbstractController
|
|||||||
'entity_workflow' => $entityWorkflow,
|
'entity_workflow' => $entityWorkflow,
|
||||||
'transition_form_errors' => $errors,
|
'transition_form_errors' => $errors,
|
||||||
'signatures' => $signatures,
|
'signatures' => $signatures,
|
||||||
|
'related_accompanying_period' => $this->entityWorkflowManager->getRelatedAccompanyingPeriod($entityWorkflow),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -87,12 +87,19 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)]
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)]
|
||||||
private string $workflowName;
|
private string $workflowName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Collection<int, EntityWorkflowAttachment>
|
||||||
|
*/
|
||||||
|
#[ORM\OneToMany(mappedBy: 'entityWorkflow', targetEntity: EntityWorkflowAttachment::class, cascade: ['remove'], orphanRemoval: true)]
|
||||||
|
private Collection $attachments;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->subscriberToFinal = new ArrayCollection();
|
$this->subscriberToFinal = new ArrayCollection();
|
||||||
$this->subscriberToStep = new ArrayCollection();
|
$this->subscriberToStep = new ArrayCollection();
|
||||||
$this->comments = new ArrayCollection();
|
$this->comments = new ArrayCollection();
|
||||||
$this->steps = new ArrayCollection();
|
$this->steps = new ArrayCollection();
|
||||||
|
$this->attachments = new ArrayCollection();
|
||||||
|
|
||||||
$initialStep = new EntityWorkflowStep();
|
$initialStep = new EntityWorkflowStep();
|
||||||
$initialStep
|
$initialStep
|
||||||
@ -142,6 +149,35 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*
|
||||||
|
* @internal use @{EntityWorkflowAttachement::__construct} instead
|
||||||
|
*/
|
||||||
|
public function addAttachment(EntityWorkflowAttachment $attachment): self
|
||||||
|
{
|
||||||
|
if (!$this->attachments->contains($attachment)) {
|
||||||
|
$this->attachments[] = $attachment;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, EntityWorkflowAttachment>
|
||||||
|
*/
|
||||||
|
public function getAttachments(): Collection
|
||||||
|
{
|
||||||
|
return $this->attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeAttachment(EntityWorkflowAttachment $attachment): self
|
||||||
|
{
|
||||||
|
$this->attachments->removeElement($attachment);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function getComments(): Collection
|
public function getComments(): Collection
|
||||||
{
|
{
|
||||||
return $this->comments;
|
return $this->comments;
|
||||||
@ -356,6 +392,17 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
return $this->getCurrentStep()->isOnHoldByUser($user);
|
return $this->getCurrentStep()->isOnHoldByUser($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isUserInvolved(User $user): bool
|
||||||
|
{
|
||||||
|
foreach ($this->getSteps() as $step) {
|
||||||
|
if ($step->getAllDestUser()->contains($user)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public function isUserSubscribedToFinal(User $user): bool
|
public function isUserSubscribedToFinal(User $user): bool
|
||||||
{
|
{
|
||||||
return $this->subscriberToFinal->contains($user);
|
return $this->subscriberToFinal->contains($user);
|
||||||
@ -420,7 +467,7 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method use by marking store.
|
* Method used by marking store.
|
||||||
*
|
*
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Entity\Workflow;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
#[ORM\Entity()]
|
||||||
|
#[ORM\Table(name: 'chill_main_workflow_entity_attachment')]
|
||||||
|
#[ORM\UniqueConstraint(name: 'unique_generic_doc_by_workflow', columns: ['relatedGenericDocKey', 'relatedGenericDocIdentifiers', 'entityworkflow_id'])]
|
||||||
|
class EntityWorkflowAttachment implements TrackCreationInterface, TrackUpdateInterface
|
||||||
|
{
|
||||||
|
use TrackCreationTrait;
|
||||||
|
use TrackUpdateTrait;
|
||||||
|
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column(type: Types::INTEGER)]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
#[ORM\Column(name: 'relatedGenericDocKey', type: Types::STRING, length: 255, nullable: false)]
|
||||||
|
private string $relatedGenericDocKey,
|
||||||
|
#[ORM\Column(name: 'relatedGenericDocIdentifiers', type: Types::JSON, nullable: false, options: ['jsonb' => true])]
|
||||||
|
private array $relatedGenericDocIdentifiers,
|
||||||
|
#[ORM\ManyToOne(targetEntity: EntityWorkflow::class, inversedBy: 'attachments')]
|
||||||
|
#[ORM\JoinColumn(nullable: false, name: 'entityworkflow_id')]
|
||||||
|
private EntityWorkflow $entityWorkflow,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stored object related to the generic doc.
|
||||||
|
*
|
||||||
|
* This is a story to keep track more easily to stored object
|
||||||
|
*/
|
||||||
|
#[ORM\ManyToOne(targetEntity: StoredObject::class)]
|
||||||
|
#[ORM\JoinColumn(nullable: false, name: 'storedobject_id')]
|
||||||
|
private StoredObject $proxyStoredObject,
|
||||||
|
) {
|
||||||
|
$this->entityWorkflow->addAttachment($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntityWorkflow(): EntityWorkflow
|
||||||
|
{
|
||||||
|
return $this->entityWorkflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedGenericDocIdentifiers(): array
|
||||||
|
{
|
||||||
|
return $this->relatedGenericDocIdentifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedGenericDocKey(): string
|
||||||
|
{
|
||||||
|
return $this->relatedGenericDocKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProxyStoredObject(): StoredObject
|
||||||
|
{
|
||||||
|
return $this->proxyStoredObject;
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,11 @@ use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
|||||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains comment for entity workflow.
|
||||||
|
*
|
||||||
|
* **NOTE**: for now, this class is not in used. Comments are, for now, stored in the EntityWorkflowStep.
|
||||||
|
*/
|
||||||
#[ORM\Entity]
|
#[ORM\Entity]
|
||||||
#[ORM\Table('chill_main_workflow_entity_comment')]
|
#[ORM\Table('chill_main_workflow_entity_comment')]
|
||||||
class EntityWorkflowComment implements TrackCreationInterface, TrackUpdateInterface
|
class EntityWorkflowComment implements TrackCreationInterface, TrackUpdateInterface
|
||||||
|
@ -16,9 +16,18 @@ use Chill\MainBundle\Entity\UserGroup;
|
|||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Validator\Constraints as Assert;
|
|
||||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A step for each EntityWorkflow.
|
||||||
|
*
|
||||||
|
* The step contains the history of position. The current one is the one which transitionAt or transitionAfter is NULL.
|
||||||
|
*
|
||||||
|
* The comments field is populated by the comment of the one who apply the transition, it means that the comment for the
|
||||||
|
* "next" step is stored in the EntityWorkflowStep in the previous step.
|
||||||
|
*
|
||||||
|
* DestUsers are the one added at the transition. DestUserByAccessKey are the users who obtained permission after having
|
||||||
|
* clicked on a link to get access (email notification to groups).
|
||||||
|
*/
|
||||||
#[ORM\Entity]
|
#[ORM\Entity]
|
||||||
#[ORM\Table('chill_main_workflow_entity_step')]
|
#[ORM\Table('chill_main_workflow_entity_step')]
|
||||||
class EntityWorkflowStep
|
class EntityWorkflowStep
|
||||||
@ -80,6 +89,11 @@ class EntityWorkflowStep
|
|||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)]
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)]
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is the final step.
|
||||||
|
*
|
||||||
|
* This property is filled by a listener.
|
||||||
|
*/
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => false])]
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => false])]
|
||||||
private bool $isFinal = false;
|
private bool $isFinal = false;
|
||||||
|
|
||||||
@ -254,6 +268,11 @@ class EntityWorkflowStep
|
|||||||
return $this->ccUser;
|
return $this->ccUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the comment from the one who apply the transition.
|
||||||
|
*
|
||||||
|
* It means that it must be saved when the user apply a transition.
|
||||||
|
*/
|
||||||
public function getComment(): string
|
public function getComment(): string
|
||||||
{
|
{
|
||||||
return $this->comment;
|
return $this->comment;
|
||||||
@ -346,6 +365,9 @@ class EntityWorkflowStep
|
|||||||
return $this->transitionByEmail;
|
return $this->transitionByEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool true if this is the end of the EntityWorkflow
|
||||||
|
*/
|
||||||
public function isFinal(): bool
|
public function isFinal(): bool
|
||||||
{
|
{
|
||||||
return $this->isFinal;
|
return $this->isFinal;
|
||||||
@ -367,6 +389,9 @@ class EntityWorkflowStep
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool if the EntityWorkflowStep is waiting for a transition, and is not the final step
|
||||||
|
*/
|
||||||
public function isWaitingForTransition(): bool
|
public function isWaitingForTransition(): bool
|
||||||
{
|
{
|
||||||
if (null !== $this->transitionAfter) {
|
if (null !== $this->transitionAfter) {
|
||||||
@ -506,26 +531,6 @@ class EntityWorkflowStep
|
|||||||
return $this->holdsOnStep;
|
return $this->holdsOnStep;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Assert\Callback]
|
|
||||||
public function validateOnCreation(ExecutionContextInterface $context, mixed $payload): void
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
|
|
||||||
if ($this->isFinalizeAfter()) {
|
|
||||||
if (0 !== \count($this->getDestUser())) {
|
|
||||||
$context->buildViolation('workflow.No dest users when the workflow is finalized')
|
|
||||||
->atPath('finalizeAfter')
|
|
||||||
->addViolation();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (0 === \count($this->getDestUser())) {
|
|
||||||
$context->buildViolation('workflow.The next step must count at least one dest')
|
|
||||||
->atPath('finalizeAfter')
|
|
||||||
->addViolation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addOnHold(EntityWorkflowStepHold $onHold): self
|
public function addOnHold(EntityWorkflowStepHold $onHold): self
|
||||||
{
|
{
|
||||||
if (!$this->holdsOnStep->contains($onHold)) {
|
if (!$this->holdsOnStep->contains($onHold)) {
|
||||||
|
@ -53,6 +53,7 @@ class EntityWorkflowStepSignature implements TrackCreationInterface, TrackUpdate
|
|||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
#[ORM\ManyToOne(targetEntity: EntityWorkflowStep::class, inversedBy: 'signatures')]
|
#[ORM\ManyToOne(targetEntity: EntityWorkflowStep::class, inversedBy: 'signatures')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
private EntityWorkflowStep $step,
|
private EntityWorkflowStep $step,
|
||||||
User|Person $signer,
|
User|Person $signer,
|
||||||
) {
|
) {
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Repository\Workflow;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowAttachment;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implements ObjectRepository<EntityWorkflowAttachment>
|
||||||
|
*/
|
||||||
|
class EntityWorkflowAttachmentRepository implements ObjectRepository
|
||||||
|
{
|
||||||
|
private readonly EntityRepository $repository;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $registry)
|
||||||
|
{
|
||||||
|
$this->repository = $registry->getRepository(EntityWorkflowAttachment::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find($id): ?EntityWorkflowAttachment
|
||||||
|
{
|
||||||
|
return $this->repository->find($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findAll(): array
|
||||||
|
{
|
||||||
|
return $this->repository->findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
|
||||||
|
{
|
||||||
|
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findOneBy(array $criteria)
|
||||||
|
{
|
||||||
|
return $this->repository->findOneBy($criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<EntityWorkflowAttachment>
|
||||||
|
*/
|
||||||
|
public function findByStoredObject(StoredObject $storedObject): array
|
||||||
|
{
|
||||||
|
$qb = $this->repository->createQueryBuilder('a');
|
||||||
|
$qb->where('a.proxyStoredObject = :storedObject')->setParameter('storedObject', $storedObject);
|
||||||
|
|
||||||
|
return $qb->getQuery()->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClassName()
|
||||||
|
{
|
||||||
|
return EntityWorkflowAttachment::class;
|
||||||
|
}
|
||||||
|
}
|
@ -480,7 +480,7 @@ div.workflow {
|
|||||||
section.step {
|
section.step {
|
||||||
border: 1px solid $chill-l-gray;
|
border: 1px solid $chill-l-gray;
|
||||||
padding: 1em 2em;
|
padding: 1em 2em;
|
||||||
div.flex-table {
|
> div.flex-table {
|
||||||
margin: 1.5em -2em;
|
margin: 1.5em -2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,10 @@ export const makeFetch = <Input, Output>(
|
|||||||
opts = Object.assign(opts, options);
|
opts = Object.assign(opts, options);
|
||||||
}
|
}
|
||||||
return fetch(url, opts).then((response) => {
|
return fetch(url, opts).then((response) => {
|
||||||
|
if (response.status === 204) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
@ -173,18 +177,26 @@ function _fetchAction<T>(
|
|||||||
|
|
||||||
throw new Error("other network error");
|
throw new Error("other network error");
|
||||||
})
|
})
|
||||||
.catch((reason: any) => {
|
.catch(
|
||||||
console.error(reason);
|
(
|
||||||
throw new Error(reason);
|
reason:
|
||||||
});
|
| NotFoundExceptionInterface
|
||||||
|
| ServerExceptionInterface
|
||||||
|
| ValidationExceptionInterface
|
||||||
|
| TransportExceptionInterface,
|
||||||
|
) => {
|
||||||
|
console.error(reason);
|
||||||
|
throw reason;
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchResults = async <T>(
|
export const fetchResults = async <T>(
|
||||||
uri: string,
|
uri: string,
|
||||||
params?: FetchParams,
|
params?: FetchParams,
|
||||||
): Promise<T[]> => {
|
): Promise<T[]> => {
|
||||||
let promises: Promise<T[]>[] = [],
|
const promises: Promise<T[]>[] = [];
|
||||||
page = 1;
|
let page = 1;
|
||||||
const firstData: PaginationResponse<T> = (await _fetchAction(
|
const firstData: PaginationResponse<T> = (await _fetchAction(
|
||||||
page,
|
page,
|
||||||
uri,
|
uri,
|
||||||
@ -229,6 +241,7 @@ const ValidationException = (
|
|||||||
return error;
|
return error;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const AccessException = (response: Response): AccessExceptionInterface => {
|
const AccessException = (response: Response): AccessExceptionInterface => {
|
||||||
const error = {} as AccessExceptionInterface;
|
const error = {} as AccessExceptionInterface;
|
||||||
error.name = "AccessException";
|
error.name = "AccessException";
|
||||||
@ -237,6 +250,7 @@ const AccessException = (response: Response): AccessExceptionInterface => {
|
|||||||
return error;
|
return error;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const NotFoundException = (response: Response): NotFoundExceptionInterface => {
|
const NotFoundException = (response: Response): NotFoundExceptionInterface => {
|
||||||
const error = {} as NotFoundExceptionInterface;
|
const error = {} as NotFoundExceptionInterface;
|
||||||
error.name = "NotFoundException";
|
error.name = "NotFoundException";
|
||||||
@ -257,6 +271,7 @@ const ServerException = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ConflictHttpException = (
|
const ConflictHttpException = (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
response: Response,
|
response: Response,
|
||||||
): ConflictHttpExceptionInterface => {
|
): ConflictHttpExceptionInterface => {
|
||||||
const error = {} as ConflictHttpExceptionInterface;
|
const error = {} as ConflictHttpExceptionInterface;
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
import { WorkflowAttachment } from "ChillMainAssets/types";
|
||||||
|
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
|
||||||
|
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||||
|
|
||||||
|
export const find_attachments_by_workflow = async (
|
||||||
|
workflowId: number,
|
||||||
|
): Promise<WorkflowAttachment[]> =>
|
||||||
|
makeFetch("GET", `/api/1.0/main/workflow/${workflowId}/attachment`);
|
||||||
|
|
||||||
|
export const create_attachment = async (
|
||||||
|
workflowId: number,
|
||||||
|
genericDoc: GenericDocForAccompanyingPeriod,
|
||||||
|
): Promise<WorkflowAttachment> =>
|
||||||
|
makeFetch("POST", `/api/1.0/main/workflow/${workflowId}/attachment`, {
|
||||||
|
relatedGenericDocKey: genericDoc.key,
|
||||||
|
relatedGenericDocIdentifiers: genericDoc.identifiers,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const delete_attachment = async (
|
||||||
|
attachment: WorkflowAttachment,
|
||||||
|
): Promise<void> =>
|
||||||
|
makeFetch("DELETE", `/api/1.0/main/workflow/attachment/${attachment.id}`);
|
@ -1,3 +1,5 @@
|
|||||||
|
import { GenericDoc } from "ChillDocStoreAssets/types/generic_doc";
|
||||||
|
|
||||||
export interface DateTime {
|
export interface DateTime {
|
||||||
datetime: string;
|
datetime: string;
|
||||||
datetime8601: string;
|
datetime8601: string;
|
||||||
@ -190,3 +192,14 @@ export interface WorkflowAvailable {
|
|||||||
name: string;
|
name: string;
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WorkflowAttachment {
|
||||||
|
id: number;
|
||||||
|
relatedGenericDocKey: string;
|
||||||
|
relatedGenericDocIdentifiers: object;
|
||||||
|
createdAt: DateTime | null;
|
||||||
|
createdBy: User | null;
|
||||||
|
updatedAt: DateTime | null;
|
||||||
|
updatedBy: User | null;
|
||||||
|
genericDoc: null | GenericDoc;
|
||||||
|
}
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, useTemplateRef } from "vue";
|
||||||
|
import type { WorkflowAttachment } from "ChillMainAssets/types";
|
||||||
|
import PickGenericDocModal from "ChillMainAssets/vuejs/WorkflowAttachment/Component/PickGenericDocModal.vue";
|
||||||
|
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
|
||||||
|
import AttachmentList from "ChillMainAssets/vuejs/WorkflowAttachment/Component/AttachmentList.vue";
|
||||||
|
import { GenericDoc } from "ChillDocStoreAssets/types";
|
||||||
|
|
||||||
|
interface AppConfig {
|
||||||
|
workflowId: number;
|
||||||
|
accompanyingPeriodId: number;
|
||||||
|
attachments: WorkflowAttachment[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(
|
||||||
|
e: "pickGenericDoc",
|
||||||
|
payload: { genericDoc: GenericDocForAccompanyingPeriod },
|
||||||
|
): void;
|
||||||
|
(e: "removeAttachment", payload: { attachment: WorkflowAttachment }): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
type PickGenericModalType = InstanceType<typeof PickGenericDocModal>;
|
||||||
|
|
||||||
|
const pickDocModal = useTemplateRef<PickGenericModalType>("pickDocModal");
|
||||||
|
const props = defineProps<AppConfig>();
|
||||||
|
|
||||||
|
const attachedGenericDoc = computed<GenericDocForAccompanyingPeriod[]>(
|
||||||
|
() =>
|
||||||
|
props.attachments
|
||||||
|
.map((a: WorkflowAttachment) => a.genericDoc)
|
||||||
|
.filter(
|
||||||
|
(g: GenericDoc | null) => g !== null,
|
||||||
|
) as GenericDocForAccompanyingPeriod[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const openModal = function () {
|
||||||
|
pickDocModal.value?.openModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPickGenericDoc = ({
|
||||||
|
genericDoc,
|
||||||
|
}: {
|
||||||
|
genericDoc: GenericDocForAccompanyingPeriod;
|
||||||
|
}) => {
|
||||||
|
emit("pickGenericDoc", { genericDoc });
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<pick-generic-doc-modal
|
||||||
|
:accompanying-period-id="props.accompanyingPeriodId"
|
||||||
|
:to-remove="attachedGenericDoc"
|
||||||
|
ref="pickDocModal"
|
||||||
|
@pickGenericDoc="onPickGenericDoc"
|
||||||
|
></pick-generic-doc-modal>
|
||||||
|
<attachment-list
|
||||||
|
:attachments="props.attachments"
|
||||||
|
@removeAttachment="(payload) => emit('removeAttachment', payload)"
|
||||||
|
></attachment-list>
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<button type="button" class="btn btn-create" @click="openModal">
|
||||||
|
Ajouter une pièce jointe
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,52 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { WorkflowAttachment } from "ChillMainAssets/types";
|
||||||
|
import GenericDocItemBox from "ChillMainAssets/vuejs/WorkflowAttachment/Component/GenericDocItemBox.vue";
|
||||||
|
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
|
||||||
|
|
||||||
|
interface AttachmentListProps {
|
||||||
|
attachments: WorkflowAttachment[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/prefer-function-type
|
||||||
|
(e: "removeAttachment", payload: { attachment: WorkflowAttachment }): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const props = defineProps<AttachmentListProps>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<p
|
||||||
|
v-if="props.attachments.length === 0"
|
||||||
|
class="chill-no-data-statement text-center"
|
||||||
|
>
|
||||||
|
Aucune pièce jointe
|
||||||
|
</p>
|
||||||
|
<!-- TODO translate -->
|
||||||
|
<div else class="flex-table">
|
||||||
|
<div v-for="a in props.attachments" :key="a.id" class="item-bloc">
|
||||||
|
<generic-doc-item-box
|
||||||
|
v-if="a.genericDoc !== null"
|
||||||
|
:generic-doc="a.genericDoc"
|
||||||
|
></generic-doc-item-box>
|
||||||
|
<div class="item-row separator">
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li v-if="a.genericDoc?.storedObject !== null">
|
||||||
|
<document-action-buttons-group
|
||||||
|
:stored-object="a.genericDoc.storedObject"
|
||||||
|
></document-action-buttons-group>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-delete"
|
||||||
|
@click="emit('removeAttachment', { attachment: a })"
|
||||||
|
></button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,18 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
|
||||||
|
|
||||||
|
interface GenericDocItemBoxProps {
|
||||||
|
genericDoc: GenericDocForAccompanyingPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<GenericDocItemBoxProps>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="'html' in props.genericDoc.metadata"
|
||||||
|
v-html="props.genericDoc.metadata.html"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,281 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
GenericDoc,
|
||||||
|
GenericDocForAccompanyingPeriod,
|
||||||
|
} from "ChillDocStoreAssets/types/generic_doc";
|
||||||
|
import PickGenericDocItem from "ChillMainAssets/vuejs/WorkflowAttachment/Component/PickGenericDocItem.vue";
|
||||||
|
import { fetch_generic_docs_by_accompanying_period } from "ChillDocStoreAssets/js/generic-doc-api";
|
||||||
|
import { computed, onMounted, ref } from "vue";
|
||||||
|
|
||||||
|
interface PickGenericDocProps {
|
||||||
|
accompanyingPeriodId: number;
|
||||||
|
pickedList: GenericDocForAccompanyingPeriod[];
|
||||||
|
toRemove: GenericDocForAccompanyingPeriod[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<PickGenericDocProps>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(
|
||||||
|
e: "pickGenericDoc",
|
||||||
|
payload: { genericDoc: GenericDocForAccompanyingPeriod },
|
||||||
|
): void;
|
||||||
|
|
||||||
|
(
|
||||||
|
e: "removeGenericDoc",
|
||||||
|
payload: { genericDoc: GenericDocForAccompanyingPeriod },
|
||||||
|
): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const genericDocs = ref<GenericDocForAccompanyingPeriod[]>([]);
|
||||||
|
const loaded = ref(false);
|
||||||
|
|
||||||
|
const isPicked = (genericDoc: GenericDocForAccompanyingPeriod): boolean =>
|
||||||
|
props.pickedList.findIndex(
|
||||||
|
(element: GenericDocForAccompanyingPeriod) =>
|
||||||
|
element.uniqueKey === genericDoc.uniqueKey,
|
||||||
|
) !== -1;
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
genericDocs.value = await fetch_generic_docs_by_accompanying_period(
|
||||||
|
props.accompanyingPeriodId,
|
||||||
|
);
|
||||||
|
loaded.value = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const textFilter = ref<string>("");
|
||||||
|
const dateFromFilter = ref<string | null>(null);
|
||||||
|
const dateToFilter = ref<string | null>(null);
|
||||||
|
const placesFilter = ref<string[]>([]);
|
||||||
|
|
||||||
|
const availablePlaces = computed<string[]>(() => {
|
||||||
|
const places = new Set<string>(
|
||||||
|
genericDocs.value.map((genericDoc: GenericDoc) => genericDoc.key),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Array.from(places).sort((a, b) => (a < b ? -1 : a === b ? 0 : 1));
|
||||||
|
});
|
||||||
|
|
||||||
|
const placeTrans = (str: string): string => {
|
||||||
|
switch (str) {
|
||||||
|
case "accompanying_course_document":
|
||||||
|
return "Documents du parcours";
|
||||||
|
case "person_document":
|
||||||
|
return "Documents de l'usager";
|
||||||
|
case "accompanying_period_calendar_document":
|
||||||
|
return "Document des rendez-vous des parcours";
|
||||||
|
case "accompanying_period_activity_document":
|
||||||
|
return "Document des échanges des parcours";
|
||||||
|
case "accompanying_period_work_evaluation_document":
|
||||||
|
return "Document des actions d'accompagnement";
|
||||||
|
default:
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredDocuments = computed<GenericDocForAccompanyingPeriod[]>(() => {
|
||||||
|
if (false === loaded.value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return genericDocs.value
|
||||||
|
.filter(
|
||||||
|
(genericDoc: GenericDocForAccompanyingPeriod) =>
|
||||||
|
!props.toRemove
|
||||||
|
.map((g: GenericDocForAccompanyingPeriod) => g.uniqueKey)
|
||||||
|
.includes(genericDoc.uniqueKey),
|
||||||
|
)
|
||||||
|
.filter((genericDoc: GenericDocForAccompanyingPeriod) => {
|
||||||
|
if (textFilter.value === "") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const needles = textFilter.value
|
||||||
|
.trim()
|
||||||
|
.split(" ")
|
||||||
|
.map((str: string) => str.trim().toLowerCase())
|
||||||
|
.filter((str: string) => str.length > 0);
|
||||||
|
const title: string =
|
||||||
|
"title" in genericDoc.metadata
|
||||||
|
? (genericDoc.metadata.title as string)
|
||||||
|
: "";
|
||||||
|
if (title === "") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return needles.every((n: string) =>
|
||||||
|
title.toLowerCase().includes(n),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.filter((genericDoc: GenericDocForAccompanyingPeriod) => {
|
||||||
|
if (placesFilter.value.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return placesFilter.value.includes(genericDoc.key);
|
||||||
|
})
|
||||||
|
.filter((genericDoc: GenericDocForAccompanyingPeriod) => {
|
||||||
|
if (dateToFilter.value === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return genericDoc.doc_date.datetime8601 < dateToFilter.value;
|
||||||
|
})
|
||||||
|
.filter((genericDoc: GenericDocForAccompanyingPeriod) => {
|
||||||
|
if (dateFromFilter.value === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return genericDoc.doc_date.datetime8601 > dateFromFilter.value;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="loaded">
|
||||||
|
<div>
|
||||||
|
<form name="f" method="get">
|
||||||
|
<div class="accordion my-3" id="filterOrderAccordion">
|
||||||
|
<h2 class="accordion-header" id="filterOrderHeading">
|
||||||
|
<button
|
||||||
|
class="accordion-button"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#filterOrderCollapse"
|
||||||
|
aria-expanded="true"
|
||||||
|
aria-controls="filterOrderCollapse"
|
||||||
|
>
|
||||||
|
<strong
|
||||||
|
><i class="fa fa-fw fa-filter"></i>Filtrer la
|
||||||
|
liste</strong
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
class="accordion-collapse collapse"
|
||||||
|
id="filterOrderCollapse"
|
||||||
|
aria-labelledby="filterOrderHeading"
|
||||||
|
data-bs-parent="#filterOrderAccordion"
|
||||||
|
style=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="accordion-body chill_filter_order container-xxl p-5 py-2"
|
||||||
|
>
|
||||||
|
<div class="row my-2">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="input-group">
|
||||||
|
<input
|
||||||
|
v-model="textFilter"
|
||||||
|
type="search"
|
||||||
|
id="f_q"
|
||||||
|
name="f[q]"
|
||||||
|
placeholder="Chercher dans la liste"
|
||||||
|
class="form-control"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-misc"
|
||||||
|
>
|
||||||
|
<i class="fa fa-search"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row my-2">
|
||||||
|
<legend
|
||||||
|
class="col-form-label col-sm-4 required"
|
||||||
|
>
|
||||||
|
Date du document
|
||||||
|
</legend>
|
||||||
|
<div class="col-sm-8 pt-1">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">Du</span>
|
||||||
|
<input
|
||||||
|
v-model="dateFromFilter"
|
||||||
|
type="date"
|
||||||
|
id="f_dateRanges_dateRange_from"
|
||||||
|
name="f[dateRanges][dateRange][from]"
|
||||||
|
class="form-control"
|
||||||
|
/>
|
||||||
|
<span class="input-group-text">Au</span>
|
||||||
|
<input
|
||||||
|
v-model="dateToFilter"
|
||||||
|
type="date"
|
||||||
|
id="f_dateRanges_dateRange_to"
|
||||||
|
name="f[dateRanges][dateRange][to]"
|
||||||
|
class="form-control"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row my-2">
|
||||||
|
<div class="col-sm-4 col-form-label">
|
||||||
|
Filtrer par
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-8 pt-2">
|
||||||
|
<div
|
||||||
|
class="form-check"
|
||||||
|
v-for="p in availablePlaces"
|
||||||
|
:key="p"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
v-model="placesFilter"
|
||||||
|
name="f[checkboxes][places][]"
|
||||||
|
class="form-check-input"
|
||||||
|
:value="p"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label">{{
|
||||||
|
placeTrans(p)
|
||||||
|
}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row my-2">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-sm btn-misc"
|
||||||
|
>
|
||||||
|
<i class="fa fa-fw fa-filter"></i>Filtrer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="genericDocs.length > 0" class="flex-table chill-task-list">
|
||||||
|
<pick-generic-doc-item
|
||||||
|
v-for="g in filteredDocuments"
|
||||||
|
:key="g.uniqueKey"
|
||||||
|
:accompanying-period-id="accompanyingPeriodId"
|
||||||
|
:genericDoc="g"
|
||||||
|
:is-picked="isPicked(g)"
|
||||||
|
@pickGenericDoc="(payload) => emit('pickGenericDoc', payload)"
|
||||||
|
@removeGenericDoc="
|
||||||
|
(payload) => emit('removeGenericDoc', payload)
|
||||||
|
"
|
||||||
|
></pick-generic-doc-item>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-center chill-no-data-statement">
|
||||||
|
Aucun document dans ce parcours
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<strong>Chargement…</strong>
|
||||||
|
<div
|
||||||
|
class="spinner-border ms-auto"
|
||||||
|
role="status"
|
||||||
|
aria-hidden="true"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,95 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
|
||||||
|
import GenericDocItemBox from "ChillMainAssets/vuejs/WorkflowAttachment/Component/GenericDocItemBox.vue";
|
||||||
|
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
|
||||||
|
|
||||||
|
interface PickGenericDocItemProps {
|
||||||
|
genericDoc: GenericDocForAccompanyingPeriod;
|
||||||
|
accompanyingPeriodId: number;
|
||||||
|
isPicked: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<PickGenericDocItemProps>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(
|
||||||
|
e: "pickGenericDoc",
|
||||||
|
payload: { genericDoc: GenericDocForAccompanyingPeriod },
|
||||||
|
): void;
|
||||||
|
|
||||||
|
(
|
||||||
|
e: "removeGenericDoc",
|
||||||
|
payload: { genericDoc: GenericDocForAccompanyingPeriod },
|
||||||
|
): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const clickOnAddButton = () => {
|
||||||
|
emit("pickGenericDoc", { genericDoc: props.genericDoc });
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="item-bloc" :class="{ isPicked: isPicked }">
|
||||||
|
<generic-doc-item-box
|
||||||
|
:generic-doc="props.genericDoc"
|
||||||
|
></generic-doc-item-box>
|
||||||
|
|
||||||
|
<div class="item-row separator">
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li v-if="props.genericDoc.storedObject !== null">
|
||||||
|
<document-action-buttons-group
|
||||||
|
:stored-object="props.genericDoc.storedObject"
|
||||||
|
></document-action-buttons-group>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
v-if="!isPicked"
|
||||||
|
type="button"
|
||||||
|
class="btn btn-chill-green text-white"
|
||||||
|
@click="clickOnAddButton"
|
||||||
|
>
|
||||||
|
<i class="bi bi-cart-plus"></i>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
type="button"
|
||||||
|
class="btn btn-chill-red text-white"
|
||||||
|
@click="
|
||||||
|
emit('removeGenericDoc', {
|
||||||
|
genericDoc: props.genericDoc,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<i class="bi bi-cart-dash"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.item-bloc {
|
||||||
|
&.isPicked {
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(25, 135, 84, 1) 0px,
|
||||||
|
rgba(25, 135, 84, 0) 9px
|
||||||
|
),
|
||||||
|
linear-gradient(
|
||||||
|
270deg,
|
||||||
|
rgba(25, 135, 84, 1) 0px,
|
||||||
|
rgba(25, 135, 84, 0) 9px
|
||||||
|
),
|
||||||
|
linear-gradient(
|
||||||
|
0deg,
|
||||||
|
rgba(25, 135, 84, 1) 0px,
|
||||||
|
rgba(25, 135, 84, 0) 9px
|
||||||
|
),
|
||||||
|
linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(25, 135, 84, 1) 0px,
|
||||||
|
rgba(25, 135, 84, 0) 9px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,113 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
|
||||||
|
import { computed, ref, useTemplateRef } from "vue";
|
||||||
|
import PickGenericDoc from "ChillMainAssets/vuejs/WorkflowAttachment/Component/PickGenericDoc.vue";
|
||||||
|
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
|
||||||
|
|
||||||
|
interface PickGenericDocModalProps {
|
||||||
|
accompanyingPeriodId: number;
|
||||||
|
toRemove: GenericDocForAccompanyingPeriod[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type PickGenericDocType = InstanceType<typeof PickGenericDoc>;
|
||||||
|
|
||||||
|
const props = defineProps<PickGenericDocModalProps>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/prefer-function-type
|
||||||
|
(
|
||||||
|
e: "pickGenericDoc",
|
||||||
|
payload: { genericDoc: GenericDocForAccompanyingPeriod },
|
||||||
|
): void;
|
||||||
|
}>();
|
||||||
|
const picker = useTemplateRef<PickGenericDocType>("picker");
|
||||||
|
const modalOpened = ref<boolean>(false);
|
||||||
|
const pickeds = ref<GenericDocForAccompanyingPeriod[]>([]);
|
||||||
|
const modalClasses = { "modal-xl": true, "modal-dialog-scrollable": true };
|
||||||
|
|
||||||
|
const numberOfPicked = computed<number>(() => pickeds.value.length);
|
||||||
|
|
||||||
|
const onPicked = ({
|
||||||
|
genericDoc,
|
||||||
|
}: {
|
||||||
|
genericDoc: GenericDocForAccompanyingPeriod;
|
||||||
|
}) => {
|
||||||
|
pickeds.value.push(genericDoc);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRemove = ({
|
||||||
|
genericDoc,
|
||||||
|
}: {
|
||||||
|
genericDoc: GenericDocForAccompanyingPeriod;
|
||||||
|
}) => {
|
||||||
|
const index = pickeds.value.findIndex(
|
||||||
|
(item) => item.uniqueKey === genericDoc.uniqueKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
throw new Error("Remove generic doc that doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
pickeds.value.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onConfirm = () => {
|
||||||
|
for (let genericDoc of pickeds.value) {
|
||||||
|
emit("pickGenericDoc", { genericDoc });
|
||||||
|
}
|
||||||
|
pickeds.value = [];
|
||||||
|
closeModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeModal = function () {
|
||||||
|
modalOpened.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openModal = function () {
|
||||||
|
modalOpened.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ openModal, closeModal });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<modal
|
||||||
|
v-if="modalOpened"
|
||||||
|
@close="closeModal"
|
||||||
|
:modal-dialog-class="modalClasses"
|
||||||
|
>
|
||||||
|
<template v-slot:header>
|
||||||
|
<h2 class="modal-title">Ajouter une pièce jointe</h2>
|
||||||
|
</template>
|
||||||
|
<template v-slot:body>
|
||||||
|
<pick-generic-doc
|
||||||
|
:accompanying-period-id="props.accompanyingPeriodId"
|
||||||
|
:to-remove="props.toRemove"
|
||||||
|
:picked-list="pickeds"
|
||||||
|
ref="picker"
|
||||||
|
@pickGenericDoc="onPicked"
|
||||||
|
@removeGenericDoc="onRemove"
|
||||||
|
></pick-generic-doc>
|
||||||
|
</template>
|
||||||
|
<template v-slot:footer>
|
||||||
|
<ul v-if="numberOfPicked > 0" class="record_actions">
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-chill-green text-white"
|
||||||
|
@click="onConfirm"
|
||||||
|
>
|
||||||
|
<template v-if="numberOfPicked > 1">
|
||||||
|
<i class="fa fa-plus"></i> Ajouter
|
||||||
|
{{ numberOfPicked }} pièces jointes
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<i class="fa fa-plus"></i> Ajouter une pièce jointe
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
</modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
@ -0,0 +1,109 @@
|
|||||||
|
import { createApp } from "vue";
|
||||||
|
import App from "./App.vue";
|
||||||
|
import { _createI18n } from "../_js/i18n";
|
||||||
|
import { WorkflowAttachment } from "ChillMainAssets/types";
|
||||||
|
import {
|
||||||
|
create_attachment,
|
||||||
|
delete_attachment,
|
||||||
|
find_attachments_by_workflow,
|
||||||
|
} from "ChillMainAssets/lib/workflow/attachments";
|
||||||
|
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
|
||||||
|
import ToastPlugin from "vue-toast-notification";
|
||||||
|
import "vue-toast-notification/dist/theme-bootstrap.css";
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const attachments = document.querySelectorAll<HTMLDivElement>(
|
||||||
|
'div[data-app="workflow_attachments"]',
|
||||||
|
);
|
||||||
|
|
||||||
|
attachments.forEach(async (el) => {
|
||||||
|
const workflowId = parseInt(el.dataset.entityWorkflowId || "");
|
||||||
|
const accompanyingPeriodId = parseInt(
|
||||||
|
el.dataset.relatedAccompanyingPeriodId || "",
|
||||||
|
);
|
||||||
|
const attachments = await find_attachments_by_workflow(workflowId);
|
||||||
|
|
||||||
|
const app = createApp({
|
||||||
|
template:
|
||||||
|
'<app :workflowId="workflowId" :accompanyingPeriodId="accompanyingPeriodId" :attachments="attachments" @pickGenericDoc="onPickGenericDoc" @removeAttachment="onRemoveAttachment"></app>',
|
||||||
|
components: { App },
|
||||||
|
data: function () {
|
||||||
|
return { workflowId, accompanyingPeriodId, attachments };
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onRemoveAttachment: async function ({
|
||||||
|
attachment,
|
||||||
|
}: {
|
||||||
|
attachment: WorkflowAttachment;
|
||||||
|
}): Promise<void> {
|
||||||
|
const index = this.$data.attachments.findIndex(
|
||||||
|
(el: WorkflowAttachment) => el.id === attachment.id,
|
||||||
|
);
|
||||||
|
if (-1 === index) {
|
||||||
|
console.warn(
|
||||||
|
"this attachment is not associated with the workflow",
|
||||||
|
attachment,
|
||||||
|
);
|
||||||
|
this.$toast.error(
|
||||||
|
"This attachment is not associated with the workflow",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await delete_attachment(attachment);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
this.$toast.error("Error while removing element");
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
this.$data.attachments.splice(index, 1);
|
||||||
|
this.$toast.success("Pièce jointe supprimée");
|
||||||
|
},
|
||||||
|
onPickGenericDoc: async function ({
|
||||||
|
genericDoc,
|
||||||
|
}: {
|
||||||
|
genericDoc: GenericDocForAccompanyingPeriod;
|
||||||
|
}): Promise<void> {
|
||||||
|
console.log("picked generic doc", genericDoc);
|
||||||
|
|
||||||
|
// prevent to create double attachment:
|
||||||
|
if (
|
||||||
|
-1 !==
|
||||||
|
this.$data.attachments.findIndex(
|
||||||
|
(el: WorkflowAttachment) =>
|
||||||
|
el.genericDoc?.key === genericDoc.key &&
|
||||||
|
JSON.stringify(el.genericDoc?.identifiers) ==
|
||||||
|
JSON.stringify(genericDoc.identifiers),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
console.warn(
|
||||||
|
"this document is already attached to the workflow",
|
||||||
|
genericDoc,
|
||||||
|
);
|
||||||
|
this.$toast.error(
|
||||||
|
"Ce document est déjà attaché au workflow",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const attachment = await create_attachment(
|
||||||
|
workflowId,
|
||||||
|
genericDoc,
|
||||||
|
);
|
||||||
|
this.$data.attachments.push(attachment);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const i18n = _createI18n({});
|
||||||
|
app.use(i18n);
|
||||||
|
app.use(ToastPlugin);
|
||||||
|
|
||||||
|
app.mount(el);
|
||||||
|
});
|
||||||
|
});
|
@ -1,123 +1,5 @@
|
|||||||
{# TODO
|
{% if related_accompanying_period is not null %}
|
||||||
Check if this template is used
|
<h2>{{ 'workflow.attachments.title'|trans }}</h2>
|
||||||
Adapt condition or Delete it
|
|
||||||
#}
|
|
||||||
{% if random(1) == 0 %}
|
|
||||||
|
|
||||||
{# For a document #}
|
|
||||||
<h2>{{ 'Document'|trans ~ 'target'|trans }}</h2>
|
|
||||||
|
|
||||||
<div class="row justify-content-center mt-5">
|
|
||||||
<div class="col-2">
|
|
||||||
<i class="fa fa-4x fa-file-text-o text-success"></i>
|
|
||||||
</div>
|
|
||||||
<div class="col-8">
|
|
||||||
<h3>Imprimé unique, parcours n°14635</h3>
|
|
||||||
<small>Document PDF (6.2 Mo)</small>
|
|
||||||
<p class="mt-2">
|
|
||||||
Description du document. Sed euismod nisi porta lorem mollis aliquam. Non curabitur gravida arcu ac tortor.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
{# For an action #}
|
|
||||||
<h2>{{ 'Accompanying Course Action'|trans ~ 'target'|trans }}</h2>
|
|
||||||
|
|
||||||
<div class="flex-table accompanying-course-work">
|
|
||||||
{# dynamic insertion
|
|
||||||
::: TODO delete all static insertion, remove condition and pass work object in inclusion
|
|
||||||
#}{% if dynamic is defined %}
|
|
||||||
|
|
||||||
{% set work = '<pass work object here>' %}
|
|
||||||
{% include '@ChillPerson/AccompanyingCourseWork/_item.html.twig' with { 'w': work } %}
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
{# BEGIN static insertion #}
|
|
||||||
<div class="item-bloc">
|
|
||||||
<div class="item-row">
|
|
||||||
<h2 class="badge-title">
|
|
||||||
<span class="title_label"></span>
|
|
||||||
<span class="title_action">Exercer un AEB > Conclure l'AEB
|
|
||||||
<ul class="small_in_title columns mt-1">
|
|
||||||
<li><span class="item-key">Date de début : </span><b>25/11/2021</b></li>
|
|
||||||
<li><span class="item-key">Date de fin : </span><b>10/03/2022</b></li>
|
|
||||||
</ul>
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div class="item-row separator">
|
|
||||||
<div class="wrap-list">
|
|
||||||
<div class="wl-row">
|
|
||||||
<div class="wl-col title"><h3>Référent</h3></div>
|
|
||||||
<div class="wl-col list"><p class="wl-item">Fred</p></div>
|
|
||||||
</div>
|
|
||||||
<div class="wl-row">
|
|
||||||
<div class="wl-col title"><h3>Usagers du parcours</h3></div>
|
|
||||||
<div class="wl-col list"><span class="wl-item">
|
|
||||||
<span class="onthefly-container" data-target-name="person" data-target-id="1937" data-action="show" data-button-text="Vernon SUBUTEX" data-display-badge="true" data-v-app=""><a data-v-0c1a1125=""><span class="chill-entity entity-person badge-person" data-v-0c1a1125="">Vernon SUBUTEX</span></a><!--teleport start--><!--teleport end--></span></span>
|
|
||||||
<span class="wl-item"><span class="onthefly-container" data-target-name="person" data-target-id="1941" data-action="show" data-button-text="Juan RAMON" data-display-badge="true" data-v-app=""><a data-v-0c1a1125=""><span class="chill-entity entity-person badge-person" data-v-0c1a1125="">Juan RAMON</span></a><!--teleport start--><!--teleport end--></span></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="wl-row">
|
|
||||||
<div class="wl-col title"><h3>Problématique sociale</h3></div>
|
|
||||||
<div class="wl-col list">
|
|
||||||
<p class="wl-item social-issues">
|
|
||||||
<span class="chill-entity entity-social-issue"><span class="badge bg-chill-l-gray text-dark"><span class="parent-0">AD - PREVENTION, ACCES AUX DROITS, BUDGET ></span><span class="child">SOUTIEN EQUILIBRE BUDGET</span></span></span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="item-row column">
|
|
||||||
<table class="obj-res-eval smallfont my-3">
|
|
||||||
<thead>
|
|
||||||
<tr><th class="obj"><h4 class="title_label">Objectif - motif - dispositif</h4></th>
|
|
||||||
<th class="res"><h4 class="title_label">Résultats - orientations</h4></th>
|
|
||||||
</tr></thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td class="obj">
|
|
||||||
<p class="chill-no-data-statement">Aucun objectif - motif - dispositif</p>
|
|
||||||
</td>
|
|
||||||
<td class="res">
|
|
||||||
<ul class="result_list">
|
|
||||||
<li>Résultat : Arrêt à l'initiative du ménage pour déménagement</li>
|
|
||||||
<li>Orientation vers une MASP</li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="item-row separator">
|
|
||||||
<div class="updatedBy">
|
|
||||||
Dernière mise à jour par
|
|
||||||
<b><span class="chill-entity entity-user">Fred<span class="user-job">(Responsable tous les territoires)</span><span class="main-scope">(ASE)</span></span></b>,<br>
|
|
||||||
le 3 décembre 2021 à 15:19
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{# END static insertion #}
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div data-app="workflow_attachments" data-workflow-id="{{ entity_workflow.id }}" data-related-accompanying-period-id="{{ related_accompanying_period.id }}" data-entity-workflow-id="{{ entity_workflow.id }}" ></div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<ul class="record_actions">
|
|
||||||
<li>
|
|
||||||
<button type="button" class="btn btn-misc">
|
|
||||||
<i class="fa fa-download fa-fw"></i>{{ 'Download'|trans }}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
{% set x = random(1) %}
|
|
||||||
<button class="btn btn-update change-icon {% if x == 1 %}disabled{% endif %}">
|
|
||||||
<i class="fa fa-fw fa-{% if x == 0 %}un{% endif %}lock"></i>
|
|
||||||
{{ 'Edit'|trans }}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
{{ encore_entry_script_tags('page_workflow_show') }}
|
{{ encore_entry_script_tags('page_workflow_show') }}
|
||||||
{{ encore_entry_script_tags('mod_wopi_link') }}
|
{{ encore_entry_script_tags('mod_wopi_link') }}
|
||||||
{{ encore_entry_script_tags('mod_document_action_buttons_group') }}
|
{{ encore_entry_script_tags('mod_document_action_buttons_group') }}
|
||||||
|
{{ encore_entry_script_tags('mod_workflow_attachment') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
@ -20,6 +21,7 @@
|
|||||||
{{ encore_entry_link_tags('page_workflow_show') }}
|
{{ encore_entry_link_tags('page_workflow_show') }}
|
||||||
{{ encore_entry_link_tags('mod_wopi_link') }}
|
{{ encore_entry_link_tags('mod_wopi_link') }}
|
||||||
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
|
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
|
||||||
|
{{ encore_entry_link_tags('mod_workflow_attachment') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% import '@ChillMain/Workflow/macro_breadcrumb.html.twig' as macro %}
|
{% import '@ChillMain/Workflow/macro_breadcrumb.html.twig' as macro %}
|
||||||
@ -58,6 +60,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="step my-4">{% include '@ChillMain/Workflow/_attachment.html.twig' %}</section>
|
||||||
|
|
||||||
<section class="step my-4">{% include '@ChillMain/Workflow/_follow.html.twig' %}</section>
|
<section class="step my-4">{% include '@ChillMain/Workflow/_follow.html.twig' %}</section>
|
||||||
{% if signatures|length > 0 %}
|
{% if signatures|length > 0 %}
|
||||||
<section class="step my-4">{% include '@ChillMain/Workflow/_signature.html.twig' %}</section>
|
<section class="step my-4">{% include '@ChillMain/Workflow/_signature.html.twig' %}</section>
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Serializer\Normalizer;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Manager;
|
||||||
|
use Chill\DocStoreBundle\Serializer\Normalizer\GenericDocNormalizer;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowAttachment;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
|
class EntityWorkflowAttachmentNormalizer implements NormalizerInterface, NormalizerAwareInterface
|
||||||
|
{
|
||||||
|
use NormalizerAwareTrait;
|
||||||
|
|
||||||
|
public const LINKED = 'entity_workflow_attachment_linked';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly Manager $manager,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function normalize($object, $format = null, array $context = []): array
|
||||||
|
{
|
||||||
|
/** @var EntityWorkflowAttachment $object */
|
||||||
|
$genericDoc = $this->manager->buildOneGenericDoc($object->getRelatedGenericDocKey(), $object->getRelatedGenericDocIdentifiers());
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $object->getId(),
|
||||||
|
'relatedGenericDocKey' => $object->getRelatedGenericDocKey(),
|
||||||
|
'relatedGenericDocIdentifiers' => $object->getRelatedGenericDocIdentifiers(),
|
||||||
|
'createdAt' => $this->normalizer->normalize($object->getCreatedAt(), $format, $context),
|
||||||
|
'createdBy' => $this->normalizer->normalize($object->getCreatedBy(), $format, $context),
|
||||||
|
'updatedAt' => $this->normalizer->normalize($object->getUpdatedAt(), $format, $context),
|
||||||
|
'updatedBy' => $this->normalizer->normalize($object->getUpdatedBy(), $format, $context),
|
||||||
|
'genericDoc' => $this->normalizer->normalize($genericDoc, $format, [
|
||||||
|
GenericDocNormalizer::ATTACHED_STORED_OBJECT_PROXY => $object->getProxyStoredObject(), ...$context,
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsNormalization($data, ?string $format = null)
|
||||||
|
{
|
||||||
|
return 'json' === $format && $data instanceof EntityWorkflowAttachment;
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
|||||||
use Chill\MainBundle\Workflow\EntityWorkflowWithPublicViewInterface;
|
use Chill\MainBundle\Workflow\EntityWorkflowWithPublicViewInterface;
|
||||||
use Chill\MainBundle\Workflow\Messenger\PostPublicViewMessage;
|
use Chill\MainBundle\Workflow\Messenger\PostPublicViewMessage;
|
||||||
use Chill\MainBundle\Workflow\Templating\EntityWorkflowViewMetadataDTO;
|
use Chill\MainBundle\Workflow\Templating\EntityWorkflowViewMetadataDTO;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
@ -189,6 +190,11 @@ class WorkflowViewSendPublicControllerTest extends TestCase
|
|||||||
throw new \BadMethodCallException('not implemented');
|
throw new \BadMethodCallException('not implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRelatedAccompanyingPeriod(EntityWorkflow $entityWorkflow): ?AccompanyingPeriod
|
||||||
|
{
|
||||||
|
throw new \BadMethodCallException('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
public function getRelatedObjects(object $object): array
|
public function getRelatedObjects(object $object): array
|
||||||
{
|
{
|
||||||
throw new \BadMethodCallException('not implemented');
|
throw new \BadMethodCallException('not implemented');
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Tests\Workflow\Attachment;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\ManagerInterface;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowAttachment;
|
||||||
|
use Chill\MainBundle\Workflow\Attachment\AddAttachmentAction;
|
||||||
|
use Chill\MainBundle\Workflow\Attachment\AddAttachmentRequestDTO;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class AddAttachmentActionTest extends TestCase
|
||||||
|
{
|
||||||
|
private EntityManagerInterface $entityManagerMock;
|
||||||
|
private AddAttachmentAction $addAttachmentAction;
|
||||||
|
|
||||||
|
private ManagerInterface $manager;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->entityManagerMock = $this->createMock(EntityManagerInterface::class);
|
||||||
|
$this->manager = $this->createMock(ManagerInterface::class);
|
||||||
|
$this->addAttachmentAction = new AddAttachmentAction($this->entityManagerMock, $this->manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInvokeCreatesAndPersistsEntityWorkflowAttachment(): void
|
||||||
|
{
|
||||||
|
$entityWorkflow = new EntityWorkflow();
|
||||||
|
$dto = new AddAttachmentRequestDTO($entityWorkflow);
|
||||||
|
$dto->relatedGenericDocKey = 'doc_key';
|
||||||
|
$dto->relatedGenericDocIdentifiers = ['id' => 1];
|
||||||
|
$this->manager->method('buildOneGenericDoc')->willReturn($g = new GenericDocDTO('doc_key', ['id' => 1], new \DateTimeImmutable(), new AccompanyingPeriod()));
|
||||||
|
$this->manager->method('fetchStoredObject')->with($g)->willReturn($storedObject = new StoredObject());
|
||||||
|
|
||||||
|
$this->entityManagerMock
|
||||||
|
->expects($this->once())
|
||||||
|
->method('persist')
|
||||||
|
->with($this->isInstanceOf(EntityWorkflowAttachment::class));
|
||||||
|
|
||||||
|
$result = $this->addAttachmentAction->__invoke($dto);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(EntityWorkflowAttachment::class, $result);
|
||||||
|
$this->assertSame('doc_key', $result->getRelatedGenericDocKey());
|
||||||
|
$this->assertSame(['id' => 1], $result->getRelatedGenericDocIdentifiers());
|
||||||
|
$this->assertSame($entityWorkflow, $result->getEntityWorkflow());
|
||||||
|
$this->assertSame($storedObject, $result->getProxyStoredObject());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Workflow\Attachment;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\Exception\AssociatedStoredObjectNotFound;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\ManagerInterface;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowAttachment;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
class AddAttachmentAction
|
||||||
|
{
|
||||||
|
public function __construct(private readonly EntityManagerInterface $em, private readonly ManagerInterface $manager) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws AssociatedStoredObjectNotFound
|
||||||
|
*/
|
||||||
|
public function __invoke(AddAttachmentRequestDTO $dto): EntityWorkflowAttachment
|
||||||
|
{
|
||||||
|
$genericDoc = $this->manager->buildOneGenericDoc($dto->relatedGenericDocKey, $dto->relatedGenericDocIdentifiers);
|
||||||
|
|
||||||
|
if (null === $genericDoc) {
|
||||||
|
throw new \RuntimeException(sprintf('could not build any generic doc, %s key and %s identifiers', $dto->relatedGenericDocKey, json_encode($dto->relatedGenericDocIdentifiers)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$storedObject = $this->manager->fetchStoredObject($genericDoc);
|
||||||
|
|
||||||
|
$attachement = new EntityWorkflowAttachment($dto->relatedGenericDocKey, $dto->relatedGenericDocIdentifiers, $dto->entityWorkflow, $storedObject);
|
||||||
|
|
||||||
|
$this->em->persist($attachement);
|
||||||
|
|
||||||
|
return $attachement;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Workflow\Attachment;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
|
final class AddAttachmentRequestDTO
|
||||||
|
{
|
||||||
|
#[Serializer\Groups('write')]
|
||||||
|
#[Assert\NotNull()]
|
||||||
|
public ?string $relatedGenericDocKey = null;
|
||||||
|
#[Serializer\Groups('write')]
|
||||||
|
#[Assert\NotNull()]
|
||||||
|
public ?array $relatedGenericDocIdentifiers = null;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public readonly EntityWorkflow $entityWorkflow,
|
||||||
|
) {}
|
||||||
|
}
|
@ -13,6 +13,7 @@ namespace Chill\MainBundle\Workflow;
|
|||||||
|
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
|
||||||
@ -35,6 +36,11 @@ interface EntityWorkflowHandlerInterface
|
|||||||
*/
|
*/
|
||||||
public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?object;
|
public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a related accompanying period, if the workflow can be related in an accompanying period.
|
||||||
|
*/
|
||||||
|
public function getRelatedAccompanyingPeriod(EntityWorkflow $entityWorkflow): ?AccompanyingPeriod;
|
||||||
|
|
||||||
public function getRelatedObjects(object $object): array;
|
public function getRelatedObjects(object $object): array;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,6 +18,7 @@ use Chill\MainBundle\Entity\Workflow\EntityWorkflowSend;
|
|||||||
use Chill\MainBundle\Workflow\Exception\HandlerNotFoundException;
|
use Chill\MainBundle\Workflow\Exception\HandlerNotFoundException;
|
||||||
use Chill\MainBundle\Workflow\Exception\HandlerWithPublicViewNotFoundException;
|
use Chill\MainBundle\Workflow\Exception\HandlerWithPublicViewNotFoundException;
|
||||||
use Chill\MainBundle\Workflow\Templating\EntityWorkflowViewMetadataDTO;
|
use Chill\MainBundle\Workflow\Templating\EntityWorkflowViewMetadataDTO;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
use Symfony\Component\Workflow\Registry;
|
use Symfony\Component\Workflow\Registry;
|
||||||
@ -157,4 +158,9 @@ class EntityWorkflowManager
|
|||||||
{
|
{
|
||||||
return $this->getHandler($entityWorkflow)->getSuggestedThirdParties($entityWorkflow);
|
return $this->getHandler($entityWorkflow)->getSuggestedThirdParties($entityWorkflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRelatedAccompanyingPeriod(EntityWorkflow $entityWorkflow): ?AccompanyingPeriod
|
||||||
|
{
|
||||||
|
return $this->getHandler($entityWorkflow)->getRelatedAccompanyingPeriod($entityWorkflow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Workflow\Helper;
|
namespace Chill\MainBundle\Workflow\Helper;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum;
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum;
|
||||||
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||||
@ -146,12 +147,14 @@ class WorkflowRelatedEntityPermissionHelper
|
|||||||
{
|
{
|
||||||
$currentUser = $this->security->getUser();
|
$currentUser = $this->security->getUser();
|
||||||
|
|
||||||
|
if (!$currentUser instanceof User) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($entityWorkflows as $entityWorkflow) {
|
foreach ($entityWorkflows as $entityWorkflow) {
|
||||||
// so, the workflow is running... We return true if the current user is involved
|
// so, the workflow is running... We return true if the current user is involved
|
||||||
foreach ($entityWorkflow->getSteps() as $step) {
|
if ($entityWorkflow->isUserInvolved($currentUser)) {
|
||||||
if ($step->getAllDestUser()->contains($currentUser)) {
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,50 @@ servers:
|
|||||||
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
|
EntityWorkflowAttachment:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: number
|
||||||
|
format: u64
|
||||||
|
minimum: 0
|
||||||
|
relatedGenericDocKey:
|
||||||
|
type: string
|
||||||
|
relatedGenericDocIdentifiers:
|
||||||
|
type: object
|
||||||
|
AddAttachmentRequest:
|
||||||
|
description: "A request to add attachment in an entity workflow"
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
relatedGenericDocKey:
|
||||||
|
type: string
|
||||||
|
relatedGenericDocIdentifiers:
|
||||||
|
type: object
|
||||||
|
PaginatedResult:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
count:
|
||||||
|
type: number
|
||||||
|
format: u64
|
||||||
|
pagination:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first:
|
||||||
|
type: number
|
||||||
|
format: u64
|
||||||
|
minimum: 0
|
||||||
|
items_per_page:
|
||||||
|
type: number
|
||||||
|
format: u64
|
||||||
|
minimum: 0
|
||||||
|
next:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
previous:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
more:
|
||||||
|
type: boolean
|
||||||
Date:
|
Date:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -990,3 +1034,70 @@ paths:
|
|||||||
$ref: '#/components/schemas/UserGroup'
|
$ref: '#/components/schemas/UserGroup'
|
||||||
403:
|
403:
|
||||||
description: "Unauthorized"
|
description: "Unauthorized"
|
||||||
|
/1.0/main/workflow/{id}/attachment:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- workflow
|
||||||
|
summary: Get a list of attachements for a given workflow
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The entity workflow id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: integer
|
||||||
|
minimum: 1
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "ok"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/EntityWorkflowAttachment'
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- workflow
|
||||||
|
summary: Create a new attachment
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The entity workflow id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: integer
|
||||||
|
minimum: 1
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/AddAttachmentRequest'
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "ok"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/EntityWorkflowAttachment'
|
||||||
|
/1.0/main/workflow/attachment/{id}:
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- workflow
|
||||||
|
summary: Remove an attachment
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The entity workflow 's attachment id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: integer
|
||||||
|
minimum: 1
|
||||||
|
responses:
|
||||||
|
204:
|
||||||
|
description: "resource was deleted successfully"
|
||||||
|
|
||||||
|
@ -150,6 +150,10 @@ module.exports = function (encore, entries) {
|
|||||||
"mod_news",
|
"mod_news",
|
||||||
__dirname + "/Resources/public/module/news/index.js",
|
__dirname + "/Resources/public/module/news/index.js",
|
||||||
);
|
);
|
||||||
|
encore.addEntry(
|
||||||
|
"mod_workflow_attachment",
|
||||||
|
__dirname + "/Resources/public/vuejs/WorkflowAttachment/index",
|
||||||
|
);
|
||||||
|
|
||||||
// Vue entrypoints
|
// Vue entrypoints
|
||||||
encore.addEntry(
|
encore.addEntry(
|
||||||
|
@ -33,6 +33,8 @@ services:
|
|||||||
# workflow related
|
# workflow related
|
||||||
Chill\MainBundle\Workflow\:
|
Chill\MainBundle\Workflow\:
|
||||||
resource: '../Workflow/'
|
resource: '../Workflow/'
|
||||||
|
exclude:
|
||||||
|
- '../Workflow/Attachment/AddAttachmentRequestDTO.php'
|
||||||
|
|
||||||
Chill\MainBundle\Workflow\EntityWorkflowManager:
|
Chill\MainBundle\Workflow\EntityWorkflowManager:
|
||||||
arguments:
|
arguments:
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\Migrations\Main;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20241129112740 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add table for EntityWorkflowAttachment';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('CREATE SEQUENCE chill_main_workflow_entity_attachment_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||||
|
$this->addSql('CREATE TABLE chill_main_workflow_entity_attachment (id INT NOT NULL, relatedGenericDocKey VARCHAR(255) NOT NULL, relatedGenericDocIdentifiers JSONB NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, entityWorkflow_id INT NOT NULL, createdBy_id INT DEFAULT NULL, updatedBy_id INT DEFAULT NULL, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('CREATE INDEX IDX_279415FFFB054143 ON chill_main_workflow_entity_attachment (entityWorkflow_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_279415FF3174800F ON chill_main_workflow_entity_attachment (createdBy_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_279415FF65FF1AEC ON chill_main_workflow_entity_attachment (updatedBy_id)');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX unique_generic_doc_by_workflow ON chill_main_workflow_entity_attachment (relatedGenericDocKey, relatedGenericDocIdentifiers, entityworkflow_id)');
|
||||||
|
$this->addSql('COMMENT ON COLUMN chill_main_workflow_entity_attachment.createdAt IS \'(DC2Type:datetime_immutable)\'');
|
||||||
|
$this->addSql('COMMENT ON COLUMN chill_main_workflow_entity_attachment.updatedAt IS \'(DC2Type:datetime_immutable)\'');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_attachment ADD CONSTRAINT FK_279415FFFB054143 FOREIGN KEY (entityWorkflow_id) REFERENCES chill_main_workflow_entity (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_attachment ADD CONSTRAINT FK_279415FF3174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_attachment ADD CONSTRAINT FK_279415FF65FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_attachment ADD storedobject_id INT NOT NULL');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_attachment ADD CONSTRAINT FK_279415FFEE684399 FOREIGN KEY (storedobject_id) REFERENCES chill_doc.stored_object (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('CREATE INDEX IDX_279415FFEE684399 ON chill_main_workflow_entity_attachment (storedobject_id)');
|
||||||
|
$this->addSql('ALTER INDEX idx_279415fffb054143 RENAME TO IDX_279415FF7D99CE94');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('DROP SEQUENCE chill_main_workflow_entity_attachment_id_seq CASCADE');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_attachment DROP CONSTRAINT FK_279415FFFB054143');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_attachment DROP CONSTRAINT FK_279415FF3174800F');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_attachment DROP CONSTRAINT FK_279415FF65FF1AEC');
|
||||||
|
$this->addSql('DROP TABLE chill_main_workflow_entity_attachment');
|
||||||
|
}
|
||||||
|
}
|
@ -609,6 +609,9 @@ workflow:
|
|||||||
reject_signature_of: Rejet de la signature de %signer%
|
reject_signature_of: Rejet de la signature de %signer%
|
||||||
reject_are_you_sure: Êtes-vous sûr de vouloir rejeter la signature de %signer%
|
reject_are_you_sure: Êtes-vous sûr de vouloir rejeter la signature de %signer%
|
||||||
|
|
||||||
|
attachments:
|
||||||
|
title: Pièces jointes
|
||||||
|
|
||||||
Subscribe final: Recevoir une notification à l'étape finale
|
Subscribe final: Recevoir une notification à l'étape finale
|
||||||
Subscribe all steps: Recevoir une notification à chaque étape
|
Subscribe all steps: Recevoir une notification à chaque étape
|
||||||
CHILL_MAIN_WORKFLOW_APPLY_ALL_TRANSITION: Appliquer les transitions sur tous les workflows
|
CHILL_MAIN_WORKFLOW_APPLY_ALL_TRANSITION: Appliquer les transitions sur tous les workflows
|
||||||
|
@ -60,9 +60,10 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
|||||||
#[ORM\ManyToOne(targetEntity: DocGeneratorTemplate::class)]
|
#[ORM\ManyToOne(targetEntity: DocGeneratorTemplate::class)]
|
||||||
private ?DocGeneratorTemplate $template = null;
|
private ?DocGeneratorTemplate $template = null;
|
||||||
|
|
||||||
#[Serializer\Groups(['read', 'write', 'accompanying_period_work_evaluation:create'])]
|
/**
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false, options: ['default' => ''])]
|
* Store the title only if the storedObject is not yet associated with the instance.
|
||||||
private ?string $title = '';
|
*/
|
||||||
|
private string $proxyTitle = '';
|
||||||
|
|
||||||
public function getAccompanyingPeriodWorkEvaluation(): ?AccompanyingPeriodWorkEvaluation
|
public function getAccompanyingPeriodWorkEvaluation(): ?AccompanyingPeriodWorkEvaluation
|
||||||
{
|
{
|
||||||
@ -89,9 +90,10 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
|||||||
return $this->template;
|
return $this->template;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Serializer\Groups(['read'])]
|
||||||
public function getTitle(): ?string
|
public function getTitle(): ?string
|
||||||
{
|
{
|
||||||
return $this->title;
|
return (string) $this->getStoredObject()?->getTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setAccompanyingPeriodWorkEvaluation(?AccompanyingPeriodWorkEvaluation $accompanyingPeriodWorkEvaluation): AccompanyingPeriodWorkEvaluationDocument
|
public function setAccompanyingPeriodWorkEvaluation(?AccompanyingPeriodWorkEvaluation $accompanyingPeriodWorkEvaluation): AccompanyingPeriodWorkEvaluationDocument
|
||||||
@ -125,6 +127,10 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
|||||||
{
|
{
|
||||||
$this->storedObject = $storedObject;
|
$this->storedObject = $storedObject;
|
||||||
|
|
||||||
|
if ('' !== $this->proxyTitle) {
|
||||||
|
$this->storedObject->setTitle($this->proxyTitle);
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,9 +141,14 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setTitle(?string $title): AccompanyingPeriodWorkEvaluationDocument
|
#[Serializer\Groups(['write', 'accompanying_period_work_evaluation:create'])]
|
||||||
|
public function setTitle(?string $proxyTitle): AccompanyingPeriodWorkEvaluationDocument
|
||||||
{
|
{
|
||||||
$this->title = $title;
|
if (null !== $this->storedObject) {
|
||||||
|
$this->storedObject->setTitle((string) $proxyTitle);
|
||||||
|
} else {
|
||||||
|
$this->proxyTitle = (string) $proxyTitle;
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -1,83 +1,3 @@
|
|||||||
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
|
||||||
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
|
||||||
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
|
|
||||||
|
|
||||||
{% set w = document.accompanyingPeriodWorkEvaluation.accompanyingPeriodWork %}
|
|
||||||
|
|
||||||
<div class="item-bloc">
|
<div class="item-bloc">
|
||||||
<div class="item-row">
|
{{ include('@ChillPerson/GenericDoc/evaluation_document_row.html.twig') }}
|
||||||
<div class="item-col" style="width: unset">
|
|
||||||
{% if document.storedObject.isPending %}
|
|
||||||
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.storedObject.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
|
||||||
{% elseif document.storedObject.isFailure %}
|
|
||||||
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
|
||||||
{% endif %}
|
|
||||||
<div>
|
|
||||||
{% if context == 'person' %}
|
|
||||||
<span class="badge bg-primary">
|
|
||||||
<i class="fa fa-random"></i> {{ w.accompanyingPeriod.id }}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
<div class="badge-accompanying-work-type">
|
|
||||||
<span class="title_label"></span>
|
|
||||||
<span class="title_action">{{ w.socialAction|chill_entity_render_string }} > {{ document.accompanyingPeriodWorkEvaluation.evaluation.title|localize_translatable_string }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="denomination h2">
|
|
||||||
{{ document.title|chill_print_or_message("No title") }}
|
|
||||||
</div>
|
|
||||||
{% if document.storedObject.type is not empty %}
|
|
||||||
<div>
|
|
||||||
{{ mm.mimeIcon(document.storedObject.type) }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if document.storedObject.hasTemplate %}
|
|
||||||
<div>
|
|
||||||
<p>{{ document.storedObject.template.name|localize_translatable_string }}</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-col">
|
|
||||||
<div class="container">
|
|
||||||
<div class="dates row text-end">
|
|
||||||
<span>{{ document.storedObject.createdAt|format_date('short') }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-row separator">
|
|
||||||
<div class="item-col item-meta">
|
|
||||||
{{ mmm.createdBy(document) }}
|
|
||||||
</div>
|
|
||||||
<ul class="item-col record_actions flex-shrink-1">
|
|
||||||
<li>
|
|
||||||
{{ chill_entity_workflow_list('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', document.id) }}
|
|
||||||
</li>
|
|
||||||
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_EVALUATION_DOCUMENT_SHOW', document) %}
|
|
||||||
<li>
|
|
||||||
{{ document.storedObject|chill_document_button_group(document.title, is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', document.accompanyingPeriodWorkEvaluation.accompanyingPeriodWork)) }}
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w) %}
|
|
||||||
<li>
|
|
||||||
<form method="post" action="{{ chill_path_add_return_path('chill_person_accompanying_period_work_evaluation_document_duplicate', {id: document.id}) }}">
|
|
||||||
<button type="submit" class="btn btn-duplicate" title="{{ 'crud.view.link_duplicate'|trans }}"></button>
|
|
||||||
</form>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w) %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', {'id': w.id, 'docId': document.id}) }}" class="btn btn-edit"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_SEE', w)%}
|
|
||||||
<li>
|
|
||||||
<a href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_show', {'id': w.id, 'docId': document.id}) }}" class="btn btn-show"></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
||||||
|
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
||||||
|
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
|
||||||
|
|
||||||
|
{% set w = document.accompanyingPeriodWorkEvaluation.accompanyingPeriodWork %}
|
||||||
|
|
||||||
|
<div class="item-row">
|
||||||
|
<div class="item-col" style="width: unset">
|
||||||
|
{% if document.storedObject.isPending %}
|
||||||
|
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.storedObject.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
||||||
|
{% elseif document.storedObject.isFailure %}
|
||||||
|
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
{% if context == 'person' %}
|
||||||
|
<span class="badge bg-primary">
|
||||||
|
<i class="fa fa-random"></i> {{ w.accompanyingPeriod.id }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
<div class="badge-accompanying-work-type">
|
||||||
|
<span class="title_label"></span>
|
||||||
|
<span class="title_action">{{ w.socialAction|chill_entity_render_string }} > {{ document.accompanyingPeriodWorkEvaluation.evaluation.title|localize_translatable_string }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="denomination h2">
|
||||||
|
{{ document.title|chill_print_or_message("No title") }}
|
||||||
|
</div>
|
||||||
|
{% if document.storedObject.type is not empty %}
|
||||||
|
<div>
|
||||||
|
{{ mm.mimeIcon(document.storedObject.type) }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if document.storedObject.hasTemplate %}
|
||||||
|
<div>
|
||||||
|
<p>{{ document.storedObject.template.name|localize_translatable_string }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-col">
|
||||||
|
<div class="container">
|
||||||
|
<div class="dates row text-end">
|
||||||
|
<span>{{ document.storedObject.createdAt|format_date('short') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if show_actions %}
|
||||||
|
<div class="item-row separator">
|
||||||
|
<div class="item-col item-meta">
|
||||||
|
{{ mmm.createdBy(document) }}
|
||||||
|
</div>
|
||||||
|
<ul class="item-col record_actions flex-shrink-1">
|
||||||
|
<li>
|
||||||
|
{{ chill_entity_workflow_list('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', document.id) }}
|
||||||
|
</li>
|
||||||
|
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_EVALUATION_DOCUMENT_SHOW', document) %}
|
||||||
|
<li>
|
||||||
|
{{ document.storedObject|chill_document_button_group(document.title, is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', document.accompanyingPeriodWorkEvaluation.accompanyingPeriodWork)) }}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w) %}
|
||||||
|
<li>
|
||||||
|
<form method="post" action="{{ chill_path_add_return_path('chill_person_accompanying_period_work_evaluation_document_duplicate', {id: document.id}) }}">
|
||||||
|
<button type="submit" class="btn btn-duplicate" title="{{ 'crud.view.link_duplicate'|trans }}"></button>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', {'id': w.id, 'docId': document.id}) }}" class="btn btn-edit"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_SEE', w)%}
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_show', {'id': w.id, 'docId': document.id}) }}" class="btn btn-show"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Service\GenericDoc\Normalizer;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocNormalizerInterface;
|
||||||
|
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocumentRepository;
|
||||||
|
use Chill\PersonBundle\Service\GenericDoc\Providers\AccompanyingPeriodWorkEvaluationGenericDocProvider;
|
||||||
|
use Chill\PersonBundle\Service\GenericDoc\Renderer\AccompanyingPeriodWorkEvaluationGenericDocRenderer;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
|
final readonly class AccompanyingPeriodWorkEvaluationGenericDocNormalizer implements GenericDocNormalizerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private AccompanyingPeriodWorkEvaluationDocumentRepository $accompanyingPeriodWorkEvaluationDocumentRepository,
|
||||||
|
private Environment $twig,
|
||||||
|
private AccompanyingPeriodWorkEvaluationGenericDocRenderer $renderer,
|
||||||
|
private TranslatorInterface $translator,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function supportsNormalization(GenericDocDTO $genericDocDTO, string $format, array $context = []): bool
|
||||||
|
{
|
||||||
|
return AccompanyingPeriodWorkEvaluationGenericDocProvider::KEY === $genericDocDTO->key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function normalize(GenericDocDTO $genericDocDTO, string $format, array $context = []): array
|
||||||
|
{
|
||||||
|
if (null === $evaluationDoc = $this->accompanyingPeriodWorkEvaluationDocumentRepository->find($genericDocDTO->identifiers['id'])) {
|
||||||
|
return ['title' => $this->translator->trans('generic_doc.document removed'), 'isPresent' => false];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => $evaluationDoc->getTitle(),
|
||||||
|
'html' => $this->twig->render(
|
||||||
|
$this->renderer->getTemplate($genericDocDTO, ['show-actions' => false, 'row-only' => true]),
|
||||||
|
$this->renderer->getTemplateData($genericDocDTO, ['show-actions' => false, 'row-only' => true])
|
||||||
|
),
|
||||||
|
'isPresent' => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -14,10 +14,12 @@ namespace Chill\PersonBundle\Service\GenericDoc\Providers;
|
|||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||||
|
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocumentRepository;
|
||||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter;
|
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
@ -30,8 +32,38 @@ final readonly class AccompanyingPeriodWorkEvaluationGenericDocProvider implemen
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private Security $security,
|
private Security $security,
|
||||||
private EntityManagerInterface $entityManager,
|
private EntityManagerInterface $entityManager,
|
||||||
|
private AccompanyingPeriodWorkEvaluationDocumentRepository $accompanyingPeriodWorkEvaluationDocumentRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
public function fetchAssociatedStoredObject(GenericDocDTO $genericDocDTO): ?StoredObject
|
||||||
|
{
|
||||||
|
return $this->accompanyingPeriodWorkEvaluationDocumentRepository->find($genericDocDTO->identifiers['id'])?->getStoredObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsGenericDoc(GenericDocDTO $genericDocDTO): bool
|
||||||
|
{
|
||||||
|
return $this->supportsKeyAndIdentifiers($genericDocDTO->key, $genericDocDTO->identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsKeyAndIdentifiers(string $key, array $identifiers): bool
|
||||||
|
{
|
||||||
|
return self::KEY === $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildOneGenericDoc(string $key, array $identifiers): ?GenericDocDTO
|
||||||
|
{
|
||||||
|
if (null === $document = $this->accompanyingPeriodWorkEvaluationDocumentRepository->find($identifiers['id'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GenericDocDTO(
|
||||||
|
$key,
|
||||||
|
$identifiers,
|
||||||
|
$document->getAccompanyingPeriodWorkEvaluation()->getCreatedAt(),
|
||||||
|
$document->getAccompanyingPeriodWorkEvaluation()->getAccompanyingPeriodWork()->getAccompanyingPeriod()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||||
{
|
{
|
||||||
$accompanyingPeriodWorkMetadata = $this->entityManager->getClassMetadata(AccompanyingPeriod\AccompanyingPeriodWork::class);
|
$accompanyingPeriodWorkMetadata = $this->entityManager->getClassMetadata(AccompanyingPeriod\AccompanyingPeriodWork::class);
|
||||||
@ -53,7 +85,6 @@ final readonly class AccompanyingPeriodWorkEvaluationGenericDocProvider implemen
|
|||||||
|
|
||||||
private function addWhereClausesToQuery(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
|
private function addWhereClausesToQuery(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
|
||||||
{
|
{
|
||||||
$classMetadata = $this->entityManager->getClassMetadata(AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument::class);
|
|
||||||
$storedObjectMetadata = $this->entityManager->getClassMetadata(StoredObject::class);
|
$storedObjectMetadata = $this->entityManager->getClassMetadata(StoredObject::class);
|
||||||
|
|
||||||
if (null !== $startDate) {
|
if (null !== $startDate) {
|
||||||
@ -74,7 +105,7 @@ final readonly class AccompanyingPeriodWorkEvaluationGenericDocProvider implemen
|
|||||||
|
|
||||||
if (null !== $content) {
|
if (null !== $content) {
|
||||||
$query->addWhereClause(
|
$query->addWhereClause(
|
||||||
sprintf('apwed.%s ilike ?', $classMetadata->getColumnName('title')),
|
sprintf('doc_store.%s ilike ?', $storedObjectMetadata->getColumnName('title')),
|
||||||
['%'.$content.'%'],
|
['%'.$content.'%'],
|
||||||
[Types::STRING]
|
[Types::STRING]
|
||||||
);
|
);
|
||||||
|
@ -16,6 +16,9 @@ use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
|||||||
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocumentRepository;
|
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocumentRepository;
|
||||||
use Chill\PersonBundle\Service\GenericDoc\Providers\AccompanyingPeriodWorkEvaluationGenericDocProvider;
|
use Chill\PersonBundle\Service\GenericDoc\Providers\AccompanyingPeriodWorkEvaluationGenericDocProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implements GenericDocRendererInterface<array{row-only?: bool, show-actions?: bool}>
|
||||||
|
*/
|
||||||
final readonly class AccompanyingPeriodWorkEvaluationGenericDocRenderer implements GenericDocRendererInterface
|
final readonly class AccompanyingPeriodWorkEvaluationGenericDocRenderer implements GenericDocRendererInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@ -29,7 +32,8 @@ final readonly class AccompanyingPeriodWorkEvaluationGenericDocRenderer implemen
|
|||||||
|
|
||||||
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
|
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
|
||||||
{
|
{
|
||||||
return '@ChillPerson/GenericDoc/evaluation_document.html.twig';
|
return ($options['row-only'] ?? false) ? '@ChillPerson/GenericDoc/evaluation_document_row.html.twig'
|
||||||
|
: '@ChillPerson/GenericDoc/evaluation_document.html.twig';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
|
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
|
||||||
@ -37,6 +41,7 @@ final readonly class AccompanyingPeriodWorkEvaluationGenericDocRenderer implemen
|
|||||||
return [
|
return [
|
||||||
'document' => $this->accompanyingPeriodWorkEvaluationDocumentRepository->find($genericDocDTO->identifiers['id']),
|
'document' => $this->accompanyingPeriodWorkEvaluationDocumentRepository->find($genericDocDTO->identifiers['id']),
|
||||||
'context' => $genericDocDTO->getContext(),
|
'context' => $genericDocDTO->getContext(),
|
||||||
|
'show_actions' => $options['show-actions'] ?? true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Service\GenericDoc\Providers;
|
namespace Service\GenericDoc\Providers;
|
||||||
|
|
||||||
|
use Chill\CalendarBundle\Repository\CalendarDocRepositoryInterface;
|
||||||
use Chill\CalendarBundle\Security\Voter\CalendarVoter;
|
use Chill\CalendarBundle\Security\Voter\CalendarVoter;
|
||||||
use Chill\CalendarBundle\Service\GenericDoc\Providers\AccompanyingPeriodCalendarGenericDocProvider;
|
use Chill\CalendarBundle\Service\GenericDoc\Providers\AccompanyingPeriodCalendarGenericDocProvider;
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder;
|
||||||
@ -35,11 +36,14 @@ class AccompanyingPeriodCalendarGenericDocProviderTest extends KernelTestCase
|
|||||||
|
|
||||||
private EntityManagerInterface $entityManager;
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
|
private CalendarDocRepositoryInterface $calendarDocRepository;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
self::bootKernel();
|
self::bootKernel();
|
||||||
$this->security = self::getContainer()->get(Security::class);
|
$this->security = self::getContainer()->get(Security::class);
|
||||||
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
|
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
|
||||||
|
$this->calendarDocRepository = self::getContainer()->get(CalendarDocRepositoryInterface::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,7 +51,7 @@ class AccompanyingPeriodCalendarGenericDocProviderTest extends KernelTestCase
|
|||||||
*/
|
*/
|
||||||
public function testBuildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void
|
public function testBuildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void
|
||||||
{
|
{
|
||||||
$provider = new AccompanyingPeriodCalendarGenericDocProvider($this->security, $this->entityManager);
|
$provider = new AccompanyingPeriodCalendarGenericDocProvider($this->security, $this->entityManager, $this->calendarDocRepository);
|
||||||
|
|
||||||
$query = $provider->buildFetchQueryForAccompanyingPeriod($accompanyingPeriod, $startDate, $endDate, $content);
|
$query = $provider->buildFetchQueryForAccompanyingPeriod($accompanyingPeriod, $startDate, $endDate, $content);
|
||||||
|
|
||||||
@ -66,7 +70,7 @@ class AccompanyingPeriodCalendarGenericDocProviderTest extends KernelTestCase
|
|||||||
$security = $this->prophesize(Security::class);
|
$security = $this->prophesize(Security::class);
|
||||||
$security->isGranted(CalendarVoter::SEE, Argument::any())->willReturn(true);
|
$security->isGranted(CalendarVoter::SEE, Argument::any())->willReturn(true);
|
||||||
|
|
||||||
$provider = new AccompanyingPeriodCalendarGenericDocProvider($security->reveal(), $this->entityManager);
|
$provider = new AccompanyingPeriodCalendarGenericDocProvider($security->reveal(), $this->entityManager, $this->calendarDocRepository);
|
||||||
|
|
||||||
$query = $provider->buildFetchQueryForPerson($person, $startDate, $endDate, $content);
|
$query = $provider->buildFetchQueryForPerson($person, $startDate, $endDate, $content);
|
||||||
|
|
||||||
@ -87,7 +91,7 @@ class AccompanyingPeriodCalendarGenericDocProviderTest extends KernelTestCase
|
|||||||
$security = $this->prophesize(Security::class);
|
$security = $this->prophesize(Security::class);
|
||||||
$security->isGranted(CalendarVoter::SEE, Argument::any())->willReturn(false);
|
$security->isGranted(CalendarVoter::SEE, Argument::any())->willReturn(false);
|
||||||
|
|
||||||
$provider = new AccompanyingPeriodCalendarGenericDocProvider($security->reveal(), $this->entityManager);
|
$provider = new AccompanyingPeriodCalendarGenericDocProvider($security->reveal(), $this->entityManager, $this->calendarDocRepository);
|
||||||
|
|
||||||
$query = $provider->buildFetchQueryForPerson($person, $startDate, $endDate, $content);
|
$query = $provider->buildFetchQueryForPerson($person, $startDate, $endDate, $content);
|
||||||
|
|
||||||
|
@ -9,10 +9,11 @@ declare(strict_types=1);
|
|||||||
* the LICENSE file that was distributed with this source code.
|
* the LICENSE file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Service\GenericDoc\Providers;
|
namespace Chill\PersonBundle\Tests\Service\GenericDoc\Providers;
|
||||||
|
|
||||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder;
|
use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocumentRepository;
|
||||||
use Chill\PersonBundle\Service\GenericDoc\Providers\AccompanyingPeriodWorkEvaluationGenericDocProvider;
|
use Chill\PersonBundle\Service\GenericDoc\Providers\AccompanyingPeriodWorkEvaluationGenericDocProvider;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Prophecy\PhpUnit\ProphecyTrait;
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
@ -29,10 +30,13 @@ class AccompanyingPeriodWorkEvaluationGenericDocProviderTest extends KernelTestC
|
|||||||
use ProphecyTrait;
|
use ProphecyTrait;
|
||||||
private EntityManagerInterface $entityManager;
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
|
private AccompanyingPeriodWorkEvaluationDocumentRepository $repository;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
self::bootKernel();
|
self::bootKernel();
|
||||||
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
|
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
|
||||||
|
$this->repository = self::getContainer()->get(AccompanyingPeriodWorkEvaluationDocumentRepository::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,7 +59,8 @@ class AccompanyingPeriodWorkEvaluationGenericDocProviderTest extends KernelTestC
|
|||||||
|
|
||||||
$provider = new AccompanyingPeriodWorkEvaluationGenericDocProvider(
|
$provider = new AccompanyingPeriodWorkEvaluationGenericDocProvider(
|
||||||
$security->reveal(),
|
$security->reveal(),
|
||||||
$this->entityManager
|
$this->entityManager,
|
||||||
|
$this->repository,
|
||||||
);
|
);
|
||||||
|
|
||||||
$query = $provider->buildFetchQueryForAccompanyingPeriod($period, $startDate, $endDate, $content);
|
$query = $provider->buildFetchQueryForAccompanyingPeriod($period, $startDate, $endDate, $content);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user