# 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](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/` — 25 SQL files, applied automatically on startup ### Key Tables | Table | Purpose | |-------|---------| | `lot` | Components / articles (primary) | | `qt_lot_metadata` | Component price metadata | | `qt_pricelist_items` | Pricelist item rows | | `stock_log` | Stock import data | | `qt_partnumber_book_items` | Canonical PN catalog; one row per `partnumber`, with composition in `lots_json` | | `qt_partnumber_books` | Versioned PN book headers with `partnumbers_json` membership | | `qt_vendor_partnumber_seen` | Seen registry (unique by `partnumber`) + `is_ignored` flag | --- ## 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)