Files
WMS/Modules/Helpdesk/Services/TicketReferenceService.php
T
2026-06-23 11:10:52 +01:00

66 lines
1.9 KiB
PHP

<?php
/**
* TicketReferenceService.php
*
* Generates collision-resistant Helpdesk ticket references.
*/
declare(strict_types=1);
namespace Modules\Helpdesk\Services;
use Illuminate\Support\Facades\DB;
use Modules\Helpdesk\Models\Ticket;
final class TicketReferenceService
{
/**
* Build the next human-readable ticket reference for the current year.
*
* The lookup uses the highest numeric suffix, not the latest database id,
* because imported, restored, or manually adjusted tickets may not be in
* reference order. On PostgreSQL an advisory transaction lock is also used
* so two users cannot generate the same reference at the same time.
*/
public function next(): string
{
$year = now()->format('Y');
$prefix = 'HD-'.$year.'-';
$this->lockReferenceSeries($prefix);
/** @var list<string> $existingReferences */
$existingReferences = Ticket::query()
->withoutGlobalScopes()
->withTrashed()
->where('reference', 'like', $prefix.'%')
->pluck('reference')
->filter(static fn (mixed $reference): bool => is_string($reference) && preg_match('/^'.preg_quote($prefix, '/').'\d+$/', $reference) === 1)
->values()
->all();
$highestSuffix = 0;
foreach ($existingReferences as $existingReference) {
$highestSuffix = max($highestSuffix, (int) substr($existingReference, strlen($prefix)));
}
return $prefix.str_pad((string) ($highestSuffix + 1), 5, '0', STR_PAD_LEFT);
}
/**
* Lock the current ticket reference series for the active transaction.
*/
private function lockReferenceSeries(string $prefix): void
{
if (DB::connection()->getDriverName() !== 'pgsql') {
return;
}
DB::select('select pg_advisory_xact_lock(hashtext(?))', [
'helpdesk-ticket-reference:'.$prefix,
]);
}
}