This commit is contained in:
afornerot 2024-12-26 17:45:58 +01:00
parent 9a2e4755e1
commit 4b798fd1f9
39 changed files with 1445 additions and 71 deletions

11
.env
View File

@ -4,4 +4,13 @@ DATABASE_URL="mysql://user:changeme@mariadb:3306/ninecompta"
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
APP_NAME=Ninecompta
APP_NOREPLY=admin@noreply.fr
APP_NOREPLY=admin@noreply.fr
MODE_AUTH=SQL
CAS_HOST=auth.cadoles.com
CAS_PORT=443
CAS_PATH=/cas
CAS_USERNAME=uid
CAS_MAIL=mail
CAS_LASTNAME=lastname
CAS_FIRSTNAME=firstname

View File

@ -3,5 +3,8 @@
"php-cs-fixer.executablePath": "${workspaceFolder}/vendor/bin/php-cs-fixer",
"php-cs-fixer.onsave": true,
"php-cs-fixer.rules": "@Symfony",
"php-cs-fixer.config": ".php-cs-fixer.dist.php"
"php-cs-fixer.config": ".php-cs-fixer.dist.php",
"vscode-php-cs-fixer.toolPath": "/usr/local/bin/php-cs-fixer",
"vscode-php-cs-fixer.config": ".php-cs-fixer.dist.php",
"vscode-php-cs-fixer.rules": ""
}

View File

@ -7,6 +7,7 @@
"php": ">=8.2",
"ext-ctype": "*",
"ext-iconv": "*",
"apereo/phpcas": "^1.6",
"doctrine/dbal": "^3",
"doctrine/doctrine-bundle": "^2.13",
"doctrine/doctrine-migrations-bundle": "^3.3",
@ -14,6 +15,7 @@
"oneup/uploader-bundle": "^5.0",
"phpdocumentor/reflection-docblock": "^5.4",
"phpstan/phpdoc-parser": "^1.33",
"ramsey/uuid": "^4.7",
"symfony/asset": "7.1.*",
"symfony/console": "7.1.*",
"symfony/doctrine-messenger": "7.1.*",

314
composer.lock generated
View File

@ -4,8 +4,139 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "26388d2182ab98b93691c53c2fa59dc5",
"content-hash": "89c167c235fc8b9bdd27e885ec95db37",
"packages": [
{
"name": "apereo/phpcas",
"version": "1.6.1",
"source": {
"type": "git",
"url": "https://github.com/apereo/phpCAS.git",
"reference": "c129708154852656aabb13d8606cd5b12dbbabac"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/apereo/phpCAS/zipball/c129708154852656aabb13d8606cd5b12dbbabac",
"reference": "c129708154852656aabb13d8606cd5b12dbbabac",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-dom": "*",
"php": ">=7.1.0",
"psr/log": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
"monolog/monolog": "^1.0.0 || ^2.0.0",
"phpstan/phpstan": "^1.5",
"phpunit/phpunit": ">=7.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3.x-dev"
}
},
"autoload": {
"files": [
"source/CAS.php"
],
"classmap": [
"source/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Joachim Fritschi",
"email": "jfritschi@freenet.de",
"homepage": "https://github.com/jfritschi"
},
{
"name": "Adam Franco",
"homepage": "https://github.com/adamfranco"
},
{
"name": "Henry Pan",
"homepage": "https://github.com/phy25"
}
],
"description": "Provides a simple API for authenticating users against a CAS server",
"homepage": "https://wiki.jasig.org/display/CASC/phpCAS",
"keywords": [
"apereo",
"cas",
"jasig"
],
"support": {
"issues": "https://github.com/apereo/phpCAS/issues",
"source": "https://github.com/apereo/phpCAS/tree/1.6.1"
},
"time": "2023-02-19T19:52:35+00:00"
},
{
"name": "brick/math",
"version": "0.12.1",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
"reference": "f510c0a40911935b77b86859eb5223d58d660df1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1",
"reference": "f510c0a40911935b77b86859eb5223d58d660df1",
"shasum": ""
},
"require": {
"php": "^8.1"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.2",
"phpunit/phpunit": "^10.1",
"vimeo/psalm": "5.16.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Brick\\Math\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Arbitrary-precision arithmetic library",
"keywords": [
"Arbitrary-precision",
"BigInteger",
"BigRational",
"arithmetic",
"bigdecimal",
"bignum",
"bignumber",
"brick",
"decimal",
"integer",
"math",
"mathematics",
"rational"
],
"support": {
"issues": "https://github.com/brick/math/issues",
"source": "https://github.com/brick/math/tree/0.12.1"
},
"funding": [
{
"url": "https://github.com/BenMorel",
"type": "github"
}
],
"time": "2023-11-29T23:19:16+00:00"
},
{
"name": "doctrine/cache",
"version": "2.2.0",
@ -2022,6 +2153,187 @@
},
"time": "2024-09-11T13:17:53+00:00"
},
{
"name": "ramsey/collection",
"version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/ramsey/collection.git",
"reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5",
"reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5",
"shasum": ""
},
"require": {
"php": "^8.1"
},
"require-dev": {
"captainhook/plugin-composer": "^5.3",
"ergebnis/composer-normalize": "^2.28.3",
"fakerphp/faker": "^1.21",
"hamcrest/hamcrest-php": "^2.0",
"jangregor/phpstan-prophecy": "^1.0",
"mockery/mockery": "^1.5",
"php-parallel-lint/php-console-highlighter": "^1.0",
"php-parallel-lint/php-parallel-lint": "^1.3",
"phpcsstandards/phpcsutils": "^1.0.0-rc1",
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/extension-installer": "^1.2",
"phpstan/phpstan": "^1.9",
"phpstan/phpstan-mockery": "^1.1",
"phpstan/phpstan-phpunit": "^1.3",
"phpunit/phpunit": "^9.5",
"psalm/plugin-mockery": "^1.1",
"psalm/plugin-phpunit": "^0.18.4",
"ramsey/coding-standard": "^2.0.3",
"ramsey/conventional-commits": "^1.3",
"vimeo/psalm": "^5.4"
},
"type": "library",
"extra": {
"captainhook": {
"force-install": true
},
"ramsey/conventional-commits": {
"configFile": "conventional-commits.json"
}
},
"autoload": {
"psr-4": {
"Ramsey\\Collection\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ben Ramsey",
"email": "ben@benramsey.com",
"homepage": "https://benramsey.com"
}
],
"description": "A PHP library for representing and manipulating collections.",
"keywords": [
"array",
"collection",
"hash",
"map",
"queue",
"set"
],
"support": {
"issues": "https://github.com/ramsey/collection/issues",
"source": "https://github.com/ramsey/collection/tree/2.0.0"
},
"funding": [
{
"url": "https://github.com/ramsey",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/ramsey/collection",
"type": "tidelift"
}
],
"time": "2022-12-31T21:50:55+00:00"
},
{
"name": "ramsey/uuid",
"version": "4.7.6",
"source": {
"type": "git",
"url": "https://github.com/ramsey/uuid.git",
"reference": "91039bc1faa45ba123c4328958e620d382ec7088"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088",
"reference": "91039bc1faa45ba123c4328958e620d382ec7088",
"shasum": ""
},
"require": {
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12",
"ext-json": "*",
"php": "^8.0",
"ramsey/collection": "^1.2 || ^2.0"
},
"replace": {
"rhumsaa/uuid": "self.version"
},
"require-dev": {
"captainhook/captainhook": "^5.10",
"captainhook/plugin-composer": "^5.3",
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
"doctrine/annotations": "^1.8",
"ergebnis/composer-normalize": "^2.15",
"mockery/mockery": "^1.3",
"paragonie/random-lib": "^2",
"php-mock/php-mock": "^2.2",
"php-mock/php-mock-mockery": "^1.3",
"php-parallel-lint/php-parallel-lint": "^1.1",
"phpbench/phpbench": "^1.0",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^1.8",
"phpstan/phpstan-mockery": "^1.1",
"phpstan/phpstan-phpunit": "^1.1",
"phpunit/phpunit": "^8.5 || ^9",
"ramsey/composer-repl": "^1.4",
"slevomat/coding-standard": "^8.4",
"squizlabs/php_codesniffer": "^3.5",
"vimeo/psalm": "^4.9"
},
"suggest": {
"ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.",
"ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.",
"ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.",
"paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
"ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
},
"type": "library",
"extra": {
"captainhook": {
"force-install": true
}
},
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"Ramsey\\Uuid\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A PHP library for generating and working with universally unique identifiers (UUIDs).",
"keywords": [
"guid",
"identifier",
"uuid"
],
"support": {
"issues": "https://github.com/ramsey/uuid/issues",
"source": "https://github.com/ramsey/uuid/tree/4.7.6"
},
"funding": [
{
"url": "https://github.com/ramsey",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/ramsey/uuid",
"type": "tidelift"
}
],
"time": "2024-04-27T21:32:50+00:00"
},
{
"name": "symfony/asset",
"version": "v7.1.6",

View File

@ -1,4 +1,6 @@
oneup_uploader:
mappings:
avatar:
frontend: dropzone
frontend: dropzone
logo:
frontend: dropzone

View File

@ -1,21 +1,24 @@
security:
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: "auto"
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
sql_provider:
entity:
class: App\Entity\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
pattern: ^/
provider: sql_provider
custom_authenticators:
- App\Security\DynamicAuthenticator
form_login:
login_path: app_login
check_path: app_login
@ -23,31 +26,15 @@ security:
default_target_path: /
logout:
path: app_logout
# where to redirect after logout
# target: app_any_route
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
- { path: ^/login, roles: PUBLIC_ACCESS }
- { path: ^/register, roles: PUBLIC_ACCESS }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/, roles: ROLE_USER }
when@test:
security:
password_hashers:
# By default, password hashers are resource intensive and take time. This is
# important to generate secure password hashes. In tests however, secure hashes
# are not important, waste resources and increase test times. The following
# reduces the work factor to the lowest possible values.
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: auto
cost: 4 # Lowest possible value for bcrypt

View File

@ -1,22 +1,23 @@
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
appEnv: "%env(resolve:APP_ENV)%"
appSecret: "%env(resolve:APP_SECRET)%"
appName: "%env(resolve:APP_NAME)%"
appNoreply: "%env(resolve:APP_NOREPLY)%"
modeAuth: "%env(resolve:MODE_AUTH)%"
casHost: "%env(resolve:CAS_HOST)%"
casPort: "%env(resolve:CAS_PORT)%"
casPath: "%env(resolve:CAS_PATH)%"
casUsername: "%env(resolve:CAS_USERNAME)%"
casMail: "%env(resolve:CAS_MAIL)%"
casLastname: "%env(resolve:CAS_LASTNAME)%"
casFirstname: "%env(resolve:CAS_FIRSTNAME)%"
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: "../src/"
exclude:
@ -30,4 +31,8 @@ services:
tags:
- name: "doctrine.event_listener"
event: "postPersist"
entity: 'App\Entity\Company'
entity: 'App\Entity\Company'
App\Security\DynamicAuthenticator:
arguments:
$modeAuth: '%env(MODE_AUTH)%'

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -21,7 +21,7 @@ class AccountingController extends AbstractController
return $this->render('accounting/list.html.twig', [
'usemenu' => true,
'usesidebar' => true,
'usesidebar' => false,
'title' => 'Plan Comptable',
'routesubmit' => 'app_user_accounting_submit',
'routeupdate' => 'app_user_accounting_update',
@ -47,7 +47,7 @@ class AccountingController extends AbstractController
return $this->render('accounting/edit.html.twig', [
'usemenu' => true,
'usesidebar' => true,
'usesidebar' => false,
'title' => 'Création Compagnie',
'routecancel' => 'app_user_accounting',
'routedelete' => 'app_user_accounting_delete',
@ -75,7 +75,7 @@ class AccountingController extends AbstractController
return $this->render('accounting/edit.html.twig', [
'usemenu' => true,
'usesidebar' => true,
'usesidebar' => false,
'title' => 'Modification Compagnie = '.$accounting->getTitle(),
'routecancel' => 'app_user_accounting',
'routedelete' => 'app_user_accounting_delete',

View File

@ -66,7 +66,7 @@ class CompanyController extends AbstractController
if ($form->isSubmitted() && $form->isValid()) {
$em->flush();
return $this->redirectToRoute('app_admin_user');
return $this->redirectToRoute('app_admin_company');
}
return $this->render('company/edit.html.twig', [

View File

@ -26,7 +26,7 @@ class HomeController extends AbstractController
#[Route('/admin', name: 'app_admin')]
public function admin(): Response
{
return $this->render('home/home.html.twig', [
return $this->render('home/blank.html.twig', [
'usemenu' => true,
'usesidebar' => true,
]);

View File

@ -0,0 +1,151 @@
<?php
namespace App\Controller;
use App\Entity\Accounting;
use App\Entity\Operation;
use App\Form\OperationType;
use App\Repository\OperationRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class OperationController extends AbstractController
{
#[Route('/user/operation', name: 'app_user_operation')]
public function list(Request $request, OperationRepository $operationRepository): Response
{
$company = $request->getSession()->get('company');
$operations = $operationRepository->findBy(['company' => $company], ['date' => 'DESC']);
return $this->render('operation/list.html.twig', [
'usemenu' => true,
'usesidebar' => false,
'title' => 'Grand Livre',
'routesubmit' => 'app_user_operation_submit',
'routeupdate' => 'app_user_operation_update',
'operations' => $operations,
]);
}
#[Route('/user/operation/submit', name: 'app_user_operation_submit')]
public function submit(Request $request, EntityManagerInterface $em): Response
{
$company = $request->getSession()->get('company');
$operation = new Operation();
$operation->setCompany($company);
$operation->setDate(new \DateTime());
$form = $this->createForm(OperationType::class, $operation, ['mode' => 'submit', 'company' => $company]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->persist($operation);
$em->flush();
return $this->redirectToRoute('app_user_operation');
}
return $this->render('operation/edit.html.twig', [
'usemenu' => true,
'usesidebar' => false,
'title' => 'Création Opération',
'routecancel' => 'app_user_operation',
'routedelete' => 'app_user_operation_delete',
'mode' => 'submit',
'form' => $form,
]);
}
#[Route('/user/operation/submit/{id}/{direction}', name: 'app_user_operation_submitwithaccounting')]
public function submitwithaccounting(int $id, string $direction, Request $request, EntityManagerInterface $em): Response
{
$company = $request->getSession()->get('company');
$accounting = $em->getRepository(Accounting::class)->find($id);
if (!$accounting) {
return $this->redirectToRoute('app_home');
}
if ('debit' != $direction && 'credit' != $direction) {
return $this->redirectToRoute('app_home');
}
$operation = new Operation();
$operation->setCompany($company);
$operation->setDate(new \DateTime());
if ('debit' == $direction) {
$operation->setDebit($accounting);
} else {
$operation->setCredit($accounting);
}
$form = $this->createForm(OperationType::class, $operation, ['mode' => 'submit', 'company' => $company, 'direction' => $direction]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->persist($operation);
$em->flush();
return $this->redirectToRoute('app_home');
}
return $this->render('operation/edit.html.twig', [
'usemenu' => true,
'usesidebar' => false,
'title' => 'Création Opération',
'routecancel' => 'app_user_operation',
'routedelete' => 'app_user_operation_delete',
'mode' => 'submit',
'form' => $form,
]);
}
#[Route('/user/operation/update/{id}', name: 'app_user_operation_update')]
public function update(int $id, Request $request, EntityManagerInterface $em): Response
{
$company = $request->getSession()->get('company');
$operation = $em->getRepository(Operation::class)->find($id);
if (!$operation || $operation->getCompany() != $company) {
return $this->redirectToRoute('app_user_operation');
}
$form = $this->createForm(OperationType::class, $operation, ['mode' => 'update', 'company' => $company]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->flush();
return $this->redirectToRoute('app_user_operation');
}
return $this->render('operation/edit.html.twig', [
'usemenu' => true,
'usesidebar' => false,
'title' => 'Modification Compagnie = '.$operation->getTitle(),
'routecancel' => 'app_user_operation',
'routedelete' => 'app_user_operation_delete',
'mode' => 'update',
'form' => $form,
]);
}
#[Route('/user/operation/delete/{id}', name: 'app_user_operation_delete')]
public function delete(int $id, Request $request, EntityManagerInterface $em): Response
{
$company = $request->getSession()->get('company');
$operation = $em->getRepository(Operation::class)->find($id);
if (!$operation || $operation->getCompany() != $company) {
return $this->redirectToRoute('app_user_operation');
}
// Tentative de suppression
try {
$em->remove($operation);
$em->flush();
} catch (\Exception $e) {
$this->addflash('error', $e->getMessage());
return $this->redirectToRoute('app_user_operation_update', ['id' => $id]);
}
return $this->redirectToRoute('app_user_operation');
}
}

View File

@ -0,0 +1,108 @@
<?php
namespace App\Controller;
use App\Entity\Year;
use App\Form\YearType;
use App\Repository\YearRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class YearController extends AbstractController
{
#[Route('/user/year', name: 'app_user_year')]
public function list(Request $request, YearRepository $yearRepository): Response
{
$company = $request->getSession()->get('company');
$years = $yearRepository->findBy(['company' => $company], ['dateStart' => 'ASC']);
return $this->render('year/list.html.twig', [
'usemenu' => true,
'usesidebar' => false,
'title' => 'Exercices',
'routesubmit' => 'app_user_year_submit',
'routeupdate' => 'app_user_year_update',
'years' => $years,
]);
}
#[Route('/user/year/submit', name: 'app_user_year_submit')]
public function submit(Request $request, EntityManagerInterface $em): Response
{
$company = $request->getSession()->get('company');
$year = new Year();
$year->setCompany($company);
$form = $this->createForm(YearType::class, $year, ['mode' => 'submit']);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->persist($year);
$em->flush();
return $this->redirectToRoute('app_user_year');
}
return $this->render('year/edit.html.twig', [
'usemenu' => true,
'usesidebar' => false,
'title' => 'Création Exercice',
'routecancel' => 'app_user_year',
'routedelete' => 'app_user_year_delete',
'mode' => 'submit',
'form' => $form,
]);
}
#[Route('/user/year/update/{id}', name: 'app_user_year_update')]
public function update(int $id, Request $request, EntityManagerInterface $em): Response
{
$company = $request->getSession()->get('company');
$year = $em->getRepository(Year::class)->find($id);
if (!$year || $year->getCompany() != $company) {
return $this->redirectToRoute('app_user_year');
}
$form = $this->createForm(YearType::class, $year, ['mode' => 'update']);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->flush();
return $this->redirectToRoute('app_user_year');
}
return $this->render('year/edit.html.twig', [
'usemenu' => true,
'usesidebar' => false,
'title' => 'Modification Exercice = '.$year->getDateStart()->format('d/m/Y').' - '.$year->getDateEnd()->format('d/m/Y'),
'routecancel' => 'app_user_year',
'routedelete' => 'app_user_year_delete',
'mode' => 'update',
'form' => $form,
]);
}
#[Route('/user/year/delete/{id}', name: 'app_user_year_delete')]
public function delete(int $id, Request $request, EntityManagerInterface $em): Response
{
$company = $request->getSession()->get('company');
$year = $em->getRepository(Year::class)->find($id);
if (!$year || $year->getCompany() != $company) {
return $this->redirectToRoute('app_user_year');
}
// Tentative de suppression
try {
$em->remove($year);
$em->flush();
} catch (\Exception $e) {
$this->addflash('error', $e->getMessage());
return $this->redirectToRoute('app_user_year_update', ['id' => $id]);
}
return $this->redirectToRoute('app_user_year');
}
}

View File

@ -200,4 +200,36 @@ class Accounting
return $this;
}
public function getSoldeDate(?\DateTime $date): float
{
$solde = (float) 0;
foreach ($this->credits as $credit) {
if (!$date || $credit->getDate() <= $date) {
if ('passif' == $this->category || 'produit' == $this->category) {
$solde += $credit->getMontant();
} else {
$solde -= $credit->getMontant();
}
}
}
foreach ($this->debits as $debit) {
if (!$date || $debit->getDate() <= $date) {
if ('passif' == $this->category || 'produit' == $this->category) {
$solde -= $debit->getMontant();
} else {
$solde += $debit->getMontant();
}
}
}
return $solde;
}
public function getSolde(): float
{
return $this->getSoldeDate(null);
}
}

View File

@ -69,10 +69,24 @@ class Company
#[ORM\ManyToMany(targetEntity: User::class, mappedBy: 'companys')]
private Collection $users;
/**
* @var Collection<int, Year>
*/
#[ORM\OneToMany(targetEntity: Year::class, mappedBy: 'company', orphanRemoval: true)]
private Collection $years;
/**
* @var Collection<int, Operation>
*/
#[ORM\OneToMany(targetEntity: Operation::class, mappedBy: 'company', orphanRemoval: true)]
private Collection $operations;
public function __construct()
{
$this->accountings = new ArrayCollection();
$this->users = new ArrayCollection();
$this->years = new ArrayCollection();
$this->operations = new ArrayCollection();
}
public function getId(): ?int
@ -280,4 +294,64 @@ class Company
return $this;
}
/**
* @return Collection<int, Year>
*/
public function getYears(): Collection
{
return $this->years;
}
public function addYear(Year $year): static
{
if (!$this->years->contains($year)) {
$this->years->add($year);
$year->setCompany($this);
}
return $this;
}
public function removeYear(Year $year): static
{
if ($this->years->removeElement($year)) {
// set the owning side to null (unless already changed)
if ($year->getCompany() === $this) {
$year->setCompany(null);
}
}
return $this;
}
/**
* @return Collection<int, Operation>
*/
public function getOperations(): Collection
{
return $this->operations;
}
public function addOperation(Operation $operation): static
{
if (!$this->operations->contains($operation)) {
$this->operations->add($operation);
$operation->setCompany($this);
}
return $this;
}
public function removeOperation(Operation $operation): static
{
if ($this->operations->removeElement($operation)) {
// set the owning side to null (unless already changed)
if ($operation->getCompany() === $this) {
$operation->setCompany(null);
}
}
return $this;
}
}

View File

@ -31,6 +31,10 @@ class Operation
#[ORM\JoinColumn(nullable: false)]
private ?Accounting $debit = null;
#[ORM\ManyToOne(inversedBy: 'operations')]
#[ORM\JoinColumn(nullable: false)]
private ?Company $company = null;
public function getId(): ?int
{
return $this->id;
@ -95,4 +99,16 @@ class Operation
return $this;
}
public function getCompany(): ?Company
{
return $this->company;
}
public function setCompany(?Company $company): static
{
$this->company = $company;
return $this;
}
}

67
src/Entity/Year.php Normal file
View File

@ -0,0 +1,67 @@
<?php
namespace App\Entity;
use App\Repository\YearRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: YearRepository::class)]
class Year
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(type: Types::DATE_MUTABLE)]
private ?\DateTimeInterface $dateStart = null;
#[ORM\Column(type: Types::DATE_MUTABLE)]
private ?\DateTimeInterface $dateEnd = null;
#[ORM\ManyToOne(inversedBy: 'years')]
#[ORM\JoinColumn(nullable: false)]
private ?Company $company = null;
public function getId(): ?int
{
return $this->id;
}
public function getDateStart(): ?\DateTimeInterface
{
return $this->dateStart;
}
public function setDateStart(\DateTimeInterface $dateStart): static
{
$this->dateStart = $dateStart;
return $this;
}
public function getDateEnd(): ?\DateTimeInterface
{
return $this->dateEnd;
}
public function setDateEnd(\DateTimeInterface $dateEnd): static
{
$this->dateEnd = $dateEnd;
return $this;
}
public function getCompany(): ?Company
{
return $this->company;
}
public function setCompany(?Company $company): static
{
$this->company = $company;
return $this;
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\EventListener;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\Security\Http\Event\LogoutEvent;
final class LogoutListener
{
private ParameterBagInterface $parameterBag;
public function __construct(ParameterBagInterface $parameterBag)
{
$this->parameterBag = $parameterBag;
}
#[AsEventListener(event: LogoutEvent::class)]
public function onLogoutEvent(LogoutEvent $event): void
{
if ('CAS' == $this->parameterBag->get('modeAuth')) {
// Initialiser phpCAS
$request = $event->getRequest();
$host = $request->headers->get('X-Forwarded-Host') ?? $request->getHost().($request->getPort() ? ':'.$request->getPort() : '');
$scheme = $request->headers->get('X-Forwarded-Proto') ?? $request->getScheme();
$url = $scheme.'://'.$host;
\phpCAS::client(CAS_VERSION_2_0, $this->parameterBag->get('casHost'), (int) $this->parameterBag->get('casPort'), $this->parameterBag->get('casPath'), $url, false);
\phpCAS::setNoCasServerValidation();
\phpCAS::logoutWithRedirectService($url);
}
}
}

View File

@ -39,8 +39,10 @@ final class SessionListener
$session->set('company', $user->getCompany());
$session->set('companys', $user->getCompanys());
if (!$user->getCompany()) {
$event->setResponse(new RedirectResponse($this->router->generate('app_user_nocompany', [])));
$currentPath = $request->getPathInfo();
$noCompanyPath = $this->router->generate('app_user_nocompany');
if (!$user->getCompany() && $currentPath != $noCompanyPath) {
$event->setResponse(new RedirectResponse($noCompanyPath));
}
}
}

View File

@ -3,7 +3,6 @@
namespace App\Form;
use App\Entity\Accounting;
use App\Form\DataTransformer\ThreeDigitTransformer;
use App\Form\Type\FontawsomeType;
use App\Form\Type\ThreeDigitType;
use Symfony\Component\Form\AbstractType;

View File

@ -0,0 +1,99 @@
<?php
namespace App\Form;
use App\Entity\Accounting;
use App\Entity\Company;
use App\Entity\Operation;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class OperationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('submit', SubmitType::class, [
'label' => 'Valider',
'attr' => ['class' => 'btn btn-success no-print'],
])
->add('date', DateType::class, [
'label' => 'Date',
])
->add('title', TextType::class, [
'label' => 'Titre',
]);
if (!$options['direction'] || 'credit' == $options['direction']) {
$builder
->add('debit', EntityType::class, [
'label' => 'Débit',
'class' => Accounting::class,
'choice_label' => 'title',
'attr' => ['class' => 'select2'],
'placeholder' => 'Selectionnez un compte',
'query_builder' => function (EntityRepository $er) use ($options) {
$result = $er->createQueryBuilder('acc')
->where('acc.company = :company')
->setParameter('company', $options['company']->getId())
->orderBy('acc.num01', 'ASC')
->orderBy('acc.num02', 'ASC');
if ($options['direction']) {
$result = $result->andWhere('acc.category=:category')->setParameter('category', 'charge');
}
return $result;
},
]);
}
if (!$options['direction'] || 'debit' == $options['direction']) {
$builder
->add('credit', EntityType::class, [
'label' => 'Crédit',
'class' => Accounting::class,
'choice_label' => 'title',
'attr' => ['class' => 'select2'],
'placeholder' => 'Selectionnez un compte',
'query_builder' => function (EntityRepository $er) use ($options) {
$result = $er->createQueryBuilder('acc')
->where('acc.company = :company')
->setParameter('company', $options['company']->getId())
->orderBy('acc.num01', 'ASC')
->orderBy('acc.num02', 'ASC');
if ($options['direction']) {
$result = $result->andWhere('acc.category=:category')->setParameter('category', 'produit');
}
return $result;
},
]);
}
$builder
->add('montant', NumberType::class, [
'label' => 'Montant',
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Operation::class,
'company' => Company::class,
'mode' => 'mode',
'direction' => null,
]);
}
}

43
src/Form/YearType.php Normal file
View File

@ -0,0 +1,43 @@
<?php
namespace App\Form;
use App\Entity\Year;
use App\Form\DataTransformer\ThreeDigitTransformer;
use App\Form\Type\FontawsomeType;
use App\Form\Type\ThreeDigitType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class YearType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('submit', SubmitType::class, [
'label' => 'Valider',
'attr' => ['class' => 'btn btn-success no-print'],
])
->add('dateStart', DateType::class, [
'label' => 'Date Début',
])
->add('dateEnd', DateType::class, [
'label' => 'Date Fin',
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Year::class,
'mode' => 'submit',
]);
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Repository;
use App\Entity\Year;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Year>
*/
class YearRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Year::class);
}
// /**
// * @return Year[] Returns an array of Year objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('y')
// ->andWhere('y.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('y.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Year
// {
// return $this->createQueryBuilder('y')
// ->andWhere('y.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@ -0,0 +1,87 @@
<?php
namespace App\Security;
use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Ramsey\Uuid\Uuid;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class CasUserProvider implements UserProviderInterface
{
private UserRepository $userRepository;
private EntityManagerInterface $em;
private ParameterBagInterface $parameterBag;
public function __construct(UserRepository $userRepository, EntityManagerInterface $em, ParameterBagInterface $parameterBag)
{
$this->userRepository = $userRepository;
$this->em = $em;
$this->parameterBag = $parameterBag;
}
/**
* Charge un utilisateur par son identifiant CAS.
*/
public function loadUserByIdentifier(string $identifier): UserInterface
{
// Récupérer l'utilisateur depuis la base de données
$user = $this->userRepository->findOneBy(['username' => $identifier]);
if (!$user) {
throw new UserNotFoundException(sprintf('User with username "%s" not found.', $identifier));
}
return $user;
}
/**
* Charge un utilisateur par son identifiant CAS et autosubmit autoupdate en fonction des attributs CAS
*/
public function loadUserByIdentifierAndAttributes(string $identifier, array $attributes): UserInterface
{
// Charger l'utilisateur existant depuis la base de données
$user = $this->userRepository->findOneBy(['username' => $identifier]);
if (!$user) {
// Créer un nouvel utilisateur avec les attributs CAS
$user = new User();
$user->setUsername($identifier);
$user->setPassword(Uuid::uuid4()->toString());
$user->setRoles(['ROLE_USER']);
// Persister l'utilisateur en base si nécessaire
$this->em->persist($user);
}
$user->setEmail($attributes[$this->parameterBag->get('casMail')] ?? null);
$this->em->flush();
return $user;
}
/**
* Permet de recharger un utilisateur déjà authentifié (si nécessaire).
*/
public function refreshUser(UserInterface $user): UserInterface
{
if (!$user instanceof User) {
throw new \InvalidArgumentException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
// Recharge l'utilisateur en base si nécessaire (ex. : rôles mis à jour)
return $this->userRepository->find($user->getId());
}
/**
* Indique si ce provider supporte un type d'utilisateur donné.
*/
public function supportsClass(string $class): bool
{
return User::class === $class;
}
}

View File

@ -0,0 +1,123 @@
<?php
namespace App\Security;
use App\Repository\UserRepository;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
class DynamicAuthenticator extends AbstractAuthenticator
{
private string $modeAuth;
private UserRepository $userRepository;
private CasUserProvider $casUserProvider;
private ParameterBagInterface $parameterBag;
public function __construct(string $modeAuth, UserRepository $userRepository, CasUserProvider $casUserProvider, ParameterBagInterface $parameterBag)
{
$this->modeAuth = $modeAuth;
$this->userRepository = $userRepository;
$this->casUserProvider = $casUserProvider;
$this->parameterBag = $parameterBag;
}
public function supports(Request $request): ?bool
{
// Vérifie si l'utilisateur est déjà connecté
if ($request->getSession()->get('_security_main')) {
return false; // L'utilisateur est déjà authentifié
}
// Exclure les routes de login et logout pour éviter les boucles
$currentPath = $request->getPathInfo();
if (in_array($currentPath, ['/login', '/logout'])) {
return false;
}
return true;
}
public function authenticate(Request $request): Passport
{
switch ($this->modeAuth) {
case 'SQL':
return $this->authenticateWithSql($request);
case 'CAS':
return $this->authenticateWithCas($request);
default:
throw new \InvalidArgumentException('Invalid authentication method');
}
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
throw $exception;
}
private function authenticateWithSql(Request $request): Passport
{
$username = $request->request->get('_username', '');
$password = $request->request->get('_password', '');
if (!$username || !$password) {
throw new AuthenticationException('Username and password are required.');
}
// Charger l'utilisateur via Doctrine
$user = $this->userRepository->findOneBy(['username' => $username]);
if (!$user) {
throw new AuthenticationException('User not found.');
}
return new Passport(
new UserBadge($username, function ($userIdentifier) {
return $this->userRepository->findOneBy(['username' => $userIdentifier]);
}),
new PasswordCredentials($password)
);
}
private function authenticateWithCas(Request $request): Passport
{
// Récupérer l'hôte d'origine derrière le reverse proxy
$host = $request->headers->get('X-Forwarded-Host') ?? $request->getHost().($request->getPort() ? ':'.$request->getPort() : '');
$scheme = $request->headers->get('X-Forwarded-Proto') ?? $request->getScheme();
// Construire l'URL
$url = $scheme.'://'.$host;
// \phpCAS::setDebug('/tmp/logcas.log');
\phpCAS::client(CAS_VERSION_2_0, $this->parameterBag->get('casHost'), (int) $this->parameterBag->get('casPort'), $this->parameterBag->get('casPath'), $url, false);
\phpCAS::setNoCasServerValidation();
\phpCAS::forceAuthentication();
$username = \phpCAS::getUser();
$attributes = \phpCAS::getAttributes();
if (!$username) {
throw new AuthenticationException('CAS authentication failed.');
}
$userBadge = new UserBadge($username, function ($userIdentifier) use ($attributes) {
return $this->casUserProvider->loadUserByIdentifierAndAttributes($userIdentifier, $attributes);
});
return new SelfValidatingPassport($userBadge);
}
}

View File

@ -40,32 +40,25 @@
{% if usemenu is defined and usemenu %}
<nav class="navbar navbar-expand-lg bg-dark" data-bs-theme="dark">
<div class="container-fluid">
<a class="navbar-brand" href="{{ path('app_home') }}"><img src="{{asset("medias/logo/logo.png")}}"> {{appName}}</a>
<a class="navbar-brand" href="{{ path('app_home') }}">
{% if app.session.get('company') %}
<img src="{{asset(app.session.get('company').logo)}}"> Compagnie = {{app.session.get('company').title}}
{% else %}
<img src="{{asset("medias/logo/logo.png")}}"> {{appName}}
{% endif %}
</a>
<div class="collapse navbar-collapse" id="navbarColor02">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a href="{{path('app_user_accounting')}}" class="nav-link" href="#">Plan Comptable</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Features</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Pricing</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">About</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">Dropdown</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Separated link</a>
</div>
</li>
<li class="nav-item">
<a href="{{path('app_user_operation')}}" class="nav-link" href="#">Grand Livre</a>
</li>
<li class="nav-item">
<a href="{{path('app_user_accounting')}}" class="nav-link" href="#">Plan Comptable</a>
</li>
<li class="nav-item">
<a href="{{path('app_user_year')}}" class="nav-link" href="#">Exercices</a>
</li>
</ul>
</div>

View File

@ -0,0 +1 @@
{% extends 'base.html.twig' %}

View File

@ -3,7 +3,8 @@
{% block body %}
<div class="d-flex justify-content-center">
<div class="d-flex justify-content-center mt-5">
{% for accounting in accountings %}
<div class="card" style="width:400px">
<div class="card-body d-flex align-items-center">
@ -11,7 +12,19 @@
<i class="fas {{accounting.icon}} fa-4x"></i>
</div>
<div class="p-3 fs-1">{{accounting.title}}</div>
<div class="p-3 fs-1 d-flex flex-column flex-grow-1 align-items-center">
<div>{{accounting.title}}</div>
<div>{{accounting.solde}}€</div>
<div class="d-flex ">
<a class="p-3" href="{{path('app_user_operation_submitwithaccounting',{id:accounting.id,direction:'credit'})}}">
<i class="fas fa-minus bg-danger text-white rounded-circle fa-fw" style="height:60px; padding-top:7px;"></i>
</a>
<a class="p-3" href="{{path('app_user_operation_submitwithaccounting',{id:accounting.id,direction:'debit'})}}">
<i class="fas fa-plus bg-success text-white rounded-circle fa-fw" style="height:60px; padding-top:7px;"></i>
</a>
</div>
</div>
</div>
</div>
{% endfor %}

View File

@ -1 +1,4 @@
<h2>Veuillez selectionner une compagnie</h2>
{% extends 'base.html.twig' %}
{%block body%}
<h2>Veuillez selectionner une compagnie</h2>
{%endblock%}

View File

@ -0,0 +1,40 @@
{% extends 'base.html.twig' %}
{% block title %} = {{title}}{% endblock %}
{% block body %}
<h1>{{title}}</h1>
{{ form_start(form) }}
{{ form_widget(form.submit) }}
<a href="{{ path(routecancel) }}" class="btn btn-secondary ms-1">Annuler</a>
{%if mode=="update" %}<a href="{{ path(routedelete,{id:form.vars.value.id}) }}" class="btn btn-danger float-end" onclick="return confirm('Confirmez-vous la suppression de cet enregistrement ?')">Supprimer</a>{%endif%}
{% include('include/error.html.twig') %}
<div class="row">
<div class="col-md-6 mx-auto">
<div class="card mt-3">
<div class="card-header">Information</div>
<div class="card-body">
{{ form_row(form.date) }}
{{ form_row(form.title) }}
{{ form.debit is defined ? form_row(form.debit):"" }}
{{ form.credit is defined ? form_row(form.credit):"" }}
{{ form_row(form.montant) }}
</div>
</div>
</div>
</div>
{{ form_end(form) }}
{% endblock %}
{% block localscript %}
<script>
$(document).ready(function() {
$("#operation_title").focus();
});
</script>
{% endblock %}

View File

@ -0,0 +1,48 @@
{% extends 'base.html.twig' %}
{% block title %} = {{title}}{% endblock %}
{% block body %}
<h1>{{title}}</h1>
<a href="{{ path(routesubmit) }}" class="btn btn-success">Ajouter</a>
<div class="dataTable_wrapper">
<table class="table table-striped table-bordered table-hover" id="dataTables" style="width:100%">
<thead>
<tr>
<th width="70px" class="no-sort">Action</th>
<th width="70px" class="no-sort">Date</th>
<th>Titre</th>
<th>Débit</th>
<th>Crédit</th>
<th width="150px">Montant</th>
</tr>
</thead>
<tbody>
{% for operation in operations %}
<tr>
<td><a href="{{ path(routeupdate,{id:operation.id}) }}"><i class="fas fa-file fa-2x"></i></a></td>
<td>{{operation.date|date("d/m/y")}}</td>
<td>{{operation.title}}</td>
<td>{{operation.debit.num01}}-{{operation.debit.num02}} = {{operation.debit.title}}</td>
<td>{{operation.credit.num01}}-{{operation.credit.num02}} = {{operation.credit.title}}</td>
<td>{{operation.montant}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
{% block localscript %}
<script>
$(document).ready(function() {
$('#dataTables').DataTable({
columnDefs: [ { "targets": "no-sort", "orderable": false }, { "targets": "no-string", "type" : "num" } ],
responsive: true,
iDisplayLength: 100,
order: [[ 1, "asc" ]]
});
});
</script>
{% endblock %}

View File

@ -44,9 +44,9 @@
}
function reportThumb() {
window.parent.$("#user_avatar").val("{{thumb}}");
window.parent.$("#{{reportThumb}}").val("{{thumb}}");
url="{{ asset(thumb) }}";
window.parent.$("#user_avatar_img").attr("src",url);
window.parent.$("#{{reportThumb}}_img").attr("src",url);
closeModal();
}

View File

@ -0,0 +1,37 @@
{% extends 'base.html.twig' %}
{% block title %} = {{title}}{% endblock %}
{% block body %}
<h1>{{title}}</h1>
{{ form_start(form) }}
{{ form_widget(form.submit) }}
<a href="{{ path(routecancel) }}" class="btn btn-secondary ms-1">Annuler</a>
{%if mode=="update" %}<a href="{{ path(routedelete,{id:form.vars.value.id}) }}" class="btn btn-danger float-end" onclick="return confirm('Confirmez-vous la suppression de cet enregistrement ?')">Supprimer</a>{%endif%}
{% include('include/error.html.twig') %}
<div class="row">
<div class="col-md-6 mx-auto">
<div class="card mt-3">
<div class="card-header">Information</div>
<div class="card-body">
{{ form_row(form.dateStart) }}
{{ form_row(form.dateEnd) }}
</div>
</div>
</div>
</div>
{{ form_end(form) }}
{% endblock %}
{% block localscript %}
<script>
$(document).ready(function() {
$("#year_dateStart").focus();
});
</script>
{% endblock %}

View File

@ -0,0 +1,42 @@
{% extends 'base.html.twig' %}
{% block title %} = {{title}}{% endblock %}
{% block body %}
<h1>{{title}}</h1>
<a href="{{ path(routesubmit) }}" class="btn btn-success">Ajouter</a>
<div class="dataTable_wrapper">
<table class="table table-striped table-bordered table-hover" id="dataTables" style="width:100%">
<thead>
<tr>
<th width="70px" class="no-sort">Action</th>
<th>Début</th>
<th>Fin</th>
</tr>
</thead>
<tbody>
{% for year in years %}
<tr>
<td><a href="{{ path(routeupdate,{id:year.id}) }}"><i class="fas fa-file fa-2x"></i></a></td>
<td>{{year.dateStart|date('d/m/Y')}}</td>
<td>{{year.dateEnd|date('d/m/Y')}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
{% block localscript %}
<script>
$(document).ready(function() {
$('#dataTables').DataTable({
columnDefs: [ { "targets": "no-sort", "orderable": false }, { "targets": "no-string", "type" : "num" } ],
responsive: true,
iDisplayLength: 100,
order: [[ 1, "asc" ]]
});
});
</script>
{% endblock %}