- 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>
10 KiB
10 KiB
Architecture
Tech Stack
| Component | Technology |
|---|---|
| Backend | Go 1.22+ (go.mod: 1.24.0) |
| Web framework | Gin |
| ORM | GORM |
| Database | MariaDB 11 |
| Frontend | htmx + Tailwind CDN (no build step) |
| Excel | excelize |
| Go module | git.mchus.pro/mchus/priceforge |
Directory Structure
PriceForge/
├── bible/ # Architectural documentation (you are here)
├── bin/ # Build output
├── cmd/
│ ├── pfs/ # Main server (main.go ~749 lines)
│ ├── cron/ # Cron job runner
│ ├── importer/ # Stock import utility
│ ├── migrate/ # Migration tool
│ └── migrate_ops_projects/ # Legacy project migrator
├── data/ # Data files
├── internal/ # Application code
│ ├── appmeta/ # Application metadata
│ ├── appstate/ # Global state
│ ├── config/ # YAML config loading
│ ├── db/ # DB connection, connection pool
│ ├── dbutil/ # DB utilities
│ ├── handlers/ # HTTP handlers (12 files)
│ ├── localdb/ # SQLite for local settings
│ ├── lotmatch/ # LOT mapping resolver
│ ├── middleware/ # HTTP middleware
│ ├── models/ # GORM models and migrations
│ ├── repository/ # Data access layer (11 files)
│ ├── services/ # Business logic
│ │ ├── alerts/ # Alert service
│ │ ├── pricelist/ # Pricelist service
│ │ ├── pricing/ # Price calculation
│ │ ├── sync/ # Sync (4 files)
│ │ ├── stock_import.go # Stock import: core, Import, replaceStockLogs
│ │ ├── stock_mappings.go # Stock import: mappings, ignore rules, seen-index
│ │ ├── stock_parse.go # Stock import: XLSX/MXL parsing, date detection
│ │ ├── competitor_import.go # Competitor Excel import, pricelist build
│ │ ├── vendor_mapping.go # Vendor mapping service
│ │ └── component.go # Component service
│ ├── tasks/ # Background Task Manager
│ └── warehouse/ # Warehouse price calculation
├── migrations/ # SQL migrations (39 files)
├── scripts/ # Build and release scripts
├── web/
│ ├── static/
│ │ ├── app.css # Tailwind CSS
│ │ └── js/ # Page JS (6 files, extracted from templates)
│ └── templates/ # HTML templates (shell only, JS in static/js/)
├── CLAUDE.md # AI agent instructions
├── README.md # Quick start
└── todo.md # Operational task list
Application Layers (3-tier)
HTTP Request
│
▼
┌─────────────┐
│ Handlers │ internal/handlers/ — request parsing, validation, response
└──────┬──────┘
│
▼
┌─────────────┐
│ Services │ internal/services/ — business logic, orchestration
└──────┬──────┘
│
▼
┌─────────────┐
│ Repository │ internal/repository/ — SQL queries via GORM
└──────┬──────┘
│
▼
MariaDB 11
Key Modules
internal/handlers/ (12 files)
| File | Purpose |
|---|---|
web.go |
Page rendering |
component.go |
Component list and detail endpoints |
pricing.go |
PricingHandler struct, NewPricingHandler, stats, query helpers |
pricing_components.go |
ListComponents, GetComponentPricing, UpdatePrice, RecalculateAll, PreviewPrice |
pricing_alerts.go |
ListAlerts, AcknowledgeAlert, ResolveAlert, IgnoreAlert |
pricing_stock.go |
ImportStockLog, stock mappings, ignore rules |
pricing_vendor.go |
Vendor mappings CRUD, CSV import/export |
pricing_lots.go |
ListLotsTable, CreateLot, ListLots, GetLotStats, PartnumberBooks |
pricelist.go |
Pricelist CRUD and CSV export |
competitor.go |
Competitor CRUD, import, ParseHeaders |
setup.go |
DB connection setup and test |
sync.go |
Sync diagnostic endpoints |
internal/services/
| File/dir | Purpose |
|---|---|
pricelist/ |
Create Estimate, Warehouse, Competitor pricelists |
pricing/ |
Price calc: median, weighted_median, coefficients |
alerts/ |
Alert generation (price staleness, demand) |
stock_import.go |
Stock import core: Import, replaceStockLogs, buildWarehousePricelistItems |
stock_mappings.go |
Mappings, ignore rules, seen-index (upsertSeenRows) |
stock_parse.go |
XLSX/MXL parsing, date detection, XLSX helper functions |
competitor_import.go |
Competitor Excel import, p/n→lot resolution, pricelist build |
sync/service.go |
Sync service struct, status, heartbeat |
sync/sync_pricelists.go |
Pricelist sync to local DB |
sync/sync_changes.go |
Push pending changes (configurations, projects) |
sync/sync_import.go |
Import configurations/projects to local DB |
vendor_mapping.go |
Resolve partnumber → LOT, bundles, ignore |
component.go |
Component listing and search |
internal/warehouse/
snapshot.go — warehouse pricelist creation: weighted average, bundle expansion, allocation.
internal/lotmatch/
matcher.go — vendor partnumber → LOT resolver:
- Exact match
(vendor, partnumber) - Fallback:
(vendor='', partnumber)
internal/tasks/
Task Manager: Submit, status polling, task types. See background-tasks.md.
HTML Templates (web/templates/)
| Template | Route | Purpose |
|---|---|---|
base.html |
— | Layout, navigation |
admin_pricing.html |
/admin/pricing |
Tabs: Estimate, Warehouse, Alerts |
lot.html |
/lot |
Tabs: LOT components, Mappings |
pricelists.html |
/pricelists |
Pricelist listing |
pricelist_detail.html |
/pricelists/:id |
Pricelist detail view |
vendor_mappings.html |
/vendor-mappings |
Vendor mapping UI |
components_list.html |
/partials/components |
Components partial |
setup.html |
/setup |
DB connection setup |
sync_status.html |
partial | Online/offline indicator |
Navigation (base.html)
LOT → /lot
Pricing Admin → /admin/pricing
Settings → /setup
Configurator, projects, and export links must not appear in the menu.
Database
- Primary: MariaDB 11 (all operational data)
- Local: SQLite (only app settings and technical data; not used in
pfsruntime) - Migrations:
migrations/— SQL files applied automatically on startup viaRunSQLMigrations
Authoritative Table List (baseline 2026-03-21, before migrations 042–049)
Active — core:
| Table | Purpose |
|---|---|
lot |
Components / articles (primary catalog) |
qt_lot_metadata |
Component price metadata. PK (lot_name) before migration 046, (lot_name, pricelist_type) after. All queries must filter WHERE pricelist_type='estimate' after 046. |
qt_pricelists |
Pricelist headers (source, version, timestamps) |
qt_pricelist_items |
Pricelist item rows. Gains lead_time_weeks INT NULL in migration 047. |
qt_categories |
Category definitions (code, name, name_ru). FK from qt_lot_metadata.category_id. |
qt_partnumber_book_items |
Canonical PN catalog; one row per partnumber, lots_json maps to lot names |
qt_partnumber_books |
Versioned PN book snapshots with partnumbers_json |
qt_vendor_partnumber_seen |
Seen registry (unique by partnumber). is_ignored + is_pattern flags. |
stock_ignore_rules |
Active ignore patterns for stock import (target, match_type, pattern). Used in ListStockIgnoreRules, UpsertStockIgnoreRule, DeleteStockIgnoreRule. |
qt_pricing_alerts |
Price alerts (staleness, spikes, drops) |
qt_component_usage_stats |
Per-lot quote counters and trend. Column names: quotes_last30d, quotes_last7d (no underscore before digit). Go model has explicit gorm:"column:..." tags for these. |
qt_configurations |
Client configurations (quote documents). Written by child app, read by PriceForge. |
qt_projects |
Client projects. Written by child app. |
qt_scheduler_runs |
Embedded scheduler job state |
qt_schema_migrations |
Applied SQL migration log |
supplier |
Supplier catalog. Extended in migration 042 with supplier_code, supplier_type, price_uplift, etc. Before 042: only supplier_name + supplier_comment. |
Active — competitor subsystem:
| Table | Purpose |
|---|---|
qt_competitors |
Competitor profiles (name, code, price_uplift, column_mapping JSON) |
partnumber_log_competitors |
Legacy competitor quote log. Read-only archive after migration 048. FK to qt_competitors.id. |
Active — legacy quote archives (read-only after parts_log backfill):
| Table | Purpose |
|---|---|
lot_log |
Historical trader quotes (lot_name, supplier, price, date). FK to lot and supplier. |
stock_log |
Historical warehouse stock records (partnumber, supplier, price, qty). No FK to supplier. |
Active — sync with child app:
| Table | Purpose |
|---|---|
qt_client_schema_state |
Per-client sync state and pricelist version tracking |
qt_pricelist_sync_status |
Pricelist sync timestamps per user |
Kept but not referenced in Go code:
| Table | Notes |
|---|---|
machine |
Legacy machine catalog. Has FK from machine_log. No Go code references. |
machine_log |
Legacy machine quote log. FK to machine and supplier. No Go code references. |
Dropped (2026-03-21):
| Table | Reason |
|---|---|
qt_client_local_migrations |
Used by old sync mechanism; removed |
qt_price_overrides |
Model existed but feature was never implemented |
qt_users |
Model existed but authentication was never implemented in PriceForge |
Startup Flow
- Parse flags (
--config,--migrate,--version) - Load
config.yaml - Fail-fast: connect to MariaDB; exit on failure
- Apply SQL migrations
- Start Gin router on
host:port - Auto-open browser (if loopback address)