Flow: Admin Petrol Setup (Dedicated Petrol Section)¶
Actor¶
HNS Admin (Match Manager) preparing a match for Petrol-channel sales.
Preconditions¶
- Admin is authenticated in the Admin Portal with
ROLE_MATCH_MANAGER - Target match exists and has stadium/sector configuration
- Inventory is sufficient for the Petrol allocation
- QR code generation library available backend-side
Context¶
Petrol sales live in a dedicated Petrol section of the admin portal (not inside the generic Quota section). Creating a Petrol quota in that section automatically:
- Flags the quota with the Petrol type identifier (enables PIN functionality at POS — per OQ-E12-2 resolution)
- Forces
deferred_payment = TRUE(Petrol settles with HNS post-match — see Financial Model below) - Provisions an auto-generated deeplink + QR code derived from the Petrol quota ID — no separate "Generate QR" action, no separate deeplinks table to manage
Petrol staff accounts are provisioned separately (out of scope for this flow; see account provisioning docs).
Financial Model — Two-Legged Settlement¶
Petrol sales have two distinct payment legs that must not be conflated:
Leg A — Customer ↔ Petrol (at the station):
- Customer pays Petrol at the POS register using Petrol's own payment system (cash, card, voucher — however Petrol handles it)
- This leg is entirely external to HNS — HNS does not collect, route, or reconcile this money
- In the quota portal, Petrol staff simply mark the reservation as sold after collecting payment at their register (no
payment_methodortotal_amountcaptured by HNS)
Leg B — HNS ↔ Petrol (deferred settlement):
- HNS sells the entire Petrol allocation on deferred payment terms to Petrol as a B2B transaction
- Petrol is effectively the quota recipient / counterparty — HNS invoices Petrol for the allocated ticket total (or adjusted to actually-sold total, per contract)
- Settlement happens post-match via Deferred Payment Collection — e-racuni payment offer → bank transfer → reconciliation
- Makes Petrol responsible for the full allocation (sold or unsold, per contract terms), which is why the allocation is pre-committed
Implication for the quota record: deferred_payment = TRUE, recipient email is the Petrol ops inbox (not a customer), and the quota appears in the Finance team's Deferred Payment Collection queue after match close.
Flow Steps¶
Step 1 — Navigate to Petrol Section¶
- Admin opens Admin Portal sidebar → Petrol
- Lands on the Petrol section overview, which shows:
- List of all Petrol quotas across matches (see Step 4 for columns)
- "Create Petrol Quota" CTA
- Existing observability tabs (overview, pins) per current
/admin/petrolscaffolding
Step 2 — Create Petrol Quota¶
- Admin clicks "Create Petrol Quota"
- Form fields (all in the Petrol section — admin does not touch the generic Quota section):
- Match — picker, only shows matches with stadium config complete
- Recipient email — Petrol operations inbox (e.g.
petrol-ops@petrol.hr); pre-fillable from a configured default - Sectors — pre-allocated sectors agreed with Petrol (e.g. D1)
- Quantity — total seats for this match's Petrol channel (e.g. 220)
- Pricing — standard (no discount); Petrol settles at full price
- Allocation algorithm — default REDOM (Sequential) — spreads seats across sector so the snake algorithm assigns them fairly across PIN lookups
- Expiration — default: match date − 2 hours (configurable)
- Hidden / forced flags (admin cannot change):
quota_type = PETROLdeferred_payment = TRUEcan_create_subquotas = FALSEtransfer_permission = NO
- Admin saves. Backend:
- Creates the quota with the forced flags
- Allocates seats per the algorithm
- The deeplink
hns://petrol-sales/{petrol-quota-id}is immediately resolvable (no DB insert required — the quota ID is the stable reference) - Audit log records quota creation
Step 3 — View Auto-Generated QR in List¶
- After save, admin returns to the Petrol quotas list (or the new quota's detail view)
- The QR code is already available — it's rendered on demand from the quota ID
- From the list row, admin can:
- Preview QR inline (small thumbnail)
- Click through to a detail panel/page showing the full QR, deeplink URL, and match context
- Download SVG (print-ready scaling) or PNG (≥ 600×600 px) directly
- Copy the deeplink URL to clipboard
- Admin sends the SVG/PNG to Petrol via the agreed distribution channel (email / partner portal)
Step 4 — List View Columns¶
- Match (teams, date, venue)
- Recipient email
- Quantity / Allocated / Reserved / Sold counters
- Quota status (Active / Past Deadline / Cancelled)
- Deferred payment status (Pending / Invoiced / Paid) — links to Deferred Payment Collection
- QR preview thumbnail + download actions
- Created / expires timestamps
- Filters: match, quota status, deferred-payment status, date range
Step 5 — Ongoing Management¶
- Edit — admin can adjust quantity, sectors, expiration within the same Petrol section (same restrictions as other quotas)
- Cancel — cancels the quota; printed QR stops resolving (backend returns "Petrol sales unavailable for this match" to scans) because the quota is no longer in an active state. Follows standard quota cancellation rules (Cancel All Unused / Cancel Unfulfilled Only)
- Rotate QR (rare) — not supported as a first-class action because the QR = quota ID. To "rotate", admin cancels and recreates — this is intentional: the operational cost (notifying Petrol, reprinting) should be deliberate, not a casual click
Step 6 — Visibility in Generic Quota Section¶
- Petrol quotas are visible but read-only in the generic Quota section (for cross-channel reporting)
- Clicking a Petrol quota row in that section shows a banner: "Managed in Petrol section →" with a deeplink
- Admins cannot create / edit / cancel Petrol quotas from the generic Quota section — all operations happen in the Petrol section
Alternative Flows¶
A1: Match Not Yet Configured
- Admin tries to create a Petrol quota for a match without stadium/sector config
- System blocks: "Configure stadium for this match before creating a Petrol quota."
A2: Petrol Quota Already Exists For Match
- Backend enforces at most one active Petrol quota per match (partial unique index on
quotas (match_id) WHERE quota_type = 'PETROL' AND status = 'ACTIVE') - Form pre-check shows existing Petrol quota with a link; second create attempt returns 409
A3: Backend QR Library Failure
- QR rendering is on demand — if it fails server-side at download time, admin sees a generic error and can retry. The quota itself is unaffected.
A4: Cancel With Active Reservations in Flight
- Cancelling a Petrol quota follows standard quota cancellation rules — ALLOCATED seats released immediately; RESERVED seats (PINs in flight at a POS) behave per selected option (Cancel All Unused vs Cancel Unfulfilled Only)
- Petrol staff attempting to complete a PIN against a cancelled quota see "Quota cancelled by admin" at the POS
Technical Requirements¶
Deeplink derivation:
- Deeplink URL format:
hns://petrol-sales/{petrol-quota-id}(or configured universal-link equivalent) - Mobile app resolves the quota ID → loads match details + sector availability for that Petrol quota
- No
petrol_deeplinkstable needed in the new design. The backend-migratedpetrol_deeplinkstable becomes deprecated / optional (see Data Model Impact in E12-F4)
Backend endpoints (admin):
POST /admin/petrol/quotas— create Petrol quota (forcesquota_type=PETROL,deferred_payment=TRUE). Body same shape as genericPOST /admin/quotasminus the forced fields.GET /admin/petrol/quotas— list Petrol quotas with filters (match, status, deferred-payment status, date range)GET /admin/petrol/quotas/{id}— detail including computed deeplink URL + QR as SVG/PNG (or separate/qr.svg//qr.pngendpoints)GET /admin/petrol/quotas/{id}/qr.svg— returns QR SVG for downloadGET /admin/petrol/quotas/{id}/qr.png— returns QR PNG at ≥ 600×600POST /admin/petrol/quotas/{id}/cancel— cancel with standard cancellation options (delegates to existing quota cancellation service)
Generic quota endpoints:
POST /admin/quotasmust rejectquota_type=PETROL— direct the caller to/admin/petrol/quotasinsteadGET /admin/quotascontinues to return Petrol quotas (for cross-channel reporting) but admin UI disables mutations on them
QR Payload:
- Default: QR encodes the deeplink URL directly (=
hns://petrol-sales/{quota-id}) - Optional hardening: encode a signed token whose resolution lives server-side — defer to security review if adopted (a signed token would require a stored record, reintroducing something like
petrol_deeplinks)
Universal Links:
- iOS
apple-app-site-associationand Androidassetlinks.jsonmust include thepetrol-sales/*path — coordinate with mobile team (E12-F1)
Mobile deeplink handler (E12-F1):
- Needs to resolve
/petrol-sales/{id}where{id}is a Petrol quota ID - Calls backend
GET /petrol-quotas/{id}/context→ returns match info + sector availability + Petrol quota status - If quota is CANCELLED or EXPIRED → shows "Petrol sales unavailable for this match" (no PIN generation)
Related Flows¶
- Physical Sales at Petrol Station Flow — end-to-end customer + POS flow
- Admin Quota Creation — generic quota creation (Petrol is not a variant of this flow — it has its own dedicated section)
- Deferred Payment Collection — post-match settlement with Petrol
Last Updated: 2026-04-13