mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-22 23:53:50 +00:00
Merge remote-tracking branch 'origin/rector/rules-symfony' into rector/rules-symfony
This commit is contained in:
@@ -50,7 +50,7 @@ final readonly class UserExportController
|
||||
fn (string $e) => $this->translator->trans('admin.users.export.' . $e),
|
||||
[
|
||||
'id',
|
||||
'username',
|
||||
// 'username',
|
||||
'email',
|
||||
'enabled',
|
||||
'civility_id',
|
||||
|
@@ -0,0 +1,45 @@
|
||||
<?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\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Twig\Environment;
|
||||
|
||||
class UserJobScopeHistoriesController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Environment $engine,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/admin/main/user/{id}/job-scope-history", name="admin_user_job_scope_history")
|
||||
*/
|
||||
public function indexAction(User $user): Response
|
||||
{
|
||||
$jobHistories = $user->getUserJobHistoriesOrdered();
|
||||
$scopeHistories = $user->getMainScopeHistoriesOrdered();
|
||||
|
||||
return new Response(
|
||||
$this->engine->render(
|
||||
'@ChillMain/User/history.html.twig',
|
||||
[
|
||||
'user' => $user,
|
||||
'jobHistories' => $jobHistories,
|
||||
'scopeHistories' => $scopeHistories,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@@ -23,6 +23,8 @@ use Chill\MainBundle\Controller\RegroupmentController;
|
||||
use Chill\MainBundle\Controller\UserController;
|
||||
use Chill\MainBundle\Controller\UserJobApiController;
|
||||
use Chill\MainBundle\Controller\UserJobController;
|
||||
use Chill\MainBundle\Controller\UserJobHistoryController;
|
||||
use Chill\MainBundle\Controller\UserScopeHistoryController;
|
||||
use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
|
||||
use Chill\MainBundle\Doctrine\DQL\Age;
|
||||
use Chill\MainBundle\Doctrine\DQL\Extract;
|
||||
@@ -31,6 +33,7 @@ use Chill\MainBundle\Doctrine\DQL\Greatest;
|
||||
use Chill\MainBundle\Doctrine\DQL\JsonAggregate;
|
||||
use Chill\MainBundle\Doctrine\DQL\JsonbArrayLength;
|
||||
use Chill\MainBundle\Doctrine\DQL\JsonbExistsInArray;
|
||||
use Chill\MainBundle\Doctrine\DQL\JsonBuildObject;
|
||||
use Chill\MainBundle\Doctrine\DQL\JsonExtract;
|
||||
use Chill\MainBundle\Doctrine\DQL\Least;
|
||||
use Chill\MainBundle\Doctrine\DQL\OverlapsI;
|
||||
@@ -56,6 +59,8 @@ use Chill\MainBundle\Entity\Regroupment;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\UserJob;
|
||||
use Chill\MainBundle\Form\CenterType;
|
||||
use Chill\MainBundle\Entity\User\UserJobHistory;
|
||||
use Chill\MainBundle\Entity\User\UserScopeHistory;
|
||||
use Chill\MainBundle\Form\CivilityType;
|
||||
use Chill\MainBundle\Form\CountryType;
|
||||
use Chill\MainBundle\Form\LanguageType;
|
||||
@@ -251,6 +256,7 @@ class ChillMainExtension extends Extension implements
|
||||
'AGGREGATE' => JsonAggregate::class,
|
||||
'REPLACE' => Replace::class,
|
||||
'JSON_EXTRACT' => JsonExtract::class,
|
||||
'JSON_BUILD_OBJECT' => JsonBuildObject::class,
|
||||
],
|
||||
'numeric_functions' => [
|
||||
'JSONB_EXISTS_IN_ARRAY' => JsonbExistsInArray::class,
|
||||
|
53
src/Bundle/ChillMainBundle/Doctrine/DQL/JsonBuildObject.php
Normal file
53
src/Bundle/ChillMainBundle/Doctrine/DQL/JsonBuildObject.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?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\MainBundle\Doctrine\DQL;
|
||||
|
||||
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
use Doctrine\ORM\Query\AST\Node;
|
||||
use Doctrine\ORM\Query\Lexer;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
/**
|
||||
* Return an aggregation of values in a json representation, as a string.
|
||||
*
|
||||
* Internally, this function use the postgresql `jsonb_agg` function. Using
|
||||
* json allow to aggregate data from different types without have to cast them.
|
||||
*/
|
||||
class JsonBuildObject extends FunctionNode
|
||||
{
|
||||
/**
|
||||
* @var array|Node[]
|
||||
*/
|
||||
private array $exprs = [];
|
||||
|
||||
public function getSql(SqlWalker $sqlWalker)
|
||||
{
|
||||
return 'JSONB_BUILD_OBJECT(' . implode(', ', array_map(static fn (Node $expr) => $expr->dispatch($sqlWalker), $this->exprs)) . ')';
|
||||
}
|
||||
|
||||
public function parse(Parser $parser)
|
||||
{
|
||||
$lexer = $parser->getLexer();
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
$this->exprs[] = $parser->ArithmeticPrimary();
|
||||
|
||||
while (Lexer::T_COMMA === $lexer->lookahead['type']) {
|
||||
$parser->match(Lexer::T_COMMA);
|
||||
$this->exprs[] = $parser->ArithmeticPrimary();
|
||||
}
|
||||
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
}
|
@@ -11,10 +11,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Entity;
|
||||
|
||||
use Chill\MainBundle\Entity\User\UserJobHistory;
|
||||
use Chill\MainBundle\Entity\User\UserScopeHistory;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Iterator;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
@@ -113,9 +118,11 @@ class User implements UserInterface, \Stringable
|
||||
private ?Location $mainLocation = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=Scope::class)
|
||||
* @var Collection&Selectable<int, UserScopeHistory>
|
||||
* @ORM\OneToMany(targetEntity=UserScopeHistory::class,
|
||||
* mappedBy="user", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||
*/
|
||||
private ?Scope $mainScope = null;
|
||||
private Collection&Selectable $scopeHistories;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
@@ -130,9 +137,11 @@ class User implements UserInterface, \Stringable
|
||||
private ?string $salt = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=UserJob::class)
|
||||
* @var Collection&Selectable<int, UserJobHistory>
|
||||
* @ORM\OneToMany(targetEntity=UserJobHistory::class,
|
||||
* mappedBy="user", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||
*/
|
||||
private ?UserJob $userJob = null;
|
||||
private Collection&Selectable $jobHistories;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=80)
|
||||
@@ -154,6 +163,8 @@ class User implements UserInterface, \Stringable
|
||||
public function __construct()
|
||||
{
|
||||
$this->groupCenters = new ArrayCollection();
|
||||
$this->scopeHistories = new ArrayCollection();
|
||||
$this->jobHistories = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -252,9 +263,38 @@ class User implements UserInterface, \Stringable
|
||||
return $this->mainLocation;
|
||||
}
|
||||
|
||||
public function getMainScope(): ?Scope
|
||||
public function getMainScope(?DateTimeImmutable $at = null): ?Scope
|
||||
{
|
||||
return $this->mainScope;
|
||||
$at ??= new DateTimeImmutable('now');
|
||||
|
||||
foreach ($this->scopeHistories as $scopeHistory) {
|
||||
if ($at >= $scopeHistory->getStartDate() && (
|
||||
null === $scopeHistory->getEndDate() || $at < $scopeHistory->getEndDate()
|
||||
)) {
|
||||
return $scopeHistory->getScope();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getMainScopeHistories(): Collection
|
||||
{
|
||||
return $this->scopeHistories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayCollection|UserScopeHistory[]
|
||||
*/
|
||||
public function getMainScopeHistoriesOrdered(): ArrayCollection
|
||||
{
|
||||
$scopeHistories = $this->getMainScopeHistories();
|
||||
|
||||
$sortedScopeHistories = $scopeHistories->toArray();
|
||||
|
||||
usort($sortedScopeHistories, fn ($a, $b) => $a->getStartDate() < $b->getStartDate() ? 1 : -1);
|
||||
|
||||
return new ArrayCollection($sortedScopeHistories);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -275,9 +315,38 @@ class User implements UserInterface, \Stringable
|
||||
return $this->salt;
|
||||
}
|
||||
|
||||
public function getUserJob(): ?UserJob
|
||||
public function getUserJob(?DateTimeImmutable $at = null): ?UserJob
|
||||
{
|
||||
return $this->userJob;
|
||||
$at ??= new DateTimeImmutable('now');
|
||||
|
||||
foreach ($this->jobHistories as $jobHistory) {
|
||||
if ($at >= $jobHistory->getStartDate() && (
|
||||
null === $jobHistory->getEndDate() || $at < $jobHistory->getEndDate()
|
||||
)) {
|
||||
return $jobHistory->getJob();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getUserJobHistories(): Collection
|
||||
{
|
||||
return $this->jobHistories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayCollection|UserJobHistory[]
|
||||
*/
|
||||
public function getUserJobHistoriesOrdered(): ArrayCollection
|
||||
{
|
||||
$jobHistories = $this->getUserJobHistories();
|
||||
|
||||
$sortedJobHistories = $jobHistories->toArray();
|
||||
|
||||
usort($sortedJobHistories, fn ($a, $b) => $a->getStartDate() < $b->getStartDate() ? 1 : -1);
|
||||
|
||||
return new ArrayCollection($sortedJobHistories);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -455,7 +524,36 @@ class User implements UserInterface, \Stringable
|
||||
|
||||
public function setMainScope(?Scope $mainScope): User
|
||||
{
|
||||
$this->mainScope = $mainScope;
|
||||
if ($mainScope === $this->getMainScope()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$newScope = new UserScopeHistory();
|
||||
|
||||
$newScope
|
||||
->setStartDate(new DateTimeImmutable('now'))
|
||||
->setScope($mainScope)
|
||||
->setUser($this);
|
||||
|
||||
$this->scopeHistories[] = $newScope;
|
||||
|
||||
$criteria = new Criteria();
|
||||
$criteria->orderBy(['startDate' => Criteria::ASC, 'id' => Criteria::ASC]);
|
||||
|
||||
/** @var Iterator $scopes */
|
||||
$scopes = $this->scopeHistories->matching($criteria)->getIterator();
|
||||
$scopes->rewind();
|
||||
|
||||
do {
|
||||
/** @var UserScopeHistory $current */
|
||||
$current = $scopes->current();
|
||||
$scopes->next();
|
||||
|
||||
if ($scopes->valid()) {
|
||||
$next = $scopes->current();
|
||||
$current->setEndDate($next->getStartDate());
|
||||
}
|
||||
} while ($scopes->valid());
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -486,7 +584,36 @@ class User implements UserInterface, \Stringable
|
||||
|
||||
public function setUserJob(?UserJob $userJob): User
|
||||
{
|
||||
$this->userJob = $userJob;
|
||||
if ($userJob === $this->getUserJob()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$newJob = new UserJobHistory();
|
||||
|
||||
$newJob
|
||||
->setStartDate(new DateTimeImmutable('now'))
|
||||
->setJob($userJob)
|
||||
->setUser($this);
|
||||
|
||||
$this->jobHistories[] = $newJob;
|
||||
|
||||
$criteria = new Criteria();
|
||||
$criteria->orderBy(['startDate' => Criteria::ASC, 'id' => Criteria::ASC]);
|
||||
|
||||
/** @var Iterator $jobs */
|
||||
$jobs = $this->jobHistories->matching($criteria)->getIterator();
|
||||
$jobs->rewind();
|
||||
|
||||
do {
|
||||
/** @var UserJobHistory $current */
|
||||
$current = $jobs->current();
|
||||
$jobs->next();
|
||||
|
||||
if ($jobs->valid()) {
|
||||
$next = $jobs->current();
|
||||
$current->setEndDate($next->getStartDate());
|
||||
}
|
||||
} while ($jobs->valid());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
107
src/Bundle/ChillMainBundle/Entity/User/UserJobHistory.php
Normal file
107
src/Bundle/ChillMainBundle/Entity/User/UserJobHistory.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?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\MainBundle\Entity\User;
|
||||
|
||||
use App\Repository\UserJobHistoryRepository;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\UserJob;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Table(name="chill_main_user_job_history")
|
||||
* @ORM\Entity(repositoryClass=UserJobHistoryRepository::class)
|
||||
*/
|
||||
class UserJobHistory
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="datetime_immutable", nullable=true)
|
||||
*/
|
||||
private ?DateTimeImmutable $endDate = null;
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=UserJob::class)
|
||||
*/
|
||||
private ?UserJob $job = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime_immutable")
|
||||
*/
|
||||
private DateTimeImmutable $startDate;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=User::class)
|
||||
*/
|
||||
private User $user;
|
||||
|
||||
|
||||
public function getEndDate(): ?DateTimeImmutable
|
||||
{
|
||||
return $this->endDate;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getJob(): ?UserJob
|
||||
{
|
||||
return $this->job;
|
||||
}
|
||||
|
||||
public function getStartDate(): ?DateTimeImmutable
|
||||
{
|
||||
return $this->startDate;
|
||||
}
|
||||
|
||||
public function getUser(): User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setEndDate(?DateTimeImmutable $endDate): self
|
||||
{
|
||||
$this->endDate = $endDate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setJob(?UserJob $job): UserJobHistory
|
||||
{
|
||||
$this->job = $job;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setStartDate(DateTimeImmutable $startDate): self
|
||||
{
|
||||
$this->startDate = $startDate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUser(User $user): UserJobHistory
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
107
src/Bundle/ChillMainBundle/Entity/User/UserScopeHistory.php
Normal file
107
src/Bundle/ChillMainBundle/Entity/User/UserScopeHistory.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?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\MainBundle\Entity\User;
|
||||
|
||||
use App\Repository\UserScopeHistoryRepository;
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Table(name="chill_main_user_scope_history")
|
||||
* @ORM\Entity(repositoryClass=UserScopeHistoryRepository::class)
|
||||
*/
|
||||
class UserScopeHistory
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="datetime_immutable", nullable=true)
|
||||
*/
|
||||
private ?DateTimeImmutable $endDate = null;
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=Scope::class)
|
||||
*/
|
||||
private ?Scope $scope = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime_immutable")
|
||||
*/
|
||||
private DateTimeImmutable $startDate;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=User::class)
|
||||
*/
|
||||
private User $user;
|
||||
|
||||
|
||||
public function getEndDate(): ?DateTimeImmutable
|
||||
{
|
||||
return $this->endDate;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getScope(): ?Scope
|
||||
{
|
||||
return $this->scope;
|
||||
}
|
||||
|
||||
public function getStartDate(): ?DateTimeImmutable
|
||||
{
|
||||
return $this->startDate;
|
||||
}
|
||||
|
||||
public function getUser(): User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setEndDate(?DateTimeImmutable $endDate): self
|
||||
{
|
||||
$this->endDate = $endDate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setScope(?Scope $scope): UserScopeHistory
|
||||
{
|
||||
$this->scope = $scope;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setStartDate(DateTimeImmutable $startDate): self
|
||||
{
|
||||
$this->startDate = $startDate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUser(User $user): UserScopeHistory
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
@@ -281,9 +281,9 @@ class ExportAddressHelper
|
||||
};
|
||||
|
||||
case 'country':
|
||||
return function ($value) use ($key) {
|
||||
return function ($value) use ($sanitizedKey, $translationPrefix) {
|
||||
if ('_header' === $value) {
|
||||
return 'export.list.acp' . $key;
|
||||
return $translationPrefix . $sanitizedKey;
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
|
@@ -20,21 +20,64 @@ class UserHelper
|
||||
{
|
||||
public function __construct(private readonly UserRender $userRender, private readonly UserRepositoryInterface $userRepository) {}
|
||||
|
||||
/**
|
||||
* Return a callable that will transform a value into a string representing a user
|
||||
*
|
||||
* The callable may receive as argument:
|
||||
*
|
||||
* - an int or a string, the id of the user;
|
||||
* - a string containing a json which will be decoded, and will have this structure: array{uid: int, d: string}. The job and scopes will be shown at this date
|
||||
*
|
||||
* @param string $key the key of the content
|
||||
* @param array $values the list of values
|
||||
* @param string $header the header's content
|
||||
*/
|
||||
public function getLabel($key, array $values, string $header): callable
|
||||
{
|
||||
return function ($value) use ($header) {
|
||||
return function (null|int|string $value) use ($header) {
|
||||
if ('_header' === $value) {
|
||||
return $header;
|
||||
}
|
||||
|
||||
if (null === $value || null === $user = $this->userRepository->find($value)) {
|
||||
if (null === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->userRender->renderString($user, []);
|
||||
if (is_numeric($value)) {
|
||||
$uid = $value;
|
||||
$date = null;
|
||||
} else {
|
||||
$decode = json_decode($value, true, 512, JSON_THROW_ON_ERROR);
|
||||
$uid = $decode['uid'];
|
||||
|
||||
if (null === $uid) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$date = new \DateTimeImmutable($decode['d']);
|
||||
}
|
||||
|
||||
if (null === $user = $this->userRepository->find($uid)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->userRender->renderString($user, ['at' => $date]);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a callable that will transform a value into a string representing a user
|
||||
*
|
||||
* The callable may receive as argument:
|
||||
*
|
||||
* - an int or a string, the id of the user;
|
||||
* - a string containing a json which will be decoded, and will have this structure: array{uid: int, d: string}. The job and scopes will be shown at this date * @param $key
|
||||
*
|
||||
* @param string $key the key of the element
|
||||
* @param array $values a list of values
|
||||
* @param string $header the header's content
|
||||
* @return callable
|
||||
*/
|
||||
public function getLabelMulti($key, array $values, string $header): callable
|
||||
{
|
||||
return function ($value) use ($header) {
|
||||
@@ -46,31 +89,36 @@ class UserHelper
|
||||
return '';
|
||||
}
|
||||
|
||||
$decoded = json_decode((string) $value, null, 512, JSON_THROW_ON_ERROR);
|
||||
$decoded = json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
if (0 === count($decoded)) {
|
||||
return '';
|
||||
}
|
||||
$asStrings = [];
|
||||
|
||||
return
|
||||
implode(
|
||||
'|',
|
||||
array_map(
|
||||
function (int $userId) {
|
||||
$user = $this->userRepository->find($userId);
|
||||
foreach ($decoded as $userId) {
|
||||
if (is_array($userId)) {
|
||||
$uid = $userId['uid'];
|
||||
$date = new \DateTimeImmutable($userId['d']);
|
||||
} else {
|
||||
$uid = $userId;
|
||||
$date = null;
|
||||
}
|
||||
|
||||
if (null === $user) {
|
||||
return '';
|
||||
}
|
||||
if (null === $uid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $this->userRender->renderString($user, []);
|
||||
},
|
||||
array_unique(
|
||||
array_filter($decoded, static fn (?int $userId) => null !== $userId),
|
||||
SORT_NUMERIC
|
||||
)
|
||||
)
|
||||
);
|
||||
$user = $this->userRepository->find($uid);
|
||||
|
||||
if (null === $user) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$asStrings[$uid] = $this->userRender->renderString($user, ['absence' => false, 'at' => $date]);
|
||||
}
|
||||
|
||||
return implode('|', $asStrings);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,29 @@
|
||||
<?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\MainBundle\Repository\User;
|
||||
|
||||
use Chill\MainBundle\Entity\User\UserJobHistory;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<UserJobHistory>
|
||||
*/
|
||||
class UserJobHistoryRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, UserJobHistory::class);
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
<?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\MainBundle\Repository\User;
|
||||
|
||||
use Chill\MainBundle\Entity\User\UserScopeHistory;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<UserScopeHistory>
|
||||
*/
|
||||
final class UserScopeHistoryRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, UserScopeHistory::class);
|
||||
}
|
||||
}
|
@@ -13,7 +13,8 @@ namespace Chill\MainBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\GroupCenter;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\ORM\AbstractQuery;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\NoResultException;
|
||||
@@ -27,7 +28,11 @@ final readonly class UserRepository implements UserRepositoryInterface
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(private EntityManagerInterface $entityManager)
|
||||
private const FIELDS = ['id', 'email', 'enabled', 'civility_id', 'civility_abbreviation', 'civility_name', 'label', 'mainCenter_id',
|
||||
'mainCenter_name', 'mainScope_id', 'mainScope_name', 'userJob_id', 'userJob_name', 'currentLocation_id', 'currentLocation_name',
|
||||
'mainLocation_id', 'mainLocation_name'];
|
||||
|
||||
public function __construct(private EntityManagerInterface $entityManager, private Connection $connection)
|
||||
{
|
||||
$this->repository = $entityManager->getRepository(User::class);
|
||||
}
|
||||
@@ -75,48 +80,55 @@ final readonly class UserRepository implements UserRepositoryInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $lang
|
||||
* @throws Exception
|
||||
*/
|
||||
public function findAllAsArray(string $lang): iterable
|
||||
{
|
||||
$dql = sprintf(<<<'DQL'
|
||||
$sql = sprintf(<<<'SQL'
|
||||
SELECT
|
||||
u.id AS id,
|
||||
u.id,
|
||||
u.username AS username,
|
||||
u.email,
|
||||
u.email AS email,
|
||||
u.enabled,
|
||||
IDENTITY(u.civility) AS civility_id,
|
||||
JSON_EXTRACT(civility.abbreviation, :lang) AS civility_abbreviation,
|
||||
JSON_EXTRACT(civility.name, :lang) AS civility_name,
|
||||
u.civility_id,
|
||||
civility.abbreviation->>:lang AS civility_abbreviation,
|
||||
civility.name->>:lang AS civility_name,
|
||||
u.label,
|
||||
mainCenter.id AS mainCenter_id,
|
||||
mainCenter.name AS mainCenter_name,
|
||||
IDENTITY(u.mainScope) AS mainScope_id,
|
||||
JSON_EXTRACT(mainScope.name, :lang) AS mainScope_name,
|
||||
IDENTITY(u.userJob) AS userJob_id,
|
||||
JSON_EXTRACT(userJob.label, :lang) AS userJob_name,
|
||||
mainScope.id AS mainScope_id,
|
||||
mainScope.name->>:lang AS mainScope_name,
|
||||
userJob.id AS userJob_id,
|
||||
userJob.label->>:lang AS userJob_name,
|
||||
currentLocation.id AS currentLocation_id,
|
||||
currentLocation.name AS currentLocation_name,
|
||||
mainLocation.id AS mainLocation_id,
|
||||
mainLocation.name AS mainLocation_name,
|
||||
u.absenceStart
|
||||
FROM Chill\MainBundle\Entity\User u
|
||||
LEFT JOIN u.civility civility
|
||||
LEFT JOIN u.currentLocation currentLocation
|
||||
LEFT JOIN u.mainLocation mainLocation
|
||||
LEFT JOIN u.mainCenter mainCenter
|
||||
LEFT JOIN u.mainScope mainScope
|
||||
LEFT JOIN u.userJob userJob
|
||||
ORDER BY u.label
|
||||
DQL);
|
||||
FROM users u
|
||||
LEFT JOIN chill_main_civility civility ON u.civility_id = civility.id
|
||||
LEFT JOIN centers mainCenter ON u.maincenter_id = mainCenter.id
|
||||
LEFT JOIN chill_main_user_job_history userJobHistory ON u.id = userJobHistory.user_id
|
||||
LEFT JOIN chill_main_user_job userJob ON userJobHistory.job_id = userJob.id AND tstzrange(userJobHistory.startdate, userJobHistory.enddate) @> NOW()
|
||||
LEFT JOIN chill_main_user_scope_history userScopeHistory ON u.id = userScopeHistory.user_id AND tstzrange(userScopeHistory.startdate, userScopeHistory.enddate) @> NOW()
|
||||
LEFT JOIN scopes mainScope ON userScopeHistory.scope_id = mainScope.id
|
||||
LEFT JOIN chill_main_location currentLocation ON u.currentlocation_id = currentLocation.id
|
||||
LEFT JOIN chill_main_location mainLocation ON u.mainlocation_id = mainLocation.id
|
||||
ORDER BY u.label, u.id
|
||||
SQL);
|
||||
|
||||
$query = $this->entityManager->createQuery($dql)
|
||||
->setHydrationMode(AbstractQuery::HYDRATE_ARRAY)
|
||||
->setParameter('lang', $lang)
|
||||
;
|
||||
$query = $this->connection->prepare($sql);
|
||||
|
||||
foreach ($query->toIterable() as $u) {
|
||||
yield $u;
|
||||
foreach ($query->executeQuery(['lang' => $lang])->iterateAssociative() as $u) {
|
||||
$converted = [];
|
||||
foreach (self::FIELDS as $f) {
|
||||
$converted[$f] = $u[strtolower($f)];
|
||||
}
|
||||
|
||||
$converted['absenceStart'] = null !== $u['absencestart'] ? new \DateTimeImmutable($u['absencestart']) : null;
|
||||
|
||||
/** @phpstan-ignore-next-line phpstan does not take into account that all required keys will be present */
|
||||
yield $converted;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,10 +1,10 @@
|
||||
<span class="chill-entity entity-user">
|
||||
{{- user.label }}
|
||||
{%- if opts['user_job'] and user.userJob is not null %}
|
||||
<span class="user-job">({{ user.userJob.label|localize_translatable_string }})</span>
|
||||
{%- if opts['user_job'] and user.userJob(opts['at']) is not null %}
|
||||
<span class="user-job">({{ user.userJob(opts['at']).label|localize_translatable_string }})</span>
|
||||
{%- endif -%}
|
||||
{%- if opts['main_scope'] and user.mainScope is not null %}
|
||||
<span class="main-scope">({{ user.mainScope.name|localize_translatable_string }})</span>
|
||||
{%- if opts['main_scope'] and user.mainScope(opts['at']) is not null %}
|
||||
<span class="main-scope">({{ user.mainScope(opts['at']).name|localize_translatable_string }})</span>
|
||||
{%- endif -%}
|
||||
{%- if opts['absence'] and user.isAbsent %}
|
||||
<span class="badge bg-danger rounded-pill" title="{{ 'absence.Absent'|trans|escape('html_attr') }}">{{ 'absence.A'|trans }}</span>
|
||||
|
@@ -4,20 +4,20 @@
|
||||
|
||||
{% block admin_content -%}
|
||||
{% embed '@ChillMain/CRUD/_index.html.twig' %}
|
||||
|
||||
|
||||
{% block index_header %}
|
||||
<h1>{{ 'List circles'|trans }}</h1>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block filter_order %}{% endblock %}
|
||||
|
||||
|
||||
{% block table_entities_thead_tr %}
|
||||
<th>id</th>
|
||||
<th>{{ 'Name'|trans }}</th>
|
||||
<th>{{ 'Active'|trans }}</th>
|
||||
<th>{{ 'Actions'|trans }}</th>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block table_entities_tbody %}
|
||||
{% for entity in entities %}
|
||||
<tr>
|
||||
@@ -40,9 +40,9 @@
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block pagination %}{% endblock %}
|
||||
|
||||
|
||||
{% block list_actions %}
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class='cancel'>
|
||||
|
@@ -1,7 +1,8 @@
|
||||
{% extends '@ChillMain/Admin/layoutWithVerticalMenu.html.twig' %}
|
||||
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
|
||||
|
||||
{% block admin_content -%}
|
||||
{% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
|
||||
|
||||
{% block crud_content_after_form %}
|
||||
{% if access_permissions_group_list %}
|
||||
<h2 class="mt-5">{{ 'Permissions granted'|trans }}</h2>
|
||||
@@ -54,5 +55,14 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content_form_actions_save_and_show %}{% endblock %}
|
||||
|
||||
{% block content_form_actions_view %}
|
||||
<li>
|
||||
<a href="{{ path('admin_user_job_scope_history', {id: entity.id}) }}" class="btn btn-show">
|
||||
{{ 'admin.users.job_scope_histories.Show history'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
{% endembed %}
|
||||
{% endblock %}
|
||||
|
@@ -0,0 +1,81 @@
|
||||
{% extends '@ChillMain/Admin/layoutWithVerticalMenu.html.twig' %}
|
||||
|
||||
{% block title %}{{ 'admin.users.job_scope_histories.index.histories'|trans }}{% endblock title %}
|
||||
|
||||
{% block admin_content %}
|
||||
<h2>{{ user.usernameCanonical }}</h2>
|
||||
<h1>{{ 'admin.users.job_scope_histories.index.histories'|trans }}</h1>
|
||||
|
||||
<h3 class="mt-5">{{ 'admin.users.job_scope_histories.index.job_history.title'|trans }}</h3>
|
||||
<table class="records_list table table-bordered border-dark">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-25">{{ 'admin.users.job_scope_histories.index.job_history.start'|trans }}</th>
|
||||
<th class="w-25">{{ 'admin.users.job_scope_histories.index.job_history.end'|trans }}</th>
|
||||
<th>{{ 'admin.users.job_scope_histories.index.job_history.job'|trans }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for entity in jobHistories %}
|
||||
<tr>
|
||||
<td>{{ entity.startDate|format_datetime('medium') }}</td>
|
||||
<td>
|
||||
{% if entity.endDate is not null %}
|
||||
{{ entity.endDate|format_datetime('medium') }}
|
||||
{% else %}
|
||||
<i class="opacity-50">{{ "admin.users.job_scope_histories.index.job_history.today"|trans }}</i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if entity.job %}
|
||||
{{ entity.job.label|localize_translatable_string }}
|
||||
{% else %}
|
||||
<i class="opacity-50">{{ 'admin.users.job_scope_histories.index.job_history.undefined'|trans }}</i>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 class="mt-5">{{ 'admin.users.job_scope_histories.index.scope_history.title'|trans }}</h3>
|
||||
<table class="records_list table table-bordered border-dark">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-25">{{ 'admin.users.job_scope_histories.index.scope_history.start'|trans }}</th>
|
||||
<th class="w-25">{{ 'admin.users.job_scope_histories.index.scope_history.end'|trans }}</th>
|
||||
<th>{{ 'admin.users.job_scope_histories.index.scope_history.scope'|trans }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for entity in scopeHistories %}
|
||||
<tr>
|
||||
<td>{{ entity.startDate|format_datetime('medium') }}</td>
|
||||
<td>
|
||||
{% if entity.endDate is not null %}
|
||||
{{ entity.endDate|format_datetime('medium') }}
|
||||
{% else %}
|
||||
<i class="opacity-50">{{ "admin.users.job_scope_histories.index.scope_history.today"|trans }}</i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if entity.scope %}
|
||||
{{ entity.scope.name|localize_translatable_string }}
|
||||
{% else %}
|
||||
<i class="opacity-50">{{ 'admin.users.job_scope_histories.index.scope_history.undefined'|trans }}</i>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class='cancel'>
|
||||
<a href="{{ path('chill_crud_admin_user_edit', {id: user.id}) }}" class="btn btn-cancel">
|
||||
{{'admin.users.job_scope_histories.index.Back to user job'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{% endblock %}
|
@@ -56,7 +56,9 @@
|
||||
</li>
|
||||
<li>
|
||||
<span class="dt">métier:</span>
|
||||
{% if entity.userJob %}{{ entity.userJob.label|localize_translatable_string }}{% endif %}
|
||||
{% if entity.userJob %}
|
||||
{{ entity.userJob.label|localize_translatable_string }}
|
||||
{% endif %}
|
||||
</li>
|
||||
<li>
|
||||
<span class="dt">cercle/centre:</span>
|
||||
|
@@ -35,7 +35,7 @@
|
||||
|
||||
{% block actions_before %}
|
||||
<li class='cancel'>
|
||||
<a href="{{ path('chill_main_admin_central') }}" class="btn btn-cancel">{{'Back to the admin'|trans}}</a>
|
||||
<a href="{{ path('chill_main_admin_central') }}" class="btn btn-cancel">{{'Back to the admin'|trans }}</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
|
@@ -34,7 +34,7 @@ final readonly class ScopeResolverDispatcher
|
||||
|
||||
/**
|
||||
* @param array|null $options
|
||||
* @return iterable<Scope>|Scope|null
|
||||
* @return Scope|iterable<Scope>|Scope|null
|
||||
*/
|
||||
public function resolveScope(mixed $entity, ?array $options = []): null|\Chill\MainBundle\Entity\Scope|iterable
|
||||
{
|
||||
|
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\Service\EntityInfo;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class ViewEntityInfoManager
|
||||
{
|
||||
@@ -21,6 +22,7 @@ class ViewEntityInfoManager
|
||||
*/
|
||||
private readonly iterable $vienEntityInfoProviders,
|
||||
private readonly Connection $connection,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
public function synchronizeOnDB(): void
|
||||
@@ -28,6 +30,8 @@ class ViewEntityInfoManager
|
||||
$this->connection->transactional(function (Connection $conn): void {
|
||||
foreach ($this->vienEntityInfoProviders as $viewProvider) {
|
||||
foreach ($this->createOrReplaceViewSQL($viewProvider, $viewProvider->getViewName()) as $sql) {
|
||||
$this->logger->debug("Will execute create view sql", ['sql' => $sql]);
|
||||
$this->logger->debug($sql);
|
||||
$conn->executeQuery($sql);
|
||||
}
|
||||
}
|
||||
@@ -41,7 +45,7 @@ class ViewEntityInfoManager
|
||||
{
|
||||
return [
|
||||
"DROP VIEW IF EXISTS {$viewName}",
|
||||
sprintf("CREATE VIEW {$viewName} AS %s", $viewProvider->getViewQuery())
|
||||
sprintf("CREATE OR REPLACE VIEW {$viewName} AS %s", $viewProvider->getViewQuery())
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -28,10 +28,14 @@ class UserRender implements ChillEntityRenderInterface
|
||||
'main_scope' => true,
|
||||
'user_job' => true,
|
||||
'absence' => true,
|
||||
'at' => null,
|
||||
];
|
||||
|
||||
public function __construct(private readonly TranslatableStringHelper $translatableStringHelper, private readonly \Twig\Environment $engine, private readonly TranslatorInterface $translator) {}
|
||||
|
||||
/**
|
||||
* @param mixed $entity
|
||||
*/
|
||||
public function renderBox($entity, array $options): string
|
||||
{
|
||||
$opts = array_merge(self::DEFAULT_OPTIONS, $options);
|
||||
@@ -42,20 +46,23 @@ class UserRender implements ChillEntityRenderInterface
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $entity
|
||||
*/
|
||||
public function renderString($entity, array $options): string
|
||||
{
|
||||
$opts = array_merge(self::DEFAULT_OPTIONS, $options);
|
||||
|
||||
$str = $entity->getLabel();
|
||||
|
||||
if (null !== $entity->getUserJob() && $opts['user_job']) {
|
||||
if (null !== $entity->getUserJob($opts['at']) && $opts['user_job']) {
|
||||
$str .= ' (' . $this->translatableStringHelper
|
||||
->localize($entity->getUserJob()->getLabel()) . ')';
|
||||
->localize($entity->getUserJob($opts['at'])->getLabel()) . ')';
|
||||
}
|
||||
|
||||
if (null !== $entity->getMainScope() && $opts['main_scope']) {
|
||||
if (null !== $entity->getMainScope($opts['at']) && $opts['main_scope']) {
|
||||
$str .= ' (' . $this->translatableStringHelper
|
||||
->localize($entity->getMainScope()->getName()) . ')';
|
||||
->localize($entity->getMainScope($opts['at'])->getName()) . ')';
|
||||
}
|
||||
|
||||
if ($entity->isAbsent() && $opts['absence']) {
|
||||
|
@@ -0,0 +1,61 @@
|
||||
<?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\MainBundle\Tests\Doctrine\DQL;
|
||||
|
||||
use Chill\MainBundle\Entity\Address;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\AbstractQuery;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class JsonBuildObjectTest extends KernelTestCase
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
self::bootKernel();
|
||||
$this->entityManager = self::$container->get(EntityManagerInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideQueries
|
||||
*/
|
||||
public function testQuery(string $sql, array $params, array $paramType): void
|
||||
{
|
||||
$query = $this->entityManager->createQuery($sql);
|
||||
|
||||
foreach ($params as $k => $v) {
|
||||
$query->setParameter($k, $v, $paramType[$k]);
|
||||
}
|
||||
$query->setMaxResults(1);
|
||||
|
||||
$result = $query->getResult(AbstractQuery::HYDRATE_ARRAY);
|
||||
|
||||
self::assertIsArray($result);
|
||||
}
|
||||
|
||||
public function provideQueries(): iterable
|
||||
{
|
||||
yield ["SELECT JSON_BUILD_OBJECT(1, 2, 3, 4) FROM " . Address::class . " a", [], []];
|
||||
yield ["SELECT JSON_BUILD_OBJECT('st', a.street, 'sn', a.streetNumber) FROM " . Address::class . ' a', [], []];
|
||||
// next query make the test fails. But we do not need it for now.
|
||||
//yield ["SELECT JSON_BUILD_OBJECT(a.street, :param), LOWER(:param) FROM " . Address::class . " a", ['param' => 1], ['param' => Types::INTEGER]];
|
||||
}
|
||||
|
||||
}
|
71
src/Bundle/ChillMainBundle/Tests/Entity/UserTest.php
Normal file
71
src/Bundle/ChillMainBundle/Tests/Entity/UserTest.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?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 Entity;
|
||||
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\UserJob;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class UserTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
public function testMainScopeHistory()
|
||||
{
|
||||
$user = new User();
|
||||
$scopeA = new Scope();
|
||||
$scopeB = new Scope();
|
||||
|
||||
$user->setMainScope($scopeA);
|
||||
$user->setMainScope($scopeB);
|
||||
|
||||
// 1. check getMainScope get now scopeB, not scopeA
|
||||
self::assertSame($scopeB, $user->getMainScope());
|
||||
|
||||
// 2. get scopeA history, check endDate is not null
|
||||
self::assertNotNull(
|
||||
$user
|
||||
->getMainScopeHistories()
|
||||
->filter(fn (User\UserScopeHistory $userScopeHistory) => $userScopeHistory->getScope() === $scopeA)
|
||||
->first()->getEndDate()
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public function testUserJobHistory()
|
||||
{
|
||||
$user = new User();
|
||||
$jobA = new UserJob();
|
||||
$jobB = new UserJob();
|
||||
|
||||
$user->setUserJob($jobA);
|
||||
$user->setUserJob($jobB);
|
||||
|
||||
// 1. check getUserJob get now jobB, not jobA
|
||||
self::assertSame($jobB, $user->getUserJob());
|
||||
|
||||
// 2. get jobA history, check endDate is not null
|
||||
self::assertNotNull(
|
||||
$user
|
||||
->getUserJobHistories()
|
||||
->filter(fn (User\UserJobHistory $userJobHistory) => $userJobHistory->getJob() === $jobA)
|
||||
->first()->getEndDate()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -18,7 +18,7 @@ final class Version20230711152947 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add data to ';
|
||||
return 'Add last execution data to cronjon execution table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
|
136
src/Bundle/ChillMainBundle/migrations/Version20230913114115.php
Normal file
136
src/Bundle/ChillMainBundle/migrations/Version20230913114115.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?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\Migrations\Main;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20230913114115 extends AbstractMigration
|
||||
{
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// drop job_history
|
||||
$this->addSql('ALTER TABLE chill_main_user_job_history DROP CONSTRAINT user_job_history_endate_null_or_after_startdate');
|
||||
$this->addSql('ALTER TABLE chill_main_user_job_history DROP CONSTRAINT user_job_history_not_overlaps');
|
||||
$this->addSql('DROP SEQUENCE chill_main_user_job_history_id_seq CASCADE');
|
||||
$this->addSql('ALTER TABLE chill_main_user_job_history DROP CONSTRAINT FK_4E3BF4DDBE04EA9');
|
||||
$this->addSql('ALTER TABLE chill_main_user_job_history DROP CONSTRAINT FK_4E3BF4DDA76ED395');
|
||||
$this->addSql('DROP TABLE chill_main_user_job_history');
|
||||
|
||||
$this->addSql('ALTER TABLE users ADD userjob_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE users ADD CONSTRAINT fk_1483a5e964b65c5b FOREIGN KEY (userjob_id) REFERENCES chill_main_user_job (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('CREATE INDEX idx_1483a5e964b65c5b ON users (userjob_id)');
|
||||
|
||||
// drop scope_history
|
||||
$this->addSql('ALTER TABLE chill_main_user_scope_history DROP CONSTRAINT user_scope_history_endate_null_or_after_startdate');
|
||||
$this->addSql('ALTER TABLE chill_main_user_scope_history DROP CONSTRAINT user_scope_history_not_overlaps');
|
||||
$this->addSql('DROP SEQUENCE chill_main_user_scope_history_id_seq CASCADE');
|
||||
$this->addSql('ALTER TABLE chill_main_user_scope_history DROP CONSTRAINT FK_48B969D7682B5931');
|
||||
$this->addSql('ALTER TABLE chill_main_user_scope_history DROP CONSTRAINT FK_48B969D7A76ED395');
|
||||
$this->addSql('DROP TABLE chill_main_user_scope_history');
|
||||
|
||||
$this->addSql('ALTER TABLE users ADD mainscope_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE users ADD CONSTRAINT fk_1483a5e9115e73f3 FOREIGN KEY (mainscope_id) REFERENCES scopes (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('CREATE INDEX idx_1483a5e9115e73f3 ON users (mainscope_id)');
|
||||
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add new entities UserScopeHistory and UserJobHistory';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// create scope_history
|
||||
$this->addSql('CREATE SEQUENCE chill_main_user_scope_history_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE TABLE chill_main_user_scope_history ('
|
||||
. 'id INT NOT NULL,'
|
||||
. 'scope_id INT DEFAULT NULL,'
|
||||
. 'user_id INT DEFAULT NULL,'
|
||||
. 'endDate TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,'
|
||||
. 'startDate TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,'
|
||||
. 'PRIMARY KEY(id))');
|
||||
|
||||
$this->addSql('CREATE INDEX IDX_48B969D7682B5931 ON chill_main_user_scope_history (scope_id)');
|
||||
$this->addSql('CREATE INDEX IDX_48B969D7A76ED395 ON chill_main_user_scope_history (user_id)');
|
||||
|
||||
$this->addSql('COMMENT ON COLUMN chill_main_user_scope_history.endDate IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_main_user_scope_history.startDate IS \'(DC2Type:datetime_immutable)\'');
|
||||
|
||||
$this->addSql('ALTER TABLE chill_main_user_scope_history ADD CONSTRAINT FK_48B969D7682B5931 '
|
||||
. 'FOREIGN KEY (scope_id) REFERENCES scopes (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
|
||||
$this->addSql('ALTER TABLE chill_main_user_scope_history ADD CONSTRAINT FK_48B969D7A76ED395 '
|
||||
. 'FOREIGN KEY (user_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
|
||||
$this->addSql('ALTER TABLE chill_main_user_scope_history '
|
||||
. 'ADD CONSTRAINT user_scope_history_not_overlaps '
|
||||
. 'EXCLUDE USING GIST (user_id with =, tsrange(startDate, endDate) with &&) '
|
||||
. 'DEFERRABLE INITIALLY DEFERRED');
|
||||
|
||||
$this->addSql('ALTER TABLE chill_main_user_scope_history '
|
||||
. 'ADD CONSTRAINT user_scope_history_endate_null_or_after_startdate '
|
||||
. 'CHECK (startDate <= endDate OR endDate IS NULL)');
|
||||
|
||||
// insert user scope_history datas
|
||||
$this->addSql('INSERT INTO chill_main_user_scope_history (id, startDate, endDate, user_id, scope_id) '
|
||||
. 'SELECT nextval(\'chill_main_user_scope_history_id_seq\'), \'1970-01-01\'::date, NULL, users.id, mainscope_id '
|
||||
. 'FROM users');
|
||||
|
||||
// remove mainscope
|
||||
$this->addSql('ALTER TABLE users DROP CONSTRAINT fk_1483a5e9115e73f3');
|
||||
$this->addSql('ALTER TABLE users DROP mainscope_id');
|
||||
|
||||
|
||||
// create job_history
|
||||
$this->addSql('CREATE SEQUENCE chill_main_user_job_history_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE TABLE chill_main_user_job_history ('
|
||||
. 'id INT NOT NULL,'
|
||||
. 'job_id INT DEFAULT NULL,'
|
||||
. 'user_id INT DEFAULT NULL,'
|
||||
. 'endDate TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,'
|
||||
. 'startDate TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,'
|
||||
. 'PRIMARY KEY(id))');
|
||||
|
||||
$this->addSql('CREATE INDEX IDX_4E3BF4DDBE04EA9 ON chill_main_user_job_history (job_id)');
|
||||
$this->addSql('CREATE INDEX IDX_4E3BF4DDA76ED395 ON chill_main_user_job_history (user_id)');
|
||||
|
||||
$this->addSql('COMMENT ON COLUMN chill_main_user_job_history.endDate IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_main_user_job_history.startDate IS \'(DC2Type:datetime_immutable)\'');
|
||||
|
||||
$this->addSql('ALTER TABLE chill_main_user_job_history ADD CONSTRAINT FK_4E3BF4DDBE04EA9 '
|
||||
. 'FOREIGN KEY (job_id) REFERENCES chill_main_user_job (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
|
||||
$this->addSql('ALTER TABLE chill_main_user_job_history ADD CONSTRAINT FK_4E3BF4DDA76ED395 '
|
||||
. 'FOREIGN KEY (user_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
|
||||
$this->addSql('ALTER TABLE chill_main_user_job_history '
|
||||
. 'ADD CONSTRAINT user_job_history_not_overlaps '
|
||||
. 'EXCLUDE USING GIST (user_id with =, tsrange(startDate, endDate) with &&) '
|
||||
. 'DEFERRABLE INITIALLY DEFERRED');
|
||||
|
||||
$this->addSql('ALTER TABLE chill_main_user_job_history '
|
||||
. 'ADD CONSTRAINT user_job_history_endate_null_or_after_startdate '
|
||||
. 'CHECK (startDate <= endDate OR endDate IS NULL)');
|
||||
|
||||
// insert user job_history datas
|
||||
$this->addSql('INSERT INTO chill_main_user_job_history (id, startDate, endDate, user_id, job_id) '
|
||||
. 'SELECT nextval(\'chill_main_user_job_history_id_seq\'), \'1970-01-01\'::date, NULL, users.id, userjob_id '
|
||||
. 'FROM users');
|
||||
|
||||
// remove userjob
|
||||
$this->addSql('ALTER TABLE users DROP CONSTRAINT fk_1483a5e964b65c5b');
|
||||
$this->addSql('ALTER TABLE users DROP userjob_id');
|
||||
|
||||
}
|
||||
}
|
@@ -651,3 +651,24 @@ admin:
|
||||
center_name: Centre
|
||||
permissionsGroup_id: Identifiant du groupe de permissions
|
||||
permissionsGroup_name: Groupe de permissions
|
||||
job_scope_histories:
|
||||
Show history: Voir l'historique
|
||||
index:
|
||||
histories: Historique des services et des métiers
|
||||
Back to user job: Revenir à l'utilisateur
|
||||
job_history:
|
||||
title: Historique des métiers
|
||||
start: Du
|
||||
end: Jusque
|
||||
today: en cours
|
||||
undefined: non défini
|
||||
user: Utilisateur
|
||||
job: Métier
|
||||
scope_history:
|
||||
title: Historique des services
|
||||
start: Du
|
||||
end: Jusque
|
||||
today: en cours
|
||||
undefined: non défini
|
||||
user: Utilisateur
|
||||
scope: Service
|
||||
|
Reference in New Issue
Block a user