add expensive report base
This commit is contained in:
parent
eae8ffde37
commit
7813d1f494
|
@ -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"
|
||||
|
|
|
@ -14,6 +14,7 @@ security:
|
|||
role_hierarchy:
|
||||
ROLE_WRITER: ROLE_USER
|
||||
ROLE_MANAGER: ROLE_WRITER
|
||||
ROLE_TREASURER: ROLE_USER
|
||||
ROLE_ADMIN: ROLE_MANAGER
|
||||
|
||||
firewalls:
|
||||
|
|
242
src/Controller/ExpenseReportAdminController.php
Normal file
242
src/Controller/ExpenseReportAdminController.php
Normal 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';
|
||||
}
|
||||
}
|
179
src/Entity/ExpenseReport.php
Normal file
179
src/Entity/ExpenseReport.php
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
50
src/EventSubscriber/ExpenseReportEventSubscriber.php
Normal file
50
src/EventSubscriber/ExpenseReportEventSubscriber.php
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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]),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
18
src/Factory/ExpenseReportFactory.php
Normal file
18
src/Factory/ExpenseReportFactory.php
Normal 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;
|
||||
}
|
||||
}
|
42
src/Form/ExpenseReportDeleteBillType.php
Normal file
42
src/Form/ExpenseReportDeleteBillType.php
Normal 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' => [],
|
||||
]);
|
||||
}
|
||||
}
|
107
src/Form/ExpenseReportMoveType.php
Normal file
107
src/Form/ExpenseReportMoveType.php
Normal 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
|
||||
]);
|
||||
}
|
||||
}
|
36
src/Form/ExpenseReportNewBillType.php
Normal file
36
src/Form/ExpenseReportNewBillType.php
Normal 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
|
||||
]);
|
||||
}
|
||||
}
|
122
src/Form/ExpenseReportType.php
Normal file
122
src/Form/ExpenseReportType.php
Normal 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,
|
||||
]);
|
||||
}
|
||||
}
|
58
src/Form/ExpenseReportVariousPaymentType.php
Normal file
58
src/Form/ExpenseReportVariousPaymentType.php
Normal 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
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -25,5 +25,17 @@ class UserType extends BaseUserType
|
|||
]
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'isTreasurer',
|
||||
CheckboxType::class,
|
||||
[
|
||||
'label' => 'Trésorier⋅ière',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
],
|
||||
'constraints' => [
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
76
src/Repository/ExpenseReportRepository.php
Normal file
76
src/Repository/ExpenseReportRepository.php
Normal 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()
|
||||
;
|
||||
}
|
||||
*/
|
||||
}
|
23
src/Repository/ExpenseReportRepositoryQuery.php
Normal file
23
src/Repository/ExpenseReportRepositoryQuery.php
Normal 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());
|
||||
}
|
||||
}
|
16
templates/admin/expense_report/_form.html.twig
Normal file
16
templates/admin/expense_report/_form.html.twig
Normal 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>
|
|
@ -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 %}
|
||||
|
|
|
@ -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>
|
||||
|
|
27
templates/core/user/user_admin/_show.html.twig
Normal file
27
templates/core/user/user_admin/_show.html.twig
Normal 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>
|
|
@ -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)"
|
||||
|
|
Loading…
Reference in a new issue