✨ feat(SignController): Ajoute la gestion des signatures Docuseal et les notifications.
This commit is contained in:
2
.env
2
.env
@@ -58,5 +58,5 @@ OVH_KEY=34bc2c2eb416b67d
|
||||
OVH_SECRET=12239d273975b5ab53318907fb66d355
|
||||
OVH_CUSTOMER=56c387eb9ca4b9a2de4d4d97fd3d7f22
|
||||
|
||||
DOCUSIGN_URL=signature.esy-web.dev
|
||||
DOCUSIGN_URL=https://signature.esy-web.dev/api
|
||||
DOCUSIGN_KEY=52u82oCoiG79awGsuxLfJqhxYjg8mrJfAsJJHejRMFa
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
"php": ">=8.2",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"chillerlan/php-qrcode": "*",
|
||||
"doctrine/dbal": "^3.10",
|
||||
"doctrine/doctrine-bundle": "^2.15",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.4.2",
|
||||
"doctrine/orm": "^3.5",
|
||||
"docusealco/docuseal-php": "^1.0",
|
||||
"docusealco/docuseal-php": "*",
|
||||
"fpdf/fpdf": "*",
|
||||
"google/cloud": "^0.296.0",
|
||||
"imagine/imagine": "^1.5",
|
||||
|
||||
161
composer.lock
generated
161
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": "0d15b0c5ba47b24aa16e0727699f4ab3",
|
||||
"content-hash": "1d76d59e951b53e55af5bbeb42af9bae",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aws/aws-crt-php",
|
||||
@@ -217,6 +217,165 @@
|
||||
],
|
||||
"time": "2025-03-29T13:50:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "chillerlan/php-qrcode",
|
||||
"version": "5.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/chillerlan/php-qrcode.git",
|
||||
"reference": "42e215640e9ebdd857570c9e4e52245d1ee51de2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/42e215640e9ebdd857570c9e4e52245d1ee51de2",
|
||||
"reference": "42e215640e9ebdd857570c9e4e52245d1ee51de2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"chillerlan/php-settings-container": "^2.1.6 || ^3.2.1",
|
||||
"ext-mbstring": "*",
|
||||
"php": "^7.4 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"chillerlan/php-authenticator": "^4.3.1 || ^5.2.1",
|
||||
"ext-fileinfo": "*",
|
||||
"phan/phan": "^5.4.5",
|
||||
"phpcompatibility/php-compatibility": "10.x-dev",
|
||||
"phpmd/phpmd": "^2.15",
|
||||
"phpunit/phpunit": "^9.6",
|
||||
"setasign/fpdf": "^1.8.2",
|
||||
"slevomat/coding-standard": "^8.15",
|
||||
"squizlabs/php_codesniffer": "^3.11"
|
||||
},
|
||||
"suggest": {
|
||||
"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
|
||||
"setasign/fpdf": "Required to use the QR FPDF output.",
|
||||
"simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"chillerlan\\QRCode\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT",
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Kazuhiko Arase",
|
||||
"homepage": "https://github.com/kazuhikoarase/qrcode-generator"
|
||||
},
|
||||
{
|
||||
"name": "ZXing Authors",
|
||||
"homepage": "https://github.com/zxing/zxing"
|
||||
},
|
||||
{
|
||||
"name": "Ashot Khanamiryan",
|
||||
"homepage": "https://github.com/khanamiryan/php-qrcode-detector-decoder"
|
||||
},
|
||||
{
|
||||
"name": "Smiley",
|
||||
"email": "smiley@chillerlan.net",
|
||||
"homepage": "https://github.com/codemasher"
|
||||
},
|
||||
{
|
||||
"name": "Contributors",
|
||||
"homepage": "https://github.com/chillerlan/php-qrcode/graphs/contributors"
|
||||
}
|
||||
],
|
||||
"description": "A QR Code generator and reader with a user-friendly API. PHP 7.4+",
|
||||
"homepage": "https://github.com/chillerlan/php-qrcode",
|
||||
"keywords": [
|
||||
"phpqrcode",
|
||||
"qr",
|
||||
"qr code",
|
||||
"qr-reader",
|
||||
"qrcode",
|
||||
"qrcode-generator",
|
||||
"qrcode-reader"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://php-qrcode.readthedocs.io",
|
||||
"issues": "https://github.com/chillerlan/php-qrcode/issues",
|
||||
"source": "https://github.com/chillerlan/php-qrcode"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://ko-fi.com/codemasher",
|
||||
"type": "Ko-Fi"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-21T16:12:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "chillerlan/php-settings-container",
|
||||
"version": "3.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/chillerlan/php-settings-container.git",
|
||||
"reference": "95ed3e9676a1d47cab2e3174d19b43f5dbf52681"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/95ed3e9676a1d47cab2e3174d19b43f5dbf52681",
|
||||
"reference": "95ed3e9676a1d47cab2e3174d19b43f5dbf52681",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpmd/phpmd": "^2.15",
|
||||
"phpstan/phpstan": "^1.11",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.2",
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"squizlabs/php_codesniffer": "^3.10"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"chillerlan\\Settings\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Smiley",
|
||||
"email": "smiley@chillerlan.net",
|
||||
"homepage": "https://github.com/codemasher"
|
||||
}
|
||||
],
|
||||
"description": "A container class for immutable settings objects. Not a DI container.",
|
||||
"homepage": "https://github.com/chillerlan/php-settings-container",
|
||||
"keywords": [
|
||||
"Settings",
|
||||
"configuration",
|
||||
"container",
|
||||
"helper"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/chillerlan/php-settings-container/issues",
|
||||
"source": "https://github.com/chillerlan/php-settings-container"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://ko-fi.com/codemasher",
|
||||
"type": "ko_fi"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-16T11:13:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/semver",
|
||||
"version": "3.4.3",
|
||||
|
||||
@@ -17,6 +17,22 @@ vich_uploader:
|
||||
inject_on_load: false
|
||||
delete_on_update: true
|
||||
delete_on_remove: true
|
||||
devis_sign:
|
||||
uri_prefix: /storage/devis_sign
|
||||
upload_destination: '%kernel.project_dir%/public/storage/devis_sign'
|
||||
namer: App\VichUploader\Namer\Customer\DevisName # Replaced namer
|
||||
directory_namer: App\VichUploader\DirectoryNamer\Customer\DevisName
|
||||
inject_on_load: false
|
||||
delete_on_update: true
|
||||
delete_on_remove: true
|
||||
devis_audit:
|
||||
uri_prefix: /storage/devis_audit
|
||||
upload_destination: '%kernel.project_dir%/public/storage/devis_audit'
|
||||
namer: App\VichUploader\Namer\Customer\DevisName # Replaced namer
|
||||
directory_namer: App\VichUploader\DirectoryNamer\Customer\DevisName
|
||||
inject_on_load: false
|
||||
delete_on_update: true
|
||||
delete_on_remove: true
|
||||
advert_payment:
|
||||
uri_prefix: /storage/advert_payment
|
||||
upload_destination: '%kernel.project_dir%/public/storage/advert_payment'
|
||||
|
||||
33
migrations/Version20250729073622.php
Normal file
33
migrations/Version20250729073622.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250729073622 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE sign_event (id SERIAL NOT NULL, submiter_event INT NOT NULL, state VARCHAR(255) NOT NULL, action_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('COMMENT ON COLUMN sign_event.action_at IS \'(DC2Type:datetime_immutable)\'');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('DROP TABLE sign_event');
|
||||
}
|
||||
}
|
||||
50
migrations/Version20250729082955.php
Normal file
50
migrations/Version20250729082955.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250729082955 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE customer_devis ADD devis_audit_file_name VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE customer_devis ADD devis_audit_dimensions JSON DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE customer_devis ADD devis_audit_size VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE customer_devis ADD devis_audit_mine_type VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE customer_devis ADD devis_audit_original_name VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE customer_devis ADD devis_sign_file_name VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE customer_devis ADD devis_sign_dimensions JSON DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE customer_devis ADD devis_sign_size VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE customer_devis ADD devis_sign_mine_type VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE customer_devis ADD devis_sign_original_name VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE customer_devis DROP devis_audit_file_name');
|
||||
$this->addSql('ALTER TABLE customer_devis DROP devis_audit_dimensions');
|
||||
$this->addSql('ALTER TABLE customer_devis DROP devis_audit_size');
|
||||
$this->addSql('ALTER TABLE customer_devis DROP devis_audit_mine_type');
|
||||
$this->addSql('ALTER TABLE customer_devis DROP devis_audit_original_name');
|
||||
$this->addSql('ALTER TABLE customer_devis DROP devis_sign_file_name');
|
||||
$this->addSql('ALTER TABLE customer_devis DROP devis_sign_dimensions');
|
||||
$this->addSql('ALTER TABLE customer_devis DROP devis_sign_size');
|
||||
$this->addSql('ALTER TABLE customer_devis DROP devis_sign_mine_type');
|
||||
$this->addSql('ALTER TABLE customer_devis DROP devis_sign_original_name');
|
||||
}
|
||||
}
|
||||
@@ -133,8 +133,6 @@ 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(),
|
||||
|
||||
@@ -111,7 +111,7 @@ class AccountController extends AbstractController
|
||||
return $this->redirectToRoute('artemis_settings_accountAdmin_view',['id'=>$account->getId()]);
|
||||
}
|
||||
$lastLogin = $accountLoginRegisterRepository->lastLogin($account);
|
||||
$account->lastLoginAt = $lastLogin[0];
|
||||
$account->lastLoginAt = $lastLogin[0] ?? new AccountLoginRegister();
|
||||
|
||||
$formPassword = $this->createForm(AdminPasswordType::class);
|
||||
$formPassword->handleRequest($request);
|
||||
|
||||
@@ -34,6 +34,7 @@ class HomeController extends AbstractController
|
||||
'error' => $authenticationUtils->getLastAuthenticationError(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route(path: '/logout',name: 'app_logout',methods: ['GET', 'POST'])]
|
||||
public function logout(AuthenticationUtils $authenticationUtils): Response
|
||||
{
|
||||
|
||||
154
src/Controller/SignController.php
Normal file
154
src/Controller/SignController.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\CustomerDevis;
|
||||
use App\Entity\SignEvent;
|
||||
use App\Repository\CustomerDevisRepository;
|
||||
use App\Service\Mailer\Mailer;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use GuzzleHttp\Psr7\UploadedFile;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
use Symfony\Component\Mime\Part\File;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
use Vich\UploaderBundle\Templating\Helper\UploaderHelper;
|
||||
|
||||
class SignController extends AbstractController
|
||||
{
|
||||
#[Route(path: '/sign-complete',name: 'app_sign_complete')]
|
||||
public function signComplete(UploaderHelper $uploaderHelper,Request $request,CustomerDevisRepository $customerDevisRepository): Response
|
||||
{
|
||||
$document = [];
|
||||
$type = $request->get('type');
|
||||
$id = $request->get('id');
|
||||
if($type == "devis") {
|
||||
$object = $customerDevisRepository->find($id);
|
||||
$document =[
|
||||
'number' => $object->getNumDevis(),
|
||||
'signatureDate' => $object->getSignAt(),
|
||||
'signer' => $object->getCustomer()->mainContact()->getName()." ". $object->getCustomer()->mainContact()->getSurname(),
|
||||
'path' => $uploaderHelper->asset($object,"devis"),
|
||||
'pathSign' => $uploaderHelper->asset($object,"devisSign"),
|
||||
];
|
||||
}
|
||||
return $this->render('admin/sign-complete.twig',[
|
||||
'document' => $document
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route(path: '/webhook/sign',name: 'app_webhook_sign')]
|
||||
public function webhookSign(KernelInterface $kernel,UploaderHelper $uploaderHelper,Mailer $mailer,EntityManagerInterface $entityManager,Request $request): Response
|
||||
{
|
||||
if(!$request->headers->has('X-Sign'))
|
||||
return $this->json([],Response::HTTP_BAD_REQUEST);
|
||||
if($request->headers->has('X-Sign') != "SignMainframe")
|
||||
return $this->json([],Response::HTTP_BAD_REQUEST);
|
||||
|
||||
$content = $request->getContent();
|
||||
$content = json_decode($content);
|
||||
$event_type = $content->event_type;
|
||||
$timestamp = $content->timestamp;
|
||||
|
||||
if($event_type == "form.declined") {
|
||||
$metadata = $content->data->metadata;
|
||||
if($metadata->type == "devis") {
|
||||
/** @var CustomerDevis $devis */
|
||||
$devis = $entityManager->getRepository(CustomerDevis::class)->find($metadata->id);
|
||||
$devis->setState("declined - ".$content->data->decline_reason);
|
||||
$entityManager->persist($devis);
|
||||
$entityManager->flush();
|
||||
}
|
||||
}
|
||||
if($event_type == "form.viewed") {
|
||||
$submittersId = $content->data->submission_id;
|
||||
|
||||
$sign = new SignEvent();
|
||||
$sign->setSubmiterEvent($submittersId);
|
||||
$sign->setState($event_type);
|
||||
$sign->setActionAt(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.v\Z',$timestamp));
|
||||
$entityManager->persist($sign);
|
||||
$entityManager->flush();
|
||||
}
|
||||
if($event_type == "submission.completed") {
|
||||
|
||||
$data = $content->data;
|
||||
$submitters = $data->submitters;
|
||||
$submittersId = $submitters[0]->id;
|
||||
$sign = new SignEvent();
|
||||
$sign->setSubmiterEvent($submittersId);
|
||||
$sign->setState( "submission.completed");
|
||||
$sign->setActionAt(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.v\Z',$timestamp));
|
||||
//$entityManager->persist($sign);
|
||||
//$entityManager->flush();
|
||||
|
||||
|
||||
//audit file
|
||||
$auditLogUrl = $content->data->audit_log_url;
|
||||
$documentUrl = $content->data->submitters[0]->documents[0]->url;
|
||||
|
||||
$auditContent = file_get_contents($auditLogUrl);
|
||||
$signContent = file_get_contents($documentUrl);
|
||||
|
||||
$tmpAuditName = "audit-".Uuid::v4().".pdf";
|
||||
$dirAudit = sys_get_temp_dir().'/'.$tmpAuditName;
|
||||
|
||||
$tmpSignName = "sign-".Uuid::v4().".pdf";
|
||||
$dirSign = sys_get_temp_dir().'/'.$tmpSignName;
|
||||
file_put_contents($dirAudit,$auditContent);
|
||||
file_put_contents($dirSign,$signContent);
|
||||
|
||||
$metadata = $content->data->submitters[0]->metadata;
|
||||
if($metadata->type == "devis") {
|
||||
/** @var CustomerDevis $devis */
|
||||
$devis = $entityManager->getRepository(CustomerDevis::class)->find($metadata->id);
|
||||
$devis->setState("accepted");
|
||||
|
||||
|
||||
$uploadFileSign = new \Symfony\Component\HttpFoundation\File\UploadedFile($dirSign,"Signé - ".$devis->getNumDevis().".pdf","application/pdf",0,true);
|
||||
$uploadFileAudit = new \Symfony\Component\HttpFoundation\File\UploadedFile($dirAudit,"Audit - ".$devis->getNumDevis().".pdf","application/pdf",0,true);
|
||||
|
||||
$devis->setDevisSign($uploadFileSign);
|
||||
$devis->setDevisAudit($uploadFileAudit);
|
||||
$devis->setSignAt(new \DateTimeImmutable());
|
||||
$entityManager->persist($devis);
|
||||
$entityManager->flush();
|
||||
|
||||
|
||||
$files =[];
|
||||
$files[] = new DataPart(file_get_contents($kernel->getProjectDir()."/public".$uploaderHelper->asset($devis,"devis")),"Devis ".$devis->getNumDevis().".pdf");
|
||||
$files[] = new DataPart($signContent,"Attestation de signature ".$devis->getNumDevis()." - devis.pdf");
|
||||
$mailer->send($devis->getCustomer()->mainContact()->getEmail(),$devis->getCustomer()->getRaisonSocial(),"[SARL SITECONSEIL] - Signature de votre de devis","mails/customer/devis-sign.twig",[
|
||||
'devis' => $devis,
|
||||
'customer' => $devis->getCustomer(),
|
||||
'contact' => $devis->getCustomer()->mainContact(),
|
||||
],$files);
|
||||
$mailer->send("s.com@siteconseil.fr","SARL SITECONSEIL","[SARL SITECONSEIL] - Signature du devis","mails/customer/devis-sign-customer.twig",[
|
||||
'devis' => $devis,
|
||||
'customer' => $devis->getCustomer(),
|
||||
'contact' => $devis->getCustomer()->mainContact(),
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
if($event_type == "submission.created") {
|
||||
$data = $content->data;
|
||||
$submitters = $data->submitters;
|
||||
$submittersId = $submitters[0]->id;
|
||||
|
||||
$sign = new SignEvent();
|
||||
$sign->setSubmiterEvent($submittersId);
|
||||
$sign->setState( "submission.created");
|
||||
$sign->setActionAt(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.v\Z',$timestamp));
|
||||
$entityManager->persist($sign);
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
return $this->json([]);
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,32 @@ class CustomerDevis
|
||||
#[ORM\OneToMany(targetEntity: CustomerDevisLine::class, mappedBy: 'devis')]
|
||||
private Collection $customerDevisLines;
|
||||
|
||||
#[Vich\UploadableField(mapping: 'devis_audit',fileNameProperty: 'devisAuditFileName', size: 'devisAuditSize', mimeType: 'devisAuditMineType', originalName: 'devisAuditOriginalName',dimensions: 'devisAuditDimensions')]
|
||||
private ?File $devisAudit = null;
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?string $devisAuditFileName = null;
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?array $devisAuditDimensions = [];
|
||||
#[ORM\Column(length: 255,nullable: true)]
|
||||
private ?string $devisAuditSize = null;
|
||||
#[ORM\Column(length: 255,nullable: true)]
|
||||
private ?string $devisAuditMineType = null;
|
||||
#[ORM\Column(length: 255,nullable: true)]
|
||||
private ?string $devisAuditOriginalName = null;
|
||||
|
||||
#[Vich\UploadableField(mapping: 'devis_sign',fileNameProperty: 'devisSignFileName', size: 'devisSignSize', mimeType: 'devisSignMineType', originalName: 'devisSignOriginalName',dimensions: 'devisSignDimensions')]
|
||||
private ?File $devisSign = null;
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?string $devisSignFileName = null;
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?array $devisSignDimensions = [];
|
||||
#[ORM\Column(length: 255,nullable: true)]
|
||||
private ?string $devisSignSize = null;
|
||||
#[ORM\Column(length: 255,nullable: true)]
|
||||
private ?string $devisSignMineType = null;
|
||||
#[ORM\Column(length: 255,nullable: true)]
|
||||
private ?string $devisSignOriginalName = null;
|
||||
|
||||
#[Vich\UploadableField(mapping: 'devis',fileNameProperty: 'devisFileName', size: 'devisSize', mimeType: 'devisMineType', originalName: 'devisOriginalName',dimensions: 'devisDimensions')]
|
||||
private ?File $devis = null;
|
||||
|
||||
@@ -330,4 +356,210 @@ class CustomerDevis
|
||||
$this->devisOriginalName = $devisOriginalName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param File|null $devisAudit
|
||||
*/
|
||||
public function setDevisAudit(?File $devisAudit): void
|
||||
{
|
||||
$this->devisAudit = $devisAudit;
|
||||
if($devisAudit !== null) {
|
||||
$this->updateAt = new \DateTimeImmutable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param File|null $devisSign
|
||||
*/
|
||||
public function setDevisSign(?File $devisSign): void
|
||||
{
|
||||
$this->devisSign = $devisSign;
|
||||
if($devisSign !== null) {
|
||||
$this->updateAt = new \DateTimeImmutable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return File|null
|
||||
*/
|
||||
public function getDevisAudit(): ?File
|
||||
{
|
||||
return $this->devisAudit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return File|null
|
||||
*/
|
||||
public function getDevisSign(): ?File
|
||||
{
|
||||
return $this->devisSign;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $devisAuditDimensions
|
||||
*/
|
||||
public function setDevisAuditDimensions(?array $devisAuditDimensions): void
|
||||
{
|
||||
$this->devisAuditDimensions = $devisAuditDimensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $devisAuditFileName
|
||||
*/
|
||||
public function setDevisAuditFileName(?string $devisAuditFileName): void
|
||||
{
|
||||
$this->devisAuditFileName = $devisAuditFileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $devisAuditMineType
|
||||
*/
|
||||
public function setDevisAuditMineType(?string $devisAuditMineType): void
|
||||
{
|
||||
$this->devisAuditMineType = $devisAuditMineType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $customerDevisLines
|
||||
*/
|
||||
public function setCustomerDevisLines(Collection $customerDevisLines): void
|
||||
{
|
||||
$this->customerDevisLines = $customerDevisLines;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $devisAuditOriginalName
|
||||
*/
|
||||
public function setDevisAuditOriginalName(?string $devisAuditOriginalName): void
|
||||
{
|
||||
$this->devisAuditOriginalName = $devisAuditOriginalName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $devisSignDimensions
|
||||
*/
|
||||
public function setDevisSignDimensions(?array $devisSignDimensions): void
|
||||
{
|
||||
$this->devisSignDimensions = $devisSignDimensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $devisAuditSize
|
||||
*/
|
||||
public function setDevisAuditSize(?string $devisAuditSize): void
|
||||
{
|
||||
$this->devisAuditSize = $devisAuditSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $devisSignFileName
|
||||
*/
|
||||
public function setDevisSignFileName(?string $devisSignFileName): void
|
||||
{
|
||||
$this->devisSignFileName = $devisSignFileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $devisSignMineType
|
||||
*/
|
||||
public function setDevisSignMineType(?string $devisSignMineType): void
|
||||
{
|
||||
$this->devisSignMineType = $devisSignMineType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $devisSignOriginalName
|
||||
*/
|
||||
public function setDevisSignOriginalName(?string $devisSignOriginalName): void
|
||||
{
|
||||
$this->devisSignOriginalName = $devisSignOriginalName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $devisSignSize
|
||||
*/
|
||||
public function setDevisSignSize(?string $devisSignSize): void
|
||||
{
|
||||
$this->devisSignSize = $devisSignSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|null
|
||||
*/
|
||||
public function getDevisAuditDimensions(): ?array
|
||||
{
|
||||
return $this->devisAuditDimensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDevisAuditFileName(): ?string
|
||||
{
|
||||
return $this->devisAuditFileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDevisAuditMineType(): ?string
|
||||
{
|
||||
return $this->devisAuditMineType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDevisAuditOriginalName(): ?string
|
||||
{
|
||||
return $this->devisAuditOriginalName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDevisAuditSize(): ?string
|
||||
{
|
||||
return $this->devisAuditSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|null
|
||||
*/
|
||||
public function getDevisSignDimensions(): ?array
|
||||
{
|
||||
return $this->devisSignDimensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDevisSignFileName(): ?string
|
||||
{
|
||||
return $this->devisSignFileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDevisSignMineType(): ?string
|
||||
{
|
||||
return $this->devisSignMineType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDevisSignOriginalName(): ?string
|
||||
{
|
||||
return $this->devisSignOriginalName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDevisSignSize(): ?string
|
||||
{
|
||||
return $this->devisSignSize;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
65
src/Entity/SignEvent.php
Normal file
65
src/Entity/SignEvent.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\SignEventRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: SignEventRepository::class)]
|
||||
class SignEvent
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?int $submiterEvent = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $state = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $actionAt = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getSubmiterEvent(): ?int
|
||||
{
|
||||
return $this->submiterEvent;
|
||||
}
|
||||
|
||||
public function setSubmiterEvent(int $submiterEvent): static
|
||||
{
|
||||
$this->submiterEvent = $submiterEvent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getState(): ?string
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
public function setState(string $state): static
|
||||
{
|
||||
$this->state = $state;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getActionAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->actionAt;
|
||||
}
|
||||
|
||||
public function setActionAt(\DateTimeImmutable $actionAt): static
|
||||
{
|
||||
$this->actionAt = $actionAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
43
src/Repository/SignEventRepository.php
Normal file
43
src/Repository/SignEventRepository.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\SignEvent;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<SignEvent>
|
||||
*/
|
||||
class SignEventRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, SignEvent::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return SignEvent[] Returns an array of SignEvent objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('s')
|
||||
// ->andWhere('s.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('s.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?SignEvent
|
||||
// {
|
||||
// return $this->createQueryBuilder('s')
|
||||
// ->andWhere('s.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
@@ -3,17 +3,21 @@
|
||||
namespace App\Service\Customer;
|
||||
|
||||
use App\Service\Customer\Billing\CreateDevisCustomerEvent;
|
||||
use App\Service\Docuseal\SignClient;
|
||||
use App\Service\Mailer\Mailer;
|
||||
use App\Service\Pdf\DevisPdf;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
#[AsEventListener(event: CreateDevisCustomerEvent::class, method: 'onBillingEvent')]
|
||||
class BillingEventSusbriber
|
||||
{
|
||||
|
||||
public function __construct(private readonly EntityManagerInterface $entityManager,private KernelInterface $kernel)
|
||||
public function __construct(private readonly Mailer $mailer,private readonly SignClient $signClient,private readonly EntityManagerInterface $entityManager,private KernelInterface $kernel)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -25,14 +29,28 @@ class BillingEventSusbriber
|
||||
|
||||
$pdf = New DevisPdf($this->kernel,$devis);
|
||||
|
||||
$dir =tempnam(sys_get_temp_dir(),"dv-").".pdf";
|
||||
$pdf->Output($dir,'F');
|
||||
|
||||
$tmpname = Uuid::v4().".pdf";
|
||||
$dir = sys_get_temp_dir().'/'.$tmpname;
|
||||
$pdf->generate();
|
||||
$content = $pdf->Output('S');
|
||||
file_put_contents($dir,$content);
|
||||
$upload = new UploadedFile($dir,"devis-".$devis->getNumDevis().".pdf","application/pdf",0,true);
|
||||
$devis->setDevis($upload);
|
||||
|
||||
$this->entityManager->persist($devis);
|
||||
$this->entityManager->flush();
|
||||
|
||||
if($send) {
|
||||
|
||||
$files =[];
|
||||
$files[] = new DataPart($content,"Devis ".$devis->getNumDevis().".pdf");
|
||||
|
||||
$url = $this->signClient->createSubmissionDevis($content,$devis);
|
||||
$this->mailer->send($devis->getCustomer()->mainContact()->getEmail(),$devis->getCustomer()->getRaisonSocial(),"[SARL SITECONSEIL] - Nouveaux devis à signée","mails/customer/devis-wait.twig",[
|
||||
'devis' => $devis,
|
||||
'customer' => $devis->getCustomer(),
|
||||
'url' => $url,
|
||||
],$files);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
79
src/Service/Docuseal/SignClient.php
Normal file
79
src/Service/Docuseal/SignClient.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Docuseal;
|
||||
|
||||
use App\Entity\CustomerDevis;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
class SignClient
|
||||
{
|
||||
private \Docuseal\Api $docuseal;
|
||||
|
||||
public function __construct(private readonly UrlGeneratorInterface $urlGenerator,private readonly EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->docuseal = new \Docuseal\Api($_ENV['DOCUSIGN_KEY'], $_ENV['DOCUSIGN_URL']);
|
||||
|
||||
}
|
||||
|
||||
public function createSubmissionDevis(string $content,CustomerDevis $devis)
|
||||
{
|
||||
|
||||
$t = new \DateTimeImmutable();
|
||||
|
||||
if($devis->getDevisSubmiterId() == null) {
|
||||
$submissionId = $this->docuseal->createSubmission([
|
||||
'template_id' => 1,
|
||||
'send_email' => false,
|
||||
'completed_redirect_url' => $this->urlGenerator->generate('app_sign_complete',['type'=>'devis','id'=>$devis->getId()],UrlGeneratorInterface::ABSOLUTE_URL),
|
||||
'submitters' => [
|
||||
[
|
||||
'role' => 'Client',
|
||||
'email' => $devis->getCustomer()->mainContact()->getEmail(),
|
||||
'metadata' => [
|
||||
'type' =>'devis',
|
||||
'id' => $devis->getId(),
|
||||
],
|
||||
'fields' => [
|
||||
[
|
||||
'name' => 'Numéro devis',
|
||||
'value' => $devis->getNumDevis(),
|
||||
'readonly' => true,
|
||||
],
|
||||
[
|
||||
'name' => 'Raison Social',
|
||||
'value' => $devis->getCustomer()->getRaisonSocial(),
|
||||
'readonly' => true,
|
||||
],
|
||||
[
|
||||
'name' => 'Adresse',
|
||||
'value' => $devis->getCustomer()->getAddress(),
|
||||
'readonly' => true,
|
||||
],
|
||||
[
|
||||
'name' => 'Email',
|
||||
'value' => $devis->getCustomer()->mainContact()->getEmail(),
|
||||
'readonly' => true,
|
||||
],
|
||||
[
|
||||
'name' => 'Date Signature',
|
||||
'value' => 'Signée le ' . $t->format('d/m/Y H:i:s'),
|
||||
'readonly' => true,
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
]);
|
||||
$devis->setDevisSubmiterId($submissionId['id']);
|
||||
$this->entityManager->persist($devis);
|
||||
$this->entityManager->flush();
|
||||
$submissionData = $this->docuseal->getSubmitter($devis->getDevisSubmiterId());
|
||||
} else {
|
||||
$submissionData = $this->docuseal->getSubmitter($devis->getDevisSubmiterId());
|
||||
}
|
||||
|
||||
return "https://signature.esy-web.dev/s/".$submissionData['slug'];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -87,7 +87,7 @@ class Mailer
|
||||
|
||||
}
|
||||
|
||||
public function send(string $address, string $addressName, string $subject, string $template, array $data)
|
||||
public function send(string $address, string $addressName, string $subject, string $template, array $data,array $files = [])
|
||||
{
|
||||
$dest = new Address($address, $addressName);
|
||||
$src = new Address("mainframe@esy-web.dev", "Mainframe EsyWeb");
|
||||
@@ -110,6 +110,9 @@ class Mailer
|
||||
]);
|
||||
$htmlContent = $this->convertMjmlToHtml($mjmlGenerator);
|
||||
$object->setContent($htmlContent);
|
||||
foreach ($files as $file) {
|
||||
$mail->addPart($file);
|
||||
}
|
||||
|
||||
$mail->html($htmlContent);
|
||||
try {
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
namespace App\Service\Pdf;
|
||||
|
||||
use App\Entity\CustomerDevis;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
use Fpdf\Fpdf;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
define('EURO',chr(128));
|
||||
|
||||
class DevisPdf extends FPDF
|
||||
{
|
||||
@@ -12,19 +14,18 @@ class DevisPdf extends FPDF
|
||||
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
|
||||
],
|
||||
];
|
||||
$items = [];
|
||||
foreach ($this->customerDevis->getCustomerDevisLines() as $line) {
|
||||
$items[$line->getPos()] =[
|
||||
'title' => $line->getName(),
|
||||
'content' =>$line->getContent(),
|
||||
'priceHt' => $line->getPriceHT(),
|
||||
'priceTTC' => (1.20*$line->getPriceHT()),
|
||||
];
|
||||
}
|
||||
ksort($items);
|
||||
$this->items = $items;
|
||||
$this->SetTitle(mb_convert_encoding("Devis N° ","ISO-8859-1","UTF-8").$this->customerDevis->getNumDevis());
|
||||
}
|
||||
|
||||
function Header()
|
||||
@@ -43,6 +44,23 @@ class DevisPdf extends FPDF
|
||||
$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'));
|
||||
|
||||
|
||||
$y = 40;
|
||||
$this->Text(120,$y,mb_convert_encoding($this->customerDevis->getCustomer()->getRaisonSocial(), 'ISO-8859-1', 'UTF-8'));
|
||||
$y=$y+5;
|
||||
$this->Text(120,$y,mb_convert_encoding($this->customerDevis->getCustomer()->getAddress(), 'ISO-8859-1', 'UTF-8'));
|
||||
if($this->customerDevis->getCustomer()->getAddress2()!="") {
|
||||
$y=$y+5;
|
||||
$this->Text(120,$y,mb_convert_encoding($this->customerDevis->getCustomer()->getAddress2(), 'ISO-8859-1', 'UTF-8'));
|
||||
}
|
||||
if($this->customerDevis->getCustomer()->getAddress3() != "") {
|
||||
$y=$y+5;
|
||||
$this->Text(120,$y,mb_convert_encoding($this->customerDevis->getCustomer()->getAddress3(), 'ISO-8859-1', 'UTF-8'));
|
||||
}
|
||||
$y=$y+5;
|
||||
$this->Text(120,$y,mb_convert_encoding($this->customerDevis->getCustomer()->getZipcode()." ".$this->customerDevis->getCustomer()->getCity(), 'ISO-8859-1', 'UTF-8'));
|
||||
$this->body();
|
||||
}
|
||||
|
||||
function Footer()
|
||||
@@ -55,4 +73,73 @@ class DevisPdf extends FPDF
|
||||
$this->Ln(5);
|
||||
$this->Cell(0, 5, 'Page ' . $this->PageNo() . '/{nb}', 0, 0, 'C');
|
||||
}
|
||||
|
||||
public function body()
|
||||
{
|
||||
$this->Line(120,70,120,220);
|
||||
$this->Line(160,70,160,220);
|
||||
$this->Text(125,70,mb_convert_encoding("PRIX HT","ISO-8859-1","UTF-8"));
|
||||
$this->Text(165,70,mb_convert_encoding("PRIX TTC","ISO-8859-1","UTF-8"));
|
||||
}
|
||||
|
||||
function generate()
|
||||
{
|
||||
$this->AliasNbPages();
|
||||
$this->AddPage();
|
||||
$this->SetFont('Arial', '', 12);
|
||||
foreach($this->items as $item) {
|
||||
if ($this->GetY() + 30 > $this->PageBreakTrigger) {
|
||||
$this->AddPage();
|
||||
$this->body();
|
||||
}
|
||||
$this->SetY($this->GetPageHeight() / 3.75);
|
||||
$this->SetFont('Arial', 'B', 12);
|
||||
$this->Cell(100,10,mb_convert_encoding($item['title'],'ISO-8859-1','UTF-8'),0,0);
|
||||
|
||||
$this->SetFont('Arial', '', 12);
|
||||
$this->SetX($this->GetX()+12);
|
||||
$this->Cell(35,10,number_format($item['priceHt'],2,",")." ".EURO,0);
|
||||
$this->SetX($this->GetX()+5);
|
||||
$this->Cell(35,10,number_format($item['priceTTC'],2,",")." ".EURO,0,true);
|
||||
$this->SetX($this->GetX());
|
||||
$this->MultiCell(100, 5, mb_convert_encoding($item['content'], 'ISO-8859-1', 'UTF-8'), 0);
|
||||
}
|
||||
|
||||
$this->displaySummary();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
function displaySummary()
|
||||
{
|
||||
|
||||
$signTag="{{Signée Ici;type=signature}}";
|
||||
$signDateTag="{{Date;type=date}}";
|
||||
|
||||
// Calculate totals
|
||||
$totalHT = array_sum(array_column($this->items, 'priceHt'));
|
||||
$totalTVA = $totalHT * 0.20; // Assuming TVA is 20%
|
||||
$totalTTC = $totalHT + $totalTVA;
|
||||
|
||||
// Position the summary block at the bottom of the page
|
||||
$this->SetY(-60); // Adjust this value as needed
|
||||
|
||||
// Move to the left for signature and date tags
|
||||
// Add signature and date tags
|
||||
$this->Text(10,$this->GetY()-10, mb_convert_encoding('Pour accepter le devis, signez ici :', 'ISO-8859-1', 'UTF-8'));
|
||||
$this->Text(10,$this->GetY(), mb_convert_encoding($signDateTag, 'ISO-8859-1', 'UTF-8'));
|
||||
$this->Text(10,$this->GetY()+10, mb_convert_encoding($signTag, 'ISO-8859-1', 'UTF-8'));
|
||||
// Display the summary
|
||||
$this->SetFont('Arial', 'B', 12);
|
||||
$this->Cell(150, 10, mb_convert_encoding('Total HT:', 'ISO-8859-1', 'UTF-8'), 0, 0, 'R');
|
||||
$this->Cell(35, 10, number_format($totalHT, 2, ",") . " " . EURO, 0, 1, 'R');
|
||||
|
||||
$this->Cell(150, 10, mb_convert_encoding('TVA (20%):', 'ISO-8859-1', 'UTF-8'), 0, 0, 'R');
|
||||
$this->Cell(35, 10, number_format($totalTVA, 2, ",") . " " . EURO, 0, 1, 'R');
|
||||
|
||||
$this->Cell(150, 10, mb_convert_encoding('Total TTC:', 'ISO-8859-1', 'UTF-8'), 0, 0, 'R');
|
||||
$this->Cell(35, 10, number_format($totalTTC, 2, ",") . " " . EURO, 0, 1, 'R');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,6 @@ class DevisName implements NamerInterface
|
||||
|
||||
public function name(object $object, PropertyMapping $mapping): string
|
||||
{
|
||||
return $object->getNumDevis().".pdf";
|
||||
return sprintf('%05d',$object->getId()).".pdf";
|
||||
}
|
||||
}
|
||||
|
||||
50
templates/admin/sign-complete.twig
Normal file
50
templates/admin/sign-complete.twig
Normal file
@@ -0,0 +1,50 @@
|
||||
{% extends 'admin/base.twig' %}
|
||||
|
||||
{% block title %}Confirmation de Signature{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-md w-full space-y-8 p-10 bg-gray-800 rounded-xl shadow-xl z-10">
|
||||
<h2 class="text-center text-3xl font-extrabold text-white">
|
||||
Confirmation de Signature
|
||||
</h2>
|
||||
<div class="mt-8 rounded-lg p-6 shadow-md">
|
||||
<div class="text-center">
|
||||
<svg class="mx-auto h-12 w-12 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
<h3 class="mt-2 text-xl font-medium text-white-900">
|
||||
Votre document a été signé avec succès
|
||||
</h3>
|
||||
<p class="mt-1 text-white-600">
|
||||
Merci pour votre confirmation. Vous trouverez ci-dessous les détails de votre document signé.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<div class="border-t border-gray-200 pt-4">
|
||||
<dl class="divide-y divide-gray-200">
|
||||
<div class="px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-white-500">Numéro de document</dt>
|
||||
<dd class="mt-1 text-sm text-white-900 sm:mt-0 sm:col-span-2">{{ document.number }}</dd>
|
||||
</div>
|
||||
<div class="px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-white-500">Date de signature</dt>
|
||||
<dd class="mt-1 text-sm text-white-900 sm:mt-0 sm:col-span-2">{{ document.signatureDate|date('d/m/Y H:i') }}</dd>
|
||||
</div>
|
||||
<div class="px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
||||
<dt class="text-sm font-medium text-white-500">Signataire</dt>
|
||||
<dd class="mt-1 text-sm text-white-900 sm:mt-0 sm:col-span-2">{{ document.signer }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6 flex justify-center">
|
||||
<a href="{{ document.path }}" download="" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
Télécharger le document
|
||||
</a>
|
||||
<a href="{{ document.pathSign }}" download="" class="ml-2 inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
Télécharger le document signée
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
17
templates/mails/customer/devis-sign-customer.twig
Normal file
17
templates/mails/customer/devis-sign-customer.twig
Normal file
@@ -0,0 +1,17 @@
|
||||
{% extends 'mails/base.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<mj-text>
|
||||
Bonjour,
|
||||
</mj-text>
|
||||
<mj-text>
|
||||
Nous vous informons que le client {{ datas.customer.raisonSocial }} a signé son devis numéro {{ datas.devis.numDevis }}.
|
||||
</mj-text>
|
||||
<mj-text>
|
||||
Merci de bien vouloir procéder aux étapes comptables nécessaires.
|
||||
</mj-text>
|
||||
<mj-text>
|
||||
Cordialement
|
||||
</mj-text>
|
||||
|
||||
{% endblock %}
|
||||
17
templates/mails/customer/devis-sign.twig
Normal file
17
templates/mails/customer/devis-sign.twig
Normal file
@@ -0,0 +1,17 @@
|
||||
{% extends 'mails/base.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<mj-text>
|
||||
Bonjour,
|
||||
</mj-text>
|
||||
<mj-text>
|
||||
Nous vous confirmons la signature de votre devis numéro {{ datas.devis.numDevis }}. Vous trouverez ci-joint le devis ainsi que l'attestation de signature.
|
||||
</mj-text>
|
||||
<mj-text>
|
||||
Merci pour votre confiance.
|
||||
</mj-text>
|
||||
<mj-text>
|
||||
Cordialement,<br />
|
||||
L'équipe
|
||||
</mj-text>
|
||||
{% endblock %}
|
||||
18
templates/mails/customer/devis-wait.twig
Normal file
18
templates/mails/customer/devis-wait.twig
Normal file
@@ -0,0 +1,18 @@
|
||||
{% extends 'mails/base.twig' %}
|
||||
{% block content %}
|
||||
<mj-text>
|
||||
Bonjour,
|
||||
</mj-text>
|
||||
<mj-text>
|
||||
Nous vous informons qu'un nouveau devis numéro {{ datas.devis.numDevis }} est prêt et attend votre signature. Vous trouverez ci-joint le devis en pièce jointe à cet email.
|
||||
</mj-text>
|
||||
<mj-button href="{{ datas.url }}" background-color="#4CAF50" color="white" font-family="Helvetica, Arial, sans-serif" font-size="16px" font-weight="bold" inner-padding="10px 25px" border-radius="3px">
|
||||
Signer le devis
|
||||
</mj-button>
|
||||
<mj-text>
|
||||
Merci de procéder à la signature dès que possible. Si vous avez des questions ou besoin d'assistance, n'hésitez pas à nous contacter.
|
||||
</mj-text>
|
||||
<mj-text>
|
||||
Cordialement
|
||||
</mj-text>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user