This commit is contained in:
2026-07-11 18:21:33 +02:00
parent cbea23083d
commit 92d94f1c95
129 changed files with 85783 additions and 11294 deletions
-278
View File
@@ -1,278 +0,0 @@
CREATE TABLE IF NOT EXISTS users (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
full_name VARCHAR(160) NOT NULL,
email VARCHAR(190) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
platform_role ENUM('platform_admin', 'user') NOT NULL DEFAULT 'user',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS password_reset_tokens (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
email VARCHAR(190) NOT NULL,
token_hash CHAR(64) NOT NULL UNIQUE,
requested_ip_hash CHAR(64) NULL,
requested_user_agent_hash CHAR(64) NULL,
expires_at DATETIME NOT NULL,
consumed_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_password_reset_user_created (user_id, created_at),
INDEX idx_password_reset_expires (expires_at),
CONSTRAINT fk_password_reset_tokens_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS rate_limits (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
action_key VARCHAR(80) NOT NULL,
bucket_key CHAR(64) NOT NULL,
attempts INT UNSIGNED NOT NULL DEFAULT 0,
window_start DATETIME NOT NULL,
last_attempt_at DATETIME NOT NULL,
blocked_until DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uniq_rate_limit_bucket (action_key, bucket_key),
INDEX idx_rate_limit_blocked_until (blocked_until),
INDEX idx_rate_limit_last_attempt (last_attempt_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS tenants (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(190) NOT NULL,
slug VARCHAR(120) NOT NULL UNIQUE,
plan ENUM('starter', 'team', 'business') NOT NULL DEFAULT 'starter',
status ENUM('trial', 'active', 'suspended') NOT NULL DEFAULT 'trial',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS tenant_domains (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
host VARCHAR(190) NOT NULL UNIQUE,
is_primary TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_tenant_domains_tenant FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS tenant_memberships (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
user_id BIGINT UNSIGNED NOT NULL,
role ENUM('owner', 'admin', 'member', 'device') NOT NULL DEFAULT 'member',
status ENUM('active', 'invited', 'disabled') NOT NULL DEFAULT 'active',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uniq_tenant_user (tenant_id, user_id),
CONSTRAINT fk_memberships_tenant FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
CONSTRAINT fk_memberships_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS tenant_settings (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
currency_code CHAR(3) NOT NULL DEFAULT 'EUR',
default_product_price_cents INT NOT NULL DEFAULT 120,
allow_self_service TINYINT(1) NOT NULL DEFAULT 1,
allow_paper_lists TINYINT(1) NOT NULL DEFAULT 1,
allow_rfid TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uniq_tenant_settings (tenant_id),
CONSTRAINT fk_tenant_settings_tenant FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS members (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
user_id BIGINT UNSIGNED NULL,
display_name VARCHAR(160) NOT NULL,
email VARCHAR(190) NULL,
access_pin VARCHAR(255) NULL,
status ENUM('active', 'archived') NOT NULL DEFAULT 'active',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uniq_member_email (tenant_id, email),
CONSTRAINT fk_members_tenant FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
CONSTRAINT fk_members_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS products (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
name VARCHAR(120) NOT NULL,
sku VARCHAR(60) NULL,
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_products_tenant FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS product_prices (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
product_id BIGINT UNSIGNED NOT NULL,
price_cents INT NOT NULL,
valid_from DATETIME NOT NULL,
valid_until DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_product_prices_tenant FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
CONSTRAINT fk_product_prices_product FOREIGN KEY (product_id) REFERENCES products (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS capture_sources (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
code VARCHAR(60) NOT NULL,
name VARCHAR(120) NOT NULL,
channel ENUM('paper', 'digital', 'admin', 'rfid') NOT NULL,
is_system TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uniq_capture_source_code (tenant_id, code),
CONSTRAINT fk_capture_sources_tenant FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS paper_sheets (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
title VARCHAR(190) NOT NULL,
period_label VARCHAR(120) NULL,
period_start DATE NULL,
period_end DATE NULL,
status ENUM('draft', 'posted') NOT NULL DEFAULT 'draft',
notes TEXT NULL,
created_by_user_id BIGINT UNSIGNED NULL,
posted_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_paper_sheets_tenant FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
CONSTRAINT fk_paper_sheets_user FOREIGN KEY (created_by_user_id) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS paper_sheet_lines (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
paper_sheet_id BIGINT UNSIGNED NOT NULL,
tenant_id BIGINT UNSIGNED NOT NULL,
member_id BIGINT UNSIGNED NOT NULL,
product_id BIGINT UNSIGNED NOT NULL,
quantity DECIMAL(8,2) NOT NULL DEFAULT 1.00,
effective_at DATETIME NULL,
note VARCHAR(255) NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_paper_sheet_lines_sheet FOREIGN KEY (paper_sheet_id) REFERENCES paper_sheets (id) ON DELETE CASCADE,
CONSTRAINT fk_paper_sheet_lines_tenant FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
CONSTRAINT fk_paper_sheet_lines_member FOREIGN KEY (member_id) REFERENCES members (id) ON DELETE CASCADE,
CONSTRAINT fk_paper_sheet_lines_product FOREIGN KEY (product_id) REFERENCES products (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS consumption_events (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
member_id BIGINT UNSIGNED NOT NULL,
product_id BIGINT UNSIGNED NOT NULL,
source_id BIGINT UNSIGNED NOT NULL,
paper_sheet_line_id BIGINT UNSIGNED NULL,
quantity DECIMAL(8,2) NOT NULL DEFAULT 1.00,
unit_price_cents INT NOT NULL,
total_cents INT NOT NULL,
effective_at DATETIME NOT NULL,
recorded_at DATETIME NOT NULL,
recorded_by_user_id BIGINT UNSIGNED NULL,
source_reference VARCHAR(120) NULL,
notes TEXT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_consumption_events_tenant FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
CONSTRAINT fk_consumption_events_member FOREIGN KEY (member_id) REFERENCES members (id) ON DELETE CASCADE,
CONSTRAINT fk_consumption_events_product FOREIGN KEY (product_id) REFERENCES products (id) ON DELETE CASCADE,
CONSTRAINT fk_consumption_events_source FOREIGN KEY (source_id) REFERENCES capture_sources (id) ON DELETE CASCADE,
CONSTRAINT fk_consumption_events_sheet_line FOREIGN KEY (paper_sheet_line_id) REFERENCES paper_sheet_lines (id) ON DELETE SET NULL,
CONSTRAINT fk_consumption_events_recorded_by FOREIGN KEY (recorded_by_user_id) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS ledger_entries (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
member_id BIGINT UNSIGNED NULL,
source_id BIGINT UNSIGNED NULL,
type ENUM('consumption', 'payment', 'correction', 'credit', 'opening_balance') NOT NULL,
reference_type VARCHAR(60) NULL,
reference_id BIGINT UNSIGNED NULL,
description VARCHAR(255) NOT NULL,
occurred_at DATETIME NOT NULL,
created_by_user_id BIGINT UNSIGNED NULL,
reversal_of_entry_id BIGINT UNSIGNED NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_ledger_entries_tenant FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
CONSTRAINT fk_ledger_entries_member FOREIGN KEY (member_id) REFERENCES members (id) ON DELETE SET NULL,
CONSTRAINT fk_ledger_entries_source FOREIGN KEY (source_id) REFERENCES capture_sources (id) ON DELETE SET NULL,
CONSTRAINT fk_ledger_entries_user FOREIGN KEY (created_by_user_id) REFERENCES users (id) ON DELETE SET NULL,
CONSTRAINT fk_ledger_entries_reversal FOREIGN KEY (reversal_of_entry_id) REFERENCES ledger_entries (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS ledger_lines (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
ledger_entry_id BIGINT UNSIGNED NOT NULL,
tenant_id BIGINT UNSIGNED NOT NULL,
member_id BIGINT UNSIGNED NULL,
account_code ENUM('member_balance', 'cashbox', 'coffee_revenue') NOT NULL,
amount_cents INT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_ledger_lines_entry FOREIGN KEY (ledger_entry_id) REFERENCES ledger_entries (id) ON DELETE CASCADE,
CONSTRAINT fk_ledger_lines_tenant FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
CONSTRAINT fk_ledger_lines_member FOREIGN KEY (member_id) REFERENCES members (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS rfid_devices (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
name VARCHAR(120) NOT NULL,
location_label VARCHAR(160) NULL,
device_token VARCHAR(80) NOT NULL UNIQUE,
status ENUM('planned', 'active', 'disabled') NOT NULL DEFAULT 'planned',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_rfid_devices_tenant FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS rfid_tags (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
member_id BIGINT UNSIGNED NULL,
uid_hash CHAR(64) NOT NULL,
label VARCHAR(120) NULL,
status ENUM('active', 'lost', 'retired') NOT NULL DEFAULT 'active',
last_seen_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uniq_rfid_tag (tenant_id, uid_hash),
CONSTRAINT fk_rfid_tags_tenant FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
CONSTRAINT fk_rfid_tags_member FOREIGN KEY (member_id) REFERENCES members (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS rfid_events (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
device_id BIGINT UNSIGNED NULL,
external_event_id VARCHAR(120) NULL,
uid_hash CHAR(64) NOT NULL,
payload_json JSON NULL,
status ENUM('received', 'linked', 'ignored', 'error') NOT NULL DEFAULT 'received',
received_at DATETIME NOT NULL,
processed_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uniq_rfid_external_event (tenant_id, external_event_id),
CONSTRAINT fk_rfid_events_tenant FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
CONSTRAINT fk_rfid_events_device FOREIGN KEY (device_id) REFERENCES rfid_devices (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS audit_events (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NULL,
actor_user_id BIGINT UNSIGNED NULL,
action VARCHAR(120) NOT NULL,
target_type VARCHAR(80) NOT NULL,
target_id BIGINT UNSIGNED NULL,
result ENUM('success', 'failure') NOT NULL,
request_id CHAR(36) NOT NULL,
ip_hash CHAR(64) NULL,
user_agent_hash CHAR(64) NULL,
meta_json JSON NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_audit_tenant_created (tenant_id, created_at),
CONSTRAINT fk_audit_events_tenant FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE SET NULL,
CONSTRAINT fk_audit_events_user FOREIGN KEY (actor_user_id) REFERENCES users (id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-278
View File
@@ -1,278 +0,0 @@
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
full_name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
platform_role TEXT NOT NULL DEFAULT 'user' CHECK(platform_role IN ('platform_admin', 'user')),
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS password_reset_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
email TEXT NOT NULL,
token_hash TEXT NOT NULL UNIQUE,
requested_ip_hash TEXT,
requested_user_agent_hash TEXT,
expires_at TEXT NOT NULL,
consumed_at TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_password_reset_user_created ON password_reset_tokens (user_id, created_at);
CREATE INDEX IF NOT EXISTS idx_password_reset_expires ON password_reset_tokens (expires_at);
CREATE TABLE IF NOT EXISTS rate_limits (
id INTEGER PRIMARY KEY AUTOINCREMENT,
action_key TEXT NOT NULL,
bucket_key TEXT NOT NULL,
attempts INTEGER NOT NULL DEFAULT 0,
window_start TEXT NOT NULL,
last_attempt_at TEXT NOT NULL,
blocked_until TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE (action_key, bucket_key)
);
CREATE INDEX IF NOT EXISTS idx_rate_limit_blocked_until ON rate_limits (blocked_until);
CREATE INDEX IF NOT EXISTS idx_rate_limit_last_attempt ON rate_limits (last_attempt_at);
CREATE TABLE IF NOT EXISTS tenants (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
slug TEXT NOT NULL UNIQUE,
plan TEXT NOT NULL DEFAULT 'starter' CHECK(plan IN ('starter', 'team', 'business')),
status TEXT NOT NULL DEFAULT 'trial' CHECK(status IN ('trial', 'active', 'suspended')),
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS tenant_domains (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id INTEGER NOT NULL,
host TEXT NOT NULL UNIQUE,
is_primary INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS tenant_memberships (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
role TEXT NOT NULL DEFAULT 'member' CHECK(role IN ('owner', 'admin', 'member', 'device')),
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'invited', 'disabled')),
created_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE (tenant_id, user_id),
FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS tenant_settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id INTEGER NOT NULL UNIQUE,
currency_code TEXT NOT NULL DEFAULT 'EUR',
default_product_price_cents INTEGER NOT NULL DEFAULT 120,
allow_self_service INTEGER NOT NULL DEFAULT 1,
allow_paper_lists INTEGER NOT NULL DEFAULT 1,
allow_rfid INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS members (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id INTEGER NOT NULL,
user_id INTEGER,
display_name TEXT NOT NULL,
email TEXT,
access_pin TEXT,
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'archived')),
created_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE (tenant_id, email),
FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id INTEGER NOT NULL,
name TEXT NOT NULL,
sku TEXT,
is_active INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS product_prices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
price_cents INTEGER NOT NULL,
valid_from TEXT NOT NULL,
valid_until TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products (id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS capture_sources (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id INTEGER NOT NULL,
code TEXT NOT NULL,
name TEXT NOT NULL,
channel TEXT NOT NULL CHECK(channel IN ('paper', 'digital', 'admin', 'rfid')),
is_system INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE (tenant_id, code),
FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS paper_sheets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id INTEGER NOT NULL,
title TEXT NOT NULL,
period_label TEXT,
period_start TEXT,
period_end TEXT,
status TEXT NOT NULL DEFAULT 'draft' CHECK(status IN ('draft', 'posted')),
notes TEXT,
created_by_user_id INTEGER,
posted_at TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
FOREIGN KEY (created_by_user_id) REFERENCES users (id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS paper_sheet_lines (
id INTEGER PRIMARY KEY AUTOINCREMENT,
paper_sheet_id INTEGER NOT NULL,
tenant_id INTEGER NOT NULL,
member_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity REAL NOT NULL DEFAULT 1.00,
effective_at TEXT,
note TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (paper_sheet_id) REFERENCES paper_sheets (id) ON DELETE CASCADE,
FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
FOREIGN KEY (member_id) REFERENCES members (id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products (id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS consumption_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id INTEGER NOT NULL,
member_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
source_id INTEGER NOT NULL,
paper_sheet_line_id INTEGER,
quantity REAL NOT NULL DEFAULT 1.00,
unit_price_cents INTEGER NOT NULL,
total_cents INTEGER NOT NULL,
effective_at TEXT NOT NULL,
recorded_at TEXT NOT NULL,
recorded_by_user_id INTEGER,
source_reference TEXT,
notes TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
FOREIGN KEY (member_id) REFERENCES members (id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products (id) ON DELETE CASCADE,
FOREIGN KEY (source_id) REFERENCES capture_sources (id) ON DELETE CASCADE,
FOREIGN KEY (paper_sheet_line_id) REFERENCES paper_sheet_lines (id) ON DELETE SET NULL,
FOREIGN KEY (recorded_by_user_id) REFERENCES users (id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS ledger_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id INTEGER NOT NULL,
member_id INTEGER,
source_id INTEGER,
type TEXT NOT NULL CHECK(type IN ('consumption', 'payment', 'correction', 'credit', 'opening_balance')),
reference_type TEXT,
reference_id INTEGER,
description TEXT NOT NULL,
occurred_at TEXT NOT NULL,
created_by_user_id INTEGER,
reversal_of_entry_id INTEGER,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
FOREIGN KEY (member_id) REFERENCES members (id) ON DELETE SET NULL,
FOREIGN KEY (source_id) REFERENCES capture_sources (id) ON DELETE SET NULL,
FOREIGN KEY (created_by_user_id) REFERENCES users (id) ON DELETE SET NULL,
FOREIGN KEY (reversal_of_entry_id) REFERENCES ledger_entries (id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS ledger_lines (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ledger_entry_id INTEGER NOT NULL,
tenant_id INTEGER NOT NULL,
member_id INTEGER,
account_code TEXT NOT NULL CHECK(account_code IN ('member_balance', 'cashbox', 'coffee_revenue')),
amount_cents INTEGER NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (ledger_entry_id) REFERENCES ledger_entries (id) ON DELETE CASCADE,
FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
FOREIGN KEY (member_id) REFERENCES members (id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS rfid_devices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id INTEGER NOT NULL,
name TEXT NOT NULL,
location_label TEXT,
device_token TEXT NOT NULL UNIQUE,
status TEXT NOT NULL DEFAULT 'planned' CHECK(status IN ('planned', 'active', 'disabled')),
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS rfid_tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id INTEGER NOT NULL,
member_id INTEGER,
uid_hash TEXT NOT NULL,
label TEXT,
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'lost', 'retired')),
last_seen_at TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE (tenant_id, uid_hash),
FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
FOREIGN KEY (member_id) REFERENCES members (id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS rfid_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id INTEGER NOT NULL,
device_id INTEGER,
external_event_id TEXT,
uid_hash TEXT NOT NULL,
payload_json TEXT,
status TEXT NOT NULL DEFAULT 'received' CHECK(status IN ('received', 'linked', 'ignored', 'error')),
received_at TEXT NOT NULL,
processed_at TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE (tenant_id, external_event_id),
FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE,
FOREIGN KEY (device_id) REFERENCES rfid_devices (id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS audit_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id INTEGER,
actor_user_id INTEGER,
action TEXT NOT NULL,
target_type TEXT NOT NULL,
target_id INTEGER,
result TEXT NOT NULL CHECK(result IN ('success', 'failure')),
request_id TEXT NOT NULL,
ip_hash TEXT,
user_agent_hash TEXT,
meta_json TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE SET NULL,
FOREIGN KEY (actor_user_id) REFERENCES users (id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_audit_tenant_created ON audit_events (tenant_id, created_at);