- New unified append-only quote log table parts_log replaces three separate log tables (stock_log, partnumber_log_competitors, lot_log) - Migrations 042-049: extend supplier, create parts_log/import_formats/ ignore_rules, rework qt_lot_metadata composite PK, add lead_time_weeks to pricelist_items, backfill data, migrate ignore rules - New services: PartsLogBackfillService, ImportFormatService, UnifiedImportService; new world pricelist type (all supplier types) - qt_lot_metadata PK changed to (lot_name, pricelist_type); all queries now filter WHERE pricelist_type='estimate' - Fix pre-existing bug: qt_component_usage_stats column names quotes_last30d/quotes_last7d (no underscore) — added explicit gorm tags - Bible: full table inventory, baseline schema snapshot, updated pricelist/ data-rules/api/history/architecture docs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3.2 KiB
Data Rules
Component Categories (lot_category)
Source
Categories are always taken from lot.lot_category (table lot).
NOT from qt_lot_metadata.
NOT derived from the LOT name or article name.
When creating pricelists
- Estimate: include only components with
current_price > 0andis_hidden = 0(pricelist_type='estimate'), then loadlot.lot_category. - Warehouse: load
lot.lot_categoryfor all positions (pricelist_type='warehouse'). - Competitor: load
lot.lot_categoryfor all positions (pricelist_type='competitor'). - World: load
lot.lot_categoryfor all positions (pricelist_type='world'). - Persist into
lot_categorycolumn ofqt_pricelist_items.
Model
type PricelistItem struct {
LotCategory *string `gorm:"column:lot_category;size:50" json:"category,omitempty"`
// JSON field is "category" for the frontend
}
Rules
- Category is not a virtual field — it is persisted to DB when the pricelist is created.
- JOIN with
lotis only needed forlot_description; category is already inqt_pricelist_items. - Default value when category is absent in source or LOT row is missing:
PART_.
Price Method
- Estimate: method determined by per-component settings in
qt_lot_metadata(pricelist_type='estimate'). Default:median. - Warehouse:
weighted_avg(quantity-weighted average). Defaultperiod_days=7. - Competitor:
weighted_median.qty NULL→ weight = 1. - World:
weighted_medianacross all supplier types.qty NULL→ weight = 1.
supplier_type
The supplier table supplier_type column determines which pricelist(s) a supplier's quotes feed:
| supplier_type | Feeds into |
|---|---|
trader |
estimate, world |
self |
warehouse, world |
competitor |
competitor, world |
price_uplift: divisor applied at import. effective_price = raw_price / price_uplift. All prices in parts_log are always in USD.
Defaults by type:
| supplier_type | price_uplift | default_lead_time_weeks |
|---|---|---|
| trader | 1.0 | NULL |
| self | 1.0 | 0 |
| competitor | 1.3 | 2 |
offer_type
parts_log.offer_type ENUM: 'private' or 'public' (default 'public').
Selected at import time. Both types are currently included in all pricelist calculations.
qt_ignore_rules
Replaces the is_ignored mechanism in qt_vendor_partnumber_seen. Checked at import before mapping.
| field | match_type | behavior |
|---|---|---|
| partnumber / vendor / description | exact / glob / contains | DROP row if matched |
Order of checks at import: ignore rules → partnumber book → write to parts_log.
Filtering by qt_partnumber_book_items
Rule (CRITICAL): only parts_log rows with a non-NULL lot_name are included in any pricelist. lot_name is populated by the daily backfill job (PartsLogBackfillService) which resolves partnumbers via qt_partnumber_book_items. Rows that cannot be resolved remain with lot_name=NULL and are excluded from all pricelists.
Component Data Source
- Source: MariaDB (not SQLite).
handlers/component.gousescomponentService.List()(MariaDB).