feat(TwigOrderExtensions): Ajoute la fonction faultPayment.

Ajoute une fonction Twig pour vérifier les défauts de paiement et modifie l'affichage des données du dashboard.
This commit is contained in:
Serreau Jovann
2025-11-13 14:38:42 +01:00
parent aa1910d6f5
commit ff04320200
7 changed files with 149 additions and 39 deletions

View File

@@ -2,6 +2,7 @@
namespace App\Controller\Artemis;
use App\Controller\Artemis\Intranet\FaultController;
use App\Entity\Customer;
use App\Entity\CustomerAdvertPayment;
use App\Repository\ComputeRepository;
@@ -9,6 +10,7 @@ use App\Repository\CustomerAdvertPaymentRepository;
use App\Repository\CustomerDevisRepository;
use App\Repository\CustomerDnsRepository;
use App\Repository\CustomerOrderRepository;
use App\Repository\FaultPaymentRepository;
use App\Service\Docuseal\SignClient;
use App\Service\Google\ComputeEngineClient;
use App\Service\Ovh\Client;
@@ -26,7 +28,7 @@ class DashboardController extends AbstractController
CustomerOrderRepository $customerOrderRepository,
CustomerAdvertPaymentRepository $customerAdvertPaymentRepository,
CustomerDevisRepository $customerDevisRepository,
ComputeRepository $computeRepository,
FaultPaymentRepository $faultPaymentRepository,
CustomerDnsRepository $customerDnsRepository,
ComputeEngineClient $computeEngineClient,
Client $client,
@@ -116,6 +118,15 @@ class DashboardController extends AbstractController
}
}
$faults= 0;
foreach ($faultPaymentRepository->findAll() as $fault) {
$sub = 0;
$d = $customerAdvertPaymentRepository->find($fault->getIdAdvertPayment());
foreach ($d->getCustomerAdvertPaymentLines() as $line) {
$sub = $sub + $line->getPriceHt()*1.20;
}
$faults = $faults + $sub;
}
return $this->render('artemis/dashboard.twig',[
'invoiceList' => $invoiceList,
@@ -125,6 +136,7 @@ class DashboardController extends AbstractController
'remaining_amount' => $remaining_amount,
'servers' => $servers,
'dns' => $dns,
'faults' => $faults,
]);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Controller\Artemis\Intranet;
use App\Entity\CustomerAdvertPayment;
use App\Entity\FaultPayment;
use App\Repository\CustomerAdvertPaymentRepository;
use App\Repository\FaultPaymentRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Attribute\Route;
class FaultController extends AbstractController
{
#[Route(path: '/artemis/customer/fault', name: 'artemis_intranet_fault', methods: ['GET', 'POST'])]
public function customerFault(FaultPaymentRepository $faultPaymentRepository, CustomerAdvertPaymentRepository $customerAdvertPayment) {
$faults =[];
$totalFault = 0;
foreach ($faultPaymentRepository->findAll() as $faultPayment) {
$advert = $customerAdvertPayment->find($faultPayment->getIdAdvertPayment());
$am = 0;
foreach ($advert->getCustomerAdvertPaymentLines() as $line) {
$am = $am + $line->getPriceHt() * 1.20;
}
$faults[] =[
'customer' => $advert->getCustomer(),
'entryAt' => $faultPayment->getEntryAt(),
'amount' => $am,
];
}
return $this->render('artemis/intranet/fault.twig',[
'faults' => $faults,
]);
}
}

View File

@@ -13,15 +13,17 @@ use App\Entity\EsyWeb\Website;
use App\Entity\EsyWeb\WebsiteDns;
use App\Entity\EsyWeb\WebsiteKey;
use App\Entity\EsyWeb\WebsiteLicense;
use App\Entity\FaultPayment;
use App\Service\Vault\VaultClient;
use Cocur\Slugify\Slugify;
use Doctrine\ORM\EntityManagerInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
class TwigOrderExtensions extends AbstractExtension
{
public function __construct(private readonly VaultClient $vaultClient)
public function __construct(private readonly EntityManagerInterface $entityManager,private readonly VaultClient $vaultClient)
{
}
@@ -44,6 +46,7 @@ class TwigOrderExtensions extends AbstractExtension
];
}
public function subHost(Website $website)
{
/** @var WebsiteDns $dns */
@@ -155,8 +158,20 @@ class TwigOrderExtensions extends AbstractExtension
{
return [
new TwigFunction('totalOrderPayment',[$this,'totalOrderPayment']),
new TwigFunction('faultPayment',[$this,'faultPayment']),
];
}
public function faultPayment(CustomerAdvertPayment $customerAdvertPayment)
{
$fault = $this->entityManager->getRepository(FaultPayment::class)->findOneBy([
'idAdvertPayment' => $customerAdvertPayment->getId(),
'customer' => $customerAdvertPayment->getCustomer(),
]);
if($fault instanceof FaultPayment){
return true;
}
return false;
}
public function totalOrderPayment(CustomerAdvertPayment $customerAdvertPayment)
{
$totalPayment = 0;

View File

@@ -132,6 +132,11 @@
<span class="ml-3">Client(s)</span>
</a>
</li>
<li>
<a href="{{ path('artemis_intranet_fault') }}" class="flex items-center p-2 text-base font-normal text-gray-900 dark:text-white {% if app.request.get('_route') == 'artemis_intranet_fault' %}bg-gray-200 dark:bg-gray-700{% endif %} rounded-lg">
<span class="ml-3">Impayée</span>
</a>
</li>
<li>
<a href="{{ path('artemis_intranet_price') }}" class="flex items-center p-2 text-base font-normal text-gray-900 dark:text-white {% if app.request.get('_route') == 'artemis_intranet_price' %}bg-gray-200 dark:bg-gray-700{% endif %} rounded-lg">
<span class="ml-3">Prix automatique</span>

View File

@@ -160,23 +160,23 @@
<div class="p-6 rounded-lg shadow-xl bg-white dark:bg-gray-800">
<p class="text-sm text-gray-500 dark:text-gray-400">CA En Cours</p>
<p class="text-2xl font-bold text-green-500">45 230 €</p>
<p class="text-2xl font-bold text-green-500">0 €</p>
</div>
<div class="p-6 rounded-lg shadow-xl bg-white dark:bg-gray-800">
<p class="text-sm text-gray-500 dark:text-gray-400">CA Prévu</p>
<p class="text-2xl font-bold text-blue-400">62 500 €</p>
<p class="text-2xl font-bold text-blue-400">0 €</p>
</div>
<div class="p-6 rounded-lg shadow-xl bg-white dark:bg-gray-800">
<p class="text-sm text-gray-500 dark:text-gray-400">Total Impayés</p>
<p class="text-2xl font-bold text-red-500">8 120 €</p>
<p class="text-2xl font-bold text-red-500">{{ faults|format_currency('EUR')}}</p>
</div>
<div class="p-6 rounded-lg shadow-xl bg-white dark:bg-gray-800 border-l-4 border-yellow-500">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Écart / Objectif Prévu</p>
<p class="text-2xl font-bold text-yellow-500">
- 17 270 €
0 €
</p>
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">
(Reste à atteindre ce mois)
@@ -224,7 +224,7 @@
<div class="p-6 rounded-lg shadow-xl bg-white dark:bg-gray-800">
<h2 class="text-xl font-semibold mb-4 text-gray-900 dark:text-gray-100">📑 Factures à Valider</h2>
<div class="border-l-4 border-yellow-500 bg-yellow-50 dark:bg-yellow-900/50 p-3">
<div class="border-l-4 border-yellow-500 bg-yellow-50 dark:bg-yellow-900/50 p-3" style="display: none">
<p class="text-sm font-medium text-gray-900 dark:text-yellow-100">Facture #2025-001 (Client A)</p>
<p class="text-xs text-gray-600 dark:text-gray-300">Montant : 1 200 € - En attente</p>
</div>
@@ -248,14 +248,7 @@
<div class="flex justify-between items-start">
<div>
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Ratio Coût Infra / CA</p>
<p class="text-2xl font-bold text-orange-500 mt-1">28.5%</p>
</div>
<div class="text-right">
<p class="text-sm text-red-500 font-semibold flex items-center">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"></path></svg>
+0.8 pt
</p>
<p class="text-xs text-gray-400 dark:text-gray-500">vs Mois Précédent</p>
<p class="text-2xl font-bold text-orange-500 mt-1">0%</p>
</div>
</div>
</div>
@@ -264,15 +257,9 @@
<div class="flex justify-between items-start">
<div>
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Ratio Coût Salarial / CA</p>
<p class="text-2xl font-bold text-teal-500 mt-1">35.0%</p>
</div>
<div class="text-right">
<p class="text-sm text-green-500 font-semibold flex items-center">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3"></path></svg>
-0.5 pt
</p>
<p class="text-xs text-gray-400 dark:text-gray-500">vs Mois Précédent</p>
<p class="text-2xl font-bold text-teal-500 mt-1">0%</p>
</div>
</div>
</div>
@@ -280,14 +267,7 @@
<div class="flex justify-between items-start">
<div>
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Coût Google Cloud (Mois)</p>
<p class="text-2xl font-bold text-blue-500 mt-1">1 850,00 €</p>
</div>
<div class="text-right">
<p class="text-sm text-red-500 font-semibold flex items-center">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"></path></svg>
+3.5%
</p>
<p class="text-xs text-gray-400 dark:text-gray-500">vs Mois Précédent</p>
<p class="text-2xl font-bold text-blue-500 mt-1">0 €</p>
</div>
</div>
</div>
@@ -296,14 +276,7 @@
<div class="flex justify-between items-start">
<div>
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Coût OVH (Mois)</p>
<p class="text-2xl font-bold text-purple-500 mt-1">425,50 €</p>
</div>
<div class="text-right">
<p class="text-sm text-green-500 font-semibold flex items-center">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3"></path></svg>
-1.2%
</p>
<p class="text-xs text-gray-400 dark:text-gray-500">vs Mois Précédent</p>
<p class="text-2xl font-bold text-purple-500 mt-1">0 €</p>
</div>
</div>
</div>

View File

@@ -18,7 +18,11 @@
<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') }} {% if orderAdvert.state == "p-payment" %} <span class="text-green-400">({{ totalOrderPayment(orderAdvert) }}€)</span>{% endif %}</td>
{% if faultPayment(orderAdvert) %}
<td class="px-6 py-4 text-red-900">Défault de paiement </td>
{% else %}
<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>
{% endif %}
<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>

View File

@@ -0,0 +1,65 @@
{% extends 'artemis/base.twig' %}
{% block content %}
{#
La classe 'dark' est appliquée ici ou sur le <body> (dépend de votre configuration Tailwind).
Pour un contrôle localisé, on peut l'appliquer sur le conteneur principal.
Les couleurs sont définies pour le mode clair par défaut, et la variante 'dark:' les surcharge.
#}
<div class="p-6 md:p-10 min-h-screen bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100">
<h2 class="text-3xl font-bold mb-6 text-indigo-700 dark:text-indigo-400 border-b pb-2 border-indigo-300 dark:border-indigo-600">
Liste des Impayés
</h2>
{% if faults is not empty %}
<ul class="space-y-4">
{% for fault in faults %}
{# Élément individuel d'impayé #}
<li class="p-4 rounded-lg shadow-md transition-all duration-300
bg-white hover:bg-gray-50
dark:bg-gray-800 dark:hover:bg-gray-700
border border-gray-200 dark:border-gray-700">
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center">
{# Détails de l'impayé #}
<div class="mb-3 sm:mb-0">
<p class="text-sm text-gray-500 dark:text-gray-400">
<strong>Client :</strong> <span class="font-semibold text-gray-800 dark:text-gray-200">
{{ fault.customer.raisonSocial }}
</span>
</p>
<p class="text-lg font-bold text-red-600 dark:text-red-400 mt-1">
<strong>Montant </strong> {{ fault.amount|format_currency('EUR') }}
</p>
</div>
{# Date et Lien #}
<div class="text-right">
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
Date d'entrée : <span class="font-medium">{{ fault.entryAt|date('d/m/Y') }}</span>
</p>
<a href="{{ path('artemis_intranet_customer_view',{id:fault.customer.id,current:"order",currentOrder:"a"}) }}"
class="inline-block px-3 py-1 text-sm font-medium rounded-full
bg-indigo-600 text-white hover:bg-indigo-700
dark:bg-indigo-500 dark:hover:bg-indigo-600
focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
Voir le client
</a>
</div>
</div>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-gray-500 dark:text-gray-400 italic">
Aucun impayé à afficher pour le moment.
</p>
{% endif %}
</div>
{% endblock %}
{% block title %}
Liste des Impayés
{% endblock %}