Skip to main content

Loyalty & Ügyfélkezelő Rendszer — Tervezési Dokumentáció

Áttekintés

A Vecton platform moduláris hűség- és ügyfélkezelő rendszere, amely lehetővé teszi a tenantek számára, hogy saját igényeik szerint konfigurálják az ügyfélélményt. A rendszer építőkocka-elven működik: minden modul önállóan ki-be kapcsolható, és csak az aktív modulok jelennek meg az admin felületen és az API-ban.

Modulok

Loyalty & Ügyfélkezelő Engine
├── Customer Identity — Ügyfél-azonosítók, kártyaszámok
├── Customer Groups — Ügyfélcsoportok, árlisták
├── Points Program — Hűségpont gyűjtés & beváltás
├── Tier System — Törzsvásárlói szintrendszer
├── Store Credit — Kredit egyenleg, hitelkeret
├── Stamp Cards — Pecsétgyűjtő kártyák
└── Promotions — Kedvezmények, akciók, feltételes ajánlatok

Tenant szintű konfiguráció

Minden tenant maga dönti el, mely modulokat használja:

Példa tenantAktív modulok
KávézóAzonosítók + Pecsétgyűjtő
B2B NagykerAzonosítók + Csoportok + Kredit
DivatwebshopAzonosítók + Pontprogram + Szintek + Promóciók
Szerviz cégAzonosítók + Kredit + Csoportok

1. Customer Identity — Ügyfél-azonosítók

Cél

Rugalmas azonosítórendszer, ahol a tenant maga definiálja, milyen azonosítókat használ (törzskártya szám, adószám, szerződésszám, stb.).

Adatmodell

-- Tenant által definiált azonosító típusok
CREATE TABLE customer_identifier_types (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
name VARCHAR(255) NOT NULL, -- "Törzskártya", "Ügyfélszám", "Adószám"
slug VARCHAR(100) NOT NULL, -- "loyalty_card", "customer_number", "tax_id"
prefix VARCHAR(20) DEFAULT NULL, -- "VEC-", "LC-", stb.
format_regex VARCHAR(255) DEFAULT NULL, -- validációs minta
auto_generate BOOLEAN DEFAULT FALSE, -- automatikus generálás
next_sequence BIGINT DEFAULT 1, -- következő sorszám (ha auto_generate)
is_unique BOOLEAN DEFAULT TRUE, -- egyedi-e a tenant-en belül
is_required BOOLEAN DEFAULT FALSE, -- kötelező-e minden ügyfélnél
sort_order INTEGER DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP,
updated_at TIMESTAMP,

UNIQUE(tenant_id, slug)
);

-- Ügyfélhez rendelt azonosítók
CREATE TABLE customer_identifiers (
id UUID PRIMARY KEY,
customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
identifier_type_id UUID NOT NULL REFERENCES customer_identifier_types(id),
value VARCHAR(255) NOT NULL,
issued_at DATE DEFAULT NULL,
expires_at DATE DEFAULT NULL,
is_active BOOLEAN DEFAULT TRUE,
metadata JSONB DEFAULT NULL, -- extra mezők, pl. kártya fizikai állapota
created_at TIMESTAMP,
updated_at TIMESTAMP,

UNIQUE(identifier_type_id, value)
);

Használati példák

TenantAzonosító típusPrefixAuto-generáltPélda érték
PékségTörzskártyaTC-IgenTC-000142
NagykerÜgyfélszámVEC-IgenVEC-003891
NagykerAdószámNem12345678-2-42
NagykerSzerződésszámSZ-NemSZ-2026/0034
WebshopHűségkártyaLY-IgenLY-982301

2. Customer Groups — Ügyfélcsoportok

Cél

Ügyfelek csoportosítása árazási, kedvezményes vagy belső szegmentációs célból. Egy ügyfél több csoporthoz is tartozhat.

Adatmodell

CREATE TABLE customer_groups (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
name VARCHAR(255) NOT NULL, -- "Nagyker", "VIP", "Alkalmazott"
slug VARCHAR(100) NOT NULL,
type VARCHAR(50) NOT NULL, -- 'pricing' | 'segment' | 'internal'
description TEXT DEFAULT NULL,
discount_percent DECIMAL(5,2) DEFAULT 0, -- alapértelmezett kedvezmény %
price_list_id UUID DEFAULT NULL, -- ha saját árlista tartozik hozzá
color VARCHAR(7) DEFAULT '#6366f1',
is_default BOOLEAN DEFAULT FALSE, -- új ügyfél automatikusan ide kerül
auto_assign_rules JSONB DEFAULT NULL, -- automatikus besorolási szabályok
sort_order INTEGER DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP,
updated_at TIMESTAMP,

UNIQUE(tenant_id, slug)
);

CREATE TABLE customer_group_memberships (
id UUID PRIMARY KEY,
customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
group_id UUID NOT NULL REFERENCES customer_groups(id) ON DELETE CASCADE,
valid_from DATE DEFAULT NULL,
valid_until DATE DEFAULT NULL,
notes TEXT DEFAULT NULL,
created_by INTEGER DEFAULT NULL,
created_at TIMESTAMP,

UNIQUE(customer_id, group_id)
);

Csoport típusok

  • pricing — árazási csoport: saját árlista vagy alapértelmezett kedvezmény tartozik hozzá
  • segment — szegmens: marketing célú csoportosítás (pl. "Visszatérő vásárló", "Új ügyfél")
  • internal — belső: adminisztrációs célú (pl. "Tesztelők", "Partnercég dolgozói")

Automatikus besorolás (auto_assign_rules)

{
"trigger": "after_purchase",
"conditions": [
{"field": "total_purchases_count", "operator": "gte", "value": 5},
{"field": "total_purchases_amount", "operator": "gte", "value": 100000}
]
}

3. Points Program — Hűségpont rendszer

Cél

Konfigurálja a tenant a saját pontprogramját: hogyan gyűjthetők pontok, mire válthatók be, mikor járnak le.

Adatmodell

-- Pontprogram konfiguráció (tenant-enként 1 vagy több program)
CREATE TABLE loyalty_programs (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
name VARCHAR(255) NOT NULL, -- "Hűségpont Program"
type VARCHAR(50) NOT NULL, -- 'points' | 'stamps' | 'cashback'
currency_name VARCHAR(50) DEFAULT 'pont', -- "pont", "pecsét", "cashback Ft"
currency_symbol VARCHAR(10) DEFAULT 'P', -- "P", "⭐", "Ft"
expiry_days INTEGER DEFAULT NULL, -- NULL = soha nem jár le
expiry_type VARCHAR(50) DEFAULT 'from_earn', -- 'from_earn' | 'end_of_year' | 'rolling_window'
min_redeem INTEGER DEFAULT 0, -- minimum beváltható pont
is_active BOOLEAN DEFAULT TRUE,
settings JSONB DEFAULT '{}', -- extra beállítások
created_at TIMESTAMP,
updated_at TIMESTAMP
);

-- Gyűjtési és beváltási szabályok
CREATE TABLE loyalty_rules (
id UUID PRIMARY KEY,
program_id UUID NOT NULL REFERENCES loyalty_programs(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL, -- "Alap pontgyűjtés", "Dupla pont hétvégén"
trigger VARCHAR(50) NOT NULL, -- 'purchase' | 'registration' | 'referral' | 'birthday' | 'manual' | 'review'
action VARCHAR(50) NOT NULL, -- 'earn' | 'redeem'

-- Mennyi pontot ad/von le
value DECIMAL(10,2) NOT NULL, -- 1, 2, 500, stb.
value_type VARCHAR(50) NOT NULL, -- 'per_amount' | 'fixed' | 'multiplier' | 'percent_of_cart'
value_unit DECIMAL(10,2) DEFAULT 1, -- per_amount esetén: "1 pont per 100 Ft" → value=1, value_unit=100

-- Beváltásnál: mennyit ér egy pont
redeem_value DECIMAL(10,2) DEFAULT NULL, -- 1 pont = 5 Ft kedvezmény
redeem_max_percent DECIMAL(5,2) DEFAULT NULL, -- max a kosár X%-a fedhető ponttal

-- Feltételek
conditions JSONB DEFAULT '{}', -- {min_amount, category_ids, day_of_week, channel, ...}

priority INTEGER DEFAULT 0, -- magasabb = előrébb értékelődik
is_active BOOLEAN DEFAULT TRUE,
valid_from TIMESTAMP DEFAULT NULL,
valid_until TIMESTAMP DEFAULT NULL,
created_at TIMESTAMP,
updated_at TIMESTAMP
);

-- Ügyfél pontegyenlege
CREATE TABLE loyalty_balances (
id UUID PRIMARY KEY,
customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
program_id UUID NOT NULL REFERENCES loyalty_programs(id) ON DELETE CASCADE,
balance BIGINT DEFAULT 0, -- aktuális egyenleg
lifetime_earned BIGINT DEFAULT 0, -- összesen szerzett (szint számításhoz)
lifetime_redeemed BIGINT DEFAULT 0, -- összesen beváltott
lifetime_expired BIGINT DEFAULT 0, -- összesen lejárt
last_activity_at TIMESTAMP DEFAULT NULL,
created_at TIMESTAMP,
updated_at TIMESTAMP,

UNIQUE(customer_id, program_id)
);

-- Pont tranzakció napló (immutable)
CREATE TABLE loyalty_transactions (
id UUID PRIMARY KEY,
customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
program_id UUID NOT NULL REFERENCES loyalty_programs(id) ON DELETE CASCADE,
type VARCHAR(50) NOT NULL, -- 'earn' | 'redeem' | 'expire' | 'adjust' | 'bonus' | 'refund'
amount BIGINT NOT NULL, -- pozitív: jóváírás, negatív: terhelés
balance_after BIGINT NOT NULL, -- tranzakció utáni egyenleg

-- Mire vonatkozik
source_type VARCHAR(100) DEFAULT NULL, -- 'order' | 'manual' | 'campaign' | 'referral' | 'review'
source_id UUID DEFAULT NULL, -- polymorphic: order_id, campaign_id, stb.
rule_id UUID DEFAULT NULL REFERENCES loyalty_rules(id),

description VARCHAR(500) DEFAULT NULL, -- "Vásárlás #ORD-1234 után 150 pont"
metadata JSONB DEFAULT NULL, -- extra adat
expires_at TIMESTAMP DEFAULT NULL, -- mikor jár le ez a pont tétel

created_by INTEGER DEFAULT NULL, -- ki hozta létre (manuális jóváírásnál)
created_at TIMESTAMP NOT NULL
);

-- Indexek
CREATE INDEX idx_loyalty_transactions_customer ON loyalty_transactions(customer_id, program_id, created_at);
CREATE INDEX idx_loyalty_transactions_expires ON loyalty_transactions(expires_at) WHERE expires_at IS NOT NULL AND type = 'earn';

Szabálypéldák

Alap pontgyűjtés (1 pont / 100 Ft):

{
"name": "Alap pontgyűjtés",
"trigger": "purchase",
"action": "earn",
"value": 1,
"value_type": "per_amount",
"value_unit": 100
}

Dupla pont hétvégén:

{
"name": "Hétvégi dupla pont",
"trigger": "purchase",
"action": "earn",
"value": 2,
"value_type": "multiplier",
"conditions": {
"day_of_week": [6, 7]
},
"priority": 10
}

500 pont regisztrációkor:

{
"name": "Üdvözlő bónusz",
"trigger": "registration",
"action": "earn",
"value": 500,
"value_type": "fixed"
}

Pont beváltás (100 pont = 500 Ft):

{
"name": "Pont beváltás",
"trigger": "purchase",
"action": "redeem",
"value": 1,
"value_type": "per_amount",
"redeem_value": 5,
"redeem_max_percent": 30
}

Pont lejárat típusok

TípusLeírásPélda
from_earnX nappal a szerzés után365 nap múlva lejár
end_of_yearAz adott év végénDec 31-én minden pont lejár
rolling_windowUtolsó aktivitástól X nap180 nap inaktivitás után lejár az összes

4. Tier System — Szintrendszer

Cél

Automatikus és manuális törzsvásárlói szintek, szinthez kötött előnyökkel.

Adatmodell

CREATE TABLE loyalty_tiers (
id UUID PRIMARY KEY,
program_id UUID NOT NULL REFERENCES loyalty_programs(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL, -- "Bronz", "Ezüst", "Arany", "Platina"
slug VARCHAR(100) NOT NULL,

-- Elérési feltétel (a tenant választja melyiket használja)
min_points BIGINT DEFAULT 0, -- lifetime_earned alapján
min_spend DECIMAL(12,2) DEFAULT 0, -- összesített vásárlás alapján
min_orders INTEGER DEFAULT 0, -- rendelések száma alapján
qualification_period_days INTEGER DEFAULT NULL, -- NULL = lifetime, 365 = éves újraértékelés

-- Szint előnyök
benefits JSONB DEFAULT '{}',
/*
{
"discount_percent": 10,
"free_shipping": true,
"free_shipping_min_amount": 0,
"point_multiplier": 1.5,
"priority_support": true,
"early_access_days": 3,
"birthday_bonus_points": 1000,
"exclusive_products": true
}
*/

color VARCHAR(7) DEFAULT '#6366f1',
icon VARCHAR(50) DEFAULT NULL, -- "tabler-award", "tabler-diamond"
sort_order INTEGER DEFAULT 0, -- Bronz=1, Ezüst=2, Arany=3, Platina=4
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP,
updated_at TIMESTAMP,

UNIQUE(program_id, slug)
);

-- Ügyfél aktuális szintje és történet
CREATE TABLE customer_tiers (
id UUID PRIMARY KEY,
customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
program_id UUID NOT NULL REFERENCES loyalty_programs(id) ON DELETE CASCADE,
tier_id UUID NOT NULL REFERENCES loyalty_tiers(id),
promoted_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP DEFAULT NULL, -- mikor értékelődik újra
is_current BOOLEAN DEFAULT TRUE,

-- Történet
previous_tier_id UUID DEFAULT NULL REFERENCES loyalty_tiers(id),
reason VARCHAR(255) DEFAULT NULL, -- 'auto_promotion' | 'auto_demotion' | 'manual'
created_by INTEGER DEFAULT NULL,
created_at TIMESTAMP,

UNIQUE(customer_id, program_id, is_current) -- csak 1 aktuális szint per program
);

Szint példák

SzintFeltételKedvezménySzállításPont szorzóExtra
Bronz0 pont0%Normál1x
Ezüst5.000 pont5%15.000 Ft felett ingyen1.25x
Arany20.000 pont10%Mindig ingyen1.5xKorai hozzáférés 2 nap
Platina50.000 pont15%Mindig ingyen2xKorai hozzáférés 5 nap, prioritás support

Visszasorolás logika

A tenant beállítja:

  • Nincs visszasorolás — egyszer elért szint megmarad
  • Éves újraértékelés — ha a qualification_period_days le, ha nem teljesíti a feltételt, visszasorolás 1 szinttel
  • Azonnali — ha a pontegyenleg a szint alá esik (pont beváltás után)

5. Store Credit — Kredit egyenleg

Cél

Előre feltöltött egyenleg (B2B), visszáru jóváírás, ajándékkártya egyenleg, vagy hitelkeret.

Adatmodell

CREATE TABLE credit_accounts (
id UUID PRIMARY KEY,
customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
tenant_id UUID NOT NULL,
balance DECIMAL(12,2) DEFAULT 0, -- aktuális egyenleg
credit_limit DECIMAL(12,2) DEFAULT 0, -- hitelkeret (0 = nincs hitel, csak előre töltött)
currency VARCHAR(3) DEFAULT 'HUF',
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP,
updated_at TIMESTAMP,

UNIQUE(customer_id, currency)
);

CREATE TABLE credit_transactions (
id UUID PRIMARY KEY,
account_id UUID NOT NULL REFERENCES credit_accounts(id) ON DELETE CASCADE,
type VARCHAR(50) NOT NULL, -- 'deposit' | 'purchase' | 'refund' | 'adjustment' | 'gift_card' | 'expiry'
amount DECIMAL(12,2) NOT NULL, -- pozitív: jóváírás, negatív: terhelés
balance_after DECIMAL(12,2) NOT NULL,

reference_type VARCHAR(100) DEFAULT NULL, -- 'order' | 'return' | 'gift_card' | 'manual'
reference_id UUID DEFAULT NULL,

description VARCHAR(500) DEFAULT NULL, -- "Előre fizetés 2026. márciusra"
note TEXT DEFAULT NULL, -- belső megjegyzés
created_by INTEGER DEFAULT NULL,
created_at TIMESTAMP NOT NULL
);

CREATE INDEX idx_credit_transactions_account ON credit_transactions(account_id, created_at);

Használati esetek

EsetTranzakció típusÖsszegLeírás
B2B előre fizetdeposit+500.000"Előleg 2026 Q2"
Vásárlás egyenlegbőlpurchase-34.500"Rendelés #ORD-4521"
Visszáru jóváírásrefund+12.000"Visszáru #RET-089"
Ajándékkártya beváltásgift_card+25.000"GC-ABCD-1234-EFGH"
Admin korrekcióadjustment+5.000"Kompenzáció késedelmes szállítás"

Hitelkeret

Ha credit_limit > 0, az ügyfél a balance alá is mehet (negatív egyenleg), de maximum -credit_limit-ig:

Elérhető összeg = balance + credit_limit

Például:

  • Balance: 0 Ft, Credit limit: 200.000 Ft → 200.000 Ft-ig vásárolhat
  • Balance: -150.000 Ft, Credit limit: 200.000 Ft → még 50.000 Ft-ig vásárolhat

6. Stamp Cards — Pecsétgyűjtő

Cél

Egyszerű "10. kávé ingyen" típusú rendszer, kis üzleteknek.

Adatmodell

CREATE TABLE stamp_cards (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
name VARCHAR(255) NOT NULL, -- "Kávé pecsétgyűjtő"
total_stamps INTEGER NOT NULL DEFAULT 10, -- hány pecsét kell a jutalomhoz
reward_type VARCHAR(50) NOT NULL, -- 'free_product' | 'discount_percent' | 'discount_fixed' | 'points'
reward_value DECIMAL(10,2) DEFAULT NULL, -- 20 (%), 2000 (Ft), 500 (pont)
reward_product_id UUID DEFAULT NULL, -- ha free_product: melyik termék

-- Mire jár pecsét
qualifying_rules JSONB DEFAULT '{}', -- {category_ids, product_ids, min_amount}

is_active BOOLEAN DEFAULT TRUE,
valid_from DATE DEFAULT NULL,
valid_until DATE DEFAULT NULL,
created_at TIMESTAMP,
updated_at TIMESTAMP
);

CREATE TABLE customer_stamp_cards (
id UUID PRIMARY KEY,
customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
stamp_card_id UUID NOT NULL REFERENCES stamp_cards(id) ON DELETE CASCADE,
stamps_collected INTEGER DEFAULT 0,
is_completed BOOLEAN DEFAULT FALSE,
reward_claimed BOOLEAN DEFAULT FALSE,
reward_claimed_at TIMESTAMP DEFAULT NULL,
started_at TIMESTAMP NOT NULL,
completed_at TIMESTAMP DEFAULT NULL,
created_at TIMESTAMP,
updated_at TIMESTAMP
);

CREATE TABLE stamp_events (
id UUID PRIMARY KEY,
customer_stamp_card_id UUID NOT NULL REFERENCES customer_stamp_cards(id) ON DELETE CASCADE,
type VARCHAR(50) NOT NULL, -- 'stamp' | 'reward_claimed' | 'reset'
source_type VARCHAR(100) DEFAULT NULL, -- 'order' | 'manual'
source_id UUID DEFAULT NULL,
created_by INTEGER DEFAULT NULL,
created_at TIMESTAMP NOT NULL
);

Működés

  1. Ügyfél vásárol (qualifying feltétel teljesül) → pecsét jóváírás
  2. stamps_collected >= total_stamps → kártya completed
  3. Ügyfél beváltja a jutalmat → reward_claimed = true
  4. Új kártya indul automatikusan

7. Promotions — Kedvezmények & akciók

Cél

Univerzális promóciómotor, amellyel bármilyen kedvezménytípus konfigurálható feltételekkel. Kiváltja a hagyományos "kuponrendszert" is.

Adatmodell

CREATE TABLE promotions (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
name VARCHAR(255) NOT NULL, -- "Tavaszi akció 20%"
description TEXT DEFAULT NULL, -- adminnak szóló leírás
public_label VARCHAR(255) DEFAULT NULL, -- ügyfélnek látható szöveg: "Tavaszi vásár!"

-- Mit ad
type VARCHAR(50) NOT NULL,
/*
'discount_percent' — X% kedvezmény
'discount_fixed' — X Ft kedvezmény
'free_shipping' — ingyenes szállítás
'free_product' — ajándék termék
'fixed_price' — fix akciós ár
'cashback' — X% visszajár kreditre
'point_multiplier' — Nx pont szorzó
'point_bonus' — fix bónusz pont
*/
value DECIMAL(10,2) NOT NULL, -- 20 (%), 2000 (Ft), 1.5 (szorzó)

-- Kupon kód (opcionális — ha nincs, automatikus promóció)
code VARCHAR(50) DEFAULT NULL, -- "TAVASZ20", NULL = automatikus

-- Limitek
max_uses_total INTEGER DEFAULT NULL, -- összes felhasználás limit (NULL = korlátlan)
max_uses_per_customer INTEGER DEFAULT NULL, -- ügyfelenként limit
current_uses INTEGER DEFAULT 0,

-- Kombinálhatóság
is_combinable BOOLEAN DEFAULT TRUE, -- kombinálható-e más promócióval
priority INTEGER DEFAULT 0, -- ha nem kombinálható, melyik nyer (magasabb = erősebb)

is_active BOOLEAN DEFAULT TRUE,
created_by INTEGER DEFAULT NULL,
created_at TIMESTAMP,
updated_at TIMESTAMP,

UNIQUE(tenant_id, code) -- kuponkód egyedi tenant-en belül
);

-- Promóció feltételek (AND logika — minden feltételnek teljesülnie kell)
CREATE TABLE promotion_conditions (
id UUID PRIMARY KEY,
promotion_id UUID NOT NULL REFERENCES promotions(id) ON DELETE CASCADE,
condition_type VARCHAR(50) NOT NULL,
/*
-- Kosár feltételek
'min_cart_value' — minimum kosárérték
'min_quantity' — minimum darabszám
'max_cart_value' — maximum kosárérték

-- Termék feltételek
'category' — adott kategóriába tartozó termék(ek)re
'product' — konkrét termék(ek)re
'brand' — adott márká(k)ra
'exclude_category' — kivéve ezek a kategóriák
'exclude_product' — kivéve ezek a termékek

-- Ügyfél feltételek
'customer_group' — adott ügyfélcsoporthoz tartozik
'tier' — adott szinten van
'first_purchase' — első vásárlás
'days_since_last_order' — X napja nem rendelt
'min_lifetime_spend' — összesen ennyit költött
'birthday_month' — születésnapi hónap
'registration_days' — X napon belül regisztrált

-- Csatorna feltételek
'channel' — 'online' | 'in_store' | 'app'
'store' — konkrét bolt(ok)

-- Idő feltételek
'day_of_week' — hét napjai
'time_range' — napszak
*/
operator VARCHAR(20) NOT NULL, -- 'eq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'not_in' | 'between'
value JSONB NOT NULL, -- érték vagy lista
created_at TIMESTAMP
);

-- Promóció érvényességi időszak
CREATE TABLE promotion_schedules (
id UUID PRIMARY KEY,
promotion_id UUID NOT NULL REFERENCES promotions(id) ON DELETE CASCADE,
valid_from TIMESTAMP NOT NULL,
valid_until TIMESTAMP NOT NULL,

-- Ismétlődő időszakok (opcionális)
recurring_days JSONB DEFAULT NULL, -- [1,2,3,4,5] = hétköznapok
recurring_hours JSONB DEFAULT NULL, -- {"from": "14:00", "to": "16:00"}

created_at TIMESTAMP
);

-- Promóció felhasználás napló
CREATE TABLE promotion_usages (
id UUID PRIMARY KEY,
promotion_id UUID NOT NULL REFERENCES promotions(id) ON DELETE CASCADE,
customer_id UUID NOT NULL REFERENCES customers(id),
order_id UUID DEFAULT NULL,
discount_amount DECIMAL(12,2) NOT NULL, -- ténylegesen adott kedvezmény
used_at TIMESTAMP NOT NULL,

created_at TIMESTAMP
);

CREATE INDEX idx_promotion_usages_customer ON promotion_usages(promotion_id, customer_id);

Promóció példák konfigurálva

"20% csak bolti vásárlásra"

promotion:
type: discount_percent
value: 20
code: NULL (automatikus)

conditions:
- condition_type: channel
operator: eq
value: "in_store"

"15% csak online rendelésre, min 10.000 Ft"

promotion:
type: discount_percent
value: 15
code: NULL

conditions:
- condition_type: channel
operator: eq
value: "online"
- condition_type: min_cart_value
operator: gte
value: 10000

"Ingyenes szállítás Arany tagoknak"

promotion:
type: free_shipping
value: 1

conditions:
- condition_type: tier
operator: in
value: ["gold", "platinum"]

"Happy hour: 30% kávéra, hétköznap 14-16h"

promotion:
type: discount_percent
value: 30

conditions:
- condition_type: category
operator: in
value: ["coffee"]
- condition_type: day_of_week
operator: in
value: [1, 2, 3, 4, 5]
- condition_type: time_range
operator: between
value: {"from": "14:00", "to": "16:00"}

"Visszacsábítás: 25% kuponkód, 1x beváltható"

promotion:
type: discount_percent
value: 25
code: "COMEBACK25"
max_uses_per_customer: 1

conditions:
- condition_type: days_since_last_order
operator: gte
value: 90

"B2B mennyiségi kedvezmény: 10 db-tól -20%"

promotion:
type: discount_percent
value: 20

conditions:
- condition_type: min_quantity
operator: gte
value: 10
- condition_type: customer_group
operator: in
value: ["wholesale"]

Tenant konfiguráció

Modul aktiválás

CREATE TABLE loyalty_module_settings (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL UNIQUE,

-- Modul kapcsolók
identifiers_enabled BOOLEAN DEFAULT FALSE,
groups_enabled BOOLEAN DEFAULT FALSE,
points_enabled BOOLEAN DEFAULT FALSE,
tiers_enabled BOOLEAN DEFAULT FALSE,
credit_enabled BOOLEAN DEFAULT FALSE,
stamps_enabled BOOLEAN DEFAULT FALSE,
promotions_enabled BOOLEAN DEFAULT FALSE,

-- Globális beállítások
default_currency VARCHAR(3) DEFAULT 'HUF',

created_at TIMESTAMP,
updated_at TIMESTAMP
);

Szolgáltatás architektúra

Melyik backend-be kerül?

Mivel ez szorosan kapcsolódik az e-commerce funkciókhoz (kosár, rendelés, termékek), de a CRM is használja (ügyfélkezelés), a javasolt elhelyezés:

tenant-backend-webshop          — Promóciók, pont engine, kosár integráció
tenant-backend-crm — Ügyfélcsoportok, azonosítók (kiterjesztés)

Vagy (ha túl összekeveredik):

tenant-backend-loyalty (új!) — Önálló loyalty service az összes modullal

API végpontok (tervezett)

# Ügyfél-azonosítók
GET /api/loyalty/identifier-types
POST /api/loyalty/identifier-types
GET /api/loyalty/customers/{id}/identifiers
POST /api/loyalty/customers/{id}/identifiers

# Csoportok
GET /api/loyalty/groups
POST /api/loyalty/groups
PUT /api/loyalty/customers/{id}/groups

# Pontprogram
GET /api/loyalty/programs
GET /api/loyalty/customers/{id}/balance
GET /api/loyalty/customers/{id}/transactions
POST /api/loyalty/customers/{id}/points/adjust
POST /api/loyalty/customers/{id}/points/redeem

# Szintek
GET /api/loyalty/tiers
GET /api/loyalty/customers/{id}/tier

# Kredit
GET /api/loyalty/customers/{id}/credit
POST /api/loyalty/customers/{id}/credit/deposit
POST /api/loyalty/customers/{id}/credit/adjust

# Pecsétgyűjtő
GET /api/loyalty/stamp-cards
GET /api/loyalty/customers/{id}/stamps
POST /api/loyalty/customers/{id}/stamps/collect

# Promóciók
GET /api/loyalty/promotions
POST /api/loyalty/promotions
POST /api/loyalty/promotions/evaluate -- kosár kiértékelés
POST /api/loyalty/promotions/validate-code

# Tenant beállítások
GET /api/loyalty/settings
PUT /api/loyalty/settings

Implementációs sorrend (javasolt)

FázisModulFüggőség
1Tenant konfiguráció (modul kapcsolók)
1Ügyfél-azonosítókCustomer modell
1ÜgyfélcsoportokCustomer modell
2Kredit egyenlegCustomer modell
2PecsétgyűjtőOrder/Purchase esemény
3Pontprogram + szabályokOrder/Purchase esemény
3SzintrendszerPontprogram
4PromóciókKosár/rendelés rendszer

Összefüggések

                    ┌─────────────────┐
│ Tenant Config │
│ (modulok on/off)│
└────────┬────────┘

┌───────────────────┼───────────────────┐
│ │ │
┌────▼────┐ ┌─────▼─────┐ ┌─────▼─────┐
│Customer │ │ Points │ │Promotions │
│Identity │ │ Program │ │ Engine │
│& Groups │ │ │ │ │
└────┬────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
│ ┌─────▼─────┐ │
│ │ Tier │ │
│ │ System │─────────────┘
│ └───────────┘ (szint = promó feltétel)

┌────▼────┐ ┌───────────┐
│ Credit │ │ Stamp │
│ Account │ │ Cards │
└─────────┘ └───────────┘