Files
QuoteForge/CLAUDE.md
2026-01-26 18:30:45 +03:00

376 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# QuoteForge - Claude Code Instructions
## Project Overview
QuoteForge — корпоративный инструмент для конфигурирования серверов и формирования коммерческих предложений (КП). Приложение интегрируется с существующей базой данных RFQ_LOG.
## Tech Stack
- **Language:** Go 1.22+
- **Web Framework:** Gin (github.com/gin-gonic/gin)
- **ORM:** GORM (gorm.io/gorm)
- **Database:** MariaDB 11 (existing database RFQ_LOG)
- **Frontend:** HTML templates + htmx + Tailwind CSS (CDN)
- **Excel Export:** excelize (github.com/xuri/excelize/v2)
- **Auth:** JWT (github.com/golang-jwt/jwt/v5)
## Project Structure
```
quoteforge/
├── cmd/
│ ├── server/main.go # Main HTTP server
│ ├── priceupdater/main.go # Cron job for price updates & alerts
│ └── importer/main.go # Import metadata from lot table
├── internal/
│ ├── config/config.go # YAML config loading
│ ├── models/ # GORM models
│ ├── handlers/ # Gin HTTP handlers
│ ├── services/ # Business logic
│ ├── middleware/ # Auth, CORS, roles
│ └── repository/ # Database queries
├── web/
│ ├── templates/ # Go HTML templates
│ └── static/ # CSS, JS
├── migrations/ # SQL migration files
├── config.yaml
└── go.mod
```
## Existing Database Tables (READ-ONLY - DO NOT MODIFY)
These tables are used by other systems. Our app only reads from them:
```sql
-- Component catalog
CREATE TABLE lot (
lot_name CHAR(255) PRIMARY KEY, -- e.g., "CPU_AMD_9654", "MB_INTEL_4.Sapphire_2S"
lot_description VARCHAR(10000)
);
-- Price history from suppliers
CREATE TABLE lot_log (
lot_log_id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
lot CHAR(255) NOT NULL, -- FK → lot.lot_name
supplier CHAR(255) NOT NULL, -- FK → supplier.supplier_name
date DATE NOT NULL,
price DOUBLE NOT NULL,
quality CHAR(255),
comments VARCHAR(15000),
FOREIGN KEY (lot) REFERENCES lot(lot_name),
FOREIGN KEY (supplier) REFERENCES supplier(supplier_name)
);
-- Supplier catalog
CREATE TABLE supplier (
supplier_name CHAR(255) PRIMARY KEY,
supplier_comment VARCHAR(10000)
);
```
## New Tables (prefix qt_)
QuoteForge creates these tables:
```sql
-- Users
CREATE TABLE qt_users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
role ENUM('viewer', 'editor', 'pricing_admin', 'admin') DEFAULT 'viewer',
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Component metadata (extends lot table)
CREATE TABLE qt_lot_metadata (
lot_name CHAR(255) PRIMARY KEY,
category_id INT,
vendor VARCHAR(50), -- Parsed from lot_name: CPU_AMD_9654 → "AMD"
model VARCHAR(100), -- Parsed: CPU_AMD_9654 → "9654"
specs JSON,
current_price DECIMAL(12,2),
price_method ENUM('manual', 'median', 'average', 'weighted_median') DEFAULT 'median',
price_period_days INT DEFAULT 90,
price_updated_at TIMESTAMP,
request_count INT DEFAULT 0,
last_request_date DATE,
popularity_score DECIMAL(10,4),
FOREIGN KEY (lot_name) REFERENCES lot(lot_name)
);
-- Categories
CREATE TABLE qt_categories (
id INT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(20) UNIQUE NOT NULL, -- MB, CPU, MEM, GPU, SSD, HDD, RAID, NIC, HCA, HBA, DPU, PS
name VARCHAR(100) NOT NULL,
name_ru VARCHAR(100),
display_order INT DEFAULT 0,
is_required BOOLEAN DEFAULT FALSE
);
-- Saved configurations
CREATE TABLE qt_configurations (
id INT AUTO_INCREMENT PRIMARY KEY,
uuid VARCHAR(36) UNIQUE NOT NULL,
user_id INT NOT NULL,
name VARCHAR(200) NOT NULL,
items JSON NOT NULL, -- [{"lot_name": "CPU_AMD_9654", "quantity": 2, "unit_price": 11500}]
total_price DECIMAL(12,2),
notes TEXT,
is_template BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES qt_users(id)
);
-- Price overrides
CREATE TABLE qt_price_overrides (
id INT AUTO_INCREMENT PRIMARY KEY,
lot_name CHAR(255) NOT NULL,
price DECIMAL(12,2) NOT NULL,
valid_from DATE NOT NULL,
valid_until DATE,
reason TEXT,
created_by INT NOT NULL,
FOREIGN KEY (lot_name) REFERENCES lot(lot_name)
);
-- Alerts for pricing admins
CREATE TABLE qt_pricing_alerts (
id INT AUTO_INCREMENT PRIMARY KEY,
lot_name CHAR(255) NOT NULL,
alert_type ENUM('high_demand_stale_price', 'price_spike', 'price_drop', 'no_recent_quotes', 'trending_no_price') NOT NULL,
severity ENUM('low', 'medium', 'high', 'critical') DEFAULT 'medium',
message TEXT NOT NULL,
details JSON,
status ENUM('new', 'acknowledged', 'resolved', 'ignored') DEFAULT 'new',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Usage statistics
CREATE TABLE qt_component_usage_stats (
lot_name CHAR(255) PRIMARY KEY,
quotes_total INT DEFAULT 0,
quotes_last_30d INT DEFAULT 0,
quotes_last_7d INT DEFAULT 0,
total_quantity INT DEFAULT 0,
total_revenue DECIMAL(14,2) DEFAULT 0,
trend_direction ENUM('up', 'stable', 'down') DEFAULT 'stable',
trend_percent DECIMAL(5,2) DEFAULT 0,
last_used_at TIMESTAMP
);
```
## Key Business Logic
### 1. Part Number Parsing
Extract category, vendor, model from lot_name:
```go
// "CPU_AMD_9654" → category="CPU", vendor="AMD", model="9654"
// "MB_INTEL_4.Sapphire_2S_32xDDR5" → category="MB", vendor="INTEL", model="4.Sapphire_2S_32xDDR5"
// "MEM_DDR5_64G_5600" → category="MEM", vendor="DDR5", model="64G_5600"
// "GPU_NV_RTX_4090_PCIe" → category="GPU", vendor="NV", model="RTX_4090_PCIe"
func ParsePartNumber(lotName string) (category, vendor, model string) {
parts := strings.SplitN(lotName, "_", 3)
if len(parts) >= 1 { category = parts[0] }
if len(parts) >= 2 { vendor = parts[1] }
if len(parts) >= 3 { model = parts[2] }
return
}
```
### 2. Price Calculation Methods
```go
// Median - simple median of prices in period
func CalculateMedian(prices []float64) float64
// Average - arithmetic mean
func CalculateAverage(prices []float64) float64
// Weighted Median - recent prices have higher weight (exponential decay)
// weight = e^(-days_since_quote / decay_days)
func CalculateWeightedMedian(prices []PricePoint, decayDays int) float64
```
### 3. Price Freshness (color coding)
```go
// Green: < 30 days AND >= 3 quotes
// Yellow: 30-60 days OR 1-2 quotes
// Orange: 60-90 days
// Red: > 90 days OR no price
func GetPriceFreshness(daysSinceUpdate int, quoteCount int) string {
if daysSinceUpdate < 30 && quoteCount >= 3 {
return "fresh" // green
} else if daysSinceUpdate < 60 {
return "normal" // yellow
} else if daysSinceUpdate < 90 {
return "stale" // orange
}
return "critical" // red
}
```
### 4. Component Sorting
Sort by: popularity + price freshness. Components without prices go to the bottom.
```go
// Sort score = popularity_score * 10 + freshness_bonus - no_price_penalty
// freshness_bonus: fresh=100, normal=50, stale=10, critical=0
// no_price_penalty: -1000 if current_price is NULL or 0
```
### 5. Alert Generation
Generate alerts when:
- **high_demand_stale_price** (CRITICAL): >= 5 quotes/month AND price > 60 days old
- **trending_no_price** (HIGH): trend_percent > 50% AND no price set
- **price_spike** (MEDIUM): price increased > 20% from previous period
- **no_recent_quotes** (MEDIUM): popular component, no supplier quotes > 90 days
## API Endpoints
### Auth
```
POST /api/auth/login → {"username", "password"} → {"token", "refresh_token"}
POST /api/auth/logout
POST /api/auth/refresh
GET /api/auth/me → current user info
```
### Components
```
GET /api/components → list with pagination
GET /api/components?category=CPU&vendor=AMD → filtered
GET /api/components/:lot_name → single component details
GET /api/categories → category list
```
### Quote Builder
```
POST /api/quote/validate → {"items": [...]} → {"valid": bool, "errors": [], "warnings": []}
POST /api/quote/calculate → {"items": [...]} → {"items": [...], "total": 45000.00}
```
### Export
```
POST /api/export/csv → {"items": [...], "name": "Config 1"} → CSV file
POST /api/export/xlsx → {"items": [...], "name": "Config 1"} → XLSX file
```
### Configurations
```
GET /api/configs → list user's configurations
POST /api/configs → save new configuration
GET /api/configs/:uuid → get by UUID
PUT /api/configs/:uuid → update
DELETE /api/configs/:uuid → delete
GET /api/configs/:uuid/export → export as JSON
POST /api/configs/import → import from JSON
```
### Pricing Admin (requires role: pricing_admin or admin)
```
GET /admin/pricing/stats → dashboard stats
GET /admin/pricing/components → components with pricing info
GET /admin/pricing/components/:lot_name → component pricing details
POST /admin/pricing/update → update price method/value
POST /admin/pricing/recalculate-all → recalculate all prices
GET /admin/pricing/alerts → list alerts
POST /admin/pricing/alerts/:id/acknowledge → mark as seen
POST /admin/pricing/alerts/:id/resolve → mark as resolved
POST /admin/pricing/alerts/:id/ignore → dismiss alert
```
### htmx Partials
```
GET /partials/components?category=CPU&vendor=AMD → HTML fragment
GET /partials/cart → cart HTML
GET /partials/summary → price summary HTML
```
## User Roles
| Role | Permissions |
|------|-------------|
| viewer | View components, create quotes, export |
| editor | + save/load configurations |
| pricing_admin | + manage prices, view alerts |
| admin | + manage users |
## Frontend Guidelines
- **Mobile-first** design
- Use **htmx** for interactivity (hx-get, hx-post, hx-target, hx-swap)
- Use **Tailwind CSS** via CDN
- Minimal custom JavaScript
- Color scheme for price freshness:
- `text-green-600 bg-green-50` - fresh
- `text-yellow-600 bg-yellow-50` - normal
- `text-orange-600 bg-orange-50` - stale
- `text-red-600 bg-red-50` - critical
## Commands
```bash
# Run development server
go run ./cmd/server
# Run price updater (cron job)
go run ./cmd/priceupdater
# Run importer (one-time setup)
go run ./cmd/importer
# Build for production
CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/quoteforge ./cmd/server
# Run tests
go test ./...
```
## Dependencies (go.mod)
```go
module git.mchus.pro/mchus/quoteforge
go 1.22
require (
github.com/gin-gonic/gin v1.9.1
github.com/go-sql-driver/mysql v1.7.1
gorm.io/gorm v1.25.5
gorm.io/driver/mysql v1.5.2
github.com/xuri/excelize/v2 v2.8.0
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/google/uuid v1.5.0
golang.org/x/crypto v0.17.0
gopkg.in/yaml.v3 v3.0.1
)
```
## Development Priorities
1. **Phase 1 (MVP):** Project setup, models, component API, basic UI, CSV export
2. **Phase 2:** JWT auth with roles, pricing admin UI, all price methods
3. **Phase 3:** Save/load configs, JSON import/export, XLSX export, cron jobs
4. **Phase 4:** Usage stats, alerts system, dashboard
5. **Phase 5:** Polish, tests, Docker, documentation
## Code Style
- Use standard Go formatting (gofmt)
- Error handling: always check errors, wrap with context
- Logging: use structured logging (slog or zerolog)
- Comments: in Russian or English, be consistent
- File naming: snake_case for files, PascalCase for types