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..3a474b70
--- /dev/null
+++ b/CHANGELOG-3.0.md
@@ -0,0 +1,15 @@
+# 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 the configured sequence scope (`global` – default, `monthly`, `annually`).
+ - Sequence scope is configured via `sylius_invoicing.sequence.scope` semantic configuration, validated at container build time and injected as `InvoiceSequenceScopeEnum`.
+ - `InvoiceSequence`: `year`, `month` and `type` columns are `NOT NULL` (defaults: `0`, `0`, `global`) with a unique index on `(type, year, month)` guarding against duplicate sequences under concurrency.
+ - `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..7d74a6ff
--- /dev/null
+++ b/UPGRADE-3.0.md
@@ -0,0 +1,137 @@
+# 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. Invoice sequence scope is configured via semantic configuration (validated at container build time):
+
+```yaml
+sylius_invoicing:
+ sequence:
+ scope: global # one of: "global" (default), "monthly", "annually"
+```
+
+An invalid value fails at container compilation instead of silently falling back to `global`.
+The `SYLIUS_INVOICING_SEQUENCE_SCOPE` environment variable and the `sylius_invoicing.sequence_scope`
+parameter are no longer supported.
+
+`SequentialInvoiceNumberGenerator` and `InvoiceFileNameGenerator` now receive the scope as an
+`InvoiceSequenceScopeEnum` instance (default: `InvoiceSequenceScopeEnum::GLOBAL`) instead of a nullable string.
+
+The generated file name is prefixed based on the configured scope:
+
+>global (default): no prefix
+>
+>monthly: monthly/…
+>
+>annually: annually/…
+
+7. `InvoiceSequence` scope columns are non-nullable:
+
+`year`, `month` (default `0`) and `type` (default `global`) are `NOT NULL`, and sequences are guarded
+by a unique index on `(type, year, month)` preventing duplicate sequences created by concurrent requests.
+`InvoiceSequenceInterface` getters/setters use non-nullable types accordingly:
+
+```php
+// before:
+public function getType(): ?InvoiceSequenceScopeEnum;
+public function getYear(): ?int;
+public function getMonth(): ?int;
+
+// after:
+public function getType(): InvoiceSequenceScopeEnum;
+public function getYear(): int;
+public function getMonth(): int;
+```
+
+8. `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/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..9731a944 100644
--- a/config/doctrine/InvoiceSequence.orm.xml
+++ b/config/doctrine/InvoiceSequence.orm.xml
@@ -11,6 +11,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 2ed6f149..d60cf3ef 100644
--- a/config/services/generators.xml
+++ b/config/services/generators.xml
@@ -38,16 +38,12 @@
-
+
-
@SyliusInvoicingPlugin/shared/download/pdf.html.twig
%sylius_invoicing.template.logo_file%
@@ -57,9 +53,6 @@
-
-
- %sylius_invoicing.pdf_generator.enabled%
diff --git a/features/managing_invoices/pdf_generation_enabled/saving_invoices_on_server_during_generation.feature b/features/managing_invoices/pdf_generation_enabled/saving_invoices_on_server_during_generation.feature
index 94d8718a..ac5ba921 100644
--- a/features/managing_invoices/pdf_generation_enabled/saving_invoices_on_server_during_generation.feature
+++ b/features/managing_invoices/pdf_generation_enabled/saving_invoices_on_server_during_generation.feature
@@ -14,10 +14,11 @@ Feature: Saving invoices on server during generation
And channel "United States" has shop billing data set as "Ragnarok", "1100110011", "Pacific Coast Hwy", "90806" "Los Angeles", "United States"
@application @pdf_enabled
- Scenario: Having invoice saved on the server after the order is placed
+ Scenario: Having invoice saved on the server once the order is paid
Given there is a customer "lucy@teamlucifer.com" that placed an order "#00000666"
When the customer bought 2 "Angel T-Shirt" products
And the customer "Lucifer Morningstar" addressed it to "Seaside Fwy", "90802" "Los Angeles" in the "United States"
And for the billing address of "Mazikeen Lilim" in the "Pacific Coast Hwy", "90806" "Los Angeles", "United States"
And the customer chose "UPS" shipping method with "Cash on Delivery" payment
+ And the order "#00000666" has just been paid
Then the invoice for order "#00000666" should be saved on the server
diff --git a/src/Creator/InvoiceCreator.php b/src/Creator/InvoiceCreator.php
index 4a8806d7..91c97008 100644
--- a/src/Creator/InvoiceCreator.php
+++ b/src/Creator/InvoiceCreator.php
@@ -13,17 +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;
-use Sylius\PdfGenerationBundle\Core\Filesystem\Manager\PdfFileManagerInterface;
-use Sylius\PdfGenerationBundle\Core\Model\PdfFile;
final class InvoiceCreator implements InvoiceCreatorInterface
{
@@ -31,20 +26,7 @@ public function __construct(
private readonly InvoiceRepositoryInterface $invoiceRepository,
private readonly OrderRepositoryInterface $orderRepository,
private readonly InvoiceGeneratorInterface $invoiceGenerator,
- private readonly InvoicePdfFileGeneratorInterface $invoicePdfFileGenerator,
- private readonly InvoiceFileManagerInterface|PdfFileManagerInterface $invoiceFileManager,
- private readonly bool $hasEnabledPdfFileGenerator = true,
) {
- if ($this->invoiceFileManager instanceof InvoiceFileManagerInterface) {
- trigger_deprecation(
- 'sylius/invoicing-plugin',
- '2.2',
- 'Passing an instance of %s to %s is deprecated and it will not be supported in 3.0, use an instance of %s instead.',
- InvoiceFileManagerInterface::class,
- self::class,
- PdfFileManagerInterface::class,
- );
- }
}
public function __invoke(string $orderNumber, \DateTimeInterface $dateTime): void
@@ -61,29 +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);
-
- if ($this->invoiceFileManager instanceof PdfFileManagerInterface) {
- $pdfFile = new PdfFile($invoicePdf->filename(), $invoicePdf->content());
- $this->invoiceFileManager->save($pdfFile, 'sylius_invoicing');
- } else {
- $this->invoiceFileManager->save($invoicePdf);
- }
-
- try {
- $this->invoiceRepository->add($invoice);
- } catch (ORMException) {
- if ($this->invoiceFileManager instanceof PdfFileManagerInterface) {
- $this->invoiceFileManager->remove($invoicePdf->filename(), 'sylius_invoicing');
- } else {
- $this->invoiceFileManager->remove($invoicePdf);
- }
- }
+ $this->invoiceRepository->add($invoice);
}
}
diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php
index 9389a97c..d43a9e10 100644
--- a/src/DependencyInjection/Configuration.php
+++ b/src/DependencyInjection/Configuration.php
@@ -28,6 +28,7 @@
use Sylius\InvoicingPlugin\Entity\LineItemInterface;
use Sylius\InvoicingPlugin\Entity\TaxItem;
use Sylius\InvoicingPlugin\Entity\TaxItemInterface;
+use Sylius\InvoicingPlugin\Enum\InvoiceSequenceScopeEnum;
use Sylius\InvoicingPlugin\Factory\BillingDataFactory;
use Sylius\InvoicingPlugin\Factory\InvoiceShopBillingDataFactory;
use Sylius\InvoicingPlugin\Factory\LineItemFactory;
@@ -45,10 +46,32 @@ public function getConfigTreeBuilder(): TreeBuilder
$this->addResourcesSection($rootNode);
$this->addPdfGeneratorSection($rootNode);
+ $this->addSequenceSection($rootNode);
return $treeBuilder;
}
+ private function addSequenceSection(ArrayNodeDefinition $node): void
+ {
+ $node
+ ->children()
+ ->arrayNode('sequence')
+ ->addDefaultsIfNotSet()
+ ->children()
+ ->enumNode('scope')
+ ->info('Scope of invoice number sequences: "global", "monthly" or "annually".')
+ ->values(array_map(
+ static fn (InvoiceSequenceScopeEnum $scope): string => $scope->value,
+ InvoiceSequenceScopeEnum::cases(),
+ ))
+ ->defaultValue(InvoiceSequenceScopeEnum::GLOBAL->value)
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ;
+ }
+
private function addResourcesSection(ArrayNodeDefinition $node): void
{
$node
diff --git a/src/DependencyInjection/SyliusInvoicingExtension.php b/src/DependencyInjection/SyliusInvoicingExtension.php
index 46bd0681..c48ab80f 100644
--- a/src/DependencyInjection/SyliusInvoicingExtension.php
+++ b/src/DependencyInjection/SyliusInvoicingExtension.php
@@ -15,6 +15,7 @@
use Sylius\Bundle\CoreBundle\DependencyInjection\PrependDoctrineMigrationsTrait;
use Sylius\Bundle\ResourceBundle\DependencyInjection\Extension\AbstractResourceExtension;
+use Sylius\InvoicingPlugin\Enum\InvoiceSequenceScopeEnum;
use Sylius\InvoicingPlugin\Generator\InvoicingAllowedFilesOptionsProcessor;
use Sylius\PdfGenerationBundle\Core\Filesystem\Manager\PdfFileManagerInterface;
use Sylius\PdfGenerationBundle\Core\Renderer\TwigToPdfRendererInterface;
@@ -41,20 +42,24 @@ public function load(array $configs, ContainerBuilder $container): void
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('sylius_invoicing.pdf_generator.allowed_files', $config['pdf_generator']['allowed_files']);
+ $sequenceScope = InvoiceSequenceScopeEnum::from($config['sequence']['scope']);
+ $container->getDefinition('sylius_invoicing.generator.invoice_number')
+ ->setArgument('$scope', $sequenceScope)
+ ;
+ $container->getDefinition('sylius_invoicing.generator.invoice_file_name')
+ ->setArgument('$scope', $sequenceScope)
+ ;
+
// TODO: Remove in 3.0 — once the legacy PDF generator is dropped, the service should be defined directly with its non-legacy arguments instead of being rewired here.
if (!$config['pdf_generator']['legacy']) {
$container->getDefinition('sylius_invoicing.generator.invoice_pdf_file')
->replaceArgument(0, new Reference(TwigToPdfRendererInterface::class))
;
- $container->getDefinition('sylius_invoicing.creator.invoice')
- ->replaceArgument(4, new Reference(PdfFileManagerInterface::class))
- ;
-
$container->getDefinition('sylius_invoicing.provider.invoice_file')
- ->replaceArgument(1, new Reference(PdfFileManagerInterface::class))
+ ->replaceArgument(0, new Reference(PdfFileManagerInterface::class))
+ ->replaceArgument(2, null)
->replaceArgument(3, null)
- ->replaceArgument(4, null)
;
$this->registerAllowedFilesProcessor($container, $config['pdf_generator']['allowed_files']);
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..6cfc8d9a 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 = InvoiceSequenceScopeEnum::GLOBAL;
+
+ protected int $year = 0;
+
+ protected int $month = 0;
+
/** @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..e88db8b8 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..aac01b80 100644
--- a/src/Generator/InvoiceFileNameGenerator.php
+++ b/src/Generator/InvoiceFileNameGenerator.php
@@ -13,14 +13,21 @@
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 InvoiceSequenceScopeEnum $scope = InvoiceSequenceScopeEnum::GLOBAL,
+ ) {
+ }
+
+ public function generateForPdf(string $invoiceNumber): string
{
- return str_replace('/', '_', $invoice->number()) . self::PDF_FILE_EXTENSION;
+ $prefix = InvoiceSequenceScopeEnum::GLOBAL === $this->scope ? '' : $this->scope->value . '/';
+
+ return $prefix . str_replace('/', '_', $invoiceNumber) . self::PDF_FILE_EXTENSION;
}
}
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 a7be900a..231c5b69 100644
--- a/src/Generator/InvoicePdfFileGenerator.php
+++ b/src/Generator/InvoicePdfFileGenerator.php
@@ -23,7 +23,6 @@ final class InvoicePdfFileGenerator implements InvoicePdfFileGeneratorInterface
public function __construct(
private readonly TwigToPdfGeneratorInterface|TwigToPdfRendererInterface $twigToPdfRenderer,
private readonly FileLocatorInterface $fileLocator,
- private readonly InvoiceFileNameGeneratorInterface $invoiceFileNameGenerator,
private readonly string $template,
private readonly string $invoiceLogoPath,
) {
@@ -41,7 +40,7 @@ public function __construct(
public function generate(InvoiceInterface $invoice): InvoicePdf
{
- $filename = $this->invoiceFileNameGenerator->generateForPdf($invoice);
+ $filename = $invoice->path();
$logoPath = $this->fileLocator->locate($this->invoiceLogoPath);
$templateParams = [
diff --git a/src/Generator/SequentialInvoiceNumberGenerator.php b/src/Generator/SequentialInvoiceNumberGenerator.php
index 89e41349..adbf9cee 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,6 +30,7 @@ public function __construct(
private readonly ClockInterface $clock,
private readonly int $startNumber = 1,
private readonly int $numberLength = 9,
+ private readonly InvoiceSequenceScopeEnum $scope = InvoiceSequenceScopeEnum::GLOBAL,
) {
}
@@ -56,15 +58,33 @@ private function generateNumber(int $index): string
private function getSequence(): InvoiceSequenceInterface
{
- /** @var InvoiceSequenceInterface $sequence */
- $sequence = $this->sequenceRepository->findOneBy([]);
-
- if (null != $sequence) {
+ $now = $this->clock->now();
+
+ $criteria = [
+ 'type' => $this->scope,
+ 'year' => match ($this->scope) {
+ InvoiceSequenceScopeEnum::MONTHLY, InvoiceSequenceScopeEnum::ANNUALLY => (int) $now->format('Y'),
+ InvoiceSequenceScopeEnum::GLOBAL => 0,
+ },
+ 'month' => match ($this->scope) {
+ InvoiceSequenceScopeEnum::MONTHLY => (int) $now->format('m'),
+ InvoiceSequenceScopeEnum::ANNUALLY, InvoiceSequenceScopeEnum::GLOBAL => 0,
+ },
+ ];
+
+ /** @var InvoiceSequenceInterface|null $sequence */
+ $sequence = $this->sequenceRepository->findOneBy($criteria);
+
+ if (null !== $sequence) {
return $sequence;
}
/** @var InvoiceSequenceInterface $sequence */
$sequence = $this->sequenceFactory->createNew();
+ $sequence->setType($this->scope);
+ $sequence->setYear($criteria['year']);
+ $sequence->setMonth($criteria['month']);
+
$this->sequenceManager->persist($sequence);
return $sequence;
diff --git a/src/Migrations/Version20251021074051.php b/src/Migrations/Version20251021074051.php
new file mode 100644
index 00000000..0b7595eb
--- /dev/null
+++ b/src/Migrations/Version20251021074051.php
@@ -0,0 +1,37 @@
+addSql("ALTER TABLE sylius_invoicing_plugin_sequence ADD year INT DEFAULT 0 NOT NULL, ADD month INT DEFAULT 0 NOT NULL, ADD type VARCHAR(255) DEFAULT 'global' NOT NULL");
+ $this->addSql('CREATE UNIQUE INDEX UNIQ_SYLIUS_INVOICING_SEQUENCE_SCOPE ON sylius_invoicing_plugin_sequence (type, year, month)');
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql('DROP INDEX UNIQ_SYLIUS_INVOICING_SEQUENCE_SCOPE ON sylius_invoicing_plugin_sequence');
+ $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 e39b116d..bf95b67f 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;
@@ -27,11 +26,11 @@
final class InvoiceFileProvider implements InvoiceFileProviderInterface
{
public function __construct(
- private readonly InvoiceFileNameGeneratorInterface $invoiceFileNameGenerator,
private readonly FilesystemInterface|PdfFileManagerInterface $filesystem,
private readonly InvoicePdfFileGeneratorInterface $invoicePdfFileGenerator,
private readonly ?InvoiceFileManagerInterface $invoiceFileManager = null,
private readonly ?string $invoicesDirectory = null,
+ private readonly bool $hasEnabledPdfFileGenerator = true,
) {
if ($this->filesystem instanceof FilesystemInterface) {
trigger_deprecation(
@@ -63,7 +62,7 @@ public function __construct(
public function provide(InvoiceInterface $invoice): InvoicePdf
{
- $invoiceFileName = $this->invoiceFileNameGenerator->generateForPdf($invoice);
+ $invoiceFileName = $invoice->path();
if ($this->filesystem instanceof PdfFileManagerInterface) {
return $this->provideUsingPdfBundle($invoiceFileName, $invoice);
@@ -81,6 +80,11 @@ private function provideUsingPdfBundle(string $invoiceFileName, InvoiceInterface
$invoicePdf = new InvoicePdf($pdfFile->filename(), $pdfFile->content());
} else {
$invoicePdf = $this->invoicePdfFileGenerator->generate($invoice);
+
+ if (!$this->hasEnabledPdfFileGenerator) {
+ return $invoicePdf;
+ }
+
$pdfFile = new PdfFile($invoicePdf->filename(), $invoicePdf->content());
$this->filesystem->save($pdfFile, 'sylius_invoicing');
}
@@ -102,7 +106,10 @@ private function provideUsingLegacyGaufrette(string $invoiceFileName, InvoiceInt
$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/Behat/Context/Hook/InvoicesContext.php b/tests/Behat/Context/Hook/InvoicesContext.php
index 08c161d3..45f88278 100644
--- a/tests/Behat/Context/Hook/InvoicesContext.php
+++ b/tests/Behat/Context/Hook/InvoicesContext.php
@@ -1,5 +1,14 @@
invoicesSavePath)) {
+ $this->clearDirectory($this->invoicesSavePath);
+ }
+
+ private function clearDirectory(string $directory): void
+ {
+ if (!is_dir($directory)) {
return;
}
- foreach (scandir($this->invoicesSavePath) as $file) {
- if (is_file($this->invoicesSavePath.'/'.$file)) {
- unlink($this->invoicesSavePath.'/'.$file);
+ foreach (scandir($directory) as $file) {
+ if (in_array($file, ['.', '..'], true)) {
+ continue;
}
+
+ $path = $directory . '/' . $file;
+
+ if (is_dir($path)) {
+ $this->clearDirectory($path);
+ rmdir($path);
+
+ continue;
+ }
+
+ unlink($path);
}
}
}
diff --git a/tests/Behat/Resources/suites/admin/managing_invoices.yml b/tests/Behat/Resources/suites/admin/managing_invoices.yml
index 28aab16f..479d0ef0 100644
--- a/tests/Behat/Resources/suites/admin/managing_invoices.yml
+++ b/tests/Behat/Resources/suites/admin/managing_invoices.yml
@@ -99,6 +99,7 @@ default:
- sylius.behat.context.transform.zone
- sylius.behat.context.transform.shared_storage
+ - Tests\Sylius\InvoicingPlugin\Behat\Context\Order\OrderContext
- Tests\Sylius\InvoicingPlugin\Behat\Context\Application\ManagingInvoicesContext
filters:
diff --git a/tests/DependencyInjection/SyliusInvoicingConfigurationTest.php b/tests/DependencyInjection/SyliusInvoicingConfigurationTest.php
index d4938407..2276f3ec 100644
--- a/tests/DependencyInjection/SyliusInvoicingConfigurationTest.php
+++ b/tests/DependencyInjection/SyliusInvoicingConfigurationTest.php
@@ -27,7 +27,7 @@ public function it_does_not_define_any_allowed_files_by_default(): void
$this->assertProcessedConfigurationEquals(
[[]],
['pdf_generator' => ['allowed_files' => []]],
- 'pdf_generator.allowed_files'
+ 'pdf_generator.allowed_files',
);
}
@@ -37,7 +37,7 @@ public function it_allows_to_define_allowed_files(): void
$this->assertProcessedConfigurationEquals(
[['pdf_generator' => ['allowed_files' => ['swans.png', 'product.png']]]],
['pdf_generator' => ['allowed_files' => ['swans.png', 'product.png']]],
- 'pdf_generator.allowed_files'
+ 'pdf_generator.allowed_files',
);
}
@@ -47,7 +47,7 @@ public function it_has_enabled_pdf_generator_by_default(): void
$this->assertProcessedConfigurationEquals(
[],
['pdf_generator' => ['enabled' => true]],
- 'pdf_generator.enabled'
+ 'pdf_generator.enabled',
);
}
@@ -57,7 +57,7 @@ public function it_allows_to_disable_pdf_generator(): void
$this->assertProcessedConfigurationEquals(
[['pdf_generator' => ['enabled' => false]]],
['pdf_generator' => ['enabled' => false]],
- 'pdf_generator.enabled'
+ 'pdf_generator.enabled',
);
}
diff --git a/tests/DependencyInjection/SyliusInvoicingExtensionTest.php b/tests/DependencyInjection/SyliusInvoicingExtensionTest.php
index 1eee5b3d..dabcdfce 100644
--- a/tests/DependencyInjection/SyliusInvoicingExtensionTest.php
+++ b/tests/DependencyInjection/SyliusInvoicingExtensionTest.php
@@ -23,11 +23,13 @@
use Sylius\InvoicingPlugin\Entity\InvoiceShopBillingData;
use Sylius\InvoicingPlugin\Entity\LineItem;
use Sylius\InvoicingPlugin\Entity\TaxItem;
+use Sylius\InvoicingPlugin\Enum\InvoiceSequenceScopeEnum;
use Sylius\InvoicingPlugin\Generator\InvoicingAllowedFilesOptionsProcessor;
use Sylius\InvoicingPlugin\Generator\TwigToPdfGenerator;
use Sylius\PdfGenerationBundle\Core\Filesystem\Manager\PdfFileManagerInterface;
use Sylius\PdfGenerationBundle\Core\Renderer\TwigToPdfRendererInterface;
use SyliusLabs\DoctrineMigrationsExtraBundle\DependencyInjection\SyliusLabsDoctrineMigrationsExtraExtension;
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
class SyliusInvoicingExtensionTest extends AbstractExtensionTestCase
@@ -42,11 +44,11 @@ public function it_autoconfigures_prepending_doctrine_migration_with_proper_migr
$doctrineMigrationsExtensionConfig = $this->container->getExtensionConfig('doctrine_migrations');
self::assertTrue(isset(
- $doctrineMigrationsExtensionConfig[0]['migrations_paths']['Sylius\InvoicingPlugin\Migrations']
+ $doctrineMigrationsExtensionConfig[0]['migrations_paths']['Sylius\InvoicingPlugin\Migrations'],
));
self::assertSame(
'@SyliusInvoicingPlugin/src/Migrations',
- $doctrineMigrationsExtensionConfig[0]['migrations_paths']['Sylius\InvoicingPlugin\Migrations']
+ $doctrineMigrationsExtensionConfig[0]['migrations_paths']['Sylius\InvoicingPlugin\Migrations'],
);
$syliusLabsDoctrineMigrationsExtraExtensionConfig = $this
@@ -55,11 +57,11 @@ public function it_autoconfigures_prepending_doctrine_migration_with_proper_migr
;
self::assertTrue(isset(
- $syliusLabsDoctrineMigrationsExtraExtensionConfig[0]['migrations']['Sylius\InvoicingPlugin\Migrations']
+ $syliusLabsDoctrineMigrationsExtraExtensionConfig[0]['migrations']['Sylius\InvoicingPlugin\Migrations'],
));
self::assertSame(
'Sylius\Bundle\CoreBundle\Migrations',
- $syliusLabsDoctrineMigrationsExtraExtensionConfig[0]['migrations']['Sylius\InvoicingPlugin\Migrations'][0]
+ $syliusLabsDoctrineMigrationsExtraExtensionConfig[0]['migrations']['Sylius\InvoicingPlugin\Migrations'][0],
);
}
@@ -91,7 +93,7 @@ public function it_loads_allowed_files_for_pdf_generator_configuration(): void
$this->assertContainerBuilderHasParameter(
'sylius_invoicing.pdf_generator.allowed_files',
- ['swans.png', 'product.png']
+ ['swans.png', 'product.png'],
);
}
@@ -100,7 +102,7 @@ public function it_prepends_configuration_with_enabled_pdf_generator(): void
{
$this->container->prependExtensionConfig(
'sylius_invoicing',
- ['pdf_generator' => ['enabled' => false]]
+ ['pdf_generator' => ['enabled' => false]],
);
$this->prepend();
@@ -207,19 +209,6 @@ public function it_replaces_invoice_pdf_file_generator_argument_when_legacy_is_d
);
}
- /** @test */
- public function it_replaces_invoice_creator_file_manager_argument_when_legacy_is_disabled(): void
- {
- $this->load(['pdf_generator' => ['legacy' => false]]);
-
- $definition = $this->container->getDefinition('sylius_invoicing.creator.invoice');
-
- self::assertEquals(
- PdfFileManagerInterface::class,
- (string) $definition->getArgument(4),
- );
- }
-
/** @test */
public function it_replaces_invoice_file_provider_arguments_when_legacy_is_disabled(): void
{
@@ -229,34 +218,66 @@ public function it_replaces_invoice_file_provider_arguments_when_legacy_is_disab
self::assertEquals(
PdfFileManagerInterface::class,
- (string) $definition->getArgument(1),
+ (string) $definition->getArgument(0),
);
+ self::assertNull($definition->getArgument(2));
self::assertNull($definition->getArgument(3));
- self::assertNull($definition->getArgument(4));
}
/** @test */
- public function it_does_not_replace_creator_or_provider_arguments_when_legacy_is_enabled(): void
+ public function it_does_not_replace_provider_arguments_when_legacy_is_enabled(): void
{
$this->load(['pdf_generator' => ['legacy' => true]]);
- $creatorDefinition = $this->container->getDefinition('sylius_invoicing.creator.invoice');
- self::assertEquals(
- 'sylius_invoicing.manager.invoice_file',
- (string) $creatorDefinition->getArgument(4),
- );
-
$providerDefinition = $this->container->getDefinition('sylius_invoicing.provider.invoice_file');
self::assertEquals(
'gaufrette.sylius_invoicing_invoice_filesystem',
- (string) $providerDefinition->getArgument(1),
+ (string) $providerDefinition->getArgument(0),
);
self::assertEquals(
'sylius_invoicing.manager.invoice_file',
- (string) $providerDefinition->getArgument(3),
+ (string) $providerDefinition->getArgument(2),
+ );
+ }
+
+ /** @test */
+ public function it_injects_global_sequence_scope_by_default(): void
+ {
+ $this->load();
+
+ self::assertSame(
+ InvoiceSequenceScopeEnum::GLOBAL,
+ $this->container->getDefinition('sylius_invoicing.generator.invoice_number')->getArgument('$scope'),
+ );
+ self::assertSame(
+ InvoiceSequenceScopeEnum::GLOBAL,
+ $this->container->getDefinition('sylius_invoicing.generator.invoice_file_name')->getArgument('$scope'),
);
}
+ /** @test */
+ public function it_injects_configured_sequence_scope(): void
+ {
+ $this->load(['sequence' => ['scope' => 'monthly']]);
+
+ self::assertSame(
+ InvoiceSequenceScopeEnum::MONTHLY,
+ $this->container->getDefinition('sylius_invoicing.generator.invoice_number')->getArgument('$scope'),
+ );
+ self::assertSame(
+ InvoiceSequenceScopeEnum::MONTHLY,
+ $this->container->getDefinition('sylius_invoicing.generator.invoice_file_name')->getArgument('$scope'),
+ );
+ }
+
+ /** @test */
+ public function it_throws_an_exception_when_sequence_scope_is_invalid(): void
+ {
+ $this->expectException(InvalidConfigurationException::class);
+
+ $this->load(['sequence' => ['scope' => 'montly']]);
+ }
+
/** @test */
public function it_prepends_sylius_pdf_storage_configuration_when_legacy_is_disabled(): void
{
@@ -330,12 +351,12 @@ public function it_prepends_configuration_with_invoice_resource_services(): void
$this->assertContainerBuilderHasParameter(
'sylius_invoicing.model.invoice.class',
- Invoice::class
+ Invoice::class,
);
$this->assertContainerBuilderHasService(
'sylius_invoicing.controller.invoice',
- ResourceController::class
+ ResourceController::class,
);
}
@@ -346,12 +367,12 @@ public function it_prepends_configuration_with_billing_data_resource_services():
$this->assertContainerBuilderHasParameter(
'sylius_invoicing.model.billing_data.class',
- BillingData::class
+ BillingData::class,
);
$this->assertContainerBuilderHasService(
'sylius_invoicing.controller.billing_data',
- ResourceController::class
+ ResourceController::class,
);
}
@@ -362,12 +383,12 @@ public function it_prepends_configuration_with_shop_billing_data_resource_servic
$this->assertContainerBuilderHasParameter(
'sylius_invoicing.model.shop_billing_data.class',
- InvoiceShopBillingData::class
+ InvoiceShopBillingData::class,
);
$this->assertContainerBuilderHasService(
'sylius_invoicing.controller.shop_billing_data',
- ResourceController::class
+ ResourceController::class,
);
}
@@ -378,12 +399,12 @@ public function it_prepends_configuration_with_line_item_resource_services(): vo
$this->assertContainerBuilderHasParameter(
'sylius_invoicing.model.line_item.class',
- LineItem::class
+ LineItem::class,
);
$this->assertContainerBuilderHasService(
'sylius_invoicing.controller.line_item',
- ResourceController::class
+ ResourceController::class,
);
}
@@ -394,12 +415,12 @@ public function it_prepends_configuration_with_tax_item_resource_services(): voi
$this->assertContainerBuilderHasParameter(
'sylius_invoicing.model.tax_item.class',
- TaxItem::class
+ TaxItem::class,
);
$this->assertContainerBuilderHasService(
'sylius_invoicing.controller.tax_item',
- ResourceController::class
+ ResourceController::class,
);
}
@@ -410,12 +431,12 @@ public function it_prepends_configuration_with_invoice_sequence_resource_service
$this->assertContainerBuilderHasParameter(
'sylius_invoicing.model.invoice_sequence.class',
- InvoiceSequence::class
+ InvoiceSequence::class,
);
$this->assertContainerBuilderHasService(
'sylius_invoicing.controller.invoice_sequence',
- ResourceController::class
+ ResourceController::class,
);
}
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/TestApplication/config/config.yaml b/tests/TestApplication/config/config.yaml
index 84b54a36..3e64755f 100644
--- a/tests/TestApplication/config/config.yaml
+++ b/tests/TestApplication/config/config.yaml
@@ -2,6 +2,10 @@ imports:
- { resource: "@SyliusInvoicingPlugin/config/config.yaml" }
- { resource: "services_test.php" }
+knp_snappy:
+ pdf:
+ binary: '%env(WKHTMLTOPDF_PATH)%'
+
twig:
paths:
'%kernel.project_dir%/../../../tests/TestApplication/templates': ~
diff --git a/tests/Unit/Creator/InvoiceCreatorTest.php b/tests/Unit/Creator/InvoiceCreatorTest.php
index 3ba8872e..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,11 +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;
-use Sylius\PdfGenerationBundle\Core\Filesystem\Manager\PdfFileManagerInterface;
-use Sylius\PdfGenerationBundle\Core\Model\PdfFile;
final class InvoiceCreatorTest extends TestCase
{
@@ -39,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
@@ -51,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,
);
}
@@ -74,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
@@ -95,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')
@@ -114,229 +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_creates_invoice_for_order_using_pdf_bundle_file_manager(): void
- {
- $pdfFileManager = $this->createMock(PdfFileManagerInterface::class);
-
- $creator = new InvoiceCreator(
- $this->invoiceRepository,
- $this->orderRepository,
- $this->invoiceGenerator,
- $this->invoicePdfFileGenerator,
- $pdfFileManager,
- );
-
- $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);
-
- $pdfFileManager
- ->expects(self::once())
- ->method('save')
- ->with(
- self::callback(fn (PdfFile $file) => $file->filename() === 'invoice.pdf' && $file->content() === 'CONTENT'),
- 'sylius_invoicing',
- );
-
- $this->invoiceRepository
- ->expects(self::once())
- ->method('add')
- ->with($invoice);
-
- $creator('0000001', $invoiceDateTime);
- }
-
- #[Test]
- public function it_removes_saved_invoice_file_using_pdf_bundle_if_database_update_fails(): void
- {
- $pdfFileManager = $this->createMock(PdfFileManagerInterface::class);
-
- $creator = new InvoiceCreator(
- $this->invoiceRepository,
- $this->orderRepository,
- $this->invoiceGenerator,
- $this->invoicePdfFileGenerator,
- $pdfFileManager,
- );
-
- $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);
-
- $pdfFileManager
- ->expects(self::once())
- ->method('save')
- ->with(
- self::callback(fn (PdfFile $file) => $file->filename() === 'invoice.pdf'),
- 'sylius_invoicing',
- );
-
- $this->invoiceRepository
- ->expects(self::once())
- ->method('add')
- ->with($invoice)
- ->willThrowException(new EntityNotFoundException());
-
- $pdfFileManager
- ->expects(self::once())
- ->method('remove')
- ->with('invoice.pdf', 'sylius_invoicing');
-
- $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..8b72adf5 100644
--- a/tests/Unit/Factory/InvoiceFactoryTest.php
+++ b/tests/Unit/Factory/InvoiceFactoryTest.php
@@ -27,21 +27,25 @@
use Sylius\InvoicingPlugin\Entity\InvoiceShopBillingDataInterface;
use Sylius\InvoicingPlugin\Factory\InvoiceFactory;
use Sylius\InvoicingPlugin\Factory\InvoiceFactoryInterface;
+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,
);
}
@@ -61,6 +65,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 +104,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..7d68b84e 100644
--- a/tests/Unit/Generator/InvoiceFileNameGeneratorTest.php
+++ b/tests/Unit/Generator/InvoiceFileNameGeneratorTest.php
@@ -15,7 +15,7 @@
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
-use Sylius\InvoicingPlugin\Entity\InvoiceInterface;
+use Sylius\InvoicingPlugin\Enum\InvoiceSequenceScopeEnum;
use Sylius\InvoicingPlugin\Generator\InvoiceFileNameGenerator;
use Sylius\InvoicingPlugin\Generator\InvoiceFileNameGeneratorInterface;
@@ -26,6 +26,7 @@ final class InvoiceFileNameGeneratorTest extends TestCase
protected function setUp(): void
{
parent::setUp();
+
$this->generator = new InvoiceFileNameGenerator();
}
@@ -38,11 +39,28 @@ 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($invoice);
+ $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_monthly_scope_is_set(): void
+ {
+ $generator = new InvoiceFileNameGenerator(InvoiceSequenceScopeEnum::MONTHLY);
+
+ $result = $generator->generateForPdf('2020/01/02/000333');
+
+ self::assertSame('monthly/2020_01_02_000333.pdf', $result);
+ }
+
+ #[Test]
+ public function it_generates_scoped_file_name_when_annually_scope_is_set(): void
+ {
+ $generator = new InvoiceFileNameGenerator(InvoiceSequenceScopeEnum::ANNUALLY);
+
+ $result = $generator->generateForPdf('2020/01/02/000333');
+
+ self::assertSame('annually/2020_01_02_000333.pdf', $result);
+ }
}
diff --git a/tests/Unit/Generator/InvoicePdfFileGeneratorTest.php b/tests/Unit/Generator/InvoicePdfFileGeneratorTest.php
index 5fe0e9ec..7f953415 100644
--- a/tests/Unit/Generator/InvoicePdfFileGeneratorTest.php
+++ b/tests/Unit/Generator/InvoicePdfFileGeneratorTest.php
@@ -18,7 +18,6 @@
use PHPUnit\Framework\TestCase;
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\InvoicingPlugin\Entity\InvoiceInterface;
-use Sylius\InvoicingPlugin\Generator\InvoiceFileNameGeneratorInterface;
use Sylius\InvoicingPlugin\Generator\InvoicePdfFileGenerator;
use Sylius\InvoicingPlugin\Generator\InvoicePdfFileGeneratorInterface;
use Sylius\InvoicingPlugin\Generator\TwigToPdfGeneratorInterface;
@@ -30,15 +29,12 @@ final class InvoicePdfFileGeneratorTest extends TestCase
{
private FileLocatorInterface&MockObject $fileLocator;
- private InvoiceFileNameGeneratorInterface&MockObject $invoiceFileNameGenerator;
-
#[Test]
public function it_implements_invoice_pdf_file_generator_interface(): void
{
$generator = new InvoicePdfFileGenerator(
$this->createMock(TwigToPdfGeneratorInterface::class),
$this->createMock(FileLocatorInterface::class),
- $this->createMock(InvoiceFileNameGeneratorInterface::class),
'invoiceTemplate.html.twig',
'@SyliusInvoicingPlugin/assets/sylius-logo.png',
);
@@ -55,7 +51,6 @@ public function it_creates_invoice_pdf_using_legacy_twig_to_pdf_generator(): voi
$generator = new InvoicePdfFileGenerator(
$twigToPdfGenerator,
$this->fileLocator,
- $this->invoiceFileNameGenerator,
'invoiceTemplate.html.twig',
'@SyliusInvoicingPlugin/assets/sylius-logo.png',
);
@@ -66,11 +61,10 @@ public function it_creates_invoice_pdf_using_legacy_twig_to_pdf_generator(): voi
$logoPath = __DIR__ . '/../../../assets/sylius-logo.png';
$logoDataUri = 'data:image/png;base64,' . base64_encode((string) file_get_contents($logoPath));
- $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);
@@ -93,7 +87,7 @@ public function it_creates_invoice_pdf_using_legacy_twig_to_pdf_generator(): voi
$result = $generator->generate($invoice);
- $expected = new InvoicePdf('2015_05_00004444.pdf', 'PDF FILE');
+ $expected = new InvoicePdf('invoice.pdf', 'PDF FILE');
self::assertEquals($expected, $result);
}
@@ -107,7 +101,6 @@ public function it_creates_invoice_pdf_using_pdf_bundle_twig_to_pdf_renderer():
$generator = new InvoicePdfFileGenerator(
$twigToPdfRenderer,
$this->fileLocator,
- $this->invoiceFileNameGenerator,
'invoiceTemplate.html.twig',
'@SyliusInvoicingPlugin/assets/sylius-logo.png',
);
@@ -118,10 +111,9 @@ public function it_creates_invoice_pdf_using_pdf_bundle_twig_to_pdf_renderer():
$logoPath = __DIR__ . '/../../../assets/sylius-logo.png';
$logoDataUri = 'data:image/png;base64,' . base64_encode((string) file_get_contents($logoPath));
- $this->invoiceFileNameGenerator
+ $invoice
->expects(self::once())
- ->method('generateForPdf')
- ->with($invoice)
+ ->method('path')
->willReturn('2015_05_00004444.pdf');
$invoice->method('channel')->willReturn($channel);
@@ -157,6 +149,5 @@ public function it_creates_invoice_pdf_using_pdf_bundle_twig_to_pdf_renderer():
private function setUpCommonDependencies(): void
{
$this->fileLocator = $this->createMock(FileLocatorInterface::class);
- $this->invoiceFileNameGenerator = $this->createMock(InvoiceFileNameGeneratorInterface::class);
}
}
diff --git a/tests/Unit/Generator/SequentialInvoiceNumberGeneratorTest.php b/tests/Unit/Generator/SequentialInvoiceNumberGeneratorTest.php
index f04a331e..4cb27aac 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(['type' => InvoiceSequenceScopeEnum::GLOBAL, 'year' => 0, 'month' => 0])
+ ->willReturn($sequence);
$sequence->method('getVersion')->willReturn(1);
$sequence->method('getIndex')->willReturn(0);
@@ -96,9 +100,15 @@ 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(['type' => InvoiceSequenceScopeEnum::GLOBAL, 'year' => 0, 'month' => 0])
+ ->willReturn(null);
$this->sequenceFactory->method('createNew')->willReturn($sequence);
+ $sequence->expects(self::once())->method('setType')->with(InvoiceSequenceScopeEnum::GLOBAL);
+ $sequence->expects(self::once())->method('setYear')->with(0);
+ $sequence->expects(self::once())->method('setMonth')->with(0);
$this->sequenceManager
->expects(self::once())
@@ -119,6 +129,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,
+ InvoiceSequenceScopeEnum::MONTHLY,
+ );
+
+ $this->sequenceRepository
+ ->method('findOneBy')
+ ->with(['type' => InvoiceSequenceScopeEnum::MONTHLY, 'year' => 2025, 'month' => 10])
+ ->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,
+ InvoiceSequenceScopeEnum::ANNUALLY,
+ );
+
+ $this->sequenceRepository
+ ->method('findOneBy')
+ ->with(['type' => InvoiceSequenceScopeEnum::ANNUALLY, 'year' => 2025, 'month' => 0])
+ ->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,
+ InvoiceSequenceScopeEnum::MONTHLY,
+ );
+
+ $scope = InvoiceSequenceScopeEnum::MONTHLY;
+
+ $this->sequenceRepository
+ ->expects(self::once())
+ ->method('findOneBy')
+ ->with(['type' => $scope, 'year' => 2025, 'month' => 10])
+ ->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 1709451f..7209136d 100644
--- a/tests/Unit/Provider/InvoiceFileProviderTest.php
+++ b/tests/Unit/Provider/InvoiceFileProviderTest.php
@@ -20,7 +20,6 @@
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
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;
@@ -31,8 +30,6 @@
final class InvoiceFileProviderTest extends TestCase
{
- private InvoiceFileNameGeneratorInterface&MockObject $invoiceFileNameGenerator;
-
private FilesystemInterface&MockObject $filesystem;
private InvoicePdfFileGeneratorInterface&MockObject $invoicePdfFileGenerator;
@@ -44,13 +41,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,
@@ -70,10 +65,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
@@ -100,10 +94,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
@@ -139,7 +132,6 @@ public function it_provides_invoice_file_using_pdf_bundle_file_manager(): void
{
$pdfFileManager = $this->createMock(PdfFileManagerInterface::class);
$provider = new InvoiceFileProvider(
- $this->invoiceFileNameGenerator,
$pdfFileManager,
$this->invoicePdfFileGenerator,
);
@@ -147,10 +139,9 @@ public function it_provides_invoice_file_using_pdf_bundle_file_manager(): void
$invoice = $this->createMock(InvoiceInterface::class);
$pdfFile = new PdfFile('invoice.pdf', 'CONTENT');
- $this->invoiceFileNameGenerator
+ $invoice
->expects(self::once())
- ->method('generateForPdf')
- ->with($invoice)
+ ->method('path')
->willReturn('invoice.pdf');
$pdfFileManager
@@ -184,17 +175,15 @@ public function it_generates_and_saves_invoice_using_pdf_bundle_if_it_does_not_e
{
$pdfFileManager = $this->createMock(PdfFileManagerInterface::class);
$provider = new InvoiceFileProvider(
- $this->invoiceFileNameGenerator,
$pdfFileManager,
$this->invoicePdfFileGenerator,
);
$invoice = $this->createMock(InvoiceInterface::class);
- $this->invoiceFileNameGenerator
+ $invoice
->expects(self::once())
- ->method('generateForPdf')
- ->with($invoice)
+ ->method('path')
->willReturn('invoice.pdf');
$pdfFileManager