format('Y'); $prefix = 'HD'; $seriesPrefix = $prefix . '-' . $year . '-'; $this->lockReferenceSeries($seriesPrefix); if (Schema::hasTable('helpdesk_ticket_sequences')) { return $this->nextFromSequenceTable($prefix, $year, $seriesPrefix); } return $this->nextFromExistingReferences($seriesPrefix); } /** * Return the next reference from the durable sequence table. */ private function nextFromSequenceTable(string $prefix, int $year, string $seriesPrefix): string { $sequence = DB::table('helpdesk_ticket_sequences') ->where('prefix', $prefix) ->where('year', $year) ->lockForUpdate() ->first(); if (! $sequence) { DB::table('helpdesk_ticket_sequences')->insert([ 'prefix' => $prefix, 'year' => $year, 'next_number' => $this->highestExistingSuffix($seriesPrefix) + 1, 'created_at' => now(), 'updated_at' => now(), ]); $sequence = DB::table('helpdesk_ticket_sequences') ->where('prefix', $prefix) ->where('year', $year) ->lockForUpdate() ->first(); } $nextNumber = max((int) $sequence->next_number, $this->highestExistingSuffix($seriesPrefix) + 1); DB::table('helpdesk_ticket_sequences') ->where('id', $sequence->id) ->update([ 'next_number' => $nextNumber + 1, 'updated_at' => now(), ]); return $seriesPrefix . str_pad((string) $nextNumber, 5, '0', STR_PAD_LEFT); } /** * Fallback for installations that have not run the sequence migration yet. */ private function nextFromExistingReferences(string $seriesPrefix): string { return $seriesPrefix . str_pad((string) ($this->highestExistingSuffix($seriesPrefix) + 1), 5, '0', STR_PAD_LEFT); } /** * Find the highest used suffix across active and soft-deleted tickets. */ private function highestExistingSuffix(string $seriesPrefix): int { /** @var list $existingReferences */ $existingReferences = Ticket::query() ->withoutGlobalScopes() ->withTrashed() ->where('reference', 'like', $seriesPrefix . '%') ->pluck('reference') ->filter(static fn (mixed $reference): bool => is_string($reference) && preg_match('/^' . preg_quote($seriesPrefix, '/') . '\d+$/', $reference) === 1) ->values() ->all(); $highestSuffix = 0; foreach ($existingReferences as $existingReference) { $highestSuffix = max($highestSuffix, (int) substr($existingReference, strlen($seriesPrefix))); } return $highestSuffix; } /** * Lock the current ticket reference series for the active transaction. */ private function lockReferenceSeries(string $seriesPrefix): void { if (DB::connection()->getDriverName() !== 'pgsql') { return; } DB::select('select pg_advisory_xact_lock(hashtext(?))', [ 'helpdesk-ticket-reference:' . $seriesPrefix, ]); } }