- 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>
97 lines
3.2 KiB
Markdown
97 lines
3.2 KiB
Markdown
# 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
|
|
|
|
1. **Estimate**: include only components with `current_price > 0` and `is_hidden = 0` (`pricelist_type='estimate'`), then load `lot.lot_category`.
|
|
2. **Warehouse**: load `lot.lot_category` for all positions (`pricelist_type='warehouse'`).
|
|
3. **Competitor**: load `lot.lot_category` for all positions (`pricelist_type='competitor'`).
|
|
4. **World**: load `lot.lot_category` for all positions (`pricelist_type='world'`).
|
|
5. Persist into `lot_category` column of `qt_pricelist_items`.
|
|
|
|
### Model
|
|
|
|
```go
|
|
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 `lot` is only needed for `lot_description`; category is already in `qt_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). Default `period_days=7`.
|
|
- **Competitor**: `weighted_median`. `qty NULL` → weight = 1.
|
|
- **World**: `weighted_median` across 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.go` uses `componentService.List()` (MariaDB).
|