- Implement RefreshPrices for local-first mode - Update prices from local_components.current_price cache - Graceful degradation when component not found - Add PriceUpdatedAt timestamp to LocalConfiguration model - Support both authenticated and no-auth price refresh - Fix sync duplicate entry bug - pushConfigurationUpdate now ensures server_id exists before update - Fetch from LocalConfiguration.ServerID or search on server if missing - Update local config with server_id after finding - Add application auto-restart after settings save - Implement restartProcess() using syscall.Exec - Setup handler signals restart via channel - Setup page polls /health endpoint and redirects when ready - Add "Back" button on setup page when settings exist - Fix setup handler password handling - Use PasswordEncrypted field consistently - Support empty password by using saved value - Improve sync status handling - Add fallback for is_offline check in SyncStatusPartial - Enhance background sync logging with prefixes - Update CLAUDE.md documentation - Mark Phase 2.5 tasks as complete - Add UI Improvements section with future tasks - Update SQLite tables documentation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
7.7 KiB
7.7 KiB
QuoteForge - Claude Code Instructions
Overview
Корпоративный конфигуратор серверов и формирование КП. MariaDB (RFQ_LOG) + SQLite для оффлайн.
Development Phases
Phase 1: Pricelists in MariaDB ✅ DONE
Phase 2: Local SQLite Database ✅ DONE
Phase 2.5: Full Offline Mode 🔶 IN PROGRESS
Local-first architecture: приложение ВСЕГДА работает с SQLite, MariaDB только для синхронизации.
Принцип работы:
- ВСЕ операции (CRUD) выполняются в SQLite
- При создании конфигурации:
- Если online → проверить новые прайслисты на сервере → скачать если есть
- Далее работаем с local_pricelists (и online, и offline одинаково)
- Background sync: push pending_changes → pull updates
DONE:
- ✅ Sync queue table (pending_changes) -
internal/localdb/models.go - ✅ Model converters: MariaDB ↔ SQLite -
internal/localdb/converters.go - ✅ LocalConfigurationService: все CRUD через SQLite -
internal/services/local_configuration.go - ✅ Pre-create pricelist check:
SyncPricelistsIfNeeded()-internal/services/sync/service.go - ✅ Push pending changes:
PushPendingChanges()- sync service + handlers - ✅ Sync API endpoints:
/api/sync/push,/pending/count,/pending - ✅ Integrate LocalConfigurationService in main.go (replace ConfigurationService)
- ✅ Add routes for new sync endpoints (
/api/sync/push,/pending/count,/pending) - ✅ ConfigurationGetter interface for handler compatibility
- ✅ Background sync worker: auto-sync every 5min (push + pull) -
internal/services/sync/worker.go - ✅ UI: sync status indicator (pending badge + sync button + offline/online dot) -
web/templates/partials/sync_status.html - ✅ RefreshPrices for local mode:
RefreshPrices()/RefreshPricesNoAuth()вlocal_configuration.go- Берёт цены из
local_components.current_price - Graceful degradation при отсутствии компонента
- Добавлено поле
price_updated_atвLocalConfiguration(models.go:72) - Обновлены converters для PriceUpdatedAt
- UI кнопка "Пересчитать цену" работает offline/online
- ✅ Fixed sync bugs:
- Duplicate entry error при update конфигураций (
sync/service.go:334-365) - pushConfigurationUpdate теперь проверяет наличие server_id перед update
- Если нет ID → получает из LocalConfiguration.ServerID или ищет на сервере
- Fixed setup.go:
settings.Password→settings.PasswordEncrypted
- Duplicate entry error при update конфигураций (
TODO:
- ❌ Conflict resolution (Phase 4, last-write-wins default)
UI Improvements 🔶 IN PROGRESS
1. Sync icon + pricelist badge в header (tasks 4+2):
- ❌
sync_status.html: заменить текст Online/Offline на SVG иконку - ❌ Кнопка sync → иконка (circular arrows) вместо текста
- ❌ Dropdown при клике: Push changes, Full sync, статус последней синхронизации
- ❌
configs.html: рядом с кнопкой "Создать" показать badge с версией активного прайслиста - ❌ Загружать через
/api/pricelists/latestпри DOMContentLoaded
2. Прайслисты → вкладка в "Администратор цен" (task 1):
- ❌
base.html: убрать отдельную ссылку "Прайслисты" из навигации - ❌
admin_pricing.html: добавить 4-ю вкладку "Прайслисты" - ❌ Перенести логику из
pricelists.html(table, create modal, CRUD) в эту вкладку - ❌ Route
/pricelists→ редирект на/admin/pricing?tab=pricelistsили удалить
3. Страница настроек: расширить + синхронизация (task 3):
- ❌
setup.html: переделать на{{template "base" .}}структуру - ❌ Увеличить до
max-w-4xl, разделить на 2 секции - ❌ Секция A: Подключение к БД (текущая форма)
- ❌ Секция B: Синхронизация данных:
- Статус Online/Offline
- Кнопки: "Синхронизировать всё", "Обновить компоненты", "Обновить прайслисты"
- Журнал синхронизации (последние N операций)
- ❌ Возможно: новый API endpoint для sync log
Phase 3: Projects and Specifications
- qt_projects, qt_specifications tables (MariaDB)
- Replace qt_configurations → Project/Specification hierarchy
- Fields: opty, customer_requirement, variant, qty, rev
- Local projects/specs with server sync
Phase 4: Price Versioning
- Bind specifications to pricelist versions
- Price diff comparison
- Auto-cleanup expired pricelists (>1 year, usage_count=0)
Tech Stack
Go 1.22+ | Gin | GORM | MariaDB 11 | SQLite (glebarez/sqlite) | htmx + Tailwind CDN | excelize
Key Tables
READ-ONLY (external systems)
lot(lot_name PK, lot_description)lot_log(lot, supplier, date, price, quality, comments)supplier(supplier_name PK)
MariaDB (qt_* prefix)
qt_lot_metadata- component prices, methods, popularityqt_categories- category codes and namesqt_pricelists- version snapshots (YYYY-MM-DD-NNN format)qt_pricelist_items- prices per pricelistqt_projects- uuid, opty, customer_requirement, name (Phase 3)qt_specifications- project_id, pricelist_id, variant, rev, qty, items JSON (Phase 3)
SQLite (data/quoteforge.db)
connection_settings- encrypted DB credentials (PasswordEncrypted field)local_pricelists/items- cached from serverlocal_components- lot cache for offline search (with current_price)local_configurations- UUID, items, price_updated_at, sync_status (pending/synced/conflict), server_idlocal_projects/specifications- Phase 3pending_changes- sync queue (entity_type, uuid, op, payload, created_at, attempts, last_error)
Business Logic
Part number parsing: CPU_AMD_9654 → category=CPU, model=AMD_9654
Price methods: manual | median | average | weighted_median
Price freshness: fresh (<30d, ≥3 quotes) | normal (<60d) | stale (<90d) | critical
Pricelist version: YYYY-MM-DD-NNN (e.g., 2024-01-31-001)
API Endpoints
| Group | Endpoints |
|---|---|
| Setup | GET/POST /setup, POST /setup/test |
| Components | GET /api/components, /api/categories |
| Pricelists | CRUD /api/pricelists, GET /latest, POST /compare |
| Projects | CRUD /api/projects/:uuid (Phase 3) |
| Specs | CRUD /api/specs/:uuid, POST /upgrade, GET /diff (Phase 3) |
| Configs | POST /:uuid/refresh-prices (обновить цены из local_components) |
| Sync | GET /status, POST /components, /pricelists, /push, /pull, /resolve-conflict |
| Export | GET /api/specs/:uuid/export, /api/projects/:uuid/export |
Commands
go run ./cmd/server # Dev server
go run ./cmd/cron -job=X # cleanup-pricelists | update-prices | update-popularity
CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/quoteforge ./cmd/server
Code Style
- gofmt, structured logging (slog), wrap errors with context
- snake_case files, PascalCase types
- RBAC disabled: DB username = user_id via
models.EnsureDBUser()
UI Guidelines
- htmx (hx-get/post/target/swap), Tailwind CDN
- Freshness colors: green (fresh) → yellow → orange → red (critical)
- Sync status + offline indicator in header