Files
parking_solution/wiki/sources/parksql2017-legacy-schema.md
T
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

88 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
type: source
tags: [parking, legacy, pricing, schema, fiscalization]
sources: [parksql2017-legacy-schema]
updated: 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|operator-as-adversary]] 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]]).