feat(subscription): QR credentials — operator-choose (QR-only now), auto-generate, multi-month, printed card
Builds out subscription credentials on top of the rename.
- Operator chooses the credential type; only QR is live (RFID shown disabled
"soon"). Backend/schema keep accepting both — re-enabling RFID is UI-only.
- QR codes are AUTO-GENERATED server-side (SUB-<base32>, crypto-random,
globally-unique-checked) — the customer/operator never picks the value.
RF stays operator-entered (the physical card id). Reader output decided =
TCP/IP full string (Wiegand-numeric fallback noted).
- Multi-month: form takes a `months` count → server sets validTo =
validFrom + N months (day-clamp); one record/one window; total = N×monthly.
- The QR card is PRINTED so the operator can hand it over: real ESC/POS 2D QR
(GS ( k) added to the Rongta driver (printSubscriptionCard); auto-print on
create (best-effort — never fails the create; returns {printed,printError})
+ reprint via POST /api/subscriptions/:id/print and a "Print code" button.
Verified via buildServer+inject incl. a TCP capture of the on-wire QR bytes
(autogen+uniqueness, Jan31+3mo→Apr30, auto-print, GS ( k QR with embedded
code, reprint, no-QR→409). Updated wiki (subscription, rongta-printer). No
migration.
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -209,12 +209,28 @@ export interface TicketData {
|
||||
readonly header?: TicketHeader;
|
||||
}
|
||||
|
||||
/** A subscription card: the customer's keepsake, printed at the booth on creation
|
||||
* (and re-printable). The driver renders the `code` as a SCANNABLE QR (the
|
||||
* subscriber scans it every entry/exit) plus the code as text + the holder/validity.
|
||||
* See wiki/entities/subscription.md. */
|
||||
export interface SubscriptionCardData {
|
||||
/** The credential value to encode in the QR (e.g. "SUB-…"). */
|
||||
readonly code: string;
|
||||
readonly holderName?: string | null;
|
||||
/** Coverage window, for the printed card (human-readable already, or ISO). */
|
||||
readonly validFrom?: string | null;
|
||||
readonly validTo?: string | null;
|
||||
readonly header?: TicketHeader;
|
||||
}
|
||||
|
||||
export interface PrinterDevice extends Device {
|
||||
printTicket(data: TicketData): Promise<void>;
|
||||
/** Print a free-form text report (a shift Z-report, a receipt). `lines` are
|
||||
* printed as-is; the driver adds a header/cut. Kept generic so the business
|
||||
* layer composes the content. See wiki/concepts/shift.md. */
|
||||
printReport(report: PrintReport): Promise<void>;
|
||||
/** Print a subscription card: a scannable QR of the code + holder/validity. */
|
||||
printSubscriptionCard(data: SubscriptionCardData): Promise<void>;
|
||||
}
|
||||
|
||||
export interface PrintReport {
|
||||
|
||||
Reference in New Issue
Block a user