- Configurable ticket numbering (Admin > Konfiguracja > Ogólne): admin-set
  prefix and minimum zero-padded length for the ticket number.
- "Ukryj kolejność zgłoszeń": an opt-in mode that displays a stable,
  HMAC-derived checksum instead of the sequential ticket number, so it gives
  no indication of ticket volume or creation order. Ticket URLs switch to
  the same checksum when this is on, so a link and the number on the page it
  points to always match. The REST API is unaffected — pinned to `id`
  regardless of this setting. Search now also matches by checksum.
- Fixed: attachments no longer show an inline image thumbnail in the
  message thread — every attachment (images included) shows as just its
  filename, opening in a new tab on click.
- Docs: README/ARCHITECTURE/wiki updated for all of the above.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 09:57:39 +02:00
parent ab90abcaa3
commit 63178b366e
21 changed files with 368 additions and 42 deletions

View File

@@ -63,6 +63,44 @@ queue + unassigned + anything assigned to them, an admin sees everything), and
work-timer tracking (`timerElapsedSeconds()`). Keep ticket-shaped logic here
rather than spreading it across Livewire components.
## Ticket numbering & URLs
A ticket carries three distinct identifiers, each with a different job:
- **`id`** — the DB primary key. Never shown to users; the REST API
(`routes/api.php`) is deliberately pinned to it (`{ticket:id}` explicit
binding on every `{ticket}` route) so external integrations have a stable
contract regardless of the numbering settings below.
- **`number`** — a plain sequential string (`Ticket::nextNumber()`, max+1
starting at 1001), unique but otherwise unremarkable. Backs `scopeSearch()`
and the numeric sort in `Operator/Queue.php` regardless of display mode.
- **`checksum`** — a 6-digit HMAC-derived value (salted with `app.key`,
keyed off `id`), assigned once in a `Ticket::booted()` `created` listener
and never changed afterward. Collisions are handled for real, not just
assumed away: `Ticket::generateUniqueChecksum()` walks a nonce forward
until the candidate is free (checked against the DB), and the column has a
`unique()` constraint as a hard backstop.
`Ticket::displayNumber()`/`formattedNumber()` pick between `number` (zero-padded
to `Settings::get('ticket_number_min_length')`) and `checksum` based on
`Settings::bool('ticket_number_obfuscate')` — the "Ukryj kolejność zgłoszeń"
toggle in Admin > Konfiguracja. `Ticket` also overrides `getRouteKey()` and
`resolveRouteBinding()` to mirror that same choice, so **the web routes**
(`routes/web.php`, all plain `{ticket}` implicit bindings — no explicit field)
resolve and generate URLs against whichever column is currently the display
number: flip the setting and both the visible number *and* every link
(`route('client.ticket', $ticket)` etc.) switch together, and a bookmarked URL
built under the old mode stops resolving. This is why the API routes need the
explicit `{ticket:id}` override — without it, the same global `getRouteKey()`
change would silently start requiring `number`/`checksum` in API path params
too, breaking the documented `integer` "Ticket id" contract.
The `{numer}` placeholder available in admin-editable e-mail templates
(Admin > Szablony e-mail / Wyzwalacze) resolves to `formattedNumber()`
*without* `displayNumber()`'s prefix — those templates already hardcode their
own `#{numer}`, so adding the prefix there too would double it up or clash
with a non-default prefix.
## Roles & permissions
`$user->roles` reads/writes as a plain array (`['client', 'operator']`), but