In this lesson, let's examine the database structure for invoice numbers. For example, an invoice number can start with a series or prefix and then a number leading with zeros, like INT-001
.
Another example could be a year and then 001 after the dash, like 2024-001
. Or some word and then the number, like Mars-108
.
So, how do you store that in the database and make that incremental?
Imagine a simplified Invoice
table where we save the invoice number in the invoice_number
column.
Schema::create('invoices', function (Blueprint $table) { $table->id(); $table->foreignId('user_id'); $table->unsignedBigInteger('invoice_number'); $table->integer('paid_total'); $table->timestamps();});
How do you set the invoice_number
column to be incremented when the invoice is created? Probably the easiest way would be to use an Eloquent mutator.
app/Models/Invoice.php:
use Illuminate\Database\Eloquent\Casts\Attribute; class Invoice extends Model{ protected $fillable = [ 'user_id', 'invoice_number', 'paid_total', ]; public function user(): BelongsTo { return $this->belongsTo(User::class); } protected function invoiceNumber(): Attribute { return Attribute::make( set: fn () => Invoice::max('invoice_number') + 1, ); } }
The downside of this method is you still need to pass some value to the invoice_number
when creating a record.
App\Models\Invoice::create(['user_id' => 1, 'paid_total' => 6969, 'invoice_number' => 0]);
And it creates the record.
App\Models\Invoice {#6048 user_id: 1, paid_total: 6969, invoice_number: 2, updated_at: "2024-06-25 10:57:26", created_at: "2024-06-25 10:57:26", id: 2,}
Now, how do you get the full invoice number? One way could be to use an Eloquent accessor.
app/Models/Invoice.php:
class Invoice extends Model{ protected $fillable = [ 'user_id', 'invoice_number', 'paid_total', ]; public function user(): BelongsTo { return $this->belongsTo(User::class); } protected function invoiceNumber(): Attribute { return Attribute::make( get: fn(int $value) => 'ABC-' . $value, set: fn () => Invoice::max('invoice_number') + 1, ); }}
The result for the latest invoice would be similar to this:
> $invoice = App\Models\Invoice::latest()->first();= App\Models\Invoice {#5742 id: 2, user_id: 1, invoice_number: 2, paid_total: 6969, created_at: "2024-06-25 10:57:26", updated_at: "2024-06-25 10:57:26", } > $invoice->invoice_number;= "ABC-2"
But what about leading zeros? To add zeros, we can use a PHP function str_pad()
.
app/Models/Invoice.php:
class Invoice extends Model{ protected $fillable = [ 'user_id', 'invoice_number', 'paid_total', ]; public function user(): BelongsTo { return $this->belongsTo(User::class); } protected function invoiceNumber(): Attribute { return Attribute::make( get: fn(int $value) => 'ABC-' . $value, get: fn(int $value) => 'ABC-' . str_pad($value, 5, '0', STR_PAD_LEFT), set: fn () => Invoice::max('invoice_number') + 1, ); }}
Now, for the same invoice, the result would look like this:
> $invoice = App\Models\Invoice::latest()->first();= App\Models\Invoice {#5742 id: 2, user_id: 1, invoice_number: 2, paid_total: 6969, created_at: "2024-06-25 10:57:26", updated_at: "2024-06-25 10:57:26", } > $invoice->invoice_number;= "ABC-00002"
What if the invoice must be set based on the user? Suppose we have an invoice_series
column in the users
table, which should be used for the prefix. If the prefix isn't set, the fallback should be the year.
app/Models/Invoice.php:
class Invoice extends Model{ protected $fillable = [ 'user_id', 'invoice_number', 'paid_total', ]; public function user(): BelongsTo { return $this->belongsTo(User::class); } protected function invoiceNumber(): Attribute { return Attribute::make( get: fn(int $value) => 'ABC-' . str_pad($value, 5, '0', STR_PAD_LEFT), set: fn () => Invoice::max('invoice_number') + 1, get: function(int $value) { $series = $this->user->invoice_series ?? date('Y'); return $series . '-' . str_pad($value, 5, '0', STR_PAD_LEFT); }, set: fn (mixed $value, array $attributes) => Invoice::where('user_id', $attributes['user_id'])->max('invoice_number') + 1, ); }}
When the invoice_series
for the user isn't set, then the prefix is set to the year.
> $invoice = App\Models\Invoice::latest()->first();= App\Models\Invoice {#5041 id: 3, user_id: 1, invoice_number: 3, paid_total: 6969, created_at: "2024-06-25 11:19:24", updated_at: "2024-06-25 11:19:24", } > $invoice->invoice_number;= "2024-00003"
But when the invoice_series
for the user is set, it is used as a prefix.
> $invoice = App\Models\Invoice::latest()->first();= App\Models\Invoice {#5742 id: 3, user_id: 1, invoice_number: 3, paid_total: 6969, created_at: "2024-06-25 11:19:24", updated_at: "2024-06-25 11:19:24", } > $invoice->invoice_number;= "XYZ-00003"
These are short tips on how to deal with invoice numbers in the database and Eloquent within Laravel.
You can also read this article: Laravel Invoices: Auto-Generate Serial Numbers - 4 Different Ways.