Merge remote-tracking branch 'origin/master' into features/create-social-action

This commit is contained in:
Julien Fastré 2021-06-21 14:38:09 +02:00
commit 50be29308d
22 changed files with 628 additions and 198 deletions

View File

@ -1,4 +1,5 @@
<template>
<div class="chill_address_address chill_address_address--multiline">
<div v-if="address.text">
{{ address.text }}
</div>
@ -28,7 +29,8 @@
</div>
<div v-if="address.distribution">
<span>{{ $t('distribution') }}</span>: {{ address.distribution }}
</div>
</div>
</div>
</template>
<script>

View File

@ -2,8 +2,8 @@
namespace Chill\PersonBundle\Controller;
use Chill\PersonBundle\Form\HouseholdType;
use Chill\MainBundle\Entity\Address;
use Chill\PersonBundle\Form\HouseholdType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
@ -13,6 +13,7 @@ use Symfony\Component\Translation\TranslatorInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\Position;
use Chill\PersonBundle\Repository\Household\PositionRepository;
/**
* @Route("/{_locale}/person/household")
@ -21,12 +22,13 @@ class HouseholdController extends AbstractController
{
private TranslatorInterface $translator;
/**
* @param TranslatorInterface $translator
*/
public function __construct(TranslatorInterface $translator)
private PositionRepository $positionRepository;
public function __construct(TranslatorInterface $translator, PositionRepository $positionRepository)
{
$this->translator = $translator;
$this->positionRepository = $positionRepository;
}
/**
@ -41,37 +43,8 @@ class HouseholdController extends AbstractController
{
// TODO ACL
$positions = $this->getDoctrine()->getManager()
->getRepository(Position::class)
->findAll()
;
// little performance improvement:
// initialize members collection, which will avoid
// some queries
$household->getMembers()->initialize();
return $this->render('@ChillPerson/Household/summary.html.twig',
[
'household' => $household,
'positions' => $positions
]
);
}
/**
* @Route(
* "/{household_id}/members",
* name="chill_person_household_members",
* methods={"GET", "HEAD"}
* )
* @ParamConverter("household", options={"id" = "household_id"})
*/
public function members(Request $request, Household $household)
{
// TODO ACL
$positions = $this->getDoctrine()->getManager()
->getRepository(Position::class)
->findAll()
$positions = $this->positionRepository
->findByActiveOrdered()
;
// little performance improvement:
@ -85,11 +58,11 @@ class HouseholdController extends AbstractController
$form = null;
}
return $this->render('@ChillPerson/Household/members.html.twig',
return $this->render('@ChillPerson/Household/summary.html.twig',
[
'household' => $household,
'positions' => $positions,
'form' => NULL !== $form ? $form->createView(): $form
'form' => NULL !== $form ? $form->createView() : null,
]
);
}
@ -142,6 +115,28 @@ class HouseholdController extends AbstractController
);
}
/**
* @Route(
* "/{household_id}/address/edit",
* name="chill_person_household_address_edit",
* methods={"GET", "HEAD", "POST"}
* )
* @ParamConverter("household", options={"id" = "household_id"})
*/
public function addressEdit(Request $request, Household $household)
{
// TODO ACL
//$address = $this->findAddressById($household, $address_id); //TODO
return $this->render('@ChillPerson/Household/address_edit.html.twig',
[
'household' => $household,
//'address' => $address,
]
);
}
/**
* @Route(
* "/{household_id}/members/metadata/edit",
@ -162,7 +157,7 @@ class HouseholdController extends AbstractController
$this->addFlash('success', $this->translator->trans('household.data_saved'));
return $this->redirectToRoute('chill_person_household_members', [
return $this->redirectToRoute('chill_person_household_summary', [
'household_id' => $household->getId()
]);
}
@ -191,25 +186,4 @@ class HouseholdController extends AbstractController
return $form;
}
/**
* @Route(
* "/{household_id}/address/edit",
* name="chill_person_household_address_edit",
* methods={"GET", "HEAD", "POST"}
* )
* @ParamConverter("household", options={"id" = "household_id"})
*/
public function addressEdit(Request $request, Household $household)
{
// TODO ACL
//$address = $this->findAddressById($household, $address_id); //TODO
return $this->render('@ChillPerson/Household/address_edit.html.twig',
[
'household' => $household,
//'address' => $address,
]
);
}
}

View File

@ -173,7 +173,7 @@ class HouseholdMemberController extends ApiController
return $this->redirect(
$request->get('returnPath', null) ??
$this->generator->generate('chill_person_household_members', [ 'household_id' =>
$this->generator->generate('chill_person_household_summary', [ 'household_id' =>
$member->getHousehold()->getId() ])
);
}

View File

@ -30,7 +30,7 @@ class Household
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Serializer\Groups({"write"})
* @Serializer\Groups({"read"})
*/
private ?int $id = null;
@ -196,6 +196,11 @@ class Household
}
public function getCurrentMembers(?\DateTimeImmutable $now = null): Collection
{
return $this->getMembers()->matching($this->buildCriteriaCurrentMembers($now));
}
private function buildCriteriaCurrentMembers(?\DateTimeImmutable $now = null): Criteria
{
$criteria = new Criteria();
$expr = Criteria::expr();
@ -211,7 +216,53 @@ class Household
$expr->gte('endDate', $date)
));
return $this->getMembers()->matching($criteria);
return $criteria;
}
/**
* @return HouseholdMember[]
*/
public function getCurrentMembersOrdered(?\DateTimeImmutable $now = null): Collection
{
$members = $this->getCurrentMembers($now);
$members->getIterator()
->uasort(
function (HouseholdMember $a, HouseholdMember $b) {
if ($a->getPosition()->getOrdering() < $b->getPosition()->getOrdering()) {
return -1;
}
if ($a->getPosition()->getOrdering() > $b->getPosition()->getOrdering()) {
return 1;
}
if ($a->isHolder() && !$b->isHolder()) {
return 1;
}
if (!$a->isHolder() && $b->isHolder()) {
return -1;
}
return 0;
}
);
return $members;
}
/**
* get current members ids
*
* Used in serialization
*
* @Serializer\Groups({"read"})
* @Serializer\SerializedName("current_members_id")
*
*/
public function getCurrentMembersIds(?\DateTimeImmutable $now = null): Collection
{
return $this->getCurrentMembers($now)->map(
fn (HouseholdMember $m) => $m->getId()
);
}
/**

View File

@ -1264,15 +1264,24 @@ class Person implements HasCenterInterface
}
public function getCurrentHousehold(?\DateTimeImmutable $at = null): ?Household
{
$participation = $this->getCurrentHouseholdParticipationShareHousehold($at);
return $participation instanceof HouseholdMember ?
$participation->getHousehold()
: null;
}
public function getCurrentHouseholdParticipationShareHousehold(?\DateTimeImmutable $at = null): ?HouseholdMember
{
$criteria = new Criteria();
$expr = Criteria::expr();
$date = NULL === $at ? new \DateTimeImmutable('now') : $at;
$date = NULL === $at ? new \DateTimeImmutable('today') : $at;
$datef = $date->format('Y-m-d');
if (
NULL !== ($this->currentHouseholdAt[$datef] ?? NULL)) {
return $this->currentHouseholdAt[$datef];
NULL !== ($this->currentHouseholdParticipationAt[$datef] ?? NULL)) {
return $this->currentHouseholdParticipationAt[$datef];
}
$criteria
@ -1281,7 +1290,7 @@ class Person implements HasCenterInterface
$expr->lte('startDate', $date),
$expr->orX(
$expr->isNull('endDate'),
$expr->gte('endDate', $date)
$expr->gt('endDate', $date)
),
$expr->eq('shareHousehold', true)
)
@ -1292,8 +1301,7 @@ class Person implements HasCenterInterface
;
return $participations->count() > 0 ?
$this->currentHouseholdAt[$datef] = $participations->first()
->getHousehold()
$this->currentHouseholdParticipationAt[$datef] = $participations->first()
: null;
}

View File

@ -35,13 +35,6 @@ class HouseholdMenuBuilder implements LocalMenuBuilderInterface
'household_id' => $household->getId()
]])
->setExtras(['order' => 10]);
$menu->addChild($this->translator->trans('household.Household members'), [
'route' => 'chill_person_household_members',
'routeParameters' => [
'household_id' => $household->getId()
]])
->setExtras(['order' => 20]);
$menu->addChild($this->translator->trans('household.Addresses'), [
'route' => 'chill_person_household_addresses',

View File

@ -7,14 +7,9 @@ use Chill\PersonBundle\Entity\Household\Position;
//use Doctrine\Persistence\ManagerRegistry;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ObjectRepository;
/**
* @method Position|null find($id, $lockMode = null, $lockVersion = null)
* @method Position|null findOneBy(array $criteria, array $orderBy = null)
* @method Position[] findAll()
* @method Position[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
final class PositionRepository
final class PositionRepository implements ObjectRepository
{
private EntityRepository $repository;
@ -30,4 +25,45 @@ final class PositionRepository
{
return $this->repository->findAll();
}
/**
* @return Position[]
*/
public function findByActiveOrdered(): array
{
return $this->repository->createQueryBuilder('p')
->select('p')
->orderBy('p.ordering', 'ASC')
->getQuery()
->getResult();
}
/**
* @return Position[]
*/
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
{
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
}
/**
* @return Position[]
*/
public function findOneBy(array $criteria): array
{
return $this->repository->findOneBy($criteria);
}
/**
* @return Position
*/
public function find($id)
{
return $this->repository->find($id);
}
public function getClassName()
{
return Position::class;
}
}

View File

@ -1,2 +1,3 @@
require('./sass/person.scss');
require('./sass/person_with_period.scss');
require('./sass/household_banner.scss');

View File

@ -0,0 +1,5 @@
.banner-household {
.current-members-explain {
font-weight: 700;
}
}

View File

@ -1,7 +1,6 @@
/// complete and overwrite flex-table in chillmain.scss
div.list-with-period,
div.list-household-members,
div.list-household-members--summary {
div.list-household-members {
.chill-entity__person {
.chill-entity__person__first-name,

View File

@ -1,13 +1,13 @@
<template>
<household></household>
<concerned></concerned>
<dates></dates>
<confirmation></confirmation>
<concerned v-if="hasHouseholdOrLeave"></concerned>
<dates v-if="showConfirm"></dates>
<confirmation v-if="showConfirm"></confirmation>
</template>
<script>
import { mapState } from 'vuex';
import { mapGetters } from 'vuex';
import Concerned from './components/Concerned.vue';
import Household from './components/Household.vue';
import Dates from './components/Dates.vue';
@ -22,11 +22,14 @@ export default {
Confirmation,
},
computed: {
// for debugging purpose
// (not working)
//...mapState({
// 'concerned', 'household', 'positions'
// })
...mapGetters([
'hasHouseholdOrLeave',
'hasPersonsWellPositionnated',
]),
showConfirm () {
return this.$store.getters.hasHouseholdOrLeave
&& this.$store.getters.hasPersonsWellPositionnated;
},
}
}

View File

@ -21,13 +21,10 @@
<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 v-if="conc.person.birthdate !== null">
@ -89,21 +86,18 @@
v-for="position in positions"
>
<h3>{{ position.label.fr }}</h3>
<div class="flex-table list-household-members">
<div v-if="concByPosition(position.id).length > 0" 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 v-else>
<p class="chill-no-data-statement">{{ $t('household_members_editor.concerned.no_person_in_position') }}</p>
</div>
</div>
</div>
@ -120,22 +114,6 @@ div.person {
}
}
.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;
@ -194,18 +172,8 @@ export default {
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);

View File

@ -2,11 +2,8 @@
<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>
<household-viewer :household="household"></household-viewer>
</div>
</div>
<div v-else-if="isForceLeaveWithoutHousehold">
@ -39,16 +36,20 @@
<script>
import { mapGetters } from 'vuex';
import HouseholdViewer from 'ChillPersonAssets/vuejs/_components/Household/Household.vue';
export default {
name: 'Household',
components: {
HouseholdViewer,
},
computed: {
...mapGetters([
'hasHousehold',
'isHouseholdNew',
]),
household() {
return this.$store.household;
return this.$store.state.household;
},
allowHouseholdCreate() {
return this.$store.state.allowHouseholdCreate;

View File

@ -19,12 +19,13 @@ const appMessages = {
move_to: "Déplacer vers",
persons_to_positionnate: 'Usagers à positionner',
persons_leaving: "Usagers quittant leurs ménages",
no_person_in_position: "Aucun usager ne sera ajouté à cette position",
},
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",
is_holder: "Est titulaire",
is_not_holder: "N'est pas titulaire",
remove_position: "Retirer des {position}",
remove_concerned: "Ne plus transférer",
household_part: "Ménage de destination",

View File

@ -19,7 +19,15 @@ const store = createStore({
state: {
concerned,
household: window.household_members_editor_data.household,
positions: window.household_members_editor_data.positions,
positions: window.household_members_editor_data.positions.sort((a, b) => {
if (a.ordering < b.ordering) {
return -1;
}
if (a.ordering > b.ordering) {
return 1;
}
return 0;
}),
startDate: new Date(),
allowHouseholdCreate: window.household_members_editor_data.allowHouseholdCreate,
allowHouseholdSearch: window.household_members_editor_data.allowHouseholdSearch,
@ -35,6 +43,13 @@ const store = createStore({
hasHousehold(state) {
return state.household !== null;
},
hasHouseholdOrLeave(state) {
return state.household !== null || state.forceLeaveWithoutHousehold;
},
hasPersonsWellPositionnated(state, getters) {
return getters.needsPositionning === false
|| (getters.persons.length > 0 && getters.concUnpositionned.length === 0);
},
persons(state) {
return state.concerned.map(conc => conc.person);
},
@ -150,7 +165,7 @@ const store = createStore({
)
},
createHousehold(state) {
state.household = { type: 'household', members: [], address: null }
state.household = { type: 'household', members: [], current_address: null }
state.forceLeaveWithoutHousehold = false;
},
forceLeaveWithoutHousehold(state) {

View File

@ -0,0 +1,139 @@
<template>
<div class="chill-entity chill-entity__household">
<!-- identifier -->
<div v-if="isHouseholdNew()" class="identifier">
<i class="fa fa-home"></i>
{{ $t('new_household') }}
</div>
<div v-else class="identifier">
<i class="fa fa-home"></i>
{{ $t('household_number', { number: household.id } ) }}
</div>
<!-- member part -->
<div v-if="hasCurrentMembers" class="members">
<span class="current-members">{{ $t('current_members') }}: </span>
<template v-for="(m, index) in currentMembers()" :key="m.id">
<person :person="m.person"></person>
<span v-if="m.holder">
&nbsp;<span class="badge badge-primary">{{ $t('holder') }}</span>
</span>
<span v-if="index != (currentMembersLength() - 1)">, </span>
</template>
</div>
<div v-else class="members">
<p class="chill-no-data-statement">{{ $t('no_members_yet') }}</p>
</div>
<!-- address part -->
<div v-if="hasAddress()" class="where">
<i class="fa fa-where"></i>
<show-address :address="household.current_address"></show-address>
</div>
<div v-else class="where">
<i class="fa fa-where"></i>
<p class="chill-no-data-statement">{{ $t('no_current_address') }}</p>
</div>
</div>
</template>
<style lang="scss">
.chill-entity__household {
display: grid;
grid-template-areas:
"identifier identifier where"
"who who where"
;
grid-template-columns:
auto auto 30%
;
.identifier {
grid-area: identifier;
font-size: 1.3em;
font-weight: 700;
color: var(--chill-blue);
}
.members {
grid-area: who;
.current-members {
font-weight: 700;
}
}
.where {
grid-area: where
}
}
</style>
<script>
import Person from 'ChillPersonAssets/vuejs/_components/Person/Person.vue';
import ShowAddress from 'ChillMainAssets/vuejs/_components/ShowAddress.vue';
const i18n = {
"messages":
{
"fr":
{
"household_number": "Ménage #{number}",
"current_members": "Membres actuels",
"no_current_address": "Sans adresse actuellement",
"new_household": "Nouveau ménage",
"no_members_yet": "Aucun membre actuellement",
"holder": "titulaire",
}
}
}
;
export default {
name: 'Household',
props: ['household'],
components: {
Person,
ShowAddress,
},
i18n,
methods: {
hasCurrentMembers() {
return this.household.current_members_id.length > 0;
},
currentMembers() {
return this.household.members.filter(m => this.household.current_members_id.includes(m.id))
.sort((a, b) => {
if (a.position.ordering < b.position.ordering) {
return -1;
}
if (a.position.ordering > b.position.ordering) {
return 1;
}
if (a.holder && !b.holder) {
return -1;
}
if (!a.holder && b.holder) {
return 1;
}
return 0;
});
},
currentMembersLength() {
return this.household.current_members_id.length;
},
isHouseholdNew() {
return !Number.isInteger(this.household.id);
},
hasAddress() {
return this.household.current_address !== null;
}
}
};
</script>

View File

@ -12,7 +12,7 @@
<ul class="record_actions sticky-form-buttons">
<li class="cancel">
<a
href="{{ chill_return_path_or('chill_person_household_members', { 'household_id': household.id}) }}"
href="{{ chill_return_path_or('chill_person_household_summary', { 'household_id': household.id}) }}"
class="sc-button bt-cancel"
>
{{ 'Cancel'|trans }}

View File

@ -12,25 +12,35 @@
<div class="grid-3" id="banner-flags"></div>
<div class="grid-3" id="banner-status"></div>
<div class="grid-3" id="banner-status">
{% set address = household.currentAddress %}
{% if address is empty %}
<p class="chill-no-data-statement">{{ 'household.Household does not have any address currently'|trans }}</p>
{% else %}
<div>
{{ address|chill_entity_render_box({'multiline': true, 'with_valid_from': false}) }}
</div>
{% endif %}
</div>
</div>
</div>
<div class="grid-12 parent" id="header-accompanying_course-details" >
<div class="grid-10 push-1 grid-mobile-12 grid-tablet-12 push-mobile-0 push-tablet-0 parent">
<div id="banner-misc">
{%- set persons = household.getCurrentPersons() -%}
{%- if persons|length > 0 -%}
{%- set members = household.getCurrentMembersOrdered() -%}
{%- if members|length > 0 -%}
<span class="current-members-explain">
{{- 'household.Current household members'|trans }}:
</span>
{%- for p in persons|slice(0, 5) -%}
{{- p|chill_entity_render_box({'addLink': false}) -}}
{%- for m in members|slice(0, 5) -%}
{{- m.person|chill_entity_render_box({'addLink': false}) -}}
{%- if m.holder %} <span class="badge badge-primary">{{ 'household.holder'|trans }}</span>{% endif -%}
{%- if false == loop.last -%}, {% endif -%}
{%- endfor -%}
{% if persons|length > 5 %}
{% if members|length > 5 %}
<span class="current-members-more">
{{ 'household.and x other persons'|trans({'x': persons|length-5}) }}
{{ 'household.and x other persons'|trans({'x': members|length-5}) }}
</span>
{% endif %}
{%- endif -%}

View File

@ -5,49 +5,233 @@
{% block content %}
<h1>{{ block('title') }}</h1>
<h2>{{ 'household.Current household members'|trans }}</h2>
<h2>{{ 'household.Current address'|trans }}</h2>
{% set address = household.currentAddress %}
{% if address is empty %}
<p class="chill-no-data-statement">{{ 'household.Household does not have any address currently'|trans }}</p>
{% else %}
<div>
{{ address|chill_entity_render_box({'multiline': true}) }}
</div>
{% endif %}
<h2>{{ 'household.Household members'|trans }}</h2>
{% if form is not null %}
{{ form_start(form) }}
{{ form_row(form.commentMembers) }}
<div id="waitingForBirthContainer">
{{ form_row(form.waitingForBirth) }}
</div>
<div id="waitingForBirthDateContainer">
{{ form_row(form.waitingForBirthDate) }}
</div>
<ul class="record_actions">
<li>
<button type="submit" class="sc-button bt-save">
{{ 'Save'|trans }}
</button>
</li>
</ul>
{{ form_end(form) }}
{% else %}
{% if not household.commentMembers.isEmpty() %}
{{ household.commentMembers|chill_entity_render_box }}
{% endif %}
{% if household.waitingForBirth %}
{% if household.waitingForBirthDate is not null %}
{{ 'household.Expecting for birth on date'|trans({ 'date': household.waitingForBirthDate|format_date('long') }) }}
{% else %}
{{ 'household.Expecting for birth'|trans }}
{% endif %}
{% else %}
<p class="chill-no-data-statement">
{{ 'household.Any expecting birth'|trans }}
</p>
{% endif %}
<ul class="record_actions">
<li>
<a
href="{{ chill_path_add_return_path('chill_person_household_summary', { 'household_id': household.id, 'edit': 1 }) }}"
class="sc-button bt-edit"
>
{{ 'household.Comment and expecting birth'|trans }}
</a>
</li>
</ul>
{% endif %}
{% for p in positions %}
{%- set members = household.currentMembersByPosition(p) %}
{% if members|length > 0 %}
<h3>{{ p.label|localize_translatable_string }}</h3>
<h3>{{ p.label|localize_translatable_string }}</h3>
{% if false == p.shareHousehold %}
<p>{{ 'household.Those members does not share address'|trans }}</p>
{% endif %}
{% if false == p.shareHousehold %}
<p>{{ 'household.Those members does not share address'|trans }}</p>
{% endif %}
<div class="flex-table list-household-members--summary">
{% for m in members %}
<div class="item-bloc">
<div class="item-row person">
<div class="item-col box-person">
<div>
{{ m.person|chill_entity_render_box({'addLink': true}) }}
{% if m.holder %}
<span class="badge badge-primary">{{ 'household.holder'|trans }}</span>
{%- set members = household.currentMembersByPosition(p) %}
{% if members|length > 0 %}
<div class="flex-table list-household-members">
{% for m in members %}
<div class="item-bloc">
<div class="item-row person">
<div class="item-col box-person">
<div>
{{ m.person|chill_entity_render_box({'addLink': true}) }}
{% if m.holder %}
<span class="badge badge-primary">{{ 'household.holder'|trans }}</span>
{% endif %}
</div>
<div>
{{ 'Born the date'|trans({ 'gender': m.person.gender, 'birthdate': m.person.birthdate|format_date('long') }) }}
</div>
</div>
<div class="item-col box-where">
<ul class="list-content fa-ul">
{% if m.startDate is not empty %}
<li>{{ 'Since %date%'|trans({'%date%': m.startDate|format_date('long') }) }}</li>
{% endif %}
{% if m.endDate is not empty %}
<li>{{ 'Until %date%'|trans({'%date%': m.endDate|format_date('long') }) }}</li>
{% endif %}
</ul>
<ul class="record_actions">
<li>
<a
href="{{ chill_path_add_return_path('chill_person_household_member_edit', { 'id': m.id }) }}"
class="sc-button bt-edit"
/>
{{ 'household.Update membership'|trans }}
</a>
</li>
<li>
<a
href="{{ chill_path_add_return_path(
'chill_person_household_members_editor',
{
'persons': [ m.person.id ],
'household': household.id
} ) }}"
class="sc-button"
/>
<i class="fa fa-arrows-h"></i>
{{ 'household.Change position'|trans }}
</a>
</li>
<li>
<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>
</ul>
</div>
</div>
{% if m.comment is not empty %}
<div class="item-row comment">
<blockquote class="chill-user-quote">
{{ m.comment|chill_markdown_to_html }}
</blockquote>
</div>
{% endif %}
</div>
<div>
{{ 'Born the date'|trans({ 'gender': m.person.gender, 'birthdate': m.person.birthdate|format_date('long') }) }}
</div>
</div>
<div class="item-col box-where">
<ul class="list-content fa-ul">
{% if m.startDate is not empty %}
<li>{{ 'Since %date%'|trans({'%date%': m.startDate|format_date('long') }) }}</li>
{% endif %}
{% if m.endDate is not empty %}
<li>{{ 'Until %date%'|trans({'%date%': m.endDate|format_date('long') }) }}</li>
{% endif %}
</ul>
{% endfor %}
</div>
{% else %}
<p class="chill-no-data-statement">{{ 'household.Any persons into this position'|trans }}</p>
{% endif %}
{% set members = household.nonCurrentMembersByPosition(p) %}
{% if members|length > 0 %}
<p><!-- force a space after table --></p>
<button class="sc-button bt-green" type="button" data-toggle="collapse" data-target="#nonCurrent_{{ p.id }}" aria-expanded="false" aria-controls="collapse non current members">
{{ 'household.Show future or past memberships'|trans({'length': members|length}) }}
</button>
<div id="nonCurrent_{{ p.id }}" class="collapse">
<div class="flex-table list-household-members">
{% for m in members %}
<div class="item-bloc">
<div class="item-row person">
<div class="item-col box-person">
<div>
{{ m.person|chill_entity_render_box({'addLink': true}) }}
{% if m.holder %}
<span class="badge badge-primary">{{ 'household.holder'|trans }}</span>
{% endif %}
</div>
<div>
{{ 'Born the date'|trans({ 'gender': m.person.gender, 'birthdate': m.person.birthdate|format_date('long') }) }}
</div>
</div>
<div class="item-col box-where">
<ul class="list-content fa-ul">
{% if m.startDate is not empty %}
<li>{{ 'Since %date%'|trans({'%date%': m.startDate|format_date('long') }) }}</li>
{% endif %}
{% if m.endDate is not empty %}
<li>{{ 'Until %date%'|trans({'%date%': m.endDate|format_date('long') }) }}</li>
{% endif %}
</ul>
<ul class="record_actions">
<li>
<a
href="{{ chill_path_add_return_path('chill_person_household_member_edit', { 'id': m.id }) }}"
class="sc-button bt-edit"
/>
{{ 'household.Update membership'|trans }}
</a>
</li>
</ul>
</div>
</div>
{% if m.comment is not empty %}
<div class="item-row comment">
<blockquote class="chill-user-quote">
{{ m.comment|chill_markdown_to_html }}
</blockquote>
</div>
{% endif %}
</div>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endif %}
{% endfor %}
<ul class="record_actions">
<li>
<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 }}
</a>
</li>
</ul>
{% endblock %}
{% block js %}
{{ encore_entry_script_tags('household_edit_metadata') }}
{% endblock %}

View File

@ -36,14 +36,30 @@ class HouseholdControllerTest extends WebTestCase
/**
* @dataProvider generateValidHouseholdIds
*/
public function testMembers($householdId)
public function testEditMetadata($householdId)
{
$this->client->request(
$crawler = $this->client->request(
Request::METHOD_GET,
"/fr/person/household/{$householdId}/members"
"/fr/person/household/{$householdId}/summary",
[
'edit' => true
]
);
$this->assertResponseIsSuccessful();
$form = $crawler->selectButton('Enregistrer')
->form();
$form['household[commentMembers][comment]'] = "This is a text **generated** by automatic tests";
$form['household[waitingForBirth]']->tick();
$form['household[waitingForBirthDate]'] = (new \DateTime('today'))
->add(new \DateInterval('P1M'))->format('Y-m-d');
$this->client->submit($form);
$this->assertResponseRedirects("/fr/person/household/{$householdId}/summary");
}
/**
@ -57,6 +73,7 @@ class HouseholdControllerTest extends WebTestCase
);
$this->assertResponseIsSuccessful();
}
/**
@ -93,5 +110,7 @@ class HouseholdControllerTest extends WebTestCase
\shuffle($ids);
yield [ \array_pop($ids)['id'] ];
yield [ \array_pop($ids)['id'] ];
yield [ \array_pop($ids)['id'] ];
}
}

View File

@ -205,19 +205,18 @@ class HouseholdMemberControllerTest extends WebTestCase
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
$yesterday = new \DateTimeImmutable('yesterday');
$personIds = $em->createQuery("SELECT p.id FROM ".Person::class." p ".
"JOIN p.center c ".
"JOIN p.householdParticipations hp ".
"WHERE ".
"c.name = :center ".
"AND hp.startDate < CURRENT_DATE() ".
"AND hp.endDate IS NULL "
"c.name = :center "
)
->setParameter('center', "Center A")
->setMaxResults(100)
->getScalarResult()
;
\shuffle($personIds);
$household = new Household();
@ -229,12 +228,31 @@ class HouseholdMemberControllerTest extends WebTestCase
->getResult()
;
yield [
\array_pop($personIds)['id'],
$household->getId(),
$positions[\random_int(0, count($positions) - 1)]['id'],
new \DateTimeImmutable('today')
];
$i = 0;
do {
$id = \array_pop($personIds)['id'];
$person = self::$container->get(EntityManagerInterface::class)
->getRepository(Person::Class)
->find($id);
$participation = $person->getCurrentHouseholdParticipationShareHousehold();
if (NULL == $participation ||
(
NULL === $participation->getEndDate()
&& $participation->getStartDate() <= $yesterday
)) {
$i++;
yield [
$id,
$household->getId(),
$positions[\random_int(0, count($positions) - 1)]['id'],
new \DateTimeImmutable('tomorrow')
];
}
} while ($i <= 1);
}
public function provideValidDataEditMember(): \Iterator

View File

@ -23,6 +23,7 @@ household:
Any persons into this position: Aucune personne n'appartient au ménage à cette position.
Leave: Quitter le ménage
Join: Rejoindre un ménage
Change position: Repositionner
Household file: Dossier ménage
Add a member: Ajouter un membre
Update membership: Modifier
@ -37,6 +38,8 @@ household:
Current household members: Membres actuels du ménage
Household summary: Résumé
Addresses: Adresses
Current address: Adresse actuelle
Household does not have any address currently: Le ménage n'a pas d'adresse renseignée actuellement
Edit household members: Modifier l'appartenance au ménage
and x other persons: >-
{x, plural,