Eshop — návrh datového modelu v4
Inspirační zdroje
| Oblast |
Inspirace |
| Katalog |
Vendure |
| Pricing |
Medusa |
| Inventory |
Medusa |
| Attribute/Option rozlišení |
Sylius |
| API |
vlastní REST |
Rozhodnutí z review (souhrn všech verzí)
PK strategie
UUID pro veřejnou identitu (API, URL, externí systémy). bigint generated always as identity pro interní PK joinovacích a překladových tabulek.
Typy pro peníze a sazby
| Pole |
Typ |
amount (ceny, poplatky) |
int — v haléřích/centech |
exchange_rate |
numeric(18,8) |
tax_rate |
numeric(5,2) |
discount_value |
numeric(12,4) |
weight |
numeric(10,3) |
width, height, length |
numeric(10,2) |
Enumy jako PostgreSQL enum typ
Pro stabilní domény (publication_status, price.type, media.type) používáme PostgreSQL ENUM — menší storage, rychlejší porovnání, přidání hodnoty non-blocking přes ALTER TYPE ... ADD VALUE. Pro proměnlivé domény (reason v dirty queue, EAV klíče) varchar.
Stavový model produktu
publication_status vyjadřuje aktuální stav produktu. Flags vyjadřují původ nebo důvod stavu. Není to duplicita.
deleted není fyzický delete. Fyzický delete probíhá až po archivaci a auditu.
publication_status |
Popis |
draft |
Rozpracovaný, nezveřejněný |
published |
Aktivní na webu |
hidden |
Dočasně skrytý (viz hide_until) |
blocked |
Blokovaný — viz flag is_blocked_by_editor |
discontinued |
Vyřazený z nabídky |
deleted |
Soft delete — označen ke smazání, fyzicky zůstává do archivace |
Flags říkají proč:
- is_blocked_by_editor — editor ručně zablokoval
- is_deleted_by_import — import označil ke smazání
- is_clearance — doprodej (může být published i discontinued)
- hide_until — dočasně skrýt do data
Soft delete strategie
Rozhodnutí platí jednotně pro celý model:
- product — publication_status = deleted, fyzický delete až po auditu
- ostatní entity — deleted_at timestamptz NULL kde je potřeba obnova
- join tabulky bez historické hodnoty — fyzický delete + audit log zachytí
Slug unikátnost
UNIQUE(locale, slug) globálně + UNIQUE(entity_id, locale) aby jedna entita neměla dva slugy pro stejný jazyk.
Audit log entity_id
entity_id text — univerzální, pokryje uuid i bigint PK bez JOIN problémů.
reserved_quantity a oversell
CHECK (reserved_quantity <= stocked_quantity) záměrně chybí. Inventura a vrácení dodavateli jsou legitimní operace které by constraint rozbil. Oversell a backorder jsou obchodní rozhodnutí — hlídají se aplikačně při vytváření rezervace.
Vrstva 1 — produkt, identita, stavy, dodavatel
product
| Pole |
Typ |
Popis |
id |
uuid PK |
Veřejná identita |
code |
varchar UK |
Obchodní kód — hlavní klíč pro import a párování |
ean |
varchar |
EAN kód |
brand_id |
uuid FK RESTRICT |
|
web_id |
bigint |
Mapovací klíč na starý systém |
publication_status |
enum |
draft, published, hidden, blocked, discontinued, deleted |
is_blocked_by_editor |
boolean |
Původ stavu blocked |
is_deleted_by_import |
boolean |
Původ stavu deleted |
is_clearance |
boolean |
Doprodej — nezávislé na publication_status |
new_until |
timestamptz |
Produkt je "nový" do tohoto data |
hide_until |
timestamptz |
Dočasně skrýt do tohoto data |
created_at |
timestamptz |
|
updated_at |
timestamptz |
|
brand
| Pole |
Typ |
Popis |
id |
uuid PK |
|
name |
varchar |
|
slug |
varchar UK |
Výchozí slug — pokud brand nemá lokalizaci |
website |
varchar |
|
logo |
varchar |
|
is_local |
boolean |
|
web_id |
bigint |
Mapovací klíč na starý systém |
Pokud brand potřebuje lokalizovaný slug, přidat brand_translation.slug s UNIQUE(locale, slug). Zatím řešeno výchozím brand.slug.
product_slug
| Pole |
Typ |
Popis |
id |
bigint PK |
|
product_id |
uuid FK CASCADE |
|
locale |
varchar |
cs, sk, de, hu, pl |
slug |
varchar |
|
|
|
UNIQUE(product_id, locale) |
|
|
UNIQUE(locale, slug) |
supplier
| Pole |
Typ |
Popis |
id |
uuid PK |
|
name |
varchar |
|
code |
varchar UK |
|
product_supplier_feed
| Pole |
Typ |
Popis |
id |
uuid PK |
|
product_id |
uuid FK CASCADE |
|
supplier_id |
uuid FK RESTRICT |
|
supplier_code |
varchar |
Kód produktu u dodavatele |
supplier_ean |
varchar |
|
lock_content |
boolean |
Nepřepisovat textová data (neprepsat) |
last_imported_at |
timestamptz |
|
|
|
UNIQUE(supplier_id, supplier_code) |
|
|
UNIQUE(supplier_id, supplier_ean) — pouze pokud je EAN u dodavatele spolehlivý |
Import logika: dodavatel insertuje pokud neexistuje code/ean → produkt vznikne jako draft. Po prvním insertu přepisuje pouze ceny a skladovou dostupnost. lock_content = true chrání textová data.
product_supplier_feed_raw
Raw payload pro debugging importů. Implementovat retenci — držet posledních N importů nebo mazat starší než X dní.
| Pole |
Typ |
Popis |
id |
bigint PK |
|
supplier_feed_id |
uuid FK CASCADE |
|
raw_data |
jsonb |
Surová data z importu |
imported_at |
timestamptz |
|
Vrstva 2 — lokalizace
Fallback na výchozí jazyk cs řeší aplikační vrstva.
product_translation
| Pole |
Typ |
Popis |
id |
bigint PK |
|
product_id |
uuid FK CASCADE |
|
locale |
varchar |
cs, sk, de, hu, pl |
name |
varchar |
|
name_heureka |
varchar |
Název pro Heureka feed |
description_short |
text |
|
description_long |
text |
|
description_tech |
text |
|
out_of_stock_text |
text |
Obchodní sdělení pro zákazníka |
is_machine_translated |
boolean |
Příznak pro editorial workflow |
created_at |
timestamptz |
|
updated_at |
timestamptz |
|
|
|
UNIQUE(product_id, locale) |
brand_translation
| Pole |
Typ |
Popis |
id |
bigint PK |
|
brand_id |
uuid FK CASCADE |
|
locale |
varchar |
|
name |
varchar |
|
slug |
varchar NULL |
Lokalizovaný slug — pokud se liší od brand.slug |
created_at |
timestamptz |
|
updated_at |
timestamptz |
|
|
|
UNIQUE(brand_id, locale) |
|
|
UNIQUE(locale, slug) WHERE slug IS NOT NULL |
Vrstva 3 — varianty a atributy
Rozlišení Option vs. Attribute (Sylius)
| Typ |
Definice |
Vliv na SKU |
| Option |
Osa varianty — velikost, barva |
Ano |
| Attribute |
Popisná vlastnost — materiál, váha |
Ne |
Historické typy varianty a barvy mapují jako option_group.type = variant a option_group.type = color.
product_variant
| Pole |
Typ |
Popis |
id |
uuid PK |
|
product_id |
uuid FK CASCADE |
|
code |
varchar UK |
|
ean |
varchar |
|
is_active |
boolean |
|
is_default |
boolean |
|
sort_order |
int |
|
created_at |
timestamptz |
|
updated_at |
timestamptz |
|
|
|
UNIQUE(product_id) WHERE is_default = true — jen jedna výchozí varianta |
product_variant_translation
| Pole |
Typ |
Popis |
id |
bigint PK |
|
variant_id |
uuid FK CASCADE |
|
locale |
varchar |
|
name |
varchar |
|
|
|
UNIQUE(variant_id, locale) |
option_group
| Pole |
Typ |
Popis |
id |
uuid PK |
|
product_id |
uuid FK CASCADE |
|
code |
varchar |
|
type |
varchar |
variant, color |
sort_order |
int |
|
|
|
UNIQUE(product_id, code) |
option_group_translation
| Pole |
Typ |
Popis |
id |
bigint PK |
|
option_group_id |
uuid FK CASCADE |
|
locale |
varchar |
|
name |
varchar |
|
|
|
UNIQUE(option_group_id, locale) |
option_value
| Pole |
Typ |
Popis |
id |
uuid PK |
|
option_group_id |
uuid FK CASCADE |
|
code |
varchar |
|
color_hex |
varchar |
Pro type = color |
image |
varchar |
Vzorník |
sort_order |
int |
|
|
|
UNIQUE(option_group_id, code) |
option_value_translation
| Pole |
Typ |
Popis |
id |
bigint PK |
|
option_value_id |
uuid FK CASCADE |
|
locale |
varchar |
|
name |
varchar |
|
|
|
UNIQUE(option_value_id, locale) |
product_variant_option_value
| Pole |
Typ |
Popis |
variant_id |
uuid FK CASCADE |
|
option_value_id |
uuid FK RESTRICT |
|
|
|
PRIMARY KEY (variant_id, option_value_id) |
Constrainty aplikační vrstvou / procedurou:
- Jedna varianta nesmí mít dvě hodnoty ze stejné option_group.
- Jedna kombinace option values nesmí existovat dvakrát pro stejný produkt.
attribute
| Pole |
Typ |
Popis |
id |
uuid PK |
|
code |
varchar UK |
|
type |
varchar |
text, number, boolean |
attribute_translation
| Pole |
Typ |
Popis |
id |
bigint PK |
|
attribute_id |
uuid FK CASCADE |
|
locale |
varchar |
|
name |
varchar |
|
|
|
UNIQUE(attribute_id, locale) |
product_attribute_value
| Pole |
Typ |
Popis |
id |
bigint PK |
|
product_id |
uuid FK CASCADE |
|
attribute_id |
uuid FK RESTRICT |
|
value |
varchar |
|
locale |
varchar |
|
|
|
UNIQUE(product_id, attribute_id, locale) |
Vrstva 4 — ceny (Medusa pricing model)
Filosofie
Varianta nemá jednu cenu — má price_set ze kterého se vybírá cena podle kontextu. Všechny peněžní hodnoty jsou int v haléřích/centech. Ceny se nikdy fyzicky nepřepisují — ukončuje se jejich platnost (valid_to).
region
| Pole |
Typ |
Popis |
id |
uuid PK |
|
code |
varchar UK |
CZ, SK, DE, HU, PL |
name |
varchar |
|
currency_code |
varchar |
CZK, EUR |
tax_mode |
varchar |
inclusive, exclusive |
exchange_rate |
numeric(18,8) |
Přepočet z CZK |
|
|
CHECK (exchange_rate > 0) |
price_set
| Pole |
Typ |
Popis |
id |
uuid PK |
|
variant_id |
uuid FK CASCADE |
UNIQUE — 1:1 s variantou |
created_at |
timestamptz |
|
price
| Pole |
Typ |
Popis |
id |
uuid PK |
|
price_set_id |
uuid FK CASCADE |
|
region_id |
uuid FK RESTRICT NULL |
NULL = platí pro všechny regiony |
currency_code |
varchar |
|
amount |
int |
V haléřích/centech |
type |
enum |
sale, original, cost, install |
min_quantity |
int NULL |
|
max_quantity |
int NULL |
|
valid_from |
timestamptz NULL |
|
valid_to |
timestamptz NULL |
|
created_at |
timestamptz |
|
updated_at |
timestamptz |
|
|
|
CHECK (amount >= 0) |
|
|
CHECK (min_quantity IS NULL OR min_quantity > 0) |
|
|
CHECK (max_quantity IS NULL OR max_quantity >= min_quantity) |
|
|
CHECK (valid_to IS NULL OR valid_to > valid_from) |
|
|
Index: (price_set_id, region_id, currency_code, type, min_quantity, valid_from, valid_to) |
Překryvy platnosti řešeny aplikačně nebo procedurou.
Mapování: cena_a → sale, cena_b → original, cena_c → cost, cena_montaze → install.
tax_rate
| Pole |
Typ |
Popis |
id |
uuid PK |
|
region_id |
uuid FK RESTRICT |
|
name |
varchar |
|
rate |
numeric(5,2) |
|
is_default |
boolean |
|
|
|
CHECK (rate >= 0) |
|
|
UNIQUE(region_id) WHERE is_default = true |
Nahrazuje polymorfní target_type/target_id explicitními pivot tabulkami.
| Pole |
Typ |
Popis |
id |
uuid PK |
|
code |
varchar UK |
|
name |
varchar |
|
discount_type |
enum |
percent, fixed |
discount_value |
numeric(12,4) |
|
priority |
int |
Řeší konflikt více akcí |
only_in_stock |
boolean |
Akce jen pokud skladem |
valid_from |
timestamptz |
|
valid_to |
timestamptz |
|
is_active |
boolean |
|
created_at |
timestamptz |
|
updated_at |
timestamptz |
|
|
|
CHECK (discount_value >= 0) |
|
|
CHECK (discount_type <> 'percent' OR discount_value <= 100) |
| Pole |
Typ |
promotion_id |
uuid FK CASCADE |
product_id |
uuid FK CASCADE |
|
PRIMARY KEY (promotion_id, product_id) |
| Pole |
Typ |
promotion_id |
uuid FK CASCADE |
brand_id |
uuid FK CASCADE |
|
PRIMARY KEY (promotion_id, brand_id) |
| Pole |
Typ |
promotion_id |
uuid FK CASCADE |
category_id |
uuid FK CASCADE |
|
PRIMARY KEY (promotion_id, category_id) |
Vrstva 5 — sklad a dostupnost (Medusa inventory model)
Filosofie
Fyzický stock je oddělen od obchodní dostupnosti. reserved_quantity je buď cache součtu inventory_reservation (varianta A), nebo počítá se za běhu (varianta B) — smíšený přístup není doporučen.
inventory_item
| Pole |
Typ |
Popis |
id |
uuid PK |
|
variant_id |
uuid FK CASCADE |
UNIQUE — 1:1 s variantou |
sku |
varchar UK |
|
requires_shipping |
boolean |
|
weight |
numeric(10,3) |
kg |
width |
numeric(10,2) |
cm |
height |
numeric(10,2) |
cm |
length |
numeric(10,2) |
cm |
created_at |
timestamptz |
|
updated_at |
timestamptz |
|
stock_location
| Pole |
Typ |
Popis |
id |
uuid PK |
|
code |
varchar UK |
|
name |
varchar |
|
is_central |
boolean |
Mapuje eshop_produkty_sklad_centrala |
inventory_level
| Pole |
Typ |
Popis |
id |
bigint PK |
|
inventory_item_id |
uuid FK RESTRICT |
|
location_id |
uuid FK RESTRICT |
|
stocked_quantity |
int |
Fyzicky na skladě |
reserved_quantity |
int |
Cache rezervací — viz inventory_reservation |
incoming_quantity |
int |
Na cestě (na_ceste) |
updated_at |
timestamptz |
|
|
|
UNIQUE(inventory_item_id, location_id) |
|
|
CHECK (stocked_quantity >= 0) |
|
|
CHECK (reserved_quantity >= 0) |
|
|
CHECK (incoming_quantity >= 0) |
Dostupné množství = stocked_quantity - reserved_quantity. CHECK (reserved <= stocked) záměrně chybí — inventura nebo vrácení dodavateli může dočasně vytvořit záporný rozdíl. Hlídáno aplikačně při vytváření rezervace.
inventory_reservation
Dohledatelná tabulka rezervací. inventory_level.reserved_quantity je cache jejich součtu.
| Pole |
Typ |
Popis |
id |
uuid PK |
|
inventory_item_id |
uuid FK RESTRICT |
|
location_id |
uuid FK RESTRICT |
|
order_id |
uuid NULL |
Vazba na objednávku |
quantity |
int |
|
status |
varchar |
pending, confirmed, released |
created_at |
timestamptz |
|
released_at |
timestamptz NULL |
|
|
|
CHECK (quantity > 0) |
supplier_availability
| Pole |
Typ |
Popis |
id |
bigint PK |
|
inventory_item_id |
uuid FK RESTRICT |
|
supplier_id |
uuid FK RESTRICT |
|
availability_code |
varchar |
in_stock, on_order, unavailable |
availability_text |
varchar |
Textový popis od dodavatele |
eta |
timestamptz |
Očekávané datum naskladnění |
updated_at |
timestamptz |
|
availability_text
| Pole |
Typ |
Popis |
id |
bigint PK |
|
inventory_item_id |
uuid FK CASCADE |
|
locale |
varchar |
|
text |
varchar |
Text zobrazený zákazníkovi |
display_type |
varchar |
in_stock, low_stock, out_of_stock, on_order |
|
|
UNIQUE(inventory_item_id, locale) |
Vrstva 6 — kategorie a kolekce
Filosofie
category_closure je zdroj pravdy pro stromové dotazy. parent_id je záměrně redundantní convenience cache. Nikdy nezapisovat přímo do category_closure — vždy přes procedury.
Closure table — povinné procedury
category_insert_node(parent_id uuid, ...) → uuid
category_move_subtree(node_id uuid, new_parent_id uuid)
category_delete_subtree(node_id uuid)
category_rebuild_closure() -- záchranná funkce
category
| Pole |
Typ |
Popis |
id |
uuid PK |
|
parent_id |
uuid FK NULL |
Convenience cache |
code |
varchar UK |
|
legacy_id |
bigint |
Staré ID pro migraci |
depth |
int |
|
sort_order |
int |
|
is_active |
boolean |
|
is_internal |
boolean |
Nezobrazuje se na webu |
created_at |
timestamptz |
|
updated_at |
timestamptz |
|
category_closure
| Pole |
Typ |
Popis |
ancestor_id |
uuid FK |
|
descendant_id |
uuid FK |
|
depth |
int |
0 = uzel sám na sebe |
|
|
PRIMARY KEY (ancestor_id, descendant_id) |
|
|
CHECK (depth >= 0) |
category_translation
| Pole |
Typ |
Popis |
id |
bigint PK |
|
category_id |
uuid FK CASCADE |
|
locale |
varchar |
|
name |
varchar |
|
description |
text |
|
slug |
varchar |
|
meta_title |
varchar |
|
meta_description |
varchar |
|
created_at |
timestamptz |
|
updated_at |
timestamptz |
|
|
|
UNIQUE(category_id, locale) |
|
|
UNIQUE(locale, slug) |
product_category_assignment
| Pole |
Typ |
Popis |
id |
bigint PK |
|
product_id |
uuid FK CASCADE |
|
category_id |
uuid FK RESTRICT |
|
is_primary |
boolean |
|
sort_order |
int |
|
|
|
UNIQUE(product_id, category_id) |
|
|
UNIQUE(product_id) WHERE is_primary = true |
category_migration_map
| Pole |
Typ |
Popis |
id |
bigint PK |
|
legacy_id |
bigint |
Staré číselné ID |
new_category_id |
uuid FK |
|
legacy_pipe_value |
varchar |
\|123\| |
is_mapped |
boolean |
|
collection
| Pole |
Typ |
Popis |
id |
uuid PK |
|
code |
varchar UK |
|
type |
varchar |
manual, automated, seasonal |
is_active |
boolean |
|
sort_order |
int |
|
collection_translation
| Pole |
Typ |
Popis |
id |
bigint PK |
|
collection_id |
uuid FK CASCADE |
|
locale |
varchar |
|
name |
varchar |
|
slug |
varchar |
|
description |
text |
|
|
|
UNIQUE(collection_id, locale) |
|
|
UNIQUE(locale, slug) |
collection_product
| Pole |
Typ |
Popis |
collection_id |
uuid FK CASCADE |
|
product_id |
uuid FK CASCADE |
|
sort_order |
int |
|
|
|
PRIMARY KEY (collection_id, product_id) |
| Pole |
Typ |
Popis |
id |
uuid PK |
|
product_id |
uuid FK CASCADE |
|
variant_id |
uuid FK NULL |
Nullable — médium může patřit variantě |
type |
enum |
image, video, document |
url |
varchar |
|
alt |
varchar |
|
sort_order |
int |
|
is_primary |
boolean |
|
created_at |
timestamptz |
|
|
|
UNIQUE(product_id) WHERE is_primary = true AND variant_id IS NULL |
|
|
UNIQUE(variant_id) WHERE is_primary = true |
variant_id musí patřit ke stejnému product_id. Hlídáno triggerem — FK to nezaručí.
product_relation
| Pole |
Typ |
Popis |
id |
bigint PK |
|
product_id |
uuid FK CASCADE |
|
related_product_id |
uuid FK CASCADE |
|
relation_type |
varchar |
related, category_related |
sort_order |
int |
|
created_at |
timestamptz |
|
|
|
UNIQUE(product_id, related_product_id, relation_type) |
|
|
CHECK (product_id <> related_product_id) |
product_accessory
| Pole |
Typ |
Popis |
id |
bigint PK |
|
product_id |
uuid FK CASCADE |
|
accessory_product_id |
uuid FK CASCADE |
|
sort_order |
int |
|
|
|
UNIQUE(product_id, accessory_product_id) |
|
|
CHECK (product_id <> accessory_product_id) |
product_merchandising
| Pole |
Typ |
Popis |
id |
bigint PK |
|
product_id |
uuid FK CASCADE |
UNIQUE — 1:1 s produktem |
is_featured |
boolean |
|
is_bestseller |
boolean |
|
is_recommended |
boolean |
|
is_sale |
boolean |
|
priority |
int |
|
badge_label |
varchar |
Štítek (stitek) |
in_slider_1 |
boolean |
|
in_slider_2 |
boolean |
|
slider_1_priority |
int |
|
slider_2_priority |
int |
|
| Pole |
Typ |
Popis |
id |
bigint PK |
|
product_id |
uuid FK CASCADE |
|
channel |
varchar |
google, heureka, zbozi |
key |
varchar |
|
value |
text |
|
created_at |
timestamptz |
|
updated_at |
timestamptz |
|
|
|
UNIQUE(product_id, channel, key) |
product_compliance
| Pole |
Typ |
Popis |
id |
bigint PK |
|
product_id |
uuid FK CASCADE |
|
fee_type |
varchar |
phe, recycling |
fee_code |
varchar |
|
linked_product_id |
uuid FK NULL |
Přidružená položka do košíku |
fee_amount |
int |
V haléřích |
|
|
CHECK (fee_amount >= 0) |
|
|
CHECK (product_id <> linked_product_id) |
product_search_document
| Pole |
Typ |
Popis |
id |
bigint PK |
|
product_id |
uuid FK CASCADE |
|
locale |
varchar |
|
search_text |
text |
Fulltext — název, kód, EAN, výrobce |
structured_data |
jsonb |
Cena, dostupnost, facety |
indexed_at |
timestamptz |
|
|
|
UNIQUE(product_id, locale) |
product_search_dirty
Dirty queue pro async reindex. Trigger pouze zapíše sem, worker provede reindex.
| Pole |
Typ |
Popis |
id |
bigint PK |
|
product_id |
uuid FK CASCADE |
|
locale |
varchar |
|
reasons |
varchar[] |
{price_changed, stock_changed, content_changed, …} |
created_at |
timestamptz |
|
processed_at |
timestamptz NULL |
NULL = čeká |
attempts |
int |
Počet pokusů |
last_error |
text NULL |
Poslední chyba |
locked_at |
timestamptz NULL |
Kdy si job vzal worker |
locked_by |
varchar NULL |
Identifikace workeru |
|
|
UNIQUE(product_id, locale) WHERE processed_at IS NULL |
Při vícenásobné změně: INSERT ... ON CONFLICT DO UPDATE SET reasons = array(SELECT DISTINCT unnest(reasons || EXCLUDED.reasons)) — distinct merge zamezí duplicitám v array.
Audit log
entity_audit_log
| Pole |
Typ |
Popis |
id |
bigint PK |
|
entity_type |
varchar |
product, price, inventory_level, category, translation, … |
entity_id |
text |
Univerzální — pokryje uuid i bigint PK |
action |
varchar |
insert, update, delete |
old_data |
jsonb |
|
new_data |
jsonb |
|
actor_type |
varchar |
editor, import, system |
actor_id |
uuid NULL |
|
request_id |
uuid NULL |
Pro dohledání konkrétního HTTP requestu nebo importní dávky |
source |
varchar NULL |
api, admin, import, cron |
created_at |
timestamptz |
|
Migrační tracking
migration_batch
| Pole |
Typ |
Popis |
id |
uuid PK |
|
source |
varchar |
Název zdrojového systému / tabulky |
started_at |
timestamptz |
|
finished_at |
timestamptz NULL |
|
status |
varchar |
running, done, failed |
total_rows |
int |
|
processed_rows |
int |
|
migration_error
| Pole |
Typ |
Popis |
id |
bigint PK |
|
batch_id |
uuid FK |
|
entity_type |
varchar |
|
legacy_id |
varchar |
|
error |
text |
|
raw_data |
jsonb |
|
created_at |
timestamptz |
|
ON DELETE pravidla — přehled
| Vztah |
Pravidlo |
product → product_translation |
CASCADE |
product → product_variant |
CASCADE |
product → product_slug |
CASCADE |
product → product_supplier_feed |
CASCADE |
product → product_category_assignment |
CASCADE |
product → product_media |
CASCADE |
product → product_search_document |
CASCADE |
product → order snapshot |
nikdy FK na živý produkt |
brand → product |
RESTRICT |
supplier → product_supplier_feed |
RESTRICT |
category → product_category_assignment |
RESTRICT |
option_group → option_value |
CASCADE |
option_value → product_variant_option_value |
RESTRICT |
price_set → price |
CASCADE |
inventory_item → inventory_level |
CASCADE |
promotion → promotion_product/brand/category |
CASCADE |
Doporučené pořadí implementace
1. product, brand, supplier, product_supplier_feed, product_supplier_feed_raw
2. product_translation, brand_translation, product_slug
3. product_variant, option_group, option_value, product_variant_option_value
attribute, product_attribute_value
4. region, price_set, price, tax_rate
promotion, promotion_product, promotion_brand, promotion_category
5. inventory_item, stock_location, inventory_level, inventory_reservation
supplier_availability, availability_text
6. category + category_closure + procedury
category_translation, product_category_assignment, category_migration_map
7. collection, product_media, product_relation, product_accessory
product_merchandising, product_external_metadata, product_compliance
8. product_search_document, product_search_dirty
9. entity_audit_log
10. migration_batch, migration_error
11. order item snapshot (samostatná vrstva)
Otevřené otázky
- Order item snapshot — které pole se snapshotuje a v jakém formátu (flat sloupce vs. jsonb blob)?
- Facety — samostatná vrstva nad atributy nebo odvozeno z
product_attribute_value při indexaci?
- Constraint překryvu cen — aplikační procedura nebo DB trigger?
- Closure table procedury — PL/pgSQL nebo aplikační vrstvou?
inventory_level.reserved_quantity — cache triggerem z inventory_reservation, nebo počítat za běhu a sloupec odstranit?
product_supplier_feed_raw retence — počet posledních N importů, nebo věk záznamů?