Fix invoice number race condition: use DB lock for atomic increment
This commit is contained in:
@@ -5,6 +5,7 @@ namespace App\Listeners;
|
||||
use App\Events\Invoice\Creating;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class InvoiceCreatingListener
|
||||
{
|
||||
@@ -13,15 +14,30 @@ class InvoiceCreatingListener
|
||||
*/
|
||||
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
|
||||
$paddedNumber = str_pad($number, config('settings.invoice_number_padding', 1), '0', STR_PAD_LEFT);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user