Merge branch 'master' of gitlab.com:Chill-Projet/chill-bundles

This commit is contained in:
2021-08-20 18:46:43 +02:00
72 changed files with 2630 additions and 666 deletions

View File

@@ -499,6 +499,17 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
return $collection->count() > 0 ? $collection->first() : NULL;
}
public function getOPenParticipations(): Collection
{
return $this
->getParticipations()
->filter(
static function(AccompanyingPeriodParticipation $participation): bool {
return null === $participation->getEndDate();
}
);
}
/**
* Return true if the accompanying period contains a person.
*
@@ -974,6 +985,22 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
return $this->personLocation;
}
/**
* Get a list of person which have an adresse available for a valid location
*
* @return Collection|Person[]
*/
public function getAvailablePersonLocation(): Collection
{
return $this->getOPenParticipations()
->filter(function(AccompanyingPeriodParticipation $p) {
return $p->getPerson()->hasCurrentHouseholdAddress();
})
->map(function(AccompanyingPeriodParticipation $p) {
return $p->getPerson();
});
}
/**
* @Groups({"write"})
*/

View File

@@ -169,7 +169,7 @@ use Symfony\Component\Validator\Constraints as Assert;
* orphanRemoval=true
* )
* @Serializer\Groups({"read"})
* @internal /!\ the serialization for read / write evaluations is handled in `AccompanyingPeriodWorkDenormalizer`
* @internal /!\ the serialization for write evaluations is handled in `AccompanyingPeriodWorkDenormalizer`
*/
private Collection $accompanyingPeriodWorkEvaluations;

View File

@@ -120,12 +120,29 @@ class AccompanyingPeriodWorkEvaluation implements TrackUpdateInterface, TrackCre
* @var Collection
* @ORM\OneToMany(
* targetEntity=AccompanyingPeriodWorkEvaluationDocument::class,
* mappedBy="accompanyingPeriodWorkEvaluation"
* mappedBy="accompanyingPeriodWorkEvaluation",
* cascade={"remove"}
* )
* @Serializer\Groups({"read"})
*/
private Collection $documents;
/**
* This is a workaround for client, to allow them to assign arbitrary data
* dedicated to their job.
*
* This data is not persisted into database, but will appears on the data
* normalized during the same request (like PUT/PATCH request)
*
* @Serializer\Groups({"read"})
* @Serializer\Groups({"write"})
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
*
* @var mixed
*
*/
private $key = null;
public function __construct()
{
$this->documents = new ArrayCollection();
@@ -187,7 +204,8 @@ class AccompanyingPeriodWorkEvaluation implements TrackUpdateInterface, TrackCre
($this->evaluation instanceof Evaluation
&& null === $evaluation)
) {
throw new \LogicException("once set, an ${self::class} cannot
$cl = AccompanyingPeriodWorkEvaluation::class;
throw new \LogicException("once set, an $cl cannot
change or remove the linked Evaluation::class");
}
@@ -366,5 +384,42 @@ class AccompanyingPeriodWorkEvaluation implements TrackUpdateInterface, TrackCre
return $this->documents;
}
public function addDocument(AccompanyingPeriodWorkEvaluationDocument $document): self
{
if (!$this->documents->contains($document)) {
$this->documents[] = $document;
$document->setAccompanyingPeriodWorkEvaluation($this);
}
return $this;
}
public function removeDocument(AccompanyingPeriodWorkEvaluationDocument $document): self
{
$this->documents->removeElement($document);
return $this;
}
/**
* Arbitrary data, used for client
*
* @return mixed
*/
public function getKey()
{
return $this->key;
}
/**
* Arbitrary data, used for client
*
* @param mixed $key
* @return AccompanyingPeriodWorkEvaluation
*/
public function setKey($key): self
{
$this->key = $key;
return $this;
}
}

View File

@@ -2,6 +2,7 @@
namespace Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
@@ -37,7 +38,7 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
* inversedBy="documents"
* )
*/
private ?AccompanyingPeriodWorkEvaluation $accompanyingPeriodWorkEvaluation;
private ?AccompanyingPeriodWorkEvaluation $accompanyingPeriodWorkEvaluation = null;
/**
* @ORM\ManyToOne(
@@ -45,13 +46,13 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
* )
* @Serializer\Groups({"read"})
*/
private ?User $createdBy;
private ?User $createdBy = null;
/**
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
* @Serializer\Groups({"read"})
*/
private ?\DateTimeImmutable $createdAt;
private ?\DateTimeImmutable $createdAt = null;
/**
* @ORM\ManyToOne(
@@ -59,18 +60,29 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
* )
* @Serializer\Groups({"read"})
*/
private ?User $updatedBy;
private ?User $updatedBy = null;
/**
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
* @Serializer\Groups({"read"})
*/
private ?DateTimeImmutable $updatedAt;
private ?\DateTimeImmutable $updatedAt = null;
// TODO: indiquer le document généré par le module "document"
private ?StoredObject $storedObject;
/**
* @ORM\ManyToOne(
* targetEntity=StoredObject::class
* )
* @Serializer\Groups({"read"})
*/
private ?StoredObject $storedObject = null;
// TODO: ajouter gabarit
/**
* @ORM\ManyToOne(
* targetEntity=DocGeneratorTemplate::class
* )
* @Serializer\Groups({"read"})
*/
private ?DocGeneratorTemplate $template = null;
/**
* @return AccompanyingPeriodWorkEvaluation|null
@@ -86,6 +98,14 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
*/
public function setAccompanyingPeriodWorkEvaluation(?AccompanyingPeriodWorkEvaluation $accompanyingPeriodWorkEvaluation): AccompanyingPeriodWorkEvaluationDocument
{
// if an evaluation is already associated, we cannot change the association (removing the association,
// by setting a null value, is allowed.
if ($this->accompanyingPeriodWorkEvaluation instanceof AccompanyingPeriodWorkEvaluation
&& $accompanyingPeriodWorkEvaluation instanceof AccompanyingPeriodWorkEvaluation) {
if ($this->accompanyingPeriodWorkEvaluation !== $accompanyingPeriodWorkEvaluation) {
throw new \RuntimeException("It is not allowed to change the evaluation for a document");
}
}
$this->accompanyingPeriodWorkEvaluation = $accompanyingPeriodWorkEvaluation;
return $this;
}
@@ -172,4 +192,22 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
return $this->updatedAt;
}
/**
* @return DocGeneratorTemplate|null
*/
public function getTemplate(): ?DocGeneratorTemplate
{
return $this->template;
}
/**
* @param DocGeneratorTemplate|null $template
* @return AccompanyingPeriodWorkEvaluationDocument
*/
public function setTemplate(?DocGeneratorTemplate $template): AccompanyingPeriodWorkEvaluationDocument
{
$this->template = $template;
return $this;
}
}

View File

@@ -103,7 +103,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
*
* @ORM\Column(type="date", nullable=true)
*/
private $birthdate;
private $birthdate;
/**
* The person's deathdate
@@ -736,8 +736,8 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
{
return $this->birthdate;
}
public function getAge(): ?int
public function getAge(): ?int
{
if ($this->birthdate instanceof \DateTimeInterface) {
return date_diff($this->birthdate, date_create('now'))->format("%y");
@@ -1439,6 +1439,11 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
}
}
public function hasCurrentHouseholdAddress(?\DateTimeImmutable $at = null): bool
{
return null !== $this->getCurrentHouseholdAddress($at);
}
public function getGenderComment(): CommentEmbeddable
{
return $this->genderComment;

View File

@@ -0,0 +1,49 @@
const onSubmit = function(e) {
e.preventDefault();
let
form = e.target,
formData = new FormData(form),
url = form.action,
payload = {
type: 'accompanying_period',
id: Number.parseInt(formData.get('periodId'), 10),
personLocation: {
type: 'person',
id: Number.parseInt(formData.get('personLocation'), 10)
}
}
;
console.log('event', e);
console.log('form', form);
console.log('formData', formData);
console.log('url', url);
console.log('payload', payload);
window.fetch(url, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload),
})
.then(r => {
if (r.ok) {
console.log('response ok');
window.location.reload();
} else {
console.err("could not patch accompanying course");
}
});
}
window.addEventListener('DOMContentLoaded', function(e) {
let forms = document.querySelectorAll('.quickLocationForm');
console.log(forms);
forms.forEach(function(form){
console.log('form quickLocation found', form);
form.addEventListener('submit', onSubmit);
})
});

View File

@@ -1,374 +0,0 @@
import { createStore } from 'vuex';
import { householdMove, fetchHouseholdSuggestionByAccompanyingPeriod, fetchAddressSuggestionByPerson} from './../api.js';
import { datetimeToISO } from 'ChillMainAssets/chill/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: "",
};
});
console.log('expand suggestions', window.household_members_editor_expand_suggestions === 1);
const store = createStore({
strict: debug,
state: {
concerned,
household: window.household_members_editor_data.household,
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,
allowLeaveWithoutHousehold: window.household_members_editor_data.allowLeaveWithoutHousehold,
forceLeaveWithoutHousehold: false,
householdSuggestionByAccompanyingPeriod: [],
showHouseholdSuggestion: window.household_members_editor_expand_suggestions === 1,
addressesSuggestion: [],
warnings: [],
errors: []
},
getters: {
isHouseholdNew(state) {
if (state.household === null) {
return false;
}
return !Number.isInteger(state.household.id);
},
hasHousehold(state) {
return state.household !== null;
},
hasHouseholdOrLeave(state) {
return state.household !== null || state.forceLeaveWithoutHousehold;
},
hasHouseholdSuggestion(state, getters) {
return getters.filterHouseholdSuggestionByAccompanyingPeriod.length > 0;
},
countHouseholdSuggestion(state, getters) {
return getters.filterHouseholdSuggestionByAccompanyingPeriod.length;
},
filterHouseholdSuggestionByAccompanyingPeriod(state) {
if (state.household === null) {
return state.householdSuggestionByAccompanyingPeriod;
}
return state.householdSuggestionByAccompanyingPeriod
.filter(h => h.id !== state.household.id)
;
},
hasPersonsWellPositionnated(state, getters) {
return getters.needsPositionning === false
|| (getters.persons.length > 0 && getters.concUnpositionned.length === 0);
},
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: [], current_address: null,
current_members_id: [] };
state.forceLeaveWithoutHousehold = false;
},
removeHousehold(state) {
state.household = null;
state.forceLeaveWithoutHousehold = false;
},
setHouseholdAddress(state, address) {
if (null === state.household) {
console.error("no household");
throw new Error("No household");
}
state.household.current_address = address;
state.household.force_new_address = address;
},
forceLeaveWithoutHousehold(state) {
state.household = null;
state.forceLeaveWithoutHousehold = true;
},
selectHousehold(state, household) {
state.household = household;
state.forceLeaveWithoutHousehold = false;
},
setHouseholdSuggestionByAccompanyingPeriod(state, households) {
let existingIds = state.householdSuggestionByAccompanyingPeriod
.map(h => h.id);
for (let i in households) {
if (!existingIds.includes(households[i].id)) {
state.householdSuggestionByAccompanyingPeriod.push(households[i]);
}
}
},
setStartDate(state, dateI) {
state.startDate = dateI;
},
toggleHouseholdSuggestion(state) {
state.showHouseholdSuggestion = !state.showHouseholdSuggestion;
},
setWarnings(state, warnings) {
state.warnings = warnings;
// reset errors, which should come from servers
state.errors.splice(0, state.errors.length);
},
setErrors(state, errors) {
state.errors = errors;
},
addAddressesSuggestion(state, addresses) {
let existingIds = state.addressesSuggestion
.map(a => a.id);
for (let i in addresses) {
if (!existingIds.includes(addresses[i].id)) {
state.addressesSuggestion.push(addresses[i]);
}
}
}
},
actions: {
addConcerned({ commit, dispatch }, person) {
commit('addConcerned', person);
dispatch('computeWarnings');
dispatch('fetchAddressSuggestions');
},
markPosition({ commit, state, dispatch }, { person_id, position_id }) {
commit('markPosition', { person_id, position_id });
dispatch('computeWarnings');
},
toggleHolder({ commit, dispatch }, conc) {
commit('toggleHolder', conc);
dispatch('computeWarnings');
},
removePosition({ commit, dispatch }, conc) {
commit('removePosition', conc);
dispatch('computeWarnings');
},
removeConcerned({ commit, dispatch }, conc) {
commit('removeConcerned', conc);
dispatch('computeWarnings');
dispatch('fetchAddressSuggestions');
},
removeHousehold({ commit, dispatch }) {
commit('removeHousehold');
dispatch('computeWarnings');
},
createHousehold({ commit, dispatch }) {
commit('createHousehold');
dispatch('computeWarnings');
},
forceLeaveWithoutHousehold({ commit, dispatch }) {
commit('forceLeaveWithoutHousehold');
dispatch('computeWarnings');
},
selectHousehold({ commit }, h) {
commit('selectHousehold', h);
dispatch('computeWarnings');
},
setStartDate({ commit, dispatch }, date) {
commit('setStartDate', date);
dispatch('computeWarnings');
},
setComment({ commit }, payload) {
commit('setComment', payload);
},
fetchHouseholdSuggestionForConcerned({ commit, state }, person) {
fetchHouseholdSuggestionByAccompanyingPeriod(person.id)
.then(households => {
commit('setHouseholdSuggestionByAccompanyingPeriod', households);
});
},
fetchAddressSuggestions({ commit, state }) {
for (let i in state.concerned) {
fetchAddressSuggestionByPerson(state.concerned[i].person.id)
.then(addresses => {
commit('addAddressesSuggestion', addresses);
})
.catch(e => {
console.log(e);
});
}
},
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, commit }) {
let payload = getters.buildPayload,
errors = [],
person_id,
household_id,
error
;
householdMove(payload).then(household => {
if (household === null) {
person_id = getters.persons[0].id;
window.location.replace(`/fr/person/${person_id}/general`);
} else {
if (household.type === 'household') {
household_id = household.id;
// nothing to do anymore here, bye-bye !
window.location.replace(`/fr/person/household/${household_id}/summary`);
} else {
// we assume the answer was 422...
error = household;
for (let i in error.violations) {
let e = error.violations[i];
errors.push(e.title);
}
commit('setErrors', errors);
}
}
});
},
}
});
store.dispatch('computeWarnings');
store.dispatch('fetchAddressSuggestions');
if (concerned.length > 0) {
concerned.forEach(c => {
store.dispatch('fetchHouseholdSuggestionForConcerned', c.person);
});
}
export { store };

View File

@@ -68,12 +68,15 @@
</li>
</ul>
</div>
<ul class="record_actions">
<ul class="record_actions" v-if="availableForCheckGoal.length > 0">
<li>
<button :title="$t('add_an_objective')" class="btn btn-create"
@click="toggleAddObjective"></button>
</li>
</ul>
<div v-else>
<span class="chill-no-data-statement">{{ $t('no_goals_available') }}</span>
</div>
</div>
<div><!-- empty for results --></div>
</div>
@@ -102,11 +105,14 @@
</li>
</ul>
</div>
<ul class="record_actions">
<ul class="record_actions" v-if="evaluationsForAction.length > 0">
<li>
<button :title="$t('add_an_evaluation')" class="btn btn-create" @click="toggleAddEvaluation"></button>
</li>
</ul>
<div v-else>
<span class="chill-no-data-statement">{{ $t('no_evaluations_available') }}</span>
</div>
</div>
</div>
@@ -255,6 +261,8 @@ const i18n = {
choose_thirdparties: "Choisir des tiers",
fix_these_errors: "Veuillez corriger les erreurs suivantes :",
available_evaluations_text: "Évaluations disponibles pour ajout :",
no_evaluations_available: "Aucune évaluation disponible",
no_goals_available: "Aucun objectif disponible",
}
}
};
@@ -378,7 +386,6 @@ export default {
this.$store.commit('removeGoal', g);
},
addEvaluation(e) {
console.log('add Evaluation', e);
this.$store.commit('addEvaluation', e);
},
toggleAddEvaluation() {

View File

@@ -19,8 +19,20 @@
<dt v-if="evaluation.warningInterval">{{ $t('warningInterval') }} :</dt>
<dd v-if="evaluation.warningInterval">{{ evaluation.warningInterval }}</dd>
<dt v-if="evaluation.documents && evaluation.documents.length > 0">{{ $t('documents') }} :</dt>
<dd v-if="evaluation.documents && evaluation.documents.length > 0">{{ evaluation.documents.length }}</dd>
<template v-if="evaluation.documents.length > 0">
<dt>{{ $t('documents') }} :</dt>
<dd>
<ul>
<li v-for="d in evaluation.documents">
{{ d.template.name.fr }}
<a :href="buildEditLink(d.storedObject)" class="btn btn-action btn-sm">
<i class="fa fa-edit"></i>
</a>
</li>
</ul>
</dd>
</template>
</dl>
<dl class="item-details">
@@ -82,7 +94,7 @@ export default {
computed: {
pickedEvaluations() {
return this.$store.state.evaluationsPicked;
}
},
},
methods: {
removeEvaluation(e) {
@@ -95,7 +107,11 @@ export default {
},
submitForm() {
this.toggleEditEvaluation();
}
},
buildEditLink(storedObject) {
return `/fr/chill_wopi/edit/${storedObject.filename}?returnPath=` + encodeURIComponent(
window.location.pathname + window.location.search + window.location.hash);
},
}
}
</script>

View File

@@ -67,19 +67,15 @@
<div class="input-group">
<select class="form-select form-select-sm" v-model="template">
<option disabled value="">{{ $t('evaluation_choose_a_template') }}</option>
<option>A</option>
<template v-for="t in getTemplatesAvailaibleForEvaluation">
<option v-bind:value="t.id">{{ t.name.fr }}</option>
</template>
</select>
<button class="btn btn-update btn-sm change-icon" type="button"><i class="fa fa-fw fa-cog"></i></button>
<button v-if="canGenerate" class="btn btn-update btn-sm change-icon" type="button" @click="generateDocument"><i class="fa fa-fw fa-cog"></i></button>
<button v-else class="btn btn-update btn-sm change-icon" type="button" disabled ><i class="fa fa-fw fa-cog"></i></button>
</div>
</div>
</div>
<!--div class="row mb-3">
<label class="col-sm-4 col-form-label">{{ $t('evaluation_add_a_document') }}</label>
<div class="col-sm-8">
<button class="btn btn-create btn-sm" @click="documents"></button>
</div>
</div-->
</div>
</div>
</template>
@@ -88,6 +84,7 @@
import {dateToISO, ISOToDate, ISOToDatetime} from 'ChillMainAssets/chill/js/date.js';
import CKEditor from '@ckeditor/ckeditor5-vue';
import ClassicEditor from 'ChillMainAssets/module/ckeditor5/index.js';
import { mapGetters, mapState } from 'vuex';
const i18n = {
messages: {
@@ -119,25 +116,19 @@ export default {
data() {
return {
editor: ClassicEditor,
//evaluation: {
// status: null,
// startDate: null,
// endDate: null,
// maxDate: null,
// warningInterval: null,
// comment: null,
// template: null,
// //documents: null
//}
template: null,
}
},
computed: {
/*
status: {
get() { return this.evaluation.status; },
set(v) { this.evaluation.status = v; }
...mapGetters([
'getTemplatesAvailaibleForEvaluation'
]),
...mapState([
'isPosting'
]),
canGenerate() {
return !this.$store.state.isPosting && this.template !== null;
},
*/
startDate: {
get() {
return dateToISO(this.evaluation.startDate);
@@ -171,10 +162,6 @@ export default {
get() { return this.evaluation.comment; },
set(v) { this.$store.commit('setEvaluationComment', { key: this.evaluation.key, comment: v }); }
},
template: {
get() { return this.evaluation.template; },
set(v) { this.evaluation.template = v; }
},
},
methods: {
listAllStatus() {
@@ -189,6 +176,10 @@ export default {
})
;
},
generateDocument() {
console.log('template picked', this.template);
this.$store.dispatch('generateDocument', { key: this.evaluation.key, templateId: this.template})
}
},
mounted() {
//this.listAllStatus();

View File

@@ -4,6 +4,7 @@ import { findSocialActionsBySocialIssue } from 'ChillPersonAssets/vuejs/_api/Soc
import { create } from 'ChillPersonAssets/vuejs/_api/AccompanyingCourseWork.js';
const debug = process.env.NODE_ENV !== 'production';
const evalFQDN = encodeURIComponent("Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation");
const store = createStore({
strict: debug,
@@ -32,6 +33,7 @@ const store = createStore({
return k;
}),
evaluationsForAction: [],
templatesAvailableForEvaluation: [],
personsPicked: window.accompanyingCourseWork.persons,
personsReachables: window.accompanyingCourseWork.accompanyingPeriod.participations.filter(p => p.endDate == null)
.map(p => p.person),
@@ -63,6 +65,9 @@ const store = createStore({
hasThirdParties(state) {
return state.thirdParties.length > 0;
},
getTemplatesAvailaibleForEvaluation(state) {
return state.templatesAvailableForEvaluation;
},
buildPayload(state) {
return {
type: 'accompanying_period_work',
@@ -99,6 +104,7 @@ const store = createStore({
accompanyingPeriodWorkEvaluations: state.evaluationsPicked.map(e => {
let o = {
type: e.type,
key: e.key,
evaluation: {
id: e.evaluation.id,
type: e.evaluation.type
@@ -216,6 +222,11 @@ const store = createStore({
let evaluation = state.evaluationsPicked.find(e => e.key === key);
evaluation.editEvaluation = !evaluation.editEvaluation;
},
setTemplatesAvailableForEvaluation(state, templates) {
for (let i in templates) {
state.templatesAvailableForEvaluation.push(templates[i]);
}
},
setPersonsPickedIds(state, ids) {
state.personsPicked = state.personsReachables
.filter(p => ids.includes(p.id))
@@ -317,7 +328,38 @@ const store = createStore({
commit('setEvaluationsForAction', data.results);
});
},
submit({ getters, state, commit }) {
getReachableTemplatesForEvaluation({commit}) {
const
url = `/fr/doc/gen/templates/for/${evalFQDN}`
;
window.fetch(url).then(r => {
if (r.ok) {
return r.json();
}
throw new Error("not possible to load templates for evaluations")
}).then(data => {
commit('setTemplatesAvailableForEvaluation', data.results);
}).catch(e => {
console.error(e);
})
},
generateDocument({ dispatch }, {key, templateId}) {
const callback = function(data) {
// get the evaluation id from the data
const
evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === key).id,
returnPath = encodeURIComponent(window.location.pathname + window.location.search + window.location.hash),
url = `/fr/doc/gen/generate/from/${templateId}/for/${evalFQDN}/${evaluationId}?returnPath=${returnPath}`
;
//http://localhost:8001/fr/doc/gen/generate/from/12/for/Chill%5CPersonBundle%5CEntity%5CAccompanyingPeriod%5CAccompanyingPeriodWorkEvaluation/41
console.log('I will generate your doc at', url);
window.location.assign(url);
};
dispatch('submit', callback);
},
submit({ getters, state, commit }, callback) {
let
payload = getters.buildPayload,
url = `/api/1.0/person/accompanying-course/work/${state.work.id}.json`,
@@ -345,6 +387,8 @@ const store = createStore({
}
commit('setErrors', errors);
commit('setIsPosting', false);
} else if (typeof(callback) !== 'undefined') {
callback(data);
} else {
console.info('nothing to do here, bye bye');
window.location.assign(`/fr/person/accompanying-period/${state.work.accompanyingPeriod.id}/work`);
@@ -360,6 +404,7 @@ const store = createStore({
dispatch('getReachablesResultsForAction');
dispatch('getReachablesGoalsForAction');
dispatch('getReachablesEvaluationsForAction');
dispatch('getReachableTemplatesForEvaluation');
},
}
});

View File

@@ -7,35 +7,43 @@
</div>
<div v-if="isHouseholdNew && !hasHouseholdAddress">
<h3>À quelle adresse habite ce ménage ?</h3>
<h3 >À quelle adresse habite ce ménage ?</h3>
<ul v-if="filterAddressesSuggestion.length > 0">
<li v-for="a in filterAddressesSuggestion">
<show-address :address="a"></show-address>
<button class="btn" @click="setHouseholdAddress(a)">
<div v-if="filterAddressesSuggestion.length > 0" class="flex-table householdAddressSuggestionList">
<div v-for="a in filterAddressesSuggestion" class="item-bloc">
<show-address :address="a"></show-address>
<button class="btn btn-action" @click="setHouseholdAddress(a)">
Le ménage habite cette adresse
</button>
</li>
</ul>
</div>
</div>
<div v-else>
<span class="chill-no-data-statement">Aucune adresse à suggérer</span>
</div>
<ul class="record_actions">
<li >
<button class="btn">
Créer une adresse
</button>
<add-address
:context="addAddress.context"
:key="addAddress.key"
:options="addAddress.options"
:result="addAddress.result"
@submitAddress="setHouseholdCreatedAddress"
ref="addAddress">
</add-address>
</li>
</ul>
</div>
<div v-if="isHouseholdNew && hasHouseholdAddress">
<ul class="record_actions">
<li >
<button class="btn" @click="removeHouseholdAddress">
<button class="btn btn-misc" @click="removeHouseholdAddress">
Supprimer cette adresse
</button>
</li>
</ul>
</div>
</div>
<div v-else-if="isForceLeaveWithoutHousehold">
@@ -51,7 +59,7 @@
class="btn btn-misc"
@click="toggleHouseholdSuggestion"
>
{{ $tc('household_members_editor.show_household_suggestion',
{{ $tc('household_members_editor.show_household_suggestion',
countHouseholdSuggestion) }}
</button>
</li>
@@ -106,11 +114,24 @@
</div>
</div>
</div>
</template>
<style lang="scss">
div.householdAddressSuggestionList {
/*
display: flex;
list-style-type: none;
padding: 0;
*/
& > li {
}
}
.householdSuggestionList {
display: flex;
flex-direction: row;
@@ -136,12 +157,45 @@
import { mapGetters, mapState } from 'vuex';
import HouseholdViewer from 'ChillPersonAssets/vuejs/_components/Household/Household.vue';
import ShowAddress from 'ChillMainAssets/vuejs/Address/components/ShowAddress.vue';
import AddAddress from 'ChillMainAssets/vuejs/Address/components/AddAddress.vue';
export default {
name: 'Household',
components: {
HouseholdViewer,
ShowAddress,
AddAddress,
},
data() {
return {
addAddress: {
context: {
entity: {
type: 'household_create',
id: 0
},
edit: false,
addressId: null
},
key: 'household_new',
options: {
hideDateFrom: true,
bindModal: {
},
button: {
text: {
create: null,
edit: null,
}
},
title: {
create: null,
edit: null,
},
}
}
}
},
computed: {
...mapGetters([
@@ -172,13 +226,13 @@ export default {
allowRemoveHousehold() {
return this.$store.getters.hasHousehold &&
(
this.allowHouseholdCreate || this.allowHouseholdSearch ||
this.allowHouseholdCreate || this.allowHouseholdSearch ||
this.allowLeaveWithoutHousehold
)
;
},
allowChangeHousehold() {
return this.allowHouseholdCreate || this.allowHouseholdSearch ||
return this.allowHouseholdCreate || this.allowHouseholdSearch ||
this.allowLeaveWithoutHousehold;
},
isForceLeaveWithoutHousehold() {
@@ -202,8 +256,15 @@ export default {
this.$store.dispatch('removeHousehold');
},
setHouseholdAddress(a) {
let payload = this.$refs.addAddress.submitNewAddress();
console.log('setHouseholdAddress', a);
this.$store.commit('setHouseholdAddress', a);
},
setHouseholdCreatedAddress() {
let payload = this.$refs.addAddress.submitNewAddress();
console.log('setHouseholdAddress', payload);
this.$store.dispatch('setHouseholdNewAddress', payload);
},
removeHouseholdAddress() {
this.$store.commit('removeHouseholdAddress');
}

View File

@@ -14,8 +14,6 @@ const concerned = window.household_members_editor_data.persons.map(p => {
};
});
console.log('expand suggestions', window.household_members_editor_expand_suggestions === 1);
const store = createStore({
strict: debug,
state: {
@@ -211,7 +209,12 @@ const store = createStore({
)
},
createHousehold(state) {
state.household = { type: 'household', members: [], current_address: null, current_members_id: [] }
state.household = {
type: 'household',
members: [],
current_address: null,
current_members_id: []
};
state.forceLeaveWithoutHousehold = false;
},
removeHousehold(state) {
@@ -274,7 +277,7 @@ const store = createStore({
state.addressesSuggestion.push(addresses[i]);
}
}
}
},
},
actions: {
addConcerned({ commit, dispatch }, person) {
@@ -307,6 +310,19 @@ const store = createStore({
commit('createHousehold');
dispatch('computeWarnings');
},
setHouseholdNewAddress({ commit }, payload) {
let url = `/api/1.0/main/address/${payload.addressId}.json`;
window.fetch(url).then(r => {
if (r.ok) {
return r.json();
}
throw new Error("error while fetch address");
}).then(data => {
commit('setHouseholdAddress', data);
}).catch(e => {
console.error(e);
});
},
forceLeaveWithoutHousehold({ commit, dispatch }) {
commit('forceLeaveWithoutHousehold');
dispatch('computeWarnings');
@@ -373,8 +389,14 @@ const store = createStore({
} else {
if (household.type === 'household') {
household_id = household.id;
// nothing to do anymore here, bye-bye !
window.location.replace(`/fr/person/household/${household_id}/summary`);
let params = new URLSearchParams(window.location.search);
if (params.has('returnPath')) {
window.location.replace(params.get('returnPath'));
} else {
window.location.replace(`/fr/person/household/${household_id}/summary`);
}
} else {
// we assume the answer was 422...
error = household;

View File

@@ -10,7 +10,7 @@
{{ $t('household_number', { number: household.id } ) }}
</div>
<!-- member part -->
<!-- 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">
@@ -40,7 +40,7 @@
<style lang="scss">
.chill-entity__household {
display: grid;
grid-template-areas:
grid-template-areas:
"identifier identifier where"
"who who where"
;
@@ -50,7 +50,7 @@
.identifier {
grid-area: identifier;
font-size: 1.3em;
font-weight: 700;
color: var(--chill-blue);
@@ -110,7 +110,7 @@ export default {
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;
return -1;
}
if (a.position.ordering > b.position.ordering) {
return 1;

View File

@@ -14,7 +14,7 @@
<div id="withoutHouseholdList" class="collapse p-3">
<form method="GET"
action="{{ chill_path_add_return_path('chill_person_household_members_editor') }}">
action="{{ path('chill_person_household_members_editor') }}">
<h3>{{ 'household.Select people to move'|trans }}</h3>
<ul>
@@ -27,6 +27,7 @@
</ul>
<input type="hidden" name="expand_suggestions" value="true" />
<input type="hidden" name="returnPath" value="{{ app.request.requestUri|escape('html_attr') }}" />
<input type="hidden" name="accompanying_period_id" value="{{ accompanyingCourse.id }}" />
<ul class="record_actions mb-0">
<li>

View File

@@ -1,5 +1,61 @@
<div class="alert alert-danger alert-with-actions mb-0">
<div class="message">
{{ 'This course is located at a temporarily address. You should locate this course to an user'|trans }}
{%- set countPersonLocation = accompanyingCourse.availablePersonLocation|length -%}
{%- set hasPersonLocation = countPersonLocation|length > 0 -%}
{% macro quickLocationForm(accompanyingCourse, person, whichButton) %}
<form method="PATCH" action="{{ path('chill_api_single_accompanying_course__entity', {'id': accompanyingCourse.id, '_format': 'json'}) }}" class="quickLocationForm">
<input type="hidden" name="personLocation" value="{{ person.id }}" />
<input type="hidden" name="periodId" value="{{ accompanyingCourse.id }}" />
{% if whichButton == 'string' %}
<button type="submit" class="btn btn-chill-pink">
<span class="text-light">{{ 'Locate by'|trans }} {{ person|chill_entity_render_string }}</span>
</button>
{% elseif whichButton == 'icon' %}
<button type="submit" class="btn btn-sm btn-secondary">
<i class="fa fa-map-marker"></i>
</button>
{% endif %}
</form>
{% endmacro %}
<div class="border border-danger">
<div class="alert alert-danger {% if hasPersonLocation %}alert-with-actions{% endif %} mb-0">
<div class="message">
{{ 'This course is located at a temporarily address. You should locate this course to an user'|trans }}
{% if not hasPersonLocation %}
{{ 'Associate at least one member with an household, and set an address to this household'|trans }}
{% endif %}
</div>
{% if 1 == countPersonLocation %}
<ul class="record_actions">
<li>
{{ _self.quickLocationForm(accompanyingCourse, accompanyingCourse.availablePersonLocation.first, 'string') }}
</li>
</ul>
{% elseif 1 < countPersonLocation %}
<ul class="record_actions">
<li>
<button class="btn btn-chill-pink" data-bs-toggle="collapse" href="#locateAtPerson">
<i class="fa fa-fw fa-caret-down"></i><span class="text-light">{{ 'Choose a person to locate by'|trans }}</span>
</button>
</li>
</ul>
{% endif %}
</div>
{% if 1 < countPersonLocation %}
<div id="locateAtPerson" class="collapse">
<p>{{ 'Locate by'|trans }}:</p>
<div class="flex-table mb-3">
{% for p in accompanyingCourse.availablePersonLocation %}
<div class="item-bloc">
{{ p|chill_entity_render_box({
'render': 'bloc', 'addLink': false, 'addInfo': true, 'addAltNames': false, 'customButtons': {
'replace': _self.quickLocationForm(accompanyingCourse, p, 'icon')
}
}) }}
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>

View File

@@ -15,6 +15,11 @@
{% endif %}
{% endmacro %}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('page_accompanying_course_index_person_locate') }}
{% endblock %}
{% block content %}
<div class="accompanyingcourse-resume">
@@ -27,11 +32,6 @@
</a>
</span>
</div>
{% if accompanyingCourse.locationStatus == 'address' or accompanyingCourse.locationStatus == 'none' %}
<div class="alert alert-danger">
{{ 'This course is located at a temporarily address. You should locate this course to an user'|trans }}
</div>
{% endif %}
{% endif %}
<h1>{{ 'Resume Accompanying Course'|trans }}</h1>
@@ -54,12 +54,29 @@
{% if withoutHousehold|length > 0 %}
{% include '@ChillPerson/AccompanyingCourse/_join_household.html.twig' with {} %}
{% endif %}
{% if accompanyingCourse.locationStatus == 'address' or accompanyingCourse.locationStatus == 'none' %}
{% include '@ChillPerson/AccompanyingCourse/_warning_address.html.twig' with {} %}
{% endif %}
{% endif %}
</div>
<div class="location mb-5">
<h2 class="mb-3">{{ 'Accompanying course location'|trans }}</h2>
{% if accompanyingCourse.locationStatus == 'person' %}
<p>{{ 'This course is located by'|trans }}: {{ accompanyingCourse.personLocation|chill_entity_render_string }}</p>
{% elseif accompanyingCourse.locationStatus == 'address' %}
<p>{{ 'This course has a temporarily location'|trans }}</p>
{% endif %}
{% if accompanyingCourse.locationStatus != 'none' %}
{{ accompanyingCourse.location|chill_entity_render_box }}
{% endif %}
{% if accompanyingCourse.locationStatus == 'address' or accompanyingCourse.locationStatus == 'none' %}
{% include '@ChillPerson/AccompanyingCourse/_warning_address.html.twig' with {} %}
{% endif %}
</div>
<div class="requestor mb-5">
<h2 class="mb-3">{{ 'Requestor'|trans }}</h2>
{% if accompanyingCourse.requestorPerson is not empty %}

View File

@@ -17,6 +17,10 @@
'layout': '@ChillPerson/menu.html.twig',
'args' : { 'accompanyingCourse': accompanyingCourse }
}) }}
{% block block_post_menu %}
{% endblock %}
{% endblock %}
{% block css %}

View File

@@ -1,4 +1,4 @@
{% extends accompanyingCourse != null ? '@ChillPerson/AccompanyingCourse/layout.html.twig'
{% extends accompanyingCourse != null ? '@ChillPerson/AccompanyingCourse/layout.html.twig'
: '@ChillMain/layout.html.twig' %}
{% block title 'household.Edit household members'|trans %}
@@ -13,11 +13,12 @@
{% endblock %}
{% block js %}
<script type="text/javascript">
window.household_members_editor_data = {{ data|json_encode|raw }};
window.household_members_editor_expand_suggestions = {{ expandSuggestions }};
</script>
{{ encore_entry_script_tags('vue_household_members_editor') }}
{{ parent() }}
<script type="text/javascript">
window.household_members_editor_data = {{ data|json_encode|raw }};
window.household_members_editor_expand_suggestions = {{ expandSuggestions }};
</script>
{{ encore_entry_script_tags('vue_household_members_editor') }}
{% endblock %}
{% block css %}

View File

@@ -16,4 +16,5 @@ module.exports = function(encore, entries)
encore.addEntry('page_household_edit_metadata', __dirname + '/Resources/public/page/household_edit_metadata/index.js');
encore.addEntry('page_person', __dirname + '/Resources/public/page/person/index.js');
encore.addEntry('page_accompanying_course_index_person_locate', __dirname + '/Resources/public/page/accompanying_course_index/person_locate.js');
};

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add a link between evaluation document, stored object, and document template
*/
final class Version20210820093927 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add a link between evaluation document, stored object, and document template';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document ADD template_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document ADD storedObject_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document ADD CONSTRAINT FK_33EC92296C99C13A FOREIGN KEY (storedObject_id) REFERENCES chill_doc.stored_object (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document ADD CONSTRAINT FK_33EC92295DA0FB8 FOREIGN KEY (template_id) REFERENCES chill_docgen_template (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_33EC92296C99C13A ON chill_person_accompanying_period_work_evaluation_document (storedObject_id)');
$this->addSql('CREATE INDEX IDX_33EC92295DA0FB8 ON chill_person_accompanying_period_work_evaluation_document (template_id)');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document DROP CONSTRAINT FK_33EC92296C99C13A');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document DROP CONSTRAINT FK_33EC92295DA0FB8');
$this->addSql('DROP INDEX IDX_33EC92296C99C13A');
$this->addSql('DROP INDEX IDX_33EC92295DA0FB8');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document DROP template_id');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document DROP storedObject_id');
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add missing sequence for evaluation documents
*/
final class Version20210820100407 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add missing sequence for evaluation documents';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE SEQUENCE chill_person_social_work_eval_doc_id_seq INCREMENT BY 1 MINVALUE 1000 START 1000');
}
public function down(Schema $schema): void
{
$this->addSql('DROP SEQUENCE chill_person_social_work_eval_doc_id_seq');
}
}

View File

@@ -376,6 +376,12 @@ Edit Accompanying Course: Modifier le parcours
Create Accompanying Course: Créer un nouveau parcours
Drop Accompanying Course: Supprimer le parcours
This course is located at a temporarily address. You should locate this course to an user: Ce parcours est localisé à une adresse temporaire. Il devrait être localisé auprès d'un usager concerné.
Accompanying course location: Localisation du parcours
This course is located by: Ce parcours est localisé auprès de
This course has a temporarily location: Ce parcours a une localisation temporaire
Choose a person to locate by: Localiser auprès d'un usager concerné
Associate at least one member with an household, and set an address to this household: Associez au moins un membre du parcours à un ménage, et indiquez une adresse à ce ménage.
Locate by: Localiser auprès de
# Household
Household: Ménage