merge conflict fixed

This commit is contained in:
2022-11-02 11:20:11 +01:00
29 changed files with 2001 additions and 387 deletions

View File

@@ -84,7 +84,7 @@ class PersonHouseholdAddress
public function getHousehold(): ?Household
{
return $this->relation;
return $this->household;
}
public function getPerson(): ?Person

View File

@@ -1760,7 +1760,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
}
/**
* @param type $spokenLanguages
* @param Collection $spokenLanguages
*/
public function setSpokenLanguages($spokenLanguages): self
{

View File

@@ -38,7 +38,7 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface
public function buildForm(FormBuilderInterface $builder): void
{
// TODO: Implement buildForm() method.
// Nothing to add here
}
public function getAllowedFormattersTypes(): array

View File

@@ -0,0 +1,416 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Export\Export;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\MainBundle\Export\Helper\DateTimeHelper;
use Chill\MainBundle\Export\Helper\ExportAddressHelper;
use Chill\MainBundle\Export\Helper\UserHelper;
use Chill\MainBundle\Export\ListInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
use DateTimeImmutable;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function strlen;
class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
{
private const FIELDS = [
'id',
'step',
'stepSince',
'openingDate',
'closingDate',
'referrer',
'referrerSince',
'administrativeLocation',
'locationIsPerson',
'locationIsTemp',
'locationPersonName',
'locationPersonId',
'origin',
'closingMotive',
'confidential',
'emergency',
'intensity',
'job',
'isRequestorPerson',
'isRequestorThirdParty',
'requestorPerson',
'requestorPersonId',
'requestorThirdParty',
'requestorThirdPartyId',
'scopes',
'socialIssues',
'createdAt',
'createdBy',
'updatedAt',
'updatedBy',
];
private ExportAddressHelper $addressHelper;
private DateTimeHelper $dateTimeHelper;
private EntityManagerInterface $entityManager;
private PersonRenderInterface $personRender;
private PersonRepository $personRepository;
private SocialIssueRender $socialIssueRender;
private SocialIssueRepository $socialIssueRepository;
private ThirdPartyRender $thirdPartyRender;
private ThirdPartyRepository $thirdPartyRepository;
private TranslatableStringHelperInterface $translatableStringHelper;
private UserHelper $userHelper;
public function __construct(
ExportAddressHelper $addressHelper,
DateTimeHelper $dateTimeHelper,
EntityManagerInterface $entityManager,
PersonRenderInterface $personRender,
PersonRepository $personRepository,
ThirdPartyRepository $thirdPartyRepository,
ThirdPartyRender $thirdPartyRender,
SocialIssueRepository $socialIssueRepository,
SocialIssueRender $socialIssueRender,
TranslatableStringHelperInterface $translatableStringHelper,
UserHelper $userHelper
) {
$this->addressHelper = $addressHelper;
$this->dateTimeHelper = $dateTimeHelper;
$this->entityManager = $entityManager;
$this->personRender = $personRender;
$this->personRepository = $personRepository;
$this->socialIssueRender = $socialIssueRender;
$this->socialIssueRepository = $socialIssueRepository;
$this->thirdPartyRender = $thirdPartyRender;
$this->thirdPartyRepository = $thirdPartyRepository;
$this->translatableStringHelper = $translatableStringHelper;
$this->userHelper = $userHelper;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('calc_date', ChillDateType::class, [
'input' => 'datetime_immutable',
'label' => 'export.list.acp.Date of calculation for associated elements',
'help' => 'export.list.acp.The associated referree, localisation, and other elements will be valid at this date',
'required' => true,
]);
}
public function getAllowedFormattersTypes()
{
return [FormatterInterface::TYPE_LIST];
}
public function getDescription()
{
return 'export.list.acp.Generate a list of accompanying periods, filtered on different parameters.';
}
public function getGroup(): string
{
return 'Exports of accompanying courses';
}
public function getLabels($key, array $values, $data)
{
if (substr($key, 0, strlen('address_fields')) === 'address_fields') {
return $this->addressHelper->getLabel($key, $values, $data, 'address_fields');
}
switch ($key) {
case 'stepSince':
case 'openingDate':
case 'closingDate':
case 'referrerSince':
case 'createdAt':
case 'updatedAt':
return $this->dateTimeHelper->getLabel('export.list.acp.' . $key);
case 'origin':
case 'closingMotive':
case 'job':
return function ($value) use ($key) {
if ('_header' === $value) {
return 'export.list.acp.' . $key;
}
if (null === $value) {
return '';
}
return $this->translatableStringHelper->localize(json_decode($value, true));
};
case 'locationPersonName':
case 'requestorPerson':
return function ($value) use ($key) {
if ('_header' === $value) {
return 'export.list.acp.' . $key;
}
if (null === $value || null === $person = $this->personRepository->find($value)) {
return '';
}
return $this->personRender->renderString($person, []);
};
case 'requestorThirdParty':
return function ($value) use ($key) {
if ('_header' === $value) {
return 'export.list.acp.' . $key;
}
if (null === $value || null === $thirdparty = $this->thirdPartyRepository->find($value)) {
return '';
}
return $this->thirdPartyRender->renderString($thirdparty, []);
};
case 'scopes':
return function ($value) use ($key) {
if ('_header' === $value) {
return 'export.list.acp.' . $key;
}
if (null === $value) {
return '';
}
return implode(
'|',
array_map(
fn ($s) => $this->translatableStringHelper->localize($s),
json_decode($value, true)
)
);
};
case 'socialIssues':
return function ($value) use ($key) {
if ('_header' === $value) {
return 'export.list.acp.' . $key;
}
if (null === $value) {
return '';
}
return implode(
'|',
array_map(
fn ($s) => $this->socialIssueRender->renderString($this->socialIssueRepository->find($s), []),
json_decode($value, true)
)
);
};
default:
return static function ($value) use ($key) {
if ('_header' === $value) {
return 'export.list.acp.' . $key;
}
if (null === $value) {
return '';
}
return $value;
};
}
}
public function getQueryKeys($data)
{
return array_merge(
self::FIELDS,
$this->addressHelper->getKeys(ExportAddressHelper::F_ALL, 'address_fields')
);
}
public function getResult($query, $data)
{
return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR);
}
public function getTitle()
{
return 'export.list.acp.List of accompanying periods';
}
public function getType()
{
return Declarations::PERSON_TYPE;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$qb = $this->entityManager->createQueryBuilder();
$qb
->from(AccompanyingPeriod::class, 'acp')
->andWhere('acp.step != :list_acp_step')
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part
JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
->setParameter('list_acp_step', AccompanyingPeriod::STEP_DRAFT)
->setParameter('authorized_centers', $centers);
$this->addSelectClauses($qb, $data['calc_date']);
return $qb;
}
public function requiredRole(): string
{
return PersonVoter::LISTS;
}
public function supportsModifiers()
{
return [
Declarations::ACP_TYPE,
];
}
private function addSelectClauses(QueryBuilder $qb, DateTimeImmutable $calcDate): void
{
// add the regular fields
foreach (['id', 'openingDate', 'closingDate', 'confidential', 'emergency', 'intensity', 'createdAt', 'updatedAt'] as $field) {
$qb->addSelect(sprintf('acp.%s AS %s', $field, $field));
}
// add the field which are simple association
foreach (['origin' => 'label', 'closingMotive' => 'name', 'job' => 'label', 'createdBy' => 'label', 'updatedBy' => 'label', 'administrativeLocation' => 'name'] as $entity => $field) {
$qb
->leftJoin(sprintf('acp.%s', $entity), "{$entity}_t")
->addSelect(sprintf('%s_t.%s AS %s', $entity, $field, $entity));
}
// step at date
$qb
->addSelect('stepHistory.step AS step')
->addSelect('stepHistory.startDate AS stepSince')
->leftJoin('acp.stepHistories', 'stepHistory')
->andWhere(
$qb->expr()->andX(
$qb->expr()->lte('stepHistory.startDate', ':calcDate'),
$qb->expr()->orX($qb->expr()->isNull('stepHistory.endDate'), $qb->expr()->gt('stepHistory.endDate', ':calcDate'))
)
);
// referree at date
$qb
->addSelect('referrer_t.label AS referrer')
->addSelect('userHistory.startDate AS referrerSince')
->leftJoin('acp.userHistories', 'userHistory')
->leftJoin('userHistory.user', 'referrer_t')
->andWhere(
$qb->expr()->orX(
$qb->expr()->isNull('userHistory'),
$qb->expr()->andX(
$qb->expr()->lte('userHistory.startDate', ':calcDate'),
$qb->expr()->orX($qb->expr()->isNull('userHistory.endDate'), $qb->expr()->gt('userHistory.endDate', ':calcDate'))
)
)
);
// location of the acp
$qb
->addSelect('CASE WHEN locationHistory.personLocation IS NOT NULL THEN 1 ELSE 0 END AS locationIsPerson')
->addSelect('CASE WHEN locationHistory.personLocation IS NOT NULL THEN 0 ELSE 1 END AS locationIsTemp')
->addSelect('IDENTITY(locationHistory.personLocation) AS locationPersonName')
->addSelect('IDENTITY(locationHistory.personLocation) AS locationPersonId')
->leftJoin('acp.locationHistories', 'locationHistory')
->andWhere(
$qb->expr()->orX(
$qb->expr()->isNull('locationHistory'),
$qb->expr()->andX(
$qb->expr()->lte('locationHistory.startDate', ':calcDate'),
$qb->expr()->orX($qb->expr()->isNull('locationHistory.endDate'), $qb->expr()->gt('locationHistory.endDate', ':calcDate'))
)
)
)
->leftJoin(PersonHouseholdAddress::class, 'personAddress', Join::WITH, 'locationHistory.personLocation = personAddress.person')
->andWhere(
$qb->expr()->orX(
$qb->expr()->isNull('personAddress'),
$qb->expr()->andX(
$qb->expr()->lte('personAddress.validFrom', ':calcDate'),
$qb->expr()->orX($qb->expr()->isNull('personAddress.validTo'), $qb->expr()->gt('personAddress.validTo', ':calcDate'))
)
)
)
->leftJoin(Address::class, 'acp_address', Join::WITH, 'COALESCE(IDENTITY(locationHistory.addressLocation), IDENTITY(personAddress.address)) = acp_address.id');
$this->addressHelper->addSelectClauses(ExportAddressHelper::F_ALL, $qb, 'acp_address', 'address_fields');
// requestor
$qb
->addSelect('CASE WHEN acp.requestorPerson IS NULL THEN 1 ELSE 0 END AS isRequestorPerson')
->addSelect('CASE WHEN acp.requestorPerson IS NULL THEN 0 ELSE 1 END AS isRequestorThirdParty')
->addSelect('IDENTITY(acp.requestorPerson) AS requestorPersonId')
->addSelect('IDENTITY(acp.requestorThirdParty) AS requestorThirdPartyId')
->addSelect('IDENTITY(acp.requestorPerson) AS requestorPerson')
->addSelect('IDENTITY(acp.requestorThirdParty) AS requestorThirdParty');
$qb
// scopes
->addSelect('(SELECT AGGREGATE(scope.name) FROM ' . Scope::class . ' scope WHERE scope MEMBER OF acp.scopes) AS scopes')
// social issues
->addSelect('(SELECT AGGREGATE(socialIssue.id) FROM ' . SocialIssue::class . ' socialIssue WHERE socialIssue MEMBER OF acp.socialIssues) AS socialIssues');
// add parameter
$qb->setParameter('calcDate', $calcDate);
}
}

View File

@@ -17,21 +17,22 @@ use Chill\CustomFieldsBundle\Service\CustomFieldProvider;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\MainBundle\Export\Helper\ExportAddressHelper;
use Chill\MainBundle\Export\ListInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Export\Helper\ListPersonHelper;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use DateTime;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query;
use Exception;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function addcslashes;
use function array_key_exists;
@@ -39,7 +40,7 @@ use function array_keys;
use function array_merge;
use function count;
use function in_array;
use function strtolower;
use function strlen;
use function uniqid;
/**
@@ -47,40 +48,35 @@ use function uniqid;
*/
class ListPerson implements ExportElementValidatedInterface, ListInterface, GroupedExportInterface
{
protected CustomFieldProvider $customFieldProvider;
private ExportAddressHelper $addressHelper;
protected EntityManagerInterface $entityManager;
private CustomFieldProvider $customFieldProvider;
protected array $fields = [
'id', 'firstName', 'lastName', 'birthdate',
'placeOfBirth', 'gender', 'memo', 'email', 'phonenumber',
'mobilenumber', 'contactInfo', 'countryOfBirth', 'nationality',
'address_street_address_1', 'address_street_address_2',
'address_valid_from', 'address_postcode_label', 'address_postcode_code',
'address_country_name', 'address_country_code', 'address_isnoaddress',
];
private EntityManagerInterface $entityManager;
protected TranslatableStringHelper $translatableStringHelper;
protected TranslatorInterface $translator;
private ListPersonHelper $listPersonHelper;
private $slugs = [];
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
ExportAddressHelper $addressHelper,
CustomFieldProvider $customFieldProvider,
ListPersonHelper $listPersonHelper,
EntityManagerInterface $em,
TranslatorInterface $translator,
TranslatableStringHelper $translatableStringHelper,
CustomFieldProvider $customFieldProvider
TranslatableStringHelper $translatableStringHelper
) {
$this->entityManager = $em;
$this->translator = $translator;
$this->translatableStringHelper = $translatableStringHelper;
$this->addressHelper = $addressHelper;
$this->customFieldProvider = $customFieldProvider;
$this->listPersonHelper = $listPersonHelper;
$this->entityManager = $em;
$this->translatableStringHelper = $translatableStringHelper;
}
public function buildForm(FormBuilderInterface $builder)
{
$choices = array_combine($this->fields, $this->fields);
$choices = array_combine(ListPersonHelper::FIELDS, ListPersonHelper::FIELDS);
foreach ($this->getCustomFields() as $cf) {
$choices[$this->translatableStringHelper->localize($cf->getName())]
@@ -96,7 +92,7 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou
'label' => 'Fields to include in export',
'choice_attr' => static function (string $val): array {
// add a 'data-display-target' for address fields
if (substr($val, 0, 8) === 'address_') {
if (substr($val, 0, 7) === 'address' || 'center' === $val || 'household' === $val) {
return ['data-display-target' => 'address_date'];
}
@@ -111,17 +107,15 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou
}
},
])],
'data' => array_values($choices),
]);
// add a date field for addresses
$builder->add('address_date', DateType::class, [
'label' => 'Address valid at this date',
'data' => new DateTime(),
'attr' => ['class' => 'datepicker'],
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
'required' => false,
'block_name' => 'list_export_form_address_date',
$builder->add('address_date', ChillDateType::class, [
'label' => 'Data valid at this date',
'help' => 'Data regarding center, addresses, and so on will be computed at this date',
'data' => new DateTimeImmutable(),
'input' => 'datetime_immutable',
]);
}
@@ -142,113 +136,35 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou
public function getLabels($key, array $values, $data)
{
switch ($key) {
case 'birthdate':
// for birthdate, we have to transform the string into a date
// to format the date correctly.
return static function ($value) {
if ('_header' === $value) {
return 'birthdate';
}
if (empty($value)) {
return '';
}
$date = DateTime::createFromFormat('Y-m-d', $value);
// check that the creation could occurs.
if (false === $date) {
throw new Exception(sprintf('The value %s could '
. 'not be converted to %s', $value, DateTime::class));
}
return $date->format('d-m-Y');
};
case 'gender':
// for gender, we have to translate men/women statement
return function ($value) {
if ('_header' === $value) {
return 'gender';
}
return $this->translator->trans($value);
};
case 'countryOfBirth':
case 'nationality':
$countryRepository = $this->entityManager
->getRepository(\Chill\MainBundle\Entity\Country::class);
// load all countries in a single query
$countryRepository->findBy(['countryCode' => $values]);
return function ($value) use ($key, $countryRepository) {
if ('_header' === $value) {
return strtolower($key);
}
if (null === $value) {
return $this->translator->trans('no data');
}
$country = $countryRepository->find($value);
return $this->translatableStringHelper->localize(
$country->getName()
);
};
case 'address_country_name':
return function ($value) use ($key) {
if ('_header' === $value) {
return strtolower($key);
}
if (null === $value) {
return '';
}
return $this->translatableStringHelper->localize(json_decode($value, true));
};
case 'address_isnoaddress':
return static function (?string $value): string {
if ('_header' === $value) {
return 'address.address_homeless';
}
if (null !== $value) {
return 'X';
}
return '';
};
default:
// for fields which are associated with person
if (in_array($key, $this->fields, true)) {
return static function ($value) use ($key) {
if ('_header' === $value) {
return strtolower($key);
}
return $value;
};
}
return $this->getLabelForCustomField($key, $values, $data);
if (in_array($key, $this->listPersonHelper->getAllPossibleFields(), true)) {
return $this->listPersonHelper->getLabels($key, $values, $data);
}
return $this->getLabelForCustomField($key, $values, $data);
}
public function getQueryKeys($data)
{
$fields = [];
foreach ($data['fields'] as $key) {
if (in_array($key, $this->fields, true)) {
$fields[] = $key;
foreach (ListPersonHelper::FIELDS as $key) {
if (!in_array($key, $data['fields'], true)) {
continue;
}
if (substr($key, 0, strlen('address_fields')) === 'address_fields') {
$fields = array_merge($fields, $this->addressHelper->getKeys(ExportAddressHelper::F_ALL, 'address_fields'));
continue;
}
if ('lifecycleUpdate' === $key) {
$fields = array_merge($fields, ['createdAt', 'createdBy', 'updatedAt', 'updatedBy']);
continue;
}
$fields[] = $key;
}
// add the key from slugs and return
@@ -270,6 +186,9 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou
return Declarations::PERSON_TYPE;
}
/**
* param array{fields: string[], address_date: DateTimeImmutable} $data.
*/
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static function ($el) {
@@ -284,40 +203,24 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou
$qb = $this->entityManager->createQueryBuilder();
foreach ($this->fields as $f) {
if (in_array($f, $data['fields'], true)) {
switch ($f) {
case 'countryOfBirth':
case 'nationality':
$qb->addSelect(sprintf('IDENTITY(person.%s) as %s', $f, $f));
$qb
->from(Person::class, 'person')
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . Person\PersonCenterHistory::class . ' pch WHERE pch.person = person.id AND pch.center IN (:authorized_centers)'
)
)
->setParameter('authorized_centers', $centers);
break;
$fields = $data['fields'];
case 'address_street_address_1':
case 'address_street_address_2':
case 'address_valid_from':
case 'address_postcode_label':
case 'address_postcode_code':
case 'address_country_name':
case 'address_country_code':
case 'address_isnoaddress':
$qb->addSelect(sprintf(
'GET_PERSON_ADDRESS_%s(person.id, :address_date) AS %s',
// get the part after address_
strtoupper(substr($f, 8)),
$f
));
$qb->setParameter('address_date', $data['address_date']);
break;
default:
$qb->addSelect(sprintf('person.%s as %s', $f, $f));
}
}
}
$this->listPersonHelper->addSelect($qb, $fields, $data['address_date']);
foreach ($this->getCustomFields() as $cf) {
if (!in_array($cf->getSlug(), $fields, true)) {
continue;
}
$cfType = $this->customFieldProvider->getCustomFieldByType($cf->getType());
if ($cfType instanceof CustomFieldChoice && $cfType->isMultiple($cf)) {
@@ -345,12 +248,6 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou
}
}
$qb
->from('ChillPersonBundle:Person', 'person')
->join('person.center', 'center')
->andWhere('center IN (:authorized_centers)')
->setParameter('authorized_centers', $centers);
return $qb;
}
@@ -368,14 +265,14 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou
{
// get the field starting with address_
$addressFields = array_filter(
$this->fields,
ListPersonHelper::FIELDS,
static fn (string $el): bool => substr($el, 0, 8) === 'address_'
);
// check if there is one field starting with address in data
if (count(array_intersect($data['fields'], $addressFields)) > 0) {
// if a field address is checked, the date must not be empty
if (empty($data['address_date'])) {
if (!$data['address_date'] instanceof DateTimeImmutable) {
$context
->buildViolation('You must set this date if an address is checked')
->atPath('address_date')
@@ -456,7 +353,7 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou
. ' | ' . $label;
}
if ('_other' === $slugChoice && $cfType->isChecked($cf, $choiceSlug, $decoded)) {
if ('_other' === $slugChoice && $cfType->isChecked($cf, $slugChoice, $decoded)) {
return $cfType->extractOtherValue($cf, $decoded);
}

View File

@@ -0,0 +1,220 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Export\Export;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\MainBundle\Export\Helper\ExportAddressHelper;
use Chill\MainBundle\Export\ListInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Export\Helper\ListPersonHelper;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use DateTimeImmutable;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function array_key_exists;
use function count;
use function in_array;
use function strlen;
class ListPersonWithAccompanyingPeriod implements ExportElementValidatedInterface, ListInterface, GroupedExportInterface
{
private ExportAddressHelper $addressHelper;
private EntityManagerInterface $entityManager;
private ListPersonHelper $listPersonHelper;
public function __construct(
ExportAddressHelper $addressHelper,
ListPersonHelper $listPersonHelper,
EntityManagerInterface $em
) {
$this->addressHelper = $addressHelper;
$this->listPersonHelper = $listPersonHelper;
$this->entityManager = $em;
}
public function buildForm(FormBuilderInterface $builder)
{
$choices = array_combine(ListPersonHelper::FIELDS, ListPersonHelper::FIELDS);
// Add a checkbox to select fields
$builder->add('fields', ChoiceType::class, [
'multiple' => true,
'expanded' => true,
'choices' => $choices,
'label' => 'Fields to include in export',
'choice_attr' => static function (string $val): array {
// add a 'data-display-target' for address fields
if (substr($val, 0, 7) === 'address' || 'center' === $val || 'household' === $val) {
return ['data-display-target' => 'address_date'];
}
return [];
},
'constraints' => [new Callback([
'callback' => static function ($selected, ExecutionContextInterface $context) {
if (count($selected) === 0) {
$context->buildViolation('You must select at least one element')
->atPath('fields')
->addViolation();
}
},
])],
'data' => array_values($choices),
]);
// add a date field for addresses
$builder->add('address_date', ChillDateType::class, [
'label' => 'Data valid at this date',
'help' => 'Data regarding center, addresses, and so on will be computed at this date',
'data' => new DateTimeImmutable(),
'input' => 'datetime_immutable',
]);
}
public function getAllowedFormattersTypes()
{
return [FormatterInterface::TYPE_LIST];
}
public function getDescription()
{
return 'export.list.person_with_acp.Create a list of people having an accompaying periods, according to various filters.';
}
public function getGroup(): string
{
return 'Exports of persons';
}
public function getLabels($key, array $values, $data)
{
return $this->listPersonHelper->getLabels($key, $values, $data);
}
public function getQueryKeys($data)
{
$fields = [];
foreach (ListPersonHelper::FIELDS as $key) {
if (!in_array($key, $data['fields'], true)) {
continue;
}
if (substr($key, 0, strlen('address_fields')) === 'address_fields') {
$fields = array_merge($fields, $this->addressHelper->getKeys(ExportAddressHelper::F_ALL, 'address_fields'));
continue;
}
if ('lifecycleUpdate' === $key) {
$fields = array_merge($fields, ['createdAt', 'createdBy', 'updatedAt', 'updatedBy']);
continue;
}
$fields[] = $key;
}
return $fields;
}
public function getResult($query, $data)
{
return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR);
}
public function getTitle()
{
return 'export.list.person_with_acp.List peoples having an accompanying period';
}
public function getType()
{
return Declarations::PERSON_TYPE;
}
/**
* param array{fields: string[], address_date: DateTimeImmutable} $data.
*/
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
// throw an error if any fields are present
if (!array_key_exists('fields', $data)) {
throw new \Doctrine\DBAL\Exception\InvalidArgumentException('any fields '
. 'have been checked');
}
$qb = $this->entityManager->createQueryBuilder();
$qb->from(Person::class, 'person')
->join('person.accompanyingPeriodParticipations', 'acppart')
->join('acppart.accompanyingPeriod', 'acp')
->andWhere($qb->expr()->neq('acp.step', "'" . AccompanyingPeriod::STEP_DRAFT . "'"))
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . PersonCenterHistory::class . ' pch WHERE pch.person = person.id AND pch.center IN (:authorized_centers)'
)
)->setParameter('authorized_centers', $centers);
$fields = $data['fields'];
$this->listPersonHelper->addSelect($qb, $fields, $data['address_date']);
return $qb;
}
public function requiredRole(): string
{
return PersonVoter::LISTS;
}
public function supportsModifiers()
{
return [Declarations::PERSON_TYPE, Declarations::PERSON_IMPLIED_IN, Declarations::ACP_TYPE];
}
public function validateForm($data, ExecutionContextInterface $context)
{
// get the field starting with address_
$addressFields = array_filter(
ListPersonHelper::FIELDS,
static fn (string $el): bool => substr($el, 0, 8) === 'address_'
);
// check if there is one field starting with address in data
if (count(array_intersect($data['fields'], $addressFields)) > 0) {
// if a field address is checked, the date must not be empty
if (!$data['address_date'] instanceof DateTimeImmutable) {
$context
->buildViolation('You must set this date if an address is checked')
->atPath('address_date')
->addViolation();
}
}
}
}

View File

@@ -12,7 +12,6 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Form\Type\PickSocialIssueType;
@@ -31,15 +30,11 @@ class SocialIssueFilter implements FilterInterface
private SocialIssueRender $socialIssueRender;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
TranslatorInterface $translator,
TranslatableStringHelper $translatableStringHelper,
SocialIssueRender $socialIssueRender
) {
$this->translator = $translator;
$this->translatableStringHelper = $translatableStringHelper;
$this->socialIssueRender = $socialIssueRender;
}
@@ -59,7 +54,7 @@ class SocialIssueFilter implements FilterInterface
$qb->andWhere($clause)
->setParameter(
'socialissues',
SocialIssue::getDescendantsWithThisForIssues($data['accepted_socialissues'])
SocialIssue::getDescendantsWithThisForIssues($data['accepted_socialissues']->toArray())
);
}

View File

@@ -0,0 +1,441 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Export\Helper;
use Chill\MainBundle\Export\Helper\ExportAddressHelper;
use Chill\MainBundle\Repository\CenterRepositoryInterface;
use Chill\MainBundle\Repository\CivilityRepositoryInterface;
use Chill\MainBundle\Repository\CountryRepository;
use Chill\MainBundle\Repository\LanguageRepositoryInterface;
use Chill\MainBundle\Repository\UserRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Repository\MaritalStatusRepositoryInterface;
use DateTime;
use DateTimeImmutable;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Exception;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use RuntimeException;
use Symfony\Contracts\Translation\TranslatorInterface;
use function in_array;
use function strlen;
/**
* Helper for list person: provide:.
*
* * the labels;
* * add select statement in query
*
* for some fields
*/
class ListPersonHelper
{
public const FIELDS = [
'id',
'civility',
'firstName',
'lastName',
'birthdate',
'center',
'deathdate',
'placeOfBirth',
'gender',
'genderComment',
'maritalStatus',
'maritalStatusComment',
'maritalStatusDate',
'memo',
'email',
'phonenumber',
'mobilenumber',
'numberOfChildren',
'contactInfo',
'countryOfBirth',
'nationality',
// add full addresses
'address_fields',
// add a list of spoken languages
'spokenLanguages',
// add household id
'household_id',
// add created at, created by, updated at, and updated by
'lifecycleUpdate',
];
private ExportAddressHelper $addressHelper;
private CenterRepositoryInterface $centerRepository;
private CivilityRepositoryInterface $civilityRepository;
private CountryRepository $countryRepository;
private LanguageRepositoryInterface $languageRepository;
private MaritalStatusRepositoryInterface $maritalStatusRepository;
private TranslatableStringHelper $translatableStringHelper;
private TranslatorInterface $translator;
private UserRepositoryInterface $userRepository;
public function __construct(
ExportAddressHelper $addressHelper,
CenterRepositoryInterface $centerRepository,
CivilityRepositoryInterface $civilityRepository,
CountryRepository $countryRepository,
LanguageRepositoryInterface $languageRepository,
MaritalStatusRepositoryInterface $maritalStatusRepository,
TranslatableStringHelper $translatableStringHelper,
TranslatorInterface $translator,
UserRepositoryInterface $userRepository
) {
$this->addressHelper = $addressHelper;
$this->centerRepository = $centerRepository;
$this->civilityRepository = $civilityRepository;
$this->countryRepository = $countryRepository;
$this->languageRepository = $languageRepository;
$this->maritalStatusRepository = $maritalStatusRepository;
$this->translatableStringHelper = $translatableStringHelper;
$this->translator = $translator;
$this->userRepository = $userRepository;
}
/**
* @param array|value-of<self::FIELDS>[] $fields
*/
public function addSelect(QueryBuilder $qb, array $fields, DateTimeImmutable $computedDate): void
{
foreach (ListPersonHelper::FIELDS as $f) {
if (!in_array($f, $fields, true)) {
continue;
}
switch ($f) {
case 'countryOfBirth':
case 'nationality':
$qb->addSelect(sprintf('IDENTITY(person.%s) as %s', $f, $f));
break;
case 'address_fields':
$this->addCurrentAddressAt($qb, $computedDate);
$qb->leftJoin('personHouseholdAddress.address', 'personAddress');
$this->addressHelper->addSelectClauses(ExportAddressHelper::F_ALL, $qb, 'personAddress', 'address_fields');
break;
case 'spokenLanguages':
$qb
->leftJoin('person.spokenLanguages', 'spokenLanguage')
->addSelect('AGGREGATE(spokenLanguage.id) AS spokenLanguages')
->addGroupBy('person');
if (in_array('center', $fields, true)) {
$qb->addGroupBy('center');
}
if (in_array('address_fields', $fields, true)) {
$qb
->addGroupBy('address_fieldsid')
->addGroupBy('address_fieldscountry_t.id')
->addGroupBy('address_fieldspostcode_t.id');
}
if (in_array('household_id', $fields, true)) {
$qb->addGroupBy('household_id');
}
break;
case 'household_id':
$qb
->addSelect('IDENTITY(personHouseholdAddress.household) AS household_id');
$this->addCurrentAddressAt($qb, $computedDate);
break;
case 'center':
$qb
->addSelect('IDENTITY(centerHistory.center) AS center')
->leftJoin('person.centerHistory', 'centerHistory')
->andWhere(
$qb->expr()->orX(
$qb->expr()->isNull('centerHistory'),
$qb->expr()->andX(
$qb->expr()->lte('centerHistory.startDate', ':address_date'),
$qb->expr()->orX(
$qb->expr()->isNull('centerHistory.endDate'),
$qb->expr()->gte('centerHistory.endDate', ':address_date')
)
)
)
)
->setParameter('address_date', $computedDate);
break;
case 'lifecycleUpdate':
$qb
->addSelect('person.createdAt AS createdAt')
->addSelect('IDENTITY(person.createdBy) AS createdBy')
->addSelect('person.updatedAt AS updatedAt')
->addSelect('IDENTITY(person.updatedBy) AS updatedBy');
break;
case 'genderComment':
$qb->addSelect('person.genderComment.comment AS genderComment');
break;
case 'maritalStatus':
$qb->addSelect('IDENTITY(person.maritalStatus) AS maritalStatus');
break;
case 'maritalStatusComment':
$qb->addSelect('person.maritalStatusComment.comment AS maritalStatusComment');
break;
case 'civility':
$qb->addSelect('IDENTITY(person.civility) AS civility');
break;
default:
$qb->addSelect(sprintf('person.%s as %s', $f, $f));
}
}
}
/**
* @return array|string[]
*/
public function getAllPossibleFields(): array
{
return array_merge(
self::FIELDS,
['createdAt', 'createdBy', 'updatedAt', 'updatedBy'],
$this->addressHelper->getKeys(ExportAddressHelper::F_ALL, 'address_fields')
);
}
public function getLabels($key, array $values, $data): callable
{
if (substr($key, 0, strlen('address_fields')) === 'address_fields') {
return $this->addressHelper->getLabel($key, $values, $data, 'address_fields');
}
switch ($key) {
case 'center':
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value || null === $center = $this->centerRepository->find($value)) {
return '';
}
return $center->getName();
};
case 'birthdate':
case 'deathdate':
case 'maritalStatusDate':
case 'createdAt':
case 'updatedAt':
// for birthdate, we have to transform the string into a date
// to format the date correctly.
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value) {
return '';
}
// warning: won't work with DateTimeImmutable as we reset time a few lines later
$date = DateTime::createFromFormat('Y-m-d', $value);
$hasTime = false;
if (false === $date) {
$date = DateTime::createFromFormat('Y-m-d H:i:s', $value);
$hasTime = true;
}
// check that the creation could occurs.
if (false === $date) {
throw new Exception(sprintf('The value %s could '
. 'not be converted to %s', $value, DateTime::class));
}
if (!$hasTime) {
$date->setTime(0, 0, 0);
}
return $date;
};
case 'createdBy':
case 'updatedBy':
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value) {
return '';
}
return $this->userRepository->find($value)->getLabel();
};
case 'civility':
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value) {
return '';
}
$civility = $this->civilityRepository->find($value);
if (null === $civility) {
return '';
}
return $this->translatableStringHelper->localize($civility->getName());
};
case 'gender':
// for gender, we have to translate men/women statement
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
return $this->translator->trans($value);
};
case 'maritalStatus':
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value) {
return '';
}
$maritalStatus = $this->maritalStatusRepository->find($value);
return $this->translatableStringHelper->localize($maritalStatus->getName());
};
case 'spokenLanguages':
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value) {
return '';
}
$ids = json_decode($value);
return
implode(
'|',
array_map(function ($id) {
if (null === $id) {
return '';
}
$lang = $this->languageRepository->find($id);
if (null === $lang) {
return null;
}
return $this->translatableStringHelper->localize($lang->getName());
}, $ids)
);
};
case 'countryOfBirth':
case 'nationality':
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
if (null === $value) {
return '';
}
$country = $this->countryRepository->find($value);
return $this->translatableStringHelper->localize(
$country->getName()
);
};
default:
if (!in_array($key, self::getAllPossibleFields(), true)) {
throw new RuntimeException("this key is not supported by this helper: {$key}");
}
// for fields which are associated with person
return function ($value) use ($key) {
if ('_header' === $value) {
return $this->translator->trans($key);
}
return $value;
};
}
}
private function addCurrentAddressAt(QueryBuilder $qb, DateTimeImmutable $date): void
{
if (!(in_array('personHouseholdAddress', $qb->getAllAliases(), true))) {
$qb
->leftJoin('person.householdAddresses', 'personHouseholdAddress')
->andWhere(
$qb->expr()->orX(
// no address at this time
$qb->expr()->isNull('personHouseholdAddress'),
// there is one address...
$qb->expr()->andX(
$qb->expr()->lte('personHouseholdAddress.validFrom', ':address_date'),
$qb->expr()->orX(
$qb->expr()->isNull('personHouseholdAddress.validTo'),
$qb->expr()->gt('personHouseholdAddress.validTo', ':address_date')
)
)
)
)
->setParameter('address_date', $date);
}
}
}

View File

@@ -14,9 +14,8 @@ namespace Chill\PersonBundle\Repository;
use Chill\PersonBundle\Entity\MaritalStatus;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ObjectRepository;
class MaritalStatusRepository implements ObjectRepository
class MaritalStatusRepository implements MaritalStatusRepositoryInterface
{
private EntityRepository $repository;

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Repository;
use Chill\PersonBundle\Entity\MaritalStatus;
interface MaritalStatusRepositoryInterface
{
public function find($id): ?MaritalStatus;
public function findAll(): array;
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array;
public function findOneBy(array $criteria): ?MaritalStatus;
public function getClassName(): string;
}

View File

@@ -15,13 +15,24 @@ services:
tags:
- { name: chill.export, alias: count_person_with_accompanying_course }
chill.person.export.list_person:
class: Chill\PersonBundle\Export\Export\ListPerson
Chill\PersonBundle\Export\Export\ListPerson:
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: list_person }
Chill\PersonBundle\Export\Export\ListPersonWithAccompanyingPeriod:
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: list_person_with_acp }
Chill\PersonBundle\Export\Export\ListAccompanyingPeriod:
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: list_acp }
chill.person.export.list_person.duplicate:
class: Chill\PersonBundle\Export\Export\ListPersonDuplicate
arguments:

View File

@@ -89,6 +89,16 @@ Any person selected: Aucune personne sélectionnée
Create a household and add an address: Ajouter une adresse pour une personne non suivie et seule dans un ménage
A new household will be created. The person will be member of this household.: Un nouveau ménage va être créé. La personne sera membre de ce ménage.
Comment on the gender: Commentaire sur le genre
genderComment: Commentaire sur le genre
maritalStatus: État civil
maritalStatusComment: Commentaire sur l'état civil
maritalStatusDate: Date de l'état civil
memo: Commentaire
numberOfChildren: Nombre d'enfants
contactInfo: Commentaire des contacts
spokenLanguages: Langues parlées
# dédoublonnage
Old person: Doublon
@@ -339,6 +349,8 @@ List peoples: Liste des personnes
Create a list of people according to various filters.: Crée une liste des personnes selon différents filtres.
Fields to include in export: Champs à inclure dans l'export
Address valid at this date: Addresse valide à cette date
Data valid at this date: Données valides à cette date
Data regarding center, addresses, and so on will be computed at this date: Les données concernant le centre, l'adresse, le ménage, sera calculé à cette date.
List duplicates: Liste des doublons
Create a list of duplicate people: Créer la liste des personnes détectées comme doublons.
Count people participating in an accompanying course: Nombre de personnes concernées par un parcours
@@ -748,6 +760,7 @@ socialAction:
defaultNotificationDelay: Délai de notification par défaut
socialIssue: Problématique sociale
household_id: Identifiant du ménage
household:
allowHolder: Peut être titulaire
shareHousehold: Membre du ménage
@@ -1006,6 +1019,49 @@ export:
Computation date for referrer: Date à laquelle le référent était actif
by_referrer:
Computation date for referrer: Date à laquelle le référent était actif
list:
person_with_acp:
List peoples having an accompanying period: Liste des personnes ayant un parcours d'accompagnement
Create a list of people having an accompaying periods, according to various filters.: Génère une liste des personnes ayant un parcours d'accompagnement, selon différents critères liés au parcours ou à l'usager
acp:
List of accompanying periods: Liste de périodes d'accompagnements
Generate a list of accompanying periods, filtered on different parameters.: Génère une liste des périodes d'accompagnement, filtrée sur différents paramètres.
Date of calculation for associated elements: Date de calcul des éléments associés
The associated referree, localisation, and other elements will be valid at this date: Les éléments associés, comme la localisation, le référent et d'autres éléments seront valides à cette date
id: Identifiant du parcours
openingDate: Date d'ouverture du parcours
closingDate: Date de fermeture du parcours
confidential: Confidentiel
emergency: Urgent
intensity: Intensité
createdAt: Créé le
updatedAt: Dernière mise à jour le
acpOrigin: Origine du parcours
acpClosingMotive: Motif de fermeture
acpJob: Métier du parcours
createdBy: Créé par
updatedBy: Dernière modification par
administrativeLocation: Location administrative
step: Etape
stepSince: Dernière modification de l'étape
referrer: Référent
referrerSince: Référent depuis le
locationIsPerson: Parcours localisé auprès d'un usager concerné
locationIsTemp: Parcours avec une localisation temporaire
acpLocationPersonName: Usager auprès duquel le parcours est localisé
locationPersonId: Identifiant de l'usager auprès duquel le parcours est localisé
acpaddress_fieldscountry: Pays de l'adresse
isRequestorPerson: Le demandeur est-il un usager ?
isRequestorThirdParty: Le demandeur est-il un tiers ?
requestorPersonId: Identifiant du demandeur personne
requestorThirdPartyId: Identifiant du tiers
acprequestorPerson: Nom du demandeur personne
acprequestorThirdPaty: Nom du demandeur tiers
scopes: Services
socialIssues: Problématiques sociales
social_action:
and children: et dérivés