mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-28 21:16:13 +00:00
396 lines
12 KiB
PHP
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 [];
|
|
}
|
|
}
|