✨ feat(CustomerController): Ajoute création de devis client.
Ajoute la possibilité de créer des devis pour un client donné, avec gestion des numéros de devis et des lignes de devis.
This commit is contained in:
@@ -4,6 +4,7 @@ import {AutoSubmit} from './class/AutoSubmit'
|
||||
import {ServerCard} from './class/ServerCard'
|
||||
import {AutoCustomer} from './class/AutoCustomer'
|
||||
import {RepeatLine} from './class/RepeatLine'
|
||||
import {OrderCtrl} from './class/OrderCtrl'
|
||||
|
||||
|
||||
function script() {
|
||||
@@ -11,6 +12,7 @@ function script() {
|
||||
customElements.define('server-card',ServerCard,{extends:'div'})
|
||||
customElements.define('auto-customer',AutoCustomer,{extends:'button'})
|
||||
customElements.define('repeat-line',RepeatLine,{extends:'div'})
|
||||
customElements.define('order-ctrl',OrderCtrl,{extends:'div'})
|
||||
|
||||
}
|
||||
|
||||
|
||||
23
assets/class/OrderCtrl.js
Normal file
23
assets/class/OrderCtrl.js
Normal file
@@ -0,0 +1,23 @@
|
||||
export class OrderCtrl extends HTMLDivElement{
|
||||
connectedCallback(){
|
||||
let element = this;
|
||||
|
||||
let selectType = this.querySelector('select[name="type"]');
|
||||
let inputNum = this.querySelector('input[name="num"]');
|
||||
|
||||
fetch("/api-interne/intranet/customer/order/num?type=avis")
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
inputNum.setAttribute('value',data.num)
|
||||
inputNum.value = data.num;
|
||||
})
|
||||
selectType.addEventListener('change', (event)=>{
|
||||
fetch("/api-interne/intranet/customer/order/num?type="+event.target.value)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
inputNum.setAttribute('value',data.num)
|
||||
inputNum.value = data.num;
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
34
migrations/Version20250724074150.php
Normal file
34
migrations/Version20250724074150.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 Version20250724074150 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 order_number_current (id SERIAL NOT NULL, current_number VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE TABLE order_number_dispo (id SERIAL NOT NULL, num VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
|
||||
}
|
||||
|
||||
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 order_number_current');
|
||||
$this->addSql('DROP TABLE order_number_dispo');
|
||||
}
|
||||
}
|
||||
65
src/Controller/ApiInterne/Intranet/NumController.php
Normal file
65
src/Controller/ApiInterne/Intranet/NumController.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\ApiInterne\Intranet;
|
||||
|
||||
use App\Entity\CustomerAdvertPayment;
|
||||
use App\Entity\OrderNumberCurrent;
|
||||
use App\Repository\CustomerAdvertPaymentRepository;
|
||||
use App\Repository\CustomerDevisLineRepository;
|
||||
use App\Repository\CustomerOrderRepository;
|
||||
use App\Repository\OrderNumberCurrentRepository;
|
||||
use App\Repository\OrderNumberDispoRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class NumController extends AbstractController
|
||||
{
|
||||
#[Route(path: '/api-interne/intranet/customer/order/num',name: 'api-interne-intranet-customer-order-num')]
|
||||
public function orderNul(Request $request,
|
||||
CustomerAdvertPaymentRepository $customerAdvertPaymentRepository,
|
||||
CustomerDevisLineRepository $customerDevisLineRepository,
|
||||
CustomerOrderRepository $customerOrderRepository,
|
||||
OrderNumberCurrentRepository $currentRepository,
|
||||
OrderNumberDispoRepository $currentDispoRepository,
|
||||
EntityManagerInterface $entityManager,
|
||||
): Response
|
||||
{
|
||||
$type = $request->query->get('type');
|
||||
$t = new \DateTimeImmutable();
|
||||
if($type == "avis"){
|
||||
return $this->json([
|
||||
'num' => "A-".$t->format('Y/m')."/".sprintf('%05d',$customerAdvertPaymentRepository->count()+1),
|
||||
]);
|
||||
} elseif ($type == "devis") {
|
||||
return $this->json([
|
||||
'num' => "D-".$t->format('Y/m')."/".sprintf('%05d',$customerDevisLineRepository->count()+1),
|
||||
]);
|
||||
} else {
|
||||
|
||||
$numFinal = null;
|
||||
if($currentDispoRepository->count() >0) {
|
||||
$numFinal = $currentDispoRepository->findBy([],['id'=>'ASC'])[0]->getNum();
|
||||
} else {
|
||||
$lastNum = $currentRepository->find(1);
|
||||
if(!$lastNum instanceof OrderNumberCurrent) {
|
||||
$lastNum = new OrderNumberCurrent();
|
||||
$lastNum->setCurrentNumber($customerOrderRepository->count());
|
||||
$numFinal = "F-".$t->format('Y/m')."/".sprintf('%05d',$customerOrderRepository->count()+1);
|
||||
$entityManager->persist($lastNum);
|
||||
$entityManager->flush();
|
||||
} else {
|
||||
$numFinal = "F-".$t->format('Y/m')."/".sprintf('%05d',$lastNum->getCurrentNumber()+1);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->json([
|
||||
'num' => $numFinal,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,10 @@
|
||||
namespace App\Controller\Artemis\Intranet;
|
||||
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\CustomerAdvertPayment;
|
||||
use App\Entity\CustomerContact;
|
||||
use App\Entity\CustomerDevis;
|
||||
use App\Entity\CustomerDevisLine;
|
||||
use App\Entity\CustomerDns;
|
||||
use App\Form\Artemis\Intranet\CustomerEditType;
|
||||
use App\Form\Artemis\Intranet\CustomerNddType;
|
||||
@@ -126,10 +129,15 @@ class CustomerController extends AbstractController
|
||||
}
|
||||
return $this->redirectToRoute('artemis_intranet_customer_view',['id'=>$customer->getId(),'current'=>'nnd']);
|
||||
}
|
||||
|
||||
|
||||
$orderDevis = $entityManager->getRepository(CustomerDevis::class)->findBy(['customer'=>$customer],['id'=>'ASC']);
|
||||
|
||||
return $this->render('artemis/intranet/customer/edit.twig',[
|
||||
'form' => $form->createView(),
|
||||
'formNdd' => $formNdd->createView(),
|
||||
'customer' => $customer,
|
||||
'orderDevis' => $orderDevis,
|
||||
'current' => $request->get('current','main'),
|
||||
'currentOrder' => $request->get('currentOrder','f'),
|
||||
'ndds' => $customerDnsRepository->findBy(['customer'=>$customer],['expiredAt'=>'asc'])
|
||||
@@ -173,6 +181,34 @@ class CustomerController extends AbstractController
|
||||
#[Route(path: '/artemis/intranet/customer/{id}/orderAdd',name: 'artemis_intranet_customer_orderAdd',methods: ['GET', 'POST'])]
|
||||
public function customerOrder(?Customer $customer,Request $request,LoggerService $loggerService,EntityManagerInterface $entityManager,EventDispatcherInterface $eventDispatcher): Response
|
||||
{
|
||||
if($request->isMethod('POST')) {
|
||||
$data = $_POST;
|
||||
if($data['type'] == "devis") {
|
||||
$devis = new CustomerDevis();
|
||||
$devis->setCustomer($customer);
|
||||
$devis->setCreateAt(\DateTimeImmutable::createFromFormat('Y-m-d\TH:i',$data['date']));
|
||||
$devis->setNumDevis($data['num']);
|
||||
$devis->setState("created");
|
||||
$entityManager->persist($devis);
|
||||
$r=0;
|
||||
|
||||
foreach ($data['lines'] as $line) {
|
||||
$devisLine = new CustomerDevisLine();
|
||||
$devisLine->setPos($r);
|
||||
$devisLine->setName($line['title']);
|
||||
$devisLine->setPriceHT($line['price']);
|
||||
$devisLine->setContent($line['description']);
|
||||
$devisLine->setTva(1.20);
|
||||
$entityManager->persist($devisLine);
|
||||
$devis->addCustomerDevisLine($devisLine);
|
||||
$entityManager->persist($devis);
|
||||
$r = $r+1;
|
||||
}
|
||||
$entityManager->flush();
|
||||
$this->addFlash("success","Création effectuée");
|
||||
return $this->redirectToRoute('artemis_intranet_customer_view',['id'=>$customer->getId(),'current'=>'order']);
|
||||
}
|
||||
}
|
||||
return $this->render('artemis/intranet/customer/order-add.twig',[
|
||||
'customer' => $customer,
|
||||
]);
|
||||
|
||||
35
src/Entity/OrderNumberCurrent.php
Normal file
35
src/Entity/OrderNumberCurrent.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\OrderNumberCurrentRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: OrderNumberCurrentRepository::class)]
|
||||
class OrderNumberCurrent
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $currentNumber = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getCurrentNumber(): ?string
|
||||
{
|
||||
return $this->currentNumber;
|
||||
}
|
||||
|
||||
public function setCurrentNumber(string $currentNumber): static
|
||||
{
|
||||
$this->currentNumber = $currentNumber;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
35
src/Entity/OrderNumberDispo.php
Normal file
35
src/Entity/OrderNumberDispo.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\OrderNumberDispoRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: OrderNumberDispoRepository::class)]
|
||||
class OrderNumberDispo
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $num = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getNum(): ?string
|
||||
{
|
||||
return $this->num;
|
||||
}
|
||||
|
||||
public function setNum(string $num): static
|
||||
{
|
||||
$this->num = $num;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
43
src/Repository/OrderNumberCurrentRepository.php
Normal file
43
src/Repository/OrderNumberCurrentRepository.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\OrderNumberCurrent;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<OrderNumberCurrent>
|
||||
*/
|
||||
class OrderNumberCurrentRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, OrderNumberCurrent::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return OrderNumberCurrent[] Returns an array of OrderNumberCurrent objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('o')
|
||||
// ->andWhere('o.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('o.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?OrderNumberCurrent
|
||||
// {
|
||||
// return $this->createQueryBuilder('o')
|
||||
// ->andWhere('o.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
43
src/Repository/OrderNumberDispoRepository.php
Normal file
43
src/Repository/OrderNumberDispoRepository.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\OrderNumberDispo;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<OrderNumberDispo>
|
||||
*/
|
||||
class OrderNumberDispoRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, OrderNumberDispo::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return OrderNumberDispo[] Returns an array of OrderNumberDispo objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('o')
|
||||
// ->andWhere('o.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('o.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?OrderNumberDispo
|
||||
// {
|
||||
// return $this->createQueryBuilder('o')
|
||||
// ->andWhere('o.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
</div>
|
||||
|
||||
<form method="post" class="mt-5 bg-gray-800 rounded-lg shadow-lg p-6 space-y-4">
|
||||
<div class="flex space-x-4">
|
||||
<div class="flex space-x-4" is="order-ctrl">
|
||||
<div class="flex-1">
|
||||
<div class="mb-5">
|
||||
<label for="type" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Type</label>
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
<fieldset class="form-section">
|
||||
<legend>
|
||||
<h2>Guests</h2>
|
||||
<h2>Ligne(s)</h2>
|
||||
</legend>
|
||||
|
||||
<div class="form-repeater" data-component="repeater" is="repeat-line">
|
||||
@@ -47,11 +47,11 @@
|
||||
<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">Titre</label>
|
||||
<input type="text" name="titre" id="lines[0]['title]" 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 />
|
||||
<input type="text" name="lines[0][title]" id="lines[0][title]" 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 class="mb-1">
|
||||
<label for="price" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Prix TTC</label>
|
||||
<input type="number" step="0.1" name="titre" id="lines[0]['price]" 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 />
|
||||
<input type="number" step="0.1" name="lines[0][price]" id="lines[0][price]" 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>
|
||||
<button
|
||||
class="w-full form-repeater__remove-button bg-red-600 hover:bg-red-700 text-white px-3 py-1 rounded"
|
||||
@@ -66,7 +66,7 @@
|
||||
<div class="form-field">
|
||||
<div class="mb-5">
|
||||
<label for="description" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Description</label>
|
||||
<textarea rows="7" type="text" name="description" id="lines[0]['description]" 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></textarea>
|
||||
<textarea rows="7" type="text" name="lines[0][description]" id="lines[0][description]" 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></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -89,7 +89,7 @@
|
||||
|
||||
<button
|
||||
class="w-full bg-purple-600 hover:bg-purple-700 text-white px-3 py-1 rounded"
|
||||
type="button"
|
||||
type="submit"
|
||||
>
|
||||
Enregistrer
|
||||
</button>
|
||||
|
||||
@@ -58,10 +58,11 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for orderDevi in orderDevis %}
|
||||
<tr class="hover:bg-gray-700">
|
||||
<td class="px-6 py-4">Facture</td>
|
||||
<td class="px-6 py-4">F2025-001</td>
|
||||
<td class="px-6 py-4">2025-07-01</td>
|
||||
<td class="px-6 py-4">DEVIS</td>
|
||||
<td class="px-6 py-4">{{ orderDevi.numDevis }}</td>
|
||||
<td class="px-6 py-4">{{ orderDevi.createAt|date('d/m/Y H:i') }}</td>
|
||||
<td class="px-6 py-4">1 200,00 €</td>
|
||||
<td class="px-6 py-4 text-green-400">Payé</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
@@ -70,6 +71,7 @@
|
||||
<button class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded">Annulée</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user