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
This commit is contained in:
+42
-11
@@ -24,7 +24,7 @@ export const en: Catalog = {
|
||||
shift: "Shift",
|
||||
setup: "Setup",
|
||||
tariff: "Tariff",
|
||||
permits: "Permits",
|
||||
subscriptions: "Subscriptions",
|
||||
site: "Site",
|
||||
},
|
||||
status: {
|
||||
@@ -32,6 +32,33 @@ export const en: Catalog = {
|
||||
connecting: "CONNECTING",
|
||||
offline: "OFFLINE",
|
||||
},
|
||||
devices: {
|
||||
footerTitle: "Devices",
|
||||
none: "No devices configured.",
|
||||
catAccess: "Barrier",
|
||||
catReader: "Reader",
|
||||
catCamera: "Camera",
|
||||
catPrinter: "Printer",
|
||||
// Role/direction suffixes for the chip label (e.g. "Reader entry").
|
||||
role: {
|
||||
entry: "entry",
|
||||
exit: "exit",
|
||||
both: "entry/exit",
|
||||
mixed: "mixed",
|
||||
lane: "lane",
|
||||
booth: "booth",
|
||||
},
|
||||
state: {
|
||||
ready: "ready",
|
||||
degraded: "degraded",
|
||||
offline: "offline",
|
||||
},
|
||||
allOk: "all ready",
|
||||
issuesCount: "{{count}} with issues",
|
||||
issuesTitle: "Device issues",
|
||||
clickForIssues: "Click for details",
|
||||
checkedAt: "checked {{time}}",
|
||||
},
|
||||
booth: {
|
||||
processTicket: "Process ticket",
|
||||
scanPlaceholder: "Scan or type ticket number…",
|
||||
@@ -92,21 +119,25 @@ export const en: Catalog = {
|
||||
publishing: "Publishing…",
|
||||
publishedOk: "New tariff version published — it's now the active rate card.",
|
||||
},
|
||||
permits: {
|
||||
title: "Permits",
|
||||
subs: {
|
||||
title: "Subscriptions",
|
||||
unnamed: "(unnamed)",
|
||||
unbound: "unbound",
|
||||
car_one: "{{count}} car",
|
||||
car_other: "{{count}} cars",
|
||||
cred: "cred",
|
||||
plates: "{{count}} plate(s)",
|
||||
noPrice: "no price",
|
||||
perMonth: "month",
|
||||
monthlyPrice: "Monthly price",
|
||||
pricePlaceholder: "e.g. 10000",
|
||||
edit: "Edit",
|
||||
revoke: "Revoke",
|
||||
delete: "Delete",
|
||||
noPermitsYet: "No permits yet.",
|
||||
addPermit: "+ Add permit",
|
||||
newPermit: "New permit",
|
||||
editPermit: "Edit permit",
|
||||
noneYet: "No subscriptions yet.",
|
||||
add: "+ Add subscription",
|
||||
new: "New subscription",
|
||||
editTitle: "Edit subscription",
|
||||
holderName: "Holder name",
|
||||
contact: "Contact",
|
||||
carLimit: "Car limit",
|
||||
@@ -121,12 +152,12 @@ export const en: Catalog = {
|
||||
qr: "QR",
|
||||
credentialValue: "credential value",
|
||||
addCredential: "+ credential",
|
||||
needCredentialOrPlate: "A permit needs at least one credential OR one bound plate.",
|
||||
needCredentialOrPlate: "A subscription needs at least one credential OR one bound plate.",
|
||||
save: "Save",
|
||||
cancel: "Cancel",
|
||||
permitSaved: "Permit saved.",
|
||||
confirmRevoke: "Revoke permit for {{name}}? It will be refused at the barrier.",
|
||||
confirmDelete: "Delete permit for {{name}}? (Past events are kept.)",
|
||||
saved: "Subscription saved.",
|
||||
confirmRevoke: "Revoke subscription for {{name}}? It will be refused at the barrier.",
|
||||
confirmDelete: "Delete subscription for {{name}}? (Past events are kept.)",
|
||||
statusActive: "active",
|
||||
statusSuspended: "suspended",
|
||||
statusRevoked: "revoked",
|
||||
|
||||
+46
-15
@@ -24,7 +24,7 @@ export const sq = {
|
||||
shift: "Turni",
|
||||
setup: "Konfigurimi",
|
||||
tariff: "Tarifa",
|
||||
permits: "Lejet",
|
||||
subscriptions: "Abonimet",
|
||||
site: "Vendi",
|
||||
},
|
||||
status: {
|
||||
@@ -32,16 +32,43 @@ export const sq = {
|
||||
connecting: "DUKE U LIDHUR",
|
||||
offline: "JASHTË LINJE",
|
||||
},
|
||||
devices: {
|
||||
footerTitle: "Pajisjet",
|
||||
none: "Asnjë pajisje e konfiguruar.",
|
||||
catAccess: "Barriera",
|
||||
catReader: "Lexuesi",
|
||||
catCamera: "Kamera",
|
||||
catPrinter: "Printer",
|
||||
// Role/direction suffixes for the chip label (e.g. "Lexuesi hyrje").
|
||||
role: {
|
||||
entry: "hyrje",
|
||||
exit: "dalje",
|
||||
both: "hyrje/dalje",
|
||||
mixed: "i përzier",
|
||||
lane: "korsia",
|
||||
booth: "kabina",
|
||||
},
|
||||
state: {
|
||||
ready: "gati",
|
||||
degraded: "i dëmtuar",
|
||||
offline: "jashtë linje",
|
||||
},
|
||||
allOk: "të gjitha gati",
|
||||
issuesCount: "{{count}} me probleme",
|
||||
issuesTitle: "Problemet e pajisjeve",
|
||||
clickForIssues: "Kliko për detajet",
|
||||
checkedAt: "kontrolluar {{time}}",
|
||||
},
|
||||
booth: {
|
||||
processTicket: "Proceso biletën",
|
||||
scanPlaceholder: "Skano ose shkruaj numrin e biletës…",
|
||||
open: "Hap",
|
||||
occupancy: "Zënia",
|
||||
occupancy: "Prania",
|
||||
occUnavailable: "zënia e padisponueshme",
|
||||
inside: "brenda",
|
||||
of: "nga",
|
||||
uncapped: "pa kufi",
|
||||
free: "lirë",
|
||||
free: "Vende të lira",
|
||||
lotFull: "● parkimi plot",
|
||||
liveFeed: "Aktiviteti live",
|
||||
events: "ngjarje",
|
||||
@@ -94,21 +121,25 @@ export const sq = {
|
||||
publishing: "Duke publikuar…",
|
||||
publishedOk: "U publikua versioni i ri i tarifës — tani është karta tarifore aktive.",
|
||||
},
|
||||
permits: {
|
||||
title: "Lejet",
|
||||
subs: {
|
||||
title: "Abonimet",
|
||||
unnamed: "(pa emër)",
|
||||
unbound: "pa kufizim",
|
||||
car_one: "{{count}} makinë",
|
||||
car_other: "{{count}} makina",
|
||||
cred: "kredencial",
|
||||
plates: "{{count}} targë(a)",
|
||||
noPrice: "pa çmim",
|
||||
perMonth: "muaj",
|
||||
monthlyPrice: "Çmimi mujor",
|
||||
pricePlaceholder: "p.sh. 10000",
|
||||
edit: "Ndrysho",
|
||||
revoke: "Anulo",
|
||||
delete: "Fshij",
|
||||
noPermitsYet: "Asnjë leje ende.",
|
||||
addPermit: "+ Shto leje",
|
||||
newPermit: "Leje e re",
|
||||
editPermit: "Ndrysho lejen",
|
||||
noneYet: "Asnjë abonim ende.",
|
||||
add: "+ Shto abonim",
|
||||
new: "Abonim i ri",
|
||||
editTitle: "Ndrysho abonimin",
|
||||
holderName: "Emri i mbajtësit",
|
||||
contact: "Kontakti",
|
||||
carLimit: "Kufiri i makinave",
|
||||
@@ -123,18 +154,18 @@ export const sq = {
|
||||
qr: "QR",
|
||||
credentialValue: "vlera e kredencialit",
|
||||
addCredential: "+ kredencial",
|
||||
needCredentialOrPlate: "Një leje kërkon të paktën një kredencial OSE një targë të lidhur.",
|
||||
needCredentialOrPlate: "Një abonim kërkon të paktën një kredencial OSE një targë të lidhur.",
|
||||
save: "Ruaj",
|
||||
cancel: "Anulo",
|
||||
permitSaved: "Leja u ruajt.",
|
||||
confirmRevoke: "Të anulohet leja për {{name}}? Do të refuzohet te barriera.",
|
||||
confirmDelete: "Të fshihet leja për {{name}}? (Ngjarjet e kaluara ruhen.)",
|
||||
statusActive: "aktive",
|
||||
saved: "Abonimi u ruajt.",
|
||||
confirmRevoke: "Të anulohet abonimi për {{name}}? Do të refuzohet te barriera.",
|
||||
confirmDelete: "Të fshihet abonimi për {{name}}? (Ngjarjet e kaluara ruhen.)",
|
||||
statusActive: "aktiv",
|
||||
statusSuspended: "pezulluar",
|
||||
statusRevoked: "anuluar",
|
||||
},
|
||||
site: {
|
||||
occupancy: "Zënia:",
|
||||
occupancy: "Prania:",
|
||||
noCapacitySet: "(pa kapacitet të caktuar)",
|
||||
free: "lirë",
|
||||
full: "PLOT",
|
||||
|
||||
Reference in New Issue
Block a user