Merge branch 'feature/bills' into develop
This commit is contained in:
commit
3f24d77863
5
.env
5
.env
|
@ -31,3 +31,8 @@ DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
|
|||
# DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7"
|
||||
# DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=13&charset=utf8"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
|
||||
WEBDAV_BILLING_SERVER=https://deblan.cloud
|
||||
WEBDAV_BILLING_BASE_URL=/public.php/webdav/
|
||||
WEBDAV_BILLING_USERNAME=
|
||||
WEBDAV_BILLING_PASSWORD=
|
||||
|
|
|
@ -1 +1 @@
|
|||
8.0
|
||||
8.1
|
||||
|
|
|
@ -4,8 +4,10 @@ const AddressAutocomplete = require('./modules/address.js')
|
|||
const FilesCollectionSorter = require('./modules/collection-sorter.js')
|
||||
const Calendar = require('./modules/calendar.js')
|
||||
const Masks = require('./modules/masks.js')
|
||||
const PdfViewer = require('./modules/pdf-viewer.js')
|
||||
|
||||
new AddressAutocomplete()
|
||||
new FilesCollectionSorter()
|
||||
new Calendar()
|
||||
new Masks()
|
||||
new PdfViewer()
|
||||
|
|
17
assets/js/modules/pdf-viewer.js
Normal file
17
assets/js/modules/pdf-viewer.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
const viewer = require('pdfobject')
|
||||
|
||||
class PdfViewer {
|
||||
constructor () {
|
||||
this.showPdfs()
|
||||
}
|
||||
|
||||
showPdfs () {
|
||||
const elements = document.querySelectorAll('*[data-pdf]')
|
||||
|
||||
for (const element of elements) {
|
||||
viewer.embed(element.getAttribute('data-pdf'), `#${element.getAttribute('id')}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PdfViewer
|
|
@ -10,7 +10,7 @@
|
|||
"doctrine/orm": "2.11.*",
|
||||
"knplabs/knp-snappy": "^1.4",
|
||||
"murph/murph-core": "^1.18",
|
||||
"sabre/dav": "^4.3"
|
||||
"sabre/dav": "^4.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/browser-kit": "^5.4",
|
||||
|
|
|
@ -41,3 +41,4 @@ core:
|
|||
# path_uri: "/uploads"
|
||||
path_locked:
|
||||
- "%kernel.project_dir%/public/uploads/Notes de frais"
|
||||
- "%kernel.project_dir%/public/uploads/Factures"
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
|
||||
parameters:
|
||||
webdav_billing_server: "%env(WEBDAV_BILLING_SERVER)%"
|
||||
webdav_billing_base_url: "%env(WEBDAV_BILLING_BASE_URL)%"
|
||||
webdav_billing_username: "%env(WEBDAV_BILLING_USERNAME)%"
|
||||
webdav_billing_password: "%env(WEBDAV_BILLING_PASSWORD)%"
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
|
@ -65,5 +69,13 @@ services:
|
|||
$privateKeyPath: '%kernel.project_dir%/config/secrets/prod.private.key'
|
||||
$filesystem: '@filesystem'
|
||||
|
||||
App\Api\Webdav\Client:
|
||||
public: true
|
||||
arguments:
|
||||
$server: '%webdav_billing_server%'
|
||||
$baseUrl: '%webdav_billing_base_url%'
|
||||
$username: '%webdav_billing_username%'
|
||||
$password: '%webdav_billing_password%'
|
||||
|
||||
# add more service definitions when explicit configuration is needed
|
||||
# please note that last definitions always *replace* previous ones
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"chunk": "^0.0.3",
|
||||
"inputmask": "^5.0.8-beta.17",
|
||||
"murph-project": "^1",
|
||||
"pdfobject": "^2.2.8",
|
||||
"vue-fragment": "^1.5.2"
|
||||
}
|
||||
}
|
||||
|
|
1
public/vendor/viewerjs
vendored
Symbolic link
1
public/vendor/viewerjs
vendored
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../manual_vendor/viewerjs-0.5.8/ViewerJS
|
81
src/Api/Webdav/Client.php
Normal file
81
src/Api/Webdav/Client.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace App\Api\Webdav;
|
||||
|
||||
use Sabre\DAV\Client as BaseClient;
|
||||
|
||||
/**
|
||||
* class Client.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class Client
|
||||
{
|
||||
protected BaseClient $client;
|
||||
protected string $baseUrl;
|
||||
|
||||
public function __construct(string $server, string $baseUrl, ?string $username, ?string $password)
|
||||
{
|
||||
$settings = [
|
||||
'baseUri' => $server,
|
||||
'userName' => $username,
|
||||
'password' => $password,
|
||||
'authType' => 1,
|
||||
];
|
||||
|
||||
$this->baseUrl = rtrim($baseUrl, '/');
|
||||
$this->client = new BaseClient($settings);
|
||||
}
|
||||
|
||||
public function sendFile(string $localFile, string $remoteFile): array
|
||||
{
|
||||
return $this->client->request(
|
||||
'PUT',
|
||||
$this->baseUrl.'/'.$remoteFile,
|
||||
fopen($localFile, 'r')
|
||||
);
|
||||
}
|
||||
|
||||
public function mv(string $source, string $destination): array
|
||||
{
|
||||
return $this->client->request(
|
||||
'MOVE',
|
||||
$this->baseUrl.'/'.$source,
|
||||
null,
|
||||
[
|
||||
'Destination' => $this->baseUrl.'/'.$destination,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function mkdir(string $directory): array
|
||||
{
|
||||
return $this->client->request(
|
||||
'MKCOL',
|
||||
$this->baseUrl.'/'.$directory
|
||||
);
|
||||
}
|
||||
|
||||
public function rm(string $file): array
|
||||
{
|
||||
return $this->client->request(
|
||||
'DELETE',
|
||||
$this->baseUrl.'/'.$file
|
||||
);
|
||||
}
|
||||
|
||||
public function ls(string $directory): array
|
||||
{
|
||||
return $this->client->propfind($this->baseUrl.'/'.$directory, [
|
||||
'{DAV:}displayname',
|
||||
'{DAV:}getcontentlength',
|
||||
], 1);
|
||||
}
|
||||
|
||||
public function exists(string $file): bool
|
||||
{
|
||||
$response = $this->client->request('GET', $this->baseUrl.'/'.$file);
|
||||
|
||||
return 404 !== $response['statusCode'];
|
||||
}
|
||||
}
|
68
src/Command/WebdavBillSyncCommand.php
Normal file
68
src/Command/WebdavBillSyncCommand.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Api\Webdav\Client;
|
||||
use App\Repository\BillRepositoryQuery;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'webdav:bill:sync',
|
||||
description: 'Sync bills',
|
||||
)]
|
||||
class WebdavBillSyncCommand extends Command
|
||||
{
|
||||
protected Client $client;
|
||||
protected BillRepositoryQuery $query;
|
||||
|
||||
public function __construct(Client $client, BillRepositoryQuery $query)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->query = $query;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
chdir(__DIR__.'/../../public');
|
||||
|
||||
$remoteFiles = $this->client->ls('/');
|
||||
$localFiles = [];
|
||||
|
||||
foreach ($this->query->find() as $entity) {
|
||||
$localFiles[basename($entity->getFile())] = $entity->getFile();
|
||||
}
|
||||
|
||||
foreach ($localFiles as $basename => $file) {
|
||||
if (!$this->client->exists($basename)) {
|
||||
$this->client->sendFile($file, $basename);
|
||||
$output->writeln(sprintf(
|
||||
'Fichier <comment>%s</comment> envoyé',
|
||||
$file
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$isFirst = true;
|
||||
|
||||
foreach ($remoteFiles as $remoteFile) {
|
||||
$name = $remoteFile['{DAV:}displayname'];
|
||||
|
||||
if (!isset($localFiles[$name]) && !$isFirst) {
|
||||
$this->client->rm($name);
|
||||
$output->writeln(sprintf(
|
||||
'Fichier distant <comment>%s</comment> supprimé',
|
||||
$name
|
||||
));
|
||||
}
|
||||
|
||||
$isFirst = false;
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
284
src/Controller/BillAdminController.php
Normal file
284
src/Controller/BillAdminController.php
Normal file
|
@ -0,0 +1,284 @@
|
|||
<?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\Manager\EntityManager;
|
||||
use App\Entity\Bill as Entity;
|
||||
use App\Factory\BillFactory as Factory;
|
||||
use App\Form\BillType as Type;
|
||||
use App\Form\BillFilterType as FilterType;
|
||||
use App\Repository\BillRepositoryQuery as RepositoryQuery;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use App\Core\Form\FileUploadHandler;
|
||||
use Symfony\Component\Form\Form;
|
||||
use function Symfony\Component\String\u;
|
||||
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
|
||||
{
|
||||
#[Route(path: '/admin/user/edit/{entity}', name: 'admin_user_edit', methods: ['GET', 'POST'])]
|
||||
|
||||
#[Route(path: "/admin/bill/{page}", name: "admin_bill_index", methods: ['GET'], requirements: ['page' => '\d+'])]
|
||||
public function index(RepositoryQuery $query, Request $request, Session $session, int $page = 1): Response
|
||||
{
|
||||
return $this->doIndex($page, $query, $request, $session);
|
||||
}
|
||||
|
||||
#[Route(path: "/admin/bill/new", name: "admin_bill_new", methods: ['GET', 'POST'])]
|
||||
public function new(Factory $factory, EntityManager $entityManager, Request $request, FileUploadHandler $fileUpload): Response
|
||||
{
|
||||
return $this->doNew(
|
||||
$factory->create(),
|
||||
$entityManager,
|
||||
$request,
|
||||
function(Entity $entity, Form $form, Request $request) use ($fileUpload) {
|
||||
$directory = 'uploads/Factures/';
|
||||
|
||||
$fileUpload->handleForm(
|
||||
$form->get('file')->getData(),
|
||||
$directory,
|
||||
function ($filename) use ($entity, $directory) {
|
||||
$entity->setFile($directory.'/'.$filename);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[Route(path: "/admin/bill/show/{entity}", name: "admin_bill_show", methods: ['GET'])]
|
||||
public function show(Entity $entity): Response
|
||||
{
|
||||
return $this->doShow($entity);
|
||||
}
|
||||
|
||||
#[Route(path: "/admin/bill/filter", name: "admin_bill_filter", methods: ['GET'])]
|
||||
public function filter(Session $session): Response
|
||||
{
|
||||
return $this->doFilter($session);
|
||||
}
|
||||
|
||||
#[Route(path: "/admin/bill/edit/{entity}", name: "admin_bill_edit", methods: ['GET', 'POST'])]
|
||||
public function edit(
|
||||
Entity $entity,
|
||||
EntityManager $entityManager,
|
||||
BillVendorRepositoryQuery $vendorQuery,
|
||||
BillVendorFactory $vendorFactory,
|
||||
BillCategoryRepositoryQuery $categoryQuery,
|
||||
BillCategoryFactory $categoryFactory,
|
||||
Request $request
|
||||
): Response
|
||||
{
|
||||
return $this->doEdit(
|
||||
$entity,
|
||||
$entityManager,
|
||||
$request,
|
||||
function(Entity $entity, Form $form, Request $request) use (
|
||||
$entityManager,
|
||||
$vendorQuery,
|
||||
$vendorFactory,
|
||||
$categoryQuery,
|
||||
$categoryFactory
|
||||
) {
|
||||
foreach ([
|
||||
'vendor' => [$vendorQuery, $vendorFactory, true],
|
||||
'category' => [$categoryQuery, $categoryFactory, false],
|
||||
] as $key => $elements) {
|
||||
$value = $form->get($key)->getData();
|
||||
$customValue = $form->get(u('custom_'.$key)->camel())->getData();
|
||||
|
||||
if ($customValue !== null) {
|
||||
if ($elements[2]) {
|
||||
$customValue = u($customValue)->upper();
|
||||
}
|
||||
|
||||
$value = $elements[0]->create()
|
||||
->where('.label = :label')
|
||||
->setParameter(':label', $customValue)
|
||||
->findOne()
|
||||
;
|
||||
|
||||
if ($value === null) {
|
||||
$value = $elements[1]->create($customValue);
|
||||
$entityManager->create($value);
|
||||
}
|
||||
}
|
||||
|
||||
$entity->{(string) u('set_'.$key)->camel()}($value);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[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
|
||||
{
|
||||
return $this->doSort($page, $query, $entityManager, $request, $session);
|
||||
}
|
||||
|
||||
#[Route(path: "/admin/bill/batch/{page}", name: "admin_bill_batch", methods: ['POST'], requirements: ['page' => '\d+'])]
|
||||
public function batch(RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session, int $page = 1): Response
|
||||
{
|
||||
return $this->doBatch($page, $query, $entityManager, $request, $session);
|
||||
}
|
||||
|
||||
#[Route(path: "/admin/bill/delete/{entity}", name: "admin_bill_delete", methods: ['DELETE', 'POST'])]
|
||||
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
|
||||
{
|
||||
return $this->doDelete($entity, $entityManager, $request);
|
||||
}
|
||||
|
||||
protected function getConfiguration(): CrudConfiguration
|
||||
{
|
||||
return CrudConfiguration::create()
|
||||
->setPageTitle('index', 'Factures')
|
||||
->setPageTitle('edit', 'Facture #{id}')
|
||||
->setPageTitle('new', 'Nouvelle facture')
|
||||
->setPageTitle('show', 'Facture #{id}')
|
||||
|
||||
->setPageRoute('index', 'admin_bill_index')
|
||||
->setPageRoute('new', 'admin_bill_new')
|
||||
->setPageRoute('edit', 'admin_bill_edit')
|
||||
->setPageRoute('show', 'admin_bill_show')
|
||||
->setPageRoute('sort', 'admin_bill_sort')
|
||||
->setPageRoute('batch', 'admin_bill_batch')
|
||||
->setPageRoute('delete', 'admin_bill_delete')
|
||||
->setPageRoute('filter', 'admin_bill_filter')
|
||||
|
||||
->setForm('edit', Type::class, [])
|
||||
->setForm('new', Type::class)
|
||||
->setForm('filter', FilterType::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)
|
||||
|
||||
->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, [
|
||||
'property_builder' => function (EntityInterface $entity) {
|
||||
return sprintf('#%d', $entity->getId());
|
||||
},
|
||||
'sort' => ['id', '.id'],
|
||||
])
|
||||
->setField('index', 'Rérérence', Field\ButtonField::class, [
|
||||
'property' => 'reference',
|
||||
'button_tag' => 'span',
|
||||
'button_attr' => ['style' => 'user-select: all'],
|
||||
'sort' => ['reference', '.reference'],
|
||||
])
|
||||
->setField('index', 'Fournisseur', Field\ButtonField::class, [
|
||||
'property' => 'vendor',
|
||||
'button_tag' => 'span',
|
||||
'button_attr' => ['style' => 'user-select: all'],
|
||||
'sort' => ['vendor', '.vendor'],
|
||||
])
|
||||
->setField('index', 'Catégorie', Field\ButtonField::class, [
|
||||
'property' => 'category',
|
||||
'button_tag' => 'span',
|
||||
'button_attr' => ['style' => 'user-select: all'],
|
||||
'sort' => ['category', '.category'],
|
||||
])
|
||||
->setField('index', 'Date', Field\DateField::class, [
|
||||
'property' => 'date',
|
||||
'sort' => ['date', '.date'],
|
||||
'format' => 'd/m/Y',
|
||||
])
|
||||
->setField('index', 'Date limite de paiement', Field\DateField::class, [
|
||||
'property' => 'paymentDeadlineDate',
|
||||
'sort' => ['paymentDeadlineDate', '.paymentDeadlineDate'],
|
||||
'format' => 'd/m/Y',
|
||||
])
|
||||
->setField('index', 'Montant TTC', Field\TextField::class, [
|
||||
'property_builder' => function(EntityInterface $entity) {
|
||||
if ($entity->getAmountTtc() !== null) {
|
||||
return sprintf('<span style="user-select: all">%01.2f</span> €', $entity->getAmountTtc());
|
||||
}
|
||||
},
|
||||
'raw' => true,
|
||||
'sort' => ['amountTtc', '.amountTtc'],
|
||||
'attr' => ['class' => 'text-right'],
|
||||
])
|
||||
->setField('index', 'Montant HT', Field\TextField::class, [
|
||||
'property_builder' => function(EntityInterface $entity) {
|
||||
if ($entity->getAmountHt() !== null) {
|
||||
return sprintf('<span style="user-select: all">%01.2f</span> €', $entity->getAmountHt());
|
||||
}
|
||||
},
|
||||
'raw' => true,
|
||||
'sort' => ['amountHt', '.amountHt'],
|
||||
'attr' => ['class' => 'text-right'],
|
||||
])
|
||||
->setField('index', 'Statut', Field\ButtonField::class, [
|
||||
'property' => 'humanStatus',
|
||||
'button_attr_builder' => function(EntityInterface $entity) {
|
||||
$classes = [
|
||||
0 => 'btn btn-sm btn-light',
|
||||
1 => 'btn btn-sm btn-warning',
|
||||
2 => 'btn btn-sm btn-success',
|
||||
];
|
||||
|
||||
return ['class' => $classes[$entity->getStatus()]];
|
||||
},
|
||||
'sort' => ['status', '.status'],
|
||||
])
|
||||
|
||||
// ->setBatchAction('index', 'delete', 'Delete', function(EntityInterface $entity, EntityManager $manager) {
|
||||
// $manager->delete($entity);
|
||||
// })
|
||||
;
|
||||
}
|
||||
|
||||
protected function getSection(): string
|
||||
{
|
||||
return 'bill';
|
||||
}
|
||||
}
|
129
src/Controller/BillCategoryAdminController.php
Normal file
129
src/Controller/BillCategoryAdminController.php
Normal file
|
@ -0,0 +1,129 @@
|
|||
<?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\Manager\EntityManager;
|
||||
use App\Entity\BillCategory as Entity;
|
||||
use App\Factory\BillCategoryFactory as Factory;
|
||||
use App\Form\BillCategoryType as Type;
|
||||
use App\Repository\BillCategoryRepositoryQuery as RepositoryQuery;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
class BillCategoryAdminController extends CrudController
|
||||
{
|
||||
#[Route(path: '/admin/user/edit/{entity}', name: 'admin_user_edit', methods: ['GET', 'POST'])]
|
||||
|
||||
#[Route(path: "/admin/bill_category/{page}", name: "admin_bill_category_index", methods: ['GET'], requirements: ['page' => '\d+'])]
|
||||
public function index(RepositoryQuery $query, Request $request, Session $session, int $page = 1): Response
|
||||
{
|
||||
return $this->doIndex($page, $query, $request, $session);
|
||||
}
|
||||
|
||||
#[Route(path: "/admin/bill_category/new", name: "admin_bill_category_new", methods: ['GET', 'POST'])]
|
||||
public function new(Factory $factory, EntityManager $entityManager, Request $request): Response
|
||||
{
|
||||
return $this->doNew($factory->create(), $entityManager, $request);
|
||||
}
|
||||
|
||||
#[Route(path: "/admin/bill_category/show/{entity}", name: "admin_bill_category_show", methods: ['GET'])]
|
||||
public function show(Entity $entity): Response
|
||||
{
|
||||
return $this->doShow($entity);
|
||||
}
|
||||
|
||||
#[Route(path: "/admin/bill_category/filter", name: "admin_bill_category_filter", methods: ['GET'])]
|
||||
public function filter(Session $session): Response
|
||||
{
|
||||
return $this->doFilter($session);
|
||||
}
|
||||
|
||||
#[Route(path: "/admin/bill_category/edit/{entity}", name: "admin_bill_category_edit", methods: ['GET', 'POST'])]
|
||||
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
|
||||
{
|
||||
return $this->doEdit($entity, $entityManager, $request);
|
||||
}
|
||||
|
||||
#[Route(path: "/admin/bill_category/sort/{page}", name: "admin_bill_category_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(path: "/admin/bill_category/batch/{page}", name: "admin_bill_category_batch", methods: ['POST'], requirements: ['page' => '\d+'])]
|
||||
public function batch(RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session, int $page = 1): Response
|
||||
{
|
||||
return $this->doBatch($page, $query, $entityManager, $request, $session);
|
||||
}
|
||||
|
||||
#[Route(path: "/admin/bill_category/delete/{entity}", name: "admin_bill_category_delete", methods: ['DELETE', 'POST'])]
|
||||
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
|
||||
{
|
||||
return $this->doDelete($entity, $entityManager, $request);
|
||||
}
|
||||
|
||||
protected function getConfiguration(): CrudConfiguration
|
||||
{
|
||||
return CrudConfiguration::create()
|
||||
->setPageTitle('index', 'Catégories des factures')
|
||||
->setPageTitle('edit', '{label}')
|
||||
->setPageTitle('new', 'Nouvelle catégorie')
|
||||
->setPageTitle('show', '{label}')
|
||||
|
||||
->setPageRoute('index', 'admin_bill_category_index')
|
||||
->setPageRoute('new', 'admin_bill_category_new')
|
||||
->setPageRoute('edit', 'admin_bill_category_edit')
|
||||
->setPageRoute('show', 'admin_bill_category_show')
|
||||
->setPageRoute('sort', 'admin_bill_category_sort')
|
||||
->setPageRoute('batch', 'admin_bill_category_batch')
|
||||
->setPageRoute('delete', 'admin_bill_category_delete')
|
||||
->setPageRoute('filter', 'admin_bill_category_filter')
|
||||
|
||||
->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', 'label', 'ASC')
|
||||
->setField('index', 'Libellé', Field\TextField::class, [
|
||||
'property' => 'label',
|
||||
'sort' => ['label', '.label'],
|
||||
])
|
||||
|
||||
// ->setField('index', 'Foo', Field\TextField::class, [
|
||||
// 'property' => 'foo',
|
||||
// ])
|
||||
|
||||
// ->setBatchAction('index', 'delete', 'Delete', function(EntityInterface $entity, EntityManager $manager) {
|
||||
// $manager->delete($entity);
|
||||
// })
|
||||
;
|
||||
}
|
||||
|
||||
protected function getSection(): string
|
||||
{
|
||||
return 'bill_category';
|
||||
}
|
||||
}
|
130
src/Controller/BillVendorAdminController.php
Normal file
130
src/Controller/BillVendorAdminController.php
Normal file
|
@ -0,0 +1,130 @@
|
|||
<?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\Manager\EntityManager;
|
||||
use App\Entity\BillVendor as Entity;
|
||||
use App\Factory\BillVendorFactory as Factory;
|
||||
use App\Form\BillVendorType as Type;
|
||||
use App\Form\BillVendorFilterType as FilterType;
|
||||
use App\Repository\BillVendorRepositoryQuery as RepositoryQuery;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
class BillVendorAdminController extends CrudController
|
||||
{
|
||||
#[Route(path: '/admin/user/edit/{entity}', name: 'admin_user_edit', methods: ['GET', 'POST'])]
|
||||
|
||||
#[Route(path: "/admin/bill_vendor/{page}", name: "admin_bill_vendor_index", methods: ['GET'], requirements: ['page' => '\d+'])]
|
||||
public function index(RepositoryQuery $query, Request $request, Session $session, int $page = 1): Response
|
||||
{
|
||||
return $this->doIndex($page, $query, $request, $session);
|
||||
}
|
||||
|
||||
#[Route(path: "/admin/bill_vendor/new", name: "admin_bill_vendor_new", methods: ['GET', 'POST'])]
|
||||
public function new(Factory $factory, EntityManager $entityManager, Request $request): Response
|
||||
{
|
||||
return $this->doNew($factory->create(), $entityManager, $request);
|
||||
}
|
||||
|
||||
#[Route(path: "/admin/bill_vendor/show/{entity}", name: "admin_bill_vendor_show", methods: ['GET'])]
|
||||
public function show(Entity $entity): Response
|
||||
{
|
||||
return $this->doShow($entity);
|
||||
}
|
||||
|
||||
#[Route(path: "/admin/bill_vendor/filter", name: "admin_bill_vendor_filter", methods: ['GET'])]
|
||||
public function filter(Session $session): Response
|
||||
{
|
||||
return $this->doFilter($session);
|
||||
}
|
||||
|
||||
#[Route(path: "/admin/bill_vendor/edit/{entity}", name: "admin_bill_vendor_edit", methods: ['GET', 'POST'])]
|
||||
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
|
||||
{
|
||||
return $this->doEdit($entity, $entityManager, $request);
|
||||
}
|
||||
|
||||
#[Route(path: "/admin/bill_vendor/sort/{page}", name: "admin_bill_vendor_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(path: "/admin/bill_vendor/batch/{page}", name: "admin_bill_vendor_batch", methods: ['POST'], requirements: ['page' => '\d+'])]
|
||||
public function batch(RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session, int $page = 1): Response
|
||||
{
|
||||
return $this->doBatch($page, $query, $entityManager, $request, $session);
|
||||
}
|
||||
|
||||
#[Route(path: "/admin/bill_vendor/delete/{entity}", name: "admin_bill_vendor_delete", methods: ['DELETE', 'POST'])]
|
||||
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
|
||||
{
|
||||
return $this->doDelete($entity, $entityManager, $request);
|
||||
}
|
||||
|
||||
protected function getConfiguration(): CrudConfiguration
|
||||
{
|
||||
return CrudConfiguration::create()
|
||||
->setPageTitle('index', 'Fournisseurs')
|
||||
->setPageTitle('edit', '{label}')
|
||||
->setPageTitle('new', 'Nouveau fournisseur')
|
||||
->setPageTitle('show', '{label}')
|
||||
|
||||
->setPageRoute('index', 'admin_bill_vendor_index')
|
||||
->setPageRoute('new', 'admin_bill_vendor_new')
|
||||
->setPageRoute('edit', 'admin_bill_vendor_edit')
|
||||
->setPageRoute('show', 'admin_bill_vendor_show')
|
||||
->setPageRoute('sort', 'admin_bill_vendor_sort')
|
||||
->setPageRoute('batch', 'admin_bill_vendor_batch')
|
||||
->setPageRoute('delete', 'admin_bill_vendor_delete')
|
||||
->setPageRoute('filter', 'admin_bill_vendor_filter')
|
||||
|
||||
->setForm('edit', Type::class, [])
|
||||
->setForm('new', Type::class)
|
||||
->setForm('filter', FilterType::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', 'label', 'ASC')
|
||||
->setField('index', 'Libellé', Field\TextField::class, [
|
||||
'property' => 'label',
|
||||
'sort' => ['label', '.label'],
|
||||
])
|
||||
|
||||
// ->setField('index', 'Foo', Field\TextField::class, [
|
||||
// 'property' => 'foo',
|
||||
// ])
|
||||
|
||||
// ->setBatchAction('index', 'delete', 'Delete', function(EntityInterface $entity, EntityManager $manager) {
|
||||
// $manager->delete($entity);
|
||||
// })
|
||||
;
|
||||
}
|
||||
|
||||
protected function getSection(): string
|
||||
{
|
||||
return 'bill_vendor';
|
||||
}
|
||||
}
|
199
src/Entity/Bill.php
Normal file
199
src/Entity/Bill.php
Normal file
|
@ -0,0 +1,199 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\BillRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use App\Core\Entity\EntityInterface;
|
||||
use App\Core\Doctrine\Timestampable;
|
||||
|
||||
#[ORM\Entity(repositoryClass: BillRepository::class)]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
class Bill implements EntityInterface
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $reference = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?float $amountTtc = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?float $amountHt = null;
|
||||
|
||||
#[ORM\Column(options: ["default" => 0])]
|
||||
private ?int $status = 0;
|
||||
|
||||
#[ORM\Column(type: Types::DATE_MUTABLE, nullable: true)]
|
||||
private ?\DateTimeInterface $date = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATE_MUTABLE, nullable: true)]
|
||||
private ?\DateTimeInterface $paymentDeadlineDate = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $file = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'bills')]
|
||||
#[ORM\JoinColumn(onDelete: 'SET NULL')]
|
||||
private ?BillVendor $vendor = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'bills')]
|
||||
#[ORM\JoinColumn(onDelete: 'SET NULL')]
|
||||
private ?BillCategory $category = null;
|
||||
|
||||
use Timestampable;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getReference(): ?string
|
||||
{
|
||||
return $this->reference;
|
||||
}
|
||||
|
||||
public function setReference(?string $reference): self
|
||||
{
|
||||
$this->reference = $reference;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAmountTtc(): ?float
|
||||
{
|
||||
return $this->amountTtc;
|
||||
}
|
||||
|
||||
public function setAmountTtc(?float $amountTtc): self
|
||||
{
|
||||
$this->amountTtc = $amountTtc;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAmountHt(): ?float
|
||||
{
|
||||
return $this->amountHt;
|
||||
}
|
||||
|
||||
public function setAmountHt(?float $amountHt): self
|
||||
{
|
||||
$this->amountHt = $amountHt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStatus(): ?int
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function setStatus(int $status): self
|
||||
{
|
||||
$this->status = $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHumanStatus(): string
|
||||
{
|
||||
return BillPeer::value($this->getStatus());
|
||||
}
|
||||
|
||||
public function getDate(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
public function setDate(\DateTimeInterface $date): self
|
||||
{
|
||||
$this->date = $date;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPaymentDeadlineDate(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->paymentDeadlineDate;
|
||||
}
|
||||
|
||||
public function setPaymentDeadlineDate(?\DateTimeInterface $paymentDeadlineDate): self
|
||||
{
|
||||
$this->paymentDeadlineDate = $paymentDeadlineDate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFile(): ?string
|
||||
{
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
public function setFile(?string $file): self
|
||||
{
|
||||
$this->file = $file;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getVendor(): ?BillVendor
|
||||
{
|
||||
return $this->vendor;
|
||||
}
|
||||
|
||||
public function setVendor(?BillVendor $vendor): self
|
||||
{
|
||||
$this->vendor = $vendor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCategory(): ?BillCategory
|
||||
{
|
||||
return $this->category;
|
||||
}
|
||||
|
||||
public function setCategory(?BillCategory $category): self
|
||||
{
|
||||
$this->category = $category;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isPdf(): bool
|
||||
{
|
||||
return (bool) preg_match('/\.pdf/', $this->getFile());
|
||||
}
|
||||
|
||||
public function getRemoteFile(): ?string
|
||||
{
|
||||
if (!$this->getFile()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$this->getDate()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$this->getCategory()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$this->getVendor()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return implode('/', [
|
||||
$this->getDate()->format('Y'),
|
||||
$this->getCategory()->getLabel(),
|
||||
$this->getVendor()->getLabel(),
|
||||
basename($this->getFile()),
|
||||
]);
|
||||
}
|
||||
}
|
85
src/Entity/BillCategory.php
Normal file
85
src/Entity/BillCategory.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\BillCategoryRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use App\Core\Entity\EntityInterface;
|
||||
use App\Core\Doctrine\Timestampable;
|
||||
|
||||
#[ORM\Entity(repositoryClass: BillCategoryRepository::class)]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
class BillCategory implements EntityInterface
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $label = null;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'category', targetEntity: Bill::class)]
|
||||
private Collection $bills;
|
||||
|
||||
use Timestampable;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->bills = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return (string) $this->getLabel();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function setLabel(string $label): self
|
||||
{
|
||||
$this->label = $label;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Bill>
|
||||
*/
|
||||
public function getBills(): Collection
|
||||
{
|
||||
return $this->bills;
|
||||
}
|
||||
|
||||
public function addBill(Bill $bill): self
|
||||
{
|
||||
if (!$this->bills->contains($bill)) {
|
||||
$this->bills->add($bill);
|
||||
$bill->setCategory($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeBill(Bill $bill): self
|
||||
{
|
||||
if ($this->bills->removeElement($bill)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($bill->getCategory() === $this) {
|
||||
$bill->setCategory(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
33
src/Entity/BillPeer.php
Normal file
33
src/Entity/BillPeer.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
class BillPeer
|
||||
{
|
||||
public const STATUS = [
|
||||
0 => 'Brouillon',
|
||||
1 => 'En attente de paiement',
|
||||
2 => 'Payée',
|
||||
];
|
||||
|
||||
public static function all(): array
|
||||
{
|
||||
return self::STATUS;
|
||||
}
|
||||
|
||||
public static function value(int $key): string
|
||||
{
|
||||
return self::STATUS[$key];
|
||||
}
|
||||
|
||||
public static function choices(): array
|
||||
{
|
||||
$data = [];
|
||||
|
||||
foreach (self::STATUS as $k => $v) {
|
||||
$data[$v] = $k;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
85
src/Entity/BillVendor.php
Normal file
85
src/Entity/BillVendor.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\BillVendorRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use App\Core\Entity\EntityInterface;
|
||||
use App\Core\Doctrine\Timestampable;
|
||||
|
||||
#[ORM\Entity(repositoryClass: BillVendorRepository::class)]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
class BillVendor implements EntityInterface
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $label = null;
|
||||
|
||||
#[ORM\OneToMany(mappedBy: 'vendor', targetEntity: Bill::class)]
|
||||
private Collection $bills;
|
||||
|
||||
use Timestampable;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->bills = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return (string) $this->getLabel();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function setLabel(string $label): self
|
||||
{
|
||||
$this->label = $label;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Bill>
|
||||
*/
|
||||
public function getBills(): Collection
|
||||
{
|
||||
return $this->bills;
|
||||
}
|
||||
|
||||
public function addBill(Bill $bill): self
|
||||
{
|
||||
if (!$this->bills->contains($bill)) {
|
||||
$this->bills->add($bill);
|
||||
$bill->setVendor($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeBill(Bill $bill): self
|
||||
{
|
||||
if ($this->bills->removeElement($bill)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($bill->getVendor() === $this) {
|
||||
$bill->setVendor(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
126
src/EventSubscriber/BillEventSubscriber.php
Normal file
126
src/EventSubscriber/BillEventSubscriber.php
Normal 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());
|
||||
}
|
||||
}
|
17
src/Factory/BillCategoryFactory.php
Normal file
17
src/Factory/BillCategoryFactory.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace App\Factory;
|
||||
|
||||
use App\Core\Factory\FactoryInterface;
|
||||
use App\Entity\BillCategory as Entity;
|
||||
|
||||
class BillCategoryFactory implements FactoryInterface
|
||||
{
|
||||
public function create(?string $label = null): Entity
|
||||
{
|
||||
$entity = new Entity();
|
||||
$entity->setLabel((string) $label);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
14
src/Factory/BillFactory.php
Normal file
14
src/Factory/BillFactory.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace App\Factory;
|
||||
|
||||
use App\Core\Factory\FactoryInterface;
|
||||
use App\Entity\Bill as Entity;
|
||||
|
||||
class BillFactory implements FactoryInterface
|
||||
{
|
||||
public function create(): Entity
|
||||
{
|
||||
return new Entity();
|
||||
}
|
||||
}
|
17
src/Factory/BillVendorFactory.php
Normal file
17
src/Factory/BillVendorFactory.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace App\Factory;
|
||||
|
||||
use App\Core\Factory\FactoryInterface;
|
||||
use App\Entity\BillVendor as Entity;
|
||||
|
||||
class BillVendorFactory implements FactoryInterface
|
||||
{
|
||||
public function create(?string $label = null): Entity
|
||||
{
|
||||
$entity = new Entity();
|
||||
$entity->setLabel((string) $label);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
54
src/Form/AmountRangeType.php
Normal file
54
src/Form/AmountRangeType.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?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;
|
||||
|
||||
class AmountRangeType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('min', NumberType::class, [
|
||||
'label' => 'Min',
|
||||
'html5' => true,
|
||||
'scale' => 2,
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'step' => 0.01,
|
||||
],
|
||||
'row_attr' => [
|
||||
'class' => 'col-md-6 pr-1',
|
||||
],
|
||||
'label_attr' => [
|
||||
'class' => 'font-weight-normal',
|
||||
],
|
||||
])
|
||||
->add('max', NumberType::class, [
|
||||
'label' => 'Max',
|
||||
'html5' => true,
|
||||
'required' => false,
|
||||
'scale' => 2,
|
||||
'attr' => [
|
||||
'step' => 0.01,
|
||||
],
|
||||
'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,
|
||||
]);
|
||||
}
|
||||
}
|
25
src/Form/BillCategoryType.php
Normal file
25
src/Form/BillCategoryType.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\BillCategory;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class BillCategoryType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('label')
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => BillCategory::class,
|
||||
]);
|
||||
}
|
||||
}
|
87
src/Form/BillFilterType.php
Normal file
87
src/Form/BillFilterType.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\BillCategory;
|
||||
use App\Entity\BillPeer;
|
||||
use App\Entity\BillVendor;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
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
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('reference', null, [
|
||||
'label' => 'Numéro de facture',
|
||||
'required' => false,
|
||||
])
|
||||
->add('vendor', null, [
|
||||
'label' => 'Fournisseur',
|
||||
'required' => false,
|
||||
])
|
||||
->add('vendor', EntityType::class, [
|
||||
'label' => 'Fournisseur',
|
||||
'required' => false,
|
||||
'class' => BillVendor::class,
|
||||
'query_builder' => function (EntityRepository $er) {
|
||||
return $er->createQueryBuilder('v')
|
||||
->orderBy('v.label', 'ASC')
|
||||
;
|
||||
},
|
||||
'attr' => [
|
||||
'data-jschoice' => '',
|
||||
],
|
||||
])
|
||||
->add('category', EntityType::class, [
|
||||
'label' => 'Catégorie',
|
||||
'required' => false,
|
||||
'class' => BillCategory::class,
|
||||
'query_builder' => function (EntityRepository $er) {
|
||||
return $er->createQueryBuilder('v')
|
||||
->orderBy('v.label', 'ASC')
|
||||
;
|
||||
},
|
||||
'attr' => [
|
||||
'data-jschoice' => '',
|
||||
],
|
||||
])
|
||||
->add('dateRange', DateRangeType::class, [
|
||||
'label' => 'Date',
|
||||
'attr' => [
|
||||
'class' => 'row',
|
||||
],
|
||||
])
|
||||
->add('amountTtcRange', AmountRangeType::class, [
|
||||
'label' => 'Montant TTC',
|
||||
'attr' => [
|
||||
'class' => 'row',
|
||||
],
|
||||
])
|
||||
->add('amountHtRange', AmountRangeType::class, [
|
||||
'label' => 'Montant HT',
|
||||
'attr' => [
|
||||
'class' => 'row',
|
||||
],
|
||||
])
|
||||
->add('status', ChoiceType::class, [
|
||||
'choices' => BillPeer::choices(),
|
||||
'required' => false,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => null,
|
||||
'csrf_protection' => false,
|
||||
]);
|
||||
}
|
||||
}
|
140
src/Form/BillType.php
Normal file
140
src/Form/BillType.php
Normal file
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Bill;
|
||||
use App\Entity\BillVendor;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\File;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
||||
use App\Entity\BillPeer;
|
||||
use App\Entity\BillCategory;
|
||||
|
||||
class BillType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
if (null === $builder->getData()->getId()) {
|
||||
$builder->add('file', FileType::class, [
|
||||
'label' => 'Fichier',
|
||||
'constraints' => [
|
||||
new File([
|
||||
'mimeTypes' => [
|
||||
'application/pdf',
|
||||
'application/x-pdf',
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
],
|
||||
'mimeTypesMessage' => 'Le fichier doit être au format PDF ou une image.',
|
||||
]),
|
||||
],
|
||||
]);
|
||||
} else {
|
||||
$builder
|
||||
->add('reference', null, [
|
||||
'label' => 'Numéro de facture',
|
||||
'constraints' => [
|
||||
new NotBlank(),
|
||||
],
|
||||
])
|
||||
->add('vendor', EntityType::class, [
|
||||
'label' => 'Fournisseur',
|
||||
'required' => false,
|
||||
'class' => BillVendor::class,
|
||||
'query_builder' => function (EntityRepository $er) {
|
||||
return $er->createQueryBuilder('v')
|
||||
->orderBy('v.label', 'ASC')
|
||||
;
|
||||
},
|
||||
'attr' => [
|
||||
'data-jschoice' => '',
|
||||
],
|
||||
])
|
||||
->add('customVendor', null, [
|
||||
'label' => 'Ajouter un fournisseur',
|
||||
'label_attr' => [
|
||||
'class' => 'font-weight-normal',
|
||||
],
|
||||
'required' => false,
|
||||
'mapped' => false,
|
||||
])
|
||||
->add('category', EntityType::class, [
|
||||
'label' => 'Catégorie',
|
||||
'required' => false,
|
||||
'class' => BillCategory::class,
|
||||
'label_attr' => [
|
||||
'class' => 'mt-3',
|
||||
],
|
||||
'query_builder' => function (EntityRepository $er) {
|
||||
return $er->createQueryBuilder('v')
|
||||
->orderBy('v.label', 'ASC')
|
||||
;
|
||||
},
|
||||
'attr' => [
|
||||
'data-jschoice' => '',
|
||||
],
|
||||
])
|
||||
->add('customCategory', null, [
|
||||
'label' => 'Ajouter une catégorie',
|
||||
'label_attr' => [
|
||||
'class' => 'font-weight-normal',
|
||||
],
|
||||
'required' => false,
|
||||
'mapped' => false,
|
||||
])
|
||||
->add('date', null, [
|
||||
'html5' => true,
|
||||
'widget' => 'single_text',
|
||||
'label_attr' => [
|
||||
'class' => 'mt-3',
|
||||
],
|
||||
])
|
||||
->add('paymentDeadlineDate', null, [
|
||||
'label' => 'Date limite de paiement',
|
||||
'html5' => true,
|
||||
'widget' => 'single_text',
|
||||
])
|
||||
->add('amountTtc', NumberType::class, [
|
||||
'label' => 'Montant TTC',
|
||||
'html5' => true,
|
||||
'scale' => 2,
|
||||
'label_attr' => [
|
||||
'class' => 'mt-3',
|
||||
],
|
||||
'attr' => [
|
||||
'step' => 0.01,
|
||||
],
|
||||
])
|
||||
->add('amountHt', NumberType::class, [
|
||||
'label' => 'Montant HT',
|
||||
'scale' => 2,
|
||||
'html5' => true,
|
||||
'attr' => [
|
||||
'step' => 0.01,
|
||||
],
|
||||
])
|
||||
->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',
|
||||
],
|
||||
])
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Bill::class,
|
||||
]);
|
||||
}
|
||||
}
|
27
src/Form/BillVendorFilterType.php
Normal file
27
src/Form/BillVendorFilterType.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\BillVendor;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
|
||||
class BillVendorFilterType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add('label', null, [
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => null,
|
||||
'csrf_protection' => false,
|
||||
]);
|
||||
}
|
||||
}
|
28
src/Form/BillVendorType.php
Normal file
28
src/Form/BillVendorType.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\BillVendor;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
|
||||
class BillVendorType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add('label', null, [
|
||||
'constraints' => [
|
||||
new NotBlank(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => BillVendor::class,
|
||||
]);
|
||||
}
|
||||
}
|
49
src/Form/DateRangeType.php
Normal file
49
src/Form/DateRangeType.php
Normal 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,
|
||||
]);
|
||||
}
|
||||
}
|
66
src/Repository/BillCategoryRepository.php
Normal file
66
src/Repository/BillCategoryRepository.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\BillCategory;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<BillCategory>
|
||||
*
|
||||
* @method BillCategory|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method BillCategory|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method BillCategory[] findAll()
|
||||
* @method BillCategory[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class BillCategoryRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, BillCategory::class);
|
||||
}
|
||||
|
||||
public function save(BillCategory $entity, bool $flush = false): void
|
||||
{
|
||||
$this->getEntityManager()->persist($entity);
|
||||
|
||||
if ($flush) {
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
|
||||
public function remove(BillCategory $entity, bool $flush = false): void
|
||||
{
|
||||
$this->getEntityManager()->remove($entity);
|
||||
|
||||
if ($flush) {
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return BillCategory[] Returns an array of BillCategory objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('b')
|
||||
// ->andWhere('b.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('b.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?BillCategory
|
||||
// {
|
||||
// return $this->createQueryBuilder('b')
|
||||
// ->andWhere('b.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
15
src/Repository/BillCategoryRepositoryQuery.php
Normal file
15
src/Repository/BillCategoryRepositoryQuery.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Core\Repository\RepositoryQuery;
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
use App\Repository\BillCategoryRepository as Repository;
|
||||
|
||||
class BillCategoryRepositoryQuery extends RepositoryQuery
|
||||
{
|
||||
public function __construct(Repository $repository, PaginatorInterface $paginator)
|
||||
{
|
||||
parent::__construct($repository, 'b', $paginator);
|
||||
}
|
||||
}
|
66
src/Repository/BillRepository.php
Normal file
66
src/Repository/BillRepository.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Bill;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<Bill>
|
||||
*
|
||||
* @method Bill|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method Bill|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method Bill[] findAll()
|
||||
* @method Bill[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class BillRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Bill::class);
|
||||
}
|
||||
|
||||
public function save(Bill $entity, bool $flush = false): void
|
||||
{
|
||||
$this->getEntityManager()->persist($entity);
|
||||
|
||||
if ($flush) {
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
|
||||
public function remove(Bill $entity, bool $flush = false): void
|
||||
{
|
||||
$this->getEntityManager()->remove($entity);
|
||||
|
||||
if ($flush) {
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return Bill[] Returns an array of Bill objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('b')
|
||||
// ->andWhere('b.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('b.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?Bill
|
||||
// {
|
||||
// return $this->createQueryBuilder('b')
|
||||
// ->andWhere('b.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
69
src/Repository/BillRepositoryQuery.php
Normal file
69
src/Repository/BillRepositoryQuery.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Core\Repository\RepositoryQuery;
|
||||
use App\Repository\BillRepository as Repository;
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
|
||||
class BillRepositoryQuery extends RepositoryQuery
|
||||
{
|
||||
public function __construct(Repository $repository, PaginatorInterface $paginator)
|
||||
{
|
||||
parent::__construct($repository, 'b', $paginator);
|
||||
}
|
||||
|
||||
protected function filterHandler(string $name, $value)
|
||||
{
|
||||
if ('vendor' === $name && null !== $value) {
|
||||
$this
|
||||
->andWhere('.vendor = :vendor')
|
||||
->setParameter('vendor', $value)
|
||||
;
|
||||
} elseif ('amountTtcRange' === $name) {
|
||||
if (null !== $value['min']) {
|
||||
$this
|
||||
->andWhere('.amountTtc >= :amountTtcMin')
|
||||
->setParameter('amountTtcMin', $value['min'])
|
||||
;
|
||||
}
|
||||
|
||||
if (null !== $value['max']) {
|
||||
$this
|
||||
->andWhere('.amountTtc <= :amountTtcMax')
|
||||
->setParameter('amountTtcMax', $value['max'])
|
||||
;
|
||||
}
|
||||
} elseif ('amountHtRange' === $name) {
|
||||
if (null !== $value['min']) {
|
||||
$this
|
||||
->andWhere('.amountHt >= :amountHtMin')
|
||||
->setParameter('amountHtMin', $value['min'])
|
||||
;
|
||||
}
|
||||
|
||||
if (null !== $value['max']) {
|
||||
$this
|
||||
->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'])
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
66
src/Repository/BillVendorRepository.php
Normal file
66
src/Repository/BillVendorRepository.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\BillVendor;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<BillVendor>
|
||||
*
|
||||
* @method BillVendor|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method BillVendor|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method BillVendor[] findAll()
|
||||
* @method BillVendor[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class BillVendorRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, BillVendor::class);
|
||||
}
|
||||
|
||||
public function save(BillVendor $entity, bool $flush = false): void
|
||||
{
|
||||
$this->getEntityManager()->persist($entity);
|
||||
|
||||
if ($flush) {
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
|
||||
public function remove(BillVendor $entity, bool $flush = false): void
|
||||
{
|
||||
$this->getEntityManager()->remove($entity);
|
||||
|
||||
if ($flush) {
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return BillVendor[] Returns an array of BillVendor objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('b')
|
||||
// ->andWhere('b.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('b.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?BillVendor
|
||||
// {
|
||||
// return $this->createQueryBuilder('b')
|
||||
// ->andWhere('b.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
15
src/Repository/BillVendorRepositoryQuery.php
Normal file
15
src/Repository/BillVendorRepositoryQuery.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Core\Repository\RepositoryQuery;
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
use App\Repository\BillVendorRepository as Repository;
|
||||
|
||||
class BillVendorRepositoryQuery extends RepositoryQuery
|
||||
{
|
||||
public function __construct(Repository $repository, PaginatorInterface $paginator)
|
||||
{
|
||||
parent::__construct($repository, 'b', $paginator);
|
||||
}
|
||||
}
|
46
templates/admin/bill/_form.html.twig
Normal file
46
templates/admin/bill/_form.html.twig
Normal file
|
@ -0,0 +1,46 @@
|
|||
{% if entity.id %}
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
{% for item in form %}
|
||||
{% if configuration.isI18n and item.vars.name == 'translations' %}
|
||||
{% include(configuration.view('form_translations', '@Core/admin/crud/_form_translations.html.twig')) with {form: item} %}
|
||||
{% else %}
|
||||
{% include(configuration.view('form_widget', '@Core/admin/crud/_form_widget.html.twig')) with {form: item} %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="col-md-8 pl-5">
|
||||
{% if entity.isPdf %}
|
||||
<div data-pdf="{{ asset(entity.file) }}" style="height: calc(100vh / 3 * 2)" id="pdf"></div>
|
||||
{% else %}
|
||||
<div class="form-filepicker">
|
||||
<div class="card">
|
||||
<div class="card-img-top bg-tiles text-center">
|
||||
<a href="{{ asset(entity.file) }}" target="_blank">
|
||||
<img src="{{ asset(entity.file) }}" class="img-fluid">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="alert alert-info">
|
||||
Seuls les fichiers au format PDF et les images sont autorisés.
|
||||
</div>
|
||||
|
||||
<p>Après avoir déposé le fichier, merci de bien vouloir remplir toutes les informations.</p>
|
||||
|
||||
{% for item in form %}
|
||||
{% if configuration.isI18n and item.vars.name == 'translations' %}
|
||||
{% include(configuration.view('form_translations', '@Core/admin/crud/_form_translations.html.twig')) with {form: item} %}
|
||||
{% else %}
|
||||
{% include(configuration.view('form_widget', '@Core/admin/crud/_form_widget.html.twig')) with {form: item} %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
42
templates/admin/bill/_show.html.twig
Normal file
42
templates/admin/bill/_show.html.twig
Normal file
|
@ -0,0 +1,42 @@
|
|||
{% set values = {
|
||||
'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,
|
||||
} %}
|
||||
<div class="row">
|
||||
<div class="col-md-12 p-3">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="list-group">
|
||||
{% for label, value in values %}
|
||||
<div class="list-group-item">
|
||||
<div class="font-weight-bold">{{ label|trans }}</div>
|
||||
<div>{{ value }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8 pl-3">
|
||||
{% if entity.isPdf %}
|
||||
<div data-pdf="{{ asset(entity.file) }}" style="height: calc(100vh / 3 * 2)" id="pdf"></div>
|
||||
{% else %}
|
||||
<div class="form-filepicker">
|
||||
<div class="card">
|
||||
<div class="card-img-top bg-tiles text-center">
|
||||
<a href="{{ asset(entity.file) }}" target="_blank">
|
||||
<img src="{{ asset(entity.file) }}" class="img-fluid">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
15
templates/admin/bill/edit.html.twig
Normal file
15
templates/admin/bill/edit.html.twig
Normal 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 %}
|
7
templates/admin/bill/index.html.twig
Normal file
7
templates/admin/bill/index.html.twig
Normal 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 %}
|
|
@ -66,6 +66,29 @@
|
|||
{{ include('@Core/admin/module/_menu_section.html.twig', {label: 'Comptabilité'}) }}
|
||||
|
||||
<ul class="nav flex-column">
|
||||
{% if is_granted('ROLE_MANAGER') or is_granted('ROLE_TREASURER') %}
|
||||
{{ include('@Core/admin/module/_menu_item.html.twig', {
|
||||
id: 'bill',
|
||||
label: 'Factures',
|
||||
route: path('admin_bill_index'),
|
||||
icon: 'fa fa-file'
|
||||
}) }}
|
||||
|
||||
{{ include('@Core/admin/module/_menu_item.html.twig', {
|
||||
id: 'bill_vendor',
|
||||
label: 'Fournisseurs',
|
||||
route: path('admin_bill_vendor_index'),
|
||||
icon: 'fa fa-store'
|
||||
}) }}
|
||||
|
||||
{{ include('@Core/admin/module/_menu_item.html.twig', {
|
||||
id: 'bill_category',
|
||||
label: 'Catégories',
|
||||
route: path('admin_bill_category_index'),
|
||||
icon: 'fa fa-folder'
|
||||
}) }}
|
||||
{% endif %}
|
||||
|
||||
{{ include('@Core/admin/module/_menu_item.html.twig', {
|
||||
id: 'expense_report',
|
||||
label: 'Notes de frais',
|
||||
|
|
17
templates/mail/bill/status_updated.html.twig
Normal file
17
templates/mail/bill/status_updated.html.twig
Normal 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 & Cie" width="50">
|
||||
</p>
|
||||
|
||||
<p style="text-align: center; font-size: 20px">Le statut de la <a href="{{ show_url }}">facture #{{ entity.id }}</a> a été modifié ☺️</p>
|
||||
|
||||
<p style="text-align: center; font-size: 20px">Nouveau statut : <strong>{{ entity.humanStatus }}</strong></p>
|
||||
{% endblock %}
|
|
@ -6429,6 +6429,11 @@ path-type@^2.0.0:
|
|||
dependencies:
|
||||
pify "^2.0.0"
|
||||
|
||||
pdfobject@^2.2.8:
|
||||
version "2.2.8"
|
||||
resolved "https://registry.yarnpkg.com/pdfobject/-/pdfobject-2.2.8.tgz#956c8ce254883cdbc7c3cbee3d74d5a017f98d0b"
|
||||
integrity sha512-dB/soWNMLtVGHfXERXnAtsKm0XwC6lyGVYegQcZxL4rw07rNOKvawc9kddBzlGr7TbiBZuGf4Drb3kyRbTf/QA==
|
||||
|
||||
performance-now@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||
|
|
Loading…
Reference in a new issue