✨ 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:
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
36
src/Controller/Artemis/Intranet/FaultController.php
Normal file
36
src/Controller/Artemis/Intranet/FaultController.php
Normal 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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
65
templates/artemis/intranet/fault.twig
Normal file
65
templates/artemis/intranet/fault.twig
Normal 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 %}
|
||||
Reference in New Issue
Block a user