5697137c52
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
88 lines
5.5 KiB
Markdown
88 lines
5.5 KiB
Markdown
---
|
||
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]]).
|