add bill categories
add webdav client change pdf viewer
This commit is contained in:
parent
cb4905b151
commit
688fdd49ee
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="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"
|
# DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=13&charset=utf8"
|
||||||
###< doctrine/doctrine-bundle ###
|
###< doctrine/doctrine-bundle ###
|
||||||
|
|
||||||
|
WEBDAV_BILLING_SERVER=https://deblan.cloud
|
||||||
|
WEBDAV_BILLING_BASE_URL=/public.php/webdav/
|
||||||
|
WEBDAV_BILLING_USERNAME=
|
||||||
|
WEBDAV_BILLING_PASSWORD=
|
||||||
|
|
|
@ -4,8 +4,10 @@ const AddressAutocomplete = require('./modules/address.js')
|
||||||
const FilesCollectionSorter = require('./modules/collection-sorter.js')
|
const FilesCollectionSorter = require('./modules/collection-sorter.js')
|
||||||
const Calendar = require('./modules/calendar.js')
|
const Calendar = require('./modules/calendar.js')
|
||||||
const Masks = require('./modules/masks.js')
|
const Masks = require('./modules/masks.js')
|
||||||
|
const PdfViewer = require('./modules/pdf-viewer.js')
|
||||||
|
|
||||||
new AddressAutocomplete()
|
new AddressAutocomplete()
|
||||||
new FilesCollectionSorter()
|
new FilesCollectionSorter()
|
||||||
new Calendar()
|
new Calendar()
|
||||||
new Masks()
|
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.*",
|
"doctrine/orm": "2.11.*",
|
||||||
"knplabs/knp-snappy": "^1.4",
|
"knplabs/knp-snappy": "^1.4",
|
||||||
"murph/murph-core": "^1.18",
|
"murph/murph-core": "^1.18",
|
||||||
"sabre/dav": "^4.3"
|
"sabre/dav": "^4.4"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/browser-kit": "^5.4",
|
"symfony/browser-kit": "^5.4",
|
||||||
|
|
|
@ -4,6 +4,10 @@
|
||||||
# Put parameters here that don't need to change on each machine where the app is deployed
|
# 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
|
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
|
||||||
parameters:
|
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:
|
services:
|
||||||
# default configuration for services in *this* file
|
# default configuration for services in *this* file
|
||||||
|
@ -65,5 +69,13 @@ services:
|
||||||
$privateKeyPath: '%kernel.project_dir%/config/secrets/prod.private.key'
|
$privateKeyPath: '%kernel.project_dir%/config/secrets/prod.private.key'
|
||||||
$filesystem: '@filesystem'
|
$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
|
# add more service definitions when explicit configuration is needed
|
||||||
# please note that last definitions always *replace* previous ones
|
# please note that last definitions always *replace* previous ones
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"chunk": "^0.0.3",
|
"chunk": "^0.0.3",
|
||||||
"inputmask": "^5.0.8-beta.17",
|
"inputmask": "^5.0.8-beta.17",
|
||||||
"murph-project": "^1",
|
"murph-project": "^1",
|
||||||
|
"pdfobject": "^2.2.8",
|
||||||
"vue-fragment": "^1.5.2"
|
"vue-fragment": "^1.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
73
src/Api/Webdav/Client.php
Normal file
73
src/Api/Webdav/Client.php
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
<?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 exists(string $file): bool
|
||||||
|
{
|
||||||
|
$response = $this->client->request('GET', $this->baseUrl.'/'.$file);
|
||||||
|
|
||||||
|
return 404 !== $response['statusCode'];
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,8 @@ use Symfony\Component\Form\Form;
|
||||||
use function Symfony\Component\String\u;
|
use function Symfony\Component\String\u;
|
||||||
use App\Repository\BillVendorRepositoryQuery;
|
use App\Repository\BillVendorRepositoryQuery;
|
||||||
use App\Factory\BillVendorFactory;
|
use App\Factory\BillVendorFactory;
|
||||||
|
use App\Repository\BillCategoryRepositoryQuery;
|
||||||
|
use App\Factory\BillCategoryFactory;
|
||||||
|
|
||||||
class BillAdminController extends CrudController
|
class BillAdminController extends CrudController
|
||||||
{
|
{
|
||||||
|
@ -71,6 +73,8 @@ class BillAdminController extends CrudController
|
||||||
EntityManager $entityManager,
|
EntityManager $entityManager,
|
||||||
BillVendorRepositoryQuery $vendorQuery,
|
BillVendorRepositoryQuery $vendorQuery,
|
||||||
BillVendorFactory $vendorFactory,
|
BillVendorFactory $vendorFactory,
|
||||||
|
BillCategoryRepositoryQuery $categoryQuery,
|
||||||
|
BillCategoryFactory $categoryFactory,
|
||||||
Request $request
|
Request $request
|
||||||
): Response
|
): Response
|
||||||
{
|
{
|
||||||
|
@ -78,25 +82,39 @@ class BillAdminController extends CrudController
|
||||||
$entity,
|
$entity,
|
||||||
$entityManager,
|
$entityManager,
|
||||||
$request,
|
$request,
|
||||||
function(Entity $entity, Form $form, Request $request) use ($entityManager, $vendorQuery, $vendorFactory) {
|
function(Entity $entity, Form $form, Request $request) use (
|
||||||
$vendor = $form->get('vendor')->getData();
|
$entityManager,
|
||||||
$customVendor = $form->get('customVendor')->getData();
|
$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 ($customVendor !== null) {
|
if ($customValue !== null) {
|
||||||
$customVendor = u($customVendor)->upper();
|
if ($elements[2]) {
|
||||||
$vendor = $vendorQuery->create()
|
$customValue = u($customValue)->upper();
|
||||||
->where('.label = :label')
|
}
|
||||||
->setParameter(':label', $customVendor)
|
|
||||||
->findOne()
|
|
||||||
;
|
|
||||||
|
|
||||||
if ($vendor === null) {
|
$value = $elements[0]->create()
|
||||||
$vendor = $vendorFactory->create($customVendor);
|
->where('.label = :label')
|
||||||
$entityManager->create($vendor);
|
->setParameter(':label', $customValue)
|
||||||
|
->findOne()
|
||||||
|
;
|
||||||
|
|
||||||
|
if ($value === null) {
|
||||||
|
$value = $elements[1]->create($customValue);
|
||||||
|
$entityManager->create($value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$entity->setVendor($vendor);
|
$entity->{(string) u('set_'.$key)->camel()}($value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -178,6 +196,12 @@ class BillAdminController extends CrudController
|
||||||
'button_attr' => ['style' => 'user-select: all'],
|
'button_attr' => ['style' => 'user-select: all'],
|
||||||
'sort' => ['vendor', '.vendor'],
|
'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, [
|
->setField('index', 'Date', Field\DateField::class, [
|
||||||
'property' => 'date',
|
'property' => 'date',
|
||||||
'sort' => ['date', '.date'],
|
'sort' => ['date', '.date'],
|
||||||
|
|
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';
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,8 +6,10 @@ use App\Repository\BillRepository;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use App\Core\Entity\EntityInterface;
|
use App\Core\Entity\EntityInterface;
|
||||||
|
use App\Core\Doctrine\Timestampable;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: BillRepository::class)]
|
#[ORM\Entity(repositoryClass: BillRepository::class)]
|
||||||
|
#[ORM\HasLifecycleCallbacks]
|
||||||
class Bill implements EntityInterface
|
class Bill implements EntityInterface
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
|
@ -40,6 +42,12 @@ class Bill implements EntityInterface
|
||||||
#[ORM\JoinColumn(onDelete: 'SET NULL')]
|
#[ORM\JoinColumn(onDelete: 'SET NULL')]
|
||||||
private ?BillVendor $vendor = null;
|
private ?BillVendor $vendor = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'bills')]
|
||||||
|
#[ORM\JoinColumn(onDelete: 'SET NULL')]
|
||||||
|
private ?BillCategory $category = null;
|
||||||
|
|
||||||
|
use Timestampable;
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
|
@ -145,4 +153,47 @@ class Bill implements EntityInterface
|
||||||
|
|
||||||
return $this;
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,8 +7,10 @@ use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use App\Core\Entity\EntityInterface;
|
use App\Core\Entity\EntityInterface;
|
||||||
|
use App\Core\Doctrine\Timestampable;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: BillVendorRepository::class)]
|
#[ORM\Entity(repositoryClass: BillVendorRepository::class)]
|
||||||
|
#[ORM\HasLifecycleCallbacks]
|
||||||
class BillVendor implements EntityInterface
|
class BillVendor implements EntityInterface
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
|
@ -22,6 +24,8 @@ class BillVendor implements EntityInterface
|
||||||
#[ORM\OneToMany(mappedBy: 'vendor', targetEntity: Bill::class)]
|
#[ORM\OneToMany(mappedBy: 'vendor', targetEntity: Bill::class)]
|
||||||
private Collection $bills;
|
private Collection $bills;
|
||||||
|
|
||||||
|
use Timestampable;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->bills = new ArrayCollection();
|
$this->bills = new ArrayCollection();
|
||||||
|
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,10 +7,10 @@ use App\Entity\BillVendor as Entity;
|
||||||
|
|
||||||
class BillVendorFactory implements FactoryInterface
|
class BillVendorFactory implements FactoryInterface
|
||||||
{
|
{
|
||||||
public function create(string $label): Entity
|
public function create(?string $label = null): Entity
|
||||||
{
|
{
|
||||||
$entity = new Entity();
|
$entity = new Entity();
|
||||||
$entity->setLabel($label);
|
$entity->setLabel((string) $label);
|
||||||
|
|
||||||
return $entity;
|
return $entity;
|
||||||
}
|
}
|
||||||
|
|
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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ use App\Entity\BillPeer;
|
||||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
use App\Entity\BillVendor;
|
use App\Entity\BillVendor;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use App\Entity\BillCategory;
|
||||||
|
|
||||||
class BillFilterType extends AbstractType
|
class BillFilterType extends AbstractType
|
||||||
{
|
{
|
||||||
|
@ -42,6 +43,19 @@ class BillFilterType extends AbstractType
|
||||||
'data-jschoice' => '',
|
'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('status', ChoiceType::class, [
|
->add('status', ChoiceType::class, [
|
||||||
'choices' => BillPeer::choices(),
|
'choices' => BillPeer::choices(),
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
|
|
@ -15,6 +15,7 @@ use Symfony\Component\Validator\Constraints\File;
|
||||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
||||||
use App\Entity\BillPeer;
|
use App\Entity\BillPeer;
|
||||||
|
use App\Entity\BillCategory;
|
||||||
|
|
||||||
class BillType extends AbstractType
|
class BillType extends AbstractType
|
||||||
{
|
{
|
||||||
|
@ -64,6 +65,30 @@ class BillType extends AbstractType
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'mapped' => 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, [
|
->add('date', null, [
|
||||||
'html5' => true,
|
'html5' => true,
|
||||||
'widget' => 'single_text',
|
'widget' => 'single_text',
|
||||||
|
@ -79,13 +104,21 @@ class BillType extends AbstractType
|
||||||
->add('amountTtc', NumberType::class, [
|
->add('amountTtc', NumberType::class, [
|
||||||
'label' => 'Montant TTC',
|
'label' => 'Montant TTC',
|
||||||
'html5' => true,
|
'html5' => true,
|
||||||
|
'scale' => 2,
|
||||||
'label_attr' => [
|
'label_attr' => [
|
||||||
'class' => 'mt-3',
|
'class' => 'mt-3',
|
||||||
],
|
],
|
||||||
|
'attr' => [
|
||||||
|
'step' => 0.01,
|
||||||
|
],
|
||||||
])
|
])
|
||||||
->add('amountHt', NumberType::class, [
|
->add('amountHt', NumberType::class, [
|
||||||
'label' => 'Montant HT',
|
'label' => 'Montant HT',
|
||||||
|
'scale' => 2,
|
||||||
'html5' => true,
|
'html5' => true,
|
||||||
|
'attr' => [
|
||||||
|
'step' => 0.01,
|
||||||
|
],
|
||||||
])
|
])
|
||||||
->add('status', ChoiceType::class, [
|
->add('status', ChoiceType::class, [
|
||||||
'choices' => BillPeer::choices(),
|
'choices' => BillPeer::choices(),
|
||||||
|
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{% if entity.id %}
|
{% if entity.id %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-3">
|
<div class="col-md-4">
|
||||||
{% for item in form %}
|
{% for item in form %}
|
||||||
{% if configuration.isI18n and item.vars.name == 'translations' %}
|
{% if configuration.isI18n and item.vars.name == 'translations' %}
|
||||||
{% include(configuration.view('form_translations', '@Core/admin/crud/_form_translations.html.twig')) with {form: item} %}
|
{% include(configuration.view('form_translations', '@Core/admin/crud/_form_translations.html.twig')) with {form: item} %}
|
||||||
|
@ -9,9 +9,20 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-9 pl-5">
|
<div class="col-md-8 pl-5">
|
||||||
<iframe src = "{{ asset('vendor/viewerjs/index.html#' ~ asset(entity.file)) }}" style="width: 100%; height: calc(100vh * 3 / 4)" u webkitallowfullscreen>
|
{% if entity.isPdf %}
|
||||||
</iframe>
|
<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>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{% set values = {
|
{% set values = {
|
||||||
'Référence': entity.reference,
|
'Référence': entity.reference,
|
||||||
'Fournisseur': entity.vendor,
|
'Fournisseur': entity.vendor,
|
||||||
|
'Catégorie': entity.category,
|
||||||
'Montant TTC': entity.amountTtc is not null ? (entity.amountTtc|number_format(2, ',') ~ ' €') : '/',
|
'Montant TTC': entity.amountTtc is not null ? (entity.amountTtc|number_format(2, ',') ~ ' €') : '/',
|
||||||
'Montant HT': entity.amountHt is not null ? (entity.amountHt|number_format(2, ',') ~ ' €') : '/',
|
'Montant HT': entity.amountHt is not null ? (entity.amountHt|number_format(2, ',') ~ ' €') : '/',
|
||||||
'Status': entity.humanStatus,
|
'Status': entity.humanStatus,
|
||||||
|
@ -19,8 +20,19 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8 pl-3">
|
<div class="col-md-8 pl-3">
|
||||||
<iframe src = "{{ asset('vendor/viewerjs/index.html#' ~ asset(entity.file)) }}" style="width: 100%; height: calc(100vh * 2 / 3)" u webkitallowfullscreen>
|
{% if entity.isPdf %}
|
||||||
</iframe>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -66,19 +66,28 @@
|
||||||
{{ include('@Core/admin/module/_menu_section.html.twig', {label: 'Comptabilité'}) }}
|
{{ include('@Core/admin/module/_menu_section.html.twig', {label: 'Comptabilité'}) }}
|
||||||
|
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
{{ include('@Core/admin/module/_menu_item.html.twig', {
|
{% if is_granted('ROLE_TREASURER') %}
|
||||||
id: 'bill',
|
{{ include('@Core/admin/module/_menu_item.html.twig', {
|
||||||
label: 'Factures',
|
id: 'bill',
|
||||||
route: path('admin_bill_index'),
|
label: 'Factures',
|
||||||
icon: 'fa fa-file'
|
route: path('admin_bill_index'),
|
||||||
}) }}
|
icon: 'fa fa-file'
|
||||||
|
}) }}
|
||||||
|
|
||||||
{{ include('@Core/admin/module/_menu_item.html.twig', {
|
{{ include('@Core/admin/module/_menu_item.html.twig', {
|
||||||
id: 'bill_vendor',
|
id: 'bill_vendor',
|
||||||
label: 'Fournisseurs',
|
label: 'Fournisseurs',
|
||||||
route: path('admin_bill_vendor_index'),
|
route: path('admin_bill_vendor_index'),
|
||||||
icon: 'fa fa-store'
|
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', {
|
{{ include('@Core/admin/module/_menu_item.html.twig', {
|
||||||
id: 'expense_report',
|
id: 'expense_report',
|
||||||
|
|
|
@ -6429,6 +6429,11 @@ path-type@^2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
pify "^2.0.0"
|
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:
|
performance-now@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||||
|
|
Loading…
Reference in a new issue