From 59577c3790927825edd108ab4b4c46b5be8e8580 Mon Sep 17 00:00:00 2001 From: vgreb Date: Tue, 30 Dec 2025 23:39:17 +0100 Subject: [PATCH] =?UTF-8?q?Refonte=20-=20Tr=C3=A9sorerie=20>=20Journal=20>?= =?UTF-8?q?=20T=C3=A9l=C3=A9charger=20un=20justificatif?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/config/routing/admin_accounting.yml | 4 + .../routing/admin_accounting/journal.yml | 6 + db/seeds/Compta.php | 21 ++ .../pages/administration/compta_journal.php | 37 -- .../administration/compta_journal.html | 2 +- .../Repository/TransactionRepository.php | 167 +++++++++ .../Accounting/Model/Transaction.php | 350 ++++++++++++++++++ .../Journal/DownloadAttachmentAction.php | 43 +++ .../features/Admin/Tresorerie/Journal.feature | 18 + 9 files changed, 610 insertions(+), 38 deletions(-) create mode 100644 app/config/routing/admin_accounting/journal.yml create mode 100644 sources/AppBundle/Accounting/Model/Repository/TransactionRepository.php create mode 100644 sources/AppBundle/Accounting/Model/Transaction.php create mode 100644 sources/AppBundle/Controller/Admin/Accounting/Journal/DownloadAttachmentAction.php diff --git a/app/config/routing/admin_accounting.yml b/app/config/routing/admin_accounting.yml index b9c022566..f681c9019 100644 --- a/app/config/routing/admin_accounting.yml +++ b/app/config/routing/admin_accounting.yml @@ -10,6 +10,10 @@ admin_accounting_bank_accounts: resource: "admin_accounting_bank_accounts.yml" prefix: /bank-accounts +admin_accounting_journal: + resource: "admin_accounting/journal.yml" + prefix: /journal + admin_accounting_quotations_list: path: /quotations/list defaults: {_controller: AppBundle\Controller\Admin\Accounting\Quotation\ListQuotationAction} diff --git a/app/config/routing/admin_accounting/journal.yml b/app/config/routing/admin_accounting/journal.yml new file mode 100644 index 000000000..ce1fdf60b --- /dev/null +++ b/app/config/routing/admin_accounting/journal.yml @@ -0,0 +1,6 @@ +admin_accounting_journal_download: + path: /download/{id} + requirements: + id: '\d+' + defaults: + _controller: AppBundle\Controller\Admin\Accounting\Journal\DownloadAttachmentAction diff --git a/db/seeds/Compta.php b/db/seeds/Compta.php index fc8056e41..394bd83f6 100644 --- a/db/seeds/Compta.php +++ b/db/seeds/Compta.php @@ -92,6 +92,27 @@ public function run(): void 'date_regl' => null, 'idevenement' => 0, ], + [ + 'id' => '6', + 'date_ecriture' => '2024-03-10', + 'numero_operation' => 'BILL-XXX', + 'nom_frs' => '', + 'montant' => 42.5, + 'description' => 'une facture', + 'comment' => null, + 'attachment_required' => 1, + 'attachment_filename' => 'facture.pdf', + 'idcompte' => 1, + 'montant_ht_soumis_tva_20' => 34, + 'idclef' => 2, + 'numero' => '', + 'obs_regl' => '', + 'idoperation' => 0, + 'idcategorie' => 0, + 'idmode_regl' => 0, + 'date_regl' => null, + 'idevenement' => 0, + ], [ 'id' => '5', 'idoperation' => 1, diff --git a/htdocs/pages/administration/compta_journal.php b/htdocs/pages/administration/compta_journal.php index f57da40fc..cead40d92 100755 --- a/htdocs/pages/administration/compta_journal.php +++ b/htdocs/pages/administration/compta_journal.php @@ -25,7 +25,6 @@ 'ventiler', 'modifier_colonne', 'export', - 'download_attachment', 'upload_attachment', ]); @@ -541,42 +540,6 @@ echo $e->getMessage(); } exit; -} - -/** - * Download a line attachment - */ elseif ($action === 'download_attachment') { - try { - // Bad request? - if (!isset($_GET['id']) || !($line = $compta->obtenir((int) $_GET['id']))) { - throw new Exception("Please verify parameters", 400); - } - - // Test line existence - if (!$line['id']) { - throw new Exception("Not found", 404); - } - - // Test file existence - $filename = AFUP_CHEMIN_RACINE . 'uploads' . DIRECTORY_SEPARATOR . $line['attachment_filename']; - if (!$line['attachment_filename'] || !is_file($filename)) { - throw new RuntimeException('File not found.'); - } - - // Download it - $finfo = new finfo(FILEINFO_MIME_TYPE); - $mime = $finfo->file($filename); - - header('Content-Type: ' . $mime); - header("Content-Transfer-Encoding: Binary"); - header("Content-disposition: attachment; filename=\"" . basename($filename) . "\""); - readfile($filename); - exit; - } catch (Exception $e) { - header('HTTP/1.1 400 Bad Request'); - header('X-Info: ' . $e->getMessage()); - } - exit; } elseif ($action == 'supprimer') { if ($compta->supprimerEcriture($_GET['id'])) { Logs::log('Suppression de l\'écriture ' . $_GET['id']); diff --git a/htdocs/templates/administration/compta_journal.html b/htdocs/templates/administration/compta_journal.html index ba4adf820..8b510703e 100644 --- a/htdocs/templates/administration/compta_journal.html +++ b/htdocs/templates/administration/compta_journal.html @@ -153,7 +153,7 @@

Journal

diff --git a/sources/AppBundle/Accounting/Model/Repository/TransactionRepository.php b/sources/AppBundle/Accounting/Model/Repository/TransactionRepository.php new file mode 100644 index 000000000..e2da00393 --- /dev/null +++ b/sources/AppBundle/Accounting/Model/Repository/TransactionRepository.php @@ -0,0 +1,167 @@ + + */ +class TransactionRepository extends Repository implements MetadataInitializer +{ + public static function initMetadata(SerializerFactoryInterface $serializerFactory, array $options = []) + { + $metadata = new Metadata($serializerFactory); + + $metadata->setEntity(Transaction::class); + $metadata->setConnectionName('main'); + $metadata->setDatabase($options['database']); + $metadata->setTable('compta'); + + $metadata + ->addField([ + 'columnName' => 'id', + 'fieldName' => 'id', + 'primary' => true, + 'autoincrement' => true, + 'type' => 'int', + ]) + ->addField([ + 'columnName' => 'idclef', + 'fieldName' => 'idKey', + 'type' => 'string', + ]) + + ->addField([ + 'columnName' => 'idoperation', + 'fieldName' => 'operationId', + 'type' => 'int', + ]) + ->addField([ + 'columnName' => 'idcategorie', + 'fieldName' => 'categoryId', + 'type' => 'int', + ]) + ->addField([ + 'columnName' => 'date_ecriture', + 'fieldName' => 'accountingDate', + 'type' => 'date', + 'serializer' => DateTime::class, + 'serializer_options' => [ + 'serialize' => ['format' => 'Y-m-d'], + 'unserialize' => ['format' => 'Y-m-d', 'unSerializeUseFormat' => true], + ], + ]) + ->addField([ + 'columnName' => 'numero_operation', + 'fieldName' => 'operationNumber', + 'type' => 'string', + ]) + ->addField([ + 'columnName' => 'nom_frs', + 'fieldName' => 'vendorName', + 'type' => 'string', + ]) + ->addField([ + 'columnName' => 'tva_intra', + 'fieldName' => 'tvaIntra', + 'type' => 'string', + ]) + ->addField([ + 'columnName' => 'tva_zone', + 'fieldName' => 'tvaZone', + 'type' => 'string', + ]) + ->addField([ + 'columnName' => 'montant', + 'fieldName' => 'amount', + 'type' => 'float', + ]) + ->addField([ + 'columnName' => 'description', + 'fieldName' => 'description', + 'type' => 'string', + ]) + ->addField([ + 'columnName' => 'comment', + 'fieldName' => 'comment', + 'type' => 'string', + ]) + ->addField([ + 'columnName' => 'attachment_required', + 'fieldName' => 'attachmentRequired', + 'type' => 'booleab', + 'default' => false, + ]) + ->addField([ + 'columnName' => 'attachment_filename', + 'fieldName' => 'attachmentFilename', + 'type' => 'string', + ]) + ->addField([ + 'columnName' => 'numero', + 'fieldName' => 'number', + 'type' => 'string', + ]) + ->addField([ + 'columnName' => 'idmode_regl', + 'fieldName' => 'paymentTypeId', + 'type' => 'int', + ]) + ->addField([ + 'columnName' => 'date_regl', + 'fieldName' => 'paymentDate', + 'type' => 'date', + 'serializer' => DateTime::class, + 'serializer_options' => [ + 'serialize' => ['format' => 'Y-m-d'], + 'unserialize' => ['format' => 'Y-m-d', 'unSerializeUseFormat' => true], + ], + ]) + ->addField([ + 'columnName' => 'obs_regl', + 'fieldName' => 'paymentComment', + 'type' => 'string', + ]) + ->addField([ + 'columnName' => 'idevenement', + 'fieldName' => 'eventId', + 'type' => 'int', + ]) + ->addField([ + 'columnName' => 'idcompte', + 'fieldName' => 'accountId', + 'type' => 'int', + ]) + ->addField([ + 'columnName' => 'montant_ht_soumis_tva_20', + 'fieldName' => 'amountTva20', + 'type' => 'flaot', + ]) + ->addField([ + 'columnName' => 'montant_ht_soumis_tva_10', + 'fieldName' => 'amountTva10', + 'type' => 'flaot', + ]) + ->addField([ + 'columnName' => 'montant_ht_soumis_tva_5_5', + 'fieldName' => 'amountTva5_5', + 'type' => 'flaot', + ]) + ->addField([ + 'columnName' => 'montant_ht_soumis_tva_0', + 'fieldName' => 'amountTva0', + 'type' => 'flaot', + ]) + ; + + return $metadata; + } +} diff --git a/sources/AppBundle/Accounting/Model/Transaction.php b/sources/AppBundle/Accounting/Model/Transaction.php new file mode 100644 index 000000000..0f8f30150 --- /dev/null +++ b/sources/AppBundle/Accounting/Model/Transaction.php @@ -0,0 +1,350 @@ +id; + } + + public function setId(int $id): self + { + $this->propertyChanged('id', $this->id, $id); + $this->id = $id; + return $this; + } + + public function getIdKey(): ?string + { + return $this->idKey; + } + + public function setIdKey(?string $idKey): self + { + $this->propertyChanged('name', $this->idKey, $idKey); + $this->idKey = $idKey; + + return $this; + } + + public function getOperationId(): ?int + { + return $this->operationId; + } + + public function setOperationId(?int $operationId): self + { + $this->propertyChanged('operationId', $this->operationId, $operationId); + $this->operationId = $operationId; + + return $this; + } + + public function getCategoryId(): ?int + { + return $this->categoryId; + } + + public function setCategoryId(?int $categoryId): self + { + $this->propertyChanged('categoryId', $this->categoryId, $categoryId); + $this->categoryId = $categoryId; + + return $this; + } + + public function getAccountingDate(): ?DateTime + { + return $this->accountingDate; + } + + public function setAccountingDate(?DateTime $accountingDate): self + { + $this->propertyChanged('accountingDate', $this->accountingDate, $accountingDate); + $this->accountingDate = $accountingDate; + + return $this; + } + + public function getOperationNumber(): ?string + { + return $this->operationNumber; + } + + public function setOperationNumber(?string $operationNumber): self + { + $this->propertyChanged('operationNumber', $this->operationNumber, $operationNumber); + $this->operationNumber = $operationNumber; + + return $this; + } + + public function getVendorName(): string + { + return $this->vendorName; + } + + public function setVendorName(string $vendorName): self + { + $this->propertyChanged('vendorName', $this->vendorName, $vendorName); + $this->vendorName = $vendorName; + + return $this; + } + + public function getTvaIntra(): ?string + { + return $this->tvaIntra; + } + + public function setTvaIntra(?string $tvaIntra): self + { + $this->propertyChanged('tvaIntra', $this->tvaIntra, $tvaIntra); + $this->tvaIntra = $tvaIntra; + + return $this; + } + + public function getTvaZone(): ?string + { + return $this->tvaZone; + } + + public function setTvaZone(?string $tvaZone): self + { + $this->propertyChanged('tvaZone', $this->tvaZone, $tvaZone); + $this->tvaZone = $tvaZone; + + return $this; + } + + public function getAmount(): float + { + return $this->amount; + } + + public function setAmount(float $amount): self + { + $this->propertyChanged('amount', $this->amount, $amount); + $this->amount = $amount; + + return $this; + } + + public function getDescription(): string + { + return $this->description; + } + + public function setDescription(string $description): self + { + $this->propertyChanged('description', $this->description, $description); + $this->description = $description; + + return $this; + } + + public function getComment(): ?string + { + return $this->comment; + } + + public function setComment(?string $comment): self + { + $this->propertyChanged('comment', $this->comment, $comment); + $this->comment = $comment; + + return $this; + } + + public function isAttachmentRequired(): bool + { + return $this->attachmentRequired; + } + + public function setAttachmentRequired(bool $attachmentRequired): self + { + $this->propertyChanged('attachmentRequired', $this->attachmentRequired, $attachmentRequired); + $this->attachmentRequired = $attachmentRequired; + + return $this; + } + + public function getAttachmentFilename(): ?string + { + return $this->attachmentFilename; + } + + public function setAttachmentFilename(?string $attachmentFilename): self + { + $this->propertyChanged('attachmentFilename', $this->attachmentFilename, $attachmentFilename); + $this->attachmentFilename = $attachmentFilename; + + return $this; + } + + public function getNumber(): string + { + return $this->number; + } + + public function setNumber(string $number): self + { + $this->propertyChanged('number', $this->number, $number); + $this->number = $number; + + return $this; + } + + public function getPaymentTypeId(): ?int + { + return $this->paymentTypeId; + } + + public function setPaymentTypeId(?int $paymentTypeId): self + { + $this->propertyChanged('paymentTypeId', $this->paymentTypeId, $paymentTypeId); + $this->paymentTypeId = $paymentTypeId; + + return $this; + } + + public function getPaymentDate(): DateTime + { + return $this->paymentDate; + } + + public function setPaymentDate(DateTime $paymentDate): self + { + $this->propertyChanged('paymentDate', $this->paymentDate, $paymentDate); + $this->paymentDate = $paymentDate; + + return $this; + } + + public function getPaymentComment(): string + { + return $this->paymentComment; + } + + public function setPaymentComment(string $paymentComment): self + { + $this->propertyChanged('paymentComment', $this->paymentComment, $paymentComment); + $this->paymentComment = $paymentComment; + + return $this; + } + + public function getEventId(): ?int + { + return $this->eventId; + } + + public function setEventId(?int $eventId): self + { + $this->propertyChanged('eventId', $this->eventId, $eventId); + $this->eventId = $eventId; + + return $this; + } + + public function getAccountId(): ?int + { + return $this->accountId; + } + + public function setAccountId(?int $accountId): self + { + $this->propertyChanged('accountId', $this->accountId, $accountId); + $this->accountId = $accountId; + + return $this; + } + + public function getAmountTva20(): ?float + { + return $this->amountTva20; + } + + public function setAmountTva20(?float $amountTva20): self + { + $this->propertyChanged('amountTva20', $this->amountTva20, $amountTva20); + $this->amountTva20 = $amountTva20; + + return $this; + } + + public function getAmountTva10(): ?float + { + return $this->amountTva10; + } + + public function setAmountTva10(?float $amountTva10): self + { + $this->propertyChanged('amountTva10', $this->amountTva10, $amountTva10); + $this->amountTva10 = $amountTva10; + + return $this; + } + + public function getAmountTva55(): ?float + { + return $this->amountTva5_5; + } + + public function setAmountTva55(?float $amountTva5_5): self + { + $this->propertyChanged('amountTva55', $this->amountTva5_5, $amountTva5_5); + $this->amountTva5_5 = $amountTva5_5; + + return $this; + } + + public function getAmountTva0(): ?float + { + return $this->amountTva0; + } + + public function setAmountTva0(?float $amountTva0): self + { + $this->propertyChanged('amountTva0', $this->amountTva0, $amountTva0); + $this->amountTva0 = $amountTva0; + + return $this; + } +} diff --git a/sources/AppBundle/Controller/Admin/Accounting/Journal/DownloadAttachmentAction.php b/sources/AppBundle/Controller/Admin/Accounting/Journal/DownloadAttachmentAction.php new file mode 100644 index 000000000..4e3b423f1 --- /dev/null +++ b/sources/AppBundle/Controller/Admin/Accounting/Journal/DownloadAttachmentAction.php @@ -0,0 +1,43 @@ +accountingRepository->get($id); + if (!$accounting instanceof Transaction) { + throw $this->createNotFoundException(); + } + + $path = $this->uploadDir . $accounting->getAttachmentFilename(); + if ($accounting->getAttachmentFilename() === null || !is_file($path)) { + throw $this->createNotFoundException('No attachment found'); + } + + try { + return new BinaryFileResponse($path, Response::HTTP_OK, [ + 'Content-disposition' => 'attachment; filename="' . basename($path) . '"', + ], false); + } catch (\Exception $e) { + return new Response('', Response::HTTP_NOT_FOUND, ['X-Info' => $e->getMessage()]); + } + } +} diff --git a/tests/behat/features/Admin/Tresorerie/Journal.feature b/tests/behat/features/Admin/Tresorerie/Journal.feature index 7e7bf4ccf..3f9ea9536 100644 --- a/tests/behat/features/Admin/Tresorerie/Journal.feature +++ b/tests/behat/features/Admin/Tresorerie/Journal.feature @@ -90,3 +90,21 @@ Feature: Administration - Trésorerie - Journal When I follow "Journal" And I follow "Télécharger les justificatifs groupés par mois" Then the response header "Content-disposition" should match '#filename="afup_justificatifs-(.*).zip"#' + + @reloadDbWithTestData + Scenario: Compte journal Télécharger un justificatif + Given I am logged in as admin and on the Administration + When I follow "Journal" + And I follow "Afficher aussi les entrées pointées" + Then I should see "Une recette qui rapporte" + When I follow "Télécharger le justificatif" + Then the response header "Content-Disposition" should match '#^attachment; filename="test_file1.pdf"#' + + @reloadDbWithTestData + Scenario: Compte journal afficher les entrées déjà pointées + Given I am logged in as admin and on the Administration + When I follow "Journal" + And I follow "Afficher aussi les entrées pointées" + Then I should see "Une recette qui rapporte" + And I should see "Une dépense très utile" + And I should see "Une dépense moins utile"