Preskoči na sadržaj

E12-F4: Admin Petrol Section (Unified Quota + Auto-QR)

Epic: E12: Physical Sales (Petrol)

Size: M (Medium)

Problem / Outcome

Provide a dedicated Petrol section in the admin portal that lets match managers create and manage Petrol quotas end-to-end — including the match-specific QR code that Petrol prints for retail distribution — without touching the generic Quota section and without an explicit "Generate QR" action.

Without this, petrol_deeplinks stays empty (or unused), no QR reaches Petrol, the mobile deeplink has nothing to open, and Petrol quotas get created inconsistently through the generic quota form (missing the Petrol type flag and/or deferred-payment flag).

Design Summary

  • Petrol quotas are created inside the Petrol section, not the generic Quota section
  • On create, the backend forces: quota_type=PETROL, deferred_payment=TRUE, can_create_subquotas=FALSE, transfer_permission=NO
  • The QR code is auto-derived from the Petrol quota ID — deeplink is hns://petrol-sales/{petrol-quota-id}. No explicit generate step, no petrol_deeplinks table needed
  • The Petrol section list shows all Petrol quotas with inline QR preview + SVG/PNG download
  • Generic quota list shows Petrol quotas as read-only with a "Managed in Petrol section" link

Scope

In-Scope:

  • New /admin/petrol/quotas sub-section in the Petrol area of the admin portal (list, create, detail, cancel)
  • Backend endpoints under /admin/petrol/quotas/* that wrap the generic quota service with the Petrol-forced flags
  • On-demand QR rendering (SVG + PNG ≥ 600×600) at /admin/petrol/quotas/{id}/qr.{svg,png} — deeplink derived from quota ID
  • Generic POST /admin/quotas must reject quota_type=PETROL with a redirect hint
  • Generic quota list displays Petrol quotas as read-only with link to Petrol section
  • Deprecate the petrol_deeplinks table (see Data Model Impact)

Out-of-Scope:

  • Scan analytics / telemetry (separate observability feature)
  • Physical printing / poster design — Petrol handles downstream
  • Mobile app deeplink handling for /petrol-sales/{quota-id} — that's in E12-F1; this feature only ensures URL format alignment
  • Two-legged financial model implementation (customer-leg payment externality, Petrol deferred settlement integration) — tracked separately under E12 — see Admin Petrol Setup Flow → Financial Model
  • QR rotation / token signing — intentionally not a first-class action; to "rotate" admin cancels and recreates the Petrol quota

Acceptance Criteria

  • AC1: Admin creates a Petrol quota from /admin/petrol/quotas — resulting quota has quota_type=PETROL, deferred_payment=TRUE, can_create_subquotas=FALSE, transfer_permission=NO (forced by backend, not user-selectable)
  • AC2: At most one active Petrol quota exists per match (409 on duplicate create)
  • AC3: Petrol quota list in Petrol section shows inline QR thumbnail, SVG/PNG download, copy-deeplink-URL, match context, counters (allocated/reserved/sold), deferred-payment status
  • AC4: SVG and PNG (≥ 600×600) downloads decode back to hns://petrol-sales/{petrol-quota-id}
  • AC5: Generic quota list shows Petrol quotas read-only with "Managed in Petrol section" link; edit/cancel disabled from generic section
  • AC6: Generic POST /admin/quotas returns 400/422 with a clear error if quota_type=PETROL is sent
  • AC7: Cancelling a Petrol quota follows standard cancellation rules; after cancel the deeplink resolves to "Petrol sales unavailable for this match" at the mobile side
  • AC8: All create / cancel actions audit-logged with admin id, quota id, match id

Data Model Impact

petrol_deeplinks table → deprecated. The deeplink is derived from the Petrol quota ID (no storage needed). Options during implementation:

  • Drop the table (cleanest): add a drop-table migration. Removes the partial-index concern from the earlier design.
  • Keep the table but stop writing (safest): mark deprecated in schema docs, backend services stop inserting, readers stop being used. Leaves the physical table for a later cleanup sprint.

No new tables required. The quotas table needs a partial unique index to enforce AC2:

CREATE UNIQUE INDEX idx_quotas_petrol_active_per_match
  ON quotas (match_id)
  WHERE quota_type = 'PETROL' AND status = 'ACTIVE';

Permissions/Roles

  • ROLE_MATCH_MANAGER (or equivalent admin role that already manages match setup)

How to Verify

# Backend
./vendor/bin/phpunit tests/E12/PetrolQuotaAdminTest.php

# Admin portal
cd ../hns-admin-portal && npm run test:e2e -- --grep "petrol section"

Expected:

  • Petrol quota created via /admin/petrol/quotas has forced flags
  • Generic POST /admin/quotas rejects quota_type=PETROL
  • QR SVG/PNG decodes to hns://petrol-sales/{quota-id}
  • At most one active Petrol quota per match

Dependencies

Implementation Tasks

See E12: Physical Sales (Petrol) Tasks → F4.

Doc References


Last Updated: 2026-04-13