Commit 3d921b28 authored by Mat's avatar Mat
Browse files

Template de base pour l'entité Lettre

parent 684d7d4c
......@@ -3,6 +3,7 @@
namespace APIBundle\Controller;
use APIBundle\Form\LettreAPIType;
use APIBundle\Form\LettreStatusAPIType;
use PotageBundle\Entity\Lettre;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
......@@ -10,13 +11,42 @@ use Symfony\Component\HttpFoundation\Response;
class LettreAPIController extends MasterAPIController
{
/**
* @return string
* @throws \Doctrine\ORM\NonUniqueResultException
*/
private function newReference()
{
$now = new \DateTime();
$today = $now->format('ymd');
$em = $this->getDoctrine()->getManager();
$num = $em->getRepository('PotageBundle:Lettre')->findOneLastReference($today);
if ($num !== null)
{
$last = intval(substr($num['reference'], -2));
$last++;
$last = sprintf("%02d", $last);
}
else {
$last = '01';
}
return 'LT' . $today . $last;
}
/**
* @param Request $request
* @return \Symfony\Component\HttpFoundation\JsonResponse
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function createAction(Request $request)
{
$lettre = new Lettre();
$reference = $this->newReference();
$lettre = new Lettre($reference);
$form = $this->createForm(LettreAPIType::class, $lettre);
$form->handleRequest($request);
......@@ -34,12 +64,13 @@ class LettreAPIController extends MasterAPIController
/**
* @param $status
* @return \Symfony\Component\HttpFoundation\JsonResponse
*/
public function readAction()
public function readByStatusAction($status)
{
$em = $this->getDoctrine()->getManager();
$lettres = $em->getRepository('PotageBundle:Lettre')->findAllForAPIRead();
$lettres = $em->getRepository('PotageBundle:Lettre')->findAllByStatusForRead($status);
return $this->api($lettres);
}
......@@ -87,6 +118,33 @@ class LettreAPIController extends MasterAPIController
}
/**
* @param Request $request
* @param $id
* @return \Symfony\Component\HttpFoundation\JsonResponse
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function updateStatusAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$lettre = $em->getRepository('PotageBundle:Lettre')->findOneForAPIUpdate($id);
$form = $this->createForm(LettreStatusAPIType::class, $lettre);
$form->handleRequest($request);
if ($lettre === null) {
return $this->api('Not found', Response::HTTP_NOT_FOUND);
}
if ($form->isSubmitted() && $form->isValid())
{
$em->flush();
return $this->api($lettre);
}
return $this->api($form, Response::HTTP_BAD_REQUEST);
}
/**
* @param $id
* @return \Symfony\Component\HttpFoundation\JsonResponse
......
<?php
namespace APIBundle\Form;
use PotageBundle\Form\Lettre\LettreStatusType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class LettreStatusAPIType extends LettreStatusType
{
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver->setDefault('csrf_protection', false);
// TODO temporaire, le temps de régler les validations au niveau des Asserts et des FormType
$resolver->setDefault('attr', array('novalidate' => true));
}
}
......@@ -150,10 +150,12 @@ api_offre_legumes_delete:
## CRUD LettreAPI
api_lettre_read:
path: /lettres
api_lettre_read_by_status:
path: /lettres/{status}
requirements:
status: \w+
defaults:
_controller: APIBundle:LettreAPI:read
_controller: APIBundle:LettreAPI:readByStatus
methods: [ GET ]
api_lettre_create:
......@@ -178,6 +180,14 @@ api_lettre_update:
_controller: APIBundle:LettreAPI:update
methods: [ POST ]
api_lettre_update_status:
path: /lettre/{id}/status
requirements:
id: \d+
defaults:
_controller: APIBundle:LettreAPI:updateStatus
methods: [ POST ]
api_lettre_delete:
path: /lettre/{id}
requirements:
......
......@@ -90,13 +90,15 @@ class Lettre
private $updatedAt;
/**
* Lettre constructor.
*
* @param $reference
*/
public function __construct()
public function __construct($reference)
{
$this->infos = new ArrayCollection();
$this->reference = $reference;
}
/**
......@@ -353,14 +355,15 @@ class Lettre
return $this->infos;
}
/**
* Set groupe
*
* @param integer $groupe
* @param \PotageBundle\Entity\Groupe $groupe
*
* @return Lettre
*/
public function setGroupe($groupe)
public function setGroupe(\PotageBundle\Entity\Groupe $groupe)
{
$this->groupe = $groupe;
......@@ -370,7 +373,7 @@ class Lettre
/**
* Get groupe
*
* @return integer
* @return \PotageBundle\Entity\Groupe
*/
public function getGroupe()
{
......
<?php
namespace PotageBundle\Form\Lettre;
use PotageBundle\Entity\Lettre;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class LettreStatusType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('status', ChoiceType::class, array(
'label' => 'Statut',
'choices' => array(
'En préparation' => 'draft',
'Envoyer' => 'current',
),
'label_attr' => array('class' => 'col-form-label'),
'attr' => array(
'class' => 'form-control form-control-sm',
),
));
$builder->add('sauver', SubmitType::class, array(
'label' => 'Changer',
'attr' => array('class' => 'btn btn-dark mb-2')
));
}
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver->setDefault('data_class', Lettre::class);
$resolver->setDefault('attr', array(
'class' => 'formulaire formulaire_offre_status',
));
}
/**
* @return null|string
*/
public function getBlockPrefix()
{
return 'offre_status';
}
}
......@@ -51,16 +51,20 @@ class LettreType extends AbstractType
);
// infos
/*
$builder->add('infos', EntityType::class, array(
'class' => Info::class,
'label' => "Dans laquelle on glisse des infos",
'label_attr' => array('class' => 'col-form-label'),
'attr' => array('class' => 'form-control form-control-sm'),
));
*/
// groupe
$builder->add('groupe', EntityType::class, array(
'class' => Groupe::class,
'choice_label' => 'getNom',
'placeholder' => '',
'label' => "À destination d'un groupe",
'label_attr' => array('class' => 'col-form-label'),
'attr' => array('class' => 'form-control form-control-sm'),
......
......@@ -12,12 +12,32 @@ class LettreRepository extends \Doctrine\ORM\EntityRepository
{
/**
* @return array Lettre
* @param $pattern
* @return mixed
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function findOneLastReference($pattern)
{
$qb = $this->createQueryBuilder('l')
->select('l.reference')
->where('l.reference LIKE :pattern')
->setParameter(':pattern', '%' . $pattern . '%')
->orderBy('l.reference', 'DESC')
->setMaxResults(1);
return $qb->getQuery()->getOneOrNullResult();
}
/**
* @param $status
* @return array
*/
public function findAllForAPIRead()
public function findAllByStatusForRead($status)
{
$qb = $this->createQueryBuilder('l')
->orderBy('l.id', 'DESC');
->where('l.status = :status')
->setParameter(':status', $status)
->orderBy('l.status')
->addOrderBy('l.reference', 'DESC');
return $qb->getQuery()->getResult();
}
......
......@@ -8,6 +8,7 @@
<nav class="navbar navbar-expand-lg navbar-light bg-light navbar-search">
<a href="{{ path('potage_offre_ajax_display') }}" class="nav-laterale left text-secondary"><i class="fas fa-long-arrow-alt-left fa-fw"></i> Les offres</a>
<a href="{{ path('potage_groupe_ajax_display') }}" class="nav-laterale right text-secondary">Les groupes <i class="fas fa-long-arrow-alt-right fa-fw"></i></a>
</nav>
{% endblock %}
......@@ -79,41 +80,41 @@
<div class="col right hd-1">
<div style="margin-top: 0.65em;">
{#
#}
<a href="javascript: void(0);" class="btn btn-outline-dark mb-2 btn-sm btn-reload">
<i class="fas fa-sync-alt"></i>
Rafraîchir la liste
</a>
#}
</div>
</div>
</div>
<div class="tab-content" id="v-pills-tabContent">
<div class="tab-pane fade show active" id="v-pills-actuel" role="tabpanel" aria-labelledby="v-pills-actuel-tab">
<h2><i class="fas fa-angle-down fa-fw"></i> En préparation</h2>
<table id="maintab-draft" class="table offres table-striped">
<table id="maintab-draft" class="table lettres table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Offre</th>
<th scope="col">Contenu</th>
<th scope="col">Créée le</th>
<th scope="col">Date de clôture</th>
<th scope="col">Référence</th>
<th scope="col">Infolettre</th>
<th scope="col">Groupe</th>
<th scope="col">Sujet</th>
<th scope="col">Pièce jointe</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<h2><i class="fas fa-angle-down fa-fw"></i> En cours</h2>
<table id="maintab-current" class="table offres table-striped">
<h2><i class="fas fa-angle-down fa-fw"></i> En cours d'envoi</h2>
<table id="maintab-current" class="table lettres table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Offre</th>
<th scope="col">Contenu</th>
<th scope="col">Créée le</th>
<th scope="col">Date de clôture</th>
<th scope="col">Référence</th>
<th scope="col">Infolettre</th>
<th scope="col">Groupe</th>
<th scope="col">Sujet</th>
<th scope="col">Pièce jointe</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
......@@ -121,16 +122,16 @@
</table>
</div>
<div class="tab-pane fade" id="v-pills-archives" role="tabpanel" aria-labelledby="v-pills-archives-tab">
<h2><i class="fas fa-angle-down fa-fw"></i> Archives</h2>
<table id="maintab-closed" class="table offres table-striped">
<h2><i class="fas fa-angle-down fa-fw"></i> Envoyées</h2>
<table id="maintab-closed" class="table lettres table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Offre</th>
<th scope="col">Contenu</th>
<th scope="col">Créée le</th>
<th scope="col">Date de clôture</th>
<th scope="col">Référence</th>
<th scope="col">Infolettre</th>
<th scope="col">Groupe</th>
<th scope="col">Sujet</th>
<th scope="col">Pièce jointe</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
......@@ -147,5 +148,273 @@
<script type="text/javascript" src="{{ asset('js/ajax_functions.js') }}"></script>
<script type="text/javascript">
/*
* Recharge la table Lettre
* chaque table affiche les lettres pour un statut donné
* @param string status
*/
function reloadTableLettre(status)
{
let table = document.querySelector('table#maintab-' + status + '.lettres');
let tbody = table.querySelector('tbody');
tbody.innerHTML = null;
let lettreURL = Routing.generate('api_lettre_read_by_status', {'status': status});
AJAX('GET', lettreURL, function(request)
{
let json = JSON.parse(request.responseText);
for(let i = 0; i < json.length; i++ )
{
let tr = buildRowLettre(json[i]);
tbody.appendChild(tr);
}
});
}
/*
* Construit une rangée du tableau des groupes
*/
function buildRowLettre(jsonRow)
{
console.log(jsonRow);
let tr = document.createElement('tr');
let td1 = document.createElement('td');
td1.textContent = jsonRow.id;
tr.appendChild(td1);
let td7 = document.createElement('td');
let listStatus = { 'draft': 'En préparation', 'current': 'Publiée', 'closed': 'Terminée' };
for (let statut in listStatus)
{
if (statut === jsonRow.status)
{
if (jsonRow.status === 'draft' && typeof jsonRow.offre !== 'undefined')
{
td7.appendChild(changeStatus(jsonRow));
}
else
{
let couleurs = {'draft': 'warning', 'current': 'success', 'closed': 'muted'};
let fa = buildColorSquare(jsonRow.status, couleurs);
td7.innerHTML = fa + listStatus[statut];
}
}
}
tr.appendChild(td7);
let td4 = document.createElement('td');
td4.innerHTML = jsonRow.groupe.nom
+ '<br><span class="details"><i class="fas fa-user fa-fw"></i> 153</span>';
tr.appendChild(td4);
let td2 = document.createElement('td');
td2.innerHTML = jsonRow.subject
+ '<br>' + formatStringDate(jsonRow.started_at) + '' + formatStringDate(jsonRow.ended_at)
+ '<br>' + jsonRow.reference;
tr.appendChild(td2);
let td3 = document.createElement('td');
td3.innerHTML = '<i class="fas fa-paperclip fa-fw"></i> ' + jsonRow.offre.reference;
tr.appendChild(td3);
let td6 = document.createElement('td');
td6.appendChild(displayEditLettre(jsonRow));
td6.appendChild(displayDeleteLettre(jsonRow));
tr.appendChild(td6);
return tr;
}
/*
* Construit et affiche le formulaire d'édition
*/
function buildEditStatusForm()
{
let editForm = document.createElement('form');
editForm.setAttribute('name', 'offre_status');
editForm.setAttribute('method', 'post');
editForm.setAttribute('novalidate', '1');
let divForm = document.createElement('div');
divForm.setAttribute('id', 'offre_status');
divForm.setAttribute('novalidate', 'novalidate');
editForm.appendChild(divForm);
let selectForm = document.createElement('select');
selectForm.setAttribute('name', 'lettre_status[status]');
selectForm.classList.add('form-control', 'form-control-sm', 'offre_status', 'bg-warning');
divForm.appendChild(selectForm);
let optionValue = {'draft': 'En préparation', 'current': 'Envoyer'};
for (let value in optionValue)
{
let optionForm = document.createElement('option');
optionForm.setAttribute('value', value);
optionForm.innerHTML = optionValue[value];
optionForm.classList.add(value);
selectForm.appendChild(optionForm);
}
return editForm;
}
/*
* Changement de statut pour une rangée
* @param json object jsonRow
*/
function changeStatus(jsonRow)
{
let editForm = buildEditStatusForm();
editForm.addEventListener('change', function()
{
sleep(500).then(() => {
let ask = confirm(
"Attention, une fois la lettre envoyée,\n" +
"vous ne pourrez forcément plus revenir en arrière !\n" +
"Êtes-vous sûr de vouloir changer son statut ?"
);
if (ask === true)
{
AJAX('POST',
Routing.generate('api_lettre_update_status', {'id': jsonRow.id }),
function() {
reloadTables();
},
editForm
);
}
else {
reloadTableLettre('draft');
}
});
});
return editForm;
}
/*
* Affiche un bouton pour éditer une rangée
* @param json object jsonRow
*/
function displayEditLettre(jsonRow)
{
let editBtn = buildEditBtn();
editBtn.dataset.target = ".form-lettre-modal";
editBtn.dataset.lettreId = jsonRow.id;
editBtn.addEventListener('click', function()
{
let lettreId = this.dataset.lettreId;
submitURL = Routing.generate('api_lettre_update', {'id': lettreId});
AJAX('GET', submitURL, function(request) {
let json = JSON.parse(request.responseText);
titleForm.textContent = 'Lettre # ' + lettreId;
form.querySelector('#lettre_groupe').value = json.groupe;
form.querySelector('#lettre_subject').value = json.subject;
form.querySelector('#lettre_offre').value = json.offre;
form.querySelector('#lettre_started_at').value = json.started_at;
form.querySelector('#lettre_ended_at').value = json.ended_at;
form.querySelector('#lettre_status').value = json.status;
});
});
return editBtn;
}
/*
* Supprimer une lettre
* @param json object jsonRow
*/
function displayDeleteLettre(jsonRow)
{
let deleteBtn = buildDeleteBtn();
deleteBtn.dataset.lettreId = jsonRow.id;
deleteBtn.addEventListener('click', function()
{