docs(wiki): tariff research — legacy ParkSQL2017 schema, time-tiers & validation/sponsorship design

Ingest the predecessor SQL Server schema (raw + source summary) and file design
pages for time-of-day/seasonal tariff tiers and merchant validation/postpaid
sponsorship. Cross-link tariff.md and validation-discounts.md. No code.
This commit is contained in:
2026-06-18 10:59:21 +02:00
parent 71aaad03b9
commit 58d8f06ba0
6 changed files with 1582 additions and 3 deletions
+87
View File
@@ -0,0 +1,87 @@
---
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]]).