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:
@@ -0,0 +1,115 @@
|
|||||||
|
---
|
||||||
|
type: concept
|
||||||
|
tags: [parking, domain, business, pricing, design]
|
||||||
|
sources: [parksql2017-legacy-schema]
|
||||||
|
updated: 2026-06-17
|
||||||
|
status: open
|
||||||
|
---
|
||||||
|
|
||||||
|
# Tariff Time Tiers — happy hour, off-peak, weekend, seasonal
|
||||||
|
|
||||||
|
Design for **time-of-day / day-of-week / seasonal pricing** on top of the existing [[tariff]] engine.
|
||||||
|
Resolves the `tariff.md` open question *"Time-of-day / weekday tiers — not in the block model yet."*
|
||||||
|
Driven by two concrete operator asks: a **happy-hour** rate, and (from [[parksql2017-legacy-schema|the
|
||||||
|
legacy schema]]) **vehicle/customer categories**.
|
||||||
|
|
||||||
|
> Status: **design, not built.** No schema/code committed yet — this records the chosen shape and
|
||||||
|
> the rejected alternatives so implementation is a transcription.
|
||||||
|
|
||||||
|
## The two real-world models we looked at
|
||||||
|
|
||||||
|
1. **Legacy `BA_TicketPrice`** ([[parksql2017-legacy-schema]]): each rate-card row is scoped by
|
||||||
|
`ValidFrom`/`ValidTo` (date window) **and** `ValidFromHour`/`ValidToHour` (daily hour window) **and**
|
||||||
|
`TicketCategoryID`. Happy hour = a second price row valid 14:00–16:00. Off-peak/season = a row
|
||||||
|
with a date or hour window. The active rate is selected by **(category, now-or-entry, date)**.
|
||||||
|
2. **Research (verified):** rates modelled as **time segments nested inside recurring time frames**,
|
||||||
|
where time frames = days-of-week / holidays / special-event days (US patent 10,762,723, 3-0
|
||||||
|
verified). Industry APIs (INRIX `structured_rate`) carry `time_in`/`time_out` + `dow` per rate.
|
||||||
|
Both point at the **same primitive**: a rate that is *active for a wall-clock window*.
|
||||||
|
|
||||||
|
Both converge: **happy hour is not a discount flag — it is a selector over which rate card is active
|
||||||
|
for a given slice of wall-clock time.**
|
||||||
|
|
||||||
|
## The decision to make: which-rate selector vs. discount modifier
|
||||||
|
|
||||||
|
| Option | Shape | Verdict |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| **A. Time-windowed rate cards** (recommended) | A stay is sliced at wall-clock boundaries; each slice priced by the rate card whose window covers it. Happy hour = a card with `window: {dow, fromHour, toHour}`. | Most general: one mechanism covers happy hour, early-bird, night flat, weekend, season. Matches both references. |
|
||||||
|
| **B. Discount modifier on one ladder** | Keep one ladder; apply `−X%`/`−N min` when the clock is inside a window. | Simpler, but can't express "different ladder at night," daily caps interact badly, and it's a second pricing path. Rejected as the primary model. |
|
||||||
|
|
||||||
|
**Recommendation: A.** A discount-style happy hour (B) is then expressible *as* a windowed card (a
|
||||||
|
cheaper ladder), so we don't lose it.
|
||||||
|
|
||||||
|
## The wall-clock slicing consequence (the hard part)
|
||||||
|
|
||||||
|
The current `computeFee(enteredAt, asOf, structure)` walks **elapsed** minutes through `blocks`. Time
|
||||||
|
tiers add a **second clock**: the *wall-clock* time-of-day, which the elapsed walk doesn't track. A
|
||||||
|
stay 13:30→15:30 that has happy hour 14:00–16:00 must be **split at 14:00**: 30 min normal + 90 min
|
||||||
|
happy. So the fee function must:
|
||||||
|
|
||||||
|
1. Resolve the **applicable rate set** for the stay (all cards matching the category, ordered by
|
||||||
|
precedence — see below).
|
||||||
|
2. Walk the stay in wall-clock order, **switching the active card at each window boundary**, while
|
||||||
|
keeping the **elapsed-duration position** in the block ladder continuous (so block steps and the
|
||||||
|
daily cap still accrue across a window switch — a happy hour mid-stay must not reset the ladder).
|
||||||
|
3. Keep it **pure, integer, offline, deterministic** — the same invariants the current engine and the
|
||||||
|
[[append-only-event-chain|signed chain]] depend on. The `payment` event still records the
|
||||||
|
`tariffVersionId`; the version now contains the windowed card set, so a past session reprices
|
||||||
|
identically.
|
||||||
|
|
||||||
|
> Open edge: does the block ladder accrue by **elapsed time** (a 2h stay is in the 2nd block
|
||||||
|
> regardless of windows) or **reset per window**? Legacy `IntervalChange` hints some sites reset.
|
||||||
|
> **Lean: elapsed-continuous** (predictable, no double-charging), revisit if a site needs otherwise.
|
||||||
|
|
||||||
|
## Precedence (when windows overlap)
|
||||||
|
|
||||||
|
Multiple cards can match one instant (a weekday-evening card + a holiday card). Need a deterministic
|
||||||
|
winner. Proposal, most-specific-wins, matching the research's "event rates override":
|
||||||
|
`special-event/holiday > specific date range > day-of-week + hour > hour-only > default`. Ties broken
|
||||||
|
by an explicit integer `priority`. This must be **total and pure** — no ambiguity the operator can't
|
||||||
|
predict, no "depends on row order."
|
||||||
|
|
||||||
|
## Vehicle / customer category (the second new axis)
|
||||||
|
|
||||||
|
Legacy `BA_TicketCategory` prices by **category** (car/bus/VIP/…), orthogonal to time. Two ways:
|
||||||
|
- **Multiple tariffs scoped by category** — the schema already reserves `tariffs.scope`
|
||||||
|
(`site`/`zone`); add `category` cleanly, no migration. The session records which category it was
|
||||||
|
priced under.
|
||||||
|
- **Category as another window dimension** on the card. Simpler table, busier card.
|
||||||
|
|
||||||
|
**Lean: category as a tariff scope** (a category is a different rate *card*, not a different *window*
|
||||||
|
of one). Deferred until a site actually needs non-car pricing, but the `scope` hook means **no
|
||||||
|
migration when it lands**.
|
||||||
|
|
||||||
|
## Proposed data shape (illustrative)
|
||||||
|
|
||||||
|
Extend the `TariffStructure` JSON (still one immutable [[tariff]] version) with an optional ordered
|
||||||
|
card list; absence = today's single-ladder behaviour (back-compatible):
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"currency": "ALL",
|
||||||
|
"defaultCard": { /* the existing blocks/cap/grace structure */ },
|
||||||
|
"windowedCards": [
|
||||||
|
{
|
||||||
|
"name": "Happy hour",
|
||||||
|
"priority": 10,
|
||||||
|
"window": { "dow": [1,2,3,4,5], "fromHour": "14:00", "toHour": "16:00" },
|
||||||
|
"blocks": [ /* cheaper ladder */ ],
|
||||||
|
"dailyCapMinor": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A bare `defaultCard` (no `windowedCards`) is exactly today's tariff — so this ships additively and a
|
||||||
|
site that never wants tiers never sees them. Keeps the **intuitive-for-operators** goal: the common
|
||||||
|
case stays one rate card; tiers are opt-in.
|
||||||
|
|
||||||
|
## Open
|
||||||
|
- Elapsed-continuous vs. per-window ladder reset (lean: elapsed-continuous).
|
||||||
|
- Holiday/special-event calendar: a date list per version, or a separate editable calendar table?
|
||||||
|
- Precedence model — confirm most-specific + explicit `priority` tiebreak.
|
||||||
|
- Category axis — confirm "category = tariff scope" vs. window dimension (deferred).
|
||||||
|
- UI: how to author windows without confusing operators (the notoriously-hard part — keep default
|
||||||
|
card front-and-center, tiers as an "advanced" add).
|
||||||
+32
-2
@@ -124,6 +124,24 @@ time references**, not one:
|
|||||||
`gracePeriodExit` is therefore a real revenue/UX parameter, not a nicety: too short traps people
|
`gracePeriodExit` is therefore a real revenue/UX parameter, not a nicety: too short traps people
|
||||||
who paid; too long gives free parking between pay and exit.
|
who paid; too long gives free parking between pay and exit.
|
||||||
|
|
||||||
|
> **As-built correction (2026-06-17):** the overstay top-up reprices from **entry**, not `paidAt` —
|
||||||
|
> `computeFee(enteredAt, now, …)` (so the timer never restarts; the customer pays the true entry→now
|
||||||
|
> total). The line above (`f(paidAt, now, …)`) was the original sketch; the implementation uses entry.
|
||||||
|
|
||||||
|
### ⚠ Open question — walk-back grace renews on every payment
|
||||||
|
|
||||||
|
A consequence of the two-time-reference model, surfaced via the [[booth-exit-flow|booth exit /
|
||||||
|
voucher]] path: every `payment` event stores its own `gracePeriodExit`, and the exit check reads the
|
||||||
|
**latest** payment's value. So an **overstay top-up re-grants a full, fresh grace window** each time.
|
||||||
|
The fee is correct (always recomputed from entry — no free exit), but the **walk-back grace doubles**
|
||||||
|
(or repeats) on every top-up — a customer could pay → wait → pay a tiny delta → earn another window →
|
||||||
|
repeat. The leak is **time, not money**, bounded by increment coarseness but real.
|
||||||
|
|
||||||
|
Candidate policies (business call): grant grace on a top-up **only when it charged new money**
|
||||||
|
(recommended), a **single non-renewing window** from the first payment, or a **per-session grace
|
||||||
|
cap**. Full analysis + the decided/undecided halves live in [[booth-exit-flow]]. Pick a policy before
|
||||||
|
production.
|
||||||
|
|
||||||
## Permit holders
|
## Permit holders
|
||||||
|
|
||||||
A valid [[permit]] bypasses tariff computation entirely for the covered period (subscription
|
A valid [[permit]] bypasses tariff computation entirely for the covered period (subscription
|
||||||
@@ -169,12 +187,24 @@ Unlike the event log, tariff data is **mutable master data** in the sense that n
|
|||||||
on the network — [[offline-first]]), a base currency, and a rounding policy. Deferred to
|
on the network — [[offline-first]]), a base currency, and a rounding policy. Deferred to
|
||||||
[[open-questions]].
|
[[open-questions]].
|
||||||
|
|
||||||
|
## Extensions under design
|
||||||
|
|
||||||
|
Two operator asks extend this engine; both have design pages (not yet built), grounded in
|
||||||
|
[[parksql2017-legacy-schema|the legacy schema]] + external research:
|
||||||
|
|
||||||
|
- **Time-of-day / weekday / seasonal tiers** (happy hour, off-peak, weekend, vehicle category) —
|
||||||
|
see [[tariff-time-tiers]]. Chosen shape: **time-windowed rate cards** selected by wall-clock window,
|
||||||
|
layered additively on this structure (a bare default card = today's behaviour). The hard part is
|
||||||
|
slicing a stay at window boundaries while keeping the block ladder + daily cap continuous.
|
||||||
|
- **Validation & sponsorship** (merchant comps, coupons, **postpaid B2B** "enter/exit free, bill the
|
||||||
|
business monthly") — see [[validation-sponsorship]]. A validation is a **typed modifier applied as a
|
||||||
|
signed event** on a transient session, distinct from a [[permit]]; postpaid sponsors accrue a
|
||||||
|
monthly-invoiced liability derivable from the chain.
|
||||||
|
|
||||||
## Open
|
## Open
|
||||||
|
|
||||||
- The **actual rate cards** are owner-authored at runtime — nothing to confirm at build time; the
|
- The **actual rate cards** are owner-authored at runtime — nothing to confirm at build time; the
|
||||||
composer UI + validation (sane blocks, non-negative, ordered `uptoMin`) is the work.
|
composer UI + validation (sane blocks, non-negative, ordered `uptoMin`) is the work.
|
||||||
- **Time-of-day / weekday tiers** — not in the block model yet; add as a tier wrapper if a site
|
|
||||||
needs day/night/weekend cards (deferred until asked).
|
|
||||||
- **Blank-tariff policy** — free vs. gated until a rate card is published (operator policy).
|
- **Blank-tariff policy** — free vs. gated until a rate card is published (operator policy).
|
||||||
- **In-progress version-boundary** — entry-version (decided) vs. pro-rate (revisit if needed).
|
- **In-progress version-boundary** — entry-version (decided) vs. pro-rate (revisit if needed).
|
||||||
- **FX** — exchange-rate system, offline rate source, base currency ([[open-questions]]).
|
- **FX** — exchange-rate system, offline rate source, base currency ([[open-questions]]).
|
||||||
|
|||||||
@@ -39,8 +39,17 @@ Because each validation is signed and attributed (`issuedBy`), over-validation b
|
|||||||
merchant is **visible to [[reconciliation]]** (a merchant validating far more than their footfall is
|
merchant is **visible to [[reconciliation]]** (a merchant validating far more than their footfall is
|
||||||
an anomaly), rather than invisible free parking.
|
an anomaly), rather than invisible free parking.
|
||||||
|
|
||||||
|
## Postpaid sponsors
|
||||||
|
|
||||||
|
When the validating party is a **business with a postpaid agreement** (its customers park free, it's
|
||||||
|
billed monthly) — not just a one-off discount — the **sponsor account + settlement** layer is in
|
||||||
|
[[validation-sponsorship]]. That's the distinction between a discount (this page) and a *sponsored*
|
||||||
|
session that accrues a receivable.
|
||||||
|
|
||||||
## Open
|
## Open
|
||||||
|
|
||||||
- Validation types the site needs (free hours / fixed amount / percentage / flat rate).
|
- Validation types the site needs (free hours / fixed amount / percentage / flat rate) — superset in
|
||||||
|
[[validation-sponsorship]] (`comp`/`percent`/`fixed`/`time-credit`/`rate-switch`).
|
||||||
- Whether merchants self-serve (portal/terminal) or the operator applies it.
|
- Whether merchants self-serve (portal/terminal) or the operator applies it.
|
||||||
- Caps (max discount, max per merchant/day).
|
- Caps (max discount, max per merchant/day).
|
||||||
|
- Prepaid coupon pool vs. postpaid accrual — see [[validation-sponsorship]].
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
---
|
||||||
|
type: concept
|
||||||
|
tags: [parking, domain, business, pricing, validation, design]
|
||||||
|
sources: [parksql2017-legacy-schema]
|
||||||
|
updated: 2026-06-17
|
||||||
|
status: open
|
||||||
|
---
|
||||||
|
|
||||||
|
# Validation & Sponsorship — merchant comps, coupons, postpaid B2B
|
||||||
|
|
||||||
|
Builds on [[validation-discounts]] (the signed-event discount mechanism) to add the layer it leaves
|
||||||
|
open: **a sponsor account and postpaid B2B billing.** The driving case — **a nearby business with a
|
||||||
|
postpaid agreement whose customers enter and exit freely, billed to the business monthly.**
|
||||||
|
|
||||||
|
> This page owns the **sponsor/account/settlement** model and the **permit-vs-validation
|
||||||
|
> distinction**. The *how a discount is applied* mechanics (signed event, `due = max(0, fee −
|
||||||
|
> discounts)`, attribution, anti-abuse) live in [[validation-discounts]] — not duplicated here.
|
||||||
|
> Status: **design, not built.**
|
||||||
|
|
||||||
|
## Why this is NOT a permit (the key distinction)
|
||||||
|
|
||||||
|
| | [[permit]] | Validation / sponsorship |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Subject | Known in advance; carries a credential (card/QR/plate) | Anonymous walk-in; identified only by the **ticket they were issued** |
|
||||||
|
| When applied | At entry (credential opens the lane) | **After entry**, against an existing session — at a pay station, by a code, or by a sponsor rule |
|
||||||
|
| Who pays | The subscriber, out-of-band | A **third party** (merchant/sponsor), or nobody (comp) |
|
||||||
|
| Model fit | `permits` + credentials | New: a **validation event** on a session + a **sponsor account** |
|
||||||
|
|
||||||
|
A permit bypasses tariff computation; a validation **adjusts the computed fee** (or zeroes it). They
|
||||||
|
compose — but they are different primitives.
|
||||||
|
|
||||||
|
## Two economic models (both real)
|
||||||
|
|
||||||
|
- **Prepaid** — merchant buys a pool of value up front (legacy `BA_Cupons`: printed single-use codes
|
||||||
|
worth `DiscMinutes`; City Center research: merchant pre-buys time tickets 15 min→all-day).
|
||||||
|
Reconciliation = count used codes against the pool.
|
||||||
|
- **Postpaid** (the asked-for case) — merchant signs an agreement; their customers park free or
|
||||||
|
discounted; the system **accrues each validation against a sponsor balance** and **invoices monthly**
|
||||||
|
(City Center: "billed for the number of tickets validated each month," verified 3-0). No money moves
|
||||||
|
at the lane.
|
||||||
|
|
||||||
|
The legacy system did **only prepaid coupons** — **the postpaid sponsor account is net-new** for this
|
||||||
|
project.
|
||||||
|
|
||||||
|
## Modifier types (extends [[validation-discounts]])
|
||||||
|
|
||||||
|
The discount-type enum lives in [[validation-discounts]]; legacy `DiscType` (smallint) and research
|
||||||
|
(Amano McGann / HUB J4M, abstained-not-refuted) confirm the set: `comp` / `percent` / `fixed` /
|
||||||
|
`time-credit` (legacy `DiscMinutes`) / `rate-switch`. **Sponsorship adds one field** to a validation:
|
||||||
|
a `sponsorId`. Full-comp + a sponsor = the "free entry/exit, bill the business" case.
|
||||||
|
|
||||||
|
## The sponsor-liability consequence (anti-fraud)
|
||||||
|
|
||||||
|
The validation is a signed event ([[validation-discounts]], [[append-only-event-chain]]); what
|
||||||
|
**sponsorship** adds is that **free-to-the-parker is not free-to-the-ledger** — it is a *receivable
|
||||||
|
from the sponsor*. Under the [[threat-model|operator-as-adversary]] model:
|
||||||
|
|
||||||
|
- A postpaid sponsor's "enter/exit freely" still **mints signed entry + exit events** (and snapshots)
|
||||||
|
— the audit trail is identical to a paying car; only the **settlement target** differs.
|
||||||
|
- The **sponsor's period liability = the sum of `sponsorId`-tagged validation events** over the
|
||||||
|
period — derivable from the chain, reconcilable like a [[shift|shift Z-report]] and visible to
|
||||||
|
[[reconciliation]] (a sponsor comping far more than plausible footfall is an anomaly).
|
||||||
|
|
||||||
|
## Proposed data shape (illustrative — design only)
|
||||||
|
|
||||||
|
```
|
||||||
|
sponsors id, name, contact, mode {prepaid|postpaid},
|
||||||
|
balance_minor (prepaid pool / postpaid accrual), billing_period, active
|
||||||
|
validations id, session_id, sponsor_id?, type, amount_minor|minutes,
|
||||||
|
code?, operator_id, created_at // append-only; one row per application
|
||||||
|
(coupons) code, value_minutes|minor, single_use, used_at? // prepaid pool, optional
|
||||||
|
```
|
||||||
|
|
||||||
|
- A **postpaid** sponsor: each full-comp validation appends a row and accrues `amount` to the
|
||||||
|
sponsor; monthly invoice = sum over the period; exit is free at the lane.
|
||||||
|
- **Free entry/exit "freely"**: either the sponsor issues credentials (then it's closer to a
|
||||||
|
[[permit]] — pick that path), or customers take a normal ticket and a sponsor rule / merchant code
|
||||||
|
comps it at exit. The agreement wording decides which; **both are expressible.**
|
||||||
|
|
||||||
|
## Reconciliation & settlement
|
||||||
|
- **Prepaid**: pool decrements; alert at low balance; no invoice.
|
||||||
|
- **Postpaid**: accrue; **monthly statement** per sponsor (legacy/City Center cadence ~the 10th).
|
||||||
|
Statement lines trace to signed validation events → disputes resolvable against the chain.
|
||||||
|
|
||||||
|
## Open
|
||||||
|
- **"Enter/exit freely" mechanism**: sponsor-issued credentials ([[permit]]-like) vs. ticket +
|
||||||
|
comp-at-exit. Likely offer both; confirm the operator's actual deal shape.
|
||||||
|
- Prepaid coupon format: printed codes (legacy) vs. QR vs. merchant web-validation portal.
|
||||||
|
- Who may apply a validation, and the **per-operator cap** (a comp is a fraud vector — bound it and
|
||||||
|
always sign it).
|
||||||
|
- Invoicing: in-app statement only, or export for external billing? FX if sponsor bills in another
|
||||||
|
currency (defer to [[tariff]] FX).
|
||||||
|
- Partial-stay sponsorship (merchant covers first 2h, parker pays the rest) — `time-credit` or
|
||||||
|
`rate-switch` covers it; confirm.
|
||||||
File diff suppressed because one or more lines are too long
@@ -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]]).
|
||||||
Reference in New Issue
Block a user