add expensive report base

This commit is contained in:
Simon Vieille 2022-04-29 11:31:12 +02:00
parent eae8ffde37
commit 7813d1f494
Signed by: deblan
GPG key ID: 579388D585F70417
21 changed files with 1126 additions and 25 deletions

View file

@ -7,6 +7,7 @@
"prefer-stable": true,
"require": {
"php": ">=8.0.0",
"doctrine/orm": "2.11.*",
"knplabs/knp-snappy": "^1.4",
"murph/murph-core": "^1.13,>=1.13.0",
"sabre/dav": "^4.3"

View file

@ -14,6 +14,7 @@ security:
role_hierarchy:
ROLE_WRITER: ROLE_USER
ROLE_MANAGER: ROLE_WRITER
ROLE_TREASURER: ROLE_USER
ROLE_ADMIN: ROLE_MANAGER
firewalls:

View file

@ -0,0 +1,242 @@
<?php
namespace App\Controller;
use App\Core\Controller\Admin\Crud\CrudController;
use App\Core\Crud\CrudConfiguration;
use App\Core\Crud\Field;
use App\Core\Entity\EntityInterface;
use App\Core\Form\FileUploadHandler;
use App\Core\Manager\EntityManager;
use App\Entity\ExpenseReport as Entity;
use App\Factory\ExpenseReportFactory as Factory;
use App\Form\ExpenseReportType as Type;
use App\Repository\ExpenseReportRepositoryQuery as RepositoryQuery;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\Annotation\Route;
class ExpenseReportAdminController extends CrudController
{
protected FileUploadHandler $fileUpload;
protected Filesystem $fs;
public function __construct(FileUploadHandler $fileUpload, Filesystem $fs)
{
$this->fileUpload = $fileUpload;
$this->fs = $fs;
}
/**
* @Route("/admin/expense_report/{page}", name="admin_expense_report_index", methods={"GET"}, requirements={"page":"\d+"})
*/
public function index(RepositoryQuery $query, Request $request, Session $session, int $page = 1): Response
{
if (!$this->isGranted('ROLE_TREASURER')) {
$query->filterByUser($this->getUser());
}
return $this->doIndex($page, $query, $request, $session);
}
/**
* @Route("/admin/expense_report/new", name="admin_expense_report_new", methods={"GET", "POST"})
*/
public function new(Factory $factory, EntityManager $entityManager, Request $request): Response
{
return $this->doNew($factory->create($this->getUser()), $entityManager, $request);
}
/**
* @Route("/admin/expense_report/show/{entity}", name="admin_expense_report_show", methods={"GET"})
*/
public function show(Entity $entity): Response
{
if (!$this->isGranted('ROLE_TREASURER')) {
if ($entity->getUser()->getId() !== $entity->getUser()->getId()) {
throw $this->createAccessDeniedException();
}
}
return $this->doShow($entity);
}
/**
* @Route("/admin/expense_report/filter", name="admin_expense_report_filter", methods={"GET"})
*/
public function filter(Session $session): Response
{
return $this->doFilter($session);
}
/**
* @Route("/admin/expense_report/edit/{entity}", name="admin_expense_report_edit", methods={"GET", "POST"})
*/
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
{
if (!$this->isGranted('ROLE_TREASURER')) {
if ($entity->getUser()->getId() !== $entity->getUser()->getId()) {
throw $this->createAccessDeniedException();
}
}
return $this->doEdit($entity, $entityManager, $request, [$this, 'beforeUpdate']);
}
protected function beforeUpdate(EntityInterface $entity, FormInterface $form, Request $request)
{
$deleteBills = $request->request->get($form->getName())['deleteBills']['bills'] ?? [];
$bills = $entity->getBills();
foreach ($bills as $key => $value) {
if (in_array($value, $deleteBills)) {
unset($bills[$key]);
$this->fs->remove($value);
}
}
$directory = sprintf(
'uploads/Notes de frais/%d/%d',
$this->getUser()->getId(),
$entity->getId()
);
$newBills = $request->files->get($form->getName())['newBills'] ?? [];
foreach ($newBills as $data) {
$this->fileUpload->handleForm(
$data['file'],
$directory,
function ($filename) use ($directory, &$bills) {
$bills[] = $directory.'/'.$filename;
},
true
);
}
$entity->setBills($bills);
}
/**
* @Route("/admin/expense_report/sort/{page}", name="admin_expense_report_sort", methods={"POST"}, requirements={"page":"\d+"})
*/
public function sort(RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session, int $page = 1): Response
{
return $this->doSort($page, $query, $entityManager, $request, $session);
}
/**
* @Route("/admin/expense_report/batch/{page}", name="admin_expense_report_batch", methods={"POST"}, requirements={"page":"\d+"})
*/
public function batch(RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session, int $page = 1): Response
{
if (!$this->isGranted('ROLE_TREASURER')) {
$query->filterByUser($this->getUser());
}
return $this->doBatch($page, $query, $entityManager, $request, $session);
}
/**
* @Route("/admin/expense_report/delete/{entity}", name="admin_expense_report_delete", methods={"DELETE"})
*/
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
{
if (!$this->isGranted('ROLE_TREASURER')) {
if ($entity->getUser()->getId() !== $entity->getUser()->getId()) {
throw $this->createAccessDeniedException();
}
}
return $this->doDelete($entity, $entityManager, $request);
}
protected function getConfiguration(): CrudConfiguration
{
return CrudConfiguration::create()
->setPageTitle('index', 'Notes de frais')
->setPageTitle('edit', 'Note de frais {id}')
->setPageTitle('new', 'Nouvelle note de frais')
->setPageTitle('show', 'Note de frais {id}')
->setPageRoute('index', 'admin_expense_report_index')
->setPageRoute('new', 'admin_expense_report_new')
->setPageRoute('edit', 'admin_expense_report_edit')
->setPageRoute('show', 'admin_expense_report_show')
->setPageRoute('sort', 'admin_expense_report_sort')
->setPageRoute('batch', 'admin_expense_report_batch')
->setPageRoute('delete', 'admin_expense_report_delete')
->setPageRoute('filter', 'admin_expense_report_filter')
->setFormOptions('new', [
'is_treasurer' => $this->isGranted('ROLE_TREASURER'),
])
->setFormOptions('edit', [
'is_treasurer' => $this->isGranted('ROLE_TREASURER'),
])
->setView('form', 'admin/expense_report/_form.html.twig')
->setForm('edit', Type::class, [])
->setForm('new', Type::class)
// ->setForm('filter', Type::class)
// ->setMaxPerPage('index', 20)
// ->setIsSortableCollection('index', false)
// ->setSortableCollectionProperty('sortOrder')
// ->setAction('index', 'new', true)
// ->setAction('index', 'show', true)
// ->setAction('index', 'edit', true)
// ->setAction('index', 'delete', true)
// ->setAction('edit', 'back', true)
// ->setAction('edit', 'show', true)
// ->setAction('edit', 'delete', true)
// ->setAction('show', 'back', true)
// ->setAction('show', 'edit', true)
->setDefaultSort('index', 'dateTo', 'desc')
->setField('index', 'Personne', Field\TextField::class, [
'property_builder' => function (EntityInterface $entity) {
return $entity->getUser()->getDisplayName();
},
'attr' => ['class' => 'col-md-3'],
])
->setField('index', 'Date from', Field\DateField::class, [
'property' => 'dateFrom',
'sort' => ['dateFrom', '.dateFrom'],
'format' => 'd/m/Y',
'attr' => ['class' => 'col-md-3'],
])
->setField('index', 'Date to', Field\DateField::class, [
'property' => 'dateTo',
'sort' => ['dateTo', '.dateTo'],
'format' => 'd/m/Y',
'attr' => ['class' => 'col-md-3'],
])
->setField('index', 'Payée', Field\ButtonField::class, [
'property_builder' => function (EntityInterface $entity) {
return $entity->getIsPaid() ? 'Yes' : 'No';
},
'attr' => ['class' => 'col-md-3'],
'button_attr_builder' => function (EntityInterface $entity) {
return ['class' => 'btn btn-sm btn-'.($entity->getIsPaid() ? 'success' : 'info')];
},
])
// ->setBatchAction('index', 'delete', 'Delete', function (EntityInterface $entity, EntityManager $manager) {
// $manager->delete($entity);
// })
;
}
protected function getSection(): string
{
return 'expense_report';
}
}

View file

@ -0,0 +1,179 @@
<?php
namespace App\Entity;
use App\Repository\ExpenseReportRepository;
use Doctrine\ORM\Mapping as ORM;
use App\Core\Entity\EntityInterface;
/**
* @ORM\Entity(repositoryClass=ExpenseReportRepository::class)
*/
class ExpenseReport implements EntityInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity=User::class, inversedBy="expenseReports")
* @ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* @ORM\Column(type="array")
*/
private $moves = [];
/**
* @ORM\Column(type="array")
*/
private $variousPayments = [];
/**
* @ORM\Column(type="boolean")
*/
private $isPaid;
/**
* @ORM\Column(type="datetime", nullable=true)
*/
private $paidAt;
/**
* @ORM\Column(type="array")
*/
private $bills = [];
/**
* @ORM\Column(type="date")
*/
private $dateFrom;
/**
* @ORM\Column(type="date")
*/
private $dateTo;
/**
* @ORM\Column(type="float")
*/
private $scalePerKilometer;
public function getId(): ?int
{
return $this->id;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
public function getMoves(): ?array
{
return $this->moves;
}
public function setMoves(array $moves): self
{
$this->moves = $moves;
return $this;
}
public function getVariousPayments(): ?array
{
return $this->variousPayments;
}
public function setVariousPayments(array $variousPayments): self
{
$this->variousPayments = $variousPayments;
return $this;
}
public function getIsPaid(): ?bool
{
return $this->isPaid;
}
public function setIsPaid(bool $isPaid): self
{
$this->isPaid = $isPaid;
return $this;
}
public function getPaidAt(): ?\DateTimeInterface
{
return $this->paidAt;
}
public function setPaidAt(?\DateTimeInterface $paidAt): self
{
$this->paidAt = $paidAt;
return $this;
}
public function getBills(): ?array
{
return $this->bills;
}
public function setBills(array $bills): self
{
$this->bills = $bills;
return $this;
}
public function getDateFrom(): ?\DateTimeInterface
{
return $this->dateFrom;
}
public function setDateFrom(\DateTimeInterface $dateFrom): self
{
$this->dateFrom = $dateFrom;
return $this;
}
public function getDateTo(): ?\DateTimeInterface
{
return $this->dateTo;
}
public function setDateTo(\DateTimeInterface $dateTo): self
{
$this->dateTo = $dateTo;
return $this;
}
public function getScalePerKilometer(): ?float
{
return $this->scalePerKilometer;
}
public function setScalePerKilometer(float $scalePerKilometer): self
{
$this->scalePerKilometer = $scalePerKilometer;
return $this;
}
}

View file

@ -5,6 +5,8 @@ namespace App\Entity;
use App\Core\Doctrine\Timestampable;
use App\Core\Entity\EntityInterface;
use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
@ -75,8 +77,19 @@ class User implements PasswordAuthenticatedUserInterface, UserInterface, TwoFact
*/
protected $isManager;
/**
* @ORM\Column(type="boolean", nullable=true)
*/
protected $isTreasurer;
/**
* @ORM\OneToMany(targetEntity=ExpenseReport::class, mappedBy="user", orphanRemoval=true)
*/
private $expenseReports;
public function __construct()
{
$this->expenseReports = new ArrayCollection();
}
public function __toString()
@ -127,6 +140,10 @@ class User implements PasswordAuthenticatedUserInterface, UserInterface, TwoFact
$roles[] = 'ROLE_MANAGER';
}
if ($this->getIsTreasurer()) {
$roles[] = 'ROLE_TREASURER';
}
return array_unique($roles);
}
@ -268,4 +285,46 @@ class User implements PasswordAuthenticatedUserInterface, UserInterface, TwoFact
return $this;
}
public function getIsTreasurer(): ?bool
{
return $this->isTreasurer;
}
public function setIsTreasurer(?bool $isTreasurer): self
{
$this->isTreasurer = $isTreasurer;
return $this;
}
/**
* @return Collection<int, ExpenseReport>
*/
public function getExpenseReports(): Collection
{
return $this->expenseReports;
}
public function addExpenseReport(ExpenseReport $expenseReport): self
{
if (!$this->expenseReports->contains($expenseReport)) {
$this->expenseReports[] = $expenseReport;
$expenseReport->setUser($this);
}
return $this;
}
public function removeExpenseReport(ExpenseReport $expenseReport): self
{
if ($this->expenseReports->removeElement($expenseReport)) {
// set the owning side to null (unless already changed)
if ($expenseReport->getUser() === $this) {
$expenseReport->setUser(null);
}
}
return $this;
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace App\EventSubscriber;
use App\Core\Entity\EntityInterface;
use App\Core\Event\EntityManager\EntityManagerEvent;
use App\Core\EventSubscriber\EntityManagerEventSubscriber;
use App\Core\Setting\SettingManager;
use App\Entity\ExpenseReport;
class ExpenseReportEventSubscriber extends EntityManagerEventSubscriber
{
protected SettingManager $manager;
public function __construct(SettingManager $manager)
{
$this->manager = $manager;
}
public function supports(EntityInterface $entity)
{
return $entity instanceof ExpenseReport;
}
public function onPreCreate(EntityManagerEvent $event)
{
if (!$this->supports($event->getEntity())) {
return;
}
$this->addScalePerKilometer($event->getEntity());
}
public function onPreUpdate(EntityManagerEvent $event)
{
if (!$this->supports($event->getEntity())) {
return;
}
$this->addScalePerKilometer($event->getEntity());
}
protected function addScalePerKilometer(EntityInterface $entity)
{
if (null === $entity->getScalePerKilometer() || 0.0 === $entity->getScalePerKilometer()) {
$scale = $this->manager->get('expense_report_scale_per_kilometer');
$entity->setScalePerKilometer($scale->getValue());
}
}
}

View file

@ -5,8 +5,9 @@ namespace App\EventSubscriber;
use App\Core\Event\Setting\SettingEvent;
use App\Core\EventSubscriber\SettingEventSubscriber as EventSubscriber;
use App\Core\Setting\SettingManager;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Range;
/**
* class SettingEventSubscriber.
@ -24,8 +25,12 @@ class SettingEventSubscriber extends EventSubscriber
public function onInit(SettingEvent $event)
{
// $this->manager->init('myapp_foo', 'My app', 'Foo', 'Default value');
// $this->manager->init('myapp_bar', 'My app', 'Bar', true);
$this->manager->init(
'expense_report_scale_per_kilometer',
'Note de frais',
'Barème au kilomètre',
0
);
}
public function onFormInit(SettingEvent $event)
@ -34,25 +39,24 @@ class SettingEventSubscriber extends EventSubscriber
$builder = $data['builder'];
$entity = $data['entity'];
// if ('myapp_foo' === $entity->getCode()) {
// $builder->add(
// 'value',
// TextType::class,
// [
// 'label' => $entity->getLabel(),
// ]
// );
// }
//
// if ('myapp_bar' === $entity->getCode()) {
// $builder->add(
// 'value',
// CheckboxType::class,
// [
// 'label' => $entity->getLabel(),
// 'required' => false,
// ]
// );
// }
if ('expense_report_scale_per_kilometer' === $entity->getCode()) {
$builder->add(
'value',
NumberType::class,
[
'html5' => true,
'required' => true,
'label' => $entity->getLabel(),
'scale' => 2,
'attr' => [
'step' => 0.01,
],
'constraints' => [
new NotBlank(),
new Range(['min' => 0]),
],
]
);
}
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace App\Factory;
use App\Core\Factory\FactoryInterface;
use App\Entity\ExpenseReport as Entity;
use App\Entity\User;
class ExpenseReportFactory implements FactoryInterface
{
public function create(User $user): Entity
{
$entity = new Entity();
$entity->setUser($user);
return $entity;
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use App\Core\Form\FileManager\FilePickerType;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class ExpenseReportDeleteBillType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$choices = [];
foreach ($options['bills'] as $bill) {
$choices[basename($bill)] = $bill;
}
$builder->add(
'bills',
ChoiceType::class,
[
'label' => false,
'required' => false,
'multiple' => true,
'choices' => $choices,
'expanded' => true,
]
);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'bills' => [],
]);
}
}

View file

@ -0,0 +1,107 @@
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Range;
class ExpenseReportMoveType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('date', DateType::class, [
'html5' => true,
'required' => true,
'widget' => 'single_text',
'row_attr' => [
'class' => 'col-md-6 pr-1',
],
'constraints' => [
new NotBlank(),
],
])
->add('event', TextType::class, [
'row_attr' => [
'class' => 'col-md-6',
],
'constraints' => [
new NotBlank(),
],
])
->add('addressFrom', TextType::class, [
'row_attr' => [
'class' => 'col-md-6 pr-1',
],
'constraints' => [
new NotBlank(),
],
])
->add('addressTo', TextType::class, [
'row_attr' => [
'class' => 'col-md-6',
],
'constraints' => [
new NotBlank(),
],
])
->add('distance', NumberType::class, [
'html5' => true,
'scale' => 0,
'row_attr' => [
'class' => 'col-6 pr-1',
],
'constraints' => [
new NotBlank(),
new Range(['min' => 0]),
],
])
->add('isRoundTrip', ChoiceType::class, [
'row_attr' => [
'class' => 'col-md-6',
],
'choices' => [
'Non' => false,
'Oui' => true,
],
'required' => true,
])
->add('highwayPay', NumberType::class, [
'row_attr' => [
'class' => 'col-md-6 pr-1',
],
'attr' => [
'step' => 0.01,
],
'scale' => 2,
'required' => false,
'html5' => true,
])
->add('parkingPay', NumberType::class, [
'row_attr' => [
'class' => 'col-md-6',
],
'attr' => [
'step' => 0.01,
],
'scale' => 2,
'required' => false,
'html5' => true,
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
// Configure your form options here
]);
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use App\Core\Form\FileManager\FilePickerType;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Form\Extension\Core\Type\FileType;
class ExpenseReportNewBillType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('file', FileType::class, [
'required' => true,
'label' => false,
'row_attr' => [
'class' => 'col-md-12',
],
'constraints' => [
new NotBlank(),
],
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
// Configure your form options here
]);
}
}

View file

@ -0,0 +1,122 @@
<?php
namespace App\Form;
use App\Core\Form\Type\CollectionType;
use App\Entity\ExpenseReport;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ExpenseReportType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('dateFrom', null, [
'html5' => true,
'widget' => 'single_text',
])
->add('dateTo', null, [
'html5' => true,
'widget' => 'single_text',
])
->add('moves', CollectionType::class, [
'entry_type' => ExpenseReportMoveType::class,
'collection_name' => 'moves',
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
'row_attr' => [
'class' => 'mb-3 pb-3 pl-3 pr-3 pt-1 bg-light',
],
'label_attr' => [
'class' => 'font-weight-bold',
],
'attr' => [
'class' => 'mb-3 row',
],
])
->add('variousPayments', CollectionType::class, [
'entry_type' => ExpenseReportVariousPaymentType::class,
'collection_name' => 'variousPayments',
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
'row_attr' => [
'class' => 'mb-3 pb-3 pl-3 pr-3 pt-1 bg-light',
],
'label_attr' => [
'class' => 'font-weight-bold',
],
'attr' => [
'class' => 'mb-3 row',
],
])
;
if ($builder->getData()->getId()) {
$builder
->add('newBills', CollectionType::class, [
'entry_type' => ExpenseReportNewBillType::class,
'mapped' => false,
'label' => 'Nouvelles factures',
'collection_name' => 'newBills',
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
'row_attr' => [
'class' => 'mb-3 pb-3 pl-3 pr-3 pt-1 bg-light',
],
'label_attr' => [
'class' => 'font-weight-bold',
],
'attr' => [
'class' => 'mb-3 row',
],
])
;
}
if (count($builder->getData()->getBills())) {
$builder
->add('deleteBills', ExpenseReportDeleteBillType::class, [
'mapped' => false,
'label' => 'Factures à supprimer',
'bills' => $builder->getData()->getBills(),
'row_attr' => [
'class' => 'mb-3 p-3 bg-light',
],
'label_attr' => [
'class' => 'font-weight-bold',
],
'attr' => [
'class' => 'row mb-3',
],
])
;
}
if ($options['is_treasurer']) {
$builder
->add('isPaid', CheckboxType::class, [
'required' => false,
])
->add('paidAt', null, [
'required' => false,
'html5' => true,
'widget' => 'single_text',
])
;
}
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => ExpenseReport::class,
'is_treasurer' => false,
]);
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Range;
class ExpenseReportVariousPaymentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('date', DateType::class, [
'html5' => true,
'required' => true,
'widget' => 'single_text',
'row_attr' => [
'class' => 'col-md-4 pr-1',
],
'constraints' => [
new NotBlank(),
],
])
->add('label', TextType::class, [
'row_attr' => [
'class' => 'col-md-4 pr-1',
],
'constraints' => [
new NotBlank(),
],
])
->add('amount', NumberType::class, [
'required' => true,
'html5' => true,
'row_attr' => [
'class' => 'col-md-4 pr-1',
],
'constraints' => [
new NotBlank(),
new Range(['min' => 0]),
],
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
// Configure your form options here
]);
}
}

View file

@ -25,5 +25,17 @@ class UserType extends BaseUserType
]
);
$builder->add(
'isTreasurer',
CheckboxType::class,
[
'label' => 'Trésorier⋅ière',
'required' => false,
'attr' => [
],
'constraints' => [
],
]
);
}
}

View file

@ -0,0 +1,76 @@
<?php
namespace App\Repository;
use App\Entity\ExpenseReport;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\ORMException;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method ExpenseReport|null find($id, $lockMode = null, $lockVersion = null)
* @method ExpenseReport|null findOneBy(array $criteria, array $orderBy = null)
* @method ExpenseReport[] findAll()
* @method ExpenseReport[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ExpenseReportRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, ExpenseReport::class);
}
/**
* @throws ORMException
* @throws OptimisticLockException
*/
public function add(ExpenseReport $entity, bool $flush = true): void
{
$this->_em->persist($entity);
if ($flush) {
$this->_em->flush();
}
}
/**
* @throws ORMException
* @throws OptimisticLockException
*/
public function remove(ExpenseReport $entity, bool $flush = true): void
{
$this->_em->remove($entity);
if ($flush) {
$this->_em->flush();
}
}
// /**
// * @return ExpenseReport[] Returns an array of ExpenseReport objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('e')
->andWhere('e.exampleField = :val')
->setParameter('val', $value)
->orderBy('e.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
/*
public function findOneBySomeField($value): ?ExpenseReport
{
return $this->createQueryBuilder('e')
->andWhere('e.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
}

View file

@ -0,0 +1,23 @@
<?php
namespace App\Repository;
use App\Core\Repository\RepositoryQuery;
use Knp\Component\Pager\PaginatorInterface;
use App\Repository\ExpenseReportRepository as Repository;
use App\Entity\User;
class ExpenseReportRepositoryQuery extends RepositoryQuery
{
public function __construct(Repository $repository, PaginatorInterface $paginator)
{
parent::__construct($repository, 'e', $paginator);
}
public function filterByUser(User $user): self
{
return $this
->andWhere('.user = :user')
->setParameter('user', $user->getId());
}
}

View file

@ -0,0 +1,16 @@
<div class="row">
<div class="col-md-3 order-2 pl-md-3">
{% for item in ['dateFrom', 'dateTo', 'isPaid', 'paidAt', 'newBills', 'deleteBills'] %}
{% if form[item] is defined %}
{% include(configuration.view('form_widget', '@Core/admin/crud/_form_widget.html.twig')) with {form: form[item]} %}
{% endif %}
{% endfor %}
</div>
<div class="col-md-9 order-1 pr-md-3">
{% for item in ['moves', 'variousPayments', 'newBills'] %}
{% if form[item] is defined %}
{% include(configuration.view('form_widget', '@Core/admin/crud/_form_widget.html.twig')) with {form: form[item]} %}
{% endif %}
{% endfor %}
</div>
</div>

View file

@ -62,4 +62,15 @@
icon: 'fa fa-list-alt'
}) }}
</ul>
{{ include('@Core/admin/module/_menu_section.html.twig', {label: 'Divers'}) }}
<ul class="nav flex-column">
{{ include('@Core/admin/module/_menu_item.html.twig', {
id: 'expense_report',
label: 'Notes de frais',
route: path('admin_expense_report_index'),
icon: 'fa fa-credit-card'
}) }}
</ul>
{% endif %}

View file

@ -1,7 +1,7 @@
<div class="row">
<div class="col-12 p-3">
<div class="row">
{% for item in ['displayName', 'email', 'isAdmin', 'isWriter', 'isManager'] %}
{% for item in ['displayName', 'email', 'isAdmin', 'isWriter', 'isManager', 'isTreasurer'] %}
<div class="col-12">
{{ form_row(form[item]) }}
</div>

View file

@ -0,0 +1,27 @@
<div class="row">
<div class="col-12 p-3">
<ul class="list-group">
<li class="list-group-item">
<span class="font-weight-bold pb-2 d-block">{{ 'Display name'|trans }}</span>
{{ entity.displayName }}
</li>
<li class="list-group-item">
<span class="font-weight-bold pb-2 d-block">{{ 'E-mail'|trans }}</span>
{{ entity.email }}
</li>
<li class="list-group-item">
<span class="font-weight-bold pb-2 d-block">{{ 'Permissions'|trans }}</span>
{{ entity.roles|join('<br>')|replace({
ROLE_USER: 'User'|trans,
ROLE_WRITER: 'Writer'|trans,
ROLE_ADMIN: 'Administrator'|trans,
ROLE_TREASURER: 'ROLE_TREASURER'|trans,
ROLE_MANAGER: 'ROLE_MANAGER'|trans,
})|raw }}
</li>
</ul>
</div>
</div>

View file

@ -1,3 +1,5 @@
"ROLE_MANAGER": "Gestionnaire"
"ROLE_TREASURER": "Trésorier⋅ière"
"Address": "Adresse"
"Zip code": "Code postal"
"City": "Ville"
@ -26,3 +28,18 @@
"Caldav password": "Mot de passe du serveur Caldav"
"Caldav calendar uri": "URI du calendrier Caldav"
"Color": "Couleur"
"Moves": "Trajets"
"Highway pay": "Frais de péage (TTC)"
"Parking pay": "Frais de parking (TTC)"
"Various payments": "Paiements divers"
"Bills": "Factures"
"Date from": "Date de début"
"Date to": "Date de fin"
"Is paid": "Est payé(e)"
"Paid at": "Payé(e) le"
"Event": "Évènement"
"Address from": "Adresse de départ"
"Address to": "Adresse d'arrivée"
"Distance": "Distance (sans retour)"
"Is round trip": "Aller-retour"
"Amount": "Montant (TTC)"