feat(Customer): Ajoute la relation OneToMany avec l'entité Website

 feat(esyweb): Ajoute un contrôleur et une page pour les sites web

🐛 fix(register): Corrige l'enregistrement des paiements partiels et complets

 feat(Payment): Gère les paiements complets et partiels via Stancer

 feat(BillingEvent): Ajoute des listeners pour les paiements complétés
This commit is contained in:
Serreau Jovann
2025-10-07 14:04:16 +02:00
parent 6c3b6aae43
commit 21f70606ee
16 changed files with 527 additions and 53 deletions

View File

@@ -4,26 +4,31 @@ export class RegisterPayment extends HTMLButtonElement {
element.addEventListener('click', (event) => {
event.preventDefault();
const advertId = element.getAttribute('id');
let modal = document.createElement('div');
modal.classList = "modal-payment"
modal.innerHTML =`
<div class="modal-payment-content">
<h2>Enregistrée un paiement</h2>
<a class="block dbclose bg-red-600 hover:bg-red-700 text-white px-3 py-1 rounded">Fermer</a>
<form method="post" class="content">
// Classes de fond du modal Dark Mode
modal.classList.add("modal-payment", "fixed", "inset-0", "bg-gray-900", "bg-opacity-75", "flex", "items-center", "justify-center", "z-50");
modal.innerHTML = `
<div class="modal-payment-content dark:bg-gray-800 bg-white shadow-2xl rounded-lg p-6 relative" style="max-width: 900px; width: 90%; max-height: 90vh; overflow-y: auto;">
<h2 class="text-xl font-bold dark:text-white mb-4">Enregistrer un paiement</h2>
<a class="block dbclose bg-red-600 hover:bg-red-700 text-white px-3 py-1 rounded" style="cursor: pointer; position: absolute; top: 10px; right: 10px;">Fermer</a>
<form method="post" class="content mb-6">
<div class="flex space-x-4 w-full">
<fieldset class="form-group form-group--horizontal w-full">
<div class="flex space-x-4">
<div class="flex-1">
<div class="form-field">
<div class="mb-1">
<label for="titre" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Type</label>
<select name="type" 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>
<label for="type" class="block mb-2 text-sm font-medium dark:text-gray-300 text-gray-900">Type</label>
<select name="type" 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>
<option value="CB">Carte Bancaire</option>
<option value="PREV">Prévélevement</option>
<option value="CH">Chéque</option>
<option value="ESP">Espéce</option>
<option value="PREV">Prévement</option>
<option value="CH">Chèque</option>
<option value="ESP">Espèce</option>
<option value="VIR">Virement</option>
</select>
</div>
@@ -32,65 +37,186 @@ export class RegisterPayment extends HTMLButtonElement {
<div class="flex-1">
<div class="form-field">
<div class="mb-1">
<label for="titre" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Date</label>
<input name="date" type="date" 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 />
<label for="date" class="block mb-2 text-sm font-medium dark:text-gray-300 text-gray-900">Date</label>
<input name="date" type="date" 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>
</div>
<div class="flex-1">
<div class="form-field">
<div class="mb-1">
<label for="titre" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Montant</label>
<input name="amount" type="number" step="0.01" 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 />
<label for="amount" class="block mb-2 text-sm font-medium dark:text-gray-300 text-gray-900">Montant</label>
<input name="amount" type="number" step="0.01" 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>
</div>
<div class="flex-1">
<div class="form-field">
<div class="mb-1">
<label for="titre" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Numéro de remise de chéque</label>
<input name="num_cheque" type="text" 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" />
<label for="num_cheque" class="block mb-2 text-sm font-medium dark:text-gray-300 text-gray-900">Numéro de remise de chèque</label>
<input name="num_cheque" type="text" 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" />
</div>
</div>
</div>
</div>
</fieldset>
</div>
<button type="submit" class="w-full bg-purple-600 hover:bg-purple-700 text-white font-semibold px-4 py-2 rounded">Enregistrer</button>
<button type="submit" class="w-full bg-purple-600 hover:bg-purple-700 text-white font-semibold px-4 py-2 rounded mt-3">Enregistrer</button>
</form>
<h3 class="mt-6 text-lg font-semibold dark:text-white border-t border-gray-700 pt-4">Historique des paiements</h3>
<div class="overflow-x-auto">
<table id="payment-table" class="min-w-full divide-y dark:divide-gray-700 divide-gray-200">
<thead>
<tr>
<th class="px-6 py-3 dark:bg-gray-700 bg-gray-100 text-left text-xs font-medium dark:text-gray-300 text-gray-500 uppercase tracking-wider">Date</th>
<th class="px-6 py-3 dark:bg-gray-700 bg-gray-100 text-left text-xs font-medium dark:text-gray-300 text-gray-500 uppercase tracking-wider">Type</th>
<th class="px-6 py-3 dark:bg-gray-700 bg-gray-100 text-left text-xs font-medium dark:text-gray-300 text-gray-500 uppercase tracking-wider">Montant</th>
<th class="px-6 py-3 dark:bg-gray-700 bg-gray-100 text-left text-xs font-medium dark:text-gray-300 text-gray-500 uppercase tracking-wider">Réf. Chèque</th>
<th class="px-6 py-3 dark:bg-gray-700 bg-gray-100 text-right text-xs font-medium dark:text-gray-300 text-gray-500 uppercase tracking-wider">Action</th>
</tr>
</thead>
<tbody class="dark:bg-gray-800 bg-white divide-y dark:divide-gray-700 divide-gray-200">
<tr id="loading-row"><td colspan="6" class="px-6 py-4 whitespace-nowrap text-sm dark:text-gray-400 text-gray-500 text-center">Chargement...</td></tr>
</tbody>
</table>
</div>
</div>
`;
modal.querySelector('.dbclose').addEventListener('click',()=>{
const paymentTableBody = modal.querySelector('#payment-table tbody');
/**
* Fonction pour récupérer et afficher la liste des paiements.
*/
const loadPayments = () => {
paymentTableBody.innerHTML = '<tr id="loading-row"><td colspan="6" class="px-6 py-4 whitespace-nowrap text-sm dark:text-gray-400 text-gray-500 text-center">Chargement...</td></tr>';
fetch(`/api-interne/intranet/customer/register?idPayment=${advertId}`)
.then(response => {
if (!response.ok) {
throw new Error('Erreur lors de la récupération des paiements (code: ' + response.status + ')');
}
return response.json();
})
.then((listPayment) => {
paymentTableBody.innerHTML = '';
if (listPayment && listPayment.length > 0) {
listPayment.forEach(payment => {
const tr = document.createElement('tr');
const date = new Date(payment.date).toLocaleDateString();
// Classes pour les lignes (alternance des couleurs en dark mode)
tr.classList.add('hover:bg-gray-700');
tr.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap text-sm dark:text-gray-300 text-gray-500">${date}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm dark:text-gray-300 text-gray-500">${payment.type}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-bold text-green-400 dark:text-green-400">${parseFloat(payment.amount).toFixed(2)} €</td>
<td class="px-6 py-4 whitespace-nowrap text-sm dark:text-gray-300 text-gray-500">${payment.num_cheque || ''}</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button data-payment-id="${payment.id}" class="delete-payment-btn text-red-500 hover:text-red-400 dark:text-red-400 dark:hover:text-red-300">Supprimer</button>
</td>
`;
paymentTableBody.appendChild(tr);
});
modal.querySelectorAll('.delete-payment-btn').forEach(button => {
button.addEventListener('click', (e) => {
const paymentId = e.currentTarget.dataset.paymentId;
deletePayment(paymentId, loadPayments);
});
});
} else {
paymentTableBody.innerHTML = '<tr><td colspan="6" class="px-6 py-4 whitespace-nowrap text-sm dark:text-gray-400 text-gray-500 text-center">Aucun paiement enregistré pour le moment.</td></tr>';
}
})
.catch(error => {
console.error("Erreur de chargement des paiements:", error);
paymentTableBody.innerHTML = `<tr><td colspan="6" class="px-6 py-4 whitespace-nowrap text-sm text-red-500 text-center">Erreur lors du chargement: ${error.message}</td></tr>`;
});
};
/**
* Fonction pour envoyer la requête de suppression de paiement.
*/
const deletePayment = (paymentId, callback) => {
if (!confirm(`Êtes-vous sûr de vouloir supprimer le paiement ?`)) {
return;
}
fetch(`/api-interne/intranet/customer/payment/${paymentId}`, {
method: 'DELETE',
})
.then(response => {
if (!response.ok) {
throw new Error('Échec de la suppression.');
}
return response.json();
})
.then(() => {
alert('Paiement supprimé avec succès.');
callback(); // Recharger la liste des paiements
})
.catch(error => {
console.error("Erreur de suppression:", error);
alert(`Erreur lors de la suppression du paiement: ${error.message}`);
});
};
// 1. Charger les paiements lors de l'ouverture du modal
loadPayments();
// 2. Écouteur pour la fermeture du modal
modal.querySelector('.dbclose').addEventListener('click', () => {
modal.remove();
})
modal.querySelector('form').addEventListener('submit',(e)=>{
// 3. Écouteur pour l'enregistrement d'un nouveau paiement
modal.querySelector('form').addEventListener('submit', (e) => {
e.preventDefault();
const formData = {};
const formElements = e.target.elements;
for (let i = 0; i < formElements.length; i++) {
const el = formElements[i];
if (el.name) {
formData[el.name] = el.value;
}
}
formData['idAdvert'] = advertId;
let inputs = [];
modal.querySelectorAll('select').forEach(selectItem=>{
inputs.push({
name: selectItem.name,
val: selectItem.value
})
})
modal.querySelectorAll('input').forEach(inputItem=>{
inputs[inputItem.name] = inputItem.value;
inputs.push({
name: inputItem.name,
val: inputItem.value
})
})
inputs.push({
name : 'idAdvert',
val :element.getAttribute('id')
});
fetch("/api-interne/intranet/customer/register",{
method:'POST',
Object.keys(formData).forEach(key => inputs.push({ name: key, val: formData[key] }));
fetch("/api-interne/intranet/customer/register", {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(inputs),
}).then(response=>response.json())
.then(()=>{
//location.reload();
})
.then(response => {
if (!response.ok) {
return response.json().then(err => { throw new Error(err.message || 'Erreur lors de l\'enregistrement'); });
}
return response.json();
})
.then(() => {
loadPayments();
e.target.reset();
})
.catch(error => {
console.error("Erreur lors de l'enregistrement:", error);
alert("Une erreur est survenue lors de l'enregistrement du paiement: " + error.message);
});
})
document.body.appendChild(modal);
})
}

View File

@@ -0,0 +1,36 @@
<?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 Version20251007110952 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 website (id SERIAL NOT NULL, customer_id INT DEFAULT NULL, uuid UUID NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_476F5DE79395C3F3 ON website (customer_id)');
$this->addSql('COMMENT ON COLUMN website.uuid IS \'(DC2Type:uuid)\'');
$this->addSql('ALTER TABLE website ADD CONSTRAINT FK_476F5DE79395C3F3 FOREIGN KEY (customer_id) REFERENCES customer (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
}
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 website DROP CONSTRAINT FK_476F5DE79395C3F3');
$this->addSql('DROP TABLE website');
}
}

View File

@@ -15,11 +15,30 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class RegisterController extends AbstractController
{
#[Route(path: '/api-interne/intranet/customer/register',name: 'api-interne-intranet-customer-register')]
public function customerRegisterPayment(EventDispatcherInterface $eventDispatcher,EntityManagerInterface $entityManager,Request $request,CustomerAdvertPaymentRepository $customerAdvertPaymentRepository): Response {
public function customerRegisterPayment(EventDispatcherInterface $eventDispatcher,EntityManagerInterface $entityManager,Request $request,CustomerAdvertPaymentRepository $customerAdvertPaymentRepository,
TranslatorInterface $translator): Response {
if($request->isMethod('GET')) {
$advert = $customerAdvertPaymentRepository->find($request->query->get('idPayment'));
$listPayments = [];
foreach ($advert->getCustomerAdvertPaymentRegisters() as $payment) {
$listPayments[] = [
'id' => $payment->getId(),
'amount' => $payment->getAmount(),
'type' => $translator->trans('register_'.$payment->getType()),
'num_cheque' => $payment->getNumeroRemise(),
'date'=> $payment->getCreateAt()->format('Y-m-d'),
];
}
return $this->json($listPayments);
}
$content = $request->getContent();
$content = json_decode($content);
$format = [];
@@ -31,6 +50,13 @@ class RegisterController extends AbstractController
foreach ($advert->getCustomerAdvertPaymentLines() as $customerAdvertPaymentLine)
$total += (1.20*$customerAdvertPaymentLine->getPriceHt());
$totalPay = 0;
foreach ($advert->getCustomerAdvertPaymentRegisters() as $customerAdvertPaymentRegister)
$totalPay += $customerAdvertPaymentRegister->getAmount();
if($total - $totalPay <=0)
return $this->json([]);
$advertRegister = new CustomerAdvertPaymentRegister();
$advertRegister->setAdvert($advert);
$advertRegister->setCreateAt(\DateTimeImmutable::createFromFormat('Y-m-d', $format['date']));
@@ -43,15 +69,17 @@ class RegisterController extends AbstractController
$diff = $total - floatval($format['amount']);
if($diff >0){
$t = new \DateTimeImmutable();
$num = 'A-' . $t->format('Y/m') . '/' . sprintf('%05d', $entityManager->getRepository(CustomerAdvertPayment::class)->count() + 1);
$num = 'DIFF-' . $advert->getId();
$advert->setState("p-payment");
$entityManager->persist($advert);
$entityManager->flush();
$diffAdvert = new CustomerAdvertPayment();
$diffAdvert->setCustomer($advertRegister->getAdvert()->getCustomer());
$diffAdvert->setDevis($advertRegister->getAdvert()->getDevis());
$diffAdvert->setUpdateAt(\DateTimeImmutable::createFromFormat('Y-m-d', $format['date']));
$diffAdvert->setCreateAt(\DateTimeImmutable::createFromFormat('Y-m-d', $format['date']));
$diffAdvert->setNumAvis($num);
$diffAdvert->setState('created');
$diffAdvert->setState('send_avis');
$entityManager->persist($diffAdvert);
$diffAdvertLine = new CustomerAdvertPaymentLine();
$diffAdvertLine->setPos(0);
@@ -65,7 +93,6 @@ class RegisterController extends AbstractController
$entityManager->flush();
$event = new CreateAvisEvent($diffAdvert, true);
$eventDispatcher->dispatch($event);
return $this->json([]);
} else {
dd("completed");

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Controller\Artemis\EsyWeb;
use App\Repository\EsyWeb\WebsiteRepository;
use App\Repository\EsyWebTutoRepository;
use App\Service\Logger\LoggerService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\Attribute\Route;
use Vich\UploaderBundle\Templating\Helper\UploaderHelper;
class EsyWebController extends AbstractController
{
#[Route(path: '/artemis/esyweb', name: 'artemis_esyweb', methods: ['GET', 'POST'])]
public function tutos(LoggerService $loggerService,WebsiteRepository $websiteRepository)
{
$loggerService->log("VIEW","Affiche la page de site internet",$this->getUser());
return $this->render('artemis/esyweb/website.twig', [
'websites' => $websiteRepository->findAll(),
]);
}
}

View File

@@ -4,13 +4,17 @@ namespace App\Controller;
use App\Entity\CustomerAdvertPayment;
use App\Entity\CustomerAdvertPaymentLine;
use App\Entity\CustomerAdvertPaymentRegister;
use App\Repository\CustomerAdvertPaymentRepository;
use App\Service\Customer\Billing\CustomerAdvertPaymentComplete;
use App\Service\Customer\Billing\SiteconseilAdvertPaymentComplete;
use App\Service\Mailer\Mailer;
use Doctrine\ORM\EntityManagerInterface;
use Stancer\Config;
use Stancer\Customer;
use Stancer\Payment;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
@@ -38,7 +42,7 @@ class PaymentController extends AbstractController
]);
}
#[Route(path: '/paiement/complete',name: 'app_payment_complete')]
public function paymentComplete(EntityManagerInterface $entityManager,Mailer $mailer,Request $request,CustomerAdvertPaymentRepository $customerAdvertPaymentRepository): Response
public function paymentComplete(EventDispatcherInterface $eventDispatcher,EntityManagerInterface $entityManager,Mailer $mailer,Request $request,CustomerAdvertPaymentRepository $customerAdvertPaymentRepository): Response
{
if(!$request->query->has('id'))
return $this->render('admin/payement_invalid.twig',[
@@ -69,9 +73,28 @@ class PaymentController extends AbstractController
$advert->setCard($cardInfo);
$advert->setPayAt(new \DateTimeImmutable());
$entityManager->persist($advert);
$num = $advert->getNumAvis();
if(str_contains($num,"DIFF-")) {
$idRef = str_replace("DIFF-", "", $num);
$mainAdvert= $entityManager->getRepository(CustomerAdvertPayment::class)->find($idRef);
/*$register = new CustomerAdvertPaymentRegister();
$register->setAmount(floatval($payment->amount/100));
$register->setType("CB");
$register->setAdvert($mainAdvert);
$register->setCreateAt(new \DateTimeImmutable());
$entityManager->persist($register);*/
$advertSiteconseilPaymentComplete = new SiteconseilAdvertPaymentComplete($advert,$mainAdvert);
} else {
$advertSiteconseilPaymentComplete = new SiteconseilAdvertPaymentComplete($advert);
}
$entityManager->flush();
dd('email customer');
dd('email siteconseil');
$advertCustomerePaymentComplete = new CustomerAdvertPaymentComplete($advert);
$eventDispatcher->dispatch($advertCustomerePaymentComplete);
$eventDispatcher->dispatch($advertSiteconseilPaymentComplete);
}
}
return $this->render('admin/payement_complete.twig',[

View File

@@ -2,6 +2,7 @@
namespace App\Entity;
use App\Entity\EsyWeb\Website;
use App\Repository\CustomerRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
@@ -101,6 +102,12 @@ class Customer
#[ORM\Column(length: 255, nullable: true)]
private ?string $stancerId = null;
/**
* @var Collection<int, Website>
*/
#[ORM\OneToMany(targetEntity: Website::class, mappedBy: 'customer')]
private Collection $websites;
public function __clone(): void
{
@@ -122,6 +129,7 @@ class Customer
$this->customerDevis = new ArrayCollection();
$this->customerAdvertPayments = new ArrayCollection();
$this->customerOrders = new ArrayCollection();
$this->websites = new ArrayCollection();
}
public function getId(): ?int
@@ -506,4 +514,34 @@ class Customer
{
$this->id = $null;
}
/**
* @return Collection<int, Website>
*/
public function getWebsites(): Collection
{
return $this->websites;
}
public function addWebsite(Website $website): static
{
if (!$this->websites->contains($website)) {
$this->websites->add($website);
$website->setCustomer($this);
}
return $this;
}
public function removeWebsite(Website $website): static
{
if ($this->websites->removeElement($website)) {
// set the owning side to null (unless already changed)
if ($website->getCustomer() === $this) {
$website->setCustomer(null);
}
}
return $this;
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace App\Entity\EsyWeb;
use App\Entity\Customer;
use App\Repository\EsyWeb\WebsiteRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Uid\Uuid;
#[ORM\Entity(repositoryClass: WebsiteRepository::class)]
class Website
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(inversedBy: 'websites')]
private ?Customer $customer = null;
#[ORM\Column(type: 'uuid')]
private ?Uuid $uuid = null;
public function getId(): ?int
{
return $this->id;
}
public function getCustomer(): ?Customer
{
return $this->customer;
}
public function setCustomer(?Customer $customer): static
{
$this->customer = $customer;
return $this;
}
public function getUuid(): ?Uuid
{
return $this->uuid;
}
public function setUuid(Uuid $uuid): static
{
$this->uuid = $uuid;
return $this;
}
}

View File

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

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Service\Customer\Billing;
use App\Entity\CustomerAdvertPayment;
class CustomerAdvertPaymentComplete
{
public function __construct(private CustomerAdvertPayment $currentAdvertPayment){
}
/**
* @return CustomerAdvertPayment
*/
public function getCurrentAdvertPayment(): CustomerAdvertPayment
{
return $this->currentAdvertPayment;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Service\Customer\Billing;
use App\Entity\CustomerAdvertPayment;
class SiteconseilAdvertPaymentComplete
{
public function __construct(private CustomerAdvertPayment $currentAdvertPayment,private ?CustomerAdvertPayment $parentAdvert=null){
}
/**
* @return CustomerAdvertPayment
*/
public function getCurrentAdvertPayment(): CustomerAdvertPayment
{
return $this->currentAdvertPayment;
}
/**
* @return CustomerAdvertPayment|null
*/
public function getParentAdvert(): ?CustomerAdvertPayment
{
return $this->parentAdvert;
}
}

View File

@@ -2,10 +2,13 @@
namespace App\Service\Customer;
use App\Entity\CustomerAdvertPayment;
use App\Entity\CustomerAdvertPaymentLine;
use App\Service\Customer\Billing\CreateAvisEventSend;
use App\Service\Customer\Billing\CreateDevisCustomerEvent;
use App\Service\Customer\Billing\CreateDevisCustomerEventSend;
use App\Service\Customer\Billing\CustomerAdvertPaymentComplete;
use App\Service\Customer\Billing\SiteconseilAdvertPaymentComplete;
use App\Service\Docuseal\SignClient;
use App\Service\Mailer\Mailer;
use App\Service\Pdf\DevisPdf;
@@ -31,6 +34,8 @@ use Vich\UploaderBundle\Templating\Helper\UploaderHelper;
#[AsEventListener(event: CreateAvisEvent::class, method: 'onCreatedAvisEvent')]
#[AsEventListener(event: CreateAvisEventSend::class, method: 'onCreatedAvisEventSend')]
#[AsEventListener(event: CreateFactureEvent::class, method: 'onCreateFactureEvent')]
#[AsEventListener(event: SiteconseilAdvertPaymentComplete::class, method: 'onSiteconseilAdvertPaymentComplete')]
#[AsEventListener(event: CustomerAdvertPaymentComplete::class, method: 'onCustomerAdvertPaymentComplete')]
class BillingEventSusbriber
{
@@ -46,6 +51,19 @@ class BillingEventSusbriber
){
}
public function onSiteconseilAdvertPaymentComplete(SiteconseilAdvertPaymentComplete $advertPaymentComplete): void
{
/** @var CustomerAdvertPayment $currentAdvert */
$currentAdvert = $advertPaymentComplete->getCurrentAdvertPayment();
/** @var CustomerAdvertPayment $parent */
$parent = $advertPaymentComplete->getParentAdvert();
}
public function onCustomerAdvertPaymentComplete(CustomerAdvertPaymentComplete $advertPaymentComplete): void
{
$currentAdvert = $advertPaymentComplete->getCurrentAdvertPayment();
}
public function onCustomerSendPasswordEmail(CustomerSendPasswordEmail $customerSendPasswordEmail)
{
$ndd = $customerSendPasswordEmail->getCustomerDnsEmail()->getDns();

View File

@@ -9,6 +9,7 @@ use App\Entity\CustomerDevis;
use App\Entity\CustomerDns;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
class TwigOrderExtensions extends AbstractExtension
{
@@ -20,6 +21,20 @@ class TwigOrderExtensions extends AbstractExtension
new TwigFilter('skFormat',[$this,'skFormat']),
];
}
public function getFunctions()
{
return [
new TwigFunction('totalOrderPayment',[$this,'totalOrderPayment']),
];
}
public function totalOrderPayment(CustomerAdvertPayment $customerAdvertPayment)
{
$totalPayment = 0;
foreach ($customerAdvertPayment->getCustomerAdvertPaymentRegisters() as $register) {
$totalPayment += $register->getAmount();
}
return $totalPayment;
}
public function countEmail(Customer $customer): int
{

View File

@@ -74,6 +74,11 @@
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400 arrow-icon" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path></svg>
</button>
<ul id="submenu-cmsesyweb" class="submenu ml-6 mt-2 space-y-2">
<li>
<a href="{{ path('artemis_esyweb') }}" class="flex items-center p-2 text-sm font-normal text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
<span class="ml-3">SITE INTERNET</span>
</a>
</li>
<li>
<a href="{{ path('artemis_esyweb_tuto') }}" class="flex items-center p-2 text-sm font-normal text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
<span class="ml-3">Tutoriels</span>

View File

@@ -0,0 +1,10 @@
{% extends 'artemis/base.twig' %}
{% block content %}
{% endblock %}
{% block title %}
Liste des site internet
{% endblock %}

View File

@@ -16,8 +16,8 @@
<td class="px-6 py-4">Avis de paiement</td>
<td class="px-6 py-4">{{ orderAdvert.numAvis }}</td>
<td class="px-6 py-4">{{ orderAdvert.createAt|date('d/m/Y H:i') }}</td>
<td class="px-6 py-4">{{ (orderAdvert|totalOrder)|format_currency('EUR') }}</td>
<td class="px-6 py-4 {% if orderAdvert.state == "pay"%} text-green-400 {% else %}text-orange-400{% endif %}">{{ orderAdvert.state|trans }} </td>
<td class="px-6 py-4">{{ (orderAdvert|totalOrder)|format_currency('EUR') }} {% if orderAdvert.state == "p-payment" %} <span class="text-green-400">({{ totalOrderPayment(orderAdvert) }}€)</span>{% endif %}</td>
<td class="px-6 py-4 {% if orderAdvert.state == "pay"%} text-green-400 {% elseif orderAdvert.state == "p-payment"%}text-purple-400{% else %}text-orange-400{% endif %}">{{ orderAdvert.state|trans }} </td>
<td class="px-6 py-4 text-center">
{% if orderAdvert.state == "created" %}
<a href="{{ path('artemis_intranet_customer_view',{id:customer.id,currentOrder:'a',current:'order',idAvis:orderAdvert.id,act:'send'}) }}" class="block bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded">Envoyée l'avis de paiement</a>
@@ -25,7 +25,7 @@
{% if orderAdvert.state == "pay" and orderAdvert.customerOrder is null %}
<a href="{{ path('artemis_intranet_customer_view',{id:customer.id,current:'order',idAvis:orderAdvert.id,act:'createFacture'}) }}" class="block bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded">Crée la facture</a>
{% endif %}
{% if orderAdvert.state == "wait-bank" or orderAdvert.state == "wait-virement" %}
{% if orderAdvert.state == "wait-bank" or orderAdvert.state == "wait-virement" or orderAdvert.state == "p-payment" %}
<button is="register-payment" id="{{ orderAdvert.id }}" class="block bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded">Enregistrée un paiement</button>
{% endif %}
</td>

View File

@@ -14,8 +14,14 @@ send: Envoyée - Attends de signature
send_avis: Envoyée - Attends de paiement
accepted: Accéptée
cancel: Annulée
p-payment: Paiement partiel
pay: Payée
wait-virement: En attends de virement
register_VIR: Virement Bancaire
register_CB: Carte Bancaire
register_CH: Chéque
register_ESP: Espéce
register_PREV: Prévévement
dashboard: Tableau de bord
custom_graphics: Personalisation - Graphique