Files
crm/schema.sql
T

307 lines
10 KiB
SQL

CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT
);
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE COLLATE NOCASE,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'user',
active INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL
);
-- Relaties (zoals accounts in EspoCRM): klantbundels (huishouden, familie, bedrijf) en partners
-- (leverancier, aannemer, ...). Contactpersonen hangen eronder met een rol.
CREATE TABLE IF NOT EXISTS relations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
kind TEXT NOT NULL DEFAULT 'klant',
type TEXT DEFAULT '',
name TEXT NOT NULL,
email TEXT DEFAULT '',
phone TEXT DEFAULT '',
website TEXT DEFAULT '',
address TEXT DEFAULT '',
postcode TEXT DEFAULT '',
city TEXT DEFAULT '',
kvk TEXT DEFAULT '',
notes TEXT DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_relations_name ON relations(kind, name COLLATE NOCASE);
CREATE TABLE IF NOT EXISTS contacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL DEFAULT '',
email TEXT DEFAULT '',
phone TEXT DEFAULT '',
company TEXT DEFAULT '',
address TEXT DEFAULT '',
postcode TEXT DEFAULT '',
city TEXT DEFAULT '',
type TEXT NOT NULL DEFAULT 'lead',
status TEXT NOT NULL DEFAULT 'nieuw',
source TEXT DEFAULT '',
value REAL NOT NULL DEFAULT 0,
assigned_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
relation_id INTEGER REFERENCES relations(id) ON DELETE SET NULL,
relation_role TEXT DEFAULT '',
portal_token TEXT,
portal_password_hash TEXT,
portal_last_login TEXT,
notes TEXT DEFAULT '',
extra TEXT DEFAULT '{}',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_contacts_email ON contacts(email COLLATE NOCASE);
CREATE TABLE IF NOT EXISTS activities (
id INTEGER PRIMARY KEY AUTOINCREMENT,
contact_id INTEGER NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
type TEXT NOT NULL,
body TEXT DEFAULT '',
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
contact_id INTEGER REFERENCES contacts(id) ON DELETE CASCADE,
stored TEXT NOT NULL,
original TEXT NOT NULL,
mime TEXT DEFAULT '',
size INTEGER DEFAULT 0,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
sku TEXT DEFAULT '',
description TEXT DEFAULT '',
unit TEXT DEFAULT 'stuk',
supplier TEXT DEFAULT '',
supplier_id INTEGER REFERENCES relations(id) ON DELETE SET NULL,
price REAL NOT NULL DEFAULT 0,
vat REAL NOT NULL DEFAULT 21,
image TEXT DEFAULT '',
active INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT
);
-- Elke wijziging aan een product (handmatig, import of API), zodat prijsmutaties terug te zien zijn.
CREATE TABLE IF NOT EXISTS product_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
product_id INTEGER NOT NULL REFERENCES products(id) ON DELETE CASCADE,
field TEXT NOT NULL,
old_value TEXT,
new_value TEXT,
source TEXT DEFAULT '',
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_product_history ON product_history(product_id, created_at);
CREATE TABLE IF NOT EXISTS quotes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
number TEXT UNIQUE NOT NULL,
contact_id INTEGER REFERENCES contacts(id) ON DELETE SET NULL,
status TEXT NOT NULL DEFAULT 'concept',
title TEXT DEFAULT '',
date TEXT NOT NULL,
valid_until TEXT,
intro TEXT DEFAULT '',
notes TEXT DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS quote_lines (
id INTEGER PRIMARY KEY AUTOINCREMENT,
quote_id INTEGER NOT NULL REFERENCES quotes(id) ON DELETE CASCADE,
position INTEGER NOT NULL DEFAULT 0,
product_id INTEGER,
description TEXT NOT NULL DEFAULT '',
qty REAL NOT NULL DEFAULT 1,
unit TEXT DEFAULT '',
price REAL NOT NULL DEFAULT 0,
vat REAL NOT NULL DEFAULT 21
);
CREATE TABLE IF NOT EXISTS emails (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uid TEXT UNIQUE,
message_id TEXT DEFAULT '',
direction TEXT NOT NULL DEFAULT 'in',
contact_id INTEGER REFERENCES contacts(id) ON DELETE SET NULL,
from_name TEXT DEFAULT '',
from_addr TEXT DEFAULT '',
to_addr TEXT DEFAULT '',
subject TEXT DEFAULT '',
body TEXT DEFAULT '',
date TEXT NOT NULL,
is_read INTEGER NOT NULL DEFAULT 0,
archived INTEGER NOT NULL DEFAULT 0,
cc TEXT DEFAULT '',
attachments TEXT DEFAULT ''
);
CREATE INDEX IF NOT EXISTS idx_emails_from ON emails(from_addr);
-- E-mailtemplates: onderwerp en tekst met velden als {voornaam}, ingevuld bij het opstellen of versturen
-- (MAIL_FIELDS in app.py).
CREATE TABLE IF NOT EXISTS email_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
subject TEXT DEFAULT '',
body TEXT DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
contact_id INTEGER REFERENCES contacts(id) ON DELETE CASCADE,
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
title TEXT NOT NULL,
due_date TEXT,
done INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL
);
-- Agenda: één agenda per gebruiker, ook via CalDAV (/dav/). `ics` is het kalenderobject zoals een
-- agenda-app het stuurde (of zoals de CRM het maakte), de losse kolommen zijn daaruit gehaald voor weergave.
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
contact_id INTEGER REFERENCES contacts(id) ON DELETE SET NULL,
uid TEXT NOT NULL,
href TEXT NOT NULL,
title TEXT NOT NULL DEFAULT '',
location TEXT DEFAULT '',
description TEXT DEFAULT '',
starts_at TEXT NOT NULL,
ends_at TEXT NOT NULL,
all_day INTEGER NOT NULL DEFAULT 0,
rrule TEXT DEFAULT '',
exdates TEXT DEFAULT '',
ics TEXT NOT NULL,
etag TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_events_href ON events(user_id, href);
CREATE UNIQUE INDEX IF NOT EXISTS idx_events_uid ON events(user_id, uid);
CREATE INDEX IF NOT EXISTS idx_events_start ON events(starts_at);
CREATE INDEX IF NOT EXISTS idx_events_contact ON events(contact_id);
-- Mailboxen: elk een eigen IMAP/SMTP-account (info@, facturen@, nieuwsbrief@, persoonlijk). purpose = rol
-- (MAILBOX_PURPOSES in app.py); toegang via mailbox_users, zonder regels is de mailbox gedeeld met iedereen.
CREATE TABLE IF NOT EXISTS mailboxes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
purpose TEXT NOT NULL DEFAULT 'inbox',
active INTEGER NOT NULL DEFAULT 1,
notify INTEGER NOT NULL DEFAULT 1,
imap_host TEXT DEFAULT '', imap_port TEXT DEFAULT '', imap_security TEXT DEFAULT '', imap_user TEXT DEFAULT '',
imap_pass TEXT DEFAULT '', imap_folder TEXT DEFAULT '', imap_days TEXT DEFAULT '',
smtp_host TEXT DEFAULT '', smtp_port TEXT DEFAULT '', smtp_security TEXT DEFAULT '', smtp_user TEXT DEFAULT '',
smtp_pass TEXT DEFAULT '', smtp_sent_folder TEXT DEFAULT '', smtp_from TEXT DEFAULT '',
signature TEXT DEFAULT '',
last_sync TEXT,
last_error TEXT DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS mailbox_users (
mailbox_id INTEGER NOT NULL REFERENCES mailboxes(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
PRIMARY KEY (mailbox_id, user_id)
);
-- Meldingen per gebruiker (bel in de bovenbalk); url = waar de melding heen leidt.
CREATE TABLE IF NOT EXISTS notifications (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
kind TEXT DEFAULT '',
title TEXT NOT NULL,
body TEXT DEFAULT '',
url TEXT DEFAULT '',
created_at TEXT NOT NULL,
read_at TEXT
);
CREATE INDEX IF NOT EXISTS idx_notifications_user ON notifications(user_id, read_at);
-- Routines (jobs.py): een type met eigen instellingen (config, JSON) en een interval in minuten.
-- running_since = slot tegen dubbel draaien.
CREATE TABLE IF NOT EXISTS jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
kind TEXT NOT NULL,
name TEXT NOT NULL,
config TEXT NOT NULL DEFAULT '{}',
interval_minutes INTEGER NOT NULL DEFAULT 60,
active INTEGER NOT NULL DEFAULT 1,
next_run TEXT,
last_run TEXT,
last_status TEXT DEFAULT '',
last_message TEXT DEFAULT '',
running_since TEXT,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS job_runs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
job_id INTEGER NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
started_at TEXT NOT NULL,
finished_at TEXT,
status TEXT NOT NULL,
message TEXT DEFAULT '',
manual INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_job_runs_job ON job_runs(job_id, started_at);
-- Segmenten: een opgeslagen selectie van contacten (filters als JSON, zie segment_where in app.py).
CREATE TABLE IF NOT EXISTS segments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
filters TEXT NOT NULL DEFAULT '{}',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
-- Nieuwsbrieven (bulkmail): bij het inplannen worden de ontvangers uit het segment vastgelegd; de routine
-- "Nieuwsbrieven versturen" verstuurt ze per batch. base_url = adres van de CRM voor de afmeldlink.
CREATE TABLE IF NOT EXISTS campaigns (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
mailbox_id INTEGER REFERENCES mailboxes(id) ON DELETE SET NULL,
segment_id INTEGER REFERENCES segments(id) ON DELETE SET NULL,
subject TEXT DEFAULT '',
body TEXT DEFAULT '',
status TEXT NOT NULL DEFAULT 'concept',
scheduled_at TEXT,
started_at TEXT,
finished_at TEXT,
base_url TEXT DEFAULT '',
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS campaign_recipients (
id INTEGER PRIMARY KEY AUTOINCREMENT,
campaign_id INTEGER NOT NULL REFERENCES campaigns(id) ON DELETE CASCADE,
contact_id INTEGER REFERENCES contacts(id) ON DELETE SET NULL,
email TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'wacht',
error TEXT DEFAULT '',
sent_at TEXT,
UNIQUE (campaign_id, email)
);
CREATE INDEX IF NOT EXISTS idx_campaign_recipients ON campaign_recipients(campaign_id, status);