GeΓ―mplementeerde Database Structuur
Status: β
Volledig geΓ―mplementeerd op 18 december 2025
Aanpak: Vereenvoudigd systeem met hergebruik van bestaande orders tabel
1. licence_regio (NIEUW)
CREATE TABLE [dbo].[licence_regio](
[regioID] INT IDENTITY(1,1) PRIMARY KEY,
[regio_code] VARCHAR(20) UNIQUE NOT NULL,
[regioNL] NVARCHAR(100) NOT NULL,
[regioFR] NVARCHAR(100) NOT NULL,
[regioEN] NVARCHAR(100) NULL,
[website] VARCHAR(100) NOT NULL DEFAULT 'syndi.be',
[active] BIT NOT NULL DEFAULT 1,
[sort_order] INT NOT NULL DEFAULT 0,
[created_date] DATETIME NOT NULL DEFAULT GETDATE(),
[modified_date] DATETIME NOT NULL DEFAULT GETDATE()
)
Sample Data (13 regio's):
- BE - BelgiΓ« / Belgique
- ANT - Antwerpen / Anvers
- LIM - Limburg / Limbourg
- OVL - Oost-Vlaanderen / Flandre-Orientale
- WVL - West-Vlaanderen / Flandre-Occidentale
- VBR - Vlaams-Brabant / Brabant flamand
- LUI - Luik / Liège
- LUX - Luxemburg / Luxembourg
- NAM - Namen / Namur
- HEN - Henegouwen / Hainaut
- WBR - Waals-Brabant / Brabant wallon
- BHG - Brussels Hoofdstedelijk Gewest
- NL - Nederland (inactive - toekomst)
2. licence_products (NIEUW)
CREATE TABLE [dbo].[licence_products](
[listID] INT IDENTITY(1,1) PRIMARY KEY,
[product_code] VARCHAR(50) UNIQUE NOT NULL,
[licence_type] VARCHAR(20) NOT NULL, -- 'master', 'executive', 'excel'
[regioID] INT NULL, -- FK, NULL = all regions
[duration_months] INT NOT NULL,
[titelNL] NVARCHAR(200) NOT NULL,
[titelFR] NVARCHAR(200) NOT NULL,
[beschrijvingNL] NVARCHAR(MAX) NULL,
[beschrijvingFR] NVARCHAR(MAX) NULL,
[price_excl_btw] MONEY NOT NULL,
[promo_price_excl_btw] MONEY NULL,
[max_downloads] INT NULL, -- NULL = unlimited
[active] BIT NOT NULL DEFAULT 1,
[visible] BIT NOT NULL DEFAULT 1,
[created_date] DATETIME NOT NULL DEFAULT GETDATE(),
[modified_date] DATETIME NOT NULL DEFAULT GETDATE(),
CONSTRAINT [FK_licence_products_regio] FOREIGN KEY ([regioID])
REFERENCES [dbo].[licence_regio]([regioID])
)
Sample Products (6 producten):
- MASTER-BE-12 - Master BelgiΓ« 1 jaar (β¬1299)
- MASTER-BE-24 - Master BelgiΓ« 2 jaar (β¬2468, promo β¬2340)
- MASTER-ANT-12 - Master Antwerpen 1 jaar (β¬499)
- EXEC-LUI-12 - Executive Luik 1 jaar (β¬299)
- EXCEL-BE-12 - Excel Downloads BelgiΓ« 1 jaar (β¬399, max 100 downloads)
- EXCEL-BE-24 - Excel Downloads BelgiΓ« 2 jaar (β¬699, unlimited)
3. orders (BESTAAND - UITGEBREID)
-- Nieuwe kolommen toegevoegd aan bestaande orders tabel:
ALTER TABLE [dbo].[orders] ADD [listID] INT NULL
ALTER TABLE [dbo].[orders] ADD [regioID] INT NULL
ALTER TABLE [dbo].[orders] ADD [duration_months] INT NULL
ALTER TABLE [dbo].[orders] ADD [start_date] DATE NULL
ALTER TABLE [dbo].[orders] ADD [download_count] INT NOT NULL DEFAULT 0
ALTER TABLE [dbo].[orders] ADD [max_downloads] INT NULL
-- Foreign keys:
CONSTRAINT [FK_orders_licence_products] FOREIGN KEY ([listID])
CONSTRAINT [FK_orders_licence_regio] FOREIGN KEY ([regioID])
Bestaande velden hergebruikt:
orderID - Primary key
clientID - Link naar klant
invoiceID - NULL = onbetaald, NOT NULL = betaald
subscription - Licentie type ('master', 'executive', 'excel')
expirationdate - Einddatum (berekend: start_date + duration_months)
VATpct - BTW percentage (0 of 21)
payment - Betaalstatus ('openstaand', 'mollie', 'factuur')
total - Totaalbedrag incl BTW
4. Hulpmiddelen
View: vw_active_licences
Overzicht van alle actieve licenties met status berekening.
Stored Procedures:
sp_check_licence_access - Controleer of klant toegang heeft tot regio
sp_get_client_licences - Haal alle licenties van klant op
sp_increment_download - Verhoog download teller met limiet check
Belangrijke Beslissingen
Waarom Vereenvoudigd?
- β
Backwards Compatible: Bestaande orders en factuur systeem blijven werken
- β
Geen Data Migratie: Oude orders blijven in dezelfde tabel
- β
Klein Project: ~100 orders/jaar, geen overkill nodig
- β
Simpel Onderhoud: Slechts 2 nieuwe tabellen + 6 kolommen
Actieve Licentie Logica
-- Een licentie is actief als:
WHERE invoiceID IS NOT NULL -- Betaald
AND expirationdate >= GETDATE() -- Niet verlopen
AND listID IS NOT NULL -- Nieuwe licentie order
-- Bij dubbele licenties: neem langst geldende
ORDER BY expirationdate DESC
Implementatie Notities (18 december 2025)
Belangrijke Implementatie Details
Database: kbo (SQL Server)
Scripts uitgevoerd: licence_system_setup.sql
Status: Volledig operationeel en getest
Aanpassingen aan bestaande orders tabel:
listID INT NULL - Link naar product catalogus (licence_products)
regioID INT NULL - Link naar regio (licence_regio)
duration_months INT NULL - Looptijd in maanden (12, 24, 36, 60)
start_date DATE NULL - Startdatum licentie (standaard = orderdate)
download_count INT DEFAULT 0 - Teller voor Excel downloads
max_downloads INT NULL - Limiet downloads (NULL = unlimited)
Waarom NULL values? Oude orders (voor licentiesysteem) hebben deze velden niet. Dit maakt het backwards compatible.
Bestaande velden hergebruikt:
subscription VARCHAR(50) - Nu gebruikt voor licence_type ('master', 'executive', 'excel')
invoiceID INT - NULL = onbetaald, NOT NULL = betaald en actief
expirationdate DATETIME - Berekend als start_date + duration_months
VATpct INT - Altijd 0 of 21 (B2B bepaald bij order aanmaken)
payment VARCHAR(50) - 'openstaand', 'mollie', 'factuur', 'bank'
Quick Reference: Veel Gebruikte Queries
1. Check of klant toegang heeft tot regio
-- Via stored procedure (aanbevolen)
EXEC sp_check_licence_access
@clientID = 123,
@regioID = 2; -- ANT
-- Direct query
SELECT TOP 1 1
FROM orders
WHERE clientID = @clientID
AND regioID = @regioID
AND subscription IN ('master', 'executive')
AND invoiceID IS NOT NULL
AND expirationdate >= GETDATE()
ORDER BY expirationdate DESC;
2. Alle actieve licenties van klant ophalen
-- Via stored procedure
EXEC sp_get_client_licences @clientID = 123;
-- Via view
SELECT * FROM vw_active_licences
WHERE clientID = 123
AND status = 'active'
ORDER BY end_date DESC;
3. Download limiet checken en verhogen
DECLARE @errorMsg NVARCHAR(500);
EXEC sp_increment_download
@orderID = 456,
@errorMessage = @errorMsg OUTPUT;
IF @errorMsg IS NOT NULL
PRINT 'Error: ' + @errorMsg;
ELSE
PRINT 'Download toegestaan';
4. Nieuwe order aanmaken (PHPvoorbeeld)
// Product ophalen uit catalogus
$sql = "SELECT * FROM licence_products WHERE product_code = ?";
$stmt = sqlsrv_query($conn, $sql, ['MASTER-ANT-12']);
$product = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
// Datums berekenen
$startDate = date('Y-m-d');
$durationMonths = $product['duration_months'];
$endDate = date('Y-m-d', strtotime("+{$durationMonths} months"));
// BTW bepalen (0 voor buitenland, 21 voor BelgiΓ«)
$vatPct = validateKBO($vatNumber) ? 21 : 0;
$total = $product['price_excl_btw'] * (1 + $vatPct/100);
// Order insert
$sql = "INSERT INTO orders (
userID, clientID, listID, regioID, subscription,
duration_months, start_date, expirationdate,
orderdate, VATpct, total, product, payment,
max_downloads, quantiy, credits
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, GETDATE(), ?, ?, ?, 'openstaand', ?, 1, 0)";
$params = [
$userID, // ID van de gebruiker die bestelt
$clientID,
$product['listID'],
$product['regioID'],
$product['licence_type'],
$durationMonths,
$startDate,
$endDate,
$vatPct,
$total,
$product['titelNL'],
$product['max_downloads']
];
sqlsrv_query($conn, $sql, $params);
Productbeheer
Nieuw product toevoegen
INSERT INTO licence_products (
product_code, licence_type, regioID, duration_months,
titelNL, titelFR, beschrijvingNL, beschrijvingFR,
price_excl_btw, promo_price_excl_btw, max_downloads
) VALUES (
'MASTER-OVL-12', -- Unieke code
'master', -- Type
4, -- Oost-Vlaanderen (uit licence_regio)
12, -- 1 jaar
'Master Oost-Vlaanderen 1 jaar',
'Master Flandre-Orientale 1 an',
'Volledige toegang tot alle VME''s...',
'Accès complet à toutes les ACP...',
499.00, -- Prijs excl BTW
NULL, -- Geen promo
NULL -- Unlimited downloads
);
Prijs aanpassen
UPDATE licence_products
SET price_excl_btw = 549.00,
promo_price_excl_btw = 499.00,
modified_date = GETDATE()
WHERE product_code = 'MASTER-ANT-12';
Product deactiveren (niet verwijderen!)
UPDATE licence_products
SET active = 0, visible = 0
WHERE product_code = 'OLD-PRODUCT-12';
Troubleshooting & Veelgestelde Vragen
Q: Oude orders werken niet meer?
A: Oude orders (van voor licentiesysteem) hebben listID = NULL. Check altijd:
WHERE (listID IS NOT NULL OR subscription IS NOT NULL)
Q: Klant heeft dubbele licentie voor zelfde regio?
A: Neem altijd de langst geldende met ORDER BY expirationdate DESC. De stored procedures doen dit automatisch.
Q: Download limiet werkt niet?
A: Check of max_downloads is opgeslagen bij order aanmaken (snapshot van product). NULL = unlimited.
Q: BTW berekening klopt niet?
A: BTW wordt bepaald bij order aanmaken (niet bij betaling). Buitenlandse B2B = 0%, Belgische B2B = 21%. Check KBO validatie.
Q: Hoe verlengen van licentie?
A: Maak nieuwe order aan met start_date = oude expirationdate. Beide orders blijven in tabel, langste geldt.
Q: Kan ik products verwijderen?
A: NEE! Nooit verwijderen (bestaande orders verwijzen ernaar). Gebruik active=0 en visible=0 i.p.v. DELETE.
Testing Checklist
Voor Go-Live:
- β Test order aanmaken voor elk product type
- β Test betaling verwerking (invoiceID update)
- β Test toegangscontrole per regio
- β Test download limiet (met en zonder limiet)
- β Test verlopen licentie (expirationdate in verleden)
- β Test dubbele licenties (meerdere voor zelfde regio)
- β Test BTW berekening (0% en 21%)
- β Test meertaligheid (NL/FR product titels)
- β Test oude orders compatibiliteit (listID = NULL)
- β Performance test (1000+ orders query < 100ms)
Migratie Pad (Toekomst)
Voor bestaande klanten met oude subscription veld:
-- Optioneel: Converteer oude orders naar nieuw systeem
-- Alleen uitvoeren als je oude data wilt migreren!
UPDATE orders
SET listID = (
SELECT TOP 1 listID
FROM licence_products
WHERE licence_type = orders.subscription
AND duration_months = 12
),
regioID = (SELECT regioID FROM licence_regio WHERE regio_code = 'BE'),
duration_months = 12,
start_date = orderdate,
download_count = 0
WHERE listID IS NULL
AND subscription IN ('master', 'executive')
AND invoiceID IS NOT NULL;
Waarschuwing: Test dit eerst op development database!
Performance Optimalisatie
-- Indices voor snelle queries (nog toe te voegen indien nodig)
CREATE NONCLUSTERED INDEX IX_orders_active_licences
ON orders (clientID, invoiceID, expirationdate, listID)
INCLUDE (subscription, regioID);
CREATE NONCLUSTERED INDEX IX_orders_region_lookup
ON orders (regioID, subscription, expirationdate)
WHERE invoiceID IS NOT NULL AND listID IS NOT NULL;
CREATE NONCLUSTERED INDEX IX_products_active
ON licence_products (active, visible, licence_type)
INCLUDE (product_code, titelNL, price_excl_btw);