Merge branch '1.9' into 1.10

* 1.9:
  Update docs/cookbook/invoices/generating-invoice-after-payment.rst
  Fixes to lists
  [DOCS] Generating invoice after payment
  Fix to ordered list
  [API] Fix formating for list in invoice cookbook
  [DOCS] How to modify invoices'
This commit is contained in:
Grzegorz Sadowski 2021-08-17 10:24:59 +02:00
commit 8e0898ee54
No known key found for this signature in database
GPG key ID: EF87FF4E6E3BD364
9 changed files with 388 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -84,6 +84,17 @@ Inventory
.. include:: /cookbook/inventory/map.rst.inc
Invoices
--------
.. toctree::
:hidden:
invoices/custom-invoice
invoices/generating-invoice-after-payment
.. include:: /cookbook/invoices/map.rst.inc
Shipping methods
----------------

View file

@ -0,0 +1,205 @@
How to customize the order invoice?
===================================
.. note::
This cookbook describes customization of a feature available only with `Sylius/InvoicingPlugin <https://github.com/Sylius/InvoicingPlugin/>`_ installed.
The invoicing plugin lets you generate invoice on admin panel and download PDF with it. This plugin also sends an email with this invoice
as well as let the administrator to delegate an email resend.
Why would you customize the invoice?
------------------------------------
Invoicing Plugin provides a generic solution for generating invoices for orders, it is enough for a basic invoicing functionality
but many shops need this feature customized for its needs.
For example, one may need to change the look of it, or add some more data.
Getting started
---------------
Before you start make sure that you have:
#. `Sylius/InvoicingPlugin <https://github.com/Sylius/InvoicingPlugin/>`_ installed.
#. `Wkhtmltopdf <https://wkhtmltopdf.org/>`_ package installed, because most of pdf generation is done with it.
How to customize the invoice appearance?
----------------------------------------
There might be need to change how the invoices look like, f.e. shop is using its own logo, there might be some colours changed
on the tables, or maybe the order of fields might be changed.
First let's prepare the HTML's so we can modify them. This command will copy only the PDF template:
.. code-block:: bash
mkdir templates/bundles/SyliusInvoicingPlugin/Invoice/Download
cp vendor/sylius/invoicing-plugin/src/Resources/views/Invoice/Download/pdf.html.twig templates/bundles/SyliusInvoicingPlugin/Download
But you can also copy all of the templates with:
.. code-block:: bash
mkdir templates/bundles/SyliusInvoicingPlugin
cp -r vendor/sylius/invoicing-plugin/src/Resources/views/Invoice templates/bundles/SyliusInvoicingPlugin/
In directory ``templates/bundles/SyliusInvoicingPlugin/Invoice`` you can find now few files.
Let's modify the generated PDF. In this case we will modify the file from ``Download/pdf.html.twig``.
This is how the default PDF looks like (don't worry this is not a real address of Myrna, or is it?):
.. image:: ../../_images/cookbook/custom-invoice/pdf_before.png
Now with some magic of HTML and CSS we can modify this template, as an example we can change the color of background to ``red`` by changing
.. code-block:: html
<!--...-->
<div class="invoice-box" style="background-color: red">
<!--...-->
and after this change we are graced with this masterpiece:
.. image:: ../../_images/cookbook/custom-invoice/pdf_after.png
.. warning::
Every PDF that you generate is stored and then extracted so it won't be created again. If you want to see the changes
go to ``private/invoices`` and remove the generated PDF. You should see the changes of your file when you generate it again.
.. note::
You can also modify the view on administrator page by changing code inside ``show.html.twig`` and related templates
.. note::
You can learn more about customizing templates at :doc:`Customization Guide </customization/index>`
How to add additional fields to invoice?
----------------------------------------
Let's say that you need (or not) some more fields. In this example we will add the customer phone number.
Because we are basing upon the existing field, there should be no problem adding it to document - just place a line into
``Download/pdf.html.twig`` file. The ``Phone Number`` field is quite nested so you need to add ``invoice.order.customer.phoneNumber``
to retrieve it:
.. code-block:: html
<!--...-->
{{ invoice.billingData.city }}<br/>
{{ invoice.order.customer.phoneNumber }}<br/>
{{ invoice.billingData.countryCode}}
<!--...-->
And as a result we can see that phone number has been added just after the city:
.. image:: ../../_images/cookbook/custom-invoice/pdf_phone.png
.. note::
You can also create some validation (for example if customer has no phone number) so the field won't be shown.
If you want to learn more about twig - visit `twig <https://twig.symfony.com/>`_.
How to change the appearance of invoice tables?
-----------------------------------------------
By default on lower right corner of invoice we are displaying ``total`` of ordered items and shipment.
Lets create now a new row where we will show ``Products total`` where only price for products will be shown.
First let's add the new table row between other ``totals`` in ``pdf.html.twig``
.. code-block:: html
<!--...-->
<tr class="totals">
<!--tr body-->
</tr>
<tr class="totals">
<td colspan="5"></td>
<td colspan="2" >{{ 'sylius_invoicing_plugin.ui.products_total'|trans([], 'messages', invoice.localeCode) }}:</td>
<td>{{ '%0.2f'|format(invoice.order.itemsTotal/100) }}</td>
<td>{{ invoice.currencyCode }}</td>
</tr>
<tr class="totals bold">
<!--...-->
And now add the translation by creating file ``translations/messages.en.yaml`` and adding:
.. code-block:: yaml
sylius_invoicing_plugin:
ui:
products_total: 'Products total'
after this changes your PDF's total table should look like this:
.. image:: ../../_images/cookbook/custom-invoice/pdf_total.png
How to extend Invoice with custom logic?
----------------------------------------
With default behavior and some simple customization it should be quite simple to achieve the Invoice you are looking for.
But life is not so straightforward as we all would like, and you are in need to create some custom logic for your needs.
Scary process isn't it? Well not exactly, let's create some custom logic for your invoice in this step.
First we need a class with our logic that will extend current Invoice:
.. code-block:: php
<?php
declare(strict_types=1);
namespace App\Entity\Invoice;
use Doctrine\ORM\Mapping as ORM;
use Sylius\InvoicingPlugin\Entity\Invoice as BaseInvoice;
/**
* @ORM\Entity
* @ORM\Table(name="sylius_invoicing_plugin_invoice")
*/
class Invoice extends BaseInvoice implements InvoiceInterface
{
public function customFunction(): mixed
{
/** your custom logic */
}
}
And if there is a need you can also create an interface that will extend the base one:
.. code-block:: php
<?php
declare(strict_types=1);
namespace App\Entity\Invoice;
use Sylius\InvoicingPlugin\Entity\InvoiceInterface as BaseInvoiceInterface;
interface InvoiceInterface extends BaseInvoiceInterface
{
public function customFunction(): mixed;
}
Now let's add those classes to the configuration:
.. code-block:: yaml
# config/packages/_sylius.yaml
sylius_invoicing:
resources:
invoice:
classes:
model: App\Entity\Invoice\Invoice
interface: App\Entity\Invoice\InvoiceInterface
.. note::
Don't forget to update your database if you are changing/adding fields.
Now you can show a new invoice table on PDF with some changes just like in chapters before.

View file

@ -0,0 +1,170 @@
How to have the Invoice generated after the payment is paid?
============================================================
.. note::
This cookbook describes customization of a feature available only with `Sylius/InvoicingPlugin <https://github.com/Sylius/InvoicingPlugin/>`_ installed.
The invoicing plugin lets you generate and download PDF invoices regarding orders. In its default behavior the invoice
is generated right after the customer creates and places the order.
In this cookbook, we will describe how to change this behavior, so the invoice will be created right after the order would be paid.
Why would you customize the invoice generation?
-----------------------------------------------
In the default case the order items should not be changed after completing order. But let's say that your shop is customized with some logic
which is not out of the box. Maybe one of these changes will let you change the order after it is placed?
In this case, it would be better to have an invoice generated after a particular step (in the case of this cookbook - after the order is paid).
Getting started
---------------
Before you start, make sure that you have:
#. `Sylius/InvoicingPlugin <https://github.com/Sylius/InvoicingPlugin/>`_ installed.
#. `Wkhtmltopdf <https://wkhtmltopdf.org/>`_ package installed, because most of pdf generation is done with it.
How to customize the invoice generation?
----------------------------------------
The concept is quite straightforward and it is based on Symfony events and event listeners.
We need to create 3 classes and declare them in config files.
Let's start first with EventListeners that will override the default ones:
**1.** The ``OrderPaymentPaidListener`` will create an invoice and check if it does not exist:
.. code-block:: php
<?php
declare(strict_types=1);
namespace App\EventListener;
use Sylius\InvoicingPlugin\Command\SendInvoiceEmail;
use Sylius\InvoicingPlugin\Creator\InvoiceCreatorInterface;
use Sylius\InvoicingPlugin\Event\OrderPaymentPaid;
use Sylius\InvoicingPlugin\Exception\InvoiceAlreadyGenerated;
use Symfony\Component\Messenger\MessageBusInterface;
final class OrderPaymentPaidListener
{
/** @var InvoiceCreatorInterface */
private $invoiceCreator;
/** @var MessageBusInterface */
private $commandBus;
public function __construct(InvoiceCreatorInterface $invoiceCreator, MessageBusInterface $commandBus)
{
$this->invoiceCreator = $invoiceCreator;
$this->commandBus = $commandBus;
}
public function __invoke(OrderPaymentPaid $event): void
{
try {
$this->invoiceCreator->__invoke($event->orderNumber(), $event->date());
} catch (InvoiceAlreadyGenerated $exception) {
}
$this->commandBus->dispatch(new SendInvoiceEmail($event->orderNumber()));
}
}
**2.** The ``NoInvoiceOnOrderPlacedListener`` will not do anything (what a lazy boy), but as we are changing the behavior, it still has very important role:
.. code-block:: php
<?php
declare(strict_types=1);
namespace App\EventListener;
use Sylius\InvoicingPlugin\Event\OrderPlaced;
final class NoInvoiceOnOrderPlacedListener
{
public function __invoke(OrderPlaced $event): void
{
// intentionally left blank
}
}
**3.** Last but not least ``OrderPaymentPaidProducer`` which will dispatch an event at a correct moment:
.. code-block:: php
<?php
declare(strict_types=1);
namespace App\Producer;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\PaymentInterface;
use Sylius\InvoicingPlugin\DateTimeProvider;
use Sylius\InvoicingPlugin\Event\OrderPaymentPaid;
use Symfony\Component\Messenger\MessageBusInterface;
final class OrderPaymentPaidProducer
{
/** @var MessageBusInterface */
private $eventBus;
/** @var DateTimeProvider */
private $dateTimeProvider;
public function __construct(
MessageBusInterface $eventBus,
DateTimeProvider $dateTimeProvider
) {
$this->eventBus = $eventBus;
$this->dateTimeProvider = $dateTimeProvider;
}
public function __invoke(PaymentInterface $payment): void
{
/** @var OrderInterface|null $order */
$order = $payment->getOrder();
if ($order === null) {
return;
}
/** @var string $number */
$number = $order->getNumber();
$this->eventBus->dispatch(new OrderPaymentPaid($number, $this->dateTimeProvider->__invoke()));
}
}
**4.** Last thing that we need to do is to register new services in a container:
.. code-block:: yaml
# config/services.yaml
services:
sylius_invoicing_plugin.listener.order_payment_paid:
class: App\EventListener\OrderPaymentPaidListener
arguments:
- '@sylius_invoicing_plugin.creator.invoice'
- '@sylius.command_bus'
tags:
- { name: messenger.message_handler }
sylius_invoicing_plugin.event_listener.order_placed:
class: App\EventListener\NoInvoiceOnOrderPlacedListener
tags:
- { name: messenger.message_handler }
sylius_invoicing_plugin.event_producer.order_payment_paid:
class: App\Producer\OrderPaymentPaidProducer
arguments:
- '@sylius.event_bus'
- '@sylius_invoicing_plugin.date_time_provider'
public: true
After these changes, the invoice will be generated after the order is paid, not just after it is placed.
.. image:: ../../_images/cookbook/generating-invoice-after-payment/before_payment.png

View file

@ -0,0 +1,2 @@
* :doc:`/cookbook/invoices/custom-invoice`
* :doc:`/cookbook/invoices/generating-invoice-after-payment`