Files
PriceForge/bible-local/architecture.md
Mikhail Chusavitin 5f8aec456b Unified Quote Journal (parts_log) v3
- 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>
2026-03-21 17:25:54 +03:00

10 KiB
Raw Permalink Blame History

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:

  1. Exact match (vendor, partnumber)
  2. 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 pfs runtime)
  • Migrations: migrations/ — SQL files applied automatically on startup via RunSQLMigrations

Authoritative Table List (baseline 2026-03-21, before migrations 042049)

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

  1. Parse flags (--config, --migrate, --version)
  2. Load config.yaml
  3. Fail-fast: connect to MariaDB; exit on failure
  4. Apply SQL migrations
  5. Start Gin router on host:port
  6. Auto-open browser (if loopback address)