mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-12 09:44:58 +00:00
Compare commits
139 Commits
convert_to
...
fix_deathd
Author | SHA1 | Date | |
---|---|---|---|
c6c0b7ce2f | |||
3973cdd3f6 | |||
13a1ab380e | |||
041655d26e | |||
6e412117d3 | |||
cc849e279d | |||
de262bbc28 | |||
2bafdd9e62 | |||
d01eaa8065 | |||
|
4a465b8f78 | ||
|
eaf7ae6f2f | ||
|
b5178c3be3 | ||
fdae6c106a | |||
1fff9ef535 | |||
20c4d07668 | |||
6e1340be27 | |||
118b208f68 | |||
e21f12d96c | |||
4b213dc290 | |||
55991a6082 | |||
|
898f25c0e4 | ||
b3929321b6 | |||
c049a0b78b | |||
10aa897899 | |||
31f5d97912 | |||
c6cda437c4 | |||
|
7e045e964a | ||
133ac67794 | |||
8fb4efc16e | |||
1823a1b031 | |||
683d055088 | |||
839fb4a211 | |||
|
3eb7ffed1a | ||
d0c5d0423f | |||
f2117ea1cd | |||
c7ee2cc600 | |||
57c4f2ee61 | |||
95caa998e3 | |||
97165c8389 | |||
1efdade382 | |||
cf219198ca | |||
5a2e6c0969 | |||
84f772b42b | |||
62b8b3e6b7 | |||
5fdbba9ef5 | |||
5b321dfb81 | |||
10f276e48c | |||
8a4748dc2d | |||
f5ae5443ab | |||
9272687ec4 | |||
6100176c9f | |||
56ab304af1 | |||
b888211ef9 | |||
0617014e91 | |||
ce171ec747 | |||
455b225f49 | |||
fa5001265f | |||
56dd825f39 | |||
4e4add3cc1 | |||
b684bdfa3b | |||
|
b683630136 | ||
e6c60e66fc | |||
b1b9acc686 | |||
f6408af543 | |||
5b4388eae6 | |||
41ecb2351e | |||
6cc0f1f98b | |||
aa68ae0b25 | |||
ebd817f61e | |||
25147704ad | |||
2788149db4 | |||
fabe32371f | |||
b120e47786 | |||
5a61f05faa | |||
7c2a283437 | |||
2d1e8b2acd | |||
afb7d4a160 | |||
96e5e4a7b2 | |||
8e6e92ae89 | |||
07f53e6758 | |||
d2c61a26ea | |||
bbc5e94033 | |||
8e8a8c1e70 | |||
f10d762e59 | |||
9924c3a3b7 | |||
cea7160d5a | |||
5669f62fdc | |||
938720be52 | |||
c4998f4ac1 | |||
|
15c7c6ccde | ||
|
3c3d63e286 | ||
|
74682549c8 | ||
51fd81c661 | |||
063ceb2c53 | |||
6c1a946608 | |||
29134f0f11 | |||
2d319fcc42 | |||
|
2e4356c3c9 | ||
51bbcab878 | |||
5645cb0cdd | |||
02c93389d8 | |||
a86ba6faf5 | |||
e1d4868f51 | |||
9244bb2f8d | |||
43702ded77 | |||
|
611f0a9ca6 | ||
|
d2704ad7e6 | ||
ff751b7f30 | |||
23eff9f6d4 | |||
e053529afb | |||
be626079d0 | |||
af6efdd0ba | |||
2245f83631 | |||
475b40e896 | |||
7719d2b073 | |||
9d0e1a82e7 | |||
9f43c99acc | |||
de4e83b3fb | |||
3404d3669c | |||
0710d6572b | |||
cf442461d6 | |||
|
6e521642a4 | ||
|
0e88d7c549 | ||
|
2993dab3f7 | ||
|
d026be3041 | ||
|
59a0b692ac | ||
|
9fa2e80d81 | ||
|
4e56075fdc | ||
|
2b01179967 | ||
|
847e610362 | ||
|
a7af7e7874 | ||
|
1bf595334e | ||
|
a3333c3302 | ||
|
7fe2172f05 | ||
bbf6b1412f | |||
9f868bff4f | |||
1c18ba20fc | |||
|
90b256daaf | ||
|
226c3eedef |
49
CHANGELOG.md
49
CHANGELOG.md
@@ -11,11 +11,50 @@ and this project adheres to
|
||||
## Unreleased
|
||||
|
||||
<!-- write down unreleased development here -->
|
||||
* [person search] fix bug when using birthdate after and birthdate before
|
||||
* [person search] increase pertinence when lastname begins with search pattern
|
||||
|
||||
## Test releases
|
||||
|
||||
### test release 2021-12-11
|
||||
|
||||
* [main] add order field to civility
|
||||
* [main] change address format in case the country is France, in Address render box and address normalizer
|
||||
* [person] add validator for accompanying period with a test on social issues (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/76)
|
||||
* [activity] fix visibility for location
|
||||
* [origin] fix origin: use correctly the translatable strings
|
||||
* /!\ everyone must update the origin table. As there is only one row, execute `update chill_person_accompanying_period_origin set label = jsonb_build_object('fr', 'appel téléphonique');`
|
||||
* [person] redirect bug fixed.
|
||||
* [action] add an unrelated issue within action creation.
|
||||
* [origin] fix origin: use correctly the translatable strings
|
||||
* /!\ everyone must update the origin table. As there is only one row, execute `update chill_person_accompanying_period_origin set label = jsonb_build_object('fr', 'appel téléphonique');`
|
||||
* [main] change order of civilities in civility fixtures (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191)
|
||||
* [person] set min attr in the minimum of children field (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191)
|
||||
* [person] add marital status date in person view (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191)
|
||||
* [person] show number of children + allow set number of children to null (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191)
|
||||
* [person] show acceptSMS option (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191)
|
||||
* [person] add death information in person render box in twig and vue render boxes (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191)
|
||||
* [asideactivity] creation of aside activity category fixed (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/262)
|
||||
* [vendee/person] fix typo "situation professionelle" => "situation professionnelle"
|
||||
|
||||
### test release 2021-12-06
|
||||
|
||||
* [main] address: use search API end points for getting postal code and reference address (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/316)
|
||||
* [main] address: in edit mode, select the encoded values in multiselect for address reference and city (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/316)
|
||||
* [person search] fix bug when using birthdate after and birthdate before
|
||||
* [person search] increase pertinence when lastname begins with search pattern
|
||||
* [activity/actions] Améliore la cohérence du design entre
|
||||
* la page résumé d'un parcours (liste d'actions récentes et liste d'activités récentes)
|
||||
* la page liste des actions
|
||||
* la page liste des activités (contexte personne / contexte parcours)
|
||||
* [household] field to edit wheter person is titulaire of household or not removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/322)
|
||||
* [activity] create work if a work with same social action is not associated to the activity
|
||||
* [visgraph] improve and fix bugs on vis-network relationship graph
|
||||
* [bugfix] posting of birth- and deathdate through api fixed.
|
||||
* [suggestions] improve suggestions lists
|
||||
* [badge-entity] design coherency between badge-person and 3 kinds of badge-thirdparty
|
||||
* [AddPersons] suggestions row are clickable, not only checkbox
|
||||
* [activity] improve show/new/edit templates, fix SEE and SEE_DETAILS acl
|
||||
* [activity][calendar] concerned groups items are clickable with on-the-fly modal, create specific badge for TMS column
|
||||
|
||||
### Test release 2021-11-19 - bis
|
||||
|
||||
* [household] do not allow to create two addresses on the same date
|
||||
@@ -23,6 +62,7 @@ and this project adheres to
|
||||
* [activity] layout for issues / actions
|
||||
* [activity][bugfix] in edit mode, the form will now load the social action list
|
||||
|
||||
|
||||
### Test release 2021-11-29
|
||||
|
||||
* [person] suggest entities (person | thirdparty) when creating/editing the accompanying course (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/119)
|
||||
@@ -50,6 +90,9 @@ and this project adheres to
|
||||
* [activity] for a new activity: suggest and create on-the-fly locations based on the accompanying course location + location of the suggested parties
|
||||
* [calendar] for a new rdv: suggest and create on-the-fly locations based on the accompanying course location + location of the suggested parties
|
||||
|
||||
|
||||
## Test releases
|
||||
|
||||
### Test release 2021-11-22
|
||||
|
||||
* [activity] delete admin_user_show in twig template because this route is not defined and should be defined
|
||||
@@ -164,7 +207,7 @@ and this project adheres to
|
||||
* fast creation buttons
|
||||
* add ordering for types
|
||||
|
||||
* [AccompanyingCourse Resume page] badge-title for AccompanyingCourseWork and for Activities;
|
||||
* [AccompanyingCourse Resume page] dashboard for AccompanyingCourseWork and for Activities;
|
||||
* Improve badges behaviour with small screens;
|
||||
|
||||
* [ThirdParty]:
|
||||
|
@@ -409,3 +409,4 @@ Créer un titre enlevable (avec une croix rouge cliquable, l'ancre a est vide)
|
||||
<a></a>
|
||||
</div>
|
||||
```
|
||||
Les classes `cols` ou `inline` peuvent être ajoutées à côté de `list-suggest` pour modifier la disposition de la liste.
|
||||
|
@@ -24,6 +24,7 @@
|
||||
"phpoffice/phpspreadsheet": "^1.16",
|
||||
"ramsey/uuid-doctrine": "^1.7",
|
||||
"sensio/framework-extra-bundle": "^5.5",
|
||||
"spomky-labs/base64url": "^2.0",
|
||||
"symfony/asset": "4.*",
|
||||
"symfony/browser-kit": "^5.2",
|
||||
"symfony/css-selector": "^5.2",
|
||||
|
@@ -10,16 +10,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php
|
||||
|
||||
-
|
||||
message: "#^Instantiated class PhpOffice\\\\PhpWord\\\\TemplateProcessor not found\\.$#"
|
||||
count: 1
|
||||
path: src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorController.php
|
||||
|
||||
-
|
||||
message: "#^Instantiated class PhpOffice\\\\PhpWord\\\\TemplateProcessor not found\\.$#"
|
||||
count: 1
|
||||
path: src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php
|
||||
|
||||
-
|
||||
message: "#^Variable \\$participation might not be defined\\.$#"
|
||||
count: 3
|
||||
|
@@ -525,24 +525,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Bundle/ChillEventBundle/Search/EventSearch.php
|
||||
|
||||
-
|
||||
message:
|
||||
"""
|
||||
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
|
||||
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
|
||||
"""
|
||||
count: 2
|
||||
path: src/Bundle/ChillEventBundle/Search/EventSearch.php
|
||||
|
||||
-
|
||||
message:
|
||||
"""
|
||||
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
|
||||
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
|
||||
"""
|
||||
count: 1
|
||||
path: src/Bundle/ChillEventBundle/Timeline/TimelineEventProvider.php
|
||||
|
||||
-
|
||||
message:
|
||||
"""
|
||||
@@ -1311,16 +1293,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php
|
||||
|
||||
-
|
||||
message:
|
||||
"""
|
||||
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
|
||||
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
|
||||
"""
|
||||
count: 1
|
||||
path: src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php
|
||||
|
||||
|
||||
-
|
||||
message:
|
||||
"""
|
||||
|
@@ -41,6 +41,7 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
|
||||
$loader->load('services/form.yaml');
|
||||
$loader->load('services/templating.yaml');
|
||||
$loader->load('services/accompanyingPeriodConsistency.yaml');
|
||||
$loader->load('services/doctrine.entitylistener.yaml');
|
||||
}
|
||||
|
||||
public function prepend(ContainerBuilder $container)
|
||||
|
@@ -47,7 +47,8 @@ use Symfony\Component\Serializer\Annotation\SerializedName;
|
||||
* })
|
||||
* @ActivityValidator\ActivityValidity
|
||||
*
|
||||
* @UserCircleConsistency(
|
||||
* TODO see if necessary
|
||||
* UserCircleConsistency(
|
||||
* "CHILL_ACTIVITY_SEE_DETAILS",
|
||||
* getUserFunction="getUser",
|
||||
* path="scope")
|
||||
|
@@ -163,16 +163,6 @@ class ActivityType
|
||||
*/
|
||||
private int $personVisible = self::FIELD_REQUIRED;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", nullable=false, options={"default": ""})
|
||||
*/
|
||||
private string $placeLabel = '';
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
|
||||
*/
|
||||
private int $placeVisible = self::FIELD_OPTIONAL;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", nullable=false, options={"default": ""})
|
||||
*/
|
||||
@@ -406,16 +396,6 @@ class ActivityType
|
||||
return $this->personVisible;
|
||||
}
|
||||
|
||||
public function getPlaceLabel(): string
|
||||
{
|
||||
return $this->placeLabel;
|
||||
}
|
||||
|
||||
public function getPlaceVisible(): int
|
||||
{
|
||||
return $this->placeVisible;
|
||||
}
|
||||
|
||||
public function getReasonsLabel(): string
|
||||
{
|
||||
return $this->reasonsLabel;
|
||||
@@ -688,20 +668,6 @@ class ActivityType
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPlaceLabel(string $placeLabel): self
|
||||
{
|
||||
$this->placeLabel = $placeLabel;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPlaceVisible(int $placeVisible): self
|
||||
{
|
||||
$this->placeVisible = $placeVisible;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setReasonsLabel(string $reasonsLabel): self
|
||||
{
|
||||
$this->reasonsLabel = $reasonsLabel;
|
||||
|
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\ActivityBundle\EntityListener;
|
||||
|
||||
use Chill\ActivityBundle\Entity\Activity;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
|
||||
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
use function in_array;
|
||||
|
||||
class ActivityEntityListener
|
||||
{
|
||||
private EntityManagerInterface $em;
|
||||
|
||||
private AccompanyingPeriodWorkRepository $workRepository;
|
||||
|
||||
public function __construct(EntityManagerInterface $em, AccompanyingPeriodWorkRepository $workRepository)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->workRepository = $workRepository;
|
||||
}
|
||||
|
||||
public function persistActionToCourse(Activity $activity)
|
||||
{
|
||||
if ($activity->getAccompanyingPeriod() instanceof AccompanyingPeriod) {
|
||||
$period = $activity->getAccompanyingPeriod();
|
||||
|
||||
$accompanyingCourseWorks = $this->workRepository->findByAccompanyingPeriod($period);
|
||||
$periodActions = [];
|
||||
$now = new DateTimeImmutable();
|
||||
|
||||
foreach ($accompanyingCourseWorks as $key => $work) {
|
||||
// take only the actions which are still opened
|
||||
if ($work->getEndDate() === null || $work->getEndDate() > ($activity->getDate() ?? $now)) {
|
||||
$periodActions[$key] = spl_object_hash($work->getSocialAction());
|
||||
}
|
||||
}
|
||||
|
||||
$associatedPersons = $activity->getPersonsAssociated();
|
||||
$associatedThirdparties = $activity->getThirdParties();
|
||||
|
||||
foreach ($activity->getSocialActions() as $action) {
|
||||
if (in_array(spl_object_hash($action), $periodActions, true)) {
|
||||
continue;
|
||||
}
|
||||
$newAction = new AccompanyingPeriodWork();
|
||||
$newAction->setSocialAction($action);
|
||||
$period->addWork($newAction);
|
||||
|
||||
$date = DateTimeImmutable::createFromMutable($activity->getDate());
|
||||
$newAction->setStartDate($date);
|
||||
|
||||
foreach ($associatedPersons as $person) {
|
||||
$newAction->addPerson($person);
|
||||
}
|
||||
|
||||
foreach ($associatedThirdparties as $thirdparty) {
|
||||
$newAction->setHandlingThierparty($thirdparty);
|
||||
}
|
||||
$this->em->persist($newAction);
|
||||
$this->em->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -260,6 +260,10 @@ class ActivityType extends AbstractType
|
||||
return implode(',', $personIds);
|
||||
},
|
||||
function (?string $personsAsString): array {
|
||||
if (null === $personsAsString) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(
|
||||
fn (string $id): ?Person => $this->om->getRepository(Person::class)->findOneBy(['id' => (int) $id]),
|
||||
explode(',', $personsAsString)
|
||||
@@ -282,6 +286,10 @@ class ActivityType extends AbstractType
|
||||
return implode(',', $thirdpartyIds);
|
||||
},
|
||||
function (?string $thirdpartyAsString): array {
|
||||
if (null === $thirdpartyAsString) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(
|
||||
fn (string $id): ?ThirdParty => $this->om->getRepository(ThirdParty::class)->findOneBy(['id' => (int) $id]),
|
||||
explode(',', $thirdpartyAsString)
|
||||
@@ -315,6 +323,10 @@ class ActivityType extends AbstractType
|
||||
return implode(',', $userIds);
|
||||
},
|
||||
function (?string $usersAsString): array {
|
||||
if (null === $usersAsString) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(
|
||||
fn (string $id): ?User => $this->om->getRepository(User::class)->findOneBy(['id' => (int) $id]),
|
||||
explode(',', $usersAsString)
|
||||
@@ -332,7 +344,7 @@ class ActivityType extends AbstractType
|
||||
return '';
|
||||
}
|
||||
|
||||
return $location->getId();
|
||||
return (string) $location->getId();
|
||||
},
|
||||
function (?string $id): ?Location {
|
||||
return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]);
|
||||
@@ -379,7 +391,7 @@ class ActivityType extends AbstractType
|
||||
$timezoneUTC = new DateTimeZone('GMT');
|
||||
/** @var DateTime $data */
|
||||
$data = $formEvent->getData() === null ?
|
||||
DateTime::createFromFormat('U', 300) :
|
||||
DateTime::createFromFormat('U', '300') :
|
||||
$formEvent->getData();
|
||||
$seconds = $data->getTimezone()->getOffset($data);
|
||||
$data->setTimeZone($timezoneUTC);
|
||||
|
@@ -55,7 +55,7 @@ class ActivityTypeType extends AbstractType
|
||||
]);
|
||||
|
||||
$fields = [
|
||||
'persons', 'user', 'date', 'place', 'persons',
|
||||
'persons', 'user', 'date', 'location', 'persons',
|
||||
'thirdParties', 'durationTime', 'travelTime', 'attendee',
|
||||
'reasons', 'comment', 'sentReceived', 'documents',
|
||||
'emergency', 'socialIssues', 'socialActions', 'users',
|
||||
|
@@ -31,6 +31,8 @@ class ActivityRepository extends ServiceEntityRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use @see{ActivityACLAwareRepositoryInterface::findByAccompanyingPeriod}
|
||||
*
|
||||
* @return Activity[]
|
||||
*/
|
||||
public function findByAccompanyingPeriod(AccompanyingPeriod $period, array $scopes, ?bool $allowNullScope = false, ?int $limit = 100, ?int $offset = 0, array $orderBy = ['date' => 'desc']): array
|
||||
|
@@ -24,14 +24,16 @@ div.new-activity-select-type {
|
||||
}
|
||||
|
||||
//// ACTIVITY LIST PAGE
|
||||
// precise badge-title specific details
|
||||
// precise dashboard specific details
|
||||
|
||||
p.date-label {
|
||||
display: inline-block;
|
||||
margin: 0 0.5em 0 0;
|
||||
font-weight: 700;
|
||||
font-size: 18pt;
|
||||
}
|
||||
div.dashboard,
|
||||
h2.badge-title {
|
||||
div.duration {
|
||||
font-size: smaller;
|
||||
padding-left: 1em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
ul.list-content {
|
||||
font-size: 70%;
|
||||
list-style-type: none;
|
||||
@@ -39,16 +41,13 @@ h2.badge-title {
|
||||
margin: 0;
|
||||
li {
|
||||
margin-bottom: 0.2em;
|
||||
// exception: change bg color for action badges above badge-title
|
||||
// exception: change bg color for action badges above dashboard
|
||||
.bg-light {
|
||||
background-color: $chill-light-gray !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div.main {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
//// ACTIVITY SHOW AND FORM PAGES
|
||||
// Exceptions for flex-bloc in concerned-groups
|
||||
|
@@ -11,7 +11,7 @@
|
||||
</persons-bloc>
|
||||
</div>
|
||||
<div v-if="getContext === 'accompanyingCourse' && suggestedEntities.length > 0">
|
||||
<ul class="list-suggest add-items">
|
||||
<ul class="list-suggest add-items inline">
|
||||
<li v-for="p in suggestedEntities" @click="addSuggestedEntity(p)">
|
||||
<span>{{ p.text }}</span>
|
||||
</li>
|
||||
|
@@ -6,17 +6,17 @@ const debug = process.env.NODE_ENV !== 'production';
|
||||
//console.log('window.activity', window.activity);
|
||||
|
||||
const addIdToValue = (string, id) => {
|
||||
let array = string ? string.split(',') : [];
|
||||
array.push(id.toString());
|
||||
let str = array.join();
|
||||
return str;
|
||||
let array = string ? string.split(',') : [];
|
||||
array.push(id.toString());
|
||||
let str = array.join();
|
||||
return str;
|
||||
};
|
||||
|
||||
const removeIdFromValue = (string, id) => {
|
||||
let array = string.split(',');
|
||||
array = array.filter(el => el !== id.toString());
|
||||
let str = array.join();
|
||||
return str;
|
||||
let array = string.split(',');
|
||||
array = array.filter(el => el !== id.toString());
|
||||
let str = array.join();
|
||||
return str;
|
||||
};
|
||||
|
||||
const store = createStore({
|
||||
@@ -50,9 +50,9 @@ const store = createStore({
|
||||
return state.activity.activityType.personsVisible === 0
|
||||
? []
|
||||
: state.activity.accompanyingPeriod.participations
|
||||
.filter((p) => p.endDate === null)
|
||||
.map((p) => p.person)
|
||||
.filter((p) => !existingPersonIds.includes(p.id));
|
||||
.filter((p) => p.endDate === null)
|
||||
.map((p) => p.person)
|
||||
.filter((p) => !existingPersonIds.includes(p.id));
|
||||
},
|
||||
suggestedRequestor(state) {
|
||||
if (state.activity.accompanyingPeriod.requestor === null) {
|
||||
@@ -78,8 +78,8 @@ const store = createStore({
|
||||
return state.activity.activityType.usersVisible === 0
|
||||
? []
|
||||
: [state.activity.accompanyingPeriod.user].filter(
|
||||
(u) => u !== null && !existingUserIds.includes(u.id)
|
||||
);
|
||||
(u) => u !== null && !existingUserIds.includes(u.id)
|
||||
);
|
||||
},
|
||||
suggestedResources(state) {
|
||||
const resources = state.activity.accompanyingPeriod.resources;
|
||||
|
@@ -1,110 +0,0 @@
|
||||
<h2 class="badge-title">
|
||||
<span class="title_label">
|
||||
|
||||
{% if activity.date %}
|
||||
<h3>{{ activity.date|format_date('short') }}</h3>
|
||||
{% endif %}
|
||||
|
||||
<div class="duration">
|
||||
{% if activity.durationTime and t.durationTimeVisible %}
|
||||
<p>
|
||||
<abbr class="fa fa-fw fa-hourglass-end" title="{{ 'Duration Time'|trans }}"></abbr>
|
||||
{{ activity.durationTime|date('H:i') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if activity.travelTime and t.travelTimeVisible %}
|
||||
<p>
|
||||
<abbr class="fa fa-fw fa-car" title="{{ 'Travel time'|trans }}"></abbr>
|
||||
{{ activity.travelTime|date('H:i') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</span>
|
||||
<span class="title_action">
|
||||
|
||||
{{ activity.type.name | localize_translatable_string }}
|
||||
|
||||
{% if activity.emergency %}
|
||||
<span class="badge bg-danger rounded-pill fs-6">{{ 'Emergency'|trans|upper }}</span>
|
||||
{% endif %}
|
||||
|
||||
<ul class="small_in_title mt-3">
|
||||
|
||||
{% if activity.sentReceived is not empty and t.sentReceivedVisible %}
|
||||
<li>
|
||||
<span class="item-key">{{ 'Sent received'|trans ~ ' : ' }}</span>
|
||||
<b>{{ activity.sentReceived|capitalize|trans }}</b>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if activity.location and t.locationVisible %}
|
||||
<li>
|
||||
<span class="item-key">{{ 'location'|trans ~ ': ' }}</span>
|
||||
<b>
|
||||
<span>{{ activity.location.locationType.title|localize_translatable_string }}</span>
|
||||
{{ activity.location.name }}
|
||||
</b>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if activity.user and t.userVisible %}
|
||||
<li>
|
||||
<span class="item-key">{{ 'Referrer'|trans ~ ': ' }}</span>
|
||||
<b>{{ activity.user.usernameCanonical }}</b>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li class="associated-persons">
|
||||
<span class="item-key">{{ 'Participants'|trans ~ ' : ' }}</span>
|
||||
{% for p in activity.personsAssociated %}
|
||||
<span class="badge-person">{{ p|chill_entity_render_box }}</span>
|
||||
{% endfor %}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="list-content my-3">
|
||||
{%- if t.reasonsVisible -%}
|
||||
{%- if activity.reasons is not empty -%}
|
||||
<li class="reasons">
|
||||
{% for r in activity.reasons %}
|
||||
{{ r|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
</li>
|
||||
{%- endif -%}
|
||||
{% endif %}
|
||||
{%- if t.socialIssuesVisible %}
|
||||
{%- if activity.socialIssues is not empty -%}
|
||||
<li class="social-issues">
|
||||
{% for r in activity.socialIssues %}
|
||||
{{ r|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
</li>
|
||||
{%- endif -%}
|
||||
{% endif %}
|
||||
{%- if t.socialActionsVisible -%}
|
||||
{%- if activity.socialActions is not empty -%}
|
||||
<li class="social-actions">
|
||||
{% for r in activity.socialActions %}
|
||||
{{ r|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
</li>
|
||||
{%- endif -%}
|
||||
{% endif %}
|
||||
</ul>
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
{% if context == 'person' and activity.accompanyingPeriod is not empty %}
|
||||
<div class="mt-3">
|
||||
<a class="btn btn-sm btn-outline-primary"
|
||||
title="{{ 'Period number %number%'|trans({'%number%': activity.accompanyingPeriod.id}) }}"
|
||||
href="{{ chill_path_add_return_path(
|
||||
"chill_person_accompanying_course_index",
|
||||
{ 'accompanying_period_id': activity.accompanyingPeriod.id }
|
||||
) }}"><i class="fa fa-random"></i>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@@ -3,6 +3,14 @@
|
||||
{{ path(pathname, parms) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro insert_onthefly(type, entity) %}
|
||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||
action: 'show', displayBadge: true,
|
||||
targetEntity: { name: type, id: entity.id },
|
||||
buttonText: entity|chill_entity_render_string
|
||||
} %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro computeWidth(nbBlocks) %}
|
||||
{{ 'flex-basis: ' ~ (100 / nbBlocks)|round(1) ~ '%;' }}
|
||||
{% endmacro %}
|
||||
@@ -13,6 +21,7 @@
|
||||
{% set blocks = blocks|merge([{
|
||||
'title': 'Others persons'|trans,
|
||||
'items': entity.persons,
|
||||
'type': 'person',
|
||||
'path' : 'chill_person_view',
|
||||
'key' : 'person_id'
|
||||
}]) %}
|
||||
@@ -20,11 +29,13 @@
|
||||
{% set blocks = blocks|merge([{
|
||||
'title': 'Persons in accompanying course'|trans,
|
||||
'items': entity.personsAssociated,
|
||||
'type': 'person',
|
||||
'path' : 'chill_person_view',
|
||||
'key' : 'person_id'
|
||||
},{
|
||||
'title': 'Third persons'|trans,
|
||||
'items': entity.personsNotAssociated,
|
||||
'type': 'person',
|
||||
'path' : 'chill_person_view',
|
||||
'key' : 'person_id',
|
||||
}]) %}
|
||||
@@ -34,6 +45,7 @@
|
||||
{% set blocks = blocks|merge([{
|
||||
'title': 'Third parties'|trans,
|
||||
'items': entity.thirdParties,
|
||||
'type': 'thirdparty',
|
||||
'path' : 'chill_crud_3party_3party_view',
|
||||
'key' : 'id',
|
||||
}]) %}
|
||||
@@ -42,6 +54,7 @@
|
||||
{% set blocks = blocks|merge([{
|
||||
'title': 'Users concerned'|trans,
|
||||
'items': entity.users,
|
||||
'type': 'user',
|
||||
'key' : 'id',
|
||||
}]) %}
|
||||
{% endif %}
|
||||
@@ -59,22 +72,12 @@
|
||||
<ul class="list-content">
|
||||
{% for item in bloc.items %}
|
||||
<li>
|
||||
{% if bloc.path is defined %}
|
||||
<a href="{{ _self.href(bloc.path, bloc.key, item.id) }}">
|
||||
<span class="{% if (badge_person is defined and badge_person == true) %}badge-person{% else %}badge bg-primary{% endif %}">
|
||||
{{ item|chill_entity_render_box({
|
||||
'render': 'raw',
|
||||
'addAltNames': false
|
||||
}) }}
|
||||
{% if bloc.type == 'user' %}
|
||||
<span class="badge-user">
|
||||
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false }) }}
|
||||
</span>
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="{% if (badge_person is defined and badge_person == true) %}badge-person{% else %}badge bg-primary{% endif %}">
|
||||
{{ item|chill_entity_render_box({
|
||||
'render': 'raw',
|
||||
'addAltNames': false
|
||||
}) }}
|
||||
</span>
|
||||
{{ _self.insert_onthefly(bloc.type, item) }}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
@@ -96,20 +99,12 @@
|
||||
<ul class="list-content">
|
||||
{% for item in bloc.items %}
|
||||
<li>
|
||||
{% if bloc.path is defined %}
|
||||
<a href="{{ _self.href(bloc.path, bloc.key, item.id) }}">
|
||||
<span class="{% if (badge_person is defined and badge_person == true) %}badge-person{% else %}badge bg-primary{% endif %}">
|
||||
{{ item|chill_entity_render_box({
|
||||
'render': 'raw',
|
||||
'addAltNames': false
|
||||
}) }}
|
||||
{% if bloc.type == 'user' %}
|
||||
<span class="badge-user">
|
||||
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false }) }}
|
||||
</span>
|
||||
</a>
|
||||
{% else %}
|
||||
{{ item|chill_entity_render_box({
|
||||
'render': 'raw',
|
||||
'addAltNames': false
|
||||
}) }}
|
||||
{{ _self.insert_onthefly(bloc.type, item) }}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
@@ -126,24 +121,18 @@
|
||||
<div class="wl-row">
|
||||
{% if bloc.items|length > 0 %}
|
||||
<div class="wl-col title">
|
||||
<h4>{{ bloc.title }}</h4>
|
||||
<h3>{{ bloc.title }}</h3>
|
||||
</div>
|
||||
<div class="wl-col list">
|
||||
{% for item in bloc.items %}
|
||||
|
||||
<span class="wl-item {% if (badge_person is defined and badge_person == true) %}badge-person{% else %}badge bg-primary{% endif %}">
|
||||
{% if bloc.path is defined %}
|
||||
<a href="{{ _self.href(bloc.path, bloc.key, item.id) }}">
|
||||
{{ item|chill_entity_render_box({
|
||||
'render': 'raw',
|
||||
'addAltNames': false
|
||||
}) }}
|
||||
</a>
|
||||
<span class="wl-item">
|
||||
{% if bloc.type == 'user' %}
|
||||
<span class="badge-user">
|
||||
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false }) }}
|
||||
</span>
|
||||
{% else %}
|
||||
{{ item|chill_entity_render_box({
|
||||
'render': 'raw',
|
||||
'addAltNames': false
|
||||
}) }}
|
||||
{{ _self.insert_onthefly(bloc.type, item) }}
|
||||
{% endif %}
|
||||
</span>
|
||||
|
||||
|
@@ -1,5 +1,12 @@
|
||||
<h1>{{ "Update activity"|trans }}</h1>
|
||||
<h2 class="chill-green mb-4">{{ entity.type.name|localize_translatable_string }}</h2>
|
||||
<h1>
|
||||
{{ "Update activity"|trans }}
|
||||
</h1>
|
||||
<h2 class="badge-title mb-5">
|
||||
<span class="title_label"></span>
|
||||
<span class="title_action">
|
||||
{{ entity.type.name | localize_translatable_string }}
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
{{ form_start(edit_form) }}
|
||||
{{ form_errors(edit_form) }}
|
||||
@@ -37,7 +44,7 @@
|
||||
{% endif %}
|
||||
|
||||
{%- if edit_form.persons is defined or edit_form.thirdParties is defined or edit_form.users is defined -%}
|
||||
<h2 class="chill-red">{{ 'Concerned groups'|trans }}</h2>
|
||||
<h2 class="chill-blue">{{ 'Concerned groups'|trans }}</h2>
|
||||
|
||||
{%- if edit_form.persons is defined -%}
|
||||
{{ form_widget(edit_form.persons) }}
|
||||
@@ -53,7 +60,7 @@
|
||||
{% endif %}
|
||||
|
||||
|
||||
<h2 class="chill-red">{{ 'Activity data'|trans }}</h2>
|
||||
<h2 class="chill-blue">{{ 'Activity data'|trans }}</h2>
|
||||
|
||||
{%- if edit_form.date is defined -%}
|
||||
{{ form_row(edit_form.date) }}
|
||||
@@ -73,7 +80,6 @@
|
||||
{% endif %}
|
||||
|
||||
{%- if edit_form.comment is defined -%}
|
||||
{# TODO .. public and private #}
|
||||
{{ form_row(edit_form.comment) }}
|
||||
{% endif %}
|
||||
|
||||
@@ -97,16 +103,16 @@
|
||||
{% set accompanying_course_id = accompanyingCourse.id %}
|
||||
{% endif %}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('chill_activity_activity_show', { 'id': entity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id } ) }}" class="btn btn-cancel">
|
||||
{{ 'Cancel'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-update" type="submit">{{ 'Save'|trans }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
{{ form_end(edit_form) }}
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('chill_activity_activity_show', { 'id': entity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id } ) }}" class="btn btn-cancel">
|
||||
{{ 'Cancel'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-update" type="submit">{{ 'Save'|trans }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{ form_end(edit_form) }}
|
||||
{# {{ form(delete_form) }} #}
|
||||
|
@@ -10,49 +10,195 @@
|
||||
{% for activity in activities %}
|
||||
{% set t = activity.type %}
|
||||
<div class="item-bloc">
|
||||
|
||||
<div class="item-row">
|
||||
{% include '@ChillActivity/Activity/activity-badge-title.html.twig' %}
|
||||
<div class="wrap-list">
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title">
|
||||
{% if activity.date %}
|
||||
<p class="date-label">
|
||||
{{ activity.date|format_date('short') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="wl-col list">
|
||||
<h2 class="badge-title">
|
||||
<span class="title_label"></span>
|
||||
<span class="title_action">
|
||||
{{ activity.type.name | localize_translatable_string }}
|
||||
|
||||
{% if activity.emergency %}
|
||||
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if activity.comment.comment is not empty
|
||||
or activity.persons|length > 0
|
||||
or activity.thirdParties|length > 0
|
||||
or activity.users|length > 0
|
||||
%}
|
||||
<div class="main">
|
||||
{% if activity.comment.comment is not empty %}
|
||||
{{ activity.comment|chill_entity_render_box({
|
||||
'disable_markdown': false,
|
||||
'limit_lines': 3,
|
||||
'metadata': false,
|
||||
}) }}
|
||||
{% endif %}
|
||||
<div class="item-row column separator">
|
||||
<div class="wrap-list">
|
||||
{% if activity.location and t.locationVisible %}
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title"><h3>{{ 'location'|trans }}</h3></div>
|
||||
<div class="wl-col list">
|
||||
<p class="wl-item">
|
||||
<span>{{ activity.location.locationType.title|localize_translatable_string }}</span>
|
||||
{{ activity.location.name }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if activity.sentReceived is not empty and t.sentReceivedVisible %}
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title"><h3>{{ 'Sent received'|trans }}</h3></div>
|
||||
<div class="wl-col list">
|
||||
<p class="wl-item">
|
||||
{{ activity.sentReceived|capitalize|trans }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if activity.user and t.userVisible %}
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title"><h3>{{ 'Referrer'|trans }}</h3></div>
|
||||
<div class="wl-col list">
|
||||
<p class="wl-item">
|
||||
{{ activity.user.usernameCanonical|chill_entity_render_string|capitalize }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
|
||||
'context': context,
|
||||
'with_display': 'row',
|
||||
'with_display': 'wrap-list',
|
||||
'entity': activity,
|
||||
'badge_person': true
|
||||
} %}
|
||||
|
||||
<div class="wrap-list">
|
||||
{%- if activity.reasons is not empty and t.reasonsVisible -%}
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title">
|
||||
<h3>{{ 'Reasons'|trans }}</h3>
|
||||
</div>
|
||||
<div class="wl-col list">
|
||||
{% for r in activity.reasons %}
|
||||
<p class="wl-item reasons">
|
||||
{{ r|chill_entity_render_box }}
|
||||
</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{%- if activity.socialIssues is not empty and t.socialIssuesVisible -%}
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title">
|
||||
<h3>{{ 'Social issues'|trans }}</h3>
|
||||
</div>
|
||||
<div class="wl-col list">
|
||||
{% for r in activity.socialIssues %}
|
||||
<p class="wl-item social-issues">
|
||||
{{ r|chill_entity_render_box }}
|
||||
</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{%- if activity.socialActions is not empty and t.socialActionsVisible -%}
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title">
|
||||
<h3>{{ 'Social actions'|trans }}</h3>
|
||||
</div>
|
||||
<div class="wl-col list">
|
||||
{% for r in activity.socialActions %}
|
||||
<p class="wl-item social-actions">
|
||||
{{ r|chill_entity_render_box }}
|
||||
</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Only if ACL SEE_DETAILS AND/OR only on template SHOW ??
|
||||
durationTime
|
||||
travelTime
|
||||
comment
|
||||
{% if activity.comment.comment is not empty and is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %}
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title">
|
||||
<h3>{{ 'Comment'|trans }}</h3>
|
||||
</div>
|
||||
<div class="wl-col list">
|
||||
{{ activity.comment|chill_entity_render_box({
|
||||
'disable_markdown': false,
|
||||
'limit_lines': 3,
|
||||
'metadata': false
|
||||
}) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
documents
|
||||
attendee
|
||||
#}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="item-row separator">
|
||||
<div class="updatedBy"></div>
|
||||
<ul class="record_actions">
|
||||
{% if context == 'person' and activity.accompanyingPeriod is not empty %}
|
||||
{#
|
||||
Disable person_id in following links, for redirect to accompanyingCourse context
|
||||
#}
|
||||
{% set person_id = null %}
|
||||
{% set accompanying_course_id = activity.accompanyingPeriod.id %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_activity_activity_list',{
|
||||
'accompanying_period_id': accompanying_course_id
|
||||
}) }}"
|
||||
class="btn btn-primary"
|
||||
title="{{ 'See activity in accompanying course context'|trans }}">
|
||||
<i class="fa fa-random fa-fw"></i>
|
||||
{{ 'Period number %number%'|trans({'%number%': accompanying_course_id}) }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_show', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
||||
class="btn btn-sm btn-show "></a>
|
||||
<a href="{{ path('chill_activity_activity_show', {'id': activity.id,
|
||||
'person_id': person_id,
|
||||
'accompanying_period_id': accompanying_course_id
|
||||
}) }}"
|
||||
class="btn btn-show"
|
||||
title="{{ 'Show'|trans }}"></a>
|
||||
</li>
|
||||
{% if no_action is not defined or no_action == false %}
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_edit', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
||||
class="btn btn-sm btn-update "></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_delete', { 'id': activity.id, 'person_id' : person_id, 'accompanying_period_id': accompanying_course_id } ) }}"
|
||||
class="btn btn-sm btn-delete "></a>
|
||||
</li>
|
||||
{% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %}
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_edit', {'id': activity.id,
|
||||
'person_id': person_id,
|
||||
'accompanying_period_id': accompanying_course_id
|
||||
}) }}"
|
||||
class="btn btn-update"
|
||||
title="{{ 'Edit'|trans }}"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_ACTIVITY_DELETE', activity) %}
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_delete', {'id': activity.id,
|
||||
'person_id': person_id,
|
||||
'accompanying_period_id': accompanying_course_id
|
||||
}) }}"
|
||||
class="btn btn-delete"
|
||||
title="{{ 'Delete'|trans }}"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
@@ -23,8 +23,8 @@
|
||||
{% if is_granted('CHILL_ACTIVITY_CREATE', accompanyingCourse) %}
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_new', {'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-create">
|
||||
{{ 'Create'|trans }}
|
||||
<a href="{{ path('chill_activity_activity_new', {'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}"
|
||||
class="btn btn-create">{{ 'Create'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@@ -3,10 +3,88 @@
|
||||
{% set t = activity.type %}
|
||||
|
||||
<a href="{{ path('chill_activity_activity_show', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
||||
class="badge-link" title="{{ 'Show the activity'|trans }}">
|
||||
class="dashboard-link" title="{{ 'Show the activity'|trans }}">
|
||||
|
||||
{% include '@ChillActivity/Activity/activity-badge-title.html.twig' %}
|
||||
<div class="dashboard">
|
||||
<span class="title_label"></span>
|
||||
<span class="title_action">
|
||||
|
||||
{%- if activity.date -%}
|
||||
<p class="date-label">{{ activity.date|format_date('short') }}</p>
|
||||
{%- endif -%}
|
||||
|
||||
<span class="like-h3">{{ activity.type.name | localize_translatable_string }}</span>
|
||||
|
||||
{% if activity.emergency %}
|
||||
<span class="badge bg-danger rounded-pill fs-6">{{ 'Emergency'|trans|upper }}</span>
|
||||
{% endif %}
|
||||
|
||||
<ul class="small_in_title mt-3">
|
||||
|
||||
{% if activity.sentReceived is not empty and t.sentReceivedVisible %}
|
||||
<li>
|
||||
<span class="item-key">{{ 'Sent received'|trans ~ ' : ' }}</span>
|
||||
<b>{{ activity.sentReceived|capitalize|trans }}</b>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if activity.location and t.locationVisible %}
|
||||
<li>
|
||||
<span class="item-key">{{ 'location'|trans ~ ': ' }}</span>
|
||||
<b>
|
||||
<span>{{ activity.location.locationType.title|localize_translatable_string }}</span>
|
||||
{{ activity.location.name }}
|
||||
</b>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if activity.user and t.userVisible %}
|
||||
<li>
|
||||
<span class="item-key">{{ 'Referrer'|trans ~ ': ' }}</span>
|
||||
<b>{{ activity.user.usernameCanonical }}</b>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li class="associated-persons">
|
||||
<span class="item-key">{{ 'Participants'|trans ~ ' : ' }}</span>
|
||||
{% for p in activity.personsAssociated %}
|
||||
<span class="badge-person">{{ p|chill_entity_render_box }}</span>
|
||||
{% endfor %}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="list-content my-3">
|
||||
{%- if t.reasonsVisible -%}
|
||||
{%- if activity.reasons is not empty -%}
|
||||
<li class="reasons">
|
||||
{% for r in activity.reasons %}
|
||||
{{ r|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
</li>
|
||||
{%- endif -%}
|
||||
{% endif %}
|
||||
{%- if t.socialIssuesVisible %}
|
||||
{%- if activity.socialIssues is not empty -%}
|
||||
<li class="social-issues">
|
||||
{% for r in activity.socialIssues %}
|
||||
{{ r|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
</li>
|
||||
{%- endif -%}
|
||||
{% endif %}
|
||||
{%- if t.socialActionsVisible -%}
|
||||
{%- if activity.socialActions is not empty -%}
|
||||
<li class="social-actions">
|
||||
{% for r in activity.socialActions %}
|
||||
{{ r|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
</li>
|
||||
{%- endif -%}
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
@@ -1,10 +1,16 @@
|
||||
<h1>{{ "Activity creation"|trans ~ ' :' }}</h1>
|
||||
<h2 class="chill-green mb-4">{{ entity.type.name|localize_translatable_string }}</h2>
|
||||
<h1>
|
||||
{{ "Activity creation"|trans }}
|
||||
</h1>
|
||||
<h2 class="badge-title mb-5">
|
||||
<span class="title_label"></span>
|
||||
<span class="title_action">
|
||||
{{ entity.type.name | localize_translatable_string }}
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
{{ form_start(form) }}
|
||||
{{ form_errors(form) }}
|
||||
|
||||
|
||||
{%- if form.emergency is defined -%}
|
||||
{{ form_row(form.emergency) }}
|
||||
{% endif %}
|
||||
@@ -39,7 +45,7 @@
|
||||
|
||||
{%- if form.persons is defined or form.thirdParties is defined or form.users is defined -%}
|
||||
|
||||
<h2 class="chill-red">{{ 'Concerned groups'|trans }}</h2>
|
||||
<h2 class="chill-blue">{{ 'Concerned groups'|trans }}</h2>
|
||||
|
||||
{%- if form.persons is defined -%}
|
||||
{{ form_widget(form.persons) }}
|
||||
@@ -55,7 +61,7 @@
|
||||
|
||||
{% endif %}
|
||||
|
||||
<h2 class="chill-red">{{ 'Activity data'|trans }}</h2>
|
||||
<h2 class="chill-blue">{{ 'Activity data'|trans }}</h2>
|
||||
|
||||
{%- if form.date is defined -%}
|
||||
{{ form_row(form.date) }}
|
||||
@@ -103,7 +109,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-create" type="submit">
|
||||
<button class="btn btn-save" type="submit">
|
||||
{{ 'Create'|trans }}
|
||||
</button>
|
||||
</li>
|
||||
|
@@ -1,128 +1,185 @@
|
||||
{%- set t = entity.type -%}
|
||||
{%- import "@ChillDocStore/Macro/macro.html.twig" as m -%}
|
||||
|
||||
<h1>
|
||||
{{ "Activity"|trans }}
|
||||
{%- if t.emergencyVisible and entity.emergency -%}
|
||||
<span class="badge bg-secondary">
|
||||
{{- 'Emergency'|trans -}}
|
||||
</span>
|
||||
{%- endif -%}
|
||||
</h1>
|
||||
<h1>{{ "Activity"|trans }}</h1>
|
||||
|
||||
<dl class="chill_view_data">
|
||||
<div class="flex-table">
|
||||
<div class="item-bloc">
|
||||
|
||||
<dt class="inline">{{ 'by'|trans|capitalize }}</dt>
|
||||
<dd>{{ entity.user }}</dd>
|
||||
<div class="item-row">
|
||||
<div class="wrap-list">
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title">
|
||||
{% if entity.date %}
|
||||
<p class="date-label">
|
||||
{{ entity.date|format_date('short') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="wl-col list">
|
||||
<h2 class="badge-title">
|
||||
<span class="title_label"></span>
|
||||
<span class="title_action">
|
||||
{{ entity.type.name | localize_translatable_string }}
|
||||
{% if entity.emergency %}
|
||||
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dt class="inline">{{ 'Type'|trans }}</dt>
|
||||
<dd>{{ entity.type.name | localize_translatable_string }}</dd>
|
||||
<div class="item-row separator">
|
||||
<dl class="chill_view_data">
|
||||
<dt class="inline">{{ 'Referrer'|trans|capitalize }}</dt>
|
||||
<dd>{{ entity.user }}</dd>
|
||||
|
||||
{%- if entity.scope -%}
|
||||
<dt class="inline">{{ 'Scope'|trans }}</dt>
|
||||
<dd><span class="scope">{{ entity.scope.name|localize_translatable_string }}</span></dd>
|
||||
{% endif %}
|
||||
{%- if entity.scope -%}
|
||||
<dt class="inline">{{ 'Scope'|trans }}</dt>
|
||||
<dd>
|
||||
<span class="scope">{{ entity.scope.name|localize_translatable_string }}</span>
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if t.socialIssuesVisible %}
|
||||
<dt class="inline">{{ 'Social issues'|trans }}</dt>
|
||||
<dd>
|
||||
{% if entity.socialIssues|length == 0 %}
|
||||
<p class="chill-no-data-statement">{{ 'No social issues associated'|trans }}</p>
|
||||
{% else %}
|
||||
{% for si in entity.socialIssues %}{{ si|chill_entity_render_box }}{% endfor %}
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% if t.socialIssuesVisible %}
|
||||
<dt class="inline">{{ 'Social issues'|trans }}</dt>
|
||||
<dd>
|
||||
{% if entity.socialIssues|length == 0 %}
|
||||
<p class="chill-no-data-statement">{{ 'No social issues associated'|trans }}</p>
|
||||
{% else %}
|
||||
{% for si in entity.socialIssues %}{{ si|chill_entity_render_box }}{% endfor %}
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if t.socialActionsVisible %}
|
||||
<dt class="inline">{{ 'Social actions'|trans }}</dt>
|
||||
<dd>
|
||||
{% if entity.socialActions|length == 0 %}
|
||||
<p class="chill-no-data-statement">{{ 'No social actions associated'|trans }}</p>
|
||||
{% else %}
|
||||
{% for sa in entity.socialActions %}{{ sa|chill_entity_render_box }}{% endfor %}
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% if t.socialActionsVisible %}
|
||||
<dt class="inline">{{ 'Social actions'|trans }}</dt>
|
||||
<dd>
|
||||
{% if entity.socialActions|length == 0 %}
|
||||
<span class="chill-no-data-statement">{{ 'No social actions associated'|trans }}</span>
|
||||
{% else %}
|
||||
{% for sa in entity.socialActions %}{{ sa|chill_entity_render_box }}{% endfor %}
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if t.reasonsVisible %}
|
||||
<dt class="inline">{{ 'Reasons'|trans }}</dt>
|
||||
{%- if entity.reasons is empty -%}
|
||||
<dd><span class="chill-no-data-statement">{{ 'No reason associated'|trans }}</span></dd>
|
||||
{%- else -%}
|
||||
<dd>{% for r in entity.reasons %}{{ r|chill_entity_render_box }} {% endfor %}</dd>
|
||||
{%- endif -%}
|
||||
{% endif %}
|
||||
{% if t.reasonsVisible %}
|
||||
<dt class="inline">{{ 'Reasons'|trans }}</dt>
|
||||
<dd>
|
||||
{%- if entity.reasons is empty -%}
|
||||
<span class="chill-no-data-statement">{{ 'No reason associated'|trans }}</span>
|
||||
{%- else -%}
|
||||
{% for r in entity.reasons %}{{ r|chill_entity_render_box }}{% endfor %}
|
||||
{%- endif -%}
|
||||
</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<h2 class="chill-red">{{ 'Concerned groups'|trans }}</h2>
|
||||
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {'context': context, 'with_display': 'bloc', 'badge_person': 'true' } %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="chill-red">{{ 'Activity data'|trans }}</h2>
|
||||
<h2 class="chill-blue">{{ 'Concerned groups'|trans }}</h2>
|
||||
|
||||
<dt class="inline">{{ 'Date'|trans }}</dt>
|
||||
<dd>{{ entity.date|format_date('long') }}</dd>
|
||||
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
|
||||
'context': context,
|
||||
'with_display': 'bloc',
|
||||
'badge_person': 'true'
|
||||
} %}
|
||||
|
||||
{% if t.locationVisible %}
|
||||
<dt class="inline">{{ 'Activity location'|trans }}</dt>
|
||||
<dd>
|
||||
{% if entity.location is not null %}
|
||||
<p>
|
||||
{{ entity.location.name }}
|
||||
<span> ({{ entity.location.locationType.title|localize_translatable_string }})</span>
|
||||
</p>
|
||||
{{ entity.location.address|chill_entity_render_box }}
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
|
||||
<h2 class="chill-blue">{{ 'Activity data'|trans }}</h2>
|
||||
|
||||
<div class="flex-table">
|
||||
<div class="item-bloc">
|
||||
|
||||
<dl class="chill_view_data">
|
||||
<dt class="inline">{{ 'Date'|trans }}</dt>
|
||||
<dd>{{ entity.date|format_date('long') }}</dd>
|
||||
|
||||
{% if t.locationVisible %}
|
||||
<dt class="inline">{{ 'Activity location'|trans }}</dt>
|
||||
<dd>
|
||||
{% if entity.location is not null %}
|
||||
<p>
|
||||
<span>{{ entity.location.locationType.title|localize_translatable_string }}</span>
|
||||
{{ entity.location.name }}
|
||||
</p>
|
||||
<div class="ms-3">{{ entity.location.address|chill_entity_render_box }}</div>
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if t.durationTimeVisible %}
|
||||
<dt class="inline">{{ 'Duration Time'|trans }}</dt>
|
||||
<dd>{% if entity.durationTime is not null %}
|
||||
{{ entity.durationTime|date('H:i') }}
|
||||
{% else %}
|
||||
{{ 'None'|trans|capitalize }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% if t.durationTimeVisible and is_granted('CHILL_ACTIVITY_SEE_DETAILS', entity) %}
|
||||
<dt class="inline">{{ 'Duration Time'|trans }}</dt>
|
||||
<dd>
|
||||
{% if entity.durationTime is not null %}
|
||||
{{ entity.durationTime|date('H:i') }}
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'None'|trans|capitalize }}</span>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if t.travelTimeVisible %}
|
||||
<dt class="inline">{{ 'Travel time'|trans }}</dt>
|
||||
<dd>{% if entity.travelTime is not null %}
|
||||
{{ entity.travelTime|date('H:i') }}
|
||||
{% else %}
|
||||
{{ 'None'|trans|capitalize }}
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% if t.travelTimeVisible and is_granted('CHILL_ACTIVITY_SEE_DETAILS', entity) %}
|
||||
<dt class="inline">{{ 'Travel time'|trans }}</dt>
|
||||
<dd>
|
||||
{% if entity.travelTime is not null %}
|
||||
{{ entity.travelTime|date('H:i') }}
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'None'|trans|capitalize }}</span>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if t.commentVisible %}
|
||||
<dt class="inline">{{ 'activity.comment'|trans }}</dt>
|
||||
{%- if entity.comment.empty -%}
|
||||
<dd><span class="chill-no-data-statement">{{ 'No comment associated'|trans }}</span></dd>
|
||||
{%- else -%}
|
||||
<dd>{{ entity.comment|chill_entity_render_box }}</dd>
|
||||
{%- endif -%}
|
||||
{% endif %}
|
||||
{% if t.commentVisible and is_granted('CHILL_ACTIVITY_SEE_DETAILS', entity) %}
|
||||
<dt class="inline">{{ 'activity.comment'|trans }}</dt>
|
||||
<dd>
|
||||
{%- if entity.comment.empty -%}
|
||||
<span class="chill-no-data-statement">{{ 'No comment associated'|trans }}</span>
|
||||
{%- else -%}
|
||||
{{ entity.comment|chill_entity_render_box }}
|
||||
{%- endif -%}
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if t.documentsVisible and entity.documents|length > 0 %}
|
||||
<dt>{{ 'Documents'|trans }}</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
{% for d in entity.documents %}
|
||||
<li>{{ m.download_button(d) }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% if t.documentsVisible and is_granted('CHILL_ACTIVITY_SEE_DETAILS', entity) %}
|
||||
<dt class="inline">{{ 'Documents'|trans }}</dt>
|
||||
<dd>
|
||||
{% if entity.documents|length > 0 %}
|
||||
<ul>
|
||||
{% for d in entity.documents %}
|
||||
<li>{{ m.download_button(d) }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'Any document found'|trans }}</span>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if t.attendeeVisible %}
|
||||
<dt class="inline">{{ 'Attendee'|trans }}</dt>
|
||||
<dd>{% if entity.attendee is not null %}{% if entity.attendee %}{{ 'present'|trans|capitalize }} {% else %} {{ 'not present'|trans|capitalize }}{% endif %}{% else %}{{ 'None'|trans|capitalize }}{% endif %}</dd>
|
||||
{% endif %}
|
||||
{% if t.attendeeVisible and is_granted('CHILL_ACTIVITY_SEE_DETAILS', entity) %}
|
||||
<dt class="inline">{{ 'Attendee'|trans }}</dt>
|
||||
<dd>
|
||||
{% if entity.attendee is not null %}
|
||||
{% if entity.attendee %}
|
||||
{{ 'present'|trans|capitalize }}
|
||||
{% else %}
|
||||
{{ 'not present'|trans|capitalize }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'None'|trans|capitalize }}</span>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
</dl>
|
||||
</dl>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% set person_id = null %}
|
||||
{% if person %}
|
||||
@@ -140,23 +197,24 @@
|
||||
{{ 'Back to the list'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% if is_granted('CHILL_ACTIVITY_UPDATE', entity) %}
|
||||
<li>
|
||||
<a class="btn btn-update" href="{{ path('chill_activity_activity_edit', { 'id': entity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}">
|
||||
{{ 'Edit'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{# TODO
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_ACTIVITY_DELETE', entity) %}
|
||||
#}
|
||||
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_delete', { 'id': entity.id, 'person_id' : person_id, 'accompanying_period_id': accompanying_course_id } ) }}" class="btn btn-delete">
|
||||
{{ 'Delete'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{#
|
||||
{% endif %}
|
||||
#}
|
||||
</ul>
|
||||
<script>
|
||||
import ShowPane from "../../../../ChillMainBundle/Resources/public/vuejs/Address/components/ShowPane";
|
||||
export default {
|
||||
components: {ShowPane}
|
||||
}
|
||||
</script>
|
||||
|
@@ -8,8 +8,6 @@
|
||||
|
||||
{% block content -%}
|
||||
<div class="activity-show">
|
||||
|
||||
{% include 'ChillActivityBundle:Activity:show.html.twig' with {'context': 'accompanyingCourse'} %}
|
||||
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
@@ -8,8 +8,6 @@
|
||||
|
||||
{% block personcontent -%}
|
||||
<div class="activity-show">
|
||||
|
||||
{% include 'ChillActivityBundle:Activity:show.html.twig' with {'context': 'person'} %}
|
||||
|
||||
{% include 'ChillActivityBundle:Activity:show.html.twig' with {'context': 'person'} %}
|
||||
</div>
|
||||
{% endblock personcontent %}
|
||||
|
@@ -92,6 +92,8 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
|
||||
public function getRoles(): array
|
||||
{
|
||||
return [
|
||||
self::SEE,
|
||||
self::SEE_DETAILS,
|
||||
self::CREATE_PERSON,
|
||||
self::CREATE_ACCOMPANYING_COURSE,
|
||||
self::UPDATE,
|
||||
|
@@ -0,0 +1,15 @@
|
||||
services:
|
||||
Chill\ActivityBundle\EntityListener\ActivityEntityListener:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
tags:
|
||||
-
|
||||
name: 'doctrine.orm.entity_listener'
|
||||
event: 'postPersist'
|
||||
entity: 'Chill\ActivityBundle\Entity\Activity'
|
||||
method: 'persistActionToCourse'
|
||||
-
|
||||
name: 'doctrine.orm.entity_listener'
|
||||
event: 'postUpdate'
|
||||
entity: 'Chill\ActivityBundle\Entity\Activity'
|
||||
method: 'persistActionToCourse'
|
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\Activity;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20211207152023 extends AbstractMigration
|
||||
{
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->throwIrreversibleMigrationException('placevisible and placelabel could not be created');
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'DROP place visible and place label, which are replaced by location';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE activitytype DROP placevisible');
|
||||
$this->addSql('ALTER TABLE activitytype DROP placelabel');
|
||||
}
|
||||
}
|
@@ -146,8 +146,8 @@ User visible: Visibilité du champ Utilisateur
|
||||
User label: Libellé du champ Utilisateur
|
||||
Date visible: Visibilité du champ Date
|
||||
Date label: Libellé du champ Date
|
||||
Place visible: Visibilité du champ Lieu
|
||||
Place label: Libellé du champ Lieu
|
||||
Location visible: Visibilité du champ Lieu
|
||||
Location label: Libellé du champ Lieu
|
||||
Third parties visible: Visibilité du champ Tiers
|
||||
Third parties label: Libellé du champ Tiers
|
||||
Duration time visible: Visibilité du champ Durée
|
||||
@@ -222,3 +222,5 @@ Aggregate by activity type: Aggréger par type d'activité
|
||||
Aggregate by activity reason: Aggréger par sujet de l'activité
|
||||
|
||||
Last activities: Les dernières activités
|
||||
|
||||
See activity in accompanying course context: Voir l'activité dans le contexte du parcours d'accompagnement
|
||||
|
@@ -162,8 +162,12 @@ class AsideActivityCategory
|
||||
public function setParent(?self $parent): self
|
||||
{
|
||||
// cache the old result for changing it during validaiton
|
||||
$this->oldParent = $this->parent;
|
||||
if ($this->parent) {
|
||||
$this->oldParent = $this->parent;
|
||||
}
|
||||
|
||||
$this->parent = $parent;
|
||||
dump($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@@ -137,7 +137,7 @@ final class AsideActivityFormType extends AbstractType
|
||||
$timezoneUTC = new DateTimeZone('GMT');
|
||||
/** @var DateTimeImmutable $data */
|
||||
$data = $formEvent->getData() === null ?
|
||||
DateTime::createFromFormat('U', 300) :
|
||||
DateTime::createFromFormat('U', '300') :
|
||||
$formEvent->getData();
|
||||
$seconds = $data->getTimezone()->getOffset($data);
|
||||
$data->setTimeZone($timezoneUTC);
|
||||
|
@@ -11,8 +11,17 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle;
|
||||
|
||||
use Chill\DocGeneratorBundle\Context\DocGeneratorContextInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class ChillDocGeneratorBundle extends Bundle
|
||||
{
|
||||
public function build(ContainerBuilder $container)
|
||||
{
|
||||
parent::build($container);
|
||||
|
||||
$container->registerForAutoconfiguration(DocGeneratorContextInterface::class)
|
||||
->addTag('chill_docgen.context');
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Context;
|
||||
|
||||
use Chill\DocGeneratorBundle\Context\Exception\ContextNotFoundException;
|
||||
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||
|
||||
class ContextManager
|
||||
{
|
||||
/**
|
||||
* @var DocGeneratorContextInterface[]|iterable
|
||||
*/
|
||||
private iterable $contexts;
|
||||
|
||||
public function __construct(iterable $contexts)
|
||||
{
|
||||
$this->contexts = $contexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throw ContextNotFoundException when the context is not found
|
||||
*/
|
||||
public function getContextByDocGeneratorTemplate(DocGeneratorTemplate $docGeneratorTemplate): DocGeneratorContextInterface
|
||||
{
|
||||
foreach ($this->contexts as $key => $context) {
|
||||
if ($docGeneratorTemplate->getContext() === $key) {
|
||||
return $context;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ContextNotFoundException($docGeneratorTemplate->getContext());
|
||||
}
|
||||
|
||||
public function getContextByKey(string $searchedKey): DocGeneratorContextInterface
|
||||
{
|
||||
foreach ($this->contexts as $key => $context) {
|
||||
if ($searchedKey === $key) {
|
||||
return $context;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ContextNotFoundException($searchedKey);
|
||||
}
|
||||
|
||||
public function getContexts(): array
|
||||
{
|
||||
return iterator_to_array($this->contexts);
|
||||
}
|
||||
}
|
@@ -11,8 +11,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Context;
|
||||
|
||||
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
|
||||
/**
|
||||
* Interface for context for for document generation.
|
||||
* Interface for context for document generation.
|
||||
*/
|
||||
interface DocGeneratorContextInterface
|
||||
{
|
||||
@@ -21,22 +24,15 @@ interface DocGeneratorContextInterface
|
||||
*
|
||||
* @param mixed $entity
|
||||
*/
|
||||
public function getData($entity): array;
|
||||
public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array;
|
||||
|
||||
/**
|
||||
* Generate the form that display.
|
||||
*
|
||||
* @param mixed $entity
|
||||
*/
|
||||
public function getForm($entity);
|
||||
public function getDescription(): string;
|
||||
|
||||
/**
|
||||
* has form.
|
||||
*/
|
||||
public function hasForm(): bool;
|
||||
public function getEntityClass(): string;
|
||||
|
||||
/**
|
||||
* True of false which entity supports.
|
||||
*/
|
||||
public function supports(string $entityClass): bool;
|
||||
public static function getKey(): string;
|
||||
|
||||
public function getName(): string;
|
||||
|
||||
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void;
|
||||
}
|
||||
|
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Context;
|
||||
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
interface DocGeneratorContextWithAdminFormInterface extends DocGeneratorContextInterface
|
||||
{
|
||||
public function adminFormReverseTransform(array $data): array;
|
||||
|
||||
public function adminFormTransform(array $data): array;
|
||||
|
||||
public function buildAdminForm(FormBuilderInterface $builder): void;
|
||||
|
||||
public function hasAdminForm(): bool;
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Context;
|
||||
|
||||
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
interface DocGeneratorContextWithPublicFormInterface extends DocGeneratorContextInterface
|
||||
{
|
||||
/**
|
||||
* Generate the form that display.
|
||||
*
|
||||
* @param mixed $entity
|
||||
*/
|
||||
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void;
|
||||
|
||||
public function getFormData(DocGeneratorTemplate $template, $entity): array;
|
||||
|
||||
/**
|
||||
* has form.
|
||||
*
|
||||
* @param mixed $entity
|
||||
*/
|
||||
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool;
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Context\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ContextNotFoundException extends RuntimeException
|
||||
{
|
||||
public function __construct($contextName)
|
||||
{
|
||||
parent::__construct(sprintf('the context with name %s is not found', $contextName));
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Context\Exception;
|
||||
|
||||
use LogicException;
|
||||
use function get_class;
|
||||
use function gettype;
|
||||
use function is_object;
|
||||
|
||||
class UnexpectedTypeException extends LogicException
|
||||
{
|
||||
public function __construct($value, string $expectedType)
|
||||
{
|
||||
parent::__construct(sprintf('Expected argument of type "%s", "%s" given', $expectedType, is_object($value) ? get_class($value) : gettype($value)));
|
||||
}
|
||||
}
|
@@ -1,109 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Context;
|
||||
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
|
||||
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
|
||||
use Exception;
|
||||
use function count;
|
||||
use function get_class;
|
||||
|
||||
/**
|
||||
* Context that display a form to select a member of a houseHold.
|
||||
*/
|
||||
class HouseholdMemberSelectionContext implements DocGeneratorContextInterface
|
||||
{
|
||||
/**
|
||||
* Get the data that will be injected to the generated document.
|
||||
*
|
||||
* @param mixed $entity
|
||||
*/
|
||||
public function getData($entity): array
|
||||
{
|
||||
$datas = [
|
||||
'setValues' => [],
|
||||
'cloneRowAndSetValues' => [],
|
||||
];
|
||||
|
||||
$persons = $entity->getAccompanyingPeriodWork()->getPersons();
|
||||
|
||||
if (count($persons) > 0) {
|
||||
$firstPerson = $persons[0];
|
||||
|
||||
$datas['setValues'][] = [
|
||||
'firstPersonFirstName' => $firstPerson->getFirstName(),
|
||||
'firstPersonLastName' => $firstPerson->getLastName(), ];
|
||||
}
|
||||
|
||||
if (get_class($entity) === AccompanyingPeriodWorkEvaluation::class) {
|
||||
$values = [];
|
||||
|
||||
foreach ($entity->getAccompanyingPeriodWork()->getPersons() as $person) {
|
||||
$i = 1;
|
||||
$values[] = [
|
||||
'personRowId' => $i,
|
||||
'personFirstName' => $person->getFirstName(),
|
||||
'personLastName' => $person->getLastName(),
|
||||
];
|
||||
}
|
||||
|
||||
$datas['cloneRowAndSetValues'][] = [
|
||||
'personRowId', $values, ];
|
||||
}
|
||||
|
||||
return $datas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the form that display.
|
||||
*
|
||||
* @param mixed $entity
|
||||
*/
|
||||
public function getForm($entity)
|
||||
{
|
||||
throw new Exception('No implemented yet', 1);
|
||||
$choices = [];
|
||||
|
||||
if (get_class($entity) === AccompanyingPeriodWorkEvaluation::class) {
|
||||
foreach ($entity->getAccompanyingPeriodWork()->getPersons() as $person) {
|
||||
$choices[$person->getId()] = $person->getName();
|
||||
}
|
||||
}
|
||||
|
||||
$builder->add('members', ChoiceType::class, [
|
||||
'choices' => $choices,
|
||||
'placeholder' => 'Choose a person',
|
||||
'label' => 'Person to add',
|
||||
]);
|
||||
|
||||
return $builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* has form.
|
||||
*/
|
||||
public function hasForm(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* True of false which entity supports.
|
||||
*/
|
||||
public function supports(string $entityClass): bool
|
||||
{
|
||||
return
|
||||
(AccompanyingPeriod::class === $entityClass)
|
||||
|| (SocialAction::class === $entityClass);
|
||||
}
|
||||
}
|
@@ -11,8 +11,77 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Controller;
|
||||
|
||||
use Chill\DocGeneratorBundle\Context\ContextManager;
|
||||
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||
use Chill\MainBundle\CRUD\Controller\CRUDController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
class AdminDocGeneratorTemplateController extends CRUDController
|
||||
{
|
||||
private ContextManager $contextManager;
|
||||
|
||||
public function __construct(ContextManager $contextManager)
|
||||
{
|
||||
$this->contextManager = $contextManager;
|
||||
}
|
||||
|
||||
public function generateTemplateParameter(string $action, $entity, Request $request, array $defaultTemplateParameters = [])
|
||||
{
|
||||
switch ($action) {
|
||||
case 'new':
|
||||
$context = $this->contextManager->getContextByKey($request->get('context'));
|
||||
// no break
|
||||
case 'edit':
|
||||
$context = $this->contextManager->getContextByDocGeneratorTemplate($entity);
|
||||
|
||||
return array_merge(
|
||||
$defaultTemplateParameters,
|
||||
['context' => $context]
|
||||
);
|
||||
|
||||
case 'index':
|
||||
return array_merge(
|
||||
$defaultTemplateParameters,
|
||||
['contextManager' => $this->contextManager]
|
||||
);
|
||||
|
||||
default:
|
||||
return parent::generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters); // TODO: Change the autogenerated stub
|
||||
}
|
||||
}
|
||||
|
||||
public function new(Request $request): Response
|
||||
{
|
||||
if (!$request->query->has('context')) {
|
||||
return $this->redirectToRoute('chill_docgen_admin_template_pick-context');
|
||||
}
|
||||
|
||||
return parent::new($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("{_locale}/admin/docgen/template/pick-context", name="chill_docgen_admin_template_pick-context")
|
||||
*/
|
||||
public function pickContext(Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
||||
|
||||
return $this->render('ChillDocGeneratorBundle:Admin/DocGeneratorTemplate:pick-context.html.twig', [
|
||||
'contexts' => $this->contextManager->getContexts(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function createEntity(string $action, Request $request): object
|
||||
{
|
||||
/** @var DocGeneratorTemplate $entity */
|
||||
$entity = parent::createEntity($action, $request);
|
||||
$key = $request->query->get('context');
|
||||
$context = $this->contextManager->getContextByKey($key);
|
||||
|
||||
$entity->setContext($key)->setEntity($context->getEntityClass());
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
||||
|
@@ -1,76 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Controller;
|
||||
|
||||
use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlOpenstackGenerator;
|
||||
use PhpOffice\PhpWord\TemplateProcessor;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\HeaderUtils;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* Class DocGeneratorController.
|
||||
*/
|
||||
class DocGeneratorController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @Route(
|
||||
* "{_locale}/doc/gen/test",
|
||||
* name="chill_docgenerator_test"
|
||||
* )
|
||||
*/
|
||||
public function getDoc(Request $request, TempUrlOpenstackGenerator $tempUrlGenerator): Response
|
||||
{
|
||||
$p = $tempUrlGenerator->generate(
|
||||
'GET',
|
||||
'FORMULAIRE_AEB.docx',
|
||||
$request->query->has('expires_delay') ? $request->query->getInt('expires_delay', 0) : null
|
||||
);
|
||||
|
||||
$tmpfname = tempnam(sys_get_temp_dir(), 'DOC_TEMPLATE');
|
||||
file_put_contents($tmpfname, file_get_contents($p->url));
|
||||
|
||||
$templateProcessor = new TemplateProcessor($tmpfname);
|
||||
$templateProcessor->setValues(['firstname' => 'John', 'lastname' => 'Doe']);
|
||||
|
||||
$tmpfname2 = tempnam(sys_get_temp_dir(), 'DOC_GENERATED');
|
||||
$templateProcessor->saveAs($tmpfname2);
|
||||
|
||||
unlink($tmpfname);
|
||||
|
||||
$fileContent = fopen($tmpfname2, 'rb'); // the generated file content
|
||||
$response = new Response(fread($fileContent, filesize($tmpfname2)));
|
||||
|
||||
$disposition = HeaderUtils::makeDisposition(
|
||||
HeaderUtils::DISPOSITION_ATTACHMENT,
|
||||
'foo.docx'
|
||||
);
|
||||
|
||||
$response->headers->set('Content-Disposition', $disposition);
|
||||
unlink($tmpfname2);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(
|
||||
* "{_locale}/doc/gen/test",
|
||||
* name="chill_docgenerator_test"
|
||||
* )
|
||||
*/
|
||||
public function testAction(): Response
|
||||
{
|
||||
return (new Response())->setContent('Test');
|
||||
}
|
||||
}
|
@@ -11,28 +11,100 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Controller;
|
||||
|
||||
use Base64Url\Base64Url;
|
||||
use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface;
|
||||
use Chill\DocGeneratorBundle\Context\HouseholdMemberSelectionContext;
|
||||
use Chill\DocGeneratorBundle\Context\ContextManager;
|
||||
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
|
||||
use Chill\DocGeneratorBundle\Context\Exception\ContextNotFoundException;
|
||||
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||
use Chill\DocGeneratorBundle\GeneratorDriver\DriverInterface;
|
||||
use Chill\DocGeneratorBundle\GeneratorDriver\Exception\TemplateException;
|
||||
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
|
||||
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Chill\MainBundle\Serializer\Model\Collection;
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\TransferException;
|
||||
use PhpOffice\PhpWord\TemplateProcessor;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
// TODO à mettre dans services
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
class DocGeneratorTemplateController extends AbstractController
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
final class DocGeneratorTemplateController extends AbstractController
|
||||
{
|
||||
private HttpClientInterface $client;
|
||||
|
||||
private ContextManager $contextManager;
|
||||
|
||||
private DocGeneratorTemplateRepository $docGeneratorTemplateRepository;
|
||||
|
||||
private DriverInterface $driver;
|
||||
|
||||
private KernelInterface $kernel;
|
||||
|
||||
private LoggerInterface $logger;
|
||||
|
||||
private PaginatorFactory $paginatorFactory;
|
||||
|
||||
private TempUrlGeneratorInterface $tempUrlGenerator;
|
||||
|
||||
public function __construct(
|
||||
ContextManager $contextManager,
|
||||
DocGeneratorTemplateRepository $docGeneratorTemplateRepository,
|
||||
DriverInterface $driver,
|
||||
LoggerInterface $logger,
|
||||
PaginatorFactory $paginatorFactory,
|
||||
TempUrlGeneratorInterface $tempUrlGenerator,
|
||||
KernelInterface $kernel,
|
||||
HttpClientInterface $client
|
||||
) {
|
||||
$this->contextManager = $contextManager;
|
||||
$this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository;
|
||||
$this->driver = $driver;
|
||||
$this->logger = $logger;
|
||||
$this->paginatorFactory = $paginatorFactory;
|
||||
$this->tempUrlGenerator = $tempUrlGenerator;
|
||||
$this->kernel = $kernel;
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(
|
||||
* "{_locale}/admin/doc/gen/generate/test/from/{template}/for/{entityClassName}/{entityId}",
|
||||
* name="chill_docgenerator_test_generate_from_template"
|
||||
* )
|
||||
*/
|
||||
public function adminTestGenerateDocFromTemplateAction(
|
||||
DocGeneratorTemplate $template,
|
||||
string $entityClassName,
|
||||
int $entityId,
|
||||
Request $request
|
||||
): Response {
|
||||
return $this->generateDocFromTemplate(
|
||||
$template,
|
||||
$entityClassName,
|
||||
$entityId,
|
||||
$request,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(
|
||||
* "{_locale}/doc/gen/generate/from/{template}/for/{entityClassName}/{entityId}",
|
||||
@@ -40,80 +112,235 @@ class DocGeneratorTemplateController extends AbstractController
|
||||
* )
|
||||
*/
|
||||
public function generateDocFromTemplateAction(
|
||||
TempUrlGeneratorInterface $tempUrlGenerator,
|
||||
DocGeneratorTemplate $template,
|
||||
string $entityClassName,
|
||||
int $entityId,
|
||||
Request $request
|
||||
): Response {
|
||||
$getUrlGen = $tempUrlGenerator->generate(
|
||||
'GET',
|
||||
$template->getFile()
|
||||
return $this->generateDocFromTemplate(
|
||||
$template,
|
||||
$entityClassName,
|
||||
$entityId,
|
||||
$request,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(
|
||||
* "/api/1.0/docgen/templates/by-entity/{entityClassName}",
|
||||
* name="chill_docgenerator_templates_for_entity_api"
|
||||
* )
|
||||
*/
|
||||
public function listTemplateApiAction(string $entityClassName): Response
|
||||
{
|
||||
$nb = $this->docGeneratorTemplateRepository->countByEntity($entityClassName);
|
||||
$paginator = $this->paginatorFactory->create($nb);
|
||||
$entities = $this->docGeneratorTemplateRepository->findByEntity(
|
||||
$entityClassName,
|
||||
$paginator->getCurrentPageFirstItemNumber(),
|
||||
$paginator->getItemsPerPage()
|
||||
);
|
||||
|
||||
$tmpfname = tempnam(sys_get_temp_dir(), 'DOC_TEMPLATE');
|
||||
file_put_contents($tmpfname, file_get_contents($getUrlGen->url));
|
||||
return $this->json(
|
||||
new Collection($entities, $paginator),
|
||||
Response::HTTP_OK,
|
||||
[],
|
||||
[AbstractNormalizer::GROUPS => ['read']]
|
||||
);
|
||||
}
|
||||
|
||||
$entity = $this->getDoctrine()->getRepository($entityClassName)->find($entityId);
|
||||
/**
|
||||
* @Route(
|
||||
* "{_locale}/admin/doc/gen/generate/test/redirect",
|
||||
* name="chill_docgenerator_test_generate_redirect"
|
||||
* )
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function redirectToTestGenerate(Request $request): RedirectResponse
|
||||
{
|
||||
$template = $request->query->getInt('template');
|
||||
|
||||
if ($template->getContext() === HouseholdMemberSelectionContext::class) {
|
||||
$context = new HouseholdMemberSelectionContext();
|
||||
$datas = $context->getData($entity);
|
||||
if (null === $template) {
|
||||
throw new BadRequestHttpException('template parameter is missing');
|
||||
}
|
||||
|
||||
$entityClassName = $request->query->get('entityClassName');
|
||||
|
||||
if (null === $entityClassName) {
|
||||
throw new BadRequestHttpException('entityClassName is missing');
|
||||
}
|
||||
|
||||
$entityId = $request->query->get('entityId');
|
||||
|
||||
if (null === $entityId) {
|
||||
throw new BadRequestHttpException('entityId is missing');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute(
|
||||
'chill_docgenerator_test_generate_from_template',
|
||||
['template' => $template, 'entityClassName' => $entityClassName, 'entityId' => $entityId,
|
||||
'returnPath' => $request->query->get('returnPath', '/'), ]
|
||||
);
|
||||
}
|
||||
|
||||
private function generateDocFromTemplate(
|
||||
DocGeneratorTemplate $template,
|
||||
string $entityClassName,
|
||||
int $entityId,
|
||||
Request $request,
|
||||
bool $isTest
|
||||
): Response {
|
||||
try {
|
||||
$context = $this->contextManager->getContextByDocGeneratorTemplate($template);
|
||||
} catch (ContextNotFoundException $e) {
|
||||
throw new NotFoundHttpException($e->getMessage(), $e);
|
||||
}
|
||||
|
||||
$entity = $this->getDoctrine()->getRepository($context->getEntityClass())->find($entityId);
|
||||
|
||||
if (null === $entity) {
|
||||
throw new NotFoundHttpException("Entity with classname {$entityClassName} and id {$entityId} is not found");
|
||||
}
|
||||
|
||||
$contextGenerationData = [];
|
||||
|
||||
if ($context instanceof DocGeneratorContextWithPublicFormInterface
|
||||
&& $context->hasPublicForm($template, $entity) || $isTest) {
|
||||
if ($context instanceof DocGeneratorContextWithPublicFormInterface) {
|
||||
$builder = $this->createFormBuilder(
|
||||
array_merge(
|
||||
$context->getFormData($template, $entity),
|
||||
$isTest ? ['test_file' => null] : []
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$builder = $this->createFormBuilder(
|
||||
['test_file' => null]
|
||||
);
|
||||
}
|
||||
|
||||
$context->buildPublicForm($builder, $template, $entity);
|
||||
|
||||
if ($isTest) {
|
||||
$builder->add('test_file', FileType::class, [
|
||||
'label' => 'Template file',
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
$form = $builder->getForm()->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$contextGenerationData = $form->getData();
|
||||
} elseif (!$form->isSubmitted() || ($form->isSubmitted() && !$form->isValid())) {
|
||||
$templatePath = '@ChillDocGenerator/Generator/basic_form.html.twig';
|
||||
$templateOptions = ['entity' => $entity, 'form' => $form->createView(),
|
||||
'template' => $template, 'context' => $context, ];
|
||||
|
||||
return $this->render($templatePath, $templateOptions);
|
||||
}
|
||||
}
|
||||
|
||||
if ($isTest && null !== $contextGenerationData['test_file']) {
|
||||
/** @var File $file */
|
||||
$file = $contextGenerationData['test_file'];
|
||||
$templateResource = fopen($file->getPathname(), 'rb');
|
||||
} else {
|
||||
throw new Exception('Not implemented', 1);
|
||||
$getUrlGen = $this->tempUrlGenerator->generate(
|
||||
'GET',
|
||||
$template->getFile()->getFilename()
|
||||
);
|
||||
|
||||
$data = $this->client->request('GET', $getUrlGen->url);
|
||||
|
||||
$iv = $template->getFile()->getIv(); // iv as an Array
|
||||
$ivGoodFormat = pack('C*', ...$iv); // iv as a String (ok for openssl_decrypt)
|
||||
|
||||
$method = 'AES-256-CBC';
|
||||
|
||||
$key = $template->getFile()->getKeyInfos()['k'];
|
||||
$keyGoodFormat = Base64Url::decode($key);
|
||||
|
||||
$dataDecrypted = openssl_decrypt($data->getContent(), $method, $keyGoodFormat, 1, $ivGoodFormat);
|
||||
|
||||
if (false === $dataDecrypted) {
|
||||
throw new Exception('Error during Decrypt ', 1);
|
||||
}
|
||||
|
||||
if (false === $templateResource = fopen('php://memory', 'r+b')) {
|
||||
$this->logger->error('Could not write data to memory');
|
||||
|
||||
throw new HttpException(500);
|
||||
}
|
||||
fwrite($templateResource, $dataDecrypted);
|
||||
rewind($templateResource);
|
||||
}
|
||||
|
||||
$templateProcessor = new TemplateProcessor($tmpfname);
|
||||
$datas = $context->getData($template, $entity, $contextGenerationData);
|
||||
|
||||
foreach ($datas['setValues'] as $setValuesConf) {
|
||||
$templateProcessor->setValues($setValuesConf);
|
||||
try {
|
||||
$generatedResource = $this->driver->generateFromResource($templateResource, $template->getFile()->getType(), $datas, $template->getFile()->getFilename());
|
||||
} catch (TemplateException $e) {
|
||||
$msg = implode("\n", $e->getErrors());
|
||||
|
||||
return new Response($msg, 400, [
|
||||
'Content-Type' => 'text/plain',
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($datas['cloneRowAndSetValues'] as $cloneRowAndSetValues) {
|
||||
$templateProcessor->cloneRowAndSetValues($cloneRowAndSetValues[0], $cloneRowAndSetValues[1]);
|
||||
fclose($templateResource);
|
||||
|
||||
if ($isTest) {
|
||||
return new StreamedResponse(
|
||||
static function () use ($generatedResource) {
|
||||
fpassthru($generatedResource);
|
||||
fclose($generatedResource);
|
||||
},
|
||||
Response::HTTP_OK,
|
||||
[
|
||||
'Content-Transfer-Encoding', 'binary',
|
||||
'Content-Type' => 'application/vnd.oasis.opendocument.text',
|
||||
'Content-Disposition' => sprintf('attachment; filename="%s.odt"', 'generated'),
|
||||
'Content-Length' => fstat($generatedResource)['size'],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
$tmpfname2 = tempnam(sys_get_temp_dir(), 'DOC_GENERATED');
|
||||
$templateProcessor->saveAs($tmpfname2);
|
||||
$genDocName = 'doc_' . sprintf('%010d', mt_rand()) . 'odt';
|
||||
|
||||
unlink($tmpfname);
|
||||
|
||||
$fileContent = fopen($tmpfname2, 'rb'); // the generated file content
|
||||
|
||||
$genDocName = 'doc_' . sprintf('%010d', mt_rand()) . '.docx';
|
||||
|
||||
$getUrlGen = $tempUrlGenerator->generate(
|
||||
$getUrlGen = $this->tempUrlGenerator->generate(
|
||||
'PUT',
|
||||
$genDocName
|
||||
);
|
||||
|
||||
unlink($tmpfname2);
|
||||
|
||||
$client = new Client();
|
||||
|
||||
try {
|
||||
$putResponse = $client->request('PUT', $getUrlGen->url, [
|
||||
'body' => $fileContent,
|
||||
'body' => $generatedResource,
|
||||
]);
|
||||
|
||||
if ($putResponse->getStatusCode() === 201) {
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$storedObject = new StoredObject();
|
||||
$storedObject
|
||||
// currently, only docx is supported
|
||||
->setType('application/vnd.openxmlformats-officedocument.wordprocessingml.document')
|
||||
->setType($template->getFile()->getType())
|
||||
->setFilename($genDocName);
|
||||
|
||||
$em->persist($storedObject);
|
||||
|
||||
// Only for evaluation
|
||||
if ($entity instanceof AccompanyingPeriodWorkEvaluation) {
|
||||
$doc = new AccompanyingPeriodWorkEvaluationDocument();
|
||||
$doc
|
||||
->setStoredObject($storedObject)
|
||||
->setTemplate($template);
|
||||
$entity->addDocument($doc);
|
||||
$em->persist($doc);
|
||||
try {
|
||||
$context->storeGenerated($template, $storedObject, $entity, $contextGenerationData);
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error('Could not store the associated document to entity', [
|
||||
'entityClassName' => $entityClassName,
|
||||
'entityId' => $entityId,
|
||||
'contextKey' => $context->getName(),
|
||||
]);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$em->flush();
|
||||
@@ -129,29 +356,4 @@ class DocGeneratorTemplateController extends AbstractController
|
||||
|
||||
throw new Exception('Unable to generate document.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(
|
||||
* "{_locale}/doc/gen/templates/for/{entityClassName}",
|
||||
* name="chill_docgenerator_templates_for_entity_api"
|
||||
* )
|
||||
*/
|
||||
public function listTemplateApiAction(
|
||||
string $entityClassName,
|
||||
DocGeneratorTemplateRepository $templateRepository
|
||||
): Response {
|
||||
$entities = $templateRepository->findByEntity($entityClassName);
|
||||
|
||||
$ret = [];
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$ret[] = [
|
||||
'id' => $entity->getId(),
|
||||
'name' => $entity->getName(),
|
||||
'description' => $entity->getDescription(),
|
||||
];
|
||||
}
|
||||
|
||||
return new JsonResponse(['results' => $ret]);
|
||||
}
|
||||
}
|
||||
|
@@ -12,9 +12,11 @@ declare(strict_types=1);
|
||||
namespace Chill\DocGeneratorBundle\DataFixtures\ORM;
|
||||
|
||||
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Service\DocGenerator\AccompanyingPeriodContext;
|
||||
use DateTime;
|
||||
use Doctrine\Common\DataFixtures\AbstractFixture;
|
||||
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
|
||||
/**
|
||||
@@ -28,31 +30,52 @@ class LoadDocGeneratorTemplate extends AbstractFixture
|
||||
[
|
||||
'name' => ['fr' => 'FORMULAIRE AEB'],
|
||||
'desc' => 'stocké sur openstack comedienbe',
|
||||
'file' => 'FORMULAIRE_AEB_WITH_DATA_INJ.docx',
|
||||
'context' => 'Chill\DocGeneratorBundle\Context\HouseholdMemberSelectionContext',
|
||||
'entities' => [AccompanyingPeriodWorkEvaluation::class],
|
||||
'file' => [
|
||||
'filename' => 'pKNlhCrQDCRsAuC8vYHDKa',
|
||||
'key' => '{"alg":"A256CBC","ext":true,"k":"_VihnD41-VDHlpS-ouwtbMPnu-OXVdtA7ENQWWtAQYM","key_ops":["encrypt","decrypt"],"kty":"oct"}',
|
||||
'iv' => '[86,231,83,148,117,107,149,173,130,19,105,194,224,145,8,48]',
|
||||
'type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
],
|
||||
'context' => AccompanyingPeriodContext::class,
|
||||
'entity' => AccompanyingPeriod::class,
|
||||
'options' => ['mainPerson' => false, 'person1' => false, 'person2' => false],
|
||||
], [
|
||||
'name' => ['fr' => 'AIDE ALIMENTAIRE'],
|
||||
'desc' => 'stocké sur openstack comedienbe',
|
||||
'file' => 'AIDE_ALIMENTAIRE.docx',
|
||||
'context' => 'Chill\DocGeneratorBundle\Context\HouseholdMemberSelectionContext',
|
||||
'entities' => ['Chill\PersonBundle\Entity\AccompanyingPeriod', 'Chill\PersonBundle\Entity\SocialWork\SocialAction', AccompanyingPeriodWorkEvaluation::class],
|
||||
'file' => [
|
||||
'filename' => 'pKNlhCrQDCRsAuC8vYHDKa',
|
||||
'key' => '{"alg":"A256CBC","ext":true,"k":"_VihnD41-VDHlpS-ouwtbMPnu-OXVdtA7ENQWWtAQYM","key_ops":["encrypt","decrypt"],"kty":"oct"}',
|
||||
'iv' => '[86,231,83,148,117,107,149,173,130,19,105,194,224,145,8,48]',
|
||||
'type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
],
|
||||
'context' => AccompanyingPeriodContext::class,
|
||||
'entity' => AccompanyingPeriod::class,
|
||||
'options' => ['mainPerson' => false, 'person1' => false, 'person2' => false],
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($templates as $template) {
|
||||
echo 'Adding doc generator templates ' . $template['file'] . ")\n";
|
||||
|
||||
$newStoredObj = (new StoredObject())
|
||||
->setFilename($template['file']['filename'])
|
||||
->setKeyInfos(json_decode($template['file']['key'], true))
|
||||
->setIv(json_decode($template['file']['iv'], true))
|
||||
->setCreationDate(new DateTime('today'))
|
||||
->setType($template['file']['type']);
|
||||
|
||||
$manager->persist($newStoredObj);
|
||||
|
||||
$newTemplate = (new DocGeneratorTemplate())
|
||||
->setName($template['name'])
|
||||
->setDescription($template['desc'])
|
||||
->setFile($template['file'])
|
||||
->setFile($newStoredObj)
|
||||
->setContext($template['context'])
|
||||
->setEntities($template['entities']);
|
||||
->setEntity($template['entity'])
|
||||
->setOptions($template['options']);
|
||||
|
||||
$manager->persist($newTemplate);
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
$manager->flush();
|
||||
}
|
||||
}
|
||||
|
@@ -26,6 +26,10 @@ class ChillDocGeneratorExtension extends Extension implements PrependExtensionIn
|
||||
{
|
||||
public function load(array $configs, ContainerBuilder $container)
|
||||
{
|
||||
$configuration = new Configuration();
|
||||
$config = $this->processConfiguration($configuration, $configs);
|
||||
$container->setParameter('chill_doc_generator', $config);
|
||||
|
||||
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../config'));
|
||||
$loader->load('services.yaml');
|
||||
$loader->load('services/controller.yaml');
|
||||
@@ -37,6 +41,7 @@ class ChillDocGeneratorExtension extends Extension implements PrependExtensionIn
|
||||
{
|
||||
$this->preprendRoutes($container);
|
||||
$this->prependCruds($container);
|
||||
$this->prependClientConfig($container);
|
||||
}
|
||||
|
||||
protected function prependCruds(ContainerBuilder $container)
|
||||
@@ -78,4 +83,23 @@ class ChillDocGeneratorExtension extends Extension implements PrependExtensionIn
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function prependClientConfig(ContainerBuilder $container)
|
||||
{
|
||||
$configs = $container->getExtensionConfig($this->getAlias());
|
||||
$resolvingBag = $container->getParameterBag();
|
||||
$configs = $resolvingBag->resolveValue($configs);
|
||||
|
||||
$config = $this->processConfiguration(new Configuration(), $configs);
|
||||
|
||||
$container->prependExtensionConfig('framework', [
|
||||
'http_client' => [
|
||||
'scoped_clients' => [
|
||||
'relatorio.client' => [
|
||||
'scope' => $config['driver']['relatorio']['url'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@@ -14,21 +14,35 @@ namespace Chill\DocGeneratorBundle\DependencyInjection;
|
||||
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||
|
||||
/**
|
||||
* This is the class that validates and merges configuration from your app/config files.
|
||||
*
|
||||
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/configuration.html}
|
||||
*/
|
||||
class Configuration implements ConfigurationInterface
|
||||
{
|
||||
public function getConfigTreeBuilder()
|
||||
{
|
||||
$treeBuilder = new TreeBuilder('chill_calendar');
|
||||
$rootNode = $treeBuilder->getRootNode('chill_calendar');
|
||||
$treeBuilder = new TreeBuilder('chill_doc_generator');
|
||||
$rootNode = $treeBuilder->getRootNode();
|
||||
|
||||
// Here you should define the parameters that are allowed to
|
||||
// configure your bundle. See the documentation linked above for
|
||||
// more information on that topic.
|
||||
$rootNode
|
||||
->children()
|
||||
->arrayNode('driver')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->enumNode('type')
|
||||
->isRequired()
|
||||
->values(['relatorio'])
|
||||
->defaultValue('relatorio')
|
||||
->end()
|
||||
->arrayNode('relatorio')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->scalarNode('url')
|
||||
->isRequired()
|
||||
->defaultValue('http://relatorio:8888/')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end();
|
||||
|
||||
return $treeBuilder;
|
||||
}
|
||||
|
@@ -11,44 +11,51 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Entity;
|
||||
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="chill_docgen_template")
|
||||
* @Serializer\DiscriminatorMap(typeProperty="type", mapping={
|
||||
* "docgen_template": DocGeneratorTemplate::class
|
||||
* })
|
||||
*/
|
||||
class DocGeneratorTemplate
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*
|
||||
* Class name of the context to use
|
||||
* @ORM\Column(type="boolean", options={"default": true})
|
||||
*/
|
||||
private bool $active = true;
|
||||
|
||||
/**
|
||||
* Class name of the context to use.
|
||||
*
|
||||
* so if $context = ''
|
||||
* this template will use '' as context
|
||||
*
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
private string $context;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=true)
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private string $description;
|
||||
private ?string $description = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="simple_array")
|
||||
* Class name of the entity for which this template can be used.
|
||||
*
|
||||
* Class name of the entities for which this template can be used
|
||||
*
|
||||
* so if $entities = ['Chill\PersonBundle\Entity\AccompanyingPeriod', 'Chill\PersonBundle\Entity\SocialWork\SocialAction']
|
||||
* this template can be selected for an AccompanyingPeriod or a SocialAction
|
||||
* @ORM\Column(type="string", options={"default": ""})
|
||||
*/
|
||||
private array $entities = [];
|
||||
private string $entity = '';
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
* @ORM\ManyToOne(targetEntity=StoredObject::class, cascade={"persist"}))
|
||||
*/
|
||||
private string $file;
|
||||
private ?StoredObject $file = null;
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
@@ -64,6 +71,13 @@ class DocGeneratorTemplate
|
||||
*/
|
||||
private array $name = [];
|
||||
|
||||
/**
|
||||
* Options for the template.
|
||||
*
|
||||
* @ORM\Column(type="json", name="template_options", options={"default":"[]"})
|
||||
*/
|
||||
private array $options = [];
|
||||
|
||||
public function getContext(): ?string
|
||||
{
|
||||
return $this->context;
|
||||
@@ -74,12 +88,12 @@ class DocGeneratorTemplate
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function getEntities(): ?array
|
||||
public function getEntity(): string
|
||||
{
|
||||
return $this->entities;
|
||||
return $this->entity;
|
||||
}
|
||||
|
||||
public function getFile(): ?string
|
||||
public function getFile(): ?StoredObject
|
||||
{
|
||||
return $this->file;
|
||||
}
|
||||
@@ -94,6 +108,23 @@ class DocGeneratorTemplate
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->active;
|
||||
}
|
||||
|
||||
public function setActive(bool $active): DocGeneratorTemplate
|
||||
{
|
||||
$this->active = $active;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setContext(string $context): self
|
||||
{
|
||||
$this->context = $context;
|
||||
@@ -108,14 +139,14 @@ class DocGeneratorTemplate
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setEntities(array $entities): self
|
||||
public function setEntity(string $entity): self
|
||||
{
|
||||
$this->entities = $entities;
|
||||
$this->entity = $entity;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setFile(string $file): self
|
||||
public function setFile(StoredObject $file): self
|
||||
{
|
||||
$this->file = $file;
|
||||
|
||||
@@ -128,4 +159,11 @@ class DocGeneratorTemplate
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setOptions(array $options): self
|
||||
{
|
||||
$this->options = $options;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@@ -11,22 +11,64 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Form;
|
||||
|
||||
use Chill\DocGeneratorBundle\Context\ContextManager;
|
||||
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
|
||||
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||
use Chill\DocStoreBundle\Form\StoredObjectType;
|
||||
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\CallbackTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class DocGeneratorTemplateType extends AbstractType
|
||||
{
|
||||
private ContextManager $contextManager;
|
||||
|
||||
public function __construct(ContextManager $contextManager)
|
||||
{
|
||||
$this->contextManager = $contextManager;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
/** @var DocGeneratorTemplate $template */
|
||||
$template = $options['data'];
|
||||
$context = $this->contextManager->getContextByKey($template->getContext());
|
||||
|
||||
$builder
|
||||
->add('name', TranslatableStringFormType::class, [
|
||||
'label' => 'Nom',
|
||||
])
|
||||
->add('description')
|
||||
->add('file');
|
||||
->add('file', StoredObjectType::class, [
|
||||
'error_bubbling' => true,
|
||||
])
|
||||
->add('active', ChoiceType::class, [
|
||||
'label' => 'Active',
|
||||
'choices' => [
|
||||
'Yes' => true,
|
||||
'No' => false,
|
||||
],
|
||||
'required' => true,
|
||||
]);
|
||||
|
||||
if ($context instanceof DocGeneratorContextWithAdminFormInterface
|
||||
&& $context->hasAdminForm()) {
|
||||
$sub = $builder
|
||||
->create('options', null, ['compound' => true])
|
||||
->addModelTransformer(new CallbackTransformer(
|
||||
static function (array $data) use ($context) {
|
||||
return $context->adminFormTransform($data);
|
||||
},
|
||||
static function (array $data) use ($context) {
|
||||
return $context->adminFormReverseTransform($data);
|
||||
}
|
||||
));
|
||||
$context->buildAdminForm($sub);
|
||||
$builder->add($sub);
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
|
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\GeneratorDriver;
|
||||
|
||||
interface DriverInterface
|
||||
{
|
||||
/**
|
||||
* @param resource $template
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function generateFromResource($template, string $resourceType, array $data, ?string $templateName = null);
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\GeneratorDriver\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* A exception to throw when there are syntax errors in the
|
||||
* template file.
|
||||
*/
|
||||
class TemplateException extends RuntimeException
|
||||
{
|
||||
private array $errors;
|
||||
|
||||
public function __construct(array $errors, $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct('Error while generating document from template', $code, $previous);
|
||||
$this->errors = $errors;
|
||||
}
|
||||
|
||||
public function getErrors(): array
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\GeneratorDriver;
|
||||
|
||||
use Chill\DocGeneratorBundle\GeneratorDriver\Exception\TemplateException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
|
||||
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
class RelatorioDriver implements DriverInterface
|
||||
{
|
||||
private LoggerInterface $logger;
|
||||
|
||||
private HttpClientInterface $relatorioClient;
|
||||
|
||||
private string $url;
|
||||
|
||||
public function __construct(
|
||||
HttpClientInterface $relatorioClient,
|
||||
ParameterBagInterface $parameterBag,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->relatorioClient = $relatorioClient;
|
||||
$this->logger = $logger;
|
||||
$this->url = $parameterBag->get('chill_doc_generator')['driver']['relatorio']['url'];
|
||||
}
|
||||
|
||||
public function generateFromResource($template, string $resourceType, array $data, ?string $templateName = null)
|
||||
{
|
||||
$formFields = [
|
||||
'variables' => json_encode($data),
|
||||
'template' => new DataPart($template, $templateName ?? uniqid('template_'), $resourceType),
|
||||
];
|
||||
$form = new FormDataPart($formFields);
|
||||
|
||||
try {
|
||||
$response = $this->relatorioClient->request('POST', $this->url, [
|
||||
'headers' => $form->getPreparedHeaders()->toArray(),
|
||||
'body' => $form->bodyToIterable(),
|
||||
]);
|
||||
|
||||
return $response->toStream();
|
||||
} catch (HttpExceptionInterface $e) {
|
||||
$content = $e->getResponse()->getContent(false);
|
||||
|
||||
if (400 === $e->getResponse()->getStatusCode()) {
|
||||
$content = json_decode($content, true);
|
||||
$this->logger->error('relatorio: template error', [
|
||||
'error' => $content['message'] ?? '_not defined',
|
||||
]);
|
||||
|
||||
throw new TemplateException([$content['message']]);
|
||||
}
|
||||
|
||||
$this->logger->error('relatorio: error while generating document', [
|
||||
'msg' => $e->getMessage(),
|
||||
'response' => $e->getResponse()->getStatusCode(),
|
||||
'content' => $content,
|
||||
]);
|
||||
|
||||
throw $e;
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
$this->logger->error('relatorio: transport exception', [
|
||||
'msg' => $e->getMessage(),
|
||||
'e' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
throw $e;
|
||||
} catch (DecodingExceptionInterface $e) {
|
||||
$this->logger->error('relatorio: could not decode response', [
|
||||
'msg' => $e->getMessage(),
|
||||
'e' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
50
src/Bundle/ChillDocGeneratorBundle/Menu/AdminMenuBuilder.php
Normal file
50
src/Bundle/ChillDocGeneratorBundle/Menu/AdminMenuBuilder.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Menu;
|
||||
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use function in_array;
|
||||
|
||||
class AdminMenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
private Security $security;
|
||||
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator, Security $security)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
$this->security = $security;
|
||||
}
|
||||
|
||||
public function buildMenu($menuId, MenuItem $menu, array $parameters)
|
||||
{
|
||||
if ($this->security->isGranted('ROLE_ADMIN')) {
|
||||
if (in_array($menuId, ['admin_index', 'admin_section'], true)) {
|
||||
$menu->addChild($this->translator->trans('docgen.Document generation'), [
|
||||
'route' => 'chill_crud_docgen_template_index',
|
||||
])->setExtras([
|
||||
'order' => 350,
|
||||
'explain' => 'docgen.Manage templates and document generation',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMenuIds(): array
|
||||
{
|
||||
return ['admin_index', 'admin_section', 'docgen_admin'];
|
||||
}
|
||||
}
|
@@ -25,6 +25,18 @@ final class DocGeneratorTemplateRepository implements ObjectRepository
|
||||
$this->repository = $entityManager->getRepository(DocGeneratorTemplate::class);
|
||||
}
|
||||
|
||||
public function countByEntity(string $entity): int
|
||||
{
|
||||
$builder = $this->repository->createQueryBuilder('t');
|
||||
|
||||
$builder
|
||||
->select('count(t)')
|
||||
->where('t.entity LIKE :entity')
|
||||
->setParameter('entity', addslashes($entity));
|
||||
|
||||
return $builder->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function find($id, $lockMode = null, $lockVersion = null): ?DocGeneratorTemplate
|
||||
{
|
||||
return $this->repository->find($id, $lockMode, $lockVersion);
|
||||
@@ -49,15 +61,22 @@ final class DocGeneratorTemplateRepository implements ObjectRepository
|
||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
public function findByEntity($entity)
|
||||
/**
|
||||
* @return array|DocGeneratorTemplate[]
|
||||
*/
|
||||
public function findByEntity(string $entity, ?int $start = 0, ?int $limit = 50): array
|
||||
{
|
||||
$builder = $this->repository->createQueryBuilder('t');
|
||||
|
||||
$builder
|
||||
->where('t.entities LIKE :entity')
|
||||
->setParameter('entity', '%' . addslashes($entity) . '%');
|
||||
->where('t.entity LIKE :entity')
|
||||
->setParameter('entity', addslashes($entity));
|
||||
|
||||
return $builder->getQuery()->execute();
|
||||
return $builder
|
||||
->getQuery()
|
||||
->setFirstResult($start)
|
||||
->setMaxResults($limit)
|
||||
->getResult();
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria, ?array $orderBy = null): ?DocGeneratorTemplate
|
||||
|
@@ -0,0 +1,10 @@
|
||||
import { fetchResults } from "ChillMainAssets/lib/api/apiMethods.js";
|
||||
|
||||
const fetchTemplates = (entityClass) => {
|
||||
let fqdnEntityClass = encodeURI(entityClass);
|
||||
return fetchResults(`/api/1.0/docgen/templates/by-entity/${fqdnEntityClass}`);
|
||||
}
|
||||
|
||||
export {
|
||||
fetchTemplates
|
||||
};
|
@@ -0,0 +1,27 @@
|
||||
import {createApp} from 'vue';
|
||||
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
|
||||
import {fetchTemplates} from 'ChillDocGeneratorAssets/api/pickTemplate.js';
|
||||
import {_createI18n} from 'ChillMainAssets/vuejs/_js/i18n';
|
||||
|
||||
const i18n = _createI18n({});
|
||||
|
||||
document.querySelectorAll('div[data-docgen-template-picker]').forEach(el => {
|
||||
fetchTemplates(el.dataset.entityClass).then(templates => {
|
||||
let
|
||||
picker = {
|
||||
template: '<pick-template :templates="this.templates" :entityId="this.entityId"></pick-template>',
|
||||
components: {
|
||||
PickTemplate,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
templates: templates,
|
||||
entityId: el.dataset.entityId,
|
||||
}
|
||||
},
|
||||
}
|
||||
;
|
||||
createApp(picker).use(i18n).mount(el);
|
||||
})
|
||||
|
||||
});
|
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="templates.length > 0">
|
||||
<slot name="title">
|
||||
<h2>{{ $t('generate_document')}}</h2>
|
||||
</slot>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<slot name="label">
|
||||
<label>{{ $t('select_a_template') }}</label>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="input-group mb-3">
|
||||
<select class="form-select" v-model="template">
|
||||
<option disabled selected value="">{{ $t('choose_a_template') }}</option>
|
||||
<template v-for="t in templates">
|
||||
<option v-bind:value="t.id">{{ t.name.fr }}</option>
|
||||
</template>
|
||||
</select>
|
||||
<button v-if="canGenerate" class="btn btn-update btn-sm change-icon" type="button" @click="generateDocument"><i class="fa fa-fw fa-cog"></i></button>
|
||||
<button v-else class="btn btn-update btn-sm change-icon" type="button" disabled ><i class="fa fa-fw fa-cog"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="hasDescription">
|
||||
<div class="col-md-8 align-self-end">
|
||||
<p>{{ getDescription }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: "PickTemplate",
|
||||
props: {
|
||||
entityId: [String, Number],
|
||||
entityClass: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
templates: {
|
||||
required: true,
|
||||
},
|
||||
// beforeMove execute "something" before
|
||||
beforeMove: {
|
||||
type: Function,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
template: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
canGenerate() {
|
||||
return this.template != null;
|
||||
},
|
||||
hasDescription() {
|
||||
if (this.template == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
getDescription() {
|
||||
return this.templates.find(t => t.id === this.template).description || '';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
generateDocument() {
|
||||
console.log('generateDocument');
|
||||
console.log('beforeMove', this.beforeMove);
|
||||
if (this.beforeMove != null) {
|
||||
console.log('execute before move');
|
||||
let r = this.beforeMove();
|
||||
if (r instanceof Promise) {
|
||||
r.then((obj) => this.goToGenerate(obj));
|
||||
} else {
|
||||
this.goToGenerate();
|
||||
}
|
||||
} else {
|
||||
this.goToGenerate();
|
||||
}
|
||||
},
|
||||
goToGenerate(obj) {
|
||||
console.log('goToGenerate');
|
||||
console.log('obj', obj);
|
||||
console.log('entityId', this.entityId);
|
||||
let
|
||||
realId = this.entityId,
|
||||
realEntityClass = this.entityClass
|
||||
;
|
||||
|
||||
if (obj !== undefined) {
|
||||
if (obj.entityId !== undefined) {
|
||||
realId = obj.entityId;
|
||||
}
|
||||
if (obj.entityClass !== undefined) {
|
||||
realEntityClass = obj.entityClass;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('realId', realId);
|
||||
console.log('realEntityClass', realEntityClass);
|
||||
|
||||
const
|
||||
entityId = encodeURI(realId),
|
||||
returnPath = encodeURIComponent(window.location.pathname + window.location.search + window.location.hash),
|
||||
fqdnEntityClass = encodeURI(realEntityClass),
|
||||
url = `/fr/doc/gen/generate/from/${this.template}/for/${fqdnEntityClass}/${entityId}?returnPath=${returnPath}`
|
||||
;
|
||||
|
||||
console.log('I will generate your doc at', url);
|
||||
window.location.assign(url);
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
messages: {
|
||||
fr: {
|
||||
generate_document: 'Générer un document',
|
||||
select_a_template: 'Choisir un gabarit',
|
||||
choose_a_template: 'Choisir',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@@ -1,12 +1,25 @@
|
||||
{% extends '@ChillPerson/Admin/layout.html.twig' %}
|
||||
{% extends '@ChillDocGenerator/Admin/layout.html.twig' %}
|
||||
|
||||
{% block title %}
|
||||
{% include('@ChillMain/CRUD/_edit_title.html.twig') %}
|
||||
{% endblock %}
|
||||
{% block title 'docgen.Edit template'|trans %}
|
||||
|
||||
{% block layout_wvm_content %}
|
||||
{% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
|
||||
|
||||
{% block crud_content_header %}
|
||||
<h1>{{ 'docgen.Edit template'|trans }}</h1>
|
||||
<h2>{{ 'docgen.With context %name%'|trans({'%name%': context.name|trans }) }}</h2>
|
||||
{% endblock crud_content_header %}
|
||||
|
||||
{% block content_form_actions_view %}{% endblock %}
|
||||
{% block content_form_actions_save_and_show %}{% endblock %}
|
||||
{% endembed %}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_script_tags('mod_async_upload') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||
{% endblock %}
|
||||
|
@@ -1,19 +1,40 @@
|
||||
{% extends '@ChillPerson/Admin/layout.html.twig' %}
|
||||
{% extends '@ChillDocGenerator/Admin/layout.html.twig' %}
|
||||
|
||||
{% block layout_wvm_content %}
|
||||
{% embed '@ChillMain/CRUD/_index.html.twig' %}
|
||||
{% block table_entities_thead_tr %}
|
||||
<th></th>
|
||||
<th>{{ 'Title'|trans }}</th>
|
||||
<th>{{ 'File'|trans }}</th>
|
||||
<th>{{ 'docgen.Context'|trans }}</th>
|
||||
<th>{{ 'docgen.test generate'|trans }}</th>
|
||||
<th>{{ 'Edit'|trans }}</th>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_entities_tbody %}
|
||||
{% for entity in entities %}
|
||||
<tr>
|
||||
<td>{{ entity.id }}</td>
|
||||
<td>{{ entity.name | localize_translatable_string }}</td>
|
||||
<td>{{ entity.file }}</td>
|
||||
<td>{{ entity.name|localize_translatable_string}}</td>
|
||||
<td>{{ contextManager.getContextByKey(entity.context).name|trans }}</td>
|
||||
<td>
|
||||
<form method="get" action="{{ path('chill_docgenerator_test_generate_redirect') }}">
|
||||
<input type="hidden" name="returnPath" value="{{ app.request.query.get('returnPath', '/')|e('html_attr') }}" />
|
||||
<input type="hidden" name="template" value="{{ entity.id|e('html_attr') }}" />
|
||||
<input type="hidden" name="entityClassName" value="{{ contextManager.getContextByKey(entity.context).entityClass|e('html_attr') }}" />
|
||||
<input type="text" name="entityId" />
|
||||
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<button type="submit" class="btn btn-mini btn-neutral">{{ 'docgen.test generate'|trans }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ chill_path_add_return_path('chill_crud_docgen_template_edit', { 'id': entity.id }) }}" class="btn btn-edit">
|
||||
{{ 'Edit'|trans }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
@@ -1,11 +1,25 @@
|
||||
{% extends '@ChillPerson/Admin/layout.html.twig' %}
|
||||
{% extends '@ChillDocGenerator/Admin/layout.html.twig' %}
|
||||
|
||||
{% block title %}
|
||||
{% include('@ChillMain/CRUD/_new_title.html.twig') %}
|
||||
{% endblock %}
|
||||
{% block title 'docgen.New template'|trans %}
|
||||
|
||||
{% block layout_wvm_content %}
|
||||
{% embed '@ChillMain/CRUD/_new_content.html.twig' %}
|
||||
|
||||
{% block crud_content_header %}
|
||||
<h1>{{ 'docgen.New template'|trans }}</h1>
|
||||
<h2>{{ 'docgen.With context %name%'|trans({'%name%': context.name|trans }) }}</h2>
|
||||
{% endblock crud_content_header %}
|
||||
|
||||
{% block content_form_actions_save_and_show %}{% endblock %}
|
||||
|
||||
{% endembed %}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_script_tags('mod_async_upload') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||
{% endblock %}
|
||||
|
@@ -0,0 +1,27 @@
|
||||
{% extends '@ChillDocGenerator/Admin/layout.html.twig' %}
|
||||
|
||||
{% block title 'docgen.Pick template context'|trans %}
|
||||
|
||||
{% block layout_wvm_content %}
|
||||
<div class="col-md-10 col-xxl">
|
||||
|
||||
<h1>{{ block('title') }}</h1>
|
||||
<div class="container">
|
||||
{% for key, context in contexts %}
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<a
|
||||
href="{{ path('chill_crud_docgen_template_new', { 'context': key }) }}"
|
||||
class="btn btn-outline-chill-green-dark">
|
||||
{{ context.name|trans }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
{{ context.description|trans|nl2br }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@@ -0,0 +1,4 @@
|
||||
{% extends '@ChillPerson/Admin/layout.html.twig' %}
|
||||
|
||||
{% block vertical_menu_content %}
|
||||
{% endblock %}
|
@@ -0,0 +1,22 @@
|
||||
{% extends 'ChillMainBundle::layout.html.twig' %}
|
||||
|
||||
{% block title 'docgen.Generate a document'|trans %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-md-10 col-xxl">
|
||||
<h1>{{ block('title') }}</h1>
|
||||
<h2>{{ template.name|localize_translatable_string }}</h2>
|
||||
|
||||
{{ form_start(form, { 'attr': { 'id': 'generate_doc_form' }}) }}
|
||||
{{ form(form) }}
|
||||
{{ form_end(form) }}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li>
|
||||
<button type="submit" class="btn btn-edit change-icon" form="generate_doc_form">
|
||||
<i class="fa fa-cog"></i> {{ 'docgen.Generate'|trans }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock %}
|
@@ -16,16 +16,27 @@ use function array_merge;
|
||||
|
||||
class NormalizeNullValueHelper
|
||||
{
|
||||
private ?string $discriminatorType = null;
|
||||
|
||||
private ?string $discriminatorValue = null;
|
||||
|
||||
private NormalizerInterface $normalizer;
|
||||
|
||||
public function __construct(NormalizerInterface $normalizer)
|
||||
public function __construct(NormalizerInterface $normalizer, $discriminatorType = null, $discriminatorValue = null)
|
||||
{
|
||||
$this->normalizer = $normalizer;
|
||||
$this->discriminatorType = $discriminatorType;
|
||||
$this->discriminatorValue = $discriminatorValue;
|
||||
}
|
||||
|
||||
public function normalize(array $attributes, string $format = 'docgen', ?array $context = [])
|
||||
{
|
||||
$data = [];
|
||||
$data['isNull'] = true;
|
||||
|
||||
if (null !== $this->discriminatorType) {
|
||||
$data[$this->discriminatorType] = $this->discriminatorValue;
|
||||
}
|
||||
|
||||
foreach ($attributes as $key => $class) {
|
||||
if (is_numeric($key)) {
|
||||
|
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\Serializer\Normalizer;
|
||||
|
||||
use ArrayObject;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
||||
|
||||
/**
|
||||
* Normalize a collection for docgen format.
|
||||
*/
|
||||
class CollectionDocGenNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface
|
||||
{
|
||||
use NormalizerAwareTrait;
|
||||
|
||||
/**
|
||||
* @param Collection $object
|
||||
*
|
||||
* @return array|ArrayObject|bool|float|int|string|void|null
|
||||
*/
|
||||
public function normalize($object, ?string $format = null, array $context = [])
|
||||
{
|
||||
$data = [];
|
||||
|
||||
if (null === $object || 0 === $object->count()) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ($object->getIterator() as $item) {
|
||||
$data[] = $this->normalizer->normalize($item, $format, $context);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function supportsNormalization($data, ?string $format = null, array $context = [])
|
||||
{
|
||||
if ('docgen' !== $format) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $data instanceof Collection
|
||||
|| (null === $data && Collection::class === ($context['docgen:expects'] ?? null));
|
||||
}
|
||||
}
|
@@ -12,7 +12,9 @@ declare(strict_types=1);
|
||||
namespace Chill\DocGeneratorBundle\Serializer\Normalizer;
|
||||
|
||||
use Chill\DocGeneratorBundle\Serializer\Helper\NormalizeNullValueHelper;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
use ReflectionClass;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
||||
use Symfony\Component\Serializer\Exception\ExceptionInterface;
|
||||
@@ -41,18 +43,32 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
|
||||
|
||||
private PropertyAccessor $propertyAccess;
|
||||
|
||||
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory)
|
||||
{
|
||||
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||
|
||||
public function __construct(
|
||||
ClassMetadataFactoryInterface $classMetadataFactory,
|
||||
TranslatableStringHelperInterface $translatableStringHelper
|
||||
) {
|
||||
$this->classMetadataFactory = $classMetadataFactory;
|
||||
$this->propertyAccess = PropertyAccess::createPropertyAccessor();
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
}
|
||||
|
||||
public function normalize($object, ?string $format = null, array $context = [])
|
||||
{
|
||||
$classMetadataKey = $object ?? $context['docgen:expects'];
|
||||
$classMetadataKey = $object ?? $context['docgen:expects'] ?? null;
|
||||
|
||||
if (null === $classMetadataKey) {
|
||||
throw new RuntimeException('Could not determine the metadata for this object. Either provide a non-null object, or a "docgen:expects" key in the context');
|
||||
}
|
||||
|
||||
if (!$this->classMetadataFactory->hasMetadataFor($classMetadataKey)) {
|
||||
throw new LogicException(sprintf('This object does not have metadata: %s. Add groups on this entity to allow to serialize with the format %s and groups %s', is_object($object) ? get_class($object) : $context['docgen:expects'], $format, implode(', ', $context['groups'])));
|
||||
throw new LogicException(sprintf(
|
||||
'This object does not have metadata: %s. Add groups on this entity to allow to serialize with the format %s and groups %s',
|
||||
is_object($object) ? get_class($object) : '(todo' /*$context['docgen:expects'],*/ ,
|
||||
$format,
|
||||
implode(', ', ($context['groups'] ?? []))
|
||||
));
|
||||
}
|
||||
|
||||
$metadata = $this->classMetadataFactory->getMetadataFor($classMetadataKey);
|
||||
@@ -86,19 +102,56 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
|
||||
|
||||
private function getExpectedType(AttributeMetadata $attribute, ReflectionClass $reflection): string
|
||||
{
|
||||
// we have to get the expected content
|
||||
if ($reflection->hasProperty($attribute->getName())) {
|
||||
$type = $reflection->getProperty($attribute->getName())->getType();
|
||||
} elseif ($reflection->hasMethod($attribute->getName())) {
|
||||
$type = $reflection->getMethod($attribute->getName())->getReturnType();
|
||||
} else {
|
||||
throw new \LogicException(sprintf(
|
||||
'Could not determine how the content is determined for the attribute %s. Add attribute property only on property or method',
|
||||
$attribute->getName()
|
||||
));
|
||||
}
|
||||
$type = null;
|
||||
|
||||
if (null === $type) {
|
||||
do {
|
||||
// we have to get the expected content
|
||||
if ($reflection->hasProperty($attribute->getName())) {
|
||||
if (!$reflection->getProperty($attribute->getName())->hasType()) {
|
||||
throw new \LogicException(sprintf(
|
||||
'Could not determine how the content is determined for the attribute %s on class %s. Add a type on this property',
|
||||
$attribute->getName(),
|
||||
$reflection->getName()
|
||||
));
|
||||
}
|
||||
|
||||
$type = $reflection->getProperty($attribute->getName())->getType();
|
||||
} elseif ($reflection->hasMethod($method = 'get' . ucfirst($attribute->getName()))) {
|
||||
if (!$reflection->getMethod($method)->hasReturnType()) {
|
||||
throw new \LogicException(sprintf(
|
||||
'Could not determine how the content is determined for the attribute %s on class %s. Add a return type on the method',
|
||||
$attribute->getName(),
|
||||
$reflection->getName()
|
||||
));
|
||||
}
|
||||
|
||||
$type = $reflection->getMethod($method)->getReturnType();
|
||||
} elseif ($reflection->hasMethod($method = 'is' . ucfirst($attribute->getName()))) {
|
||||
if (!$reflection->getMethod($method)->hasReturnType()) {
|
||||
throw new \LogicException(sprintf(
|
||||
'Could not determine how the content is determined for the attribute %s on class %s. Add a return type on the method',
|
||||
$attribute->getName(),
|
||||
$reflection->getName()
|
||||
));
|
||||
}
|
||||
|
||||
$type = $reflection->getMethod($method)->getReturnType();
|
||||
} elseif ($reflection->hasMethod($attribute->getName())) {
|
||||
if (!$reflection->getMethod($attribute->getName())->hasReturnType()) {
|
||||
throw new \LogicException(sprintf(
|
||||
'Could not determine how the content is determined for the attribute %s on class %s. Add a return type on the method',
|
||||
$attribute->getName(),
|
||||
$reflection->getName()
|
||||
));
|
||||
}
|
||||
|
||||
$type = $reflection->getMethod($attribute->getName())->getReturnType();
|
||||
} else {
|
||||
$reflection = $reflection->getParentClass();
|
||||
}
|
||||
} while (null === $type && $reflection instanceof ReflectionClass);
|
||||
|
||||
if (null === $type ?? null) {
|
||||
throw new \LogicException(sprintf(
|
||||
'Could not determine the type for this attribute: %s. Add a return type to the method or property declaration',
|
||||
$attribute->getName()
|
||||
@@ -115,12 +168,32 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
|
||||
{
|
||||
$keys = [];
|
||||
|
||||
// add a discriminator
|
||||
if (null !== $discriminator = $metadata->getClassDiscriminatorMapping()) {
|
||||
$typeKey = $discriminator->getTypeProperty();
|
||||
$typeValue = null;
|
||||
|
||||
foreach ($discriminator->getTypesMapping() as $type => $typeClass) {
|
||||
if ($typeClass === $context['docgen:expects']) {
|
||||
$typeValue = $type;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $typeValue) {
|
||||
$typeKey = null;
|
||||
}
|
||||
} else {
|
||||
$typeKey = $typeValue = null;
|
||||
}
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
$key = $attribute->getSerializedName() ?? $attribute->getName();
|
||||
$keys[$key] = $this->getExpectedType($attribute, $metadata->getReflectionClass());
|
||||
}
|
||||
|
||||
$normalizer = new NormalizeNullValueHelper($this->normalizer);
|
||||
$normalizer = new NormalizeNullValueHelper($this->normalizer, $typeKey, $typeValue);
|
||||
|
||||
return $normalizer->normalize($keys, $format, $context);
|
||||
}
|
||||
@@ -134,11 +207,19 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
|
||||
|
||||
switch ($type) {
|
||||
case 'array':
|
||||
if (in_array('is-translatable', $attribute->getNormalizationContextForGroups(['docgen:read']), true)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return [];
|
||||
|
||||
case 'bool':
|
||||
case 'double':
|
||||
case 'float':
|
||||
case 'int':
|
||||
case 'resource':
|
||||
return null;
|
||||
|
||||
case 'string':
|
||||
return '';
|
||||
|
||||
@@ -166,14 +247,37 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
|
||||
private function normalizeObject($object, $format, array $context, array $expectedGroups, ClassMetadata $metadata, array $attributes)
|
||||
{
|
||||
$data = [];
|
||||
$data['isNull'] = false;
|
||||
$reflection = $metadata->getReflectionClass();
|
||||
|
||||
// add a discriminator
|
||||
if (null !== $discriminator = $metadata->getClassDiscriminatorMapping()) {
|
||||
$data[$discriminator->getTypeProperty()] = $discriminator->getMappedObjectType($object);
|
||||
}
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
/** @var AttributeMetadata $attribute */
|
||||
$value = $this->propertyAccess->getValue($object, $attribute->getName());
|
||||
$key = $attribute->getSerializedName() ?? $attribute->getName();
|
||||
$isTranslatable = $attribute->getNormalizationContextForGroups(
|
||||
is_array($context['groups']) ? $context['groups'] : [$context['groups']]
|
||||
)['is-translatable'] ?? false;
|
||||
|
||||
if (is_object($value)) {
|
||||
if ($isTranslatable) {
|
||||
$data[$key] = $this->translatableStringHelper
|
||||
->localize($value);
|
||||
} elseif (is_iterable($value)) {
|
||||
$arr = [];
|
||||
|
||||
foreach ($value as $k => $v) {
|
||||
$arr[$k] =
|
||||
$this->normalizer->normalize($v, $format, array_merge(
|
||||
$context,
|
||||
$attribute->getNormalizationContextForGroups($expectedGroups)
|
||||
));
|
||||
}
|
||||
$data[$key] = $arr;
|
||||
} elseif (is_object($value)) {
|
||||
$data[$key] =
|
||||
$this->normalizer->normalize($value, $format, array_merge(
|
||||
$context,
|
||||
@@ -182,7 +286,7 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
|
||||
} elseif (null === $value) {
|
||||
$data[$key] = $this->normalizeNullOutputValue($format, $context, $attribute, $reflection);
|
||||
} else {
|
||||
$data[$key] = (string) $value;
|
||||
$data[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,4 +3,6 @@ module.exports = function(encore, entries) {
|
||||
encore.addAliases({
|
||||
ChillDocGeneratorAssets: __dirname + '/Resources/public'
|
||||
});
|
||||
|
||||
encore.addEntry('mod_docgen_picktemplate', __dirname + '/Resources/public/module/PickTemplate/index.js');
|
||||
};
|
||||
|
@@ -9,9 +9,38 @@ services:
|
||||
autoconfigure: true
|
||||
resource: '../Repository/'
|
||||
|
||||
Chill\DocGeneratorBundle\Menu\:
|
||||
autoconfigure: true
|
||||
autowire: true
|
||||
resource: '../Menu/'
|
||||
|
||||
Chill\DocGeneratorBundle\Serializer\Normalizer\:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
resource: '../Serializer/Normalizer/'
|
||||
tags:
|
||||
- { name: 'serializer.normalizer', priority: -152 }
|
||||
Chill\DocGeneratorBundle\Serializer\Normalizer\CollectionDocGenNormalizer:
|
||||
tags:
|
||||
- { name: 'serializer.normalizer', priority: -126 }
|
||||
|
||||
Chill\DocGeneratorBundle\Controller\:
|
||||
resource: "../Controller"
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\DocGeneratorBundle\Form\:
|
||||
resource: "../Form/"
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\DocGeneratorBundle\GeneratorDriver\:
|
||||
resource: "../GeneratorDriver/"
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\DocGeneratorBundle\Driver\RelatorioDriver: '@Chill\DocGeneratorBundle\Driver\DriverInterface'
|
||||
|
||||
Chill\DocGeneratorBundle\Context\ContextManager:
|
||||
arguments:
|
||||
$contexts: !tagged_iterator { tag: chill_docgen.context, default_index_method: getKey }
|
||||
|
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\DocGenerator;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Using DocStore objects inside the DocGenTemplate.
|
||||
*/
|
||||
final class Version20211103111010 extends AbstractMigration
|
||||
{
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_docgen_template DROP CONSTRAINT FK_49A347E893CB796C');
|
||||
$this->addSql('DROP INDEX IDX_49A347E893CB796C');
|
||||
$this->addSql('ALTER TABLE chill_docgen_template ADD file VARCHAR(255) NOT NULL');
|
||||
$this->addSql('ALTER TABLE chill_docgen_template DROP file_id');
|
||||
$this->addSql('ALTER TABLE chill_docgen_template ALTER entities DROP NOT NULL');
|
||||
$this->addSql('ALTER TABLE chill_docgen_template ALTER context DROP NOT NULL');
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Using DocStore objects inside the DocGenTemplate';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_docgen_template ADD file_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE chill_docgen_template DROP file');
|
||||
$this->addSql('ALTER TABLE chill_docgen_template ALTER entities SET NOT NULL');
|
||||
$this->addSql('ALTER TABLE chill_docgen_template ALTER context SET NOT NULL');
|
||||
$this->addSql('ALTER TABLE chill_docgen_template ADD CONSTRAINT FK_49A347E893CB796C FOREIGN KEY (file_id) REFERENCES chill_doc.stored_object (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('CREATE INDEX IDX_49A347E893CB796C ON chill_docgen_template (file_id)');
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\DocGenerator;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20211201191757 extends AbstractMigration
|
||||
{
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_docgen_template DROP active');
|
||||
$this->addSql('ALTER TABLE chill_docgen_template DROP entity');
|
||||
$this->addSql('ALTER TABLE chill_docgen_template DROP template_options');
|
||||
$this->addSql('ALTER TABLE chill_docgen_template ADD entities TEXT');
|
||||
$this->addSql('COMMENT ON COLUMN chill_docgen_template.entities IS \'(DC2Type:simple_array)\'');
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add options, active and link to entity in docgen_template';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_docgen_template ADD active BOOLEAN DEFAULT true NOT NULL');
|
||||
$this->addSql('ALTER TABLE chill_docgen_template ADD entity VARCHAR(255) NOT NULL DEFAULT \'\'');
|
||||
$this->addSql('ALTER TABLE chill_docgen_template ADD template_options JSONB NOT NULL DEFAULT \'[]\'::jsonb ');
|
||||
$this->addSql('COMMENT ON COLUMN chill_docgen_template.template_options IS \'(DC2Type:json)\'');
|
||||
$this->addSql('ALTER TABLE chill_docgen_template DROP entities');
|
||||
}
|
||||
}
|
@@ -11,7 +11,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocGeneratorBundle\tests\Serializer\Normalizer;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||
@@ -35,25 +35,36 @@ final class DocGenObjectNormalizerTest extends KernelTestCase
|
||||
|
||||
public function testNormalizationBasic()
|
||||
{
|
||||
$user = new User();
|
||||
$user->setUsername('User Test');
|
||||
$user->setMainCenter($center = new Center());
|
||||
$center->setName('test');
|
||||
$scope = new Scope();
|
||||
$scope->setName(['fr' => 'scope']);
|
||||
|
||||
$normalized = $this->normalizer->normalize($user, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read']]);
|
||||
$normalized = $this->normalizer->normalize($scope, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Scope::class]);
|
||||
$expected = [
|
||||
'label' => 'User Test',
|
||||
'email' => '',
|
||||
'mainCenter' => [
|
||||
'name' => 'test',
|
||||
],
|
||||
'id' => null,
|
||||
'name' => 'scope',
|
||||
'type' => 'scope',
|
||||
'isNull' => false,
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $normalized, 'test normalization fo an user');
|
||||
$this->assertEquals($expected, $normalized, 'test normalization fo a scope');
|
||||
}
|
||||
|
||||
public function testNormalizeNull()
|
||||
{
|
||||
$actual = $this->normalizer->normalize(null, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Scope::class]);
|
||||
$expected = [
|
||||
'id' => '',
|
||||
'name' => '',
|
||||
'type' => 'scope',
|
||||
'isNull' => true,
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $actual, 'test normalization for a null scope');
|
||||
}
|
||||
|
||||
public function testNormalizeNullObjectWithObjectEmbedded()
|
||||
{
|
||||
$this->markTestIncomplete('test to implement');
|
||||
$normalized = $this->normalizer->normalize(null, 'docgen', [
|
||||
AbstractNormalizer::GROUPS => ['docgen:read'],
|
||||
'docgen:expects' => User::class,
|
||||
@@ -62,7 +73,7 @@ final class DocGenObjectNormalizerTest extends KernelTestCase
|
||||
$expected = [
|
||||
'label' => '',
|
||||
'email' => '',
|
||||
'mainCenter' => [
|
||||
'main_center' => [
|
||||
'name' => '',
|
||||
],
|
||||
];
|
||||
@@ -72,10 +83,11 @@ final class DocGenObjectNormalizerTest extends KernelTestCase
|
||||
|
||||
public function testNormalizeWithNullValueEmbedded()
|
||||
{
|
||||
$this->markTestIncomplete('test to write');
|
||||
$user = new User();
|
||||
$user->setUsername('User Test');
|
||||
|
||||
$normalized = $this->normalizer->normalize($user, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read']]);
|
||||
$normalized = $this->normalizer->normalize($user, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => User::class]);
|
||||
$expected = [
|
||||
'label' => 'User Test',
|
||||
'email' => '',
|
||||
|
@@ -0,0 +1,19 @@
|
||||
docgen:
|
||||
Generate a document: Génerer un document
|
||||
Generate: Génerer
|
||||
Document generation: Génération de documents
|
||||
Manage templates and document generation: Gestion des documents générés et de leurs gabarits
|
||||
Pick template context: Choisir un contexte
|
||||
Context: Contexte
|
||||
New template: Nouveau gabarit
|
||||
Edit template: Modifier gabarit
|
||||
With context: 'Avec le contexte :'
|
||||
|
||||
|
||||
crud:
|
||||
docgen_template:
|
||||
index:
|
||||
title: Génération de documents
|
||||
add_new: Créer
|
||||
|
||||
|
||||
|
@@ -15,7 +15,7 @@ use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Table("chill_doc.document_category")
|
||||
* @ORM\Entity(repositoryClass="Chill\DocStoreBundle\EntityRepository\DocumentCategoryRepository")
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class DocumentCategory
|
||||
{
|
||||
|
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocStoreBundle\EntityRepository;
|
||||
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* Get an available idInsideBUndle.
|
||||
*/
|
||||
class DocumentCategoryRepository extends EntityRepository
|
||||
{
|
||||
public function nextIdInsideBundle()
|
||||
{
|
||||
$array_res = $this->getEntityManager()
|
||||
->createQuery(
|
||||
'SELECT MAX(c.idInsideBundle) + 1 FROM ChillDocStoreBundle:DocumentCategory c'
|
||||
)
|
||||
->getSingleResult();
|
||||
|
||||
return reset($array_res);
|
||||
}
|
||||
}
|
@@ -11,8 +11,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocStoreBundle\Form;
|
||||
|
||||
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
|
||||
use Chill\DocStoreBundle\Entity\Document;
|
||||
use Chill\DocStoreBundle\Entity\PersonDocument;
|
||||
use Chill\DocStoreBundle\Entity\DocumentCategory;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Form\Type\ChillDateType;
|
||||
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
||||
@@ -70,11 +71,11 @@ class AccompanyingCourseDocumentType extends AbstractType
|
||||
//TODO : adapt to using AccompanyingCourseDocument categories. Currently there are none...
|
||||
->add('category', EntityType::class, [
|
||||
'placeholder' => 'Choose a document category',
|
||||
'class' => 'ChillDocStoreBundle:DocumentCategory',
|
||||
'class' => DocumentCategory::class,
|
||||
'query_builder' => static function (EntityRepository $er) {
|
||||
return $er->createQueryBuilder('c')
|
||||
->where('c.documentClass = :docClass')
|
||||
->setParameter('docClass', PersonDocument::class);
|
||||
->setParameter('docClass', AccompanyingCourseDocument::class);
|
||||
},
|
||||
'choice_label' => function ($entity = null) {
|
||||
return $entity ? $this->translatableStringHelper->localize($entity->getName()) : '';
|
||||
|
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocStoreBundle\Repository;
|
||||
|
||||
use Chill\DocStoreBundle\Entity\DocumentCategory;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
/**
|
||||
* Get an available idInsideBUndle.
|
||||
*/
|
||||
class DocumentCategoryRepository implements ObjectRepository
|
||||
{
|
||||
private EntityManagerInterface $em;
|
||||
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->repository = $em->getRepository(DocumentCategory::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $id
|
||||
*/
|
||||
public function find($id): ?DocumentCategory
|
||||
{
|
||||
return $this->repository->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|DocumentCategory[]
|
||||
*/
|
||||
public function findAll(): array
|
||||
{
|
||||
return $this->repository->findAll();
|
||||
}
|
||||
|
||||
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null)
|
||||
{
|
||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria): ?DocumentCategory
|
||||
{
|
||||
return $this->findOneBy($criteria);
|
||||
}
|
||||
|
||||
public function getClassName()
|
||||
{
|
||||
return DocumentCategory::class;
|
||||
}
|
||||
|
||||
public function nextIdInsideBundle()
|
||||
{
|
||||
$array_res = $this->em
|
||||
->createQuery(
|
||||
'SELECT MAX(c.idInsideBundle) + 1 FROM ChillDocStoreBundle:DocumentCategory c'
|
||||
)
|
||||
->getSingleResult();
|
||||
|
||||
return reset($array_res);
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@ var algo = 'AES-CBC';
|
||||
var initializeButtons = (root) => {
|
||||
var
|
||||
buttons = root.querySelectorAll('a[data-download-button]');
|
||||
|
||||
|
||||
for (let i = 0; i < buttons.length; i ++) {
|
||||
initialize(buttons[i]);
|
||||
}
|
||||
@@ -33,9 +33,12 @@ var download = (button) => {
|
||||
fetchError = "Error while fetching file",
|
||||
key, url
|
||||
;
|
||||
|
||||
|
||||
console.log('keyData', keyData);
|
||||
console.log('ivData', ivData);
|
||||
|
||||
button.textContent = labelPreparing;
|
||||
|
||||
|
||||
window.fetch(urlGenerator)
|
||||
.then((r) => {
|
||||
if (r.ok) {
|
||||
@@ -46,26 +49,19 @@ var download = (button) => {
|
||||
})
|
||||
.then(data => {
|
||||
url = data.url;
|
||||
|
||||
return window.crypto.subtle.importKey('jwk', keyData, { name: algo, iv: iv}, false, ['decrypt']);
|
||||
})
|
||||
.catch(e => {
|
||||
console.error("error while importing key");
|
||||
console.error(e);
|
||||
button.appendChild(document.createTextNode(decryptError));
|
||||
|
||||
if (keyData.length > 0) {
|
||||
return window.crypto.subtle.importKey('jwk', keyData, { name: algo, iv: iv}, false, ['decrypt']);
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
})
|
||||
.then(nKey => {
|
||||
key = nKey;
|
||||
|
||||
return window.fetch(url);
|
||||
})
|
||||
.catch(e => {
|
||||
console.error("error while fetching data");
|
||||
console.error(e);
|
||||
button.textContent = "";
|
||||
button.appendChild(document.createTextNode(fetchError));
|
||||
})
|
||||
.then(r => {
|
||||
console.log('r', r);
|
||||
if (r.ok) {
|
||||
return r.arrayBuffer();
|
||||
} else {
|
||||
@@ -73,16 +69,16 @@ var download = (button) => {
|
||||
}
|
||||
})
|
||||
.then(buffer => {
|
||||
return window.crypto.subtle.decrypt({ name: algo, iv: iv }, key, buffer);
|
||||
})
|
||||
.catch(e => {
|
||||
console.error("error while importing key");
|
||||
console.error(e);
|
||||
button.textContent = "";
|
||||
button.appendChild(document.createTextNode(decryptError));
|
||||
console.log('buffer', buffer);
|
||||
if (keyData.length > 0) {
|
||||
return window.crypto.subtle.decrypt({ name: algo, iv: iv }, key, buffer);
|
||||
}
|
||||
|
||||
return Promise.resolve(buffer);
|
||||
})
|
||||
.then(decrypted => {
|
||||
var
|
||||
console.log('decrypted', decrypted);
|
||||
var
|
||||
blob = new Blob([decrypted], { type: mimeType }),
|
||||
url = window.URL.createObjectURL(blob)
|
||||
;
|
||||
|
@@ -41,5 +41,6 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||
{% endblock %}
|
||||
|
@@ -11,6 +11,13 @@
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_script_tags('mod_async_upload') }}
|
||||
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||
{{ encore_entry_link_tags('mod_docgen_picktemplate') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@@ -29,7 +36,7 @@
|
||||
{% for document in documents %}
|
||||
<tr>
|
||||
<td>{{ document.title }}</td>
|
||||
<td>{{ document.category.name|localize_translatable_string }}</td>
|
||||
<td>{% if document.category %}{{ document.category.name|localize_translatable_string }}{% endif %}</td>
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
|
||||
@@ -58,6 +65,8 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div data-docgen-template-picker="data-docgen-template-picker" data-entity-class="Chill\PersonBundle\Entity\AccompanyingPeriod" data-entity-id="{{ accompanyingCourse.id }}"></div>
|
||||
|
||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_CREATE', accompanyingCourse) %}
|
||||
<ul class="record_actions">
|
||||
<li class="create">
|
||||
|
@@ -42,5 +42,6 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||
{% endblock %}
|
||||
|
@@ -14,6 +14,11 @@
|
||||
{{ encore_entry_script_tags('mod_async_upload') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{{ 'Document %title%' | trans({ '%title%': document.title }) }}</h1>
|
||||
@@ -22,8 +27,10 @@
|
||||
<dt>{{ 'Title'|trans }}</dt>
|
||||
<dd>{{ document.title }}</dd>
|
||||
|
||||
<dt>{{ 'Category'|trans }}</dt>
|
||||
<dd>{{ document.category.name|localize_translatable_string }}</dd>
|
||||
{% if document.category is not null %}
|
||||
<dt>{{ 'Category'|trans }}</dt>
|
||||
<dd>{{ document.category.name|localize_translatable_string }}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt>{{ 'Description' | trans }}</dt>
|
||||
<dd>
|
||||
|
@@ -19,7 +19,6 @@ use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
use Symfony\Component\Templating\EngineInterface as TemplatingEngine;
|
||||
use function count;
|
||||
|
||||
@@ -145,7 +144,7 @@ class EventSearch extends AbstractSearch
|
||||
{
|
||||
// add security clauses
|
||||
$reachableCenters = $this->helper
|
||||
->getReachableCenters($this->user, new Role('CHILL_EVENT_SEE'));
|
||||
->getReachableCenters($this->user, 'CHILL_EVENT_SEE');
|
||||
|
||||
if (count($reachableCenters) === 0) {
|
||||
// add a clause to block all events
|
||||
@@ -158,7 +157,7 @@ class EventSearch extends AbstractSearch
|
||||
foreach ($reachableCenters as $center) {
|
||||
$circles = $this->helper->getReachableScopes(
|
||||
$this->user,
|
||||
new Role('CHILL_EVENT_SEE'),
|
||||
'CHILL_EVENT_SEE',
|
||||
$center
|
||||
);
|
||||
$where = $qb->expr()->andX(
|
||||
|
@@ -23,7 +23,6 @@ use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
@@ -188,7 +187,7 @@ class TimelineEventProvider implements TimelineProviderInterface
|
||||
ClassMetadata $metadataPerson,
|
||||
Person $person
|
||||
) {
|
||||
$role = new Role('CHILL_EVENT_SEE');
|
||||
$role = 'CHILL_EVENT_SEE';
|
||||
|
||||
$reachableCenters = $this->helper->getReachableCenters($this->user, $role);
|
||||
$associationMapping = $metadataParticipation->getAssociationMapping('person');
|
||||
|
@@ -12,14 +12,66 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||
use Chill\MainBundle\Entity\PostalCode;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||
use Chill\MainBundle\Repository\AddressReferenceRepository;
|
||||
use Chill\MainBundle\Serializer\Model\Collection;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||
use function trim;
|
||||
|
||||
/**
|
||||
* Class AddressReferenceAPIController.
|
||||
*/
|
||||
class AddressReferenceAPIController extends ApiController
|
||||
final class AddressReferenceAPIController extends ApiController
|
||||
{
|
||||
private AddressReferenceRepository $addressReferenceRepository;
|
||||
|
||||
private PaginatorFactory $paginatorFactory;
|
||||
|
||||
public function __construct(AddressReferenceRepository $addressReferenceRepository, PaginatorFactory $paginatorFactory)
|
||||
{
|
||||
$this->addressReferenceRepository = $addressReferenceRepository;
|
||||
$this->paginatorFactory = $paginatorFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/api/1.0/main/address-reference/by-postal-code/{id}/search.json")
|
||||
*/
|
||||
public function search(PostalCode $postalCode, Request $request): JsonResponse
|
||||
{
|
||||
$this->denyAccessUnlessGranted('ROLE_USER');
|
||||
|
||||
if (!$request->query->has('q')) {
|
||||
throw new BadRequestHttpException('You must supply a "q" parameter');
|
||||
}
|
||||
|
||||
$pattern = $request->query->get('q');
|
||||
|
||||
if ('' === trim($pattern)) {
|
||||
throw new BadRequestHttpException('the search pattern is empty');
|
||||
}
|
||||
|
||||
$nb = $this->addressReferenceRepository->countByPostalCodePattern($postalCode, $pattern);
|
||||
$paginator = $this->paginatorFactory->create($nb);
|
||||
$addresses = $this->addressReferenceRepository->findByPostalCodePattern(
|
||||
$postalCode,
|
||||
$pattern,
|
||||
false,
|
||||
$paginator->getCurrentPageFirstItemNumber(),
|
||||
$paginator->getItemsPerPage()
|
||||
);
|
||||
|
||||
return $this->json(
|
||||
new Collection($addresses, $paginator),
|
||||
Response::HTTP_OK,
|
||||
[],
|
||||
[AbstractNormalizer::GROUPS => ['read']]
|
||||
);
|
||||
}
|
||||
|
||||
protected function customizeQuery(string $action, Request $request, $qb): void
|
||||
{
|
||||
if ($request->query->has('postal_code')) {
|
||||
|
@@ -108,7 +108,7 @@ class PasswordController extends AbstractController
|
||||
$username = $query->get(TokenManager::USERNAME_CANONICAL);
|
||||
$hash = $query->getAlnum(TokenManager::HASH);
|
||||
$token = $query->getAlnum(TokenManager::TOKEN);
|
||||
$timestamp = $query->getInt(TokenManager::TIMESTAMP);
|
||||
$timestamp = $query->getAlnum(TokenManager::TIMESTAMP);
|
||||
$user = $this->getDoctrine()->getRepository(User::class)
|
||||
->findOneByUsernameCanonical($username);
|
||||
|
||||
|
@@ -12,13 +12,80 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Chill\MainBundle\Repository\CountryRepository;
|
||||
use Chill\MainBundle\Repository\PostalCodeRepository;
|
||||
use Chill\MainBundle\Serializer\Model\Collection;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||
|
||||
/**
|
||||
* Class PostalCodeAPIController.
|
||||
*/
|
||||
class PostalCodeAPIController extends ApiController
|
||||
final class PostalCodeAPIController extends ApiController
|
||||
{
|
||||
private CountryRepository $countryRepository;
|
||||
|
||||
private PaginatorFactory $paginatorFactory;
|
||||
|
||||
private PostalCodeRepository $postalCodeRepository;
|
||||
|
||||
public function __construct(
|
||||
CountryRepository $countryRepository,
|
||||
PostalCodeRepository $postalCodeRepository,
|
||||
PaginatorFactory $paginatorFactory
|
||||
) {
|
||||
$this->countryRepository = $countryRepository;
|
||||
$this->postalCodeRepository = $postalCodeRepository;
|
||||
$this->paginatorFactory = $paginatorFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/api/1.0/main/postal-code/search.json")
|
||||
*/
|
||||
public function search(Request $request): JsonResponse
|
||||
{
|
||||
$this->denyAccessUnlessGranted('ROLE_USER');
|
||||
|
||||
if (!$request->query->has('q')) {
|
||||
throw new BadRequestHttpException('You must supply a "q" parameter');
|
||||
}
|
||||
|
||||
$pattern = $request->query->get('q');
|
||||
|
||||
if ('' === trim($pattern)) {
|
||||
throw new BadRequestHttpException('the search pattern is empty');
|
||||
}
|
||||
|
||||
if ($request->query->has('country')) {
|
||||
$country = $this->countryRepository->find($request->query->getInt('country'));
|
||||
|
||||
if (null === $country) {
|
||||
throw new NotFoundHttpException('country not found');
|
||||
}
|
||||
} else {
|
||||
$country = null;
|
||||
}
|
||||
|
||||
$nb = $this->postalCodeRepository->countByPattern($pattern, $country);
|
||||
$paginator = $this->paginatorFactory->create($nb);
|
||||
$codes = $this->postalCodeRepository->findByPattern(
|
||||
$pattern,
|
||||
$country,
|
||||
$paginator->getCurrentPageFirstItemNumber(),
|
||||
$paginator->getItemsPerPage()
|
||||
);
|
||||
|
||||
return $this->json(
|
||||
new Collection($codes, $paginator),
|
||||
Response::HTTP_OK,
|
||||
[],
|
||||
[AbstractNormalizer::GROUPS => ['read']]
|
||||
);
|
||||
}
|
||||
|
||||
protected function customizeQuery(string $action, Request $request, $qb): void
|
||||
{
|
||||
if ($request->query->has('country')) {
|
||||
|
@@ -26,8 +26,8 @@ class LoadCivility extends Fixture implements FixtureGroupInterface
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
$civilities = [
|
||||
['name' => ['fr' => 'Monsieur'], 'abbrev' => ['fr' => 'M.']],
|
||||
['name' => ['fr' => 'Madame'], 'abbrev' => ['fr' => 'Mme']],
|
||||
['name' => ['fr' => 'Monsieur'], 'abbrev' => ['fr' => 'M.']],
|
||||
['name' => ['fr' => 'Docteur'], 'abbrev' => ['fr' => 'Dr']],
|
||||
['name' => ['fr' => 'Professeur'], 'abbrev' => ['fr' => 'Pr']],
|
||||
['name' => ['fr' => 'Madame la Directrice'], 'abbrev' => ['fr' => 'Mme']],
|
||||
|
@@ -22,6 +22,15 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
||||
*/
|
||||
class AddressReference
|
||||
{
|
||||
/**
|
||||
* This is an internal column which is populated by database.
|
||||
*
|
||||
* This column will ease the search operations
|
||||
*
|
||||
* @ORM\Column(type="text", options={"default": ""})
|
||||
*/
|
||||
private string $addressCanonical = '';
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
|
@@ -38,8 +38,9 @@ class Center implements HasCenterInterface
|
||||
* @ORM\Id
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
* @Serializer\Groups({"docgen:read"})
|
||||
*/
|
||||
private $id;
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
|
@@ -22,7 +22,8 @@ class Civility
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="json")
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"read", "docgen:read"})
|
||||
* @Serializer\Context({"is-translatable": true}, groups={"docgen:read"})
|
||||
*/
|
||||
private array $abbreviation = [];
|
||||
|
||||
@@ -35,16 +36,22 @@ class Civility
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"read", "docgen:read"})
|
||||
*/
|
||||
private $id;
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json")
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"read", "docgen:read"})
|
||||
* @Serializer\Context({"is-translatable": true}, groups={"docgen:read"})
|
||||
*/
|
||||
private array $name = [];
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="float", name="ordering", nullable=true, options={"default": 0.0})
|
||||
*/
|
||||
private float $order = 0;
|
||||
|
||||
public function getAbbreviation(): array
|
||||
{
|
||||
return $this->abbreviation;
|
||||
@@ -65,6 +72,11 @@ class Civility
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getOrder(): ?float
|
||||
{
|
||||
return $this->order;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Civility
|
||||
*/
|
||||
@@ -88,4 +100,11 @@ class Civility
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setOrder(float $order): self
|
||||
{
|
||||
$this->order = $order;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Context;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
/**
|
||||
@@ -25,12 +26,11 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
||||
class Country
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="string", length=3)
|
||||
* @groups({"read"})
|
||||
* @groups({"read", "docgen:read"})
|
||||
* @Context({"is-translatable": true}, groups={"docgen:read"})
|
||||
*/
|
||||
private $countryCode;
|
||||
private string $countryCode = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
@@ -38,15 +38,16 @@ class Country
|
||||
* @ORM\Id
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
* @groups({"read"})
|
||||
* @groups({"read", "docgen:read"})
|
||||
*/
|
||||
private $id;
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="json")
|
||||
* @groups({"read"})
|
||||
* @groups({"read", "docgen:read"})
|
||||
* @Context({"is-translatable": true}, groups={"docgen:read"})
|
||||
*/
|
||||
private $name;
|
||||
|
||||
|
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
|
||||
/**
|
||||
* Language.
|
||||
@@ -28,15 +29,18 @@ class Language
|
||||
*
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="string")
|
||||
* @Serializer\Groups({"docgen:read"})
|
||||
*/
|
||||
private $id;
|
||||
private ?string $id = null;
|
||||
|
||||
/**
|
||||
* @var string array
|
||||
*
|
||||
* @ORM\Column(type="json")
|
||||
* @Serializer\Groups({"docgen:read"})
|
||||
* @Serializer\Context({"is-translatable": true}, groups={"docgen:read"})
|
||||
*/
|
||||
private $name;
|
||||
private array $name = [];
|
||||
|
||||
/**
|
||||
* Get id.
|
||||
|
@@ -29,6 +29,15 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
||||
*/
|
||||
class PostalCode
|
||||
{
|
||||
/**
|
||||
* This is an internal column which is populated by database.
|
||||
*
|
||||
* This column will ease the search operations
|
||||
*
|
||||
* @ORM\Column(type="text", options={"default": ""})
|
||||
*/
|
||||
private string $canonical = '';
|
||||
|
||||
/**
|
||||
* @var Point
|
||||
*
|
||||
|
@@ -14,6 +14,7 @@ namespace Chill\MainBundle\Entity;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Context;
|
||||
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
@@ -28,24 +29,21 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
||||
class Scope
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @ORM\Id
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
* @Groups({"read"})
|
||||
* @Groups({"read", "docgen:read"})
|
||||
*/
|
||||
private $id;
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* translatable names.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(type="json")
|
||||
* @Groups({"read"})
|
||||
* @Groups({"read", "docgen:read"})
|
||||
* @Context({"is-translatable": true}, groups={"docgen:read"})
|
||||
*/
|
||||
private $name = [];
|
||||
private array $name = [];
|
||||
|
||||
/**
|
||||
* @var Collection
|
||||
@@ -65,31 +63,27 @@ class Scope
|
||||
$this->roleScopes = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function addRoleScope(RoleScope $roleScope)
|
||||
public function addRoleScope(RoleScope $roleScope): self
|
||||
{
|
||||
$this->roleScopes->add($roleScope);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getName()
|
||||
public function getName(): array
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getRoleScopes()
|
||||
public function getRoleScopes(): Collection
|
||||
{
|
||||
return $this->roleScopes;
|
||||
}
|
||||
@@ -99,7 +93,7 @@ class Scope
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($name)
|
||||
public function setName(array $name): self
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
|
@@ -33,22 +33,18 @@ use function in_array;
|
||||
class User implements AdvancedUserInterface
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
* @ORM\Id
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
*/
|
||||
protected $id;
|
||||
protected ?int $id = null;
|
||||
|
||||
/**
|
||||
* Array where SAML attributes's data are stored.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(type="json", nullable=true)
|
||||
*/
|
||||
private $attributes;
|
||||
private array $attributes;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=Location::class)
|
||||
@@ -59,55 +55,44 @@ class User implements AdvancedUserInterface
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="string", length=150, nullable=true)
|
||||
* @Serializer\Groups({"docgen:read"})
|
||||
*/
|
||||
private ?string $email = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(
|
||||
* type="string",
|
||||
* length=150,
|
||||
* nullable=true,
|
||||
* unique=true)
|
||||
*/
|
||||
private $emailCanonical;
|
||||
private ?string $emailCanonical = null;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
private $enabled = true;
|
||||
private bool $enabled = true;
|
||||
|
||||
/**
|
||||
* @var Collection
|
||||
*
|
||||
* @ORM\ManyToMany(
|
||||
* targetEntity="Chill\MainBundle\Entity\GroupCenter",
|
||||
* inversedBy="users")
|
||||
* @ORM\Cache(usage="NONSTRICT_READ_WRITE")
|
||||
*/
|
||||
private $groupCenters;
|
||||
private Collection $groupCenters;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=200)
|
||||
* @Serializer\Groups({"docgen:read"})
|
||||
*/
|
||||
private string $label = '';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*
|
||||
* @ORM\Column(type="boolean")
|
||||
* sf4 check: in yml was false by default !?
|
||||
*/
|
||||
private $locked = true;
|
||||
private bool $locked = true;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=Center::class)
|
||||
* @Serializer\Groups({"docgen:read"})
|
||||
*/
|
||||
private ?Center $mainCenter = null;
|
||||
|
||||
@@ -117,20 +102,16 @@ class User implements AdvancedUserInterface
|
||||
private ?Scope $mainScope = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
private $password;
|
||||
private string $password = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @internal must be set to null if we use bcrypt
|
||||
*
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
*/
|
||||
private $salt;
|
||||
private ?string $salt = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=UserJob::class)
|
||||
@@ -138,22 +119,18 @@ class User implements AdvancedUserInterface
|
||||
private ?UserJob $userJob = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="string", length=80)
|
||||
*/
|
||||
private $username;
|
||||
private string $username = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(
|
||||
* type="string",
|
||||
* length=80,
|
||||
* unique=true,
|
||||
* nullable=true)
|
||||
*/
|
||||
private $usernameCanonical;
|
||||
private ?string $usernameCanonical = null;
|
||||
|
||||
/**
|
||||
* User constructor.
|
||||
@@ -209,7 +186,7 @@ class User implements AdvancedUserInterface
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getEmail()
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
@@ -406,10 +383,7 @@ class User implements AdvancedUserInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $enabled
|
||||
*/
|
||||
public function setEnabled($enabled)
|
||||
public function setEnabled(bool $enabled)
|
||||
{
|
||||
$this->enabled = $enabled;
|
||||
|
||||
|
@@ -32,14 +32,14 @@ class UserJob
|
||||
* @ORM\Id
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"read", "docgen:read"})
|
||||
*/
|
||||
protected ?int $id = null;
|
||||
|
||||
/**
|
||||
* @var array|string[]A
|
||||
* @ORM\Column(name="label", type="json")
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"read", "docgen:read"})
|
||||
*/
|
||||
protected array $label = [];
|
||||
|
||||
|
@@ -545,7 +545,7 @@ class ExportManager
|
||||
if (null === $centers) {
|
||||
$centers = $this->authorizationHelper->getReachableCenters(
|
||||
$this->user,
|
||||
$role
|
||||
$role->getRole(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -585,7 +585,7 @@ class ExportManager
|
||||
'center' => $center,
|
||||
'circles' => $this->authorizationHelper->getReachableScopes(
|
||||
$this->user,
|
||||
$element->requiredRole(),
|
||||
$element->requiredRole()->getRole(),
|
||||
$center
|
||||
),
|
||||
];
|
||||
|
@@ -80,7 +80,7 @@ class ScopePickerType extends AbstractType
|
||||
{
|
||||
$items = $this->authorizationHelper->getReachableScopes(
|
||||
$this->security->getUser(),
|
||||
$options['role'],
|
||||
$options['role']->getRole(),
|
||||
$options['center']
|
||||
);
|
||||
|
||||
|
@@ -81,7 +81,7 @@ class UserPickerType extends AbstractType
|
||||
->setAllowedTypes('scope', [Scope::class, 'array', 'null'])
|
||||
->setNormalizer('choices', function (Options $options) {
|
||||
$users = $this->userACLAwareRepository
|
||||
->findUsersByReachedACL($options['role'], $options['center'], $options['scope'], true);
|
||||
->findUsersByReachedACL($options['role']->getRole(), $options['center'], $options['scope'], true);
|
||||
|
||||
if (null !== $options['having_permissions_group_flag']) {
|
||||
return $this->userRepository
|
||||
|
@@ -109,6 +109,10 @@ class Paginator implements PaginatorInterface
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (0 === $this->totalItems) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$nb = floor($this->totalItems / $this->itemPerPage);
|
||||
|
||||
if ($this->totalItems % $this->itemPerPage > 0) {
|
||||
@@ -211,6 +215,10 @@ class Paginator implements PaginatorInterface
|
||||
|
||||
public function hasPage($number)
|
||||
{
|
||||
if (0 === $this->totalItems) {
|
||||
return 1 === $number;
|
||||
}
|
||||
|
||||
return 0 < $number
|
||||
&& $this->countPages() >= $number;
|
||||
}
|
||||
|
@@ -204,7 +204,7 @@ class PhonenumberHelper
|
||||
return null;
|
||||
}
|
||||
|
||||
$format = json_decode($response->getBody())->national_format;
|
||||
$format = json_decode($response->getBody()->getContents())->national_format;
|
||||
|
||||
$item
|
||||
->set($format)
|
||||
@@ -260,7 +260,7 @@ class PhonenumberHelper
|
||||
return null;
|
||||
}
|
||||
|
||||
$validation = json_decode($response->getBody())->carrier->type;
|
||||
$validation = json_decode($response->getBody()->getContents())->carrier->type;
|
||||
|
||||
$item
|
||||
->set($validation)
|
||||
|
@@ -14,7 +14,7 @@ namespace Chill\MainBundle\Redis;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class RedisConnectionFactory implements EventSubscriberInterface
|
||||
final class RedisConnectionFactory implements EventSubscriberInterface
|
||||
{
|
||||
private string $host;
|
||||
|
||||
@@ -29,12 +29,11 @@ class RedisConnectionFactory implements EventSubscriberInterface
|
||||
$this->host = $parameters['host'];
|
||||
$this->port = (int) $parameters['port'];
|
||||
$this->timeout = (int) $parameters['timeout'];
|
||||
$this->redis = new ChillRedis();
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$this->redis = new ChillRedis();
|
||||
|
||||
$result = $this->redis->connect($this->host, $this->port, $this->timeout);
|
||||
|
||||
if (false === $result) {
|
||||
@@ -55,8 +54,6 @@ class RedisConnectionFactory implements EventSubscriberInterface
|
||||
|
||||
public function onKernelFinishRequest()
|
||||
{
|
||||
if (null !== $this->redis) {
|
||||
$this->redis->close();
|
||||
}
|
||||
$this->redis->close();
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user