mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-12 21:34:25 +00:00
Merge branch 'features/household-editor' into features/household-validation
This commit is contained in:
commit
af740fd87d
121
src/Bundle/ChillMainBundle/Resources/public/img/draggable.svg
Normal file
121
src/Bundle/ChillMainBundle/Resources/public/img/draggable.svg
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
id="svg34"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 32 32">
|
||||||
|
<metadata
|
||||||
|
id="metadata40">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<defs
|
||||||
|
id="defs38" />
|
||||||
|
<rect
|
||||||
|
id="rect2"
|
||||||
|
x="0"
|
||||||
|
y="4"
|
||||||
|
width="4"
|
||||||
|
height="4" />
|
||||||
|
<rect
|
||||||
|
id="rect4"
|
||||||
|
x="0"
|
||||||
|
y="12"
|
||||||
|
width="4"
|
||||||
|
height="4" />
|
||||||
|
<rect
|
||||||
|
id="rect6"
|
||||||
|
x="0"
|
||||||
|
y="20"
|
||||||
|
width="4"
|
||||||
|
height="4" />
|
||||||
|
<rect
|
||||||
|
id="rect8"
|
||||||
|
x="0"
|
||||||
|
y="28"
|
||||||
|
width="4"
|
||||||
|
height="4" />
|
||||||
|
<rect
|
||||||
|
id="rect10"
|
||||||
|
x="8"
|
||||||
|
y="4"
|
||||||
|
width="4"
|
||||||
|
height="4" />
|
||||||
|
<rect
|
||||||
|
id="rect12"
|
||||||
|
x="8"
|
||||||
|
y="12"
|
||||||
|
width="4"
|
||||||
|
height="4" />
|
||||||
|
<rect
|
||||||
|
id="rect14"
|
||||||
|
x="8"
|
||||||
|
y="20"
|
||||||
|
width="4"
|
||||||
|
height="4" />
|
||||||
|
<rect
|
||||||
|
id="rect16"
|
||||||
|
x="8"
|
||||||
|
y="28"
|
||||||
|
width="4"
|
||||||
|
height="4" />
|
||||||
|
<rect
|
||||||
|
id="rect18"
|
||||||
|
x="16"
|
||||||
|
y="4"
|
||||||
|
width="4"
|
||||||
|
height="4" />
|
||||||
|
<rect
|
||||||
|
id="rect20"
|
||||||
|
x="16"
|
||||||
|
y="12"
|
||||||
|
width="4"
|
||||||
|
height="4" />
|
||||||
|
<rect
|
||||||
|
id="rect22"
|
||||||
|
x="16"
|
||||||
|
y="20"
|
||||||
|
width="4"
|
||||||
|
height="4" />
|
||||||
|
<rect
|
||||||
|
id="rect24"
|
||||||
|
x="16"
|
||||||
|
y="28"
|
||||||
|
width="4"
|
||||||
|
height="4" />
|
||||||
|
<rect
|
||||||
|
id="rect26"
|
||||||
|
x="24"
|
||||||
|
y="4"
|
||||||
|
width="4"
|
||||||
|
height="4" />
|
||||||
|
<rect
|
||||||
|
id="rect28"
|
||||||
|
x="24"
|
||||||
|
y="12"
|
||||||
|
width="4"
|
||||||
|
height="4" />
|
||||||
|
<rect
|
||||||
|
id="rect30"
|
||||||
|
x="24"
|
||||||
|
y="20"
|
||||||
|
width="4"
|
||||||
|
height="4" />
|
||||||
|
<rect
|
||||||
|
id="rect32"
|
||||||
|
x="24"
|
||||||
|
y="28"
|
||||||
|
width="4"
|
||||||
|
height="4" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
88
src/Bundle/ChillMainBundle/Resources/public/js/date.js
Normal file
88
src/Bundle/ChillMainBundle/Resources/public/js/date.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* Some utils for manipulating dates
|
||||||
|
*
|
||||||
|
* **WARNING** experimental
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the date to local ISO date, like YYYY-mm-dd
|
||||||
|
*
|
||||||
|
* The date is valid for the same timezone as the date's locale
|
||||||
|
*
|
||||||
|
* Do not take time into account
|
||||||
|
*
|
||||||
|
* **Experimental**
|
||||||
|
*/
|
||||||
|
const dateToISO = (date) => {
|
||||||
|
return [
|
||||||
|
this.$store.state.startDate.getFullYear(),
|
||||||
|
(this.$store.state.startDate.getMonth() + 1).toString().padStart(2, '0'),
|
||||||
|
this.$store.state.startDate.getDate().toString().padStart(2, '0')
|
||||||
|
].join('-');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a date object from iso string formatted as YYYY-mm-dd
|
||||||
|
*
|
||||||
|
* **Experimental**
|
||||||
|
*/
|
||||||
|
const ISOToDate = (str) => {
|
||||||
|
let
|
||||||
|
[year, month, day] = str.split('-');
|
||||||
|
|
||||||
|
return new Date(year, month-1, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a date object from iso string formatted as YYYY-mm-dd:HH:MM:ss+01:00
|
||||||
|
*
|
||||||
|
* **Experimental**
|
||||||
|
*/
|
||||||
|
const ISOToDatetime = (str) => {
|
||||||
|
console.log(str);
|
||||||
|
let
|
||||||
|
[cal, times] = str.split('T'),
|
||||||
|
[year, month, date] = cal.split('-'),
|
||||||
|
[time, timezone] = cal.split(times.charAt(9)),
|
||||||
|
[hours, minutes, seconds] = cal.split(':')
|
||||||
|
;
|
||||||
|
|
||||||
|
return new Date(year, month-1, date, hours, minutes, seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a date to ISO8601, valid for usage in api
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const datetimeToISO = (date) => {
|
||||||
|
let cal, time, offset;
|
||||||
|
cal = [
|
||||||
|
date.getFullYear(),
|
||||||
|
(date.getMonth() + 1).toString().padStart(2, '0'),
|
||||||
|
date.getDate().toString().padStart(2, '0')
|
||||||
|
].join('-');
|
||||||
|
|
||||||
|
time = [
|
||||||
|
date.getHours().toString().padStart(2, '0'),
|
||||||
|
date.getMinutes().toString().padStart(2, '0'),
|
||||||
|
date.getSeconds().toString().padStart(2, '0')
|
||||||
|
].join(':');
|
||||||
|
|
||||||
|
offset = [
|
||||||
|
date.getTimezoneOffset() <= 0 ? '+' : '-',
|
||||||
|
Math.abs(Math.floor(date.getTimezoneOffset() / 60)).toString().padStart(2, '0'),
|
||||||
|
':',
|
||||||
|
Math.abs(date.getTimezoneOffset() % 60).toString().padStart(2, '0'),
|
||||||
|
].join('');
|
||||||
|
|
||||||
|
let x = cal + 'T' + time + offset;
|
||||||
|
|
||||||
|
return x;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
dateToISO,
|
||||||
|
ISOToDate,
|
||||||
|
ISOToDatetime,
|
||||||
|
datetimeToISO
|
||||||
|
};
|
@ -17,7 +17,7 @@
|
|||||||
// @import "bootstrap/scss/grid";
|
// @import "bootstrap/scss/grid";
|
||||||
// @import "bootstrap/scss/tables";
|
// @import "bootstrap/scss/tables";
|
||||||
// @import "bootstrap/scss/forms";
|
// @import "bootstrap/scss/forms";
|
||||||
// @import "bootstrap/scss/buttons";
|
@import "bootstrap/scss/buttons";
|
||||||
@import "bootstrap/scss/transitions";
|
@import "bootstrap/scss/transitions";
|
||||||
// @import "bootstrap/scss/dropdown";
|
// @import "bootstrap/scss/dropdown";
|
||||||
// @import "bootstrap/scss/button-group";
|
// @import "bootstrap/scss/button-group";
|
||||||
@ -30,7 +30,7 @@
|
|||||||
// @import "bootstrap/scss/pagination";
|
// @import "bootstrap/scss/pagination";
|
||||||
@import "bootstrap/scss/badge";
|
@import "bootstrap/scss/badge";
|
||||||
// @import "bootstrap/scss/jumbotron";
|
// @import "bootstrap/scss/jumbotron";
|
||||||
// @import "bootstrap/scss/alert";
|
@import "bootstrap/scss/alert";
|
||||||
// @import "bootstrap/scss/progress";
|
// @import "bootstrap/scss/progress";
|
||||||
// @import "bootstrap/scss/media";
|
// @import "bootstrap/scss/media";
|
||||||
// @import "bootstrap/scss/list-group";
|
// @import "bootstrap/scss/list-group";
|
||||||
|
@ -121,7 +121,7 @@ div.flex-bloc {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
div.item-row {
|
& > div.item-row {
|
||||||
flex-grow: 1; flex-shrink: 1; flex-basis: auto;
|
flex-grow: 1; flex-shrink: 1; flex-basis: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -62,7 +62,7 @@ const messages = {
|
|||||||
person: "un nouvel usager",
|
person: "un nouvel usager",
|
||||||
thirdparty: "un nouveau tiers"
|
thirdparty: "un nouveau tiers"
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
namespace Chill\PersonBundle\Controller;
|
namespace Chill\PersonBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\Household\Position;
|
||||||
|
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\PersonBundle\Entity\Household\Household;
|
||||||
use Chill\PersonBundle\Form\HouseholdMemberType;
|
use Chill\PersonBundle\Form\HouseholdMemberType;
|
||||||
use Chill\PersonBundle\Entity\Household\HouseholdMember;
|
use Chill\PersonBundle\Entity\Household\HouseholdMember;
|
||||||
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
||||||
@ -31,7 +35,7 @@ class HouseholdMemberController extends ApiController
|
|||||||
/**
|
/**
|
||||||
* @Route(
|
* @Route(
|
||||||
* "/api/1.0/person/household/members/move.{_format}",
|
* "/api/1.0/person/household/members/move.{_format}",
|
||||||
* name="chill_person_household_members_move"
|
* name="chill_api_person_household_members_move"
|
||||||
* )
|
* )
|
||||||
*/
|
*/
|
||||||
public function move(Request $request, $_format): Response
|
public function move(Request $request, $_format): Response
|
||||||
@ -50,13 +54,94 @@ class HouseholdMemberController extends ApiController
|
|||||||
//
|
//
|
||||||
$em = $this->getDoctrine()->getManager();
|
$em = $this->getDoctrine()->getManager();
|
||||||
|
|
||||||
|
// if new household, persist it
|
||||||
|
if (
|
||||||
|
$editor->hasHousehold()
|
||||||
|
&&
|
||||||
|
FALSE === $em->contains($editor->getHousehold())
|
||||||
|
) {
|
||||||
|
$em->persist($editor->getHousehold());
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($editor->getPersistable() as $el) {
|
foreach ($editor->getPersistable() as $el) {
|
||||||
$em->persist($el);
|
$em->persist($el);
|
||||||
}
|
}
|
||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
||||||
return $this->json($editor->getHousehold(), Response::HTTP_OK, [], [
|
return $this->json($editor->getHousehold(), Response::HTTP_OK, [], ["groups" => ["read"]]);
|
||||||
"groups" => ["read"],
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route for showing an editor to leave a household.
|
||||||
|
*
|
||||||
|
* Possibles arguments are:
|
||||||
|
*
|
||||||
|
* * persons[]: an id of the person to add to the form
|
||||||
|
* * household: the id of the destination household
|
||||||
|
* * allow_leave_without_household: if present, the editor will allow
|
||||||
|
* to leave household without joining another
|
||||||
|
*
|
||||||
|
* @Route(
|
||||||
|
* "/{_locale}/person/household/members/editor",
|
||||||
|
* name="chill_person_household_members_editor"
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function editor(Request $request)
|
||||||
|
{
|
||||||
|
$em = $this->getDoctrine()->getManager();
|
||||||
|
|
||||||
|
if ($request->query->has('persons')) {
|
||||||
|
$ids = $request->query->get('persons', []);
|
||||||
|
|
||||||
|
if (0 === count($ids)) {
|
||||||
|
throw new BadRequestExceptions("parameters persons in query ".
|
||||||
|
"is not an array or empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
$persons = $em->getRepository(Person::class)
|
||||||
|
->findById($ids)
|
||||||
|
;
|
||||||
|
|
||||||
|
foreach ($persons as $person) {
|
||||||
|
$this->denyAccessUnlessGranted(PersonVoter::SEE, $person,
|
||||||
|
"You are not allowed to see person with id {$person->getId()}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($householdId = $request->query->get('household', false)) {
|
||||||
|
$household = $em->getRepository(Household::class)
|
||||||
|
->find($householdId)
|
||||||
|
;
|
||||||
|
$allowHouseholdCreate = false;
|
||||||
|
$allowHouseholdSearch = false;
|
||||||
|
$allowLeaveWithoutHousehold = false;
|
||||||
|
|
||||||
|
if (NULL === $household) {
|
||||||
|
throw $this->createNotFoundException('household not found');
|
||||||
|
}
|
||||||
|
// TODO ACL on household
|
||||||
|
}
|
||||||
|
|
||||||
|
$positions = $this->getDoctrine()->getManager()
|
||||||
|
->getRepository(Position::class)
|
||||||
|
->findAll()
|
||||||
|
;
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'persons' => $persons ?? false ?
|
||||||
|
$this->getSerializer()->normalize($persons, 'json', [ 'groups' => [ 'read' ]]) : [],
|
||||||
|
'household' => $household ?? false ?
|
||||||
|
$this->getSerializer()->normalize($household, 'json', [ 'groups' => [ 'read' ]]) : null,
|
||||||
|
'positions' =>
|
||||||
|
$this->getSerializer()->normalize($positions, 'json', [ 'groups' => [ 'read' ]]),
|
||||||
|
'allowHouseholdCreate' => $allowHouseholdCreate ?? true,
|
||||||
|
'allowHouseholdSearch' => $allowHouseholdSearch ?? true,
|
||||||
|
'allowLeaveWithoutHousehold' => $allowLeaveWithoutHousehold ?? $request->query->has('allow_leave_without_household'),
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->render('@ChillPerson/Household/members_editor.html.twig', [
|
||||||
|
'data' => $data
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,6 +514,25 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
|
|||||||
],
|
],
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'class' => \Chill\PersonBundle\Entity\Household\Household::class,
|
||||||
|
'name' => 'household',
|
||||||
|
'base_path' => '/api/1.0/person/household',
|
||||||
|
// TODO: acl
|
||||||
|
'base_role' => 'ROLE_USER',
|
||||||
|
'actions' => [
|
||||||
|
'_entity' => [
|
||||||
|
'methods' => [
|
||||||
|
Request::METHOD_GET => true,
|
||||||
|
Request::METHOD_HEAD => true,
|
||||||
|
],
|
||||||
|
'roles' => [
|
||||||
|
Request::METHOD_GET => 'ROLE_USER',
|
||||||
|
Request::METHOD_HEAD => 'ROLE_USER',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
]
|
||||||
|
],
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -116,8 +116,7 @@ class Household
|
|||||||
{
|
{
|
||||||
$criteria = new Criteria();
|
$criteria = new Criteria();
|
||||||
$expr = Criteria::expr();
|
$expr = Criteria::expr();
|
||||||
$date = $now === null ? (new \DateTimeImmutable('now')) : $now;
|
$date = $now === null ? (new \DateTimeImmutable('today')) : $now;
|
||||||
|
|
||||||
|
|
||||||
$criteria
|
$criteria
|
||||||
->where($expr->orX(
|
->where($expr->orX(
|
||||||
@ -149,7 +148,7 @@ class Household
|
|||||||
{
|
{
|
||||||
$criteria = new Criteria();
|
$criteria = new Criteria();
|
||||||
$expr = Criteria::expr();
|
$expr = Criteria::expr();
|
||||||
$date = $now === null ? (new \DateTimeImmutable('now')) : $now;
|
$date = $now === null ? (new \DateTimeImmutable('today')) : $now;
|
||||||
|
|
||||||
$criteria
|
$criteria
|
||||||
->where(
|
->where(
|
||||||
|
@ -50,9 +50,9 @@ class HouseholdMember
|
|||||||
private ?string $comment = NULL;
|
private ?string $comment = NULL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="boolean")
|
* @ORM\Column(type="boolean", name="sharedhousehold")
|
||||||
*/
|
*/
|
||||||
private bool $sharedHousehold = false;
|
private bool $shareHousehold = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="boolean", options={"default": false})
|
* @ORM\Column(type="boolean", options={"default": false})
|
||||||
@ -98,7 +98,7 @@ class HouseholdMember
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->position = $position;
|
$this->position = $position;
|
||||||
$this->sharedHousehold = $position->getShareHousehold();
|
$this->shareHousehold = $position->getShareHousehold();
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -144,7 +144,7 @@ class HouseholdMember
|
|||||||
*/
|
*/
|
||||||
public function getShareHousehold(): ?bool
|
public function getShareHousehold(): ?bool
|
||||||
{
|
{
|
||||||
return $this->sharedHousehold;
|
return $this->shareHousehold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ use Doctrine\ORM\Mapping as ORM;
|
|||||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Entity(repositoryClass=PositionRepository::class)
|
* @ORM\Entity
|
||||||
* @ORM\Table(name="chill_person_household_position")
|
* @ORM\Table(name="chill_person_household_position")
|
||||||
* @Serializer\DiscriminatorMap(typeProperty="type", mapping={
|
* @Serializer\DiscriminatorMap(typeProperty="type", mapping={
|
||||||
* "household_position"=Position::class
|
* "household_position"=Position::class
|
||||||
@ -25,21 +25,25 @@ class Position
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="json")
|
* @ORM\Column(type="json")
|
||||||
|
* @Serializer\Groups({ "read" })
|
||||||
*/
|
*/
|
||||||
private array $label = [];
|
private array $label = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="boolean")
|
* @ORM\Column(type="boolean")
|
||||||
|
* @Serializer\Groups({ "read" })
|
||||||
*/
|
*/
|
||||||
private bool $shareHouseHold = true;
|
private bool $shareHouseHold = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="boolean")
|
* @ORM\Column(type="boolean")
|
||||||
|
* @Serializer\Groups({ "read" })
|
||||||
*/
|
*/
|
||||||
private bool $allowHolder = false;
|
private bool $allowHolder = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="float")
|
* @ORM\Column(type="float")
|
||||||
|
* @Serializer\Groups({ "read" })
|
||||||
*/
|
*/
|
||||||
private float $ordering = 0.00;
|
private float $ordering = 0.00;
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ namespace Chill\PersonBundle\Entity;
|
|||||||
use ArrayIterator;
|
use ArrayIterator;
|
||||||
use Chill\MainBundle\Entity\Center;
|
use Chill\MainBundle\Entity\Center;
|
||||||
use Chill\MainBundle\Entity\Country;
|
use Chill\MainBundle\Entity\Country;
|
||||||
|
use Chill\PersonBundle\Entity\Household\Household;
|
||||||
use Chill\PersonBundle\Entity\MaritalStatus;
|
use Chill\PersonBundle\Entity\MaritalStatus;
|
||||||
use Chill\PersonBundle\Entity\Household\HouseholdMember;
|
use Chill\PersonBundle\Entity\Household\HouseholdMember;
|
||||||
use Chill\MainBundle\Entity\HasCenterInterface;
|
use Chill\MainBundle\Entity\HasCenterInterface;
|
||||||
@ -281,6 +282,11 @@ class Person implements HasCenterInterface
|
|||||||
*/
|
*/
|
||||||
private Collection $householdParticipations;
|
private Collection $householdParticipations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache the computation of household
|
||||||
|
*/
|
||||||
|
private array $currentHouseholdAt = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Person constructor.
|
* Person constructor.
|
||||||
*
|
*
|
||||||
@ -1202,4 +1208,43 @@ class Person implements HasCenterInterface
|
|||||||
{
|
{
|
||||||
return $this->householdParticipations;
|
return $this->householdParticipations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCurrentHousehold(?\DateTimeImmutable $at = null): ?Household
|
||||||
|
{
|
||||||
|
$criteria = new Criteria();
|
||||||
|
$expr = Criteria::expr();
|
||||||
|
$date = NULL === $at ? new \DateTimeImmutable('now') : $at;
|
||||||
|
$datef = $date->format('Y-m-d');
|
||||||
|
|
||||||
|
if (
|
||||||
|
NULL !== ($this->currentHouseholdAt[$datef] ?? NULL)) {
|
||||||
|
return $this->currentHouseholdAt[$datef];
|
||||||
|
}
|
||||||
|
|
||||||
|
$criteria
|
||||||
|
->where(
|
||||||
|
$expr->andX(
|
||||||
|
$expr->lte('startDate', $date),
|
||||||
|
$expr->orX(
|
||||||
|
$expr->isNull('endDate'),
|
||||||
|
$expr->gte('endDate', $date)
|
||||||
|
),
|
||||||
|
$expr->eq('shareHousehold', true)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$participations = $this->getHouseholdParticipations()
|
||||||
|
->matching($criteria)
|
||||||
|
;
|
||||||
|
|
||||||
|
return $participations->count() > 0 ?
|
||||||
|
$this->currentHouseholdAt[$datef] = $participations->first()
|
||||||
|
->getHousehold()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isSharingHousehold(?\DateTimeImmutable $at = null): bool
|
||||||
|
{
|
||||||
|
return NULL !== $this->getCurrentHousehold($at);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace Chill\PersonBundle\Household;
|
namespace Chill\PersonBundle\Household;
|
||||||
|
|
||||||
use Symfony\Component\Validator\ConstraintViolationListInterface;
|
use Symfony\Component\Validator\ConstraintViolationListInterface;
|
||||||
|
use Doctrine\Common\Collections\Criteria;
|
||||||
use Chill\PersonBundle\Entity\Household\HouseholdMember;
|
use Chill\PersonBundle\Entity\Household\HouseholdMember;
|
||||||
use Chill\PersonBundle\Entity\Household\Position;
|
use Chill\PersonBundle\Entity\Household\Position;
|
||||||
use Chill\PersonBundle\Entity\Household\Household;
|
use Chill\PersonBundle\Entity\Household\Household;
|
||||||
@ -13,12 +14,12 @@ use Symfony\Component\Validator\Validator\ValidatorInterface;
|
|||||||
class MembersEditor
|
class MembersEditor
|
||||||
{
|
{
|
||||||
private ValidatorInterface $validator;
|
private ValidatorInterface $validator;
|
||||||
private Household $household;
|
private ?Household $household = null;
|
||||||
|
|
||||||
private array $persistables = [];
|
private array $persistables = [];
|
||||||
private array $membershipsAffected = [];
|
private array $membershipsAffected = [];
|
||||||
|
|
||||||
public function __construct(ValidatorInterface $validator, Household $household)
|
public function __construct(ValidatorInterface $validator, ?Household $household)
|
||||||
{
|
{
|
||||||
$this->validation = $validator;
|
$this->validation = $validator;
|
||||||
$this->household = $household;
|
$this->household = $household;
|
||||||
@ -35,9 +36,9 @@ class MembersEditor
|
|||||||
->setPerson($person)
|
->setPerson($person)
|
||||||
->setPosition($position)
|
->setPosition($position)
|
||||||
->setHolder($holder)
|
->setHolder($holder)
|
||||||
->setHousehold($this->household)
|
|
||||||
->setComment($comment)
|
->setComment($comment)
|
||||||
;
|
;
|
||||||
|
$this->household->addMember($membership);
|
||||||
|
|
||||||
if ($position->getShareHousehold()) {
|
if ($position->getShareHousehold()) {
|
||||||
foreach ($person->getHouseholdParticipations() as $participation) {
|
foreach ($person->getHouseholdParticipations() as $participation) {
|
||||||
@ -62,6 +63,33 @@ class MembersEditor
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function leaveMovement(
|
||||||
|
\DateTimeImmutable $date,
|
||||||
|
Person $person
|
||||||
|
): self {
|
||||||
|
$criteria = new Criteria();
|
||||||
|
$expr = Criteria::expr();
|
||||||
|
|
||||||
|
$criteria->where(
|
||||||
|
$expr->andX(
|
||||||
|
$expr->lt('startDate', $date),
|
||||||
|
$expr->isNull('endDate', $date)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$participations = $person->getHouseholdParticipations()
|
||||||
|
->matching($criteria)
|
||||||
|
;
|
||||||
|
|
||||||
|
foreach ($participations as $participation) {
|
||||||
|
$participation->setEndDate($date);
|
||||||
|
$this->membershipsAffected[] = $participation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function validate(): ConstraintViolationListInterface
|
public function validate(): ConstraintViolationListInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -72,8 +100,13 @@ class MembersEditor
|
|||||||
return $this->persistables;
|
return $this->persistables;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHousehold(): Household
|
public function getHousehold(): ?Household
|
||||||
{
|
{
|
||||||
return $this->household;
|
return $this->household;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function hasHousehold(): bool
|
||||||
|
{
|
||||||
|
return $this->household !== null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ class MembersEditorFactory
|
|||||||
$this->validator = $validator;
|
$this->validator = $validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createEditor(Household $household): MembersEditor
|
public function createEditor(?Household $household = null): MembersEditor
|
||||||
{
|
{
|
||||||
return new MembersEditor($this->validator, $household);
|
return new MembersEditor($this->validator, $household);
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
namespace Chill\PersonBundle\Repository\Household;
|
namespace Chill\PersonBundle\Repository\Household;
|
||||||
|
|
||||||
use Chill\PersonBundle\Entity\Household\Position;
|
use Chill\PersonBundle\Entity\Household\Position;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
//use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
//use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @method Position|null find($id, $lockMode = null, $lockVersion = null)
|
* @method Position|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
@ -12,11 +14,20 @@ use Doctrine\Persistence\ManagerRegistry;
|
|||||||
* @method Position[] findAll()
|
* @method Position[] findAll()
|
||||||
* @method Position[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
* @method Position[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
*/
|
*/
|
||||||
class PositionRepository extends ServiceEntityRepository
|
final class PositionRepository
|
||||||
{
|
{
|
||||||
public function __construct(ManagerRegistry $registry)
|
private EntityRepository $repository;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $entityManager)
|
||||||
{
|
{
|
||||||
parent::__construct($registry, Position::class);
|
$this->repository = $entityManager->getRepository(Position::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Position[]
|
||||||
|
*/
|
||||||
|
public function findAll(): array
|
||||||
|
{
|
||||||
|
return $this->repository->findAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,5 +59,3 @@ div.list-household-members--summary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<household></household>
|
||||||
|
<concerned></concerned>
|
||||||
|
<dates></dates>
|
||||||
|
<confirmation></confirmation>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import { mapState } from 'vuex';
|
||||||
|
import Concerned from './components/Concerned.vue';
|
||||||
|
import Household from './components/Household.vue';
|
||||||
|
import Dates from './components/Dates.vue';
|
||||||
|
import Confirmation from './components/Confirmation.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'App',
|
||||||
|
components: {
|
||||||
|
Concerned,
|
||||||
|
Household,
|
||||||
|
Dates,
|
||||||
|
Confirmation,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// for debugging purpose
|
||||||
|
// (not working)
|
||||||
|
//...mapState({
|
||||||
|
// 'concerned', 'household', 'positions'
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
*/
|
||||||
|
const householdMove = (payload) => {
|
||||||
|
const url = `/api/1.0/person/household/members/move.json`;
|
||||||
|
console.log(payload);
|
||||||
|
console.log(JSON.stringify(payload));
|
||||||
|
|
||||||
|
return fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
throw Error('Error with testing move');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const householdMoveTest = (payload) => {
|
||||||
|
const url = `/api/1.0/person/household/members/move/test.json`;
|
||||||
|
return fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.status === 422) {
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
if (response.ok) {
|
||||||
|
// return an empty array if ok
|
||||||
|
return new Promise((resolve, reject) => resolve({ violations: [] }) );
|
||||||
|
}
|
||||||
|
throw Error('Error with testing move');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
householdMove,
|
||||||
|
householdMoveTest
|
||||||
|
};
|
@ -0,0 +1,215 @@
|
|||||||
|
<template>
|
||||||
|
<h2>{{ $t('household_members_editor.concerned.title') }}</h2>
|
||||||
|
|
||||||
|
<h3 v-if="needsPositionning">
|
||||||
|
{{ $t('household_members_editor.concerned.persons_to_positionnate') }}
|
||||||
|
</h3>
|
||||||
|
<h3 v-else>
|
||||||
|
{{ $t('household_members_editor.concerned.persons_leaving') }}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div v-if="noPerson">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
{{ $t('household_members_editor.add_at_least_onePerson') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="allPersonsPositionnated">
|
||||||
|
<span class="chill-no-data-statement">{{ $t('household_members_editor.all_positionnated') }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="flex-table list-household-members">
|
||||||
|
<div v-for="conc in concUnpositionned"
|
||||||
|
class="item-bloc"
|
||||||
|
v-bind:key="conc.person.id"
|
||||||
|
draggable="true"
|
||||||
|
@dragstart="onStartDragConcern($event, conc.person.id)"
|
||||||
|
>
|
||||||
|
<div class="item-row person">
|
||||||
|
<div class="item-col box-person">
|
||||||
|
<div>
|
||||||
|
<img src="~ChillMainAssets/img/draggable.svg" class="drag-icon" />
|
||||||
|
<person :person="conc.person"></person>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ $t('person.born', {'gender': conc.person.gender} ) }}
|
||||||
|
{{ $d(conc.person.birthdate.datetime, 'short') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="item-col box-where">
|
||||||
|
<ul class="list-content fa-ul">
|
||||||
|
<li>
|
||||||
|
<i class="fa fa-li fa-map-marker"></i>
|
||||||
|
<span class="chill-no-data-statement">Sans adresse</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="needsPositionning" class="item-row move_to">
|
||||||
|
<div class="item-col">
|
||||||
|
|
||||||
|
<p class="move_hint">{{ $t('household_members_editor.concerned.move_to') }}:</p>
|
||||||
|
|
||||||
|
<template
|
||||||
|
v-for="position in positions"
|
||||||
|
>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-primary"
|
||||||
|
@click="moveToPosition(conc.person.id, position.id)"
|
||||||
|
>
|
||||||
|
{{ position.label.fr }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<button v-if="conc.allowRemove" @click="removeConcerned(conc)" class="btn bt-primary">
|
||||||
|
{{ $t('household_members_editor.remove_concerned') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<add-persons
|
||||||
|
buttonTitle="household_members_editor.concerned.add_persons"
|
||||||
|
modalTitle="household_members_editor.concerned.search"
|
||||||
|
v-bind:key="addPersons.key"
|
||||||
|
v-bind:options="addPersons.options"
|
||||||
|
@addNewPersons="addNewPersons"
|
||||||
|
ref="addPersons"> <!-- to cast child method -->
|
||||||
|
</add-persons>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div v-if="needsPositionning" class="positions">
|
||||||
|
<div
|
||||||
|
v-for="position in positions"
|
||||||
|
>
|
||||||
|
<h3>{{ position.label.fr }}</h3>
|
||||||
|
<div class="flex-table list-household-members">
|
||||||
|
<member-details
|
||||||
|
v-for="conc in concByPosition(position.id)"
|
||||||
|
v-bind:key="conc.person.id"
|
||||||
|
v-bind:conc="conc"
|
||||||
|
>
|
||||||
|
</member-details>
|
||||||
|
<div
|
||||||
|
class="droppable_zone"
|
||||||
|
@drop="onDropConcern($event, position.id)"
|
||||||
|
@dragover.prevent
|
||||||
|
@dragenter.prevent
|
||||||
|
>
|
||||||
|
{{ $t('household_members_editor.drop_persons_here', {'position': position.label.fr }) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div.person {
|
||||||
|
cursor: move;
|
||||||
|
|
||||||
|
* {
|
||||||
|
cursor: move
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-icon {
|
||||||
|
height: 1.1em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.droppable_zone {
|
||||||
|
background-color: var(--chill-llight-gray);
|
||||||
|
color: white;
|
||||||
|
font-size: large;
|
||||||
|
text-align: center;
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
padding: 1em;
|
||||||
|
background: linear-gradient(to top, var(--chill-light-gray), 30%, var(--chill-llight-gray));
|
||||||
|
}
|
||||||
|
|
||||||
|
.move_to {
|
||||||
|
.move_hint {
|
||||||
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.400rem 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue';
|
||||||
|
import Person from 'ChillPersonAssets/vuejs/_components/Person/Person.vue';
|
||||||
|
import MemberDetails from './MemberDetails.vue';
|
||||||
|
import { ISOToDatetime } from 'ChillMainAssets/js/date.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Concerned',
|
||||||
|
components: {
|
||||||
|
AddPersons,
|
||||||
|
MemberDetails,
|
||||||
|
Person,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'concUnpositionned',
|
||||||
|
'positions',
|
||||||
|
'concByPosition',
|
||||||
|
'needsPositionning'
|
||||||
|
]),
|
||||||
|
noPerson () {
|
||||||
|
return this.$store.getters.persons.length === 0;
|
||||||
|
},
|
||||||
|
allPersonsPositionnated () {
|
||||||
|
return this.$store.getters.persons.length > 0
|
||||||
|
&& this.$store.getters.concUnpositionned.length === 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
addPersons: {
|
||||||
|
key: 'household_members_editor_concerned',
|
||||||
|
options: {
|
||||||
|
type: ['person'],
|
||||||
|
priority: null,
|
||||||
|
uniq: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addNewPersons({ selected, modal }) {
|
||||||
|
selected.forEach(function(item) {
|
||||||
|
this.$store.dispatch('addConcerned', item.result);
|
||||||
|
}, this);
|
||||||
|
this.$refs.addPersons.resetSearch(); // to cast child method
|
||||||
|
modal.showModal = false;
|
||||||
|
},
|
||||||
|
onStartDragConcern(evt, person_id) {
|
||||||
|
evt.dataTransfer.dropEffect = 'move'
|
||||||
|
evt.dataTransfer.effectAllowed = 'move'
|
||||||
|
evt.dataTransfer.setData('application/x.person', person_id)
|
||||||
|
},
|
||||||
|
onDropConcern(evt, position_id) {
|
||||||
|
const person_id = Number(evt.dataTransfer.getData('application/x.person'));
|
||||||
|
this.moveToPosition(person_id, position_id);
|
||||||
|
},
|
||||||
|
moveToPosition(person_id, position_id) {
|
||||||
|
this.$store.dispatch('markPosition', { person_id, position_id });
|
||||||
|
|
||||||
|
},
|
||||||
|
removeConcerned(conc) {
|
||||||
|
this.$store.dispatch('removeConcerned', conc);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<div v-if="hasWarnings" class="alert alert-warning">
|
||||||
|
{{ $t('household_members_editor.confirmation.there_are_warnings') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p v-if="hasWarnings">
|
||||||
|
{{ $t('household_members_editor.confirmation.check_those_items') }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li v-for="(msg, index) in warnings">
|
||||||
|
{{ $t(msg.m, msg.a) }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="record_actions sticky-form-buttons">
|
||||||
|
<li>
|
||||||
|
<button class="sc-button bt-save" :disabled="hasWarnings" @click="confirm">
|
||||||
|
{{ $t('household_members_editor.confirmation.save') }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'vuex';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Confirmation',
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
warnings: (state) => state.warnings,
|
||||||
|
hasNoWarnings: (state) => state.warnings.length === 0,
|
||||||
|
hasWarnings: (state) => state.warnings.length > 0,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
confirm() {
|
||||||
|
this.$store.dispatch('confirm');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<h2>{{ $t('household_members_editor.dates_title') }}</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="start_date">
|
||||||
|
{{ $t('household_members_editor.dates.start_date') }}
|
||||||
|
</label>
|
||||||
|
<input type="date" v-model="startDate" />
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Dates',
|
||||||
|
computed: {
|
||||||
|
startDate: {
|
||||||
|
get() {
|
||||||
|
return [
|
||||||
|
this.$store.state.startDate.getFullYear(),
|
||||||
|
(this.$store.state.startDate.getMonth() + 1).toString().padStart(2, '0'),
|
||||||
|
this.$store.state.startDate.getDate().toString().padStart(2, '0')
|
||||||
|
].join('-');
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
let
|
||||||
|
[year, month, day] = value.split('-'),
|
||||||
|
dValue = new Date(year, month-1, day);
|
||||||
|
|
||||||
|
this.$store.dispatch('setStartDate', dValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<h2>{{ $t('household_members_editor.household_part') }}</h2>
|
||||||
|
|
||||||
|
<div v-if="hasHousehold">
|
||||||
|
<span v-if="isHouseholdNew">
|
||||||
|
{{ $t('household_members_editor.household.new_household') }}
|
||||||
|
</span>
|
||||||
|
<div v-else>
|
||||||
|
Ménage existant
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="isForceLeaveWithoutHousehold">
|
||||||
|
{{ $t('household_members_editor.household.will_leave_any_household') }}
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="alert alert-info">{{ $t('household_members_editor.household.no_household_choose_one') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul v-if="allowChangeHousehold" class="record_actions">
|
||||||
|
<li v-if="allowHouseholdCreate">
|
||||||
|
<button class="sc-button bt-create" @click="createHousehold">
|
||||||
|
{{ $t('household_members_editor.household.create_household') }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li v-if="allowHouseholdSearch">
|
||||||
|
<button class="sc-button">
|
||||||
|
<i class="fa fa-search"></i>{{ $t('household_members_editor.household.search_household') }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li v-if="allowLeaveWithoutHousehold" >
|
||||||
|
<button @click="forceLeaveWithoutHousehold" class="sc-button bt-orange">
|
||||||
|
<i class="fa fa-sign-out"></i>{{ $t('household_members_editor.household.leave_without_household') }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Household',
|
||||||
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'hasHousehold',
|
||||||
|
'isHouseholdNew',
|
||||||
|
]),
|
||||||
|
household() {
|
||||||
|
return this.$store.household;
|
||||||
|
},
|
||||||
|
allowHouseholdCreate() {
|
||||||
|
return this.$store.state.allowHouseholdCreate;
|
||||||
|
},
|
||||||
|
allowHouseholdSearch() {
|
||||||
|
return this.$store.state.allowHouseholdSearch;
|
||||||
|
},
|
||||||
|
allowLeaveWithoutHousehold() {
|
||||||
|
return this.$store.state.allowLeaveWithoutHousehold;
|
||||||
|
},
|
||||||
|
allowChangeHousehold() {
|
||||||
|
return this.allowHouseholdCreate || this.allowHouseholdSearch ||
|
||||||
|
this.allowLeaveWithoutHousehold;
|
||||||
|
},
|
||||||
|
isForceLeaveWithoutHousehold() {
|
||||||
|
return this.$store.state.forceLeaveWithoutHousehold;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
createHousehold() {
|
||||||
|
this.$store.dispatch('createHousehold');
|
||||||
|
},
|
||||||
|
forceLeaveWithoutHousehold() {
|
||||||
|
this.$store.dispatch('forceLeaveWithoutHousehold');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
@ -0,0 +1,126 @@
|
|||||||
|
<template>
|
||||||
|
<div class="item-bloc">
|
||||||
|
<div class="item-row person">
|
||||||
|
<div class="item-col box-person">
|
||||||
|
<div>
|
||||||
|
<person :person="conc.person"></person>
|
||||||
|
<span v-if="isHolder" class="badge badge-primary holder">
|
||||||
|
{{ $t('household_members_editor.holder') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>{{ $t('person.born', {'gender': conc.person.gender} ) }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-col box-where">
|
||||||
|
<ul class="list-content fa-ul">
|
||||||
|
<li>
|
||||||
|
<i class="fa fa-li fa-map-marker"></i>
|
||||||
|
<span class="chill-no-data-statement">Sans adresse</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-row comment">
|
||||||
|
<ckeditor :editor="editor" v-model="comment" tag-name="textarea"></ckeditor>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-row participation-details">
|
||||||
|
<div v-if="conc.position.allowHolder" class="action">
|
||||||
|
<button class="btn" :class="{ 'btn-primary': isHolder, 'btn-secondary': !isHolder}" @click="toggleHolder">
|
||||||
|
{{ $t(isHolder ? 'household_members_editor.is_holder' : 'household_members_editor.is_not_holder') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button @click="removePosition" class="btn btn-outline-primary">
|
||||||
|
{{ $t('household_members_editor.remove_position', {position: conc.position.label.fr}) }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button v-if="conc.allowRemove" @click="removeConcerned" class="btn btn-primary">
|
||||||
|
{{ $t('household_members_editor.remove_concerned') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
.drag-icon {
|
||||||
|
height: 1.1em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
div.participation-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row !important;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.action {
|
||||||
|
align-self: flex-start;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.holder {
|
||||||
|
display: inline;
|
||||||
|
vertical-align: super;
|
||||||
|
font-size: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import Person from 'ChillPersonAssets/vuejs/_components/Person/Person.vue';
|
||||||
|
import CKEditor from '@ckeditor/ckeditor5-vue';
|
||||||
|
import ClassicEditor from 'ChillMainAssets/modules/ckeditor5/index.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'MemberDetails',
|
||||||
|
components: {
|
||||||
|
Person,
|
||||||
|
ckeditor: CKEditor.component,
|
||||||
|
},
|
||||||
|
props: [
|
||||||
|
'conc'
|
||||||
|
],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: ClassicEditor,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters( [
|
||||||
|
'concByPersonId'
|
||||||
|
]),
|
||||||
|
isHolder() {
|
||||||
|
return this.conc.holder;
|
||||||
|
},
|
||||||
|
comment: {
|
||||||
|
get() {
|
||||||
|
return this.conc.comment;
|
||||||
|
},
|
||||||
|
set(text) {
|
||||||
|
console.log('set comment');
|
||||||
|
console.log('comment', text);
|
||||||
|
|
||||||
|
this.$store.dispatch('setComment', { conc: this.conc, comment: text });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleHolder() {
|
||||||
|
this.$store.dispatch('toggleHolder', this.conc);
|
||||||
|
},
|
||||||
|
removePosition() {
|
||||||
|
this.$store.dispatch('removePosition', this.conc);
|
||||||
|
},
|
||||||
|
removeConcerned() {
|
||||||
|
this.$store.dispatch('removeConcerned', this.conc);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
</script>
|
@ -0,0 +1,16 @@
|
|||||||
|
import { createApp } from 'vue';
|
||||||
|
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
|
||||||
|
import { appMessages } from './js/i18n';
|
||||||
|
import { store } from './store';
|
||||||
|
|
||||||
|
import App from './App.vue';
|
||||||
|
|
||||||
|
const i18n = _createI18n(appMessages);
|
||||||
|
|
||||||
|
const app = createApp({
|
||||||
|
template: `<app></app>`,
|
||||||
|
})
|
||||||
|
.use(store)
|
||||||
|
.use(i18n)
|
||||||
|
.component('app', App)
|
||||||
|
.mount('#household_members_editor');
|
@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n'
|
||||||
|
|
||||||
|
const appMessages = {
|
||||||
|
fr: {
|
||||||
|
household_members_editor: {
|
||||||
|
household: {
|
||||||
|
no_household_choose_one: "Aucun ménage de destination. Choisissez un ménage.",
|
||||||
|
new_household: "Nouveau ménage",
|
||||||
|
create_household: "Créer un ménage",
|
||||||
|
search_household: "Chercher un ménage",
|
||||||
|
will_leave_any_household: "Ne rejoignent pas de ménage",
|
||||||
|
leave_without_household: "Sans nouveau ménage"
|
||||||
|
},
|
||||||
|
concerned: {
|
||||||
|
title: "Usagers concernés",
|
||||||
|
add_persons: "Ajouter d'autres usagers",
|
||||||
|
search: "Rechercher des usagers",
|
||||||
|
move_to: "Déplacer vers",
|
||||||
|
persons_to_positionnate: 'Usagers à positionner',
|
||||||
|
persons_leaving: "Usagers quittant leurs ménages",
|
||||||
|
},
|
||||||
|
drop_persons_here: "Glissez-déposez ici les usagers pour la position \"{position}\"",
|
||||||
|
all_positionnated: "Tous les usagers sont positionnés",
|
||||||
|
holder: "Titulaire",
|
||||||
|
is_holder: "Sera titulaire",
|
||||||
|
is_not_holder: "Ne sera pas titulaire",
|
||||||
|
remove_position: "Retirer des {position}",
|
||||||
|
remove_concerned: "Ne plus transférer",
|
||||||
|
household_part: "Ménage de destination",
|
||||||
|
dates_title: "Période de validité",
|
||||||
|
dates: {
|
||||||
|
start_date: "Début de validité",
|
||||||
|
end_date: "Fin de validité",
|
||||||
|
},
|
||||||
|
confirmation: {
|
||||||
|
save: "Enregistrer",
|
||||||
|
there_are_warnings: "Impossible de valider actuellement",
|
||||||
|
check_those_items: "Veuillez corriger les éléments suivants",
|
||||||
|
},
|
||||||
|
give_a_position_to_every_person: "Indiquez une position pour chaque usager concerné",
|
||||||
|
add_destination: "Indiquez un ménage de destination",
|
||||||
|
add_at_least_onePerson: "Indiquez au moins un usager à transférer",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(appMessages.fr, personMessages.fr);
|
||||||
|
|
||||||
|
export {
|
||||||
|
appMessages
|
||||||
|
};
|
@ -0,0 +1,239 @@
|
|||||||
|
import { createStore } from 'vuex';
|
||||||
|
import { householdMove, householdMoveTest } from './../api.js';
|
||||||
|
import { datetimeToISO } from 'ChillMainAssets/js/date.js';
|
||||||
|
|
||||||
|
const debug = process.env.NODE_ENV !== 'production';
|
||||||
|
|
||||||
|
const concerned = window.household_members_editor_data.persons.map(p => {
|
||||||
|
return {
|
||||||
|
person: p,
|
||||||
|
position: null,
|
||||||
|
allowRemove: false,
|
||||||
|
holder: false,
|
||||||
|
comment: "",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = createStore({
|
||||||
|
strict: debug,
|
||||||
|
state: {
|
||||||
|
concerned,
|
||||||
|
household: window.household_members_editor_data.household,
|
||||||
|
positions: window.household_members_editor_data.positions,
|
||||||
|
startDate: new Date(),
|
||||||
|
allowHouseholdCreate: window.household_members_editor_data.allowHouseholdCreate,
|
||||||
|
allowHouseholdSearch: window.household_members_editor_data.allowHouseholdSearch,
|
||||||
|
allowLeaveWithoutHousehold: window.household_members_editor_data.allowLeaveWithoutHousehold,
|
||||||
|
forceLeaveWithoutHousehold: false,
|
||||||
|
warnings: [],
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
isHouseholdNew(state) {
|
||||||
|
return !Number.isInteger(state.household.id);
|
||||||
|
},
|
||||||
|
hasHousehold(state) {
|
||||||
|
return state.household !== null;
|
||||||
|
},
|
||||||
|
persons(state) {
|
||||||
|
return state.concerned.map(conc => conc.person);
|
||||||
|
},
|
||||||
|
concUnpositionned(state) {
|
||||||
|
return state.concerned
|
||||||
|
.filter(conc => conc.position === null)
|
||||||
|
;
|
||||||
|
},
|
||||||
|
positions(state) {
|
||||||
|
return state.positions;
|
||||||
|
},
|
||||||
|
personByPosition: (state) => (position_id) => {
|
||||||
|
return state.concerned
|
||||||
|
.filter(conc =>
|
||||||
|
conc.position !== null ? conc.position.id === position_id : false
|
||||||
|
)
|
||||||
|
.map(conc => conc.person)
|
||||||
|
;
|
||||||
|
},
|
||||||
|
concByPosition: (state) => (position_id) => {
|
||||||
|
return state.concerned
|
||||||
|
.filter(conc =>
|
||||||
|
conc.position !== null ? conc.position.id === position_id : false
|
||||||
|
)
|
||||||
|
;
|
||||||
|
},
|
||||||
|
concByPersonId: (state) => (person_id) => {
|
||||||
|
return state.concerned
|
||||||
|
.find(conc => conc.person.id === person_id)
|
||||||
|
;
|
||||||
|
},
|
||||||
|
needsPositionning(state) {
|
||||||
|
return state.forceLeaveWithoutHousehold === false;
|
||||||
|
},
|
||||||
|
buildPayload: (state) => {
|
||||||
|
let
|
||||||
|
conc,
|
||||||
|
payload_conc,
|
||||||
|
payload = {
|
||||||
|
concerned: [],
|
||||||
|
destination: null
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
if (state.forceLeaveWithoutHousehold === false) {
|
||||||
|
payload.destination = {
|
||||||
|
id: state.household.id,
|
||||||
|
type: state.household.type
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i in state.concerned) {
|
||||||
|
conc = state.concerned[i];
|
||||||
|
payload_conc = {
|
||||||
|
person: {
|
||||||
|
id: conc.person.id,
|
||||||
|
type: conc.person.type
|
||||||
|
},
|
||||||
|
start_date: {
|
||||||
|
datetime: datetimeToISO(state.startDate)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (state.forceLeaveWithoutHousehold === false) {
|
||||||
|
payload_conc.position = {
|
||||||
|
id: conc.position.id,
|
||||||
|
type: conc.position.type
|
||||||
|
};
|
||||||
|
payload_conc.holder = conc.holder;
|
||||||
|
payload_conc.comment = conc.comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.concerned.push(payload_conc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
addConcerned(state, person) {
|
||||||
|
let persons = state.concerned.map(conc => conc.person.id);
|
||||||
|
if (!persons.includes(person.id)) {
|
||||||
|
state.concerned.push({
|
||||||
|
person,
|
||||||
|
position: null,
|
||||||
|
allowRemove: true,
|
||||||
|
holder: false,
|
||||||
|
comment: "",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.err("person already included");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
markPosition(state, { person_id, position_id}) {
|
||||||
|
let
|
||||||
|
position = state.positions.find(pos => pos.id === position_id),
|
||||||
|
conc = state.concerned.find(c => c.person.id === person_id);
|
||||||
|
conc.position = position;
|
||||||
|
},
|
||||||
|
setComment(state, {conc, comment}) {
|
||||||
|
conc.comment = comment;
|
||||||
|
},
|
||||||
|
toggleHolder(state, conc) {
|
||||||
|
conc.holder = !conc.holder;
|
||||||
|
},
|
||||||
|
removePosition(state, conc) {
|
||||||
|
conc.holder = false;
|
||||||
|
conc.position = null;
|
||||||
|
},
|
||||||
|
removeConcerned(state, conc) {
|
||||||
|
state.concerned = state.concerned.filter(c =>
|
||||||
|
c.person.id !== conc.person.id
|
||||||
|
)
|
||||||
|
},
|
||||||
|
createHousehold(state) {
|
||||||
|
state.household = { type: 'household', members: [], address: null }
|
||||||
|
state.forceLeaveWithoutHousehold = false;
|
||||||
|
},
|
||||||
|
forceLeaveWithoutHousehold(state) {
|
||||||
|
state.household = null;
|
||||||
|
state.forceLeaveWithoutHousehold = true;
|
||||||
|
},
|
||||||
|
setStartDate(state, dateI) {
|
||||||
|
state.startDate = dateI;
|
||||||
|
},
|
||||||
|
setWarnings(state, warnings) {
|
||||||
|
state.warnings = warnings;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
addConcerned({ commit, dispatch }, person) {
|
||||||
|
commit('addConcerned', person);
|
||||||
|
dispatch('computeWarnings');
|
||||||
|
},
|
||||||
|
markPosition({ commit, state, dispatch }, { person_id, position_id }) {
|
||||||
|
commit('markPosition', { person_id, position_id });
|
||||||
|
dispatch('computeWarnings');
|
||||||
|
},
|
||||||
|
toggleHolder({ commit }, conc) {
|
||||||
|
commit('toggleHolder', conc);
|
||||||
|
},
|
||||||
|
removePosition({ commit, dispatch }, conc) {
|
||||||
|
commit('removePosition', conc);
|
||||||
|
dispatch('computeWarnings');
|
||||||
|
},
|
||||||
|
removeConcerned({ commit, dispatch }, conc) {
|
||||||
|
commit('removeConcerned', conc);
|
||||||
|
dispatch('computeWarnings');
|
||||||
|
},
|
||||||
|
createHousehold({ commit, dispatch }) {
|
||||||
|
commit('createHousehold');
|
||||||
|
dispatch('computeWarnings');
|
||||||
|
},
|
||||||
|
forceLeaveWithoutHousehold({ commit, dispatch }) {
|
||||||
|
commit('forceLeaveWithoutHousehold');
|
||||||
|
dispatch('computeWarnings');
|
||||||
|
},
|
||||||
|
setStartDate({ commit }, date) {
|
||||||
|
commit('setStartDate', date);
|
||||||
|
},
|
||||||
|
setComment({ commit }, payload) {
|
||||||
|
commit('setComment', payload);
|
||||||
|
},
|
||||||
|
computeWarnings({ commit, state, getters }) {
|
||||||
|
let warnings = [],
|
||||||
|
payload;
|
||||||
|
|
||||||
|
if (!getters.hasHousehold && !state.forceLeaveWithoutHousehold) {
|
||||||
|
warnings.push({ m: 'household_members_editor.add_destination', a: {} });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.concerned.length === 0) {
|
||||||
|
warnings.push({ m: 'household_members_editor.add_at_least_onePerson', a: {} });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getters.concUnpositionned.length > 0
|
||||||
|
&& !state.forceLeaveWithoutHousehold) {
|
||||||
|
warnings.push({ m: 'household_members_editor.give_a_position_to_every_person', a: {} })
|
||||||
|
}
|
||||||
|
|
||||||
|
commit('setWarnings', warnings);
|
||||||
|
},
|
||||||
|
confirm({ getters, state }) {
|
||||||
|
let payload = getters.buildPayload,
|
||||||
|
person_id,
|
||||||
|
household_id;
|
||||||
|
householdMove(payload).then(household => {
|
||||||
|
if (household === null) {
|
||||||
|
person_id = getters.persons[0].id;
|
||||||
|
window.location.replace(`/fr/person/${person_id}/general`);
|
||||||
|
} else {
|
||||||
|
household_id = household.id;
|
||||||
|
// nothing to do anymore here, bye-bye !
|
||||||
|
window.location.replace(`/fr/person/household/${household_id}/members`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
store.dispatch('computeWarnings');
|
||||||
|
|
||||||
|
export { store };
|
@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<span class="chill-entity chill-entity__person">
|
||||||
|
<span class="chill-entity__person__text">
|
||||||
|
{{ person.text }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Person',
|
||||||
|
props: ['person']
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
@ -15,7 +15,15 @@ const personMessages = {
|
|||||||
person: {
|
person: {
|
||||||
firstname: "Prénom",
|
firstname: "Prénom",
|
||||||
lastname: "Nom",
|
lastname: "Nom",
|
||||||
born: "né{e} le ",
|
born: (ctx) => {
|
||||||
|
if (ctx.gender === 'man') {
|
||||||
|
return 'Né le';
|
||||||
|
} else if (ctx.gender === 'woman') {
|
||||||
|
return 'Née le';
|
||||||
|
} else {
|
||||||
|
return 'Né·e le';
|
||||||
|
}
|
||||||
|
},
|
||||||
center_id: "Identifiant du centre",
|
center_id: "Identifiant du centre",
|
||||||
center_type: "Type de centre",
|
center_type: "Type de centre",
|
||||||
center_name: "Territoire", // vendée
|
center_name: "Territoire", // vendée
|
||||||
|
@ -62,6 +62,19 @@
|
|||||||
<li>
|
<li>
|
||||||
<a href="{{ path('chill_person_view', { person_id: p.person.id }) }}" class="sc-button bt-show" target="_blank" title="Voir"></a>
|
<a href="{{ path('chill_person_view', { person_id: p.person.id }) }}" class="sc-button bt-show" target="_blank" title="Voir"></a>
|
||||||
</li>
|
</li>
|
||||||
|
{% if p.person.isSharingHousehold %}
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="{{ chill_path_add_return_path(
|
||||||
|
'chill_person_household_summary',
|
||||||
|
{ 'household_id': p.person.getCurrentHousehold.id }
|
||||||
|
) }}"
|
||||||
|
class="sc-button">
|
||||||
|
<i class="fa fa-home"></i>
|
||||||
|
{{ 'household.Household file'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -48,7 +48,18 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" class="sc-button" /><i class="fa fa-sign-out"></i>{{ 'household.Leave'|trans }}</a>
|
<a
|
||||||
|
href="{{ chill_path_add_return_path(
|
||||||
|
'chill_person_household_members_editor',
|
||||||
|
{
|
||||||
|
'persons': [ m.person.id ],
|
||||||
|
'allow_leave_without_household': true
|
||||||
|
} ) }}"
|
||||||
|
class="sc-button"
|
||||||
|
/>
|
||||||
|
<i class="fa fa-sign-out"></i>
|
||||||
|
{{ 'household.Leave'|trans }}
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -129,7 +140,9 @@
|
|||||||
|
|
||||||
<ul class="record_actions sticky-form-buttons">
|
<ul class="record_actions sticky-form-buttons">
|
||||||
<li>
|
<li>
|
||||||
<a class="sc-button bt-create">
|
<a
|
||||||
|
href="{{ chill_path_add_return_path('chill_person_household_members_editor', {'household': household.id }) }}"
|
||||||
|
class="sc-button bt-create">
|
||||||
{{ 'household.Add a member'|trans }}
|
{{ 'household.Add a member'|trans }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
{% extends '@ChillMain/layout.html.twig' %}
|
||||||
|
|
||||||
|
{% block title 'household.Edit household members'|trans %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="grid-12 parent">
|
||||||
|
<div class="grid-10 push-1 parent">
|
||||||
|
<h1>{{ block('title') }}</h1>
|
||||||
|
<div id="household_members_editor"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.household_members_editor_data = {{ data|json_encode|raw }};
|
||||||
|
</script>
|
||||||
|
{{ encore_entry_script_tags('household_members_editor') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{{ encore_entry_link_tags('household_members_editor') }}
|
||||||
|
{% endblock %}
|
@ -24,6 +24,58 @@ class MembersEditorNormalizer implements DenormalizerInterface, DenormalizerAwar
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function denormalize($data, string $type, string $format = null, array $context = [])
|
public function denormalize($data, string $type, string $format = null, array $context = [])
|
||||||
|
{
|
||||||
|
// some test about schema first...
|
||||||
|
$this->performChecks($data);
|
||||||
|
|
||||||
|
// route to "leave movement" (all concerned leave household)
|
||||||
|
// or "move to another household" (all concerned go to another
|
||||||
|
// household)
|
||||||
|
if (NULL === $data['destination']) {
|
||||||
|
return $this->denormalizeLeave($data, $type, $format, $context);
|
||||||
|
} else {
|
||||||
|
return $this->denormalizeMove($data, $type, $format, $context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function performChecks($data): void
|
||||||
|
{
|
||||||
|
if (NULL == $data['concerned'] ?? NULL
|
||||||
|
&& FALSE === ·\is_array('concerned')) {
|
||||||
|
throw new Exception\UnexpectedValueException("The schema does not have any key 'concerned'");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FALSE === \array_key_exists('destination', $data)) {
|
||||||
|
throw new Exception\UnexpectedValueException("The schema does not have any key 'destination'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function denormalizeLeave($data, string $type, string $format, array $context = [])
|
||||||
|
{
|
||||||
|
$editor = $this->factory->createEditor(null);
|
||||||
|
|
||||||
|
foreach ($data['concerned'] as $key => $concerned) {
|
||||||
|
$person = $this->denormalizer->denormalize($concerned['person'] ?? null, Person::class,
|
||||||
|
$format, $context);
|
||||||
|
$startDate = $this->denormalizer->denormalize($concerned['start_date'] ?? null, \DateTimeImmutable::class,
|
||||||
|
$format, $context);
|
||||||
|
|
||||||
|
if (
|
||||||
|
NULL === $person
|
||||||
|
&& NULL === $startDate
|
||||||
|
) {
|
||||||
|
throw new Exception\InvalidArgumentException("position with ".
|
||||||
|
"key $key could not be denormalized: missing ".
|
||||||
|
"person or start_date.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$editor->leaveMovement($startDate, $person);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function denormalizeMove($data, string $type, string $format, array $context = [])
|
||||||
{
|
{
|
||||||
$household = $this->denormalizer->denormalize($data['destination'], Household::class,
|
$household = $this->denormalizer->denormalize($data['destination'], Household::class,
|
||||||
$format, $context);
|
$format, $context);
|
||||||
@ -34,11 +86,6 @@ class MembersEditorNormalizer implements DenormalizerInterface, DenormalizerAwar
|
|||||||
|
|
||||||
$editor = $this->factory->createEditor($household);
|
$editor = $this->factory->createEditor($household);
|
||||||
|
|
||||||
if (NULL == $data['concerned'] ?? []
|
|
||||||
&& FALSE === ·\is_array('concerned')) {
|
|
||||||
throw new Exception\UnexpectedValueException("The schema does not have any key 'concerned'");
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($data['concerned'] as $key => $concerned) {
|
foreach ($data['concerned'] as $key => $concerned) {
|
||||||
$person = $this->denormalizer->denormalize($concerned['person'] ?? null, Person::class,
|
$person = $this->denormalizer->denormalize($concerned['person'] ?? null, Person::class,
|
||||||
$format, $context);
|
$format, $context);
|
||||||
@ -62,9 +109,9 @@ class MembersEditorNormalizer implements DenormalizerInterface, DenormalizerAwar
|
|||||||
|
|
||||||
$editor->addMovement($startDate, $person, $position, $holder,
|
$editor->addMovement($startDate, $person, $position, $holder,
|
||||||
$comment);
|
$comment);
|
||||||
|
|
||||||
return $editor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function supportsDenormalization($data, string $type, string $format = null)
|
public function supportsDenormalization($data, string $type, string $format = null)
|
||||||
|
@ -67,6 +67,116 @@ class HouseholdMemberControllerTest extends WebTestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideValidDataMove
|
||||||
|
*/
|
||||||
|
public function testMoveMemberToNewHousehold($personId, $householdId, $positionId, \DateTimeInterface $date)
|
||||||
|
{
|
||||||
|
$client = $this->getClientAuthenticated();
|
||||||
|
|
||||||
|
$client->request(
|
||||||
|
Request::METHOD_POST,
|
||||||
|
'/api/1.0/person/household/members/move.json',
|
||||||
|
[], // parameters
|
||||||
|
[], // files
|
||||||
|
[], // server
|
||||||
|
\json_encode(
|
||||||
|
[
|
||||||
|
'concerned' =>
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'person' =>
|
||||||
|
[
|
||||||
|
'type' => 'person',
|
||||||
|
'id' => $personId
|
||||||
|
],
|
||||||
|
'start_date' =>
|
||||||
|
[
|
||||||
|
'datetime' => $date->format(\DateTimeInterface::RFC3339)
|
||||||
|
],
|
||||||
|
'position' =>
|
||||||
|
[
|
||||||
|
'type' => 'household_position',
|
||||||
|
'id' => $positionId
|
||||||
|
],
|
||||||
|
'holder' => false,
|
||||||
|
'comment' => "Introduced by automated test",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'destination' =>
|
||||||
|
[
|
||||||
|
'type' => 'household',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
true)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(Response::HTTP_OK,
|
||||||
|
$client->getResponse()->getStatusCode()
|
||||||
|
);
|
||||||
|
|
||||||
|
$data = \json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
|
$this->assertIsArray($data);
|
||||||
|
$this->assertArrayHasKey('members', $data);
|
||||||
|
$this->assertIsArray($data['members']);
|
||||||
|
$this->assertEquals(1, count($data['members']),
|
||||||
|
"assert new household count one member");
|
||||||
|
$this->assertArrayHasKey('person', $data['members'][0]);
|
||||||
|
$this->assertArrayHasKey('id', $data['members'][0]['person']);
|
||||||
|
$this->assertEquals($personId, $data['members'][0]['person']['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideValidDataMove
|
||||||
|
*/
|
||||||
|
public function testLeaveWithoutHousehold($personId, $householdId, $positionId, \DateTimeInterface $date)
|
||||||
|
{
|
||||||
|
$client = $this->getClientAuthenticated();
|
||||||
|
|
||||||
|
$client->request(
|
||||||
|
Request::METHOD_POST,
|
||||||
|
'/api/1.0/person/household/members/move.json',
|
||||||
|
[], // parameters
|
||||||
|
[], // files
|
||||||
|
[], // server
|
||||||
|
\json_encode(
|
||||||
|
[
|
||||||
|
'concerned' =>
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'person' =>
|
||||||
|
[
|
||||||
|
'type' => 'person',
|
||||||
|
'id' => $personId
|
||||||
|
],
|
||||||
|
'start_date' =>
|
||||||
|
[
|
||||||
|
'datetime' => $date->format(\DateTimeInterface::RFC3339)
|
||||||
|
],
|
||||||
|
'position' =>
|
||||||
|
[
|
||||||
|
'type' => 'household_position',
|
||||||
|
'id' => $positionId
|
||||||
|
],
|
||||||
|
'holder' => false,
|
||||||
|
'comment' => "Introduced by automated test",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'destination' => null
|
||||||
|
],
|
||||||
|
true)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(Response::HTTP_OK,
|
||||||
|
$client->getResponse()->getStatusCode()
|
||||||
|
);
|
||||||
|
|
||||||
|
$data = \json_decode($client->getResponse()->getContent(), true);
|
||||||
|
|
||||||
|
$this->assertEquals(null, $data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider provideValidDataEditMember
|
* @dataProvider provideValidDataEditMember
|
||||||
*/
|
*/
|
||||||
@ -97,7 +207,13 @@ class HouseholdMemberControllerTest extends WebTestCase
|
|||||||
$em = self::$container->get(EntityManagerInterface::class);
|
$em = self::$container->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
$personIds = $em->createQuery("SELECT p.id FROM ".Person::class." p ".
|
$personIds = $em->createQuery("SELECT p.id FROM ".Person::class." p ".
|
||||||
"JOIN p.center c WHERE c.name = :center")
|
"JOIN p.center c ".
|
||||||
|
"JOIN p.householdParticipations hp ".
|
||||||
|
"WHERE ".
|
||||||
|
"c.name = :center ".
|
||||||
|
"AND hp.startDate < CURRENT_DATE() ".
|
||||||
|
"AND hp.endDate IS NULL "
|
||||||
|
)
|
||||||
->setParameter('center', "Center A")
|
->setParameter('center', "Center A")
|
||||||
->setMaxResults(100)
|
->setMaxResults(100)
|
||||||
->getScalarResult()
|
->getScalarResult()
|
||||||
|
@ -22,6 +22,9 @@
|
|||||||
|
|
||||||
namespace Chill\PersonBundle\Tests\Entity;
|
namespace Chill\PersonBundle\Tests\Entity;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\Household\HouseholdMember;
|
||||||
|
use Chill\PersonBundle\Entity\Household\Position;
|
||||||
|
use Chill\PersonBundle\Entity\Household\Household;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\MainBundle\Entity\Address;
|
use Chill\MainBundle\Entity\Address;
|
||||||
@ -205,4 +208,39 @@ class PersonTest extends \PHPUnit\Framework\TestCase
|
|||||||
$this::assertEquals($address3, $p->getLastAddress($addressDate3));
|
$this::assertEquals($address3, $p->getLastAddress($addressDate3));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testIsSharingHousehold()
|
||||||
|
{
|
||||||
|
$person = new Person();
|
||||||
|
$household = new Household();
|
||||||
|
$positionShare = (new Position())
|
||||||
|
->setShareHousehold(true);
|
||||||
|
$positionNotShare = (new Position())
|
||||||
|
->setShareHousehold(false);
|
||||||
|
|
||||||
|
$membership1 = (new HouseholdMember())
|
||||||
|
->setStartDate(new \DateTimeImmutable('10 years ago'))
|
||||||
|
->setEndDate(new \DateTimeImmutable('5 years ago'))
|
||||||
|
->setPerson($person)
|
||||||
|
->setPosition($positionShare)
|
||||||
|
;
|
||||||
|
$household->addMember($membership1);
|
||||||
|
|
||||||
|
$membership2 = (new HouseholdMember())
|
||||||
|
->setStartDate(new \DateTimeImmutable('4 years ago'))
|
||||||
|
->setEndDate(new \DateTimeImmutable('2 years ago'))
|
||||||
|
->setPerson($person)
|
||||||
|
->setPosition($positionNotShare)
|
||||||
|
;
|
||||||
|
$household->addMember($membership2);
|
||||||
|
|
||||||
|
$this->assertEquals(2, $person->getHouseholdParticipations()
|
||||||
|
->count());
|
||||||
|
|
||||||
|
$this->assertFalse($person->isSharingHousehold());
|
||||||
|
$this->assertTrue($person->isSharingHousehold(
|
||||||
|
new \DateTimeImmutable('6 years ago')));
|
||||||
|
$this->assertFalse($person->isSharingHousehold(
|
||||||
|
new \DateTimeImmutable('3 years ago')));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -802,18 +802,61 @@ paths:
|
|||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
person:
|
person:
|
||||||
$ref: '#/components/schemas/PersonById'
|
$ref: '#/components/schemas/PersonById'
|
||||||
start_date:
|
start_date:
|
||||||
$ref: '#/components/schemas/Date'
|
$ref: '#/components/schemas/Date'
|
||||||
position:
|
position:
|
||||||
$ref: '#/components/schemas/HouseholdPosition'
|
$ref: '#/components/schemas/HouseholdPosition'
|
||||||
holder:
|
holder:
|
||||||
type: boolean
|
type: boolean
|
||||||
comment:
|
comment:
|
||||||
type: string
|
type: string
|
||||||
destination:
|
destination:
|
||||||
oneOf:
|
$ref: '#/components/schemas/Household'
|
||||||
- $ref: '#/components/schemas/Household'
|
examples:
|
||||||
|
Moving person to a new household:
|
||||||
|
value:
|
||||||
|
concerned:
|
||||||
|
-
|
||||||
|
person:
|
||||||
|
id: 0
|
||||||
|
type: person
|
||||||
|
position:
|
||||||
|
type: position
|
||||||
|
id: 1
|
||||||
|
start_date:
|
||||||
|
datetime: 2021-06-01T00:00:00+02:00
|
||||||
|
comment: "This is my comment for moving"
|
||||||
|
holder: false
|
||||||
|
destination:
|
||||||
|
type: household
|
||||||
|
Moving person to an existing household:
|
||||||
|
value:
|
||||||
|
concerned:
|
||||||
|
-
|
||||||
|
person:
|
||||||
|
id: 0
|
||||||
|
type: person
|
||||||
|
position:
|
||||||
|
type: position
|
||||||
|
id: 1
|
||||||
|
start_date:
|
||||||
|
datetime: 2021-06-01T00:00:00+02:00
|
||||||
|
comment: "This is my comment for moving"
|
||||||
|
holder: false
|
||||||
|
destination:
|
||||||
|
type: household
|
||||||
|
id: 54
|
||||||
|
Removing a person from any household:
|
||||||
|
value:
|
||||||
|
concerned:
|
||||||
|
-
|
||||||
|
person:
|
||||||
|
id: 0
|
||||||
|
type: person
|
||||||
|
start_date:
|
||||||
|
datetime: 2021-06-01T00:00:00+02:00
|
||||||
|
destination: null
|
||||||
responses:
|
responses:
|
||||||
401:
|
401:
|
||||||
description: "Unauthorized"
|
description: "Unauthorized"
|
||||||
|
@ -8,5 +8,7 @@ module.exports = function(encore, entries)
|
|||||||
ChillPersonAssets: __dirname + '/Resources/public'
|
ChillPersonAssets: __dirname + '/Resources/public'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
encore.addEntry('accompanying_course', __dirname + '/Resources/public/vuejs/AccompanyingCourse/index.js');
|
||||||
|
encore.addEntry('household_members_editor', __dirname + '/Resources/public/vuejs/HouseholdMembersEditor/index.js');
|
||||||
encore.addEntry('vue_accourse', __dirname + '/Resources/public/vuejs/AccompanyingCourse/index.js');
|
encore.addEntry('vue_accourse', __dirname + '/Resources/public/vuejs/AccompanyingCourse/index.js');
|
||||||
};
|
};
|
||||||
|
@ -17,6 +17,8 @@ household:
|
|||||||
Those members does not share address: Ces usagers ne partagent pas l'adresse du ménage.
|
Those members does not share address: Ces usagers ne partagent pas l'adresse du ménage.
|
||||||
Any persons into this position: Aucune personne n'appartient au ménage à cette position.
|
Any persons into this position: Aucune personne n'appartient au ménage à cette position.
|
||||||
Leave: Quitter le ménage
|
Leave: Quitter le ménage
|
||||||
|
Household file: Dossier ménage
|
||||||
|
Add a member: Ajouter un membre
|
||||||
Update membership: Modifier
|
Update membership: Modifier
|
||||||
successfully saved member: Membre enregistré avec succès
|
successfully saved member: Membre enregistré avec succès
|
||||||
Start date: Date de début de l'appartenance au ménage
|
Start date: Date de début de l'appartenance au ménage
|
||||||
@ -29,6 +31,7 @@ household:
|
|||||||
Current household members: Membres actuels du ménage
|
Current household members: Membres actuels du ménage
|
||||||
Household summary: Résumé
|
Household summary: Résumé
|
||||||
Addresses: Adresses
|
Addresses: Adresses
|
||||||
|
Edit household members: Modifier l'appartenance au ménage
|
||||||
and x other persons: >-
|
and x other persons: >-
|
||||||
{x, plural,
|
{x, plural,
|
||||||
one {et une autre personne}
|
one {et une autre personne}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user