Skip to content

Commit a1b8aeb

Browse files
committed
Refonte Trésorie > Facture - Ajout / Edition
1 parent fbd602c commit a1b8aeb

15 files changed

Lines changed: 487 additions & 20 deletions

File tree

app/config/routing/admin_accounting.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ admin_accounting_invoices_list:
3434
path: /invoices/list
3535
defaults: {_controller: AppBundle\Controller\Admin\Accounting\Invoice\ListInvoiceAction}
3636

37+
admin_accounting_invoices_edit:
38+
path: /invoices/edit
39+
defaults: {_controller: AppBundle\Controller\Admin\Accounting\Invoice\EditInvoiceAction}
40+
3741
admin_accounting_invoices_download:
3842
path: /invoices/download
3943
defaults: {_controller: AppBundle\Controller\Admin\Accounting\Invoice\DownloadInvoiceAction}

composer.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AppBundle\Accounting\Form;
6+
7+
use Afup\Site\Utils\Pays;
8+
use AppBundle\Accounting\InvoicingCurrency;
9+
use AppBundle\Accounting\InvoicingPaymentStatus;
10+
use AppBundle\Accounting\Model\InvoicingDetail;
11+
use Symfony\Component\Form\AbstractType;
12+
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
13+
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
14+
use Symfony\Component\Form\Extension\Core\Type\DateType;
15+
use Symfony\Component\Form\Extension\Core\Type\EmailType;
16+
use Symfony\Component\Form\Extension\Core\Type\EnumType;
17+
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
18+
use Symfony\Component\Form\Extension\Core\Type\TextType;
19+
use Symfony\Component\Form\FormBuilderInterface;
20+
use Symfony\Component\Validator\Constraints as Assert;
21+
22+
class InvoiceType extends AbstractType
23+
{
24+
public function __construct(private readonly Pays $pays) {}
25+
26+
public function buildForm(FormBuilderInterface $builder, array $options): void
27+
{
28+
$builder->add('invoiceDate', DateType::class, [
29+
'label' => 'Date facture',
30+
'required' => true,
31+
'widget' => 'single_text',
32+
])->add('company', TextType::class, [
33+
'label' => 'Société',
34+
'empty_data' => '',
35+
'constraints' => [
36+
new Assert\NotBlank(),
37+
new Assert\Type('string'),
38+
new Assert\Length(max: 50),
39+
],
40+
])->add('service', TextType::class, [
41+
'label' => 'Service',
42+
'required' => false,
43+
'empty_data' => '',
44+
'constraints' => [
45+
new Assert\Type('string'),
46+
new Assert\Length(max: 50),
47+
],
48+
])->add('address', TextareaType::class, [
49+
'label' => 'Adresse',
50+
'empty_data' => '',
51+
'constraints' => [
52+
new Assert\Type('string'),
53+
],
54+
])->add('zipcode', TextType::class, [
55+
'label' => 'Code postal',
56+
'empty_data' => '',
57+
'constraints' => [
58+
new Assert\NotBlank(),
59+
new Assert\Type('string'),
60+
new Assert\Length(max: 10),
61+
],
62+
])->add('city', TextType::class, [
63+
'label' => 'Ville',
64+
'empty_data' => '',
65+
'constraints' => [
66+
new Assert\NotBlank(),
67+
new Assert\Type('string'),
68+
new Assert\Length(max: 50),
69+
],
70+
])->add('countryId', ChoiceType::class, [
71+
'label' => 'Pays',
72+
'choices' => array_flip($this->pays->obtenirPays()),
73+
])->add('lastname', TextType::class, [
74+
'label' => 'Nom',
75+
'required' => false,
76+
'empty_data' => '',
77+
'constraints' => [
78+
new Assert\Type('string'),
79+
new Assert\Length(max: 50),
80+
],
81+
])->add('firstname', TextType::class, [
82+
'label' => 'Prénom',
83+
'required' => false,
84+
'empty_data' => '',
85+
'constraints' => [
86+
new Assert\Type('string'),
87+
new Assert\Length(max: 50),
88+
],
89+
])->add('phone', TextType::class, [
90+
'label' => 'Tel',
91+
'required' => false,
92+
'empty_data' => '',
93+
'constraints' => [
94+
new Assert\Type('string'),
95+
new Assert\Length(max: 30),
96+
],
97+
])->add('email', EmailType::class, [
98+
'label' => 'Email (facture)',
99+
'required' => true,
100+
'empty_data' => '',
101+
'constraints' => [
102+
new Assert\NotBlank(),
103+
new Assert\Type('string'),
104+
new Assert\Length(max: 100),
105+
],
106+
])->add('tvaIntra', TextType::class, [
107+
'label' => 'TVA intracommunautaire (facture)',
108+
'required' => false,
109+
'empty_data' => '',
110+
'constraints' => [
111+
new Assert\Type('string'),
112+
new Assert\Length(max: 20),
113+
],
114+
])->add('refClt1', TextType::class, [
115+
'label' => 'Référence client',
116+
'required' => false,
117+
'empty_data' => '',
118+
'constraints' => [
119+
new Assert\Type('string'),
120+
new Assert\Length(max: 50),
121+
],
122+
])->add('refClt2', TextType::class, [
123+
'label' => 'Référence client 2',
124+
'required' => false,
125+
'empty_data' => '',
126+
'constraints' => [
127+
new Assert\Type('string'),
128+
new Assert\Length(max: 50),
129+
],
130+
])->add('refClt3', TextType::class, [
131+
'label' => 'Référence client 3',
132+
'required' => false,
133+
'empty_data' => '',
134+
'constraints' => [
135+
new Assert\Type('string'),
136+
new Assert\Length(max: 50),
137+
],
138+
])->add('observation', TextareaType::class, [
139+
'required' => false,
140+
'empty_data' => '',
141+
'label' => 'Observation',
142+
])->add('currency', EnumType::class, [
143+
'required' => false,
144+
'class' => InvoicingCurrency::class,
145+
'attr' => ['size' => count(InvoicingCurrency::cases())],
146+
'label' => 'Monnaie de la facture',
147+
'placeholder' => false,
148+
])->add('details', CollectionType::class, [
149+
'entry_type' => InvoicingRowType::class,
150+
'keep_as_list' => true,
151+
'delete_empty' => $this->isEmpty(...),
152+
])->add('quotationNumber', TextType::class, [
153+
'label' => 'Numéro de devis',
154+
'required' => false,
155+
'attr' => ['readonly' => 'readonly'],
156+
'constraints' => [
157+
new Assert\Type('string'),
158+
new Assert\Length(max: 50),
159+
],
160+
])->add('invoiceNumber', TextType::class, [
161+
'label' => 'Numéro facture',
162+
'required' => false,
163+
'attr' => ['readonly' => 'readonly'],
164+
'constraints' => [
165+
new Assert\Type('string'),
166+
new Assert\Length(max: 50),
167+
],
168+
])->add('paymentStatus', EnumType::class, [
169+
'required' => false,
170+
'class' => InvoicingPaymentStatus::class,
171+
'attr' => ['size' => count(InvoicingPaymentStatus::cases())],
172+
'label' => 'État paiement',
173+
'placeholder' => false,
174+
'choice_label' => fn(InvoicingPaymentStatus $choice, string $key, mixed $value): string => $choice->label(),
175+
])
176+
->add('paymentDate', DateType::class, [
177+
'label' => 'Date de paiement',
178+
'required' => false,
179+
'widget' => 'single_text',
180+
]);
181+
}
182+
183+
private function isEmpty(?InvoicingDetail $detail = null): bool
184+
{
185+
return null === $detail || (empty($detail->getUnitPrice()) && empty($detail->getQuantity()));
186+
}
187+
}

sources/AppBundle/Accounting/InvoicingPaymentStatus.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,13 @@ enum InvoicingPaymentStatus: int
99
case Waiting = 0;
1010
case Payed = 1;
1111
case Cancelled = 2;
12+
13+
public function label(): string
14+
{
15+
return match ($this) {
16+
self::Waiting => 'En attente de paiement',
17+
self::Payed => 'Payé',
18+
self::Cancelled => 'Annulé',
19+
};
20+
}
1221
}

sources/AppBundle/Accounting/Model/Invoicing.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Afup\Site\Utils\Utils;
88
use AppBundle\Accounting\InvoicingCurrency;
9+
use AppBundle\Accounting\InvoicingPaymentStatus;
910
use CCMBenchmark\Ting\Entity\NotifyProperty;
1011
use CCMBenchmark\Ting\Entity\NotifyPropertyInterface;
1112
use DateTime;
@@ -34,7 +35,7 @@ class Invoicing implements NotifyPropertyInterface
3435
private string $lastname = '';
3536
private string $firstname = '';
3637
private string $phone = '';
37-
private int $paymentStatus = 0;
38+
private InvoicingPaymentStatus $paymentStatus = InvoicingPaymentStatus::Waiting;
3839
private ?DateTime $paymentDate = null;
3940
private ?InvoicingCurrency $currency = null;
4041
/** @var InvoicingDetail[] */
@@ -301,12 +302,12 @@ public function setPhone(string $phone): self
301302
return $this;
302303
}
303304

304-
public function getPaymentStatus(): int
305+
public function getPaymentStatus(): InvoicingPaymentStatus
305306
{
306307
return $this->paymentStatus;
307308
}
308309

309-
public function setPaymentStatus(int $paymentStatus): self
310+
public function setPaymentStatus(InvoicingPaymentStatus $paymentStatus): self
310311
{
311312
$this->propertyChanged('paymentStatus', $this->paymentStatus, $paymentStatus);
312313
$this->paymentStatus = $paymentStatus;

sources/AppBundle/Accounting/Model/Repository/InvoicingRepository.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace AppBundle\Accounting\Model\Repository;
66

7+
use AppBundle\Accounting\InvoicingPaymentStatus;
78
use CCMBenchmark\Ting\Repository\Hydrator\AggregateFrom;
89
use CCMBenchmark\Ting\Repository\Hydrator\AggregateTo;
910
use CCMBenchmark\Ting\Repository\Hydrator\RelationMany;
@@ -25,21 +26,21 @@
2526
*/
2627
class InvoicingRepository extends Repository implements MetadataInitializer
2728
{
28-
public function getQuotationById(int $periodId): ?Invoicing
29+
public function getById(int $id): ?Invoicing
2930
{
3031
/** @var Select $builder */
3132
$builder = $this->getQueryBuilder(self::QUERY_SELECT);
3233
$builder->cols(['acf.*', 'acfd.*'])
3334
->from('afup_compta_facture acf')
3435
->leftJoin('afup_compta_facture_details acfd', 'acfd.idafup_compta_facture = acf.id')
35-
->where('acf.id = :periodId');
36+
->where('acf.id = :id');
3637

3738
$hydrator = new HydratorRelational();
3839
$hydrator->addRelation(new RelationMany(new AggregateFrom('acfd'), new AggregateTo('acf'), 'setDetails'));
3940
$hydrator->callableFinalizeAggregate(fn(array $row) => $row['acf']);
4041

4142
$collection = $this->getQuery($builder->getStatement())
42-
->setParams(['periodId' => $periodId])
43+
->setParams(['id' => $id])
4344
->query($this->getCollection($hydrator));
4445

4546
if ($collection->count() === 0) {
@@ -240,7 +241,11 @@ public static function initMetadata(SerializerFactoryInterface $serializerFactor
240241
->addField([
241242
'columnName' => 'etat_paiement',
242243
'fieldName' => 'paymentStatus',
243-
'type' => 'int',
244+
'type' => 'enum',
245+
'serializer' => BackedEnum::class,
246+
'serializer_options' => [
247+
'unserialize' => ['enum' => InvoicingPaymentStatus::class],
248+
],
244249
])
245250
->addField([
246251
'columnName' => 'date_paiement',
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AppBundle\Controller\Admin\Accounting\Invoice;
6+
7+
use AppBundle\Accounting\Form\InvoiceType;
8+
use AppBundle\Accounting\Model\Invoicing;
9+
use AppBundle\Accounting\Model\Repository\InvoicingDetailRepository;
10+
use AppBundle\Accounting\Model\Repository\InvoicingRepository;
11+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
12+
use Symfony\Component\HttpFoundation\Request;
13+
use Symfony\Component\HttpFoundation\Response;
14+
15+
class EditInvoiceAction extends AbstractController
16+
{
17+
public function __construct(
18+
private readonly InvoicingRepository $invoicingRepository,
19+
private readonly InvoicingDetailRepository $invoicingDetailRepository,
20+
) {}
21+
22+
public function __invoke(Request $request): Response
23+
{
24+
$invoiceId = $request->query->getInt('invoiceId');
25+
$invoice = $this->invoicingRepository->getById($invoiceId);
26+
if (!$invoice instanceof Invoicing) {
27+
throw $this->createNotFoundException("Cette facture n'existe pas");
28+
}
29+
30+
$form = $this->createForm(InvoiceType::class, $invoice);
31+
$form->handleRequest($request);
32+
if ($form->isSubmitted() && $form->isValid()) {
33+
try {
34+
$this->invoicingRepository->startTransaction();
35+
$keepIds = array_filter(array_map(fn($d) => $d->getId(), $invoice->getDetails()));
36+
$existingIds = $this->invoicingDetailRepository->getRowsIdsPerInvoicingId($invoiceId);
37+
$toDelete = array_diff($existingIds, $keepIds);
38+
if ($toDelete !== []) {
39+
$this->invoicingDetailRepository->removeRowsPerIds($toDelete);
40+
}
41+
foreach ($invoice->getDetails() as $detail) {
42+
$this->invoicingDetailRepository->save($detail);
43+
}
44+
$this->invoicingRepository->save($invoice);
45+
$this->invoicingRepository->commit();
46+
$this->addFlash('success', 'L\'écriture a été modifiée');
47+
return $this->redirectToRoute('admin_accounting_invoices_list');
48+
} catch (\Exception $e) {
49+
$this->invoicingRepository->rollback();
50+
$this->addFlash('error', 'L\'écriture n\'a pas pu être enregistrée');
51+
}
52+
}
53+
54+
return $this->render('admin/accounting/invoice/edit.html.twig', [
55+
'invoice' => $invoice,
56+
'form' => $form->createView(),
57+
'submitLabel' => 'Modifier',
58+
]);
59+
}
60+
}

sources/AppBundle/Controller/Admin/Accounting/Invoice/ListInvoiceAction.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function __invoke(Request $request): Response
3838

3939
/** @var Invoicing $invoice */
4040
foreach ($invoices as $invoice) {
41-
if ($invoice->getPaymentStatus() === InvoicingPaymentStatus::Cancelled->value) {
41+
if ($invoice->getPaymentStatus() === InvoicingPaymentStatus::Cancelled) {
4242
continue;
4343
}
4444

sources/AppBundle/Controller/Admin/Accounting/Quotation/AddQuotationAction.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public function __invoke(Request $request): Response
5454

5555
private function init(int $quotationId): Invoicing
5656
{
57-
$baseQuotation = $this->invoicingRepository->getQuotationById($quotationId);
57+
$baseQuotation = $this->invoicingRepository->getById($quotationId);
5858
if (!$baseQuotation instanceof Invoicing) {
5959
$quotation = new Invoicing();
6060
$quotation->setQuotationDate(new \DateTime());

sources/AppBundle/Controller/Admin/Accounting/Quotation/EditQuotationAction.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public function __construct(
2121
public function __invoke(Request $request): Response
2222
{
2323
$quotationId = $request->query->getInt('quotationId');
24-
$quotation = $this->invoicingRepository->getQuotationById($quotationId);
24+
$quotation = $this->invoicingRepository->getById($quotationId);
2525
if ($quotation === null) {
2626
throw $this->createNotFoundException("Ce devis n'existe pas");
2727
}

0 commit comments

Comments
 (0)