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>
54 lines
1.5 KiB
PHP
54 lines
1.5 KiB
PHP
<?php
|
|
/**
|
|
* GeoGraSim — Thin Database Wrapper
|
|
*/
|
|
|
|
require_once __DIR__ . '/../config/db.php';
|
|
|
|
class Database {
|
|
private PDO $pdo;
|
|
private static ?Database $instance = null;
|
|
|
|
private function __construct() {
|
|
$this->pdo = getDB();
|
|
}
|
|
|
|
public static function get(): Database {
|
|
if (self::$instance === null) {
|
|
self::$instance = new Database();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
public function query(string $sql, array $params = []): PDOStatement {
|
|
$stmt = $this->pdo->prepare($sql);
|
|
$stmt->execute($params);
|
|
return $stmt;
|
|
}
|
|
|
|
public function fetchOne(string $sql, array $params = []): ?array {
|
|
$stmt = $this->query($sql, $params);
|
|
$row = $stmt->fetch();
|
|
return $row ?: null;
|
|
}
|
|
|
|
public function fetchAll(string $sql, array $params = []): array {
|
|
return $this->query($sql, $params)->fetchAll();
|
|
}
|
|
|
|
public function execute(string $sql, array $params = []): int {
|
|
$stmt = $this->query($sql, $params);
|
|
return $stmt->rowCount();
|
|
}
|
|
|
|
public function lastInsertId(): string {
|
|
return $this->pdo->lastInsertId();
|
|
}
|
|
|
|
// ── Transaktionen (fuer Bulk-Operationen, z.B. Chargen-Vergabe) ──
|
|
public function begin(): void { $this->pdo->beginTransaction(); }
|
|
public function commit(): void { $this->pdo->commit(); }
|
|
public function rollBack(): void { if ($this->pdo->inTransaction()) $this->pdo->rollBack(); }
|
|
public function inTransaction(): bool { return $this->pdo->inTransaction(); }
|
|
}
|