396 lines
12 KiB
PHP

<?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\JobBundle\Export;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\ListInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\PersonBundle\Entity\Person;
use Chill\JobBundle\Security\Authorization\ExportsJobVoter;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
* Class ListCV.
*
* @author Mathieu Jaumotte mathieu.jaumotte@champs-libres.coop
*/
class ListCV implements ListInterface, ExportElementValidatedInterface
{
/**
* @var array
*/
public const FIELDS = [
'id' => 'integer',
'firstname' => 'string',
'lastname' => 'string',
'gender' => 'string',
'birthdate' => 'date',
'placeofbirth' => 'string',
'countryofbirth' => 'json',
'formationlevel' => 'string',
];
/**
* @var array
*/
protected $personIds = [];
/**
* @var EntityManagerInterface
*/
protected $entityManager;
/**
* ListAcquisition constructor.
*/
public function __construct(EntityManagerInterface $em)
{
$this->entityManager = $em;
}
/**
* validate the form's data and, if required, build a contraint
* violation on the data.
*
* @param mixed $data the data, as returned by the user
*/
public function validateForm($data, ExecutionContextInterface $context) {}
/**
* get a title, which will be used in UI (and translated).
*
* @return string
*/
public function getTitle()
{
return 'Liste des CVs par personne';
}
/**
* Add a form to collect data from the user.
*/
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('fields', ChoiceType::class, [
'multiple' => true,
'expanded' => true,
'choices_as_values' => true,
'label' => 'Fields to include in export',
'choices' => array_combine($this->getFields(), $this->getFields()),
'data' => array_combine($this->getFields(), $this->getFields()),
'choice_attr' => [],
'attr' => ['class' => ''],
'constraints' => [new Callback([
'callback' => function ($selected, ExecutionContextInterface $context) {
if (0 === count($selected)) {
$context
->buildViolation('You must select at least one element')
->atPath('fields')
->addViolation();
}
},
])],
])
->add('reportdate_min', ChillDateType::class, [
'label' => 'Date du rapport après le',
'required' => false,
'label_attr' => [
'class' => 'reportdate_range',
],
'attr' => [
'class' => 'reportdate_range',
],
])
->add('reportdate_max', ChillDateType::class, [
'label' => 'Date du rapport avant le',
'required' => false,
'label_attr' => [
'class' => 'report_date_range',
],
'attr' => [
'class' => 'report_date_range',
],
])
;
}
/**
* Return the Export's type. This will inform _on what_ export will apply.
* Most of the type, it will be a string which references an entity.
*
* Example of types : Chill\PersonBundle\Export\Declarations::PERSON_TYPE
*
* @return string
*/
public function getType()
{
return Person::class;
}
/**
* A description, which will be used in the UI to explain what the export does.
* This description will be translated.
*
* @return string
*/
public function getDescription()
{
return 'Crée une liste des CVs en fonction de différents paramètres.';
}
/**
* The initial query, which will be modified by ModifiersInterface
* (i.e. AggregatorInterface, FilterInterface).
*
* This query should take into account the `$acl` and restrict result only to
* what the user is allowed to see. (Do not show personal data the user
* is not allowed to see).
*
* The returned object should be an instance of QueryBuilder or NativeQuery.
*
* @param array $acl an array where each row has a `center` key containing the Chill\MainBundle\Entity\Center, and `circles` keys containing the reachable circles. Example: `array( array('center' => $centerA, 'circles' => array($circleA, $circleB) ) )`
* @param array $data the data from the form, if any
*
* @return QueryBuilder|\Doctrine\ORM\NativeQuery the query to execute
*/
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
return $this->entityManager->createQueryBuilder()
->from('ChillPersonBundle:Person', 'person');
}
/**
* Inform which ModifiersInterface (i.e. AggregatorInterface, FilterInterface)
* are allowed. The modifiers should be an array of types the _modifier_ apply on
* (@see ModifiersInterface::applyOn()).
*
* @return string[]
*/
public function supportsModifiers()
{
return ['cv', 'person'];
}
/**
* Return the required Role to execute the Export.
*/
public function requiredRole(): string
{
return ExportsJobVoter::EXPORT;
}
/**
* Return which formatter type is allowed for this report.
*
* @return string[]
*/
public function getAllowedFormattersTypes()
{
return [FormatterInterface::TYPE_LIST];
}
/**
* give the list of keys the current export added to the queryBuilder in
* self::initiateQuery.
*
* Example: if your query builder will contains `SELECT count(id) AS count_id ...`,
* this function will return `array('count_id')`.
*
* @param mixed[] $data the data from the export's form (added by self::buildForm)
*
* @return array
*/
public function getQueryKeys($data)
{
return array_keys($data['fields']);
}
/**
* Return array FIELDS keys only.
*
* @return array
*/
private function getFields()
{
return array_keys(self::FIELDS);
}
/**
* Return the results of the query builder.
*
* @param QueryBuilder|\Doctrine\ORM\NativeQuery $qb
* @param mixed[] $data the data from the export's form (added by self::buildForm)
*/
public function getResult($qb, $data)
{
$qb->select('person.id');
$ids = $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
$this->personIds = array_map(fn ($e) => $e['id'], $ids);
$personIdsParameters = '?'.\str_repeat(', ?', count($this->personIds) - 1);
$query = \str_replace('%person_ids%', $personIdsParameters, self::QUERY);
$rsm = new Query\ResultSetMapping();
foreach (self::FIELDS as $name => $type) {
if (null !== $data['fields'][$name]) {
$rsm->addScalarResult($name, $name, $type);
}
}
$nq = $this->entityManager->createNativeQuery($query, $rsm);
$idx = 1;
for ($i = 1; $i <= count($this->personIds); ++$i) {
++$idx;
$nq->setParameter($i, $this->personIds[$i - 1]);
}
$nq->setParameter($idx++, $data['reportdate_min'], 'date');
$nq->setParameter($idx, $data['reportdate_max'], 'date');
return $nq->getResult();
}
/**
* transform the results to viewable and understable string.
*
* The callable will have only one argument: the `value` to translate.
*
* The callable should also be able to return a key `_header`, which
* will contains the header of the column.
*
* The string returned **must** be already translated if necessary,
* **with an exception** for the string returned for `_header`.
*
* Example :
*
* ```
* protected $translator;
*
* public function getLabels($key, array $values, $data)
* {
* return function($value) {
* case $value
* {
* case '_header' :
* return 'my header not translated';
* case true:
* return $this->translator->trans('true');
* case false:
* return $this->translator->trans('false');
* default:
* // this should not happens !
* throw new \LogicException();
* }
* }
* ```
*
* **Note:** Why each string must be translated with an exception for
* the `_header` ? For performance reasons: most of the value will be number
* which do not need to be translated, or value already translated in
* database. But the header must be, in every case, translated.
*
* @param string $key The column key, as added in the query
* @param mixed[] $values The values from the result. if there are duplicates, those might be given twice. Example: array('FR', 'BE', 'CZ', 'FR', 'BE', 'FR')
* @param mixed $data The data from the export's form (as defined in `buildForm`
*/
public function getLabels($key, array $values, $data)
{
return match ($key) {
'birthdate' => function ($value) use ($key) {
if ('_header' === $value) {
return $key;
}
if (null === $value || '' === $value) {
return '';
}
return $value->format('d-m-Y');
},
'countryofbirth' => function ($value) use ($key) {
if ('_header' === $value) {
return $key;
}
return $value['fr'];
},
'gender' => function ($value) use ($key) {
$gend_array = ['man' => 'Homme', 'woman' => 'Femme', 'both' => 'Indéterminé'];
if ('_header' === $value) {
return $key;
}
if (null === $value || '' === $value) {
return '';
}
return $gend_array[$value];
},
default => function ($value) use ($key) {
if ('_header' === $value) {
return $key;
}
return $value;
},
};
}
/**
* Native Query SQL.
*/
public const QUERY = <<<'SQL'
SELECT
p.id as id,
p.firstname as firstname,
p.lastname as lastname,
p.gender as gender,
p.birthdate as birthdate,
p.place_of_birth as placeofbirth,
cn.name as countryofbirth,
cv.formationlevel as formationlevel
FROM
public.chill_person_person AS p
LEFT JOIN chill_job.cv AS cv
ON p.id = cv.person_id
LEFT JOIN public.country AS cn
ON p.countryofbirth_id = cn.id
-- condition 1
WHERE p.id IN (%person_ids%)
-- condition 2
AND (
cv.reportdate
BETWEEN COALESCE(?::date, '1900-01-01')
AND COALESCE(?::date, '2100-01-01')
)
ORDER BY cv.reportdate DESC
SQL;
public function getFormDefaultData(): array
{
return [];
}
}