Compare commits

..

6 Commits

Author SHA1 Message Date
68688dd528 Revert "Merge branch 'revert-671bb6d5' into 'master'"
This reverts merge request !732
2024-09-19 13:40:09 +00:00
bfd7dc2270 Merge branch 'revert-671bb6d5' into 'master'
Revert "Merge branch 'signature-app/wp-576-restorestored-object-version' into 'master'"

See merge request Chill-Projet/chill-bundles!732
2024-09-19 13:32:41 +00:00
e4d0705e84 Revert "Merge branch 'signature-app/wp-576-restorestored-object-version' into 'master'"
This reverts merge request !586
2024-09-19 13:26:12 +00:00
671bb6d593 Merge branch 'signature-app/wp-576-restorestored-object-version' into 'master'
See the list of stored object and restore some versions

Closes #307

See merge request Chill-Projet/chill-bundles!730
2024-09-19 11:51:41 +00:00
79e26ffeae Merge branch '309-fix-referrer-scope-aggregator' into 'master'
Fix referrer scope date comparison in aggregator

Closes #309

See merge request Chill-Projet/chill-bundles!728
2024-09-16 13:58:58 +00:00
be8901a5c4 Fix referrer scope date comparison in aggregator
Correct the date comparison logic to use openingDate instead of closingDate when evaluating user history end dates. This ensures accurate grouping by referrer in the accompanying course aggregators. Added a changelog entry for Issue #309.
2024-09-16 15:52:48 +02:00
30 changed files with 74 additions and 719 deletions

View File

@@ -0,0 +1,6 @@
kind: Fixed
body: |
Correctly compute the grouping by referrer aggregator
time: 2024-09-16T15:51:50.268336979+02:00
custom:
Issue: "309"

View File

@@ -1,55 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\DocStoreBundle\Controller;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
use Chill\DocStoreBundle\Workflow\AccompanyingCourseDocumentDuplicator;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Security;
final readonly class DocumentAccompanyingCourseDuplicateController
{
public function __construct(
private Security $security,
private AccompanyingCourseDocumentDuplicator $documentWorkflowDuplicator,
private EntityManagerInterface $entityManager,
private UrlGeneratorInterface $urlGenerator,
) {}
#[Route('/{_locale}/doc-store/accompanying-course-document/{id}/duplicate', name: 'chill_doc_store_accompanying_course_document_duplicate')]
public function __invoke(AccompanyingCourseDocument $document, Request $request, Session $session): Response
{
if (!$this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $document)) {
throw new AccessDeniedHttpException('not allowed to see this document');
}
if (!$this->security->isGranted(AccompanyingCourseDocumentVoter::CREATE, $document->getCourse())) {
throw new AccessDeniedHttpException('not allowed to create this document');
}
$duplicated = $this->documentWorkflowDuplicator->duplicate($document);
$this->entityManager->persist($duplicated);
$this->entityManager->flush();
return new RedirectResponse(
$this->urlGenerator->generate('accompanying_course_document_edit', ['id' => $duplicated->getId(), 'course' => $duplicated->getCourse()->getId()])
);
}
}

View File

@@ -23,7 +23,7 @@ class AccompanyingCourseDocumentRepository implements ObjectRepository, Associat
{
private readonly EntityRepository $repository;
public function __construct(EntityManagerInterface $em)
public function __construct(private readonly EntityManagerInterface $em)
{
$this->repository = $em->getRepository(AccompanyingCourseDocument::class);
}

View File

@@ -73,15 +73,8 @@
<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>
<a href="{{ chill_path_add_return_path('accompanying_course_document_show', {'course': accompanyingCourse.id, 'id': document.id}) }}" class="btn btn-show"></a>
</li>
{% endif %}
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document) %}
@@ -89,9 +82,9 @@
<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>
{% 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 %}
{% else %}

View File

@@ -1,47 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\DocStoreBundle\Service;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Entity\StoredObjectVersion;
use Psr\Log\LoggerInterface;
/**
* Class which duplicate a stored object into a new one, recreating a stored object.
*/
class StoredObjectDuplicate
{
public function __construct(private readonly StoredObjectManagerInterface $storedObjectManager, private readonly LoggerInterface $logger) {}
public function duplicate(StoredObject|StoredObjectVersion $from): StoredObject
{
$fromVersion = $from instanceof StoredObjectVersion ? $from : $from->getCurrentVersion();
$oldContent = $this->storedObjectManager->read($fromVersion);
$storedObject = new StoredObject();
$newVersion = $this->storedObjectManager->write($storedObject, $oldContent, $fromVersion->getType());
$newVersion->setCreatedFrom($fromVersion);
$this->logger->info('[StoredObjectDuplicate] Duplicated stored object from a version of a previous stored object', [
'from_stored_object_uuid' => $fromVersion->getStoredObject()->getUuid(),
'to_stored_object_uuid' => $storedObject->getUuid(),
'old_version_id' => $fromVersion->getId(),
'old_version_version' => $fromVersion->getVersion(),
'new_version_id' => $newVersion->getVersion(),
]);
return $storedObject;
}
}

View File

@@ -14,9 +14,6 @@ namespace Chill\DocStoreBundle\Service;
use Chill\DocStoreBundle\Entity\StoredObjectVersion;
use Psr\Log\LoggerInterface;
/**
* Class responsible for restoring stored object versions into the same stored object.
*/
final readonly class StoredObjectRestore implements StoredObjectRestoreInterface
{
public function __construct(private readonly StoredObjectManagerInterface $storedObjectManager, private readonly LoggerInterface $logger) {}

View File

@@ -11,7 +11,6 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\Service;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum;
use Chill\MainBundle\Workflow\EntityWorkflowManager;
use Symfony\Component\Security\Core\Security;
@@ -32,16 +31,6 @@ class WorkflowStoredObjectPermissionHelper
if (!$workflow->getCurrentStep()->getAllDestUser()->contains($currentUser)) {
return false;
}
// as soon as there is one signatured applyied, we are not able to
// edit the document any more
foreach ($workflow->getSteps() as $step) {
foreach ($step->getSignatures() as $signature) {
if (EntityWorkflowSignatureStateEnum::SIGNED === $signature->getState()) {
return false;
}
}
}
}
return true;

View File

@@ -1,48 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\DocStoreBundle\Tests\Service;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Service\StoredObjectDuplicate;
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
/**
* @internal
*
* @coversNothing
*/
class StoredObjectDuplicateTest extends TestCase
{
public function testDuplicateHappyScenario(): void
{
$storedObject = new StoredObject();
$version = $storedObject->registerVersion(type: $type = 'application/test');
$manager = $this->createMock(StoredObjectManagerInterface::class);
$manager->method('read')->with($version)->willReturn('1234');
$manager
->expects($this->once())
->method('write')
->with($this->isInstanceOf(StoredObject::class), '1234', 'application/test')
->willReturnCallback(fn (StoredObject $so, $content, $type) => $so->registerVersion(type: $type));
$storedObjectDuplicate = new StoredObjectDuplicate($manager, new NullLogger());
$actual = $storedObjectDuplicate->duplicate($storedObject);
self::assertNotNull($actual->getCurrentVersion());
self::assertNotNull($actual->getCurrentVersion()->getCreatedFrom());
self::assertSame($version, $actual->getCurrentVersion()->getCreatedFrom());
}
}

View File

@@ -1,101 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\DocStoreBundle\Tests\Service;
use Chill\DocStoreBundle\Service\WorkflowStoredObjectPermissionHelper;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStepSignature;
use Chill\MainBundle\Workflow\EntityWorkflowManager;
use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO;
use Chill\PersonBundle\Entity\Person;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Security\Core\Security;
/**
* @internal
*
* @coversNothing
*/
class WorkflowStoredObjectPermissionHelperTest extends TestCase
{
use ProphecyTrait;
/**
* @dataProvider provideDataNotBlockByWorkflow
*/
public function testNotBlockByWorkflow(EntityWorkflow $entityWorkflow, User $user, bool $expected, string $message): void
{
$object = new \stdClass();
$helper = $this->buildHelper($object, $entityWorkflow, $user);
self::assertEquals($expected, $helper->notBlockedByWorkflow($entityWorkflow), $message);
}
private function buildHelper(object $relatedEntity, EntityWorkflow $entityWorkflow, User $user): WorkflowStoredObjectPermissionHelper
{
$security = $this->prophesize(Security::class);
$security->getUser()->willReturn($user);
$entityWorkflowManager = $this->prophesize(EntityWorkflowManager::class);
$entityWorkflowManager->findByRelatedEntity(Argument::type('object'))->willReturn([$entityWorkflow]);
return new WorkflowStoredObjectPermissionHelper($security->reveal(), $entityWorkflowManager->reveal());
}
public static function provideDataNotBlockByWorkflow(): iterable
{
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable());
yield [$entityWorkflow, new User(), false, 'blocked because the user is not present as a dest user'];
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers[] = $user = new User();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), $user);
yield [$entityWorkflow, $user, true, 'allowed because the user is present as a dest user'];
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers[] = $user = new User();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), $user);
$entityWorkflow->getCurrentStep()->setIsFinal(true);
yield [$entityWorkflow, $user, false, 'blocked because the step is final'];
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers[] = $user = new User();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), $user);
$step = $entityWorkflow->getCurrentStep();
new EntityWorkflowStepSignature($step, new Person());
yield [$entityWorkflow, $user, true, 'allow, a signature is present but still pending'];
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers[] = $user = new User();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), $user);
$step = $entityWorkflow->getCurrentStep();
$signature = new EntityWorkflowStepSignature($step, new Person());
$signature->setState(EntityWorkflowSignatureStateEnum::SIGNED);
yield [$entityWorkflow, $user, false, 'blocked, a signature is present and signed'];
}
}

View File

@@ -1,70 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\DocStoreBundle\Tests\Workflow;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\DocStoreBundle\Entity\DocumentCategory;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Service\StoredObjectDuplicate;
use Chill\DocStoreBundle\Workflow\AccompanyingCourseDocumentDuplicator;
use Chill\MainBundle\Entity\User;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Clock\MockClock;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @internal
*
* @coversNothing
*/
class AccompanyingCourseDocumentDuplicatorTest extends TestCase
{
public function testDuplicate(): void
{
$object = new StoredObject();
$document = new AccompanyingCourseDocument();
$document
->setDate($date = new \DateTimeImmutable())
->setObject($object)
->setTitle('Title')
->setUser($user = new User())
->setCategory($category = new DocumentCategory('bundle', 10))
->setDescription($description = 'Description');
$actual = $this->buildDuplicator()->duplicate($document);
self::assertSame($date, $actual->getDate());
// FYI, the duplication of object is checked by the mock
self::assertNotNull($actual->getObject());
self::assertStringStartsWith('Title', $actual->getTitle());
self::assertSame($user, $actual->getUser());
self::assertSame($category, $actual->getCategory());
self::assertEquals($description, $actual->getDescription());
}
private function buildDuplicator(): AccompanyingCourseDocumentDuplicator
{
$storedObjectDuplicate = $this->createMock(StoredObjectDuplicate::class);
$storedObjectDuplicate->expects($this->once())->method('duplicate')
->with($this->isInstanceOf(StoredObject::class))->willReturn(new StoredObject());
$translator = $this->createMock(TranslatorInterface::class);
$translator->method('trans')->withAnyParameters()->willReturn('duplicated');
$clock = new MockClock();
return new AccompanyingCourseDocumentDuplicator(
$storedObjectDuplicate,
$translator,
$clock
);
}
}

View File

@@ -1,45 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\DocStoreBundle\Workflow;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\DocStoreBundle\Service\StoredObjectDuplicate;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Stores the logic to duplicate an AccompanyingCourseDocument associated to a workflow.
*/
class AccompanyingCourseDocumentDuplicator
{
public function __construct(
private readonly StoredObjectDuplicate $storedObjectDuplicate,
private readonly TranslatorInterface $translator,
private readonly ClockInterface $clock,
) {}
public function duplicate(AccompanyingCourseDocument $document): AccompanyingCourseDocument
{
$newDoc = new AccompanyingCourseDocument();
$newDoc
->setCourse($document->getCourse())
->setTitle($document->getTitle().' ('.$this->translator->trans('acc_course_document.duplicated_at', ['at' => $this->clock->now()]).')')
->setDate($document->getDate())
->setDescription($document->getDescription())
->setCategory($document->getCategory())
->setUser($document->getUser())
->setObject($this->storedObjectDuplicate->duplicate($document->getObject()))
;
return $newDoc;
}
}

View File

@@ -1,3 +0,0 @@
acc_course_document:
duplicated_at: >-
Dupliqué le {at, date, long} à {at, time, short}

View File

@@ -13,7 +13,6 @@ namespace Chill\MainBundle\Repository\Workflow;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
@@ -212,19 +211,17 @@ class EntityWorkflowRepository implements ObjectRepository
$qb->select('sw.id')
// only the workflow which are not finalized
->where(sprintf('NOT EXISTS (SELECT 1 FROM %s ews WHERE ews.isFinal = TRUE AND ews.entityWorkflow = sw.id)', EntityWorkflowStep::class))
->where('NOT EXISTS (SELECT 1 FROM chill_main_entity_workflow_step ews WHERE ews.isFinal = TRUE AND ews.entityWorkflow = sw.id)')
->andWhere(
$qb->expr()->orX(
// only the workflow where all the transitions are older than transitionAt
sprintf(':olderThanDate > ALL (SELECT ews2.transitionAt FROM %s ews2 WHERE ews2.transitionAt IS NOT NULL AND ews2.entityWorkflow = sw)', EntityWorkflowStep::class),
// only the workflow where all the last transition is older than transitionAt
':olderThanDate > ALL (SELECT ews.transitionAt FROM chill_main_entity_workflow_step ews WHERE ews.transitionAt IS NOT NULL AND ews.entityWorkflow = sw.id)',
// or the workflow which have only the initial step, with no transition
sprintf('1 = (SELECT COUNT(ews3.id) FROM %s ews3 WHERE ews3.currentStep = :initial AND ews3.transitionAt IS NULL AND ews3.entityWorkflow = sw)', EntityWorkflowStep::class),
'1 = (SELECT COUNT(ews.id) FROM chill_main_entity_workflow_step ews WHERE ews.step = :initial AND ews.transitionAt IS NULL AND ews.createdAt < :olderThanDate AND ews.entityWorkflow = sw.id)',
)
)
->andWhere('sw.createdAt < :olderThanDate')
->setParameter('olderThanDate', $olderThanDate)
->setParameter('initial', 'initial')
;
->setParameter('olderThanDate', $olderThanDate);
return $qb->getQuery()->getResult();
}

View File

@@ -1,8 +1,12 @@
export const buildLinkCreate = (workflowName: string, relatedEntityClass: string, relatedEntityId: number): string => {
const buildLinkCreate = function(workflowName, relatedEntityClass, relatedEntityId) {
let params = new URLSearchParams();
params.set('entityClass', relatedEntityClass);
params.set('entityId', relatedEntityId.toString(10));
params.set('entityId', relatedEntityId);
params.set('workflow', workflowName);
return `/fr/main/workflow/create?`+params.toString();
};
export {
buildLinkCreate,
};

View File

@@ -168,8 +168,3 @@ export interface NewsItemType {
startDate: DateTime;
endDate: DateTime | null;
}
export interface WorkflowAvailable {
name: string;
text: string;
}

View File

@@ -1,52 +1,63 @@
<template>
<template v-if="props.workflowsAvailables.length >= 1">
<template v-if="workflowsAvailables.length >= 1">
<div class="dropdown d-grid gap-2">
<button class="btn btn-primary dropdown-toggle" type="button" id="createWorkflowButton" data-bs-toggle="dropdown" aria-expanded="false">
Créer un workflow
</button>
<ul class="dropdown-menu" aria-labelledby="createWorkflowButton">
<li v-for="w in props.workflowsAvailables" :key="w.name">
<button class="dropdown-item" type="button" @click.prevent="goToGenerateWorkflow($event, w.name)">{{ w.text }}</button>
<li v-for="w in workflowsAvailables" :key="w.name">
<a class="dropdown-item" :href="makeLink(w.name)" @click.prevent="goToGenerateWorkflow($event, w.name)">{{ w.text }}</a>
</li>
</ul>
</div>
</template>
</template>
<script setup lang="ts">
<script>
import {buildLinkCreate} from '../../../lib/entity-workflow/api';
import {WorkflowAvailable} from "../../../types";
import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api.js';
interface PickWorkflowConfig {
relatedEntityClass: string;
relatedEntityId: number;
workflowsAvailables: WorkflowAvailable[];
preventDefaultMoveToGenerate: boolean;
goToGenerateWorkflowPayload: object;
}
export default {
name: "PickWorkflow",
props: {
relatedEntityClass: {
type: String,
required: true,
},
relatedEntityId: {
type: Number,
required: false,
},
workflowsAvailables: {
type: Array,
required: true,
},
preventDefaultMoveToGenerate: {
type: Boolean,
required: false,
default: false,
},
goToGenerateWorkflowPayload: {
required: false,
default: {}
},
},
emits: ['goToGenerateWorkflow'],
methods: {
makeLink(workflowName) {
return buildLinkCreate(workflowName, this.relatedEntityClass, this.relatedEntityId);
},
goToGenerateWorkflow(event, workflowName) {
console.log('goToGenerateWorkflow', event, workflowName);
const props = withDefaults(defineProps<PickWorkflowConfig>(), {preventDefaultMoveToGenerate: false, goToGenerateWorkflowPayload: {}});
if (!this.$props.preventDefaultMoveToGenerate) {
console.log('to go generate');
window.location.assign(this.makeLink(workflowName));
}
const emit = defineEmits<{
(e: 'goToGenerateWorkflow', {event: MouseEvent, workflowName: string, link: string, payload: object}): void;
}>();
const makeLink = (workflowName: string): string => buildLinkCreate(workflowName, props.relatedEntityClass, props.relatedEntityId);
const goToGenerateWorkflow = (event: MouseEvent, workflowName: string): void => {
console.log('goToGenerateWorkflow', event, workflowName);
if (!props.preventDefaultMoveToGenerate) {
console.log('to go generate');
window.location.assign(makeLink(workflowName));
this.$emit('goToGenerateWorkflow', {event, workflowName, link: this.makeLink(workflowName), payload: this.goToGenerateWorkflowPayload});
}
}
emit('goToGenerateWorkflow', {event, workflowName, link: makeLink(workflowName), payload: props.goToGenerateWorkflowPayload});
}
const goToDuplicateRelatedEntity = (event: MouseEvent, workflowName: string): void => {
}
</script>

View File

@@ -1,46 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace ChillMainBundle\Tests\Repository;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* @internal
*
* @coversNothing
*/
class EntityWorkflowRepositoryTest extends KernelTestCase
{
private EntityManagerInterface $em;
protected function setUp(): void
{
self::bootKernel();
$this->em = static::getContainer()->get(EntityManagerInterface::class);
}
/**
* This test only check that the query is ok.
*
* The semantic of the query is not checked.
*/
public function testFindWorkflowsWithoutFinalStepAndOlderThanCheckQueryIsOk(): void
{
$repository = new EntityWorkflowRepository($this->em);
$actual = $repository->findWorkflowsWithoutFinalStepAndOlderThan(new \DateTimeImmutable('10 years ago'));
self::assertIsArray($actual, 'check that the query is successful');
}
}

View File

@@ -13,6 +13,7 @@ namespace Chill\MainBundle\Workflow\Templating;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
use Chill\MainBundle\Workflow\EntityWorkflowManager;
use Chill\MainBundle\Workflow\Helper\MetadataExtractor;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Workflow\Registry;
@@ -22,7 +23,7 @@ use Twig\Extension\RuntimeExtensionInterface;
class WorkflowTwigExtensionRuntime implements RuntimeExtensionInterface
{
public function __construct(private readonly Registry $registry, private readonly EntityWorkflowRepository $repository, private readonly MetadataExtractor $metadataExtractor, private readonly NormalizerInterface $normalizer) {}
public function __construct(private readonly EntityWorkflowManager $entityWorkflowManager, private readonly Registry $registry, private readonly EntityWorkflowRepository $repository, private readonly MetadataExtractor $metadataExtractor, private readonly NormalizerInterface $normalizer) {}
public function getTransitionByString(EntityWorkflow $entityWorkflow, string $key): ?Transition
{

View File

@@ -1,55 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Controller;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter;
use Chill\PersonBundle\Service\AccompanyingPeriodWorkEvaluationDocument\AccompanyingPeriodWorkEvaluationDocumentDuplicator;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\SerializerInterface;
class AccompanyingPeriodWorkEvaluationDocumentDuplicateController
{
public function __construct(
private readonly AccompanyingPeriodWorkEvaluationDocumentDuplicator $duplicator,
private readonly Security $security,
private readonly SerializerInterface $serializer,
private readonly EntityManagerInterface $entityManager,
) {}
#[Route('/api/1.0/person/accompanying-course-work-evaluation-document/{id}/duplicate', methods: ['POST'])]
public function __invoke(AccompanyingPeriodWorkEvaluationDocument $document): Response
{
$work = $document->getAccompanyingPeriodWorkEvaluation()->getAccompanyingPeriodWork();
if (!$this->security->isGranted(AccompanyingPeriodWorkVoter::UPDATE, $work)) {
throw new AccessDeniedHttpException('not allowed to edit this accompanying period work');
}
$duplicatedDocument = $this->duplicator->duplicate($document);
$this->entityManager->persist($duplicatedDocument);
$this->entityManager->persist($duplicatedDocument->getStoredObject());
$this->entityManager->flush();
return new JsonResponse(
$this->serializer->serialize($duplicatedDocument, 'json', [AbstractNormalizer::GROUPS => ['read']]),
json: true
);
}
}

View File

@@ -55,7 +55,7 @@ readonly class ReferrerScopeAggregator implements AggregatorInterface, DataTrans
$qb->expr()->gte('COALESCE(acp.closingDate, CURRENT_TIMESTAMP())', "{$p}_userHistory.startDate"),
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_userHistory.endDate"),
$qb->expr()->lt('COALESCE(acp.closingDate, CURRENT_TIMESTAMP())', "{$p}_userHistory.endDate")
$qb->expr()->lt('COALESCE(acp.openingDate, CURRENT_TIMESTAMP())', "{$p}_userHistory.endDate")
)
),
$qb->expr()->andX(

View File

@@ -1,12 +1,4 @@
import {
Address,
Center,
Civility,
DateTime,
User,
WorkflowAvailable
} from "../../../ChillMainBundle/Resources/public/types";
import {StoredObject} from "../../../ChillDocStoreBundle/Resources/public/types";
import {Address, Center, Civility, DateTime} from "../../../ChillMainBundle/Resources/public/types";
export interface Person {
id: number;
@@ -28,16 +20,3 @@ export interface Person {
current_household_id: number;
current_residential_addresses: Address[];
}
export interface AccompanyingPeriodWorkEvaluationDocument {
id: number;
type: "accompanying_period_work_evaluation_document"
storedObject: StoredObject;
title: string;
createdAt: DateTime | null;
createdBy: User | null;
updatedAt: DateTime | null;
updatedBy: User | null;
workflows_availables: WorkflowAvailable[];
workflows: object[];
}

View File

@@ -342,7 +342,7 @@ import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue';
import ListWorkflowModal from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue';
import PickWorkflow from 'ChillMainAssets/vuejs/_components/EntityWorkflow/PickWorkflow.vue';
import PersonText from 'ChillPersonAssets/vuejs/_components/Entity/PersonText.vue';
import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api';
import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api.js';
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods';
const i18n = {

View File

@@ -54,7 +54,7 @@
import FormEvaluation from './FormEvaluation.vue';
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
import ListWorkflowModal from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue';
import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api';
import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api.js';
const i18n = {
messages: {

View File

@@ -147,9 +147,6 @@
<a class="btn btn-delete" @click="removeDocument(d)">
</a>
</li>
<li v-if="Number.isInteger(d.id)">
<button type="button" @click="duplicateDocument(d)" class="btn btn-duplicate" title="Dupliquer"></button>
</li>
</ul>
</div>
</div>
@@ -192,7 +189,7 @@ import { mapGetters, mapState } from 'vuex';
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
import {buildLink} from 'ChillDocGeneratorAssets/lib/document-generator';
import ListWorkflowModal from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue';
import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api';
import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api.js';
import {buildLinkCreate as buildLinkCreateNotification} from 'ChillMainAssets/lib/entity-notification/api';
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
import DropFileModal from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileModal.vue";
@@ -399,9 +396,6 @@ export default {
this.$store.commit('removeDocument', {key: this.evaluation.key, document: document});
}
},
duplicateDocument(document) {
this.$store.dispatch('duplicateDocument', {evaluation_key: this.evaluation.key, document: document});
},
onStatusDocumentChanged(newStatus) {
console.log('onStatusDocumentChanged', newStatus);
this.$store.commit('statusDocumentChanged', {key: this.evaluation.key, newStatus: newStatus});

View File

@@ -4,7 +4,6 @@ import { findSocialActionsBySocialIssue } from 'ChillPersonAssets/vuejs/_api/Soc
import { create } from 'ChillPersonAssets/vuejs/_api/AccompanyingCourseWork.js';
import { fetchResults, makeFetch } from 'ChillMainAssets/lib/api/apiMethods.ts';
import { fetchTemplates } from 'ChillDocGeneratorAssets/api/pickTemplate.js';
import { duplicate } from '../_api/accompanyingCourseWorkEvaluationDocument';
const debug = process.env.NODE_ENV !== 'production';
const evalFQDN = encodeURIComponent("Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation");
@@ -239,14 +238,6 @@ const store = createStore({
}
evaluation.documents = evaluation.documents.filter(d => d.key !== document.key);
},
addDuplicatedDocument(state, {document, evaluation_key}) {
console.log('add duplicated document', document);
console.log('add duplicated dcuemnt - evaluation key', evaluation_key);
let evaluation = state.evaluationsPicked.find(e => e.key === evaluation_key);
document.key = evaluation.documents.length + 1;
evaluation.documents.splice(0, 0, document);
},
/**
* Replaces a document in the state with a new document.
*
@@ -515,10 +506,6 @@ const store = createStore({
addDocument({commit}, payload) {
commit('addDocument', payload);
},
async duplicateDocument({commit}, {document, evaluation_key}) {
const newDoc = await duplicate(document.id);
commit('addDuplicatedDocument', {document: newDoc, evaluation_key});
},
removeDocument({commit}, payload) {
commit('removeDocument', payload);
},

View File

@@ -1,6 +0,0 @@
import {AccompanyingPeriodWorkEvaluationDocument} from "../../types";
import {makeFetch} from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
export const duplicate = async (id: number): Promise<AccompanyingPeriodWorkEvaluationDocument> => {
return makeFetch<null, AccompanyingPeriodWorkEvaluationDocument>("POST", `/api/1.0/person/accompanying-course-work-evaluation-document/${id}/duplicate`);
}

View File

@@ -1,39 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Service\AccompanyingPeriodWorkEvaluationDocument;
use Chill\DocStoreBundle\Service\StoredObjectDuplicate;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class AccompanyingPeriodWorkEvaluationDocumentDuplicator
{
public function __construct(
private readonly StoredObjectDuplicate $storedObjectDuplicate,
private readonly TranslatorInterface $translator,
private readonly ClockInterface $clock,
) {}
public function duplicate(AccompanyingPeriodWorkEvaluationDocument $document): AccompanyingPeriodWorkEvaluationDocument
{
$newDocument = new AccompanyingPeriodWorkEvaluationDocument();
$newDocument
->setTitle($document->getTitle().' ('.$this->translator->trans('accompanying_course_evaluation_document.duplicated_at', ['at' => $this->clock->now()]).')')
->setStoredObject($this->storedObjectDuplicate->duplicate($document->getStoredObject()))
;
$document->getAccompanyingPeriodWorkEvaluation()->addDocument($newDocument);
return $newDocument;
}
}

View File

@@ -1,57 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Tests\Service\AccompanyingPeriodWorkEvaluationDocument;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Service\StoredObjectDuplicate;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
use Chill\PersonBundle\Service\AccompanyingPeriodWorkEvaluationDocument\AccompanyingPeriodWorkEvaluationDocumentDuplicator;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Clock\MockClock;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @internal
*
* @coversNothing
*/
class AccompanyingPeriodWorkEvaluationDocumentDuplicatorTest extends TestCase
{
public function testDuplicate()
{
$storedObject = new StoredObject();
$evaluation = new AccompanyingPeriodWorkEvaluation();
$document = new AccompanyingPeriodWorkEvaluationDocument();
$document
->setStoredObject($storedObject)
->setTitle('test title')
->setAccompanyingPeriodWorkEvaluation($evaluation);
$storedObjectDuplicator = $this->createMock(StoredObjectDuplicate::class);
$storedObjectDuplicator->expects($this->once())->method('duplicate')->with($storedObject)->willReturn($newStoredObject = new StoredObject());
$translator = $this->createMock(TranslatorInterface::class);
$translator->method('trans')->willReturn('test translated');
$clock = new MockClock();
$duplicator = new AccompanyingPeriodWorkEvaluationDocumentDuplicator($storedObjectDuplicator, $translator, $clock);
$actual = $duplicator->duplicate($document);
self::assertNotSame($document, $actual);
self::assertStringStartsWith('test title', $actual->getTitle());
self::assertSame($newStoredObject, $actual->getStoredObject());
self::assertSame($evaluation, $actual->getAccompanyingPeriodWorkEvaluation());
}
}

View File

@@ -1948,25 +1948,3 @@ paths:
responses:
200:
description: "OK"
/1.0/person/accompanying-course-work-evaluation-document/{id}/duplicate:
post:
tags:
- accompanying-course-work-evaluation-document
summary: Dupliate an an accompanying period work evaluation document
parameters:
- in: path
name: id
required: true
description: The document's id
schema:
type: integer
format: integer
minimum: 1
responses:
200:
description: "OK"
content:
application/json:
schema:
type: object

View File

@@ -163,8 +163,8 @@ exports:
{ total, plural,
=0 {Aucun usager ne correspond aux termes de recherche}
one {Un usager correspond aux termes de recherche}
many {# usagers correspondent aux termes de recherche}
other {# usagers correspondent aux termes de recherche}
many {# usagers correspondent aux terms de recherche}
other {# usagers correspondent aux terms de recherche}
}
'nb person with similar name. Please verify that this is a new person': >-
@@ -173,7 +173,3 @@ exports:
one {Un usager a un nom similaire. Vérifiez qu'il ne s'agit pas de lui.}
other {# usagers ont un nom similaire. Vérifiez qu'il ne s'agit pas de l'un d'eux}
}
accompanying_course_evaluation_document:
duplicated_at: >-
Dupliqué le {at, date, long} à {at, time, short}