svg
This commit is contained in:
126
src/Controller/BillController.php
Normal file
126
src/Controller/BillController.php
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\Bill;
|
||||||
|
use App\Form\BillType;
|
||||||
|
use App\Repository\BillRepository;
|
||||||
|
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 BillController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route('/user/bill', name: 'app_user_bill')]
|
||||||
|
public function list(Request $request, BillRepository $billRepository): Response
|
||||||
|
{
|
||||||
|
$company = $request->getSession()->get('company');
|
||||||
|
$bills = $billRepository->findBy(['company' => $company]);
|
||||||
|
|
||||||
|
return $this->render('bill/list.html.twig', [
|
||||||
|
'usemenu' => true,
|
||||||
|
'usesidebar' => false,
|
||||||
|
'title' => 'Devis / Factures',
|
||||||
|
'routesubmit' => 'app_user_bill_submit',
|
||||||
|
'routeupdate' => 'app_user_bill_update',
|
||||||
|
'routeprint' => 'app_user_bill_print',
|
||||||
|
'bills' => $bills,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/user/bill/print/{id}', name: 'app_user_bill_print')]
|
||||||
|
public function print(int $id, Request $request, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
$company = $request->getSession()->get('company');
|
||||||
|
$bill = $em->getRepository(Bill::class)->find($id);
|
||||||
|
if (!$bill || $bill->getCompany() != $company) {
|
||||||
|
return $this->redirectToRoute('app_user_bill');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('bill/print.html.twig', [
|
||||||
|
'usemenu' => false,
|
||||||
|
'usesidebar' => false,
|
||||||
|
'bill' => $bill,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/user/bill/submit', name: 'app_user_bill_submit')]
|
||||||
|
public function submit(Request $request, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
$company = $request->getSession()->get('company');
|
||||||
|
$bill = new Bill();
|
||||||
|
$bill->setCompany($company);
|
||||||
|
$bill->setBillDate(new \DateTime());
|
||||||
|
|
||||||
|
$form = $this->createForm(BillType::class, $bill, ['mode' => 'submit']);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$em->persist($bill);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_user_bill');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('bill/edit.html.twig', [
|
||||||
|
'usemenu' => true,
|
||||||
|
'usesidebar' => false,
|
||||||
|
'title' => 'Création Compagnie',
|
||||||
|
'routecancel' => 'app_user_bill',
|
||||||
|
'routedelete' => 'app_user_bill_delete',
|
||||||
|
'mode' => 'submit',
|
||||||
|
'form' => $form,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/user/bill/update/{id}', name: 'app_user_bill_update')]
|
||||||
|
public function update(int $id, Request $request, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
$company = $request->getSession()->get('company');
|
||||||
|
$bill = $em->getRepository(Bill::class)->find($id);
|
||||||
|
if (!$bill || $bill->getCompany() != $company) {
|
||||||
|
return $this->redirectToRoute('app_user_bill');
|
||||||
|
}
|
||||||
|
|
||||||
|
$form = $this->createForm(BillType::class, $bill, ['mode' => 'update']);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_user_bill');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('bill/edit.html.twig', [
|
||||||
|
'usemenu' => true,
|
||||||
|
'usesidebar' => false,
|
||||||
|
'title' => 'Modification = '.$bill->isBill() ? 'Facture' : 'Devis',
|
||||||
|
'routecancel' => 'app_user_bill',
|
||||||
|
'routedelete' => 'app_user_bill_delete',
|
||||||
|
'mode' => 'update',
|
||||||
|
'form' => $form,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/user/bill/delete/{id}', name: 'app_user_bill_delete')]
|
||||||
|
public function delete(int $id, Request $request, EntityManagerInterface $em): Response
|
||||||
|
{
|
||||||
|
$company = $request->getSession()->get('company');
|
||||||
|
$bill = $em->getRepository(Bill::class)->find($id);
|
||||||
|
if (!$bill) {
|
||||||
|
return $this->redirectToRoute('app_user_bill');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tentative de suppression
|
||||||
|
try {
|
||||||
|
$em->remove($bill);
|
||||||
|
$em->flush();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->addflash('error', $e->getMessage());
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_user_bill_update', ['id' => $id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_user_bill');
|
||||||
|
}
|
||||||
|
}
|
@@ -44,7 +44,7 @@ class Accounting
|
|||||||
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'accountings')]
|
#[ORM\ManyToOne(inversedBy: 'accountings')]
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
private ?Company $company = null;
|
private Company $company;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Collection<int, Operation>
|
* @var Collection<int, Operation>
|
||||||
@@ -129,12 +129,12 @@ class Accounting
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCompany(): ?Company
|
public function getCompany(): Company
|
||||||
{
|
{
|
||||||
return $this->company;
|
return $this->company;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setCompany(?Company $company): static
|
public function setCompany(Company $company): static
|
||||||
{
|
{
|
||||||
$this->company = $company;
|
$this->company = $company;
|
||||||
|
|
||||||
|
152
src/Entity/Bill.php
Normal file
152
src/Entity/Bill.php
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\BillRepository;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: BillRepository::class)]
|
||||||
|
class Bill
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: false)]
|
||||||
|
private \DateTimeInterface $billDate;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: false)]
|
||||||
|
private string $clientName;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::TEXT, nullable: false)]
|
||||||
|
private string $clientAddress;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::BOOLEAN, nullable: false)]
|
||||||
|
private bool $bill = false;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'bills')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
private Company $company;
|
||||||
|
|
||||||
|
#[ORM\OneToMany(mappedBy: 'bill', targetEntity: BillDetail::class, orphanRemoval: true, cascade: ['persist', 'remove'])]
|
||||||
|
private Collection $billDetails;
|
||||||
|
|
||||||
|
private int $total;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->billDetails = new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBillDate(): \DateTimeInterface
|
||||||
|
{
|
||||||
|
return $this->billDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBillDate(\DateTimeInterface $billDate): static
|
||||||
|
{
|
||||||
|
$this->billDate = $billDate;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClientName(): string
|
||||||
|
{
|
||||||
|
return $this->clientName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setClientName(string $clientName): static
|
||||||
|
{
|
||||||
|
$this->clientName = $clientName;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClientAddress(): string
|
||||||
|
{
|
||||||
|
return $this->clientAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setClientAddress(string $clientAddress): static
|
||||||
|
{
|
||||||
|
$this->clientAddress = $clientAddress;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isBill(): ?bool
|
||||||
|
{
|
||||||
|
return $this->bill;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBill(bool $bill): static
|
||||||
|
{
|
||||||
|
$this->bill = $bill;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCompany(): Company
|
||||||
|
{
|
||||||
|
return $this->company;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCompany(Company $company): static
|
||||||
|
{
|
||||||
|
$this->company = $company;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotal(): int
|
||||||
|
{
|
||||||
|
$total = 0;
|
||||||
|
foreach ($this->billDetails as $billDetail) {
|
||||||
|
$total += $billDetail->getTotal();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTotal(int $total): static
|
||||||
|
{
|
||||||
|
$this->total = $total;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, BillDetail>
|
||||||
|
*/
|
||||||
|
public function getBillDetails(): Collection
|
||||||
|
{
|
||||||
|
return $this->billDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addBillDetail(BillDetail $billDetail): static
|
||||||
|
{
|
||||||
|
if (!$this->billDetails->contains($billDetail)) {
|
||||||
|
$this->billDetails->add($billDetail);
|
||||||
|
$billDetail->setBill($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeBillDetail(BillDetail $billDetail): static
|
||||||
|
{
|
||||||
|
if ($this->billDetails->removeElement($billDetail)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
126
src/Entity/BillDetail.php
Normal file
126
src/Entity/BillDetail.php
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\BillDetailRepository;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: BillDetailRepository::class)]
|
||||||
|
class BillDetail
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'billDetails')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
private Bill $bill;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::INTEGER, nullable: false)]
|
||||||
|
private int $rowOrder = 0;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::TEXT, nullable: false)]
|
||||||
|
private string $description;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::INTEGER, nullable: false)]
|
||||||
|
private int $price = 0;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::INTEGER, nullable: false)]
|
||||||
|
private int $quantity = 0;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::INTEGER, nullable: false)]
|
||||||
|
private int $discount = 0;
|
||||||
|
|
||||||
|
private int $total = 0;
|
||||||
|
|
||||||
|
public function getId(): int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBill(): Bill
|
||||||
|
{
|
||||||
|
return $this->bill;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBill(Bill $bill): static
|
||||||
|
{
|
||||||
|
$this->bill = $bill;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRowOrder(): int
|
||||||
|
{
|
||||||
|
return $this->rowOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRowOrder(int $rowOrder): static
|
||||||
|
{
|
||||||
|
$this->rowOrder = $rowOrder;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDescription(string $description): static
|
||||||
|
{
|
||||||
|
$this->description = $description;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPrice(): int
|
||||||
|
{
|
||||||
|
return $this->price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPrice(int $price): static
|
||||||
|
{
|
||||||
|
$this->price = $price;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQuantity(): int
|
||||||
|
{
|
||||||
|
return $this->quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setQuantity(int $quantity): static
|
||||||
|
{
|
||||||
|
$this->quantity = $quantity;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDiscount(): int
|
||||||
|
{
|
||||||
|
return $this->discount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDiscount(int $discount): static
|
||||||
|
{
|
||||||
|
$this->discount = $discount;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotal(): int
|
||||||
|
{
|
||||||
|
return $this->quantity * $this->price * (100 - $this->discount) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTotal(int $total): static
|
||||||
|
{
|
||||||
|
$this->total = $total;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@@ -57,6 +57,12 @@ class Company
|
|||||||
#[ORM\Column(length: 255, nullable: true)]
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
private ?string $bankbic = null;
|
private ?string $bankbic = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Collection<int, Bill>
|
||||||
|
*/
|
||||||
|
#[ORM\OneToMany(targetEntity: Bill::class, mappedBy: 'company', orphanRemoval: true)]
|
||||||
|
private Collection $bills;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Collection<int, Accounting>
|
* @var Collection<int, Accounting>
|
||||||
*/
|
*/
|
||||||
@@ -83,6 +89,7 @@ class Company
|
|||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
|
$this->bills = new ArrayCollection();
|
||||||
$this->accountings = new ArrayCollection();
|
$this->accountings = new ArrayCollection();
|
||||||
$this->users = new ArrayCollection();
|
$this->users = new ArrayCollection();
|
||||||
$this->years = new ArrayCollection();
|
$this->years = new ArrayCollection();
|
||||||
@@ -238,6 +245,24 @@ class Company
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, Bill>
|
||||||
|
*/
|
||||||
|
public function getBills(): Collection
|
||||||
|
{
|
||||||
|
return $this->bills;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addBill(Bill $bill): static
|
||||||
|
{
|
||||||
|
if (!$this->bills->contains($bill)) {
|
||||||
|
$this->bills->add($bill);
|
||||||
|
$bill->setCompany($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection<int, Accounting>
|
* @return Collection<int, Accounting>
|
||||||
*/
|
*/
|
||||||
@@ -256,18 +281,6 @@ class Company
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function removeAccounting(Accounting $accounting): static
|
|
||||||
{
|
|
||||||
if ($this->accountings->removeElement($accounting)) {
|
|
||||||
// set the owning side to null (unless already changed)
|
|
||||||
if ($accounting->getCompany() === $this) {
|
|
||||||
$accounting->setCompany(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection<int, User>
|
* @return Collection<int, User>
|
||||||
*/
|
*/
|
||||||
@@ -313,18 +326,6 @@ class Company
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function removeYear(Year $year): static
|
|
||||||
{
|
|
||||||
if ($this->years->removeElement($year)) {
|
|
||||||
// set the owning side to null (unless already changed)
|
|
||||||
if ($year->getCompany() === $this) {
|
|
||||||
$year->setCompany(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection<int, Operation>
|
* @return Collection<int, Operation>
|
||||||
*/
|
*/
|
||||||
@@ -342,16 +343,4 @@ class Company
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function removeOperation(Operation $operation): static
|
|
||||||
{
|
|
||||||
if ($this->operations->removeElement($operation)) {
|
|
||||||
// set the owning side to null (unless already changed)
|
|
||||||
if ($operation->getCompany() === $this) {
|
|
||||||
$operation->setCompany(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -33,7 +33,7 @@ class Operation
|
|||||||
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'operations')]
|
#[ORM\ManyToOne(inversedBy: 'operations')]
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
private ?Company $company = null;
|
private Company $company;
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
@@ -100,12 +100,12 @@ class Operation
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCompany(): ?Company
|
public function getCompany(): Company
|
||||||
{
|
{
|
||||||
return $this->company;
|
return $this->company;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setCompany(?Company $company): static
|
public function setCompany(Company $company): static
|
||||||
{
|
{
|
||||||
$this->company = $company;
|
$this->company = $company;
|
||||||
|
|
||||||
|
@@ -22,7 +22,7 @@ class Year
|
|||||||
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'years')]
|
#[ORM\ManyToOne(inversedBy: 'years')]
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
private ?Company $company = null;
|
private Company $company;
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
@@ -53,12 +53,12 @@ class Year
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCompany(): ?Company
|
public function getCompany(): Company
|
||||||
{
|
{
|
||||||
return $this->company;
|
return $this->company;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setCompany(?Company $company): static
|
public function setCompany(Company $company): static
|
||||||
{
|
{
|
||||||
$this->company = $company;
|
$this->company = $company;
|
||||||
|
|
||||||
|
46
src/Form/BillDetailType.php
Normal file
46
src/Form/BillDetailType.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Entity\BillDetail;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class BillDetailType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('rowOrder', IntegerType::class, [
|
||||||
|
'label' => 'Ordre',
|
||||||
|
'attr' => ['min' => 0],
|
||||||
|
])
|
||||||
|
->add('description', TextareaType::class, [
|
||||||
|
'label' => 'Description',
|
||||||
|
])
|
||||||
|
->add('price', IntegerType::class, [
|
||||||
|
'label' => 'Prix unitaire',
|
||||||
|
])
|
||||||
|
->add('quantity', IntegerType::class, [
|
||||||
|
'label' => 'Quantité',
|
||||||
|
])
|
||||||
|
->add('discount', IntegerType::class, [
|
||||||
|
'label' => 'Remise (%)',
|
||||||
|
])
|
||||||
|
->add('total', IntegerType::class, [
|
||||||
|
'label' => 'Total',
|
||||||
|
'disabled' => true,
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => BillDetail::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
64
src/Form/BillType.php
Normal file
64
src/Form/BillType.php
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Entity\Bill;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class BillType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('submit', SubmitType::class, [
|
||||||
|
'label' => 'Valider',
|
||||||
|
'attr' => ['class' => 'btn btn-success no-print'],
|
||||||
|
])
|
||||||
|
|
||||||
|
->add('billDate', DateType::class, [
|
||||||
|
'widget' => 'single_text',
|
||||||
|
'label' => 'Date',
|
||||||
|
])
|
||||||
|
->add('clientName', TextType::class, [
|
||||||
|
'label' => 'Nom du client',
|
||||||
|
])
|
||||||
|
->add('clientAddress', TextareaType::class, [
|
||||||
|
'label' => 'Adresse du client',
|
||||||
|
])
|
||||||
|
->add('bill', CheckboxType::class, [
|
||||||
|
'label' => 'Est-ce une facture ?',
|
||||||
|
'required' => false,
|
||||||
|
])
|
||||||
|
->add('billDetails', CollectionType::class, [
|
||||||
|
'entry_type' => BillDetailType::class,
|
||||||
|
'entry_options' => ['label' => false],
|
||||||
|
'allow_add' => true,
|
||||||
|
'allow_delete' => true,
|
||||||
|
'by_reference' => false,
|
||||||
|
'prototype' => true,
|
||||||
|
'label' => false,
|
||||||
|
])
|
||||||
|
->add('total', IntegerType::class, [
|
||||||
|
'label' => 'Total',
|
||||||
|
'disabled' => true,
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => Bill::class,
|
||||||
|
'mode' => 'string',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@@ -9,6 +9,7 @@ use Symfony\Component\Form\AbstractType;
|
|||||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
@@ -34,7 +35,7 @@ class CompanyType extends AbstractType
|
|||||||
|
|
||||||
->add('logo', HiddenType::class)
|
->add('logo', HiddenType::class)
|
||||||
|
|
||||||
->add('adress', TextType::class, [
|
->add('adress', TextareaType::class, [
|
||||||
'label' => 'Adresse',
|
'label' => 'Adresse',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
])
|
])
|
||||||
|
18
src/Repository/BillDetailRepository.php
Normal file
18
src/Repository/BillDetailRepository.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\BillDetail;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<BillDetail>
|
||||||
|
*/
|
||||||
|
class BillDetailRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, BillDetail::class);
|
||||||
|
}
|
||||||
|
}
|
18
src/Repository/BillRepository.php
Normal file
18
src/Repository/BillRepository.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\Bill;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<Bill>
|
||||||
|
*/
|
||||||
|
class BillRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, Bill::class);
|
||||||
|
}
|
||||||
|
}
|
159
templates/bill/edit.html.twig
Normal file
159
templates/bill/edit.html.twig
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %} = {{title}}{% endblock %}
|
||||||
|
|
||||||
|
{% block localstyle %}
|
||||||
|
<style>
|
||||||
|
td {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h1>{{title}}</h1>
|
||||||
|
|
||||||
|
|
||||||
|
{{ form_start(form) }}
|
||||||
|
{{ form_widget(form.submit) }}
|
||||||
|
<a href="{{ path(routecancel) }}" class="btn btn-secondary ms-1">Annuler</a>
|
||||||
|
{%if mode=="update" %}<a href="{{ path(routedelete,{id:form.vars.value.id}) }}" class="btn btn-danger float-end" onclick="return confirm('Confirmez-vous la suppression de cet enregistrement ?')">Supprimer</a>{%endif%}
|
||||||
|
|
||||||
|
{% include('include/error.html.twig') %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mx-auto">
|
||||||
|
<div class="card mt-3">
|
||||||
|
<div class="card-header">Information</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{{ form_row(form.billDate) }}
|
||||||
|
{{ form_row(form.clientName) }}
|
||||||
|
{{ form_row(form.clientAddress) }}
|
||||||
|
{{ form_row(form.bill) }}
|
||||||
|
{{ form_row(form.total) }}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-md-12 mx-auto">
|
||||||
|
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:100px">N°</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th style="width:100px">Qte</th>
|
||||||
|
<th style="width:100px">PU</th>
|
||||||
|
<th style="width:100px">Rem</th>
|
||||||
|
<th style="width:100px">Total</th>
|
||||||
|
<th style="width:100px"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="bill_details" style="zoom:80%"
|
||||||
|
data-prototype='
|
||||||
|
<tr class="bill-detail">
|
||||||
|
<td>{{ form_widget(form.billDetails.vars.prototype.rowOrder)|e('html_attr') }}</td>
|
||||||
|
<td>{{ form_widget(form.billDetails.vars.prototype.description)|e('html_attr') }}</td>
|
||||||
|
<td>{{ form_widget(form.billDetails.vars.prototype.quantity)|e('html_attr') }}</td>
|
||||||
|
<td>{{ form_widget(form.billDetails.vars.prototype.price)|e('html_attr') }}</td>
|
||||||
|
<td>{{ form_widget(form.billDetails.vars.prototype.discount)|e('html_attr') }}</td>
|
||||||
|
<td>{{ form_widget(form.billDetails.vars.prototype.total)|e('html_attr') }}</td>
|
||||||
|
<td><button type="button" class="btn btn-danger remove-item">X</button></td>
|
||||||
|
</tr>
|
||||||
|
'>
|
||||||
|
|
||||||
|
{% for detailForm in form.billDetails %}
|
||||||
|
<tr class="bill-detail">
|
||||||
|
<td>{{ form_widget(detailForm.rowOrder) }}</td>
|
||||||
|
<td>{{ form_widget(detailForm.description) }}</td>
|
||||||
|
<td>{{ form_widget(detailForm.quantity) }}</td>
|
||||||
|
<td>{{ form_widget(detailForm.price) }}</td>
|
||||||
|
<td>{{ form_widget(detailForm.discount) }}</td>
|
||||||
|
<td>{{ form_widget(detailForm.total) }}</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn btn-danger remove-item">X</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-primary" id="add_item">Ajouter une ligne</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ form_rest(form) }}
|
||||||
|
|
||||||
|
|
||||||
|
<div style="display:none;">
|
||||||
|
{{ form_rest(form) }}
|
||||||
|
</div>
|
||||||
|
{{ form_end(form, { render_rest: false }) }}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block localscript %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$("#accounting_billDate").focus();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const container = document.getElementById('bill_details');
|
||||||
|
const addButton = document.getElementById('add_item');
|
||||||
|
let index = container.querySelectorAll('.bill-detail').length;
|
||||||
|
|
||||||
|
function recalc(row) {
|
||||||
|
const qty = parseFloat(row.querySelector('[name$="[quantity]"]').value) || 0;
|
||||||
|
const price = parseFloat(row.querySelector('[name$="[price]"]').value) || 0;
|
||||||
|
const discount = parseFloat(row.querySelector('[name$="[discount]"]').value) || 0;
|
||||||
|
|
||||||
|
let total = qty * price;
|
||||||
|
if (discount > 0) {
|
||||||
|
total = total - (total * (discount / 100));
|
||||||
|
}
|
||||||
|
row.querySelector('[name$="[total]"]').value = total.toFixed(0);
|
||||||
|
}
|
||||||
|
function recalcGlobal() {
|
||||||
|
let sum = 0;
|
||||||
|
container.querySelectorAll('[name$="[total]"').forEach(input => {
|
||||||
|
sum += parseFloat(input.value) || 0;
|
||||||
|
});
|
||||||
|
console.log(sum);
|
||||||
|
console.log(sum.toFixed(0))
|
||||||
|
$('#bill_total').val(sum.toFixed(0));
|
||||||
|
}
|
||||||
|
container.addEventListener('input', (e) => {
|
||||||
|
if (e.target.matches('[name$="[quantity]"], [name$="[price]"], [name$="[discount]"]')) {
|
||||||
|
const row = e.target.closest('.bill-detail');
|
||||||
|
if (row) {
|
||||||
|
recalc(row);
|
||||||
|
recalcGlobal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addButton.addEventListener('click', () => {
|
||||||
|
const prototype = container.dataset.prototype.replace(/__name__/g, index);
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
tr.classList.add('bill-detail');
|
||||||
|
tr.innerHTML = prototype;
|
||||||
|
container.appendChild(tr);
|
||||||
|
index++;
|
||||||
|
});
|
||||||
|
|
||||||
|
container.addEventListener('click', (e) => {
|
||||||
|
if (e.target.classList.contains('remove-item')) {
|
||||||
|
e.target.closest('tr').remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
49
templates/bill/list.html.twig
Normal file
49
templates/bill/list.html.twig
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %} = {{title}}{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h1>{{title}}</h1>
|
||||||
|
<a href="{{ path(routesubmit) }}" class="btn btn-success">Ajouter</a>
|
||||||
|
|
||||||
|
<div class="dataTable_wrapper">
|
||||||
|
<table class="table table-striped table-bordered table-hover" id="dataTables" style="width:100%">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="120px" class="no-sort">Action</th>
|
||||||
|
<th width="120px">Date</th>
|
||||||
|
<th width="120px">Type</th>
|
||||||
|
<th>Client</th>
|
||||||
|
<th width="120px">Montant</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for bill in bills %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{{ path(routeupdate,{id:bill.id}) }}"><i class="fas fa-file fa-2x"></i></a>
|
||||||
|
<a href="{{ path(routeprint,{id:bill.id}) }}" class="ms-2"><i class="fas fa-print fa-2x"></i></a>
|
||||||
|
</td>
|
||||||
|
<td>{{ bill.billDate|date("Y-m-d") }}</td>
|
||||||
|
<td>{{ bill.isBill?'Facture':'Devis' }}</td>
|
||||||
|
<td>{{ bill.clientName }}</td>
|
||||||
|
<td>{{ bill.total }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block localscript %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#dataTables').DataTable({
|
||||||
|
columnDefs: [ { "targets": "no-sort", "orderable": false }, { "targets": "no-string", "type" : "num" } ],
|
||||||
|
responsive: true,
|
||||||
|
iDisplayLength: 100,
|
||||||
|
order: [[ 1, "desc" ]]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
79
templates/bill/print.html.twig
Normal file
79
templates/bill/print.html.twig
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block localstyle %}
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
zoom:70%;
|
||||||
|
}
|
||||||
|
body, .table>:not(caption)>*>* {
|
||||||
|
background-color: #fff !important;
|
||||||
|
color: #000;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.company, .client {
|
||||||
|
width: 400px;
|
||||||
|
border: 1px solid #000;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width:150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="d-flex">
|
||||||
|
<center><img src="{{ asset(bill.company.logo)}}" class="logo mb-3"></center>
|
||||||
|
<h1 class="ms-auto">{{bill.isBill?'Facture FA':'Devis DE' }}{{bill.billDate|date("Ymd")}}</h1>
|
||||||
|
</div>
|
||||||
|
<div class="company">
|
||||||
|
<h4>{{bill.company.title}}</h4>
|
||||||
|
{{bill.company.adress|nl2br}}<br><br>
|
||||||
|
{{bill.company.email}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="client ms-auto ms-3 mt-3">
|
||||||
|
<h4>{{bill.clientName}}</h4>
|
||||||
|
{{bill.clientAddress|nl2br}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-bordered mt-3">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Description</th>
|
||||||
|
<th style="width:100px;text-align:center;">Qte</th>
|
||||||
|
<th style="width:100px;text-align:center;">PU</th>
|
||||||
|
<th style="width:100px;text-align:center;">Rem</th>
|
||||||
|
<th style="width:100px;text-align:center;">Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for billDetail in bill.billDetails %}
|
||||||
|
<tr class="bill-detail">
|
||||||
|
<td>{{ billDetail.description }}</td>
|
||||||
|
<td style="text-align:center">{{ billDetail.quantity }}</td>
|
||||||
|
<td style="text-align:center">{{ billDetail.price }}</td>
|
||||||
|
<td style="text-align:center">{{ billDetail.discount }}</td>
|
||||||
|
<td style="text-align:center">{{ billDetail.total }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td style="text-align:right" colspan="4">Total</td>
|
||||||
|
<td style="text-align:center">{{bill.total}}</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock %}
|
Reference in New Issue
Block a user