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"