Implementing an Invoice Numbering System with PHP using Laravel 6
Tags: howto, invoicing, php, laravel
Published: 2020-05-10 10:32:13

---
title: Implementing an Invoice Numbering System with PHP using Laravel 6
published: true
description: Implementing an Invoice Numbering System with PHP using Laravel 6
tags: howto, invoicing, php, laravel
---

In my last post in [How I believe Xero pulled off implementing their Invoice Numbering System](https://dev.to/mirmayne/how-i-believe-xero-pulled-off-implementing-their-invoice-numbering-system-481k), I showed you how I created a customisable and incremental invoice numbering system using Ruby on Rails. This post will demonstrate how it's done using PHP (Laravel 6).

To recap, we have two sections that make up an invoice number:

1. The Prefix
2. A numeric value

This approach is customisable as a user can change the prefix to match their business needs and start the sequential value at any number ie 001, 0001 or 1, 
max ten characters. 001 is widely used.

In your terminal, run:

```bash
php artisan make:migration create_invoice_settings_table
```

Now, edit the migration file to match below:

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateInvoiceSettingsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('invoice_settings', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->bigInteger('organisation_id')->unsigned();
            $table->string('prefix')->default('INV-');
            $table->string('number_sequence')->default('001');

            // You may have other fields...

            // The association
            $table->foreign('organisation_id')->references('id')->on('organisations');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('invoice_settings');
    }
}
```
Run your migration to create the table with default values.

A new InvoiceSettings should be created when a organisation is added to your system, that way we have the default data assigned to an organisation or create a Seeder to create this recored for the current organisation.

Our InvoiceSettings controller would look as follows:

```php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
// use App\InvoiceSettings;

class InvoiceSettingsController extends Controller
{
    // A constructor will not be used in this example.

    /**
     * Gets the defaults values.
     *
     * @param  int  $organisationId
     * @return Response
     */
    public function show($organisationId)
    {
        //
    } 
    
    /**
     * Update the invoice settings for the given organisation.
     *
     * @param  request  $request
     * @return Response
     */
    public function update(Request $request)
    {
        //
    }
}
```
Go ahead and create an invoice settings model with migration etc then uncomment InvoiceSettings alias in the above when finished.

## Validation
Most Laravel developer place their validations in the controller, we won't be doing that, you should not be doing that. Our controllers should be easily testable, slim and saves item to the database/retrieves items from database etc.

If you have created a route for `localhost:4000/organisations/1/invoice-settings`, you should see a form with two fields, populating the default invoice settings values. Go ahead and implement that yourself by using an api route or using the blade template.

Once you've done that, we'll create a [Laravel Form Request](https://laravel.com/docs/6.x/validation#form-request-validation)

```bash
# Spell this how you see fit
php artisan make:request ShouldBeSequentializeAndUnique
```

```php
[...]
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;

[...]

/**
 * Get the custom validation rules that apply to the request.
 *
 * @return array
 */
public function rules()
{
    return $this->customRule();
}

/**
 * Validate sequence number is numeric and not already in database.
 * 
 * @return array|exception
 */
private function customRule()
{
    $prefix = $this->prefix;
    $numberSequence = $this->number_sequence;

    // In most cases, you should have this already in your controller,
    // but we need it here also.
    $organisationId = request()->organisation_id;
    $invoiceReferenceNumber = strtolower($prefix . $numberSequence); // we now/should have something like inv-001
    
    $rules = [
      'prefix'          => 'required|string|max:10',
      'organisation_id' => 'required|integer',
         
         'number_sequence' => [
           'required', 'numeric', 'digits_between:1,10',
    
            Rule::unique('some-table')->where(function ($query) use ($organisationId, $invoiceReferenceNumber) {
              $q = $query
                  ->whereRaw('LOWER(number) LIKE ?', '%' . $invoiceReferenceNumber . '%')
                  ->where('organisation_id', $organisationId)->first();
              if ($q) {
                  throw ValidationException::withMessages(['number_sequence' => 'Sequence number already in use']);
              }
              return $q;
            })
          ],
    ];

    return $rules;
}
```

WOW! What's going here? For starters, `prefix` should be a string with a max length of 10 characters. `number_sequence` should be numeric with a max length of 10 characters. Noticed we have not used `max:10` as the input field type should be `number`. When it's set to `type=number`, `max:10` sums up the value; instead, we use `digits_between` 1 and 10.

What is `some-table`? For your homework, create another migration called `invoices` with a column called `number` of type string (required), then replace `some-table` with `invoices`. Obviously you'll need other columns etc.

Our `Rule` searches for invoices with the same `$invoiceReferenceNumber` and if found then `$numberSequence` is already in use as we combined prefix + numberSequence to make an invoice reference number.

Generating an invoice with number `iNv-001` will throw an error*. Generating an invoice with `ii-001` will not throw an error* as the user only wants to change the `prefix`.

This error* only refers to updating the invoice settings, not when creating an invoice. For your homework, create a validation to ensure `number` is unique for all invoices but only per user's organisation.

Our controller should now look like:

```php

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Http\Requests\ShouldBeSequentializeAndUnique;
use App\InvoiceSettings;

class InvoiceSettingsController extends Controller
{
    // A constructor will not be used in this example.


    /**
     * Update the invoice settings for the given organisation.
     *
     * @param  ShouldBeSequentializeAndUnique  $request
     * @return Json
     */
    public function update(ShouldBeSequentializeAndUnique $request)
    {
        // Run the validation.
        $validated = $request->validated();
       
        // $organisationId comes from session or the param.

       // Everything should be ok from here.
       InvoiceSettings::where('organisation_id', $organisationId)
           ->update([
                 'prefix'          => $request->prefix,
                 'number_sequence' => $request->number_sequence
             ]);

      return response()->json($validated, 200);
    }
}

```

***EDIT per comment***

```php
public function update(ShouldBeSequentializeAndUnique $request)
    {     
       // $organisationId comes from session or the param.

       // Everything should be ok from here.
       $settings = InvoiceSettings::updateOrCreate(
         ['organisation_id'   => $request->organisation_id],
         [
            'prefix'          => $request->prefix,
            'number_sequence' => $request->number_sequence
         ]);

      return response()->json($settings, 200);
    }
```

There are many ways write code. The above maybe incorrect for some but, as we all say, "it works for me" 😁. I'm open for improvements as I haven't been professionally using Laravel for years.

In another post, I'll demonstrate how to increment the `number_sequence` on every invoice creation.