add status update with mail

This commit is contained in:
Simon Vieille 2023-04-10 16:47:59 +02:00
parent ffca1b4dc4
commit 732f2dab51
Signed by: deblan
GPG key ID: 579388D585F70417
12 changed files with 275 additions and 8 deletions

View file

@ -47,6 +47,7 @@ security:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/2fa, role: IS_AUTHENTICATED_2FA_IN_PROGRESS }
- { path: ^/admin/bill, roles: [ROLE_MANAGER, ROLE_TREASURER] }
- { path: ^/admin/user, roles: ROLE_ADMIN }
- { path: ^/admin/task, roles: ROLE_ADMIN }
- { path: ^/admin/setting, roles: ROLE_ADMIN }

View file

@ -23,6 +23,8 @@ use App\Repository\BillVendorRepositoryQuery;
use App\Factory\BillVendorFactory;
use App\Repository\BillCategoryRepositoryQuery;
use App\Factory\BillCategoryFactory;
use App\Event\EntityManagerEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class BillAdminController extends CrudController
{
@ -119,6 +121,27 @@ class BillAdminController extends CrudController
);
}
#[Route(path: "/admin/bill/update_status/{entity}/{status}", name: "admin_bill_update_status", methods: ['GET'])]
public function updateStatus(Entity $entity, int $status, EntityManager $entityManager, Request $request, EventDispatcherInterface $eventDispatcher): Response
{
if (!$this->isCsrfTokenValid('update_status', $request->query->get('_token'))) {
throw $this->createAccessDeniedException();
}
$entity->setStatus($status);
$entityManager->update($entity);
$eventDispatcher->dispatch(new EntityManagerEvent($entity, [
'user' => $this->getUser(),
]), 'bill.status_updated');
$this->addFlash('success', 'The data has been saved.');
return $this->redirectToRoute('admin_bill_show', [
'entity' => $entity->getId(),
]);
}
#[Route(path: "/admin/bill/sort/{page}", name: "admin_bill_sort", methods: ['POST'], requirements: ['page' => '\d+'])]
public function sort(RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session, int $page = 1): Response
{
@ -176,6 +199,8 @@ class BillAdminController extends CrudController
// ->setAction('show', 'edit', true)
->setView('form', 'admin/bill/_form.html.twig')
->setView('index', 'admin/bill/index.html.twig')
->setView('edit', 'admin/bill/edit.html.twig')
->setView('show_entity', 'admin/bill/_show.html.twig')
->setDefaultSort('index', 'id', 'desc')
->setField('index', 'ID', Field\TextField::class, [
@ -214,7 +239,7 @@ class BillAdminController extends CrudController
])
->setField('index', 'Montant TTC', Field\TextField::class, [
'property_builder' => function(EntityInterface $entity) {
if ($entity->getAmountTtc()) {
if ($entity->getAmountTtc() !== null) {
return sprintf('<span style="user-select: all">%01.2f</span> €', $entity->getAmountTtc());
}
},
@ -224,7 +249,7 @@ class BillAdminController extends CrudController
])
->setField('index', 'Montant HT', Field\TextField::class, [
'property_builder' => function(EntityInterface $entity) {
if ($entity->getAmountHt()) {
if ($entity->getAmountHt() !== null) {
return sprintf('<span style="user-select: all">%01.2f</span> €', $entity->getAmountHt());
}
},

View file

@ -0,0 +1,126 @@
<?php
namespace App\EventSubscriber;
use App\Core\Entity\EntityInterface;
use App\Core\Event\EntityManager\EntityManagerEvent;
use App\Core\EventSubscriber\EntityManagerEventSubscriber;
use App\Entity\Bill;
use Symfony\Component\Filesystem\Filesystem;
use App\Core\Slugify\Slugify;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use App\Core\Notification\MailNotifier;
use App\Repository\UserRepositoryQuery;
class BillEventSubscriber extends EntityManagerEventSubscriber
{
protected Slugify $slugger;
protected MailNotifier $notifier;
protected UrlGeneratorInterface $urlGenerator;
public function __construct(
Slugify $slugger,
MailNotifier $notifier,
UserRepositoryQuery $userQuery,
UrlGeneratorInterface $urlGenerator
)
{
$this->slugger = $slugger;
$this->notifier = $notifier;
$this->userQuery = $userQuery;
$this->urlGenerator = $urlGenerator;
}
public static function getSubscribedEvents()
{
return array_merge(parent::getSubscribedEvents(), [
'bill.status_updated' => ['onStatusUpdate', self::$priority],
]);
}
public function supports(EntityInterface $entity)
{
return $entity instanceof Bill;
}
public function onStatusUpdate(EntityManagerEvent $event)
{
if (!$this->supports($event->getEntity())) {
return;
}
$this->notifier
->setSubject('[Tinternet][Facture] '.$event->getEntity()->getHumanStatus())
->addRecipientsByUsers($this->userQuery
->where('.isTreasurer = true')
->orWhere('.isManager = true')
->find(),
true)
->notify('mail/bill/status_updated.html.twig', [
'entity' => $event->getEntity(),
'user' => $event->getParams()['user'],
'show_url' => $this->urlGenerator->generate(
'admin_bill_show',
[
'entity' => $event->getEntity()->getId(),
],
UrlGeneratorInterface::ABSOLUTE_URL
),
])
;
}
public function onPreUpdate(EntityManagerEvent $event)
{
if (!$this->supports($event->getEntity())) {
return;
}
$entity = $event->getEntity();
$splInfo = new \SplFileInfo($event->getEntity()->getFile());
$datas = [];
if ($entity->getDate()) {
$datas[] = $entity->getDate()->format('Ymd');
}
if ($entity->getCategory()) {
$datas[] = $this->slugger->slugify($entity->getCategory()->getLabel());
}
if ($entity->getVendor()) {
$datas[] = $this->slugger->slugify($entity->getVendor()->getLabel());
}
if ($entity->getReference()) {
$datas[] = $entity->getReference();
}
$datas[] = $entity->getId().'.'.$splInfo->getExtension();
$expectedFilename = implode('_', $datas);
$expectedPath = dirname($entity->getFile()).'/'.$expectedFilename;
if ($splInfo->getFilename() !== $expectedFilename) {
$fs = new Filesystem();
$fs->rename(
$event->getEntity()->getFile(),
$expectedPath
);
$event->getEntity()->setFile($expectedPath);
}
}
public function onDelete(EntityManagerEvent $event)
{
if (!$this->supports($event->getEntity())) {
return;
}
$fs = new Filesystem();
$fs->remove($event->getEntity()->getFile());
}
}

View file

@ -11,6 +11,7 @@ use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use App\Form\DateRangeType;
class BillFilterType extends AbstractType
{
@ -51,9 +52,11 @@ class BillFilterType extends AbstractType
'data-jschoice' => '',
],
])
->add('status', ChoiceType::class, [
'choices' => BillPeer::choices(),
'required' => false,
->add('dateRange', DateRangeType::class, [
'label' => 'Date',
'attr' => [
'class' => 'row',
],
])
->add('amountTtcRange', AmountRangeType::class, [
'label' => 'Montant TTC',
@ -67,6 +70,10 @@ class BillFilterType extends AbstractType
'class' => 'row',
],
])
->add('status', ChoiceType::class, [
'choices' => BillPeer::choices(),
'required' => false,
])
;
}

View file

@ -122,6 +122,7 @@ class BillType extends AbstractType
])
->add('status', ChoiceType::class, [
'choices' => BillPeer::choices(),
'help' => 'Modifier cette valeur manuellement ne permet pas d\'envoyer des notifications par mail',
'label_attr' => [
'class' => 'mt-3',
],

View file

@ -0,0 +1,49 @@
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\DateType;
class DateRangeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('min', DateType::class, [
'label' => 'Min',
'html5' => true,
'widget' => 'single_text',
'required' => false,
'row_attr' => [
'class' => 'col-md-6 pr-1',
],
'label_attr' => [
'class' => 'font-weight-normal',
],
])
->add('max', DateType::class, [
'label' => 'Max',
'html5' => true,
'widget' => 'single_text',
'required' => false,
'row_attr' => [
'class' => 'col-md-6 pl-1',
],
'label_attr' => [
'class' => 'font-weight-normal',
],
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => null,
]);
}
}

View file

@ -44,10 +44,26 @@ class BillRepositoryQuery extends RepositoryQuery
if (null !== $value['max']) {
$this
->andWhere('.amountHt <= :amountHtMin')
->andWhere('.amountHt <= :amountHtMax')
->setParameter('amountHtMax', $value['max'])
;
}
} elseif ('dateRange' === $name) {
if (null !== $value['min']) {
$this
->andWhere('.date >= :dateMin')
->setParameter('dateMin', $value['min'])
;
}
if (null !== $value['max']) {
$value['max']->add(new \DateInterval('PT'.(3600 * 24).'S'));
$this
->andWhere('.date <= :dateMax')
->setParameter('dateMax', $value['max'])
;
}
}
}
}

View file

@ -2,6 +2,9 @@
'Référence': entity.reference,
'Fournisseur': entity.vendor,
'Catégorie': entity.category,
'Date': entity.date ? entity.date.format('d/m/Y') : '/',
'Date limite de paiement': entity.paymentDeadlineDate ? entity.paymentDeadlineDate.format('d/m/Y') : '/',
'Catégorie': entity.category,
'Montant TTC': entity.amountTtc is not null ? (entity.amountTtc|number_format(2, ',') ~ ' €') : '/',
'Montant HT': entity.amountHt is not null ? (entity.amountHt|number_format(2, ',') ~ ' €') : '/',
'Status': entity.humanStatus,
@ -14,7 +17,7 @@
{% for label, value in values %}
<div class="list-group-item">
<div class="font-weight-bold">{{ label|trans }}</div>
<div class="mb-3">{{ value }}</div>
<div>{{ value }}</div>
</div>
{% endfor %}
</div>

View file

@ -0,0 +1,15 @@
{% extends '@Core/admin/crud/edit.html.twig' %}
{% block header_actions_after %}
{% if entity.status == 0 %}
<a href="{{ path('admin_bill_update_status', {entity: entity.id, status: 1, _token: csrf_token('update_status')}) }}" class="btn btn-success">
Demander le paiement
</a>
{% endif %}
{% if entity.status == 1 %}
<a href="{{ path('admin_bill_update_status', {entity: entity.id, status: 2, _token: csrf_token('update_status')}) }}" class="btn btn-success">
Définir comme payée
</a>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,7 @@
{% extends '@Core/admin/crud/index.html.twig' %}
{% block list_item_actions_before %}
<a href="{{ asset(item.file) }}" target="_blank" class="btn btn-sm btn-warning mr-1">
<span class="fa fa-download"></span>
</a>
{% endblock %}

View file

@ -66,7 +66,7 @@
{{ include('@Core/admin/module/_menu_section.html.twig', {label: 'Comptabilité'}) }}
<ul class="nav flex-column">
{% if is_granted('ROLE_TREASURER') %}
{% if is_granted('ROLE_MANAGER') or is_granted('ROLE_TREASURER') %}
{{ include('@Core/admin/module/_menu_item.html.twig', {
id: 'bill',
label: 'Factures',

View file

@ -0,0 +1,17 @@
{% extends '@Core/mail/base.html.twig' %}
{% block css %}
<style>
a {color: #1ab5dc}
</style>
{% endblock %}
{% block body %}
<p style="text-align: center; margin-bottom: 20px">
<img src="{{ absolute_url(asset('build/images/tinternet.png')) }}" alt="Tinternet &amp; Cie" width="50">
</p>
<p style="text-align: center; font-size: 20px">Le statut de la <a href="{{ show_url }}">facture&nbsp;#{{ entity.id }}</a> a été modifié&nbsp;☺️</p>
<p style="text-align: center; font-size: 20px">Nouveau statut&nbsp;:&nbsp;<strong>{{ entity.humanStatus }}</strong></p>
{% endblock %}