DEV Community

Daniel Werner
Daniel Werner

Posted on • Originally published at 42coders.com on

How to create invoices easily with document-templates package

Introduction

There are some common features almost every web application has: registration, login, email sending, pdf generation, admin interface etc. We created our document-templates Laravel package to allow the admins to easily edit their email (and any other) templates using a wysiwyg editor. This package is a generic templating system, which can be used to create any output format like: html, markdown, txt, etc, but the most common usage is to use html template. The package comes with build in pdf rendering engine and it can be used seamlessly with Laravel mailables. The package uses Twig as a template engine.

Why Twig instead of Blade?

This is a logical question, why would we use different template engines? We already have a good build in template engine in Laravel. The basic idea was to allow the users/admins to put some logic into the templates like: ifs, for loops etc. It could be easily solved also with Blade as well, but for security reasons I wanted to allow the developers have control on what functions/logic can be used in the editable part of the template. Twig has a built in configurable sandboxing feature. The sandbox is applied on every part of the templates which can be edited by the users/admins.

How it works?

We create a basic layout of the document, and define which parts of it can be editable by the user. To achieve this we use the block feature of Twig. In the next step we create a document class which uses a trait from the package, and defines the datasources for the document. Every document class represents a document type in your application. For example you should create different classes for invoice, confirmation email, registration email, etc. Render the document on the proper place in your application (for example generate the invoice when the payment finished).

How can I use it?

The architecture of the package consists of 3 main parts:

  1. The core features include the twig engine, datasources and the layout
  2. The rendering module allows the document rendering as html, pdf etc
  3. Admin part comes with routes, controller, vue component, and Ckeditor to quickly build the administration for the documents

You can decide if you want to simply use the core features and use the advantages of twig, and build the other parts yourself. You can also utilize the pdf rendering capabilities, or use the whole package with all the belts and whistles.

The conceptual diagram of the package can be found below:

Let’s build something!

In the next paragraphs we will build an invoice generation system from scratch using this package.

Installation

Install the package with composer:

composer require 42coders/document-templates
Enter fullscreen mode Exit fullscreen mode

Publish the migrations, views, vue components and config:

php artisan vendor:publish --provider="BWF\DocumentTemplates\DocumentTemplatesServiceProvider"
Enter fullscreen mode Exit fullscreen mode

Run the migration

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

Create the base layout

Create the template file: resources/templates/InvoiceTemplate.html.twig with the following content:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Invoice - #123</title>

    <style type="text/css">
        @page {
            margin: 0px;
        }

        body {
            margin: 0px;
        }

        * {
            font-family: Verdana, Arial, sans-serif;
        }

        a {
            color: #fff;
            text-decoration: none;
        }

        table {
            font-size: x-small;
        }

        tfoot tr td {
            font-weight: bold;
            font-size: x-small;
        }

        .invoice table {
            margin: 15px;
        }

        .invoice h3 {
            margin-left: 15px;
        }

        .information {
            background-color: rgb(85, 47, 218);
            color: #FFF;
        }

        .information .logo {
            margin: 5px;
        }

        .information table {
            padding: 10px;
        }
    </style>

</head>
<body>

<div class="information">
    <table width="100%">
        <tr>
            <td align="left" style="width: 40%;">
                {% block customer_details %}
                {% endblock %}
<br/><br/>
<pre>
Date: 2018-01-01
Identifier: #uniquehash
Status: Paid
</pre>


            </td>
            <td align="center">
                <img src="https://42coders.com/wp-content/uploads/elementor/thumbs/logo-o9jp5a9c6jnn02f7yrqrfuzv6pr1lrqw5amfzuffv4.png" alt="Logo" width="64" class="logo"/>
            </td>
            <td align="right" style="width: 40%;">

                <h3>42coders</h3>
                <pre>
                    https://42coders.com

                    Schopenhauerstr. 71
                    80807 München
                </pre>
            </td>
        </tr>

    </table>
</div>


<br/>

<div class="invoice">
    <h3>Invoice specification #123</h3>
        {% block order_items %}
        {% endblock %}
</div>

<div class="information" style="position: absolute; bottom: 0;">
    <table width="100%">
        <tr>
            <td align="left" style="width: 50%;">
            </td>
            <td align="right" style="width: 50%;">
                42coders
            </td>
        </tr>

    </table>
</div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

In the above template we’ve defined 2 blocks: customer_details and order_items. These blocks will be editable in the admin area with Ckeditor.

Create the document class

Let’s create our document class in app/DocumentTemplates/InvoiceTemplate.php :

<?php


namespace App\DocumentTemplates;

use App\User;
use BWF\DocumentTemplates\DocumentTemplates\DocumentTemplate;
use BWF\DocumentTemplates\DocumentTemplates\DocumentTemplateInterface;

class InvoiceTemplate implements DocumentTemplateInterface
{
    use DocumentTemplate;

    protected $testOrders = [
        [
            'id' => '1',
            'description' => 'Package Basic',
            'price' => 10
        ],
        [
            'id' => '2',
            'description' => 'Package Pro',
            'price' => 20
        ],
        [
            'id' => '3',
            'description' => 'Package Advanced',
            'price' => 30
        ],
    ];

    protected function dataSources()
    {
        return [
            $this->dataSource($this->testOrders[0], 'order', true, 'orders'),
            $this->dataSource(new User(), 'user'),
        ];
    }

Enter fullscreen mode Exit fullscreen mode

In this step we create the document class, and define the datasources for the document. For the sake of simplicity we use an array as the order data, and the User model for the customer data. To be able to use the User as a datasource for the invoice, it needs to implement the TemplateDataSourceInterface , so we will use the ModelProvidesTemplateData trait to achieve this:

<?php

namespace App;

use BWF\DocumentTemplates\TemplateDataSources\ModelProvidesTemplateData;
use BWF\DocumentTemplates\TemplateDataSources\TemplateDataSourceInterface;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable implements TemplateDataSourceInterface
{
    use Notifiable, ModelProvidesTemplateData;
Enter fullscreen mode Exit fullscreen mode

Create controller

We create a controller for administration and to test the document editing/rendering:

<?php

namespace App\Http\Controllers;

use App\DocumentTemplates\InvoiceTemplate;
use BWF\DocumentTemplates\DocumentTemplates\DocumentTemplateFactory;
use BWF\DocumentTemplates\DocumentTemplates\DocumentTemplateModelInterface;
use BWF\DocumentTemplates\Http\Controllers\DocumentTemplatesController;
use BWF\DocumentTemplates\TemplateDataSources\TemplateDataSource;
use BWF\DocumentTemplates\TemplateDataSources\TemplateDataSourceFactory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class InvoiceTemplatesController extends DocumentTemplatesController
{
    protected $testOrders = [
        [
            'id' => '1',
            'description' => 'Package Basic',
            'price' => 10
        ],
        [
            'id' => '2',
            'description' => 'Package Pro',
            'price' => 20
        ],
        [
            'id' => '3',
            'description' => 'Package Advanced',
            'price' => 30
        ],
    ];

    /**
     * @return TemplateDataSource[]
     */
    protected function getTestOrders()
    {
        $dataSources = [];

        foreach ($this->testOrders as $item) {
            $dataSources[] = TemplateDataSourceFactory::build($item, 'order');
        }

        return collect($dataSources);
    }

    protected $documentClasses = [
        InvoiceTemplate::class,
    ];

    protected function getTemplateData()
    {
        return [
            'user' => Auth::user(),
            'orders' => $this->getTestOrders(),
        ];
    }

    public function show(DocumentTemplateModelInterface $documentTemplateModel)
    {

        $documentTemplate = DocumentTemplateFactory::build($documentTemplateModel);

        $templateData = $this->getTemplateData();

        foreach ($templateData as $name => $data) {
            $documentTemplate->addTemplateData($data, $name);
        }

        return $documentTemplate->render();

    }
}
Enter fullscreen mode Exit fullscreen mode

Add the package routes to the routes file:

\BWF\DocumentTemplates\DocumentTemplates::routes(InvoiceTemplatesController::class);
Enter fullscreen mode Exit fullscreen mode

Create seeders

Now it is time to fill the database with some test data. Create seeder for editable templates database/seeds/EditableTemplatesTableSeeder.php:

<?php

use Illuminate\Database\Seeder;

class EditableTemplatesTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $documentTemplate = \BWF\DocumentTemplates\DocumentTemplates\DocumentTemplateModel::create([
            'name' => 'Invoice',
            'document_class' => \App\DocumentTemplates\InvoiceTemplate::class,
            'layout' => 'InvoiceTemplate.html.twig'
        ]);

        $documentTemplate->save();


        \BWF\DocumentTemplates\EditableTemplates\EditableTemplate::create([
            'document_template_id' => $documentTemplate->id,
            'name' => 'order_items',
            'content' => '
    <table width="100%">
        <thead>
        <tr>
            <th>Description</th>
            <th>Quantity</th>
            <th>Total</th>
        </tr>
        </thead>
        <tbody>
                {% for order in orders %}
                    <tr>
                        <td>
                            {{order.description}}
                        </td>
                        <td>1</td>
                        <td>
                            €{{order.price}}
                        </td>
                    </tr>
                {% endfor %}
                            </tbody>

        <tfoot>
        <tr>
            <td colspan="1"></td>
            <td align="left">Total</td>
            <td align="left" class="gray">€60,-</td>
        </tr>
        </tfoot>
    </table>
                '
        ])->save();

        \BWF\DocumentTemplates\EditableTemplates\EditableTemplate::create([
            'document_template_id' => $documentTemplate->id,
            'name' => 'customer_details',
            'content' => '
                <h3>{{user.name}}</h3>
                <pre>
Street 15
123456 City
United Kingdom
</pre>
                '
        ])->save();
    }
}
Enter fullscreen mode Exit fullscreen mode

database/seeds/UsersTableSeeder.php:

<?php

use Illuminate\Database\Seeder;

class UsersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        factory(App\User::class, 1)->create(
            [
                'name' => 'Administrator',
                'email' => 'admin@bwf',
                'password' => bcrypt('secret')
            ]
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Add these seeders to the DatabaseSeeder:

public function run()
    {
        $this->call(UsersTableSeeder::class);
        $this->call(EditableTemplatesTableSeeder::class);
    }
Enter fullscreen mode Exit fullscreen mode

Run the seeders:

php artisan db:seed
Enter fullscreen mode Exit fullscreen mode

If you use Laravel 6 you might need to create frontend scaffolding, please check the Laravel documentation here:

composer require laravel/ui --dev 
php artisan ui bootstrap 
php artisan ui vue
Enter fullscreen mode Exit fullscreen mode

Compile the assets:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Create the app layout – optional

If your application don’t have already, create the app layout: resources/views/layouts/app.blade.php:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet" type="text/css">

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
    <div id="app">
        <nav class="navbar navbar-expand-md navbar-light navbar-laravel">
            <div class="container">
                <a class="navbar-brand" href="{{ url('/') }}">
                    {{ config('app.name', 'Laravel') }}
                </a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
                    <span class="navbar-toggler-icon"></span>
                </button>

                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <!-- Left Side Of Navbar -->
                    <ul class="navbar-nav mr-auto">
                        <li class="nav-item">
                            <a class="nav-link" href="{{ route(config('document_templates.base_url') . '.index') }}">{{ __('Document templates') }}</a>
                        </li>
                    </ul>

                    <!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ml-auto">
                    </ul>
                </div>
            </div>
        </nav>

        <main class="py-4">
            @yield('content')
        </main>
    </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Let’s try it

If everything went well when we navigate to the /document-templates/ url, we should see something like this:

The edit form should look like this:

and the generated invoice with with the placeholders filled should look like this:

Closing thoughts

The package has been tested with Laravel version >=5.7. For more information on how to use the package and for more code examples, please check the source code of the demo application here and the package documentation.

The post How to create invoices easily with document-templates package appeared first on 42 Coders.

Top comments (0)