Merge branch 'issue511_document_evaluations' into 'master'

AccompanyingCourseWorkEdit: replace document by a new one, fix deleting

See merge request Chill-Projet/chill-bundles!384
This commit is contained in:
Julien Fastré 2022-03-21 15:17:34 +00:00
commit 0a0243eb85
15 changed files with 157 additions and 39 deletions

View File

@ -11,6 +11,9 @@ and this project adheres to
## Unreleased
<!-- write down unreleased development here -->
* [person] AccompanyingCourseWorkEdit: fix deleting evaluation documents (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/546)
* [person] AccompanyingCourseWorkEdit: download existing documents (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/512)
* [person] AccompanyingCourseWorkEdit: replace document by a new one (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/511)
* [person] AccompanyingPeriodWork: add referrers to work, add doctrine event listener to add logged user to referrers collection and display a referrers list in work list (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/502)
* [person] AccompanyingPeriodWorkEvaluation: fix circular reference when serialising (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/495)
* [person] order accompanying period by opening date in search persons, person and household period lists (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/493)

View File

@ -61,13 +61,13 @@ class StoredObject implements AsyncFileInterface, Document
/**
* @var int[]
* @ORM\Column(type="json", name="iv")
* @Serializer\Groups({"write"})
* @Serializer\Groups({"read", "write"})
*/
private array $iv = [];
/**
* @ORM\Column(type="json", name="key")
* @Serializer\Groups({"write"})
* @Serializer\Groups({"read", "write"})
*/
private array $keyInfos = [];

View File

@ -73,6 +73,7 @@ var download = (button) => {
button.type = mimeType;
button.textContent = labelReady;
if (hasFilename) {
button.download = filename;
if (extension !== false) {
button.download = button.download + '.' + extension;
@ -92,4 +93,4 @@ window.addEventListener('load', function(e) {
initializeButtons(e.target);
});
module.exports = initializeButtons;
export { initializeButtons, download };

View File

@ -1,6 +1,6 @@
var algo = 'AES-CBC';
import Dropzone from 'dropzone';
var initializeDownload = require('./downloader.js');
import { initializeButtons } from './downloader.js';
/**
@ -359,7 +359,7 @@ var insertDownloadButton = (zone, zoneData) => {
addBelowButton(newButton, zone, zoneData);
//zone.appendChild(newButton);
initializeDownload(zone);
initializeButtons(zone);
};
window.addEventListener('load', function(e) {

View File

@ -1,5 +1,5 @@
<template>
<a class="btn btn-create" :title="$t(buttonTitle)" @click="openModal">
<a :class="btnClasses" :title="$t(buttonTitle)" @click="openModal">
<span>{{ $t(buttonTitle) }}</span>
</a>
<teleport to="body">
@ -96,10 +96,27 @@ export default {
Modal
},
i18n,
props: [
'buttonTitle',
'options'
],
props: {
buttonTitle: {
type: String,
default: 'Ajouter un document',
},
options: {
type: Object,
default: {
maxFiles: 1,
maxPostSize: 262144000, // 250MB
required: false,
}
},
btnClasses: {
type: Object,
default: {
btn: true,
'btn-create': true
}
}
},
emits: ['addDocument'],
data() {
return {

View File

@ -0,0 +1,45 @@
<template>
<a
class="btn btn-download"
:title="$t(buttonTitle)"
:data-key=JSON.stringify(storedObject.keyInfos)
:data-iv=JSON.stringify(storedObject.iv)
:data-mime-type=storedObject.type
:data-label-preparing="$t('dataLabelPreparing')"
:data-label-ready="$t('dataLabelReady')"
:data-temp-url-get-generator="url"
@click.once="downloadDocument">
</a>
</template>
<script>
import { download } from '../../module/async_upload/downloader';
const i18n = {
messages: {
fr: {
dataLabelPreparing: "Chargement...",
dataLabelReady: "",
}
}
};
export default {
name: "AddAsyncUploadDownloader",
i18n,
props: [
'buttonTitle',
'storedObject'
],
computed: {
url() {
return `/asyncupload/temp_url/generate/GET?object_name=${this.storedObject.filename}`;
}
},
methods: {
downloadDocument(e) {
download(e.target);
}
}
}
</script>

View File

@ -100,10 +100,8 @@ final class UserControllerTest extends WebTestCase
$crawler = $this->client->followRedirect();
// Check data in the show view
$this->assertGreaterThan(
0,
$crawler->filter('td:contains("Test_user")')->count(),
'Missing element td:contains("Test user")'
$this->assertStringContainsString(
"Test_user", $crawler->text(), "page contains the name of the user"
);
//test the auth of the new client
@ -125,11 +123,7 @@ final class UserControllerTest extends WebTestCase
$this->client->submit($form);
$crawler = $this->client->followRedirect();
// Check the element contains an attribute with value equals "Foo"
$this->assertGreaterThan(
0,
$crawler->filter('[data-username="' . $username . '"]')->count(),
'Missing element [data-username="Foo bar"]'
);
$this->assertResponseIsSuccessful();
}
/**

View File

@ -15,7 +15,6 @@ use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Templating\Entity\UserRender;
use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepository;
use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepositoryInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

View File

@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\EventListener;
use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Symfony\Component\Security\Core\Security;
@ -25,6 +26,8 @@ class AccompanyingPeriodWorkEventListener
public function prePersistAccompanyingPeriodWork(AccompanyingPeriodWork $work): void
{
$work->addReferrer($this->security->getUser());
if ($this->security->getUser() instanceof User) {
$work->addReferrer($this->security->getUser());
}
}
}

View File

@ -16,8 +16,9 @@ use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Symfony\Component\Security\Core\Security;
use DateTime;
use Symfony\Component\Security\Core\Security;
use function count;
final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodACLAwareRepositoryInterface
@ -55,7 +56,7 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC
)
)
->setParameter('user', $user)
->setParameter('now', new \DateTime('now'))
->setParameter('now', new DateTime('now'))
->setParameter('draft', AccompanyingPeriod::STEP_DRAFT);
return $qb;
@ -140,7 +141,7 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC
->setMaxResults($limit);
foreach ($orderBy as $field => $direction) {
$qb->addOrderBy('ap.'.$field, $direction);
$qb->addOrderBy('ap.' . $field, $direction);
}
return $qb->getQuery()->getResult();

View File

@ -16,6 +16,8 @@ use Chill\PersonBundle\Entity\Person;
interface AccompanyingPeriodACLAwareRepositoryInterface
{
public function countByUserOpenedAccompanyingPeriod(?User $user): int;
public function findByPerson(
Person $person,
string $role,
@ -25,6 +27,4 @@ interface AccompanyingPeriodACLAwareRepositoryInterface
): array;
public function findByUserOpenedAccompanyingPeriod(?User $user, array $orderBy = [], int $limit = 0, int $offset = 50): array;
public function countByUserOpenedAccompanyingPeriod(?User $user): int;
}

View File

@ -127,7 +127,7 @@
</div>
<ul class="record_actions" v-if="evaluationsForAction.length > 0">
<li>
<button :title="$t('add_an_evaluation')" class="btn btn-create" @click="toggleAddEvaluation"></button>
<button :title="$t('add_an_evaluation')" class="btn btn-create" @click="toggleAddEvaluation">{{ $t('add_an_evaluation') }}</button>
</li>
</ul>
<div v-else>

View File

@ -27,7 +27,7 @@
</li>
<li v-if="canDelete">
<a class="btn btn-delete" @click="modal.showModal = true" :title="$t('action.delete')"></a>
<a class="btn btn-delete" @click="modal.showModal = true" :title="$t('action.delete')">{{ $t('delete_evaluation')}}</a>
</li>
</ul>
</div>
@ -72,7 +72,8 @@ const i18n = {
sure: "Êtes-vous sûr?",
sure_description: "Cette évaluation sera supprimée de cette action d'accompagnement",
ok: "Supprimer"
}
},
delete_evaluation: "Supprimer l'évaluation",
}
}
};

View File

@ -65,7 +65,7 @@
<h5>{{ $t('Documents') }} :</h5>
<div class="flex-table">
<div class="item-bloc" v-for="(d, i) in evaluation.documents" :key="d.key">
<div class="item-bloc" v-for="(d, i) in evaluation.documents" :key="d.id">
<div class="item-row">
<div class="input-group input-group-lg mb-3">
<div>
@ -75,6 +75,7 @@
type="text"
:value="d.title"
:id="d.id"
:data-key="i"
@input="onInputDocumentTitle"/>
</div>
</div>
@ -84,6 +85,8 @@
<p v-if="d.createdBy" class="createdBy">Créé par {{ d.createdBy.text }}<br/>
Le {{ $d(ISOToDatetime(d.createdAt.datetime), 'long') }}</p>
</div>
</div>
<div class="item-row">
<div class="item-col">
<ul class="record_actions" >
<li v-if="d.workflows_availables.length > 0">
@ -98,6 +101,23 @@
@go-to-generate-workflow="goToGenerateWorkflowEvaluationDocument"
></list-workflow-modal>
</li>
<li>
<add-async-upload
:buttonTitle="$t('replace')"
:options="asyncUploadOptions"
:btnClasses="{'btn': true, 'btn-edit': true}"
@addDocument="(arg) => replaceDocument(d, arg)"
>
</add-async-upload>
</li>
<li>
<add-async-upload-downloader
:buttonTitle="$t('download')"
:storedObject="d.storedObject"
>
</add-async-upload-downloader>
</li>
<li>
<a :href="buildEditLink(d.storedObject)" class="btn btn-wopilink"></a>
</li>
@ -152,6 +172,7 @@ import { mapGetters, mapState } from 'vuex';
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
import {buildLink} from 'ChillDocGeneratorAssets/lib/document-generator';
import AddAsyncUpload from 'ChillDocStoreAssets/vuejs/_components/AddAsyncUpload.vue';
import AddAsyncUploadDownloader from 'ChillDocStoreAssets/vuejs/_components/AddAsyncUploadDownloader.vue';
import ListWorkflowModal from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue';
import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api.js';
@ -176,7 +197,9 @@ const i18n = {
document_upload: "Téléverser un document",
document_title: "Titre du document",
template_title: "Nom du template",
browse: "Ajouter un document"
browse: "Ajouter un document",
replace: "Remplacer",
download: "Télécharger le fichier existant"
}
}
};
@ -188,6 +211,7 @@ export default {
ckeditor: CKEditor.component,
PickTemplate,
AddAsyncUpload,
AddAsyncUploadDownloader,
ListWorkflowModal,
},
i18n,
@ -274,8 +298,9 @@ export default {
},
onInputDocumentTitle(event) {
const id = Number(event.target.id);
const key = Number(event.target.dataset.key) + 1;
const title = event.target.value;
this.$store.commit('updateDocumentTitle', {id: id, evaluationKey: this.evaluation.key, title: title});
this.$store.commit('updateDocumentTitle', {id: id, key: key, evaluationKey: this.evaluation.key, title: title});
},
addDocument(storedObject) {
let document = {
@ -285,6 +310,14 @@ export default {
};
this.$store.commit('addDocument', {key: this.evaluation.key, document: document});
},
replaceDocument(oldDocument, storedObject) {
let document = {
type: 'accompanying_period_work_evaluation_document',
storedObject: storedObject,
title: oldDocument.title
};
this.$store.commit('replaceDocument', {key: this.evaluation.key, document: document, oldDocument: oldDocument});
},
removeDocument(document) {
if (window.confirm("Êtes-vous sûr·e de vouloir supprimer le document qui a pour titre \"" + document.title +"\" ?")) {
this.$store.commit('removeDocument', {key: this.evaluation.key, document: document});

View File

@ -136,9 +136,9 @@ const store = createStore({
endDate: e.endDate !== null ? ISOToDatetime(e.endDate.datetime) : null,
maxDate: e.maxDate !== null ? ISOToDatetime(e.maxDate.datetime) : null,
warningInterval: e.warningInterval !== null ? intervalISOToDays(e.warningInterval) : null,
documents: e.documents.map((d, dindex) => {
documents: e.documents.map((d, docIndex) => {
return Object.assign(d, {
key: index
key: docIndex
});
}),
});
@ -218,13 +218,26 @@ const store = createStore({
}));
},
removeDocument(state, {key, document}) {
let evaluations = state.evaluationsPicked.find(e => e.key === key);
if (evaluations === undefined) {
let evaluation = state.evaluationsPicked.find(e => e.key === key);
if (evaluation === undefined) {
return;
}
evaluation.documents = evaluation.documents.filter(d => d.key !== document.key);
},
replaceDocument(state, payload) {
let evaluation = state.evaluationsPicked.find(e => e.key === payload.key);
if (evaluation === undefined) {
return;
}
evaluations.documents = evaluations.documents.filter(d => d.key !== document.key);
let newDocument = Object.assign(
payload.document, {
key: evaluation.documents.length + 1,
workflows_availables: state.work.workflows_availables_evaluation_documents,
workflows: [],
}
);
evaluation.documents = evaluation.documents.map(d => d.id === payload.oldDocument.id ? newDocument : d);
},
addEvaluation(state, evaluation) {
let e = {
@ -326,8 +339,13 @@ const store = createStore({
state.isPosting = st;
},
updateDocumentTitle(state, payload) {
state.evaluationsPicked.find(e => e.key === payload.evaluationKey)
if (payload.id === 0) {
state.evaluationsPicked.find(e => e.key === payload.evaluationKey)
.documents.find(d => d.key === payload.key).title = payload.title;
} else {
state.evaluationsPicked.find(e => e.key === payload.evaluationKey)
.documents.find(d => d.id === payload.id).title = payload.title;
}
}
},
actions: {
@ -424,6 +442,9 @@ const store = createStore({
removeDocument({commit}, payload) {
commit('removeDocument', payload);
},
replaceDocument({commit}, payload) {
commit('replaceDocument', payload);
},
submit({ getters, state, commit }, callback) {
let
payload = getters.buildPayload,