Files
parking_solution/wiki/sources/parksql2017-legacy-schema.md
julian 5697137c52 feat(subscription): rename permit→subscription + monthly pricing
The "permit/lejet" feature is really a subscription. Full rename of the
mutable master data, plus a recurring monthly price.

- DB (migration 0004, data-preserving ALTER RENAME): permits→subscriptions,
  permit_credentials/_plates→subscription_*, sessions.permit_id→subscription_id.
- Pricing: per-subscription priceMinor + period(monthly) + currency, with a
  site default (site_config.subscription_monthly_price_minor) pre-filling the form.
- Server: subscription-flow.ts (SubscriptionFlow), routes/subscriptions.ts
  (/api/subscriptions). Web: SubscriptionManager, route, i18n (sq Abonimet/en).
- The signed ledger `permitId` payload is intentionally kept — immutable
  hash-chained history; renaming it would break verification of past events.

Deferred (wiki notes): fee collection into the ledger/shift (a shift-attributed
payment), LPR/ANPR plate source, time-of-day access windows (overnight subscriber).

Also carries the device-footer UI surface (api DeviceStatus, router mount,
i18n devices) due to shared-file overlap with the preceding footer commit.

Verified end-to-end on a fresh DB and migration on a live-DB copy (sessions
preserved). Live DB migrated. Full monorepo builds clean.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-18 13:15:04 +02:00

5.5 KiB
Raw Permalink Blame History

type, tags, sources, updated
type tags sources updated
source
parking
legacy
pricing
schema
fiscalization
parksql2017-legacy-schema
2026-06-17

ParkSQL2017 — Legacy Parking System Schema (source summary)

A SQL Server 2017 schema dump (raw/parksql2017-legacy-schema.sql, scripted 2024-10-07) of an existing/predecessor parking system in the same Albanian market this project targets. It is the single most concrete reference we have for how the prior generation modelled tariffs, discounts, memberships, sessions, shifts, and fiscalization — a real, deployed data model rather than vendor marketing. Treat it as evidence of what worked and what to improve, not as a spec to copy (it has clear anti-patterns, e.g. money as float).

Albanian-context tells: BA_TicketFisc.nivf (NIVF fiscalization code), the Cupons spelling, LostPrice1..4 tiers. BA_ = business-app table prefix; SYS_ = system/auth tables.

Table map (24 tables)

Pricing / tariff

  • BA_TicketPrice — the rate-card header. Key fields: Code, Name, TicketCategoryID, ParkID, ValidFrom/ValidTo (date window), ValidFromHour/ValidToHour (time-of-day window), FixedPrice (flat option), IntervalType (2-char unit, e.g. MI/HR/DY), Interval (increment size), LostPenalty, IsDefault, IsActive. → A rate card is scoped by (category × date-range × hour-range). This is the happy-hour / time-of-day mechanism.
  • BA_TicketPriceHours — the stepped ladder (child of TicketPrice): rows of HourFrom, HourTo, Price. → equivalent to this project's tariff blocks[].
  • BA_TicketCategory — vehicle/customer category (Code, Name, IsDefault, IntervalChange). → a pricing axis by category the current model lacks.
  • BA_TicketFisc — TicketID → nivf (Albanian fiscalization code per ticket).

Discounts / validation

  • BA_Cupons — CODE, DiscMinutes (discount as free minutes), IsUsed, LastUsed, IsPrinted. → validation = a single-use coupon code worth N free minutes; reconciled by counting used codes (prepaid model, no merchant account/ledger).

Sessions

  • BA_ParkRecords — the transient parking session (ticket cars). Carries lifecycle (InTime/OutTime/ExitTime, In/Out Mode/Addr/OperatorID, In/Out ShiftID), money (OrgCharge, Charge, Discount, FreeMin, IsPaid), discount detail (DiscMinutes, DiscType smallint, DisTicketSerial), entry/exit plate+image columns, and ManualOpenReason / ExpiredTime/DateApproval audit fields.
  • BA_MembersCheckINOUT — per-event check-in/out log for members (cards), separate from ticket sessions.
  • BA_ManualCheckINOUT — every manual barrier open, with Reason + OperatorID + image.

Memberships (≈ this project's permits)

  • BA_Members — the member (card+plate identity, contact, isVIP).
  • BA_Memberships — an issued subscription: PlanID, StartDate/EndDate, Price, CalculatedPrice, Paid, AllowedDays.
  • BA_MembershipPlans — plan template: Type, Duration, Price, ActiveDays.
  • BA_MembershipPlansTime — StartTime/EndTime windows per plan → memberships valid only in specific hours (commuter/day-shift permits).

Site / ops / auth

  • BA_Park — a lot: capacity (ParkingPlaces/FreePlaces), LED sign addr, default ticket/lost category codes, FreeMinutes, DiscMinutesTicket, DiscMinutesApp, LostPrice1..4.
  • BA_Shifts — cashier shift / Z-report: open/close, Charged, TicketCharges, CardCharges, entry/exit + manual counts, Reconciled, UserID, MachineID.
  • BA_CashRegister, SYS_Configs (company/fiscal/ticket header+footer, AllowTimeExeed, ImageDays), SYS_User/SYS_Role/SYS_Rights/SYS_UserRights, SYS_Controls, SYS_Language (DB-driven i18n).

What it confirms for our design

  1. Stepped ladder — BA_TicketPriceHours (HourFrom/HourTo/Price) ≈ our blocks[]. Good signal.
  2. Per-rate lost penalty + site-level lost tiers (LostPrice1..4) ≈ our lostTicketMinor (+ the admin-override idea).
  3. Typed discount on the session record (DiscType) ≈ the research's "typed validation modifier."
  4. Manual-open + reason logging at the schema level ≈ our threat-model audit need.

What it adds (genuinely new vs. our current model)

  1. Time-of-day + date windows on the rate card (ValidFromHour/ValidToHour, ValidFrom/ValidTo) — the shipped way to do happy hour / seasonal. See tariff-time-tiers.
  2. Vehicle/customer category as a pricing axis (BA_TicketCategory). See tariff-time-tiers.
  3. Time-/day-restricted memberships (MembershipPlansTime, ActiveDays) — a subscription gap.

Anti-patterns to NOT copy

  • Money as float everywhere (Charge, Price, LostPenalty) — drifts across a revenue ledger. Our integer-minor-units rule is the deliberate fix. (rejected alternative)
  • Mutable rate rows (Updated/UpdatedBy in place) — a past session can't reliably reprice against the rate then in force. Our immutable effective-dated tariff versions fix this.
  • No merchant/sponsor account or postpaid ledger — only prepaid printed coupons. The B2B postpaid case is net-new; see validation-sponsorship.
  • Images stored as image BLOBs in-row — we keep snapshot bytes out of the event row and store a reference instead (append-only-event-chain).