```
✨ feat(contrats): Améliore la gestion des contrats et des paiements.
- Rend le champ details non obligatoire dans add.twig
- Ajoute une valeur par défaut pour isSigned et type dans les entités.
- Corrige l'ajout des lignes et options au contrat.
- Ajoute la création automatique du client Stripe.
```
This commit is contained in:
34
migrations/Version20260206150000.php
Normal file
34
migrations/Version20260206150000.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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 Version20260206150000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Set default value for is_signed in contrats table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE contrats ALTER is_signed SET DEFAULT false');
|
||||
$this->addSql('UPDATE contrats SET is_signed = false WHERE is_signed IS NULL');
|
||||
$this->addSql('ALTER TABLE contrats ALTER is_signed SET NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE contrats ALTER is_signed DROP DEFAULT');
|
||||
$this->addSql('ALTER TABLE contrats ALTER is_signed DROP NOT NULL');
|
||||
}
|
||||
}
|
||||
34
migrations/Version20260206160000.php
Normal file
34
migrations/Version20260206160000.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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 Version20260206160000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Set default value for type in contrats_line table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql("ALTER TABLE contrats_line ALTER type SET DEFAULT 'product'");
|
||||
$this->addSql("UPDATE contrats_line SET type = 'product' WHERE type IS NULL");
|
||||
$this->addSql("ALTER TABLE contrats_line ALTER type SET NOT NULL");
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE contrats_line ALTER type DROP DEFAULT');
|
||||
$this->addSql('ALTER TABLE contrats_line ALTER type DROP NOT NULL');
|
||||
}
|
||||
}
|
||||
@@ -166,7 +166,7 @@ class ContratController extends AbstractController
|
||||
|
||||
$tvaEnabled = isset($_ENV['TVA_ENABLED']) && $_ENV['TVA_ENABLED'] === "true";
|
||||
$totals = $this->calculateContractTotals($contrat, $tvaEnabled);
|
||||
|
||||
|
||||
$totalDays = $totals['days'];
|
||||
$totalHT = $totals['totalHT'];
|
||||
$totalTTC = $totals['totalTTC'];
|
||||
@@ -205,11 +205,11 @@ class ContratController extends AbstractController
|
||||
|
||||
if ($request->query->has('act') && $request->query->get('act') === 'accomptePay') {
|
||||
$response = $this->handlePayment(
|
||||
$contrat,
|
||||
'accompte',
|
||||
$arrhes,
|
||||
$entityManager,
|
||||
$stripeClient,
|
||||
$contrat,
|
||||
'accompte',
|
||||
$arrhes,
|
||||
$entityManager,
|
||||
$stripeClient,
|
||||
false
|
||||
);
|
||||
if ($response) return $response;
|
||||
@@ -222,15 +222,15 @@ class ContratController extends AbstractController
|
||||
$type = $isSoldeTotal ? 'solde' : 'solde_partiel';
|
||||
|
||||
$response = $this->handlePayment(
|
||||
$contrat,
|
||||
$type,
|
||||
$finalAmount,
|
||||
$entityManager,
|
||||
$stripeClient,
|
||||
$contrat,
|
||||
$type,
|
||||
$finalAmount,
|
||||
$entityManager,
|
||||
$stripeClient,
|
||||
$isSoldeTotal
|
||||
);
|
||||
if ($response) return $response;
|
||||
|
||||
|
||||
// Fallback
|
||||
$this->addFlash('error', 'Impossible de générer le lien de paiement.');
|
||||
return new RedirectResponse($request->headers->get('referer'));
|
||||
@@ -291,7 +291,8 @@ class ContratController extends AbstractController
|
||||
EntityManagerInterface $em,
|
||||
UserPasswordHasherInterface $hasher,
|
||||
CustomerRepository $customerRepository,
|
||||
Security $security // Injection du service Security
|
||||
Security $security, // Injection du service Security
|
||||
\App\Service\Stripe\Client $stripeClient
|
||||
): Response {
|
||||
$session = $request->getSession();
|
||||
$customer = $session->get('config_customer_id') ? $customerRepository->find($session->get('config_customer_id')) : null;
|
||||
@@ -315,9 +316,17 @@ class ContratController extends AbstractController
|
||||
$customer->setVerificationCode(null);
|
||||
$customer->setVerificationCodeExpiresAt(null);
|
||||
|
||||
// Création Stripe automatique
|
||||
try {
|
||||
$stripeClient->createCustomer($customer);
|
||||
} catch (\Exception $e) {
|
||||
// Log l'erreur mais ne bloque pas le process, on pourra le refaire plus tard
|
||||
// $logger->error('Erreur création Stripe auto : ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$em->flush();
|
||||
|
||||
$security->login($customer, 'form_login', 'main');
|
||||
$security->login($customer, \App\Security\CustomerAuthenticator::class, 'main');
|
||||
|
||||
return $this->render('reservation/contrat/finish_activate.twig', [
|
||||
'customer' => $customer,
|
||||
@@ -421,13 +430,13 @@ class ContratController extends AbstractController
|
||||
|
||||
foreach ($contrat->getContratsLines() as $line) {
|
||||
$linePriceHT = $line->getPrice1DayHt();
|
||||
|
||||
|
||||
if ($totalDays > 1) {
|
||||
$linePriceHT += (($line->getPriceSupDayHt()) * ($totalDays - 1));
|
||||
}
|
||||
|
||||
|
||||
$totalHT += $linePriceHT;
|
||||
|
||||
|
||||
if ($tvaEnabled) {
|
||||
// Calculation matches original logic: (Price1Day * 1.2) + (PriceSup * 1.2 * days)
|
||||
$linePriceTTC = $line->getPrice1DayHt() * 1.20;
|
||||
@@ -479,7 +488,7 @@ class ContratController extends AbstractController
|
||||
'state' => ['complete', 'created'],
|
||||
'type' => $type
|
||||
];
|
||||
|
||||
|
||||
// For solde/solde_partiel, we only check for 'created' to allow new attempts if previous failed/expired
|
||||
// but typically 'solde' payments are unique or sequential.
|
||||
if ($type === 'solde' || $type === 'solde_partiel') {
|
||||
@@ -493,6 +502,18 @@ class ContratController extends AbstractController
|
||||
$existingPayment = $em->getRepository(ContratsPayments::class)->findOneBy($criteria);
|
||||
|
||||
if (!$existingPayment) {
|
||||
// Check if customer exists in Stripe
|
||||
$customer = $contrat->getCustomer();
|
||||
if (!$customer->getCustomerId()) {
|
||||
try {
|
||||
$stripeClient->createCustomer($customer);
|
||||
$em->flush();
|
||||
} catch (\Exception $e) {
|
||||
// Log error or handle it, maybe return null to show error
|
||||
// For now we continue, assuming createPayment might fail gracefully or we just try
|
||||
}
|
||||
}
|
||||
|
||||
// Create new payment intent
|
||||
if ($type === 'accompte') {
|
||||
$result = $stripeClient->createPaymentAccompte($amount, $contrat);
|
||||
|
||||
@@ -331,15 +331,16 @@ class ContratsController extends AbstractController
|
||||
->setPriceSupDayHt($line['priceHtSupDay'])
|
||||
->setCaution($line['caution']);
|
||||
$this->em->persist($vc);
|
||||
$contrat->addContratsLine($vc);
|
||||
}
|
||||
|
||||
foreach ($postData['options'] ?? [] as $opt) {
|
||||
$vo = (new ContratsOption())
|
||||
->setContrat($contrat)
|
||||
->setName($opt['name'])
|
||||
->setDetails($opt['details'])
|
||||
->setPrice($opt['priceHt']);
|
||||
$this->em->persist($vo);
|
||||
$contrat->addContratsOption($vo);
|
||||
}
|
||||
|
||||
$contrat->setNumReservation($this->generateReservationNumber());
|
||||
|
||||
@@ -68,8 +68,8 @@ class Contrats
|
||||
#[ORM\Column(type: Types::TEXT,nullable: true)]
|
||||
private ?string $notes = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?bool $isSigned = null;
|
||||
#[ORM\Column(options: ['default' => false])]
|
||||
private ?bool $isSigned = false;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $signID = null;
|
||||
|
||||
@@ -28,8 +28,8 @@ class ContratsLine
|
||||
#[ORM\Column]
|
||||
private ?float $caution = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $type = null;
|
||||
#[ORM\Column(length: 255, options: ['default' => 'product'])]
|
||||
private ?string $type = 'product';
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
</div>
|
||||
<div class="lg:col-span-2">
|
||||
<label class="text-[9px] font-black text-slate-300 uppercase tracking-widest ml-1 mb-2 block">Détails</label>
|
||||
<input type="text" name="options[{{ key }}][details]" value="{{ line.details }}" required class="w-full bg-slate-950/50 border-white/5 rounded-2xl text-white focus:ring-purple-500/20 focus:border-purple-500 transition-all py-3 px-5 text-sm font-mono">
|
||||
<input type="text" name="options[{{ key }}][details]" value="{{ line.details }}" class="w-full bg-slate-950/50 border-white/5 rounded-2xl text-white focus:ring-purple-500/20 focus:border-purple-500 transition-all py-3 px-5 text-sm font-mono">
|
||||
</div>
|
||||
{# 2. PRIX 1J #}
|
||||
<div class="lg:col-span-3">
|
||||
|
||||
@@ -89,16 +89,6 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# CAUTION #}
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<span class="text-[10px] font-bold text-gray-400 uppercase">Caution</span>
|
||||
{% if contratPaymentPay(contrat, 'caution') %}
|
||||
<span class="text-[10px] font-black text-green-600 bg-green-100 px-2 py-0.5 rounded-lg uppercase tracking-tight">Réceptionnée</span>
|
||||
{% else %}
|
||||
<span class="text-[10px] font-black text-red-500 bg-red-100 px-2 py-0.5 rounded-lg uppercase tracking-tight">Manquante</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# SOLDE #}
|
||||
<div class="flex items-center gap-2 px-3 py-2 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<span class="text-[10px] font-bold text-gray-400 uppercase">Solde</span>
|
||||
|
||||
Reference in New Issue
Block a user