Fix invoice number race condition: use DB lock for atomic increment

This commit is contained in:
2026-03-11 19:24:06 -04:00
parent 7a6ea21925
commit f00ba32643

View File

@@ -5,6 +5,7 @@ namespace App\Listeners;
use App\Events\Invoice\Creating; use App\Events\Invoice\Creating;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Setting; use App\Models\Setting;
use Illuminate\Support\Facades\DB;
class InvoiceCreatingListener class InvoiceCreatingListener
{ {
@@ -13,15 +14,30 @@ class InvoiceCreatingListener
*/ */
public function handle(Creating $event): void public function handle(Creating $event): void
{ {
// Use database lock to prevent duplicate invoice numbers across clustered nodes
$number = DB::transaction(function () {
$setting = Setting::where('key', 'invoice_number')
->whereNull('settingable_id')
->lockForUpdate()
->first();
$current = $setting ? (int) $setting->value : (int) config('settings.invoice_number', 0);
$next = $current + 1;
Setting::updateOrCreate([
'key' => 'invoice_number',
'settingable_id' => null,
'settingable_type' => null,
], [
'value' => $next,
]);
// Update in-memory config so subsequent calls in the same request are correct
config(['settings.invoice_number' => $next]);
return $next;
});
// Get the next invoice number
$number = config('settings.invoice_number', 1) + 1;
// Update setting
Setting::updateOrCreate([
'key' => 'invoice_number',
], [
'value' => $number,
]);
// Pad the invoice number with leading zeros // Pad the invoice number with leading zeros
$paddedNumber = str_pad($number, config('settings.invoice_number_padding', 1), '0', STR_PAD_LEFT); $paddedNumber = str_pad($number, config('settings.invoice_number_padding', 1), '0', STR_PAD_LEFT);