Data Model
This page describes every entity in the Quantaprice system and how they relate to each other.
Entity Relationship Overview
┌──────────┐
│ Currency │
└────┬─────┘
│ currency_code
v
┌─────────┐ ┌──────────┐ ┌───────────┐
│ Article │◄──────►│ Price │◄──────►│ Pricelist │
│ (SKU) │ sku │ │ code │ │
└────┬────┘ └──────────┘ └─────┬─────┘
│ │ parent_code
│ vat_class override │ (inheritance)
v v
┌──────────┐ ┌───────────┐ ┌──────────┐
│ Tax Area │───►│ VAT Rate │◄────────│VAT Class │
└──────────┘ └───────────┘ └──────────┘
Prices are the central join — each price connects an Article to a Pricelist at a specific quantity break and effective date. Pricelists define the pricing context (currency, VAT mode), and can inherit from a parent pricelist.
Articles
Products or SKUs in your catalog.
| Field | Type | Description |
|---|---|---|
sku | string | Unique SKU code (primary identifier) |
name | string | Display name |
metadata | object | Flexible key/value attributes (e.g. {"brand": "Acme", "category": "tools"}) |
updated_at | datetime | Last modification |
deleted_at | datetime | Soft-delete timestamp (null = active) |
Article metadata
Metadata is a free-form key/value map. These attributes can be used to:
- Filter and search articles
- Target pricing rules to specific product segments (e.g. all articles in a category)
- Store custom fields without schema changes
Bundled articles
An article can be a bundle — a composite product made up of other articles. A bundle contains a list of components, each with a reference SKU and quantity. Bundle prices are calculated by aggregating component prices.
Pricelists
Define the pricing context: what currency, how VAT is handled, and optional inheritance.
| Field | Type | Description |
|---|---|---|
code | string | Unique pricelist code |
currency_code | string | ISO 4217 currency (e.g. SEK, EUR) |
vat_mode | string | NET (prices exclude VAT) or GROSS (prices include VAT) |
parent_code | string | Parent pricelist for inheritance (optional) |
start_date | datetime | Effective start date |
end_date | datetime | Effective end date (optional) |
name | string | Display name |
description | string | Description |
price_rules | array | Dynamic price transformation rules (optional) |
metadata | object | Custom attributes |
updated_by | string | Last modifier |
updated_at | datetime | Last modification |
deleted_at | datetime | Soft-delete timestamp |
Pricelist inheritance
Pricelists can form a hierarchy using parent_code. When a price is not found on a pricelist, the system walks up the parent chain until it finds one. This lets you define a base price list and override only the prices that differ in child lists.
Price rules
Rules are JEXL expressions attached to a pricelist for dynamic price transformation (e.g. apply a 10% markup, set a floor price). Each rule has:
| Field | Type | Description |
|---|---|---|
code | string | Rule identifier |
expression | string | JEXL expression |
description | string | Human-readable description |
Prices
The central entity — each price connects an article (SKU) to a pricelist at a specific quantity break and effective date.
Price fields
When creating or updating a price, you provide a price structure for each quantity tier:
| Field | Type | Description |
|---|---|---|
quantity_break | number | Minimum quantity for this tier (default: 1) |
fixed | number | Fixed override price |
catalog | number | Catalog/list price |
cost | number | Cost price |
floor | number | Minimum allowed price |
recommended | number | Recommended retail price |
When reading a price, you also receive:
| Field | Type | Description |
|---|---|---|
sales_price | number | Computed final sales price (after rules, FX, VAT, rounding) |
Effective dating
Prices are effective-dated — each price has an effective_from timestamp. When querying, the system returns the most recent price that is effective at the requested point in time. This allows you to:
- Schedule future price changes
- Query historical prices at any past date
- Maintain a full audit trail of price changes
Quantity breaks
A single article/pricelist combination can have multiple price tiers based on quantity. For example:
| Quantity break | Price |
|---|---|
| 1 | 10.00 |
| 10 | 9.50 |
| 100 | 8.00 |
When querying with a quantity of 25, the system selects the tier for quantity break 10 (the highest break not exceeding the requested quantity).
VAT System
VAT calculation uses three entities working together:
Tax areas
Geographic regions with distinct tax rules.
| Field | Type | Description |
|---|---|---|
code | string | Unique code (e.g. SE, DE, US-CA) |
name | string | Display name |
is_active | boolean | Active status |
VAT classes
Product tax categories.
| Field | Type | Description |
|---|---|---|
code | string | Unique code (e.g. STANDARD, FOOD, BOOKS) |
name | string | Display name |
description | string | Description |
is_active | boolean | Active status |
VAT rates
The actual tax percentage, defined per (tax area, VAT class) pair with effective dating.
| Field | Type | Description |
|---|---|---|
tax_area_code | string | Tax area |
vat_class_code | string | VAT class |
rate | number | Percentage (e.g. 25 for 25%) |
start_date | datetime | Effective from |
VAT calculation flow
- Resolve the tax area (from query parameters)
- Determine the VAT class for the article (default, or per-article override)
- Look up the effective VAT rate for that (tax area, VAT class) pair at the requested date
- Apply based on the pricelist's
vat_mode:- NET: Calculate VAT and add to get price inc. VAT
- GROSS: Back-calculate VAT and subtract to get price exc. VAT
- Round VAT amount to the currency's
vat_precisiondecimal places
Currencies
See Currencies for full documentation, including vat_precision and active/inactive behavior.
Exchange Rates
See Exchange Rates for full documentation, including auto-update scheduling, date lookup fallback, and cross-rate computation.
Rounding Profiles
Range-based rounding rules that can be applied per currency.
| Field | Type | Description |
|---|---|---|
code | string | Profile code |
currency_code | string | Target currency |
default_global | boolean | Use as default for all currencies |
default_for_currency | boolean | Use as default for this currency |
rules | array | Range-based rounding tiers |
Each rounding tier defines behavior for a price range. For example:
- Prices under 10: round to nearest 0.05
- Prices 10–100: round to nearest 0.10
- Prices over 100: round to nearest 1.00
Webhooks
Push notifications for entity changes. See Change Events for full documentation.
| Field | Type | Description |
|---|---|---|
url | string | Delivery URL |
secret | string | HMAC-SHA256 signing secret |
entity_type_filter | string | Filter by entity type (optional, null = all) |
active | boolean | Enabled status |
Change Log
An append-only feed of all entity changes, accessible via polling. See Change Events for full documentation.
| Field | Type | Description |
|---|---|---|
entity_type | string | Which entity changed (e.g. article, price) |
change_type | string | created, updated, or deleted |
entity_code | string | Business identifier (SKU, pricelist code, etc.) |
composite_key | string | Secondary identifier when needed (e.g. pricelist code for prices) |
changed_at | datetime | When the change occurred |
changed_by | string | Who made the change |
content_hash | string | Hash for deduplication |
Settings
Runtime configuration key-value pairs.
| Field | Type | Description |
|---|---|---|
key | string | Setting name (e.g. fx.rate.auto.update) |
value | string | Setting value |
Settings are versioned — each update creates a new entry, preserving history.
Price Calculation Pipeline
When a price is requested, the system processes it through a fixed pipeline:
- Resolve metadata — currency, VAT mode, article attributes
- Fetch base price — from the pricelist (or parent chain), selecting the correct quantity break and effective date
- Apply price rules — JEXL expressions on the pricelist (discounts, markups, overrides)
- Convert currency — FX conversion if the query requests a different currency
- Apply VAT — add (NET mode) or subtract (GROSS mode), rounded to
vat_precision - Apply rounding — final price rounding per the rounding profile
- Return sales price — with full audit trail of each step
Every step is recorded so that the final price is fully auditable and reproducible.