✨ feat(order): Ajoute la génération de devis PDF et un filtre Twig pour le total.
🐛 fix(order-add): Corrige le label du prix en "Prix HT" sur le formulaire. ✏️ chore(customer): Corrige une faute de frappe dans le titre de la page.
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
"doctrine/doctrine-migrations-bundle": "^3.4.2",
|
||||
"doctrine/orm": "^3.5",
|
||||
"docusealco/docuseal-php": "^1.0",
|
||||
"fpdf/fpdf": "*",
|
||||
"google/cloud": "^0.296.0",
|
||||
"imagine/imagine": "^1.5",
|
||||
"knplabs/knp-paginator-bundle": "^6.8",
|
||||
|
||||
55
composer.lock
generated
55
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "63990e443aad6c359435a6ba0b7f3054",
|
||||
"content-hash": "0d15b0c5ba47b24aa16e0727699f4ab3",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aws/aws-crt-php",
|
||||
@@ -1591,6 +1591,59 @@
|
||||
},
|
||||
"time": "2025-04-09T20:32:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fpdf/fpdf",
|
||||
"version": "1.86.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/coreydoughty/Fpdf.git",
|
||||
"reference": "d2a0cbd9e4b5557b9c6c29ddc8eb2b36d8761e00"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/coreydoughty/Fpdf/zipball/d2a0cbd9e4b5557b9c6c29ddc8eb2b36d8761e00",
|
||||
"reference": "d2a0cbd9e4b5557b9c6c29ddc8eb2b36d8761e00",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.6.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"aliases": {
|
||||
"FPDF": "Fpdf\\Fpdf"
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Fpdf\\": "src/Fpdf"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Corey Doughty",
|
||||
"email": "corey@doughty.ca"
|
||||
}
|
||||
],
|
||||
"description": "FPDF Composer Wrapper",
|
||||
"homepage": "https://github.com/coreydoughty/Fpdf",
|
||||
"keywords": [
|
||||
"fpdf",
|
||||
"pdf",
|
||||
"wrapper"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/coreydoughty/Fpdf/issues",
|
||||
"source": "https://github.com/coreydoughty/Fpdf/tree/1.86.0"
|
||||
},
|
||||
"time": "2023-07-04T16:41:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "google/auth",
|
||||
"version": "v1.47.1",
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Form\Artemis\Intranet\CustomerNddType;
|
||||
use App\Form\Artemis\Intranet\CustomerType;
|
||||
use App\Repository\CustomerDnsRepository;
|
||||
use App\Repository\CustomerRepository;
|
||||
use App\Service\Customer\Billing\CreateDevisCustomerEvent;
|
||||
use App\Service\Customer\DeleteCustomerEvent;
|
||||
use App\Service\Customer\RestoreCustomerEvent;
|
||||
use App\Service\Logger\LoggerService;
|
||||
@@ -132,6 +133,8 @@ class CustomerController extends AbstractController
|
||||
|
||||
|
||||
$orderDevis = $entityManager->getRepository(CustomerDevis::class)->findBy(['customer'=>$customer],['id'=>'ASC']);
|
||||
$event = new CreateDevisCustomerEvent($orderDevis[0],true);
|
||||
$eventDispatcher->dispatch($event);
|
||||
|
||||
return $this->render('artemis/intranet/customer/edit.twig',[
|
||||
'form' => $form->createView(),
|
||||
|
||||
33
src/Service/Customer/Billing/CreateDevisCustomerEvent.php
Normal file
33
src/Service/Customer/Billing/CreateDevisCustomerEvent.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Customer\Billing;
|
||||
|
||||
use App\Entity\CustomerDevis;
|
||||
|
||||
class CreateDevisCustomerEvent
|
||||
{
|
||||
private CustomerDevis $customerDevis;
|
||||
private bool $sendDevis;
|
||||
|
||||
public function __construct(CustomerDevis $customerDevis, bool $sendDevis)
|
||||
{
|
||||
$this->customerDevis = $customerDevis;
|
||||
$this->sendDevis = $sendDevis;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CustomerDevis
|
||||
*/
|
||||
public function getCustomerDevis(): CustomerDevis
|
||||
{
|
||||
return $this->customerDevis;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isSendDevis(): bool
|
||||
{
|
||||
return $this->sendDevis;
|
||||
}
|
||||
}
|
||||
31
src/Service/Customer/BillingEventSusbriber.php
Normal file
31
src/Service/Customer/BillingEventSusbriber.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Customer;
|
||||
|
||||
use App\Service\Customer\Billing\CreateDevisCustomerEvent;
|
||||
use App\Service\Pdf\DevisPdf;
|
||||
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
|
||||
#[AsEventListener(event: CreateDevisCustomerEvent::class, method: 'onBillingEvent')]
|
||||
class BillingEventSusbriber
|
||||
{
|
||||
|
||||
public function __construct(private KernelInterface $kernel)
|
||||
{
|
||||
}
|
||||
|
||||
public function onBillingEvent(CreateDevisCustomerEvent $createDevisCustomerEvent)
|
||||
{
|
||||
$devis = $createDevisCustomerEvent->getCustomerDevis();
|
||||
$send = $createDevisCustomerEvent->isSendDevis();
|
||||
|
||||
|
||||
$pdf = New DevisPdf($this->kernel,$devis);
|
||||
$pdf->generate();
|
||||
$pdf->Output('I');
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
110
src/Service/Pdf/DevisPdf.php
Normal file
110
src/Service/Pdf/DevisPdf.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
namespace App\Service\Pdf;
|
||||
|
||||
use App\Entity\CustomerDevis;
|
||||
use Fpdf\Fpdf;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
|
||||
class DevisPdf extends FPDF
|
||||
{
|
||||
private $items;
|
||||
|
||||
public function __construct(private readonly KernelInterface $kernel, private readonly CustomerDevis $customerDevis)
|
||||
{
|
||||
parent::__construct();
|
||||
$items = [
|
||||
[
|
||||
'title' => 'Produit 1',
|
||||
'description' => 'Description détaillée du produit 1. Cette description est assez longue pour nécessiter plusieurs lignes dans le tableau.',
|
||||
'unitPrice' => 10.00
|
||||
],
|
||||
[
|
||||
'title' => 'Produit 2',
|
||||
'description' => 'Description détaillée du produit 2. Cette description est également assez longue pour nécessiter plusieurs lignes dans le tableau.',
|
||||
'unitPrice' => 15.00
|
||||
],
|
||||
];
|
||||
$this->items = $items;
|
||||
}
|
||||
|
||||
function Header()
|
||||
{
|
||||
$this->Image($this->kernel->getProjectDir()."/public/assets/logo_siteconseil.png", 5, 5, 25);
|
||||
$this->SetFont('Arial', 'B', 12);
|
||||
$this->Text(30, 10, mb_convert_encoding("SITECONSEIL", 'ISO-8859-1', 'UTF-8'));
|
||||
$this->SetFont('Arial', '', 12);
|
||||
$this->Text(30, 15, mb_convert_encoding("27 rue le sérurier", 'ISO-8859-1', 'UTF-8'));
|
||||
$this->Text(30, 20, mb_convert_encoding("02100 SAINT-QUENTIN", 'ISO-8859-1', 'UTF-8'));
|
||||
$this->Text(30, 25, mb_convert_encoding("s.com@siteconseil.fr", 'ISO-8859-1', 'UTF-8'));
|
||||
$this->Text(30, 30, mb_convert_encoding("03 23 05 62 43", 'ISO-8859-1', 'UTF-8'));
|
||||
$this->Text(8, 35, mb_convert_encoding("SIRET: 41866405800025", 'ISO-8859-1', 'UTF-8'));
|
||||
$this->Text(8, 40, mb_convert_encoding("RCS: RCS St-Quentin 418 664 058", 'ISO-8859-1', 'UTF-8'));
|
||||
$this->Text(8, 45, mb_convert_encoding("TVA: FR05418664058", 'ISO-8859-1', 'UTF-8'));
|
||||
$this->SetFont('Arial', 'B', 12);
|
||||
$this->Text(150, 10, mb_convert_encoding("DEVIS N° ".$this->customerDevis->getNumDevis(), 'ISO-8859-1', 'UTF-8'));
|
||||
$this->Text(150, 15, mb_convert_encoding("Date: ".$this->customerDevis->getCreateAt()->format('d/m/Y'), 'ISO-8859-1', 'UTF-8'));
|
||||
}
|
||||
|
||||
function Footer()
|
||||
{
|
||||
$this->SetY(-20);
|
||||
$this->SetFont('Arial', 'I', 8);
|
||||
$this->Cell(0, 5, 'SITECONSEIL - 27 rue le serrurier - 02100 SAINT-QUENTIN - s.com@siteconseil.fr', 0, 0, 'C');
|
||||
$this->Ln(5);
|
||||
$this->Cell(0, 5, '03 23 05 62 43 - SIRET: 41866405800025 - RCS: RCS St-Quentin - TVA: FR05418664058', 0, 0, 'C');
|
||||
$this->Ln(5);
|
||||
$this->Cell(0, 5, 'Page ' . $this->PageNo() . '/{nb}', 0, 0, 'C');
|
||||
}
|
||||
|
||||
function generate()
|
||||
{
|
||||
$this->AliasNbPages();
|
||||
$this->AddPage();
|
||||
$this->SetFont('Arial', 'B', 12);
|
||||
$this->SetY(50);
|
||||
|
||||
// En-tête du tableau
|
||||
$this->Cell(70, 10, mb_convert_encoding('Titre', 'ISO-8859-1', 'UTF-8'), 1);
|
||||
$this->Cell(80, 10, mb_convert_encoding('Description', 'ISO-8859-1', 'UTF-8'), 1);
|
||||
$this->Cell(30, 10, mb_convert_encoding('Prix', 'ISO-8859-1', 'UTF-8'), 1, 1);
|
||||
|
||||
$this->SetFont('Arial', '', 12);
|
||||
$totalHT = 0;
|
||||
|
||||
foreach ($this->items as $item) {
|
||||
$startY = $this->GetY(); // Position Y de départ pour la ligne actuelle
|
||||
|
||||
// Titre de l'article
|
||||
$this->Cell(70, 10, mb_convert_encoding($item['title'], 'ISO-8859-1', 'UTF-8'), 0);
|
||||
|
||||
// Description de l'article avec MultiCell
|
||||
$this->MultiCell(80, 10, mb_convert_encoding($item['description'], 'ISO-8859-1', 'UTF-8'), 0);
|
||||
|
||||
// Obtenir la position Y après MultiCell
|
||||
$currentY = $this->GetY();
|
||||
|
||||
// Repositionner pour le prix
|
||||
$this->SetXY(180, $startY);
|
||||
$this->Cell(30, 10, mb_convert_encoding($item['unitPrice'] . ' €', 'ISO-8859-1', 'UTF-8'), 0);
|
||||
|
||||
// Mettre à jour la position Y pour la prochaine ligne
|
||||
$this->SetY(max($currentY, $startY + 10));
|
||||
|
||||
// Calcul du total
|
||||
$totalHT += $item['unitPrice'];
|
||||
}
|
||||
|
||||
$tva = $totalHT * 0.20;
|
||||
$totalTTC = $totalHT + $tva;
|
||||
|
||||
$this->SetFont('Arial', 'B', 12);
|
||||
$this->Cell(150, 10, mb_convert_encoding('Total HT:', 'ISO-8859-1', 'UTF-8'), 0, 0, 'R');
|
||||
$this->Cell(30, 10, mb_convert_encoding($totalHT . ' €', 'ISO-8859-1', 'UTF-8'), 1, 1);
|
||||
|
||||
$this->Cell(150, 10, mb_convert_encoding('TVA (20%):', 'ISO-8859-1', 'UTF-8'), 0, 0, 'R');
|
||||
$this->Cell(30, 10, mb_convert_encoding($tva . ' €', 'ISO-8859-1', 'UTF-8'), 1, 1);
|
||||
|
||||
$this->Cell(150, 10, mb_convert_encoding('Total TTC:', 'ISO-8859-1', 'UTF-8'), 0, 0, 'R');
|
||||
$this->Cell(30, 10, mb_convert_encoding($totalTTC . ' €', 'ISO-8859-1', 'UTF-8'), 1, 1);
|
||||
}
|
||||
}
|
||||
30
src/Twig/TwigOrderExtensions.php
Normal file
30
src/Twig/TwigOrderExtensions.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Twig;
|
||||
|
||||
|
||||
use App\Entity\CustomerDevis;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
|
||||
class TwigOrderExtensions extends AbstractExtension
|
||||
{
|
||||
public function getFilters()
|
||||
{
|
||||
return [
|
||||
new TwigFilter('totalOrder',[$this,'totalOrder']),
|
||||
];
|
||||
}
|
||||
public function totalOrder($object): float|int
|
||||
{
|
||||
if($object instanceof CustomerDevis) {
|
||||
$total = 0;
|
||||
foreach ($object->getCustomerDevisLines() as $customerDevisLine){
|
||||
$total = $total + (1.20*$customerDevisLine->getPriceHT());
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends 'artemis/base.twig' %}
|
||||
{% block title %}Intrante - Crée Client{% endblock %}
|
||||
{% block title %}Intranet - Crée Client{% endblock %}
|
||||
{% block content %}
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-3xl font-semibold text-gray-800 dark:text-gray-200">Modification du client - {{ customer.raisonSocial }}</h2>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<input type="text" name="lines[0][title]" id="lines[0][title]" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label for="price" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Prix TTC</label>
|
||||
<label for="price" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Prix HT</label>
|
||||
<input type="number" step="0.1" name="lines[0][price]" id="lines[0][price]" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
|
||||
</div>
|
||||
<button
|
||||
|
||||
@@ -63,8 +63,8 @@
|
||||
<td class="px-6 py-4">DEVIS</td>
|
||||
<td class="px-6 py-4">{{ orderDevi.numDevis }}</td>
|
||||
<td class="px-6 py-4">{{ orderDevi.createAt|date('d/m/Y H:i') }}</td>
|
||||
<td class="px-6 py-4">0</td>
|
||||
<td class="px-6 py-4 text-green-400">{{ orderDevi.state }}</td>
|
||||
<td class="px-6 py-4">{{ (orderDevi|totalOrder)|format_currency('EUR') }}</td>
|
||||
<td class="px-6 py-4 text-orange-400">{{ orderDevi.state|trans }}</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<button class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded">Modifier</button>
|
||||
<button class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded">Télécharger</button>
|
||||
|
||||
@@ -8,3 +8,5 @@ ei: Entreprise Individuelle
|
||||
particulier: Particulier
|
||||
Mairie: Mairie
|
||||
autre: Autre
|
||||
|
||||
created: Crée - Attends de signature
|
||||
|
||||
Reference in New Issue
Block a user