Expand 03-database.md with complete table structure reference for all 23 tables in the final schema: active QuoteForge tables, competitor subsystem, legacy RFQ tables, and server-side-only tables. Also clarifies access patterns per group and notes removal of qt_client_local_migrations from the schema. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
14 KiB
03 - Database
SQLite
SQLite is the local runtime database.
Main tables:
| Table | Purpose |
|---|---|
local_components |
synced component metadata |
local_pricelists |
local pricelist headers |
local_pricelist_items |
local pricelist rows, the only runtime price source |
local_projects |
user projects |
local_configurations |
user configurations |
local_configuration_versions |
immutable revision snapshots |
local_partnumber_books |
partnumber book headers |
local_partnumber_book_items |
PN -> LOT catalog payload |
pending_changes |
sync queue |
connection_settings |
encrypted MariaDB connection settings |
app_settings |
local app state |
local_schema_migrations |
applied local migration markers |
Rules:
- cache tables may be rebuilt if local migration recovery requires it;
- user-authored tables must not be dropped as a recovery shortcut;
local_pricelist_itemsis the only valid runtime source of prices;- configuration
itemsandvendor_specare stored as JSON payloads inside configuration rows.
MariaDB
MariaDB is the central sync database (RFQ_LOG). Final schema as of 2026-03-21.
QuoteForge tables (qt_* and stock_*)
Runtime read:
qt_categories— pricelist categoriesqt_lot_metadata— component metadata, price settingsqt_pricelists— pricelist headers (source: estimate / warehouse / competitor)qt_pricelist_items— pricelist rowsstock_log— raw supplier price log, source for pricelist generationstock_ignore_rules— patterns to skip during stock importqt_partnumber_books— partnumber book headersqt_partnumber_book_items— PN→LOT catalog payload
Runtime read/write:
qt_projects— projectsqt_configurations— configurationsqt_client_schema_state— per-client sync status and version trackingqt_pricelist_sync_status— pricelist sync timestamps per user
Insert-only tracking:
qt_vendor_partnumber_seen— vendor partnumbers encountered during sync
Server-side only (not queried by client runtime):
qt_component_usage_stats— aggregated component popularity stats (written by server jobs)qt_pricing_alerts— price anomaly alerts (models exist in Go; feature disabled in runtime)qt_schema_migrations— server migration history (applied viago run ./cmd/qfs -migrate)qt_scheduler_runs— server background job tracking (no Go code references it in this repo)
Competitor subsystem (server-side only, not used by QuoteForge Go code)
qt_competitors— competitor registrypartnumber_log_competitors— competitor price log (FK → qt_competitors)
These tables exist in the schema and are maintained by another tool or workflow.
QuoteForge references competitor pricelists only via qt_pricelists (source='competitor').
Legacy RFQ tables (pre-QuoteForge, no Go code references)
lot— original component registry (data preserved; superseded byqt_lot_metadata)lot_log— original supplier price log (superseded bystock_log)supplier— supplier registry (FK target for lot_log and machine_log)machine— device model registrymachine_log— device price/quote log
These tables are retained for historical data. QuoteForge does not read or write them at runtime.
Rules:
- QuoteForge runtime must not depend on any legacy RFQ tables;
- stock enrichment happens during sync and is persisted into SQLite;
- normal UI requests must not query MariaDB tables directly;
qt_client_local_migrationswas removed from the schema on 2026-03-21 (was in earlier drafts).
MariaDB Table Structures
Full column reference as of 2026-03-21 (RFQ_LOG final schema).
qt_categories
| Column | Type | Notes |
|---|---|---|
| id | bigint UNSIGNED PK AUTO_INCREMENT | |
| code | varchar(20) UNIQUE NOT NULL | |
| name | varchar(100) NOT NULL | |
| name_ru | varchar(100) | |
| display_order | bigint DEFAULT 0 | |
| is_required | tinyint(1) DEFAULT 0 |
qt_client_schema_state
PK: (username, hostname)
| Column | Type | Notes |
|---|---|---|
| username | varchar(100) | |
| hostname | varchar(255) DEFAULT '' | |
| last_applied_migration_id | varchar(128) | |
| app_version | varchar(64) | |
| last_sync_at | datetime | |
| last_sync_status | varchar(32) | |
| pending_changes_count | int DEFAULT 0 | |
| pending_errors_count | int DEFAULT 0 | |
| configurations_count | int DEFAULT 0 | |
| projects_count | int DEFAULT 0 | |
| estimate_pricelist_version | varchar(128) | |
| warehouse_pricelist_version | varchar(128) | |
| competitor_pricelist_version | varchar(128) | |
| last_sync_error_code | varchar(128) | |
| last_sync_error_text | text | |
| last_checked_at | datetime NOT NULL | |
| updated_at | datetime NOT NULL |
qt_component_usage_stats
PK: lot_name
| Column | Type | Notes |
|---|---|---|
| lot_name | varchar(255) | |
| quotes_total | bigint DEFAULT 0 | |
| quotes_last30d | bigint DEFAULT 0 | |
| quotes_last7d | bigint DEFAULT 0 | |
| total_quantity | bigint DEFAULT 0 | |
| total_revenue | decimal(14,2) DEFAULT 0 | |
| trend_direction | enum('up','stable','down') DEFAULT 'stable' | |
| trend_percent | decimal(5,2) DEFAULT 0 | |
| last_used_at | datetime(3) |
qt_competitors
| Column | Type | Notes |
|---|---|---|
| id | bigint UNSIGNED PK AUTO_INCREMENT | |
| name | varchar(255) NOT NULL | |
| code | varchar(100) UNIQUE NOT NULL | |
| delivery_basis | varchar(50) DEFAULT 'DDP' | |
| currency | varchar(10) DEFAULT 'USD' | |
| column_mapping | longtext JSON | |
| is_active | tinyint(1) DEFAULT 1 | |
| created_at | timestamp | |
| updated_at | timestamp ON UPDATE | |
| price_uplift | decimal(8,4) DEFAULT 1.3 | effective_price = price / price_uplift |
qt_configurations
| Column | Type | Notes |
|---|---|---|
| id | bigint UNSIGNED PK AUTO_INCREMENT | |
| uuid | varchar(36) UNIQUE NOT NULL | |
| user_id | bigint UNSIGNED | |
| owner_username | varchar(100) NOT NULL | |
| app_version | varchar(64) | |
| project_uuid | char(36) | FK → qt_projects.uuid ON DELETE SET NULL |
| name | varchar(200) NOT NULL | |
| items | longtext JSON NOT NULL | component list |
| total_price | decimal(12,2) | |
| notes | text | |
| is_template | tinyint(1) DEFAULT 0 | |
| created_at | datetime(3) | |
| custom_price | decimal(12,2) | |
| server_count | bigint DEFAULT 1 | |
| server_model | varchar(100) | |
| support_code | varchar(20) | |
| article | varchar(80) | |
| pricelist_id | bigint UNSIGNED | FK → qt_pricelists.id |
| warehouse_pricelist_id | bigint UNSIGNED | FK → qt_pricelists.id |
| competitor_pricelist_id | bigint UNSIGNED | FK → qt_pricelists.id |
| disable_price_refresh | tinyint(1) DEFAULT 0 | |
| only_in_stock | tinyint(1) DEFAULT 0 | |
| line_no | int | position within project |
| price_updated_at | timestamp | |
| vendor_spec | longtext JSON |
qt_lot_metadata
PK: lot_name
| Column | Type | Notes |
|---|---|---|
| lot_name | varchar(255) | |
| category_id | bigint UNSIGNED | FK → qt_categories.id |
| vendor | varchar(50) | |
| model | varchar(100) | |
| specs | longtext JSON | |
| current_price | decimal(12,2) | cached computed price |
| price_method | enum('manual','median','average','weighted_median') DEFAULT 'median' | |
| price_period_days | bigint DEFAULT 90 | |
| price_updated_at | datetime(3) | |
| request_count | bigint DEFAULT 0 | |
| last_request_date | date | |
| popularity_score | decimal(10,4) DEFAULT 0 | |
| price_coefficient | decimal(5,2) DEFAULT 0 | markup % |
| manual_price | decimal(12,2) | |
| meta_prices | varchar(1000) | raw price samples JSON |
| meta_method | varchar(20) | method used for last compute |
| meta_period_days | bigint DEFAULT 90 | |
| is_hidden | tinyint(1) DEFAULT 0 |
qt_partnumber_books
| Column | Type | Notes |
|---|---|---|
| id | bigint UNSIGNED PK AUTO_INCREMENT | |
| version | varchar(30) UNIQUE NOT NULL | |
| created_at | timestamp | |
| created_by | varchar(100) | |
| is_active | tinyint(1) DEFAULT 0 | only one active at a time |
| partnumbers_json | longtext DEFAULT '[]' | flat list of partnumbers |
qt_partnumber_book_items
| Column | Type | Notes |
|---|---|---|
| id | bigint UNSIGNED PK AUTO_INCREMENT | |
| partnumber | varchar(255) UNIQUE NOT NULL | |
| lots_json | longtext NOT NULL | JSON array of lot_names |
| description | varchar(10000) |
qt_pricelists
| Column | Type | Notes |
|---|---|---|
| id | bigint UNSIGNED PK AUTO_INCREMENT | |
| source | varchar(20) DEFAULT 'estimate' | 'estimate' / 'warehouse' / 'competitor' |
| version | varchar(20) NOT NULL | UNIQUE with source |
| created_at | datetime(3) | |
| created_by | varchar(100) | |
| is_active | tinyint(1) DEFAULT 1 | |
| usage_count | bigint DEFAULT 0 | |
| expires_at | datetime(3) | |
| notification | varchar(500) | shown to clients on sync |
qt_pricelist_items
| Column | Type | Notes |
|---|---|---|
| id | bigint UNSIGNED PK AUTO_INCREMENT | |
| pricelist_id | bigint UNSIGNED NOT NULL | FK → qt_pricelists.id |
| lot_name | varchar(255) NOT NULL | INDEX with pricelist_id |
| lot_category | varchar(50) | |
| price | decimal(12,2) NOT NULL | |
| price_method | varchar(20) | |
| price_period_days | bigint DEFAULT 90 | |
| price_coefficient | decimal(5,2) DEFAULT 0 | |
| manual_price | decimal(12,2) | |
| meta_prices | varchar(1000) |
qt_pricelist_sync_status
PK: username
| Column | Type | Notes |
|---|---|---|
| username | varchar(100) | |
| last_sync_at | datetime NOT NULL | |
| updated_at | datetime NOT NULL | |
| app_version | varchar(64) |
qt_pricing_alerts
| Column | Type | Notes |
|---|---|---|
| id | bigint UNSIGNED PK AUTO_INCREMENT | |
| lot_name | varchar(255) NOT NULL | |
| alert_type | enum('high_demand_stale_price','price_spike','price_drop','no_recent_quotes','trending_no_price') | |
| severity | enum('low','medium','high','critical') DEFAULT 'medium' | |
| message | text NOT NULL | |
| details | longtext JSON | |
| status | enum('new','acknowledged','resolved','ignored') DEFAULT 'new' | |
| created_at | datetime(3) |
qt_projects
| Column | Type | Notes |
|---|---|---|
| id | bigint UNSIGNED PK AUTO_INCREMENT | |
| uuid | char(36) UNIQUE NOT NULL | |
| owner_username | varchar(100) NOT NULL | |
| code | varchar(100) NOT NULL | UNIQUE with variant |
| variant | varchar(100) DEFAULT '' | UNIQUE with code |
| name | varchar(200) | |
| tracker_url | varchar(500) | |
| is_active | tinyint(1) DEFAULT 1 | |
| is_system | tinyint(1) DEFAULT 0 | |
| created_at | timestamp | |
| updated_at | timestamp ON UPDATE |
qt_schema_migrations
| Column | Type | Notes |
|---|---|---|
| id | bigint UNSIGNED PK AUTO_INCREMENT | |
| filename | varchar(255) UNIQUE NOT NULL | |
| applied_at | datetime(3) |
qt_scheduler_runs
PK: job_name
| Column | Type | Notes |
|---|---|---|
| job_name | varchar(100) | |
| last_started_at | datetime | |
| last_finished_at | datetime | |
| last_status | varchar(20) DEFAULT 'idle' | |
| last_error | text | |
| updated_at | timestamp ON UPDATE |
qt_vendor_partnumber_seen
| Column | Type | Notes |
|---|---|---|
| id | bigint UNSIGNED PK AUTO_INCREMENT | |
| source_type | varchar(32) NOT NULL | |
| vendor | varchar(255) DEFAULT '' | |
| partnumber | varchar(255) UNIQUE NOT NULL | |
| description | varchar(10000) | |
| last_seen_at | datetime(3) NOT NULL | |
| is_ignored | tinyint(1) DEFAULT 0 | |
| is_pattern | tinyint(1) DEFAULT 0 | |
| ignored_at | datetime(3) | |
| ignored_by | varchar(100) | |
| created_at | datetime(3) | |
| updated_at | datetime(3) |
stock_ignore_rules
| Column | Type | Notes |
|---|---|---|
| id | bigint UNSIGNED PK AUTO_INCREMENT | |
| target | varchar(20) NOT NULL | UNIQUE with match_type+pattern |
| match_type | varchar(20) NOT NULL | |
| pattern | varchar(500) NOT NULL | |
| created_at | timestamp |
stock_log
| Column | Type | Notes |
|---|---|---|
| stock_log_id | bigint UNSIGNED PK AUTO_INCREMENT | |
| partnumber | varchar(255) NOT NULL | INDEX with date |
| supplier | varchar(255) | |
| date | date NOT NULL | |
| price | decimal(12,2) NOT NULL | |
| quality | varchar(255) | |
| comments | text | |
| vendor | varchar(255) | INDEX |
| qty | decimal(14,3) |
partnumber_log_competitors
| Column | Type | Notes |
|---|---|---|
| id | bigint UNSIGNED PK AUTO_INCREMENT | |
| competitor_id | bigint UNSIGNED NOT NULL | FK → qt_competitors.id |
| partnumber | varchar(255) NOT NULL | |
| description | varchar(500) | |
| vendor | varchar(255) | |
| price | decimal(12,2) NOT NULL | |
| price_loccur | decimal(12,2) | local currency price |
| currency | varchar(10) | |
| qty | decimal(12,4) DEFAULT 1 | |
| date | date NOT NULL | |
| created_at | timestamp |
Legacy tables (lot / lot_log / machine / machine_log / supplier)
Retained for historical data only. Not queried by QuoteForge.
lot: lot_name (PK, char 255), lot_category, lot_description lot_log: lot_log_id AUTO_INCREMENT, lot (FK→lot), supplier (FK→supplier), date, price double, quality, comments supplier: supplier_name (PK, char 255), supplier_comment machine: machine_name (PK, char 255), machine_description machine_log: machine_log_id AUTO_INCREMENT, date, supplier (FK→supplier), country, opty, type, machine (FK→machine), customer_requirement, variant, price_gpl, price_estimate, qty, quality, carepack, lead_time_weeks, prepayment_percent, price_got, Comment
Migrations
SQLite:
- schema creation and additive changes go through GORM
AutoMigrate; - data fixes, index repair, and one-off rewrites go through
runLocalMigrations; - local migration state is tracked in
local_schema_migrations.
MariaDB:
- SQL files live in
migrations/; - they are applied by
go run ./cmd/qfs -migrate.