96a73e22b2
Erweitert das Lizenzmodul um eine Vertriebs-/Vergabe-Schicht ueber dem bestehenden Einloese-Flow (non-breaking, alle Alt-Codes + generate/redeem /assign unveraendert). Schema (Migration 2026-08-26-lizenz-vertrieb.sql, additiv): - license_customers (Besteller: Kundennummer/Email/Name) - license_batches (Bestellung/Charge: externe Bestellnummer, Typ, Menge, Gueltigkeit, Sammel-Einloesetoken, Storno) - licenses += batch_id, issued_at, valid_from/until (pro Code -> Verlaengerung), canceled_at/reason; code auf VARCHAR(32) (laengere Codes) - license_mails (Mail-Archiv, unabh. vom IMAP), license_redeem_attempts (Brute-Force-Schutz), license_events (Audit-Log) Backend (php/api/licenses.php): - issue_batch: N frische 100-Bit-Codes in Transaktion, sofort vergeben, an Kunde/Bestellung/Gueltigkeit gebunden; sticky Default Gueltig-bis - Uebersicht (view=batches mit Aggregat ausgegeben/storniert/netto/eingeloest /abgelaufen), batch_detail, Kunden-Autocomplete, sales_default - cancel_batch/cancel_license, extend_batch/extend_license - send_batch_mail (Server-Treiber via Mailer, archiviert, Anhang bei >40) - redeem_charge (Sammel-Einloeselink) - Brute-Force-Bremse + Audit + Storno-Ablehnung im redeem/redeem_batch UI (admin-licenses.html): Ausgabe-Formular (Bestaetigung bei Grossmengen), Ergebnis mit Copy/Download(txt+csv)/Mail-Composer/Sammellink, Bestellungs- Uebersicht mit Filtern + Detail-Modal (Storno/Verlaengern/Verlauf/Mails). Einloese-Landingpage (einloesen.html + pages/einloesen.php): ?charge=Token (alle auf einmal) und ?code=CODE, mit Lehrer-Login-Gate. Lib: Database Transaktionen; Mailer sendWithAttachments + renderPlainBody. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
104 lines
5.2 KiB
SQL
104 lines
5.2 KiB
SQL
-- =====================================================================
|
|
-- Lizenz-Vergabe / Vertrieb (2026-08-26)
|
|
-- Additiv & non-breaking: bestehende `licenses`-Zeilen (batch_id NULL) und
|
|
-- die Flows generate/redeem/assign bleiben unveraendert funktionsfaehig.
|
|
-- Neu: Kunden, Chargen (Bestellungen), Mail-Archiv, Storno, Verlaengerung,
|
|
-- Brute-Force-Schutz + Audit-Log, laengere Codes, Charge-Einloeselink.
|
|
-- =====================================================================
|
|
|
|
-- 1) Kunden (Besteller) ------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS license_customers (
|
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
customer_no VARCHAR(40) NULL, -- INTERNE Kundennummer (optional)
|
|
email VARCHAR(190) NULL,
|
|
name VARCHAR(190) NULL, -- Besteller / Verlag / Schule
|
|
note VARCHAR(255) NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
KEY idx_customer_no (customer_no),
|
|
KEY idx_email (email),
|
|
KEY idx_name (name)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- 2) Chargen (Bestellungen) -------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS license_batches (
|
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
customer_id INT UNSIGNED NULL,
|
|
order_no VARCHAR(120) NULL, -- EXTERNE Bestellnummer (Marktplatz/Ministerium), Freitext -> geht in die Mail
|
|
sales_type VARCHAR(30) NOT NULL DEFAULT 'privat', -- marktplatz | direktvertrieb | privat (spaeter erweiterbar)
|
|
qty INT UNSIGNED NOT NULL DEFAULT 0,
|
|
default_valid_from DATE NULL,
|
|
default_valid_until DATE NULL,
|
|
note VARCHAR(255) NULL,
|
|
redeem_token CHAR(40) NULL, -- Sammel-Einloeselink der ganzen Charge
|
|
created_by INT UNSIGNED NULL, -- admin_users.id
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
canceled_at TIMESTAMP NULL,
|
|
canceled_reason VARCHAR(255) NULL,
|
|
UNIQUE KEY uq_redeem_token (redeem_token),
|
|
KEY idx_customer (customer_id),
|
|
KEY idx_order (order_no),
|
|
KEY idx_type (sales_type),
|
|
KEY idx_created (created_at),
|
|
CONSTRAINT fk_batch_customer FOREIGN KEY (customer_id) REFERENCES license_customers(id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- 3) licenses erweitern ------------------------------------------------
|
|
-- code auf 32 Zeichen verbreitern (laengere Codes), Vertriebsfelder ergaenzen.
|
|
ALTER TABLE licenses
|
|
MODIFY COLUMN code VARCHAR(32) NOT NULL,
|
|
ADD COLUMN batch_id INT UNSIGNED NULL AFTER school_year,
|
|
ADD COLUMN issued_at TIMESTAMP NULL AFTER redeemed_at,
|
|
ADD COLUMN valid_from DATE NULL AFTER issued_at,
|
|
ADD COLUMN valid_until DATE NULL AFTER valid_from,
|
|
ADD COLUMN canceled_at TIMESTAMP NULL AFTER valid_until,
|
|
ADD COLUMN canceled_reason VARCHAR(255) NULL AFTER canceled_at,
|
|
ADD KEY idx_batch (batch_id),
|
|
ADD KEY idx_valid_until (valid_until),
|
|
ADD KEY idx_issued (issued_at),
|
|
ADD CONSTRAINT fk_license_batch FOREIGN KEY (batch_id) REFERENCES license_batches(id) ON DELETE SET NULL;
|
|
|
|
-- 4) Mail-Archiv / Outbox (unabhaengig vom IMAP-Postausgang) -----------
|
|
CREATE TABLE IF NOT EXISTS license_mails (
|
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
batch_id INT UNSIGNED NULL,
|
|
to_email VARCHAR(190) NOT NULL,
|
|
subject VARCHAR(255) NOT NULL,
|
|
body_text MEDIUMTEXT NULL, -- gerenderter Klartext (kopierbar + Archiv)
|
|
body_html MEDIUMTEXT NULL,
|
|
provider VARCHAR(20) NOT NULL DEFAULT 'server', -- server | ses
|
|
status VARCHAR(12) NOT NULL DEFAULT 'queued', -- queued | sent | failed
|
|
error VARCHAR(255) NULL,
|
|
created_by INT UNSIGNED NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
sent_at TIMESTAMP NULL,
|
|
KEY idx_batch (batch_id),
|
|
KEY idx_status (status),
|
|
CONSTRAINT fk_mail_batch FOREIGN KEY (batch_id) REFERENCES license_batches(id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- 5) Brute-Force-Schutz + Audit beim Einloesen -------------------------
|
|
CREATE TABLE IF NOT EXISTS license_redeem_attempts (
|
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
ip VARBINARY(16) NULL, -- INET6_ATON(...)
|
|
teacher_id INT UNSIGNED NULL,
|
|
code_tried VARCHAR(32) NULL,
|
|
ok TINYINT(1) NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
KEY idx_ip_time (ip, created_at),
|
|
KEY idx_teacher_time (teacher_id, created_at)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- 6) Audit-Log fuer Chargen/Codes -------------------------------------
|
|
CREATE TABLE IF NOT EXISTS license_events (
|
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
batch_id INT UNSIGNED NULL,
|
|
license_id INT UNSIGNED NULL,
|
|
event VARCHAR(30) NOT NULL, -- issued|extended|canceled|mailed|redeemed
|
|
detail VARCHAR(255) NULL,
|
|
admin_id INT UNSIGNED NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
KEY idx_batch (batch_id),
|
|
KEY idx_license (license_id),
|
|
KEY idx_event (event)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|