Files
PriceForge/bible-local/architecture.md
Mikhail Chusavitin f48615e8a9 Modularize Go files, extract JS to static, implement competitor pricelists
Go refactoring:
- Split handlers/pricing.go (2446→291 lines) into 5 focused files
- Split services/stock_import.go (1334→~400 lines) into stock_mappings.go + stock_parse.go
- Split services/sync/service.go (1290→~250 lines) into 3 files

JS extraction:
- Move all inline <script> blocks to web/static/js/ (6 files)
- Templates reduced: admin_pricing 2873→521, lot 1531→304, vendor_mappings 1063→169, etc.

Competitor pricelists (migrations 033-039):
- qt_competitors + partnumber_log_competitors tables
- Excel import with column mapping, dedup, bulk insert
- p/n→lot resolution via weighted_median, discount applied
- Unmapped p/ns written to qt_vendor_partnumber_seen
- Quote counts (unique/total) shown on /admin/competitors
- price_method="weighted_median", price_period_days=0 stored explicitly

Fix price_method/price_period_days for warehouse items:
- warehouse: weighted_avg, period=0
- competitor: weighted_median, period=0
- Removes misleading DB defaults (was: median/90)

Update bible: architecture.md, pricelist.md, history.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 07:44:10 +03:00

204 lines
7.7 KiB
Markdown

# 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)