diff --git a/.gitignore b/.gitignore
index 012f2eeb..830c0a03 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+/var/
/vendor/
/node_modules/
/composer.lock
diff --git a/CHANGELOG-2.0.md b/CHANGELOG-2.0.md
index c68dbd6f..22127eec 100644
--- a/CHANGELOG-2.0.md
+++ b/CHANGELOG-2.0.md
@@ -1,5 +1,10 @@
# CHANGELOG
+### v2.0.3 (2025-10-21)
+
+- [#373](https://github.com/Sylius/InvoicingPlugin/pull/373) Add configurable invoice sequence scope (`monthly`/`annually`/`global`)
+ via SYLIUS_INVOICING_SEQUENCE_SCOPE ENV ([@tomkalon](https://github.com/tomkalon))
+
### v2.0.2 (2025-07-03)
- [#373](https://github.com/Sylius/InvoicingPlugin/pull/373) Add sylius/test-application ([@Wojdylak](https://github.com/Wojdylak))
diff --git a/CHANGELOG-3.0.md b/CHANGELOG-3.0.md
new file mode 100644
index 00000000..e77d59b0
--- /dev/null
+++ b/CHANGELOG-3.0.md
@@ -0,0 +1,13 @@
+# CHANGELOG
+
+### v3.0.0 (2025-10-24)
+
+- [#397](https://github.com/Sylius/InvoicingPlugin/pull/397) Isolate plugin messaging: Introduce dedicated event & command buses for InvoicingPlugin ([@tomkalon](https://github.com/tomkalon))
+- [#398](https://github.com/Sylius/InvoicingPlugin/pull/398) Persisted PDF path & file flow
+ - `Invoice`: added `path` field (UNIQUE).
+ - `InvoiceFactory`: inject `InvoiceFileNameGeneratorInterface`; use `generateForPdf($number)` to set `path` on creation.
+ - `InvoiceFileProvider`: removed dependency on file-name generator; added `%sylius_invoicing.pdf_generator.enabled%`; now relies on `Invoice::path()`.
+ - `InvoiceCreator`: removed `InvoicePdfFileGeneratorInterface` and `InvoiceFileManagerInterface`; PDF is no longer generated on invoice creation—it's generated lazily on first download/provide.
+ - `InvoiceFileNameGeneratorInterface::generateForPdf()` now accepts `string $invoiceNumber` instead of `InvoiceInterface`.
+ - `InvoiceFileNameGeneratorInterface::generateForPdf()` can prefix filenames based on `SYLIUS_INVOICING_SEQUENCE_SCOPE` (`global` – default, `monthly`, `annually`).
+ - `InvoicePdfFileGenerator`: removed `InvoiceFileNameGeneratorInterface` from constructor; filename is taken from `Invoice::path()`; update DI to drop the generator argument.
diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md
index 052c0a91..667c4aa9 100644
--- a/UPGRADE-2.0.md
+++ b/UPGRADE-2.0.md
@@ -1,3 +1,17 @@
+# UPGRADE FROM 2.0 TO 2.1
+
+## Changes
+
+1. Added support for configurable invoice sequence scoping via the SYLIUS_INVOICING_SEQUENCE_SCOPE environment variable:
+
+- monthly: resets invoice numbering each month
+- annually: resets invoice numbering each year
+- global or unset (default): uses a single global sequence (as previously)
+
+## Deprecations
+
+1. Not passing the $scope argument (of type InvoiceSequenceScopeEnum) to the constructor of SequentialInvoiceNumberGenerator is deprecated and will be required starting from version 3.0.
+
# UPGRADE FROM 1.X TO 2.0
1. Support for Sylius 2.0 has been added, it is now the recommended Sylius version to use with InvoicingPlugin.
diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md
new file mode 100644
index 00000000..e7532d5c
--- /dev/null
+++ b/UPGRADE-3.0.md
@@ -0,0 +1,104 @@
+# UPGRADE FROM 2.2 TO 3.0
+
+## Changes
+
+1. Persisted PDF path on `Invoice`:
+
+- Added to `Invoice` new `path` field (unique) storing the final PDF location (e.g. annually/2025_10_000000001.pdf).
+
+2. Filename generation moved from `InvoiceCreator` to `InvoiceFactory`.
+
+`InvoiceCreator` no longer generates PDFs at creation time.
+
+PDFs are generated on first provide/download via the provider.
+
+```xml
+
+ %sylius_invoicing.model.invoice.class%
+
++
+
+```
+
+```xml
+
+
+
+
+-
+-
+- %sylius_invoicing.pdf_generator.enabled%
+
+```
+
+3. `InvoiceFactory` now depends on `InvoiceFileNameGeneratorInterface`.
+```xml
+
+ %sylius_invoicing.model.invoice.class%
+
++
+
+```
+
+On creation, it calls:
+```php
+$fileName = $invoiceFileNameGenerator->generateForPdf($number);
+```
+and passes it to the `Invoice` constructor as `$path`.
+
+4. `InvoiceFileProvider` is now the primary orchestrator of PDF generation
+
+Removed `InvoiceFileNameGeneratorInterface` from `InvoiceFileProvider`.
+
+Added `sylius_invoicing.pdf_generator.enabled` parameter to constructor.
+
+```xml
+
+-
+
+
+
+ %sylius_invoicing.invoice_save_path%
++ %sylius_invoicing.pdf_generator.enabled%
+
+```
+
+5. `InvoiceFileNameGenerator` signature & scoping
+
+BC break: `generateForPdf()` now accepts string $invoiceNumber (not `InvoiceInterface`).
+
+```php
+// before:
+public function generateForPdf(InvoiceInterface $invoice): string;
+
+// after:
+public function generateForPdf(string $invoiceNumber): string;
+```
+
+6. Can prefix filenames based on `SYLIUS_INVOICING_SEQUENCE_SCOPE`:
+
+>global (default): no prefix
+>
+>monthly: monthly/…
+>
+>annually: annually/…
+
+7. `InvoicePdfFileGenerator` simplified:
+
+- Removed dependency on InvoiceFileNameGeneratorInterface.
+
+- Uses `Invoice::path()` as the filename:
+
+```xml
+
+
+
+-
+ @SyliusInvoicingPlugin/shared/download/pdf.html.twig
+ %sylius_invoicing.template.logo_file%
+
+```
+
+```php
+$filename = $invoice->path();
+```
diff --git a/config/config.yaml b/config/config.yaml
index 87569df3..47fe4ced 100644
--- a/config/config.yaml
+++ b/config/config.yaml
@@ -5,6 +5,8 @@ imports:
parameters:
sylius_invoicing.invoice_save_path: "%kernel.project_dir%/private/invoices/"
sylius_invoicing.filesystem_adapter.invoice: "sylius_invoicing_invoice"
+ sylius_invoicing.sequence_scope: '%env(default::SYLIUS_INVOICING_SEQUENCE_SCOPE)%'
+ env(SYLIUS_INVOICING_SEQUENCE_SCOPE): 'global'
sylius_invoicing:
pdf_generator:
diff --git a/config/doctrine/Invoice.orm.xml b/config/doctrine/Invoice.orm.xml
index c754a98f..5603b2d6 100644
--- a/config/doctrine/Invoice.orm.xml
+++ b/config/doctrine/Invoice.orm.xml
@@ -10,6 +10,7 @@
+
diff --git a/config/doctrine/InvoiceSequence.orm.xml b/config/doctrine/InvoiceSequence.orm.xml
index ed04a5e4..4d6591d2 100644
--- a/config/doctrine/InvoiceSequence.orm.xml
+++ b/config/doctrine/InvoiceSequence.orm.xml
@@ -11,6 +11,9 @@
+
+
+
diff --git a/config/services.xml b/config/services.xml
index 47ea616a..f8c972f2 100644
--- a/config/services.xml
+++ b/config/services.xml
@@ -51,6 +51,7 @@
%sylius_invoicing.model.invoice.class%
+
@@ -60,11 +61,11 @@
-
%sylius_invoicing.invoice_save_path%
+ %sylius_invoicing.pdf_generator.enabled%
diff --git a/config/services/generators.xml b/config/services/generators.xml
index cfca0642..b5cd3d80 100644
--- a/config/services/generators.xml
+++ b/config/services/generators.xml
@@ -22,6 +22,7 @@
+ %sylius_invoicing.sequence_scope%
@@ -38,16 +39,14 @@
-
+
+ %sylius_invoicing.sequence_scope%
+
-
@SyliusInvoicingPlugin/shared/download/pdf.html.twig
%sylius_invoicing.template.logo_file%
@@ -57,9 +56,6 @@
-
-
- %sylius_invoicing.pdf_generator.enabled%
diff --git a/src/Creator/InvoiceCreator.php b/src/Creator/InvoiceCreator.php
index 15b18b96..91c97008 100644
--- a/src/Creator/InvoiceCreator.php
+++ b/src/Creator/InvoiceCreator.php
@@ -13,15 +13,12 @@
namespace Sylius\InvoicingPlugin\Creator;
-use Doctrine\ORM\Exception\ORMException;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
use Sylius\InvoicingPlugin\Doctrine\ORM\InvoiceRepositoryInterface;
use Sylius\InvoicingPlugin\Entity\InvoiceInterface;
use Sylius\InvoicingPlugin\Exception\InvoiceAlreadyGenerated;
use Sylius\InvoicingPlugin\Generator\InvoiceGeneratorInterface;
-use Sylius\InvoicingPlugin\Generator\InvoicePdfFileGeneratorInterface;
-use Sylius\InvoicingPlugin\Manager\InvoiceFileManagerInterface;
final class InvoiceCreator implements InvoiceCreatorInterface
{
@@ -29,9 +26,6 @@ public function __construct(
private readonly InvoiceRepositoryInterface $invoiceRepository,
private readonly OrderRepositoryInterface $orderRepository,
private readonly InvoiceGeneratorInterface $invoiceGenerator,
- private readonly InvoicePdfFileGeneratorInterface $invoicePdfFileGenerator,
- private readonly InvoiceFileManagerInterface $invoiceFileManager,
- private readonly bool $hasEnabledPdfFileGenerator = true,
) {
}
@@ -49,19 +43,6 @@ public function __invoke(string $orderNumber, \DateTimeInterface $dateTime): voi
$invoice = $this->invoiceGenerator->generateForOrder($order, $dateTime);
- if (!$this->hasEnabledPdfFileGenerator) {
- $this->invoiceRepository->add($invoice);
-
- return;
- }
-
- $invoicePdf = $this->invoicePdfFileGenerator->generate($invoice);
- $this->invoiceFileManager->save($invoicePdf);
-
- try {
- $this->invoiceRepository->add($invoice);
- } catch (ORMException) {
- $this->invoiceFileManager->remove($invoicePdf);
- }
+ $this->invoiceRepository->add($invoice);
}
}
diff --git a/src/Entity/Invoice.php b/src/Entity/Invoice.php
index 969e6bd8..46ce1615 100644
--- a/src/Entity/Invoice.php
+++ b/src/Entity/Invoice.php
@@ -36,6 +36,7 @@ public function __construct(
protected ChannelInterface $channel,
protected string $paymentState,
protected InvoiceShopBillingDataInterface $shopBillingData,
+ protected string $path,
) {
$this->issuedAt = clone $issuedAt;
@@ -143,4 +144,9 @@ public function paymentState(): string
{
return $this->paymentState;
}
+
+ public function path(): string
+ {
+ return $this->path;
+ }
}
diff --git a/src/Entity/InvoiceInterface.php b/src/Entity/InvoiceInterface.php
index 0ddede9a..e8ef1e66 100644
--- a/src/Entity/InvoiceInterface.php
+++ b/src/Entity/InvoiceInterface.php
@@ -53,4 +53,6 @@ public function channel(): ChannelInterface;
public function shopBillingData(): InvoiceShopBillingDataInterface;
public function paymentState(): string;
+
+ public function path(): string;
}
diff --git a/src/Entity/InvoiceSequence.php b/src/Entity/InvoiceSequence.php
index 064eb8e8..a8ec4226 100644
--- a/src/Entity/InvoiceSequence.php
+++ b/src/Entity/InvoiceSequence.php
@@ -13,6 +13,8 @@
namespace Sylius\InvoicingPlugin\Entity;
+use Sylius\InvoicingPlugin\Enum\InvoiceSequenceScopeEnum;
+
/** @final */
class InvoiceSequence implements InvoiceSequenceInterface
{
@@ -23,6 +25,12 @@ class InvoiceSequence implements InvoiceSequenceInterface
protected ?int $version = 1;
+ protected ?InvoiceSequenceScopeEnum $type = null;
+
+ protected ?int $year = null;
+
+ protected ?int $month = null;
+
/** @return mixed */
public function getId()
{
@@ -48,4 +56,34 @@ public function setVersion(?int $version): void
{
$this->version = $version;
}
+
+ public function getType(): ?InvoiceSequenceScopeEnum
+ {
+ return $this->type;
+ }
+
+ public function setType(?InvoiceSequenceScopeEnum $type): void
+ {
+ $this->type = $type;
+ }
+
+ public function getYear(): ?int
+ {
+ return $this->year;
+ }
+
+ public function setYear(?int $year): void
+ {
+ $this->year = $year;
+ }
+
+ public function getMonth(): ?int
+ {
+ return $this->month;
+ }
+
+ public function setMonth(?int $month): void
+ {
+ $this->month = $month;
+ }
}
diff --git a/src/Entity/InvoiceSequenceInterface.php b/src/Entity/InvoiceSequenceInterface.php
index a263fe9a..51efcd57 100644
--- a/src/Entity/InvoiceSequenceInterface.php
+++ b/src/Entity/InvoiceSequenceInterface.php
@@ -15,10 +15,23 @@
use Sylius\Component\Resource\Model\ResourceInterface;
use Sylius\Component\Resource\Model\VersionedInterface;
+use Sylius\InvoicingPlugin\Enum\InvoiceSequenceScopeEnum;
interface InvoiceSequenceInterface extends ResourceInterface, VersionedInterface
{
public function getIndex(): int;
public function incrementIndex(): void;
+
+ public function getType(): ?InvoiceSequenceScopeEnum;
+
+ public function setType(?InvoiceSequenceScopeEnum $type): void;
+
+ public function getYear(): ?int;
+
+ public function getMonth(): ?int;
+
+ public function setYear(?int $year): void;
+
+ public function setMonth(?int $month): void;
}
diff --git a/src/Enum/InvoiceSequenceScopeEnum.php b/src/Enum/InvoiceSequenceScopeEnum.php
new file mode 100644
index 00000000..110876b9
--- /dev/null
+++ b/src/Enum/InvoiceSequenceScopeEnum.php
@@ -0,0 +1,21 @@
+invoiceFileNameGenerator->generateForPdf($number);
+
/** @var InvoiceInterface $invoice */
$invoice = new $this->className(
$id,
@@ -63,6 +67,7 @@ public function createForData(
$channel,
$paymentState,
$shopBillingData ?? $this->invoiceShopBillingDataFactory->createNew(),
+ $fileName,
);
Assert::isInstanceOf($invoice, InvoiceInterface::class);
diff --git a/src/Generator/InvoiceFileNameGenerator.php b/src/Generator/InvoiceFileNameGenerator.php
index 5ab62abf..47e8673e 100644
--- a/src/Generator/InvoiceFileNameGenerator.php
+++ b/src/Generator/InvoiceFileNameGenerator.php
@@ -13,14 +13,28 @@
namespace Sylius\InvoicingPlugin\Generator;
-use Sylius\InvoicingPlugin\Entity\InvoiceInterface;
+use Sylius\InvoicingPlugin\Enum\InvoiceSequenceScopeEnum;
final class InvoiceFileNameGenerator implements InvoiceFileNameGeneratorInterface
{
private const PDF_FILE_EXTENSION = '.pdf';
- public function generateForPdf(InvoiceInterface $invoice): string
+ public function __construct(
+ private readonly ?string $scope = null,
+ ) {
+ }
+
+ public function generateForPdf(string $invoiceNumber): string
{
- return str_replace('/', '_', $invoice->number()) . self::PDF_FILE_EXTENSION;
+ $scope = InvoiceSequenceScopeEnum::tryFrom($this->scope ?? '') ?? InvoiceSequenceScopeEnum::GLOBAL;
+ $prefix = $scope->value . '/';
+
+ if ($scope === InvoiceSequenceScopeEnum::GLOBAL) {
+ $prefix = '';
+ }
+
+ $fileName = str_replace('/', '_', $invoiceNumber) . self::PDF_FILE_EXTENSION;
+
+ return $prefix . $fileName;
}
}
diff --git a/src/Generator/InvoiceFileNameGeneratorInterface.php b/src/Generator/InvoiceFileNameGeneratorInterface.php
index f57fe7f5..60834eab 100644
--- a/src/Generator/InvoiceFileNameGeneratorInterface.php
+++ b/src/Generator/InvoiceFileNameGeneratorInterface.php
@@ -13,9 +13,7 @@
namespace Sylius\InvoicingPlugin\Generator;
-use Sylius\InvoicingPlugin\Entity\InvoiceInterface;
-
interface InvoiceFileNameGeneratorInterface
{
- public function generateForPdf(InvoiceInterface $invoice): string;
+ public function generateForPdf(string $invoiceNumber): string;
}
diff --git a/src/Generator/InvoicePdfFileGenerator.php b/src/Generator/InvoicePdfFileGenerator.php
index b211caef..82679469 100644
--- a/src/Generator/InvoicePdfFileGenerator.php
+++ b/src/Generator/InvoicePdfFileGenerator.php
@@ -22,7 +22,6 @@ final class InvoicePdfFileGenerator implements InvoicePdfFileGeneratorInterface
public function __construct(
private readonly TwigToPdfGeneratorInterface $twigToPdfGenerator,
private readonly FileLocatorInterface $fileLocator,
- private readonly InvoiceFileNameGeneratorInterface $invoiceFileNameGenerator,
private readonly string $template,
private readonly string $invoiceLogoPath,
) {
@@ -30,7 +29,7 @@ public function __construct(
public function generate(InvoiceInterface $invoice): InvoicePdf
{
- $filename = $this->invoiceFileNameGenerator->generateForPdf($invoice);
+ $filename = $invoice->path();
$pdf = $this->twigToPdfGenerator->generate(
$this->template,
diff --git a/src/Generator/SequentialInvoiceNumberGenerator.php b/src/Generator/SequentialInvoiceNumberGenerator.php
index 89e41349..b5754949 100644
--- a/src/Generator/SequentialInvoiceNumberGenerator.php
+++ b/src/Generator/SequentialInvoiceNumberGenerator.php
@@ -18,6 +18,7 @@
use Sylius\Component\Resource\Factory\FactoryInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Sylius\InvoicingPlugin\Entity\InvoiceSequenceInterface;
+use Sylius\InvoicingPlugin\Enum\InvoiceSequenceScopeEnum;
use Symfony\Component\Clock\ClockInterface;
final class SequentialInvoiceNumberGenerator implements InvoiceNumberGenerator
@@ -29,7 +30,17 @@ public function __construct(
private readonly ClockInterface $clock,
private readonly int $startNumber = 1,
private readonly int $numberLength = 9,
+ private readonly ?string $scope = null,
) {
+ if (null === $this->scope) {
+ trigger_deprecation(
+ 'sylius/invoicing-plugin',
+ '2.1',
+ 'Not passing the "%s" argument to "%s::__construct()" is deprecated and will be required in version 3.0. Pass a valid scope explicitly (e.g. "monthly", "annually", or "global").',
+ 'scope',
+ self::class,
+ );
+ }
}
public function generate(): string
@@ -56,15 +67,47 @@ private function generateNumber(int $index): string
private function getSequence(): InvoiceSequenceInterface
{
- /** @var InvoiceSequenceInterface $sequence */
- $sequence = $this->sequenceRepository->findOneBy([]);
-
- if (null != $sequence) {
+ $now = $this->clock->now();
+ $scope = InvoiceSequenceScopeEnum::tryFrom($this->scope ?? '') ?? InvoiceSequenceScopeEnum::GLOBAL;
+
+ $criteria = match ($scope) {
+ InvoiceSequenceScopeEnum::MONTHLY => [
+ 'year' => (int) $now->format('Y'),
+ 'month' => (int) $now->format('m'),
+ 'type' => $scope,
+ ],
+ InvoiceSequenceScopeEnum::ANNUALLY => [
+ 'year' => (int) $now->format('Y'),
+ 'type' => $scope,
+ ],
+ InvoiceSequenceScopeEnum::GLOBAL => [
+ 'year' => null,
+ 'month' => null,
+ ],
+ };
+
+ /** @var InvoiceSequenceInterface|null $sequence */
+ $sequence = $this->sequenceRepository->findOneBy($criteria);
+
+ if (null !== $sequence) {
return $sequence;
}
/** @var InvoiceSequenceInterface $sequence */
$sequence = $this->sequenceFactory->createNew();
+
+ if (isset($criteria['year'])) {
+ $sequence->setYear($criteria['year']);
+ }
+
+ if (isset($criteria['month'])) {
+ $sequence->setMonth($criteria['month']);
+ }
+
+ if (isset($criteria['type'])) {
+ $sequence->setType($criteria['type']);
+ }
+
$this->sequenceManager->persist($sequence);
return $sequence;
diff --git a/src/Migrations/Version20251021074051.php b/src/Migrations/Version20251021074051.php
new file mode 100644
index 00000000..c8933206
--- /dev/null
+++ b/src/Migrations/Version20251021074051.php
@@ -0,0 +1,35 @@
+addSql('ALTER TABLE sylius_invoicing_plugin_sequence ADD year INT DEFAULT NULL, ADD month INT DEFAULT NULL, ADD type VARCHAR(255) DEFAULT NULL');
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql('ALTER TABLE sylius_invoicing_plugin_sequence DROP year, DROP month, DROP type');
+ }
+}
diff --git a/src/Migrations/Version20251023082457.php b/src/Migrations/Version20251023082457.php
new file mode 100644
index 00000000..167e8f86
--- /dev/null
+++ b/src/Migrations/Version20251023082457.php
@@ -0,0 +1,37 @@
+addSql('ALTER TABLE sylius_invoicing_plugin_invoice ADD path VARCHAR(255) NOT NULL');
+ $this->addSql('CREATE UNIQUE INDEX UNIQ_3AA279BFB548B0F ON sylius_invoicing_plugin_invoice (path)');
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql('DROP INDEX UNIQ_3AA279BFB548B0F ON sylius_invoicing_plugin_invoice');
+ $this->addSql('ALTER TABLE sylius_invoicing_plugin_invoice DROP path');
+ }
+}
diff --git a/src/Provider/InvoiceFileProvider.php b/src/Provider/InvoiceFileProvider.php
index 711ce694..480fc6f2 100644
--- a/src/Provider/InvoiceFileProvider.php
+++ b/src/Provider/InvoiceFileProvider.php
@@ -16,7 +16,6 @@
use Gaufrette\Exception\FileNotFound;
use Gaufrette\FilesystemInterface;
use Sylius\InvoicingPlugin\Entity\InvoiceInterface;
-use Sylius\InvoicingPlugin\Generator\InvoiceFileNameGeneratorInterface;
use Sylius\InvoicingPlugin\Generator\InvoicePdfFileGeneratorInterface;
use Sylius\InvoicingPlugin\Manager\InvoiceFileManagerInterface;
use Sylius\InvoicingPlugin\Model\InvoicePdf;
@@ -24,24 +23,27 @@
final class InvoiceFileProvider implements InvoiceFileProviderInterface
{
public function __construct(
- private readonly InvoiceFileNameGeneratorInterface $invoiceFileNameGenerator,
private readonly FilesystemInterface $filesystem,
private readonly InvoicePdfFileGeneratorInterface $invoicePdfFileGenerator,
private readonly InvoiceFileManagerInterface $invoiceFileManager,
private readonly string $invoicesDirectory,
+ private readonly bool $hasEnabledPdfFileGenerator = true,
) {
}
public function provide(InvoiceInterface $invoice): InvoicePdf
{
- $invoiceFileName = $this->invoiceFileNameGenerator->generateForPdf($invoice);
+ $invoiceFileName = $invoice->path();
try {
$invoiceFile = $this->filesystem->get($invoiceFileName);
$invoicePdf = new InvoicePdf($invoiceFileName, $invoiceFile->getContent());
} catch (FileNotFound) {
$invoicePdf = $this->invoicePdfFileGenerator->generate($invoice);
- $this->invoiceFileManager->save($invoicePdf);
+
+ if ($this->hasEnabledPdfFileGenerator) {
+ $this->invoiceFileManager->save($invoicePdf);
+ }
}
$invoicePdf->setFullPath($this->invoicesDirectory . '/' . $invoiceFileName);
diff --git a/src/Ui/Action/DownloadInvoiceAction.php b/src/Ui/Action/DownloadInvoiceAction.php
index 4f32e3bf..2575730e 100644
--- a/src/Ui/Action/DownloadInvoiceAction.php
+++ b/src/Ui/Action/DownloadInvoiceAction.php
@@ -49,8 +49,10 @@ public function __invoke(string $id): Response
$invoiceFile = $this->invoiceFilePathProvider->provide($invoice);
$response = new Response($invoiceFile->content(), Response::HTTP_OK, ['Content-Type' => 'application/pdf']);
+ $filename = basename($invoiceFile->filename());
+
$response->headers->add([
- 'Content-Disposition' => $response->headers->makeDisposition('attachment', $invoiceFile->filename()),
+ 'Content-Disposition' => $response->headers->makeDisposition('attachment', $filename),
]);
return $response;
diff --git a/tests/Behat/Context/Application/ManagingInvoicesContext.php b/tests/Behat/Context/Application/ManagingInvoicesContext.php
index 765d8128..01996f16 100644
--- a/tests/Behat/Context/Application/ManagingInvoicesContext.php
+++ b/tests/Behat/Context/Application/ManagingInvoicesContext.php
@@ -30,7 +30,7 @@ public function theInvoiceForOrderShouldBeSavedOnTheServer(OrderInterface $order
{
/** @var InvoiceInterface $invoice */
$invoice = $this->invoiceRepository->findOneByOrder($order);
- $filePath = $this->invoicesSavePath.'/'.str_replace('/', '_', $invoice->number()).'.pdf';
+ $filePath = $this->invoicesSavePath.'/'.$invoice->path();
Assert::true(file_exists($filePath));
}
diff --git a/tests/TestApplication/.env b/tests/TestApplication/.env
index 657afa80..a055682d 100644
--- a/tests/TestApplication/.env
+++ b/tests/TestApplication/.env
@@ -9,3 +9,5 @@ WKHTMLTOPDF_PATH=/usr/local/bin/wkhtmltopdf
###< knplabs/knp-snappy-bundle ###
TEST_SYLIUS_INVOICING_PDF_GENERATION_DISABLED=false
+
+SYLIUS_INVOICING_SEQUENCE_SCOPE='monthly'
diff --git a/tests/Unit/Creator/InvoiceCreatorTest.php b/tests/Unit/Creator/InvoiceCreatorTest.php
index deafe858..7edfc1b0 100644
--- a/tests/Unit/Creator/InvoiceCreatorTest.php
+++ b/tests/Unit/Creator/InvoiceCreatorTest.php
@@ -13,7 +13,6 @@
namespace Tests\Sylius\InvoicingPlugin\Unit\Creator;
-use Doctrine\ORM\EntityNotFoundException;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@@ -25,9 +24,6 @@
use Sylius\InvoicingPlugin\Entity\InvoiceInterface;
use Sylius\InvoicingPlugin\Exception\InvoiceAlreadyGenerated;
use Sylius\InvoicingPlugin\Generator\InvoiceGeneratorInterface;
-use Sylius\InvoicingPlugin\Generator\InvoicePdfFileGeneratorInterface;
-use Sylius\InvoicingPlugin\Manager\InvoiceFileManagerInterface;
-use Sylius\InvoicingPlugin\Model\InvoicePdf;
final class InvoiceCreatorTest extends TestCase
{
@@ -37,10 +33,6 @@ final class InvoiceCreatorTest extends TestCase
private InvoiceGeneratorInterface&MockObject $invoiceGenerator;
- private InvoicePdfFileGeneratorInterface&MockObject $invoicePdfFileGenerator;
-
- private InvoiceFileManagerInterface&MockObject $invoiceFileManager;
-
private InvoiceCreator $creator;
protected function setUp(): void
@@ -49,15 +41,11 @@ protected function setUp(): void
$this->invoiceRepository = $this->createMock(InvoiceRepositoryInterface::class);
$this->orderRepository = $this->createMock(OrderRepositoryInterface::class);
$this->invoiceGenerator = $this->createMock(InvoiceGeneratorInterface::class);
- $this->invoicePdfFileGenerator = $this->createMock(InvoicePdfFileGeneratorInterface::class);
- $this->invoiceFileManager = $this->createMock(InvoiceFileManagerInterface::class);
$this->creator = new InvoiceCreator(
$this->invoiceRepository,
$this->orderRepository,
$this->invoiceGenerator,
- $this->invoicePdfFileGenerator,
- $this->invoiceFileManager,
);
}
@@ -72,7 +60,6 @@ public function it_creates_invoice_for_order(): void
{
$order = $this->createMock(OrderInterface::class);
$invoice = $this->createMock(InvoiceInterface::class);
- $invoicePdf = new InvoicePdf('invoice.pdf', 'CONTENT');
$invoiceDateTime = new \DateTimeImmutable('2019-02-25');
$this->orderRepository
@@ -93,17 +80,6 @@ public function it_creates_invoice_for_order(): void
->with($order, $invoiceDateTime)
->willReturn($invoice);
- $this->invoicePdfFileGenerator
- ->expects(self::once())
- ->method('generate')
- ->with($invoice)
- ->willReturn($invoicePdf);
-
- $this->invoiceFileManager
- ->expects(self::once())
- ->method('save')
- ->with($invoicePdf);
-
$this->invoiceRepository
->expects(self::once())
->method('add')
@@ -112,107 +88,6 @@ public function it_creates_invoice_for_order(): void
($this->creator)('0000001', $invoiceDateTime);
}
- #[Test]
- public function it_creates_invoice_without_generating_pdf_file(): void
- {
- $creator = new InvoiceCreator(
- $this->invoiceRepository,
- $this->orderRepository,
- $this->invoiceGenerator,
- $this->invoicePdfFileGenerator,
- $this->invoiceFileManager,
- false,
- );
-
- $order = $this->createMock(OrderInterface::class);
- $invoice = $this->createMock(InvoiceInterface::class);
- $invoiceDateTime = new \DateTimeImmutable('2019-02-25');
-
- $this->orderRepository
- ->expects(self::once())
- ->method('findOneByNumber')
- ->with('0000001')
- ->willReturn($order);
-
- $this->invoiceRepository
- ->expects(self::once())
- ->method('findOneByOrder')
- ->with($order)
- ->willReturn(null);
-
- $this->invoiceGenerator
- ->expects(self::once())
- ->method('generateForOrder')
- ->with($order, $invoiceDateTime)
- ->willReturn($invoice);
-
- $this->invoicePdfFileGenerator
- ->expects($this->never())
- ->method('generate');
-
- $this->invoiceFileManager
- ->expects($this->never())
- ->method('save');
-
- $this->invoiceRepository
- ->expects(self::once())
- ->method('add')
- ->with($invoice);
-
- $creator('0000001', $invoiceDateTime);
- }
-
- #[Test]
- public function it_removes_saved_invoice_file_if_database_update_fails(): void
- {
- $order = $this->createMock(OrderInterface::class);
- $invoice = $this->createMock(InvoiceInterface::class);
- $invoicePdf = new InvoicePdf('invoice.pdf', 'CONTENT');
- $invoiceDateTime = new \DateTimeImmutable('2019-02-25');
-
- $this->orderRepository
- ->expects(self::once())
- ->method('findOneByNumber')
- ->with('0000001')
- ->willReturn($order);
-
- $this->invoiceRepository
- ->expects(self::once())
- ->method('findOneByOrder')
- ->with($order)
- ->willReturn(null);
-
- $this->invoiceGenerator
- ->expects(self::once())
- ->method('generateForOrder')
- ->with($order, $invoiceDateTime)
- ->willReturn($invoice);
-
- $this->invoicePdfFileGenerator
- ->expects(self::once())
- ->method('generate')
- ->with($invoice)
- ->willReturn($invoicePdf);
-
- $this->invoiceFileManager
- ->expects(self::once())
- ->method('save')
- ->with($invoicePdf);
-
- $this->invoiceRepository
- ->expects(self::once())
- ->method('add')
- ->with($invoice)
- ->willThrowException(new EntityNotFoundException());
-
- $this->invoiceFileManager
- ->expects(self::once())
- ->method('remove')
- ->with($invoicePdf);
-
- ($this->creator)('0000001', $invoiceDateTime);
- }
-
#[Test]
public function it_throws_an_exception_when_invoice_was_already_created_for_given_order(): void
{
diff --git a/tests/Unit/Entity/InvoiceTest.php b/tests/Unit/Entity/InvoiceTest.php
index be210558..8b1193d6 100644
--- a/tests/Unit/Entity/InvoiceTest.php
+++ b/tests/Unit/Entity/InvoiceTest.php
@@ -78,6 +78,7 @@ protected function setUp(): void
$this->channel,
InvoiceInterface::PAYMENT_STATE_COMPLETED,
$this->shopBillingData,
+ 'invoice.pdf',
);
}
@@ -98,6 +99,7 @@ public function it_has_data(): void
{
self::assertSame('7903c83a-4c5e-4bcf-81d8-9dc304c6a353', $this->invoice->id());
self::assertSame('2019/01/000000001', $this->invoice->number());
+ self::assertSame('invoice.pdf', $this->invoice->path());
self::assertSame($this->order, $this->invoice->order());
self::assertSame($this->billingData, $this->invoice->billingData());
self::assertSame('USD', $this->invoice->currencyCode());
diff --git a/tests/Unit/Factory/InvoiceFactoryTest.php b/tests/Unit/Factory/InvoiceFactoryTest.php
index 87ff411c..3fdaa1a7 100644
--- a/tests/Unit/Factory/InvoiceFactoryTest.php
+++ b/tests/Unit/Factory/InvoiceFactoryTest.php
@@ -27,21 +27,26 @@
use Sylius\InvoicingPlugin\Entity\InvoiceShopBillingDataInterface;
use Sylius\InvoicingPlugin\Factory\InvoiceFactory;
use Sylius\InvoicingPlugin\Factory\InvoiceFactoryInterface;
+use Sylius\InvoicingPlugin\Generator\InvoiceFileNameGenerator;
+use Sylius\InvoicingPlugin\Generator\InvoiceFileNameGeneratorInterface;
final class InvoiceFactoryTest extends TestCase
{
private FactoryInterface&MockObject $invoiceShopBillingDataFactory;
+ private InvoiceFileNameGeneratorInterface&MockObject $invoiceFileNameGenerator;
+
private InvoiceFactory $invoiceFactory;
protected function setUp(): void
{
parent::setUp();
$this->invoiceShopBillingDataFactory = $this->createMock(FactoryInterface::class);
-
+ $this->invoiceFileNameGenerator = $this->createMock(InvoiceFileNameGeneratorInterface::class);
$this->invoiceFactory = new InvoiceFactory(
Invoice::class,
$this->invoiceShopBillingDataFactory,
+ $this->invoiceFileNameGenerator,
);
}
@@ -49,6 +54,7 @@ protected function setUp(): void
public function it_implements_invoice_factory_interface(): void
{
self::assertInstanceOf(InvoiceFactoryInterface::class, $this->invoiceFactory);
+
}
#[Test]
@@ -61,6 +67,12 @@ public function it_creates_an_invoice_for_given_data(): void
$date = new \DateTimeImmutable('2019-03-06');
+ $this->invoiceFileNameGenerator
+ ->expects(self::once())
+ ->method('generateForPdf')
+ ->with('2019/03/0000001')
+ ->willReturn('2019_03_0000001.pdf');
+
$result = $this->invoiceFactory->createForData(
'7903c83a-4c5e-4bcf-81d8-9dc304c6a353',
'2019/03/0000001',
@@ -94,6 +106,12 @@ public function it_allows_for_nullable_shop_billing_data(): void
->method('createNew')
->willReturn(new InvoiceShopBillingData());
+ $this->invoiceFileNameGenerator
+ ->expects(self::once())
+ ->method('generateForPdf')
+ ->with('2019/03/0000001')
+ ->willReturn('2019_03_0000001.pdf');
+
$result = $this->invoiceFactory->createForData(
'7903c83a-4c5e-4bcf-81d8-9dc304c6a353',
'2019/03/0000001',
diff --git a/tests/Unit/Generator/InvoiceFileNameGeneratorTest.php b/tests/Unit/Generator/InvoiceFileNameGeneratorTest.php
index c2697c3a..65be4263 100644
--- a/tests/Unit/Generator/InvoiceFileNameGeneratorTest.php
+++ b/tests/Unit/Generator/InvoiceFileNameGeneratorTest.php
@@ -1,21 +1,11 @@
generator = new InvoiceFileNameGenerator();
}
@@ -38,10 +29,27 @@ public function it_implements_invoice_file_name_generator_interface(): void
#[Test]
public function it_generates_invoice_file_name_based_on_its_number(): void
{
- $invoice = $this->createMock(InvoiceInterface::class);
- $invoice->method('number')->willReturn('2020/01/02/000333');
+ $result = $this->generator->generateForPdf('2020/01/02/000333');
+
+ self::assertSame('2020_01_02_000333.pdf', $result);
+ }
+
+ #[Test]
+ public function it_generates_scoped_file_name_when_scope_is_set(): void
+ {
+ $generator = new InvoiceFileNameGenerator('monthly');
+
+ $result = $generator->generateForPdf('2020/01/02/000333');
+
+ self::assertSame('monthly/2020_01_02_000333.pdf', $result);
+ }
+
+ #[Test]
+ public function it_uses_global_scope_when_scope_is_invalid_or_null(): void
+ {
+ $generator = new InvoiceFileNameGenerator('invalid_scope');
- $result = $this->generator->generateForPdf($invoice);
+ $result = $generator->generateForPdf('2020/01/02/000333');
self::assertSame('2020_01_02_000333.pdf', $result);
}
diff --git a/tests/Unit/Generator/InvoicePdfFileGeneratorTest.php b/tests/Unit/Generator/InvoicePdfFileGeneratorTest.php
index 26e830d4..958318ee 100644
--- a/tests/Unit/Generator/InvoicePdfFileGeneratorTest.php
+++ b/tests/Unit/Generator/InvoicePdfFileGeneratorTest.php
@@ -31,8 +31,6 @@ final class InvoicePdfFileGeneratorTest extends TestCase
private FileLocatorInterface&MockObject $fileLocator;
- private InvoiceFileNameGeneratorInterface&MockObject $invoiceFileNameGenerator;
-
private InvoicePdfFileGenerator $generator;
protected function setUp(): void
@@ -40,12 +38,10 @@ protected function setUp(): void
parent::setUp();
$this->twigToPdfGenerator = $this->createMock(TwigToPdfGeneratorInterface::class);
$this->fileLocator = $this->createMock(FileLocatorInterface::class);
- $this->invoiceFileNameGenerator = $this->createMock(InvoiceFileNameGeneratorInterface::class);
$this->generator = new InvoicePdfFileGenerator(
$this->twigToPdfGenerator,
$this->fileLocator,
- $this->invoiceFileNameGenerator,
'invoiceTemplate.html.twig',
'@SyliusInvoicingPlugin/assets/sylius-logo.png',
);
@@ -63,11 +59,10 @@ public function it_creates_invoice_pdf_with_generated_content_and_filename_basin
$invoice = $this->createMock(InvoiceInterface::class);
$channel = $this->createMock(ChannelInterface::class);
- $this->invoiceFileNameGenerator
+ $invoice
->expects(self::once())
- ->method('generateForPdf')
- ->with($invoice)
- ->willReturn('2015_05_00004444.pdf');
+ ->method('path')
+ ->willReturn('invoice.pdf');
$invoice->method('channel')->willReturn($channel);
@@ -85,7 +80,7 @@ public function it_creates_invoice_pdf_with_generated_content_and_filename_basin
$result = $this->generator->generate($invoice);
- $expected = new InvoicePdf('2015_05_00004444.pdf', 'PDF FILE');
+ $expected = new InvoicePdf('invoice.pdf', 'PDF FILE');
self::assertEquals($expected, $result);
}
diff --git a/tests/Unit/Generator/SequentialInvoiceNumberGeneratorTest.php b/tests/Unit/Generator/SequentialInvoiceNumberGeneratorTest.php
index f04a331e..21f7c89d 100644
--- a/tests/Unit/Generator/SequentialInvoiceNumberGeneratorTest.php
+++ b/tests/Unit/Generator/SequentialInvoiceNumberGeneratorTest.php
@@ -21,6 +21,7 @@
use Sylius\Component\Resource\Factory\FactoryInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Sylius\InvoicingPlugin\Entity\InvoiceSequenceInterface;
+use Sylius\InvoicingPlugin\Enum\InvoiceSequenceScopeEnum;
use Sylius\InvoicingPlugin\Generator\InvoiceNumberGenerator;
use Sylius\InvoicingPlugin\Generator\SequentialInvoiceNumberGenerator;
use Symfony\Component\Clock\ClockInterface;
@@ -69,7 +70,10 @@ public function it_generates_invoice_number(): void
$dateTime = new \DateTimeImmutable('now');
$this->clock->method('now')->willReturn($dateTime);
- $this->sequenceRepository->method('findOneBy')->with([])->willReturn($sequence);
+ $this->sequenceRepository
+ ->method('findOneBy')
+ ->with(['year' => null, 'month' => null])
+ ->willReturn($sequence);
$sequence->method('getVersion')->willReturn(1);
$sequence->method('getIndex')->willReturn(0);
@@ -96,7 +100,10 @@ public function it_generates_invoice_number_when_sequence_is_null(): void
$dateTime = new \DateTimeImmutable('now');
$this->clock->method('now')->willReturn($dateTime);
- $this->sequenceRepository->method('findOneBy')->with([])->willReturn(null);
+ $this->sequenceRepository
+ ->method('findOneBy')
+ ->with(['year' => null, 'month' => null])
+ ->willReturn(null);
$this->sequenceFactory->method('createNew')->willReturn($sequence);
@@ -119,6 +126,139 @@ public function it_generates_invoice_number_when_sequence_is_null(): void
$result = $this->generator->generate();
- $this->assertSame($dateTime->format('Y/m') . '/000000001', $result);
+ self::assertSame($dateTime->format('Y/m') . '/000000001', $result);
+ }
+
+ #[Test]
+ public function it_generates_invoice_number_with_monthly_scope(): void
+ {
+ $sequence = $this->createMock(InvoiceSequenceInterface::class);
+
+ $dateTime = new \DateTimeImmutable('2025-10-15');
+ $this->clock->method('now')->willReturn($dateTime);
+
+ $generator = new SequentialInvoiceNumberGenerator(
+ $this->sequenceRepository,
+ $this->sequenceFactory,
+ $this->sequenceManager,
+ $this->clock,
+ 1,
+ 9,
+ 'monthly'
+ );
+
+ $this->sequenceRepository
+ ->method('findOneBy')
+ ->with(['year' => 2025, 'month' => 10, 'type' => InvoiceSequenceScopeEnum::MONTHLY])
+ ->willReturn($sequence);
+
+ $sequence->method('getVersion')->willReturn(1);
+ $sequence->method('getIndex')->willReturn(0);
+
+ $this->sequenceManager
+ ->expects(self::once())
+ ->method('lock')
+ ->with($sequence, LockMode::OPTIMISTIC, 1);
+
+ $sequence
+ ->expects(self::once())
+ ->method('incrementIndex');
+
+ $result = $generator->generate();
+
+ self::assertSame('2025/10/000000001', $result);
+ }
+
+ #[Test]
+ public function it_generates_invoice_number_with_annually_scope(): void
+ {
+ $sequence = $this->createMock(InvoiceSequenceInterface::class);
+
+ $dateTime = new \DateTimeImmutable('2025-11-15');
+ $this->clock->method('now')->willReturn($dateTime);
+
+ $generator = new SequentialInvoiceNumberGenerator(
+ $this->sequenceRepository,
+ $this->sequenceFactory,
+ $this->sequenceManager,
+ $this->clock,
+ 1,
+ 9,
+ 'annually'
+ );
+
+ $this->sequenceRepository
+ ->method('findOneBy')
+ ->with(['year' => 2025, 'type' => InvoiceSequenceScopeEnum::ANNUALLY])
+ ->willReturn($sequence);
+
+ $sequence->method('getVersion')->willReturn(1);
+ $sequence->method('getIndex')->willReturn(0);
+
+ $this->sequenceManager
+ ->expects(self::once())
+ ->method('lock')
+ ->with($sequence, LockMode::OPTIMISTIC, 1);
+
+ $sequence
+ ->expects(self::once())
+ ->method('incrementIndex');
+
+ $result = $generator->generate();
+
+ self::assertSame('2025/11/000000001', $result);
+ }
+
+ #[Test]
+ public function it_generates_invoice_number_when_monthly_sequence_is_null(): void
+ {
+ $sequence = $this->createMock(InvoiceSequenceInterface::class);
+
+ $dateTime = new \DateTimeImmutable('2025-10-15');
+ $this->clock->method('now')->willReturn($dateTime);
+
+ $generator = new SequentialInvoiceNumberGenerator(
+ $this->sequenceRepository,
+ $this->sequenceFactory,
+ $this->sequenceManager,
+ $this->clock,
+ 1,
+ 9,
+ 'monthly'
+ );
+
+ $scope = InvoiceSequenceScopeEnum::MONTHLY;
+
+ $this->sequenceRepository
+ ->expects(self::once())
+ ->method('findOneBy')
+ ->with(['year' => 2025, 'month' => 10, 'type' => $scope])
+ ->willReturn(null);
+
+ $this->sequenceFactory->expects(self::once())->method('createNew')->willReturn($sequence);
+ $sequence->expects(self::once())->method('setYear')->with(2025);
+ $sequence->expects(self::once())->method('setMonth')->with(10);
+ $sequence->expects(self::once())->method('setType')->with($scope);
+
+ $this->sequenceManager
+ ->expects(self::once())
+ ->method('persist')
+ ->with($sequence);
+
+ $sequence->method('getVersion')->willReturn(1);
+ $sequence->method('getIndex')->willReturn(0);
+
+ $this->sequenceManager
+ ->expects(self::once())
+ ->method('lock')
+ ->with($sequence, LockMode::OPTIMISTIC, 1);
+
+ $sequence
+ ->expects(self::once())
+ ->method('incrementIndex');
+
+ $result = $generator->generate();
+
+ self::assertSame('2025/10/000000001', $result);
}
}
diff --git a/tests/Unit/Provider/InvoiceFileProviderTest.php b/tests/Unit/Provider/InvoiceFileProviderTest.php
index 541df8a7..5493ee99 100644
--- a/tests/Unit/Provider/InvoiceFileProviderTest.php
+++ b/tests/Unit/Provider/InvoiceFileProviderTest.php
@@ -29,8 +29,6 @@
final class InvoiceFileProviderTest extends TestCase
{
- private InvoiceFileNameGeneratorInterface&MockObject $invoiceFileNameGenerator;
-
private FilesystemInterface&MockObject $filesystem;
private InvoicePdfFileGeneratorInterface&MockObject $invoicePdfFileGenerator;
@@ -42,13 +40,11 @@ final class InvoiceFileProviderTest extends TestCase
protected function setUp(): void
{
parent::setUp();
- $this->invoiceFileNameGenerator = $this->createMock(InvoiceFileNameGeneratorInterface::class);
$this->filesystem = $this->createMock(FilesystemInterface::class);
$this->invoicePdfFileGenerator = $this->createMock(InvoicePdfFileGeneratorInterface::class);
$this->invoiceFileManager = $this->createMock(InvoiceFileManagerInterface::class);
$this->provider = new InvoiceFileProvider(
- $this->invoiceFileNameGenerator,
$this->filesystem,
$this->invoicePdfFileGenerator,
$this->invoiceFileManager,
@@ -68,10 +64,9 @@ public function it_provides_invoice_file_for_invoice(): void
$invoice = $this->createMock(InvoiceInterface::class);
$invoiceFile = $this->createMock(File::class);
- $this->invoiceFileNameGenerator
+ $invoice
->expects(self::once())
- ->method('generateForPdf')
- ->with($invoice)
+ ->method('path')
->willReturn('invoice.pdf');
$this->filesystem
@@ -98,10 +93,9 @@ public function it_generates_invoice_if_it_does_not_exist_and_provides_it(): voi
{
$invoice = $this->createMock(InvoiceInterface::class);
- $this->invoiceFileNameGenerator
+ $invoice
->expects(self::once())
- ->method('generateForPdf')
- ->with($invoice)
+ ->method('path')
->willReturn('invoice.pdf');
$this->filesystem