Merge remote-tracking branch 'origin/issue466_file_upload' into workflow/fixes-2022-02

This commit is contained in:
Julien Fastré 2022-02-25 15:40:38 +01:00
commit c1f5730c4d
16 changed files with 655 additions and 76 deletions

View File

@ -11,6 +11,10 @@ and this project adheres to
## Unreleased ## Unreleased
<!-- write down unreleased development here --> <!-- write down unreleased development here -->
* [docstore] Add an API entrypoint for StoredObject (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/466)
* [person] Add the possibility of uploading existing documents to AccPeriodWorkEvaluationDocument (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/466)
* [person] Add title to AccPeriodWorkEvaluationDocument (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/466)
## Test releases ## Test releases

View File

@ -16,8 +16,8 @@
<div class="input-group mb-3"> <div class="input-group mb-3">
<select class="form-select" v-model="template"> <select class="form-select" v-model="template">
<option disabled selected value="">{{ $t('choose_a_template') }}</option> <option disabled selected value="">{{ $t('choose_a_template') }}</option>
<template v-for="t in templates"> <template v-for="t in templates" :key="t.id">
<option v-bind:value="t.id">{{ t.name.fr || 'Aucun nom défini' }}</option> <option :value="t.id" >{{ t.name.fr || 'Aucun nom défini' }}</option>
</template> </template>
</select> </select>
<a v-if="canGenerate" class="btn btn-update btn-sm change-icon" :href="buildUrlGenerate" @click.prevent="clickGenerate($event, buildUrlGenerate)"><i class="fa fa-fw fa-cog"></i></a> <a v-if="canGenerate" class="btn btn-update btn-sm change-icon" :href="buildUrlGenerate" @click.prevent="clickGenerate($event, buildUrlGenerate)"><i class="fa fa-fw fa-cog"></i></a>

View File

@ -17,6 +17,7 @@ use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader; use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\HttpKernel\DependencyInjection\Extension;
/** /**
@ -46,6 +47,28 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf
$this->prependRoute($container); $this->prependRoute($container);
$this->prependAuthorization($container); $this->prependAuthorization($container);
$this->prependTwig($container); $this->prependTwig($container);
$this->prependApis($container);
}
protected function prependApis(ContainerBuilder $container)
{
$container->prependExtensionConfig('chill_main', [
'apis' => [
[
'class' => \Chill\DocStoreBundle\Entity\StoredObject::class,
'name' => 'stored_object',
'base_path' => '/api/1.0/docstore/stored-object',
'base_role' => 'ROLE_USER',
'actions' => [
'_entity' => [
'methods' => [
Request::METHOD_POST => true,
],
],
],
],
],
]);
} }
protected function prependAuthorization(ContainerBuilder $container) protected function prependAuthorization(ContainerBuilder $container)

View File

@ -34,19 +34,19 @@ class StoredObject implements AsyncFileInterface, Document
{ {
/** /**
* @ORM\Column(type="datetime", name="creation_date") * @ORM\Column(type="datetime", name="creation_date")
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read", "write"})
*/ */
private DateTimeInterface $creationDate; private DateTimeInterface $creationDate;
/** /**
* @ORM\Column(type="json", name="datas") * @ORM\Column(type="json", name="datas")
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read", "write"})
*/ */
private array $datas = []; private array $datas = [];
/** /**
* @ORM\Column(type="text") * @ORM\Column(type="text")
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read", "write"})
*/ */
private $filename; private $filename;
@ -54,30 +54,32 @@ class StoredObject implements AsyncFileInterface, Document
* @ORM\Id * @ORM\Id
* @ORM\GeneratedValue * @ORM\GeneratedValue
* @ORM\Column(type="integer") * @ORM\Column(type="integer")
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read", "write"})
*/ */
private $id; private $id;
/** /**
* @var int[] * @var int[]
* @ORM\Column(type="json", name="iv") * @ORM\Column(type="json", name="iv")
* @Serializer\Groups({"write"})
*/ */
private array $iv = []; private array $iv = [];
/** /**
* @ORM\Column(type="json", name="key") * @ORM\Column(type="json", name="key")
* @Serializer\Groups({"write"})
*/ */
private array $keyInfos = []; private array $keyInfos = [];
/** /**
* @ORM\Column(type="text", name="type") * @ORM\Column(type="text", name="type")
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read", "write"})
*/ */
private string $type = ''; private string $type = '';
/** /**
* @ORM\Column(type="uuid", unique=true) * @ORM\Column(type="uuid", unique=true)
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read", "write"})
*/ */
private UuidInterface $uuid; private UuidInterface $uuid;

View File

@ -37,7 +37,6 @@ var keyDefinition = {
var searchForZones = function(root) { var searchForZones = function(root) {
var zones = root.querySelectorAll('div[data-stored-object]'); var zones = root.querySelectorAll('div[data-stored-object]');
for(let i=0; i < zones.length; i++) { for(let i=0; i < zones.length; i++) {
initialize(zones[i]); initialize(zones[i]);
} }
@ -370,3 +369,5 @@ window.addEventListener('load', function(e) {
window.addEventListener('collection-add-entry', function(e) { window.addEventListener('collection-add-entry', function(e) {
searchForZones(e.detail.entry); searchForZones(e.detail.entry);
}); });
export { searchForZones };

View File

@ -0,0 +1,156 @@
<template>
<a class="btn btn-create" :title="$t(buttonTitle)" @click="openModal">
<span>{{ $t(buttonTitle) }}</span>
</a>
<teleport to="body">
<div>
<modal v-if="modal.showModal"
:modalDialogClass="modal.modalDialogClass"
@close="modal.showModal = false">
<template v-slot:header>
{{ $t('upload_a_document') }}
</template>
<template v-slot:body>
<div id="dropZoneWrapper" ref="dropZoneWrapper">
<div
data-stored-object="data-stored-object"
:data-label-preparing="$t('data_label_preparing')"
:data-label-quiet-button="$t('data_label_quiet_button')"
:data-label-ready="$t('data_label_ready')"
:data-dict-file-too-big="$t('data_dict_file_too_big')"
:data-dict-default-message="$t('data_dict_default_message')"
:data-dict-remove-file="$t('data_dict_remove_file')"
:data-dict-max-files-exceeded="$t('data_dict_max_files_exceeded')"
:data-dict-cancel-upload="$t('data_dict_cancel_upload')"
:data-dict-cancel-upload-confirm="$t('data_dict_cancel_upload_confirm')"
:data-dict-upload-canceled="$t('data_dict_upload_canceled')"
:data-dict-remove="$t('data_dict_remove')"
:data-allow-remove="!options.required"
data-temp-url-generator="/asyncupload/temp_url/generate/GET">
<input
type="hidden"
data-async-file-upload="data-async-file-upload"
data-generate-temp-url-post="/asyncupload/temp_url/generate/post?expires_delay=180&amp;submit_delay=3600"
data-temp-url-get="/asyncupload/temp_url/generate/GET"
:data-max-files="options.maxFiles"
:data-max-post-size="options.maxPostSize"
:v-model="dataAsyncFileUpload"
>
<input
type="hidden"
data-stored-object-key="1"
>
<input
type="hidden"
data-stored-object-iv="1"
>
<input
type="hidden"
data-async-file-type="1"
>
</div>
</div>
</template>
<template v-slot:footer>
<button class="btn btn-create"
@click.prevent="saveDocument">
{{ $t('action.add')}}
</button>
</template>
</modal>
</div>
</teleport>
</template>
<script>
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
import { searchForZones } from '../../module/async_upload/uploader';
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
const i18n = {
messages: {
fr: {
upload_a_document: "Téléversez un document",
data_label_preparing: "Chargement...",
data_label_quiet_button: "Téléchargez le fichier existant",
data_label_ready: "Prêt à montrer",
data_dict_file_too_big: "Fichier trop volumineux",
data_dict_default_message: "Glissez votre fichier ou cliquez ici",
data_dict_remove_file: "Enlevez votre fichier pour en téléversez un autre",
data_dict_max_files_exceeded: "Nombre maximum de fichiers atteint. Enlevez les fichiers précédents",
data_dict_cancel_upload: "Annulez le téléversement",
data_dict_cancel_upload_confirm: "Êtes-vous sûr·e de vouloir annuler ce téléversement?",
data_dict_upload_canceled: "Téléversement annulé",
data_dict_remove: "Enlevez le fichier existant",
}
}
};
export default {
name: "AddAsyncUpload",
components: {
Modal
},
i18n,
props: [
'buttonTitle',
'options'
],
emits: ['addDocument'],
data() {
return {
modal: {
showModal: false,
modalDialogClass: "modal-dialog-centered modal-md"
},
}
},
updated() {
if (this.modal.showModal){
searchForZones(this.$refs.dropZoneWrapper);
}
},
methods: {
openModal() {
this.modal.showModal = true;
},
saveDocument() {
const dropzone = this.$refs.dropZoneWrapper;
if (dropzone) {
const inputKey = dropzone.querySelector('input[data-stored-object-key]');
const inputIv = dropzone.querySelector('input[data-stored-object-iv]');
const inputObject = dropzone.querySelector('input[data-async-file-upload]');
const inputType = dropzone.querySelector('input[data-async-file-type]');
const url = '/api/1.0/docstore/stored-object.json';
const body = {
filename: inputObject.value,
keyInfos: JSON.parse(inputKey.value),
iv: JSON.parse(inputIv.value),
type: inputType.value,
};
makeFetch('POST', url, body)
.then(r => {
this.$emit("addDocument", r);
this.modal.showModal = false;
})
.catch((error) => {
if (error.name === 'ValidationException') {
for (let v of error.violations) {
this.$toast.open({message: v });
}
} else {
this.$toast.open({message: 'An error occurred'});
}
});
} else {
this.$toast.open({message: 'An error occurred - drop zone not found'});
}
}
}
}
</script>

View File

@ -0,0 +1,51 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\DocStoreBundle\Serializer\Normalizer;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use function array_key_exists;
use function is_array;
class StoredObjectDenormalizer implements DenormalizerInterface
{
private StoredObjectRepository $storedObjectRepository;
public function __construct(StoredObjectRepository $storedObjectRepository)
{
$this->storedObjectRepository = $storedObjectRepository;
}
public function denormalize($data, $type, $format = null, array $context = [])
{
if (array_key_exists(AbstractNormalizer::OBJECT_TO_POPULATE, $context)) {
return $context[AbstractNormalizer::OBJECT_TO_POPULATE];
}
return $this->storedObjectRepository->find($data['id']);
}
public function supportsDenormalization($data, $type, $format = null)
{
if (false === is_array($data)) {
return false;
}
if (false === array_key_exists('id', $data)) {
return false;
}
return StoredObject::class === $type;
}
}

View File

@ -0,0 +1,46 @@
---
openapi: "3.0.0"
info:
version: "1.0.0"
title: "Chill api"
description: "Api documentation for chill. Currently, work in progress"
servers:
- url: "/api"
description: "Your current dev server"
components:
schemas:
StoredObject:
type: object
properties:
id:
type: integer
filename:
type: string
type:
type: string
paths:
/1.0/docstore/stored-object.json:
post:
tags:
- storedobject
summary: Create a stored object
requestBody:
description: "A stored object"
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/StoredObject"
responses:
200:
description: "OK"
content:
application/json:
schema:
$ref: "#/components/schemas/StoredObject"
403:
description: "Unauthorized"
422:
description: "Invalid data"

View File

@ -1,4 +1,7 @@
module.exports = function(encore) module.exports = function(encore)
{ {
encore.addAliases({
ChillDocStoreAssets: __dirname + '/Resources/public'
});
encore.addEntry('mod_async_upload', __dirname + '/Resources/public/module/async_upload/index.js'); encore.addEntry('mod_async_upload', __dirname + '/Resources/public/module/async_upload/index.js');
}; };

View File

@ -33,3 +33,10 @@ services:
resource: './../Workflow/' resource: './../Workflow/'
autoconfigure: true autoconfigure: true
autowire: true autowire: true
Chill\DocStoreBundle\Serializer\Normalizer\:
autowire: true
resource: '../Serializer/Normalizer/'
tags:
- { name: 'serializer.normalizer', priority: 16 }

View File

@ -70,9 +70,10 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
* @ORM\OneToMany( * @ORM\OneToMany(
* targetEntity=AccompanyingPeriodWorkEvaluationDocument::class, * targetEntity=AccompanyingPeriodWorkEvaluationDocument::class,
* mappedBy="accompanyingPeriodWorkEvaluation", * mappedBy="accompanyingPeriodWorkEvaluation",
* cascade={"remove"} * cascade={"remove", "persist"}
* ) * )
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read", "write"})
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
*/ */
private Collection $documents; private Collection $documents;

View File

@ -61,7 +61,7 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
* @ORM\SequenceGenerator(sequenceName="chill_person_social_work_eval_doc_id_seq", allocationSize=1, initialValue=1000) * @ORM\SequenceGenerator(sequenceName="chill_person_social_work_eval_doc_id_seq", allocationSize=1, initialValue=1000)
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read"})
*/ */
private ?int $id; private ?int $id = null;
/** /**
* @ORM\ManyToOne( * @ORM\ManyToOne(
@ -69,6 +69,8 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
* cascade={"remove"}, * cascade={"remove"},
* ) * )
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read"})
* @Serializer\Groups({"write"})
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
*/ */
private ?StoredObject $storedObject = null; private ?StoredObject $storedObject = null;
@ -80,6 +82,14 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
*/ */
private ?DocGeneratorTemplate $template = null; private ?DocGeneratorTemplate $template = null;
/**
* @ORM\Column(type="text", nullable=true)
* @Serializer\Groups({"read"})
* @Serializer\Groups({"write"})
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
*/
private ?string $title = null;
/** /**
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null}) * @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read"})
@ -127,6 +137,11 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
return $this->template; return $this->template;
} }
public function getTitle(): ?string
{
return $this->title;
}
/** /**
* @return DateTimeImmutable|null * @return DateTimeImmutable|null
*/ */
@ -185,6 +200,13 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
return $this; return $this;
} }
public function setTitle(?string $title): AccompanyingPeriodWorkEvaluationDocument
{
$this->title = $title;
return $this;
}
public function setUpdatedAt(DateTimeInterface $datetime): TrackUpdateInterface public function setUpdatedAt(DateTimeInterface $datetime): TrackUpdateInterface
{ {
$this->updatedAt = $datetime; $this->updatedAt = $datetime;

View File

@ -65,14 +65,31 @@
<h5>{{ $t('Documents') }} :</h5> <h5>{{ $t('Documents') }} :</h5>
<div class="flex-table"> <div class="flex-table">
<div class="item-bloc" v-for="d in evaluation.documents"> <div class="item-bloc" v-for="(d, i) in evaluation.documents" :key="i">
<div class="item-row"> <div class="item-row">
<div class="item-col"><h6>{{ d.template.name.fr }}</h6></div> <div class="item-col" style="margin-right: 6px;">
<div class="item-col"> <label class="col-form-label">
<p>Créé par {{ d.createdBy.text }}<br/> {{ $t('document_title') }}
Le {{ $d(ISOToDatetime(d.createdAt.datetime), 'long') }}</p> </label>
<div>
</div> <input
class="form-control form-control-sm"
type="string"
:value=d.title
:id=d.id
@input="onInputDocumentTitle"/>
</div>
<div v-if="d.template">
<label class="col-form-label">
{{ $t('template_title') }}
</label>
<div>{{ d.template.name.fr }}</div>
</div>
</div>
<div class="item-col">
<p v-if="d.createdBy">Créé par {{ d.createdBy.text }}<br/>
Le {{ $d(ISOToDatetime(d.createdAt.datetime), 'long') }}</p>
</div>
</div> </div>
<div class="item-row"> <div class="item-row">
<ul class="record_actions" > <ul class="record_actions" >
@ -81,6 +98,10 @@
<i class="fa fa-edit"></i> <i class="fa fa-edit"></i>
</a> </a>
</li> </li>
<li>
<a class="btn btn-sm btn-delete" @click="removeDocument(d)">
</a>
</li>
</ul> </ul>
</div> </div>
</div> </div>
@ -88,6 +109,7 @@
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<h6>{{ $t('document_add') }} :</h6>
<pick-template <pick-template
entityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation" entityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation"
:id="evaluation.id" :id="evaluation.id"
@ -99,6 +121,20 @@
<label class="col-sm-4 col-form-label">{{ $t('evaluation_generate_a_document') }}</label> <label class="col-sm-4 col-form-label">{{ $t('evaluation_generate_a_document') }}</label>
</template> </template>
</pick-template> </pick-template>
<div>
<label class="col-sm-4 col-form-label">{{ $t('document_upload') }}</label>
<ul class="record_actions">
<li>
<add-async-upload
:buttonTitle="$t('browse')"
:options="asyncUploadOptions"
@addDocument="addDocument"
>
</add-async-upload>
</li>
</ul>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -111,6 +147,7 @@ import ClassicEditor from 'ChillMainAssets/module/ckeditor5/index.js';
import { mapGetters, mapState } from 'vuex'; import { mapGetters, mapState } from 'vuex';
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue'; import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
import {buildLink} from 'ChillDocGeneratorAssets/lib/document-generator'; import {buildLink} from 'ChillDocGeneratorAssets/lib/document-generator';
import AddAsyncUpload from 'ChillDocStoreAssets/vuejs/_components/AddAsyncUpload.vue';
const i18n = { const i18n = {
messages: { messages: {
@ -129,6 +166,11 @@ const i18n = {
evaluation_add_a_document: "Ajouter un document", evaluation_add_a_document: "Ajouter un document",
evaluation_add: "Ajouter une évaluation", evaluation_add: "Ajouter une évaluation",
Documents: "Documents", Documents: "Documents",
document_add: "Générer ou téléverser un document",
document_upload: "Téléverser un document",
document_title: "Titre du document",
template_title: "Nom du template",
browse: "Ajouter un document"
} }
} }
}; };
@ -139,14 +181,23 @@ export default {
components: { components: {
ckeditor: CKEditor.component, ckeditor: CKEditor.component,
PickTemplate, PickTemplate,
AddAsyncUpload
}, },
i18n, i18n,
data() { data() {
return { return {
editor: ClassicEditor, editor: ClassicEditor,
template: null, template: null,
asyncUploadOptions: {
maxFiles: 1,
maxPostSize: 15000000,
required: false,
}
} }
}, },
mounted() {
console.log(this.evaluation)
},
computed: { computed: {
...mapState([ ...mapState([
'isPosting' 'isPosting'
@ -162,7 +213,6 @@ export default {
return dateToISO(this.evaluation.startDate); return dateToISO(this.evaluation.startDate);
}, },
set(v) { set(v) {
console.log(v);
this.$store.commit('setEvaluationStartDate', { key: this.evaluation.key, date: ISOToDate(v) }); this.$store.commit('setEvaluationStartDate', { key: this.evaluation.key, date: ISOToDate(v) });
} }
}, },
@ -205,11 +255,11 @@ export default {
}) })
; ;
}, },
buildEditLink(storedObject) { buildEditLink(storedObject) {
return `/wopi/edit/${storedObject.uuid}?returnPath=` + encodeURIComponent( return `/wopi/edit/${storedObject.uuid}?returnPath=` + encodeURIComponent(
window.location.pathname + window.location.search + window.location.hash); window.location.pathname + window.location.search + window.location.hash);
}, },
submitBeforeGenerate({template}) { submitBeforeGenerate({template}) {
const callback = (data) => { const callback = (data) => {
let evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key).id; let evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key).id;
@ -217,6 +267,21 @@ export default {
}; };
return this.$store.dispatch('submit', callback).catch(e => { console.log(e); throw e; }); return this.$store.dispatch('submit', callback).catch(e => { console.log(e); throw e; });
},
onInputDocumentTitle(event) {
const id = Number(event.target.id);
const title = event.target.value;
this.$store.commit('updateDocumentTitle', {id: id, evaluationKey: this.evaluation.key, title: title});
},
addDocument(storedObject) {
let document = {
type: 'accompanying_period_work_evaluation_document',
storedObject: storedObject
};
this.$store.commit('addDocument', {key: this.evaluation.key, document: document});
},
removeDocument(document) {
this.$store.commit('removeDocument', {key: this.evaluation.key, document: document});
} }
}, },
} }

View File

@ -110,6 +110,7 @@ const store = createStore({
maxDate: e.maxDate !== null ? { datetime: datetimeToISO(e.maxDate) } : null, maxDate: e.maxDate !== null ? { datetime: datetimeToISO(e.maxDate) } : null,
warningInterval: intervalDaysToISO(e.warningInterval), warningInterval: intervalDaysToISO(e.warningInterval),
comment: e.comment, comment: e.comment,
documents: e.documents
}; };
if (e.id !== undefined) { if (e.id !== undefined) {
o.id = e.id; o.id = e.id;
@ -197,6 +198,18 @@ const store = createStore({
found.results = found.results.filter(r => r.id !== result.id); found.results = found.results.filter(r => r.id !== result.id);
}, },
addDocument(state, payload) {
state.evaluationsPicked.find(e => e.key === payload.key).documents.push(payload.document);
},
removeDocument(state, payload) {
let evaluations = state.evaluationsPicked.find(e => e.key === payload.key);
if (evaluations === undefined) {
return;
}
evaluations.documents = evaluations.documents.filter(d => d.id !== payload.document.id);
},
addEvaluation(state, evaluation) { addEvaluation(state, evaluation) {
let e = { let e = {
type: "accompanying_period_work_evaluation", type: "accompanying_period_work_evaluation",
@ -284,6 +297,10 @@ const store = createStore({
setIsPosting(state, st) { setIsPosting(state, st) {
state.isPosting = st; state.isPosting = st;
}, },
updateDocumentTitle(state, payload) {
state.evaluationsPicked.find(e => e.key === payload.evaluationKey)
.documents.find(d => d.id === payload.id).title = payload.title;
}
}, },
actions: { actions: {
updateThirdParty({ commit }, payload) { updateThirdParty({ commit }, payload) {
@ -374,13 +391,18 @@ const store = createStore({
}); });
} }
}, },
addDocument({commit}, payload) {
commit('addDocument', payload);
},
removeDocument({commit}, payload) {
commit('removeDocument', payload);
},
submit({ getters, state, commit }, callback) { submit({ getters, state, commit }, callback) {
let let
payload = getters.buildPayload, payload = getters.buildPayload,
url = `/api/1.0/person/accompanying-course/work/${state.work.id}.json`, url = `/api/1.0/person/accompanying-course/work/${state.work.id}.json`,
errors = [] errors = []
; ;
commit('setIsPosting', true); commit('setIsPosting', true);
return makeFetch('PUT', url, payload) return makeFetch('PUT', url, payload)
@ -397,6 +419,9 @@ const store = createStore({
commit('setErrors', error.violations); commit('setErrors', error.violations);
}); });
}, },
updateDocumentTitle({commit}, payload) {
commit('updateDocumentTitle', payload)
}
} }
}); });

View File

@ -0,0 +1,137 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\PersonBundle\Serializer\Normalizer;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\ObjectToPopulateTrait;
use function array_key_exists;
use function array_merge;
use function in_array;
use function is_array;
/**
* This denormalizer rely on AbstractNormalizer for most of the job, and
* add some logic for synchronizing collection.
*/
class AccompanyingPeriodWorkEvaluationDenormalizer implements ContextAwareDenormalizerInterface, DenormalizerAwareInterface
{
use DenormalizerAwareTrait;
use ObjectToPopulateTrait;
private EntityManagerInterface $em;
private AccompanyingPeriodWorkRepository $workRepository;
public function __construct(
AccompanyingPeriodWorkRepository $workRepository,
EntityManagerInterface $em
) {
$this->workRepository = $workRepository;
$this->em = $em;
}
public function denormalize($data, $type, $format = null, array $context = [])
{
$evaluation = $this->denormalizer->denormalize($data, $type, $format, array_merge(
$context,
['skip' => self::class]
));
//if (in_array('accompanying_period_work:edit', $context['groups'] ?? [], true)) {
$this->handleEvaluationCollection($data, $evaluation, $format, $context);
//}
return $evaluation;
}
public function supportsDenormalization($data, $type, $format = null, array $context = []): bool
{
return AccompanyingPeriodWorkEvaluation::class === $type
&& self::class !== ($context['skip'] ?? null)
&& is_array($data)
&& array_key_exists('type', $data)
&& 'accompanying_period_work_evaluation' === $data['type'];
}
private function handleEvaluationCollection(array $data, AccompanyingPeriodWorkEvaluation $evaluation, string $format, array $context)
{
$dataById = [];
$dataWithoutId = [];
foreach ($data['documents'] as $e) {
if (array_key_exists('id', $e)) {
$dataById[$e['id']] = $e;
} else {
$dataWithoutId[] = $e;
}
}
dump($dataById);
dump($dataWithoutId);
dump($evaluation);
//partition the separate kept documents and removed one
[$kept, $removed] = $evaluation->getDocuments()
->partition(
static fn (int $key, AccompanyingPeriodWorkEvaluationDocument $a) => array_key_exists($a->getId(), $dataById)
);
//$kept = $evaluation->getDocuments();
dump($kept);
dump($removed);
// remove the document from evaluation
foreach ($removed as $r) {
dump($r);
$evaluation->removeDocument($r);
}
// handle the documents kept
foreach ($kept as $k) {
dump($k); // Cannot iterate over $kept which is a PersistentCollection
$evaluation->removeDocument($k);
dump($evaluation);
$document = $this->denormalizer->denormalize(
$dataById[$k->getId()],
AccompanyingPeriodWorkEvaluationDocument::class,
$format,
array_merge(
$context,
[
'groups' => ['write'],
AbstractNormalizer::OBJECT_TO_POPULATE => $k,
]
)
);
$evaluation->addDocument($document);
}
// create new document
foreach ($dataWithoutId as $newData) {
dump($newData);
$document = $this->denormalizer->denormalize(
$newData,
AccompanyingPeriodWorkEvaluationDocument::class,
$format,
array_merge(
$context,
['groups' => ['accompanying_period_work_evaluation:create']]
)
);
$evaluation->addDocument($document);
}
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add title to AccompanyingPeriodWorkEvaluationDocument.
*/
final class Version20220224145951 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document DROP title');
}
public function getDescription(): string
{
return 'Add title to AccompanyingPeriodWorkEvaluationDocument';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document ADD title TEXT DEFAULT NULL');
}
}