--- 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 [[permit]] 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]]).