Add comprehensive database permissions documentation: - Full list of required tables with their purpose - Separate sections for: existing user grants, new user creation, and important notes - Clarifies that sync tables (qt_client_local_migrations, qt_client_schema_state, qt_pricelist_sync_status) must be created by DB admin - app doesn't need CREATE TABLE - Explains read-only vs read-write permissions for each table - Uses placeholder '<DB_USER>' instead of hardcoded usernames This helps administrators set up proper permissions without CREATE TABLE requirements, fixing the sync blockage issue in v1.1.0. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
24 KiB
QuoteForge
Server Configuration & Quotation Tool
QuoteForge — корпоративный инструмент для конфигурирования серверов и формирования коммерческих предложений (КП).
Приложение работает в strict local-first режиме: пользовательские операции выполняются через локальную SQLite, MariaDB используется для синхронизации и серверного администрирования прайслистов.
Возможности
Для пользователей
- 📱 Mobile-first интерфейс — удобная работа с телефона и планшета
- 🖥️ Конфигуратор серверов — пошаговый выбор компонентов с проверкой совместимости
- 💰 Автоматический расчёт цен — актуальные цены на основе истории закупок
- 📊 Экспорт в CSV/XLSX — готовые спецификации для клиентов
- 💾 Сохранение конфигураций — история и шаблоны для повторного использования
- 🔌 Полная офлайн-работа — можно продолжать работу без сети и синхронизировать позже
- 🛡️ Защищенная синхронизация — sync блокируется preflight-проверкой, если локальная схема не готова
Для ценовых администраторов
- 📈 Умный расчёт цен — медиана, взвешенная медиана, среднее
- 🎯 Система алертов — уведомления о популярных компонентах с устаревшими ценами
- 📉 Аналитика использования — какие компоненты востребованы в КП
- ⚙️ Гибкие настройки — периоды расчёта, методы, ручные переопределения
Индикация актуальности цен
| Цвет | Статус | Условие |
|---|---|---|
| 🟢 Зелёный | Свежая | < 30 дней, ≥ 3 источника |
| 🟡 Жёлтый | Нормальная | 30-60 дней |
| 🟠 Оранжевый | Устаревающая | 60-90 дней |
| 🔴 Красный | Устаревшая | > 90 дней или нет данных |
Технологии
- Backend: Go 1.22+, Gin, GORM
- Frontend: HTML, Tailwind CSS, htmx
- Database: SQLite (runtime/local-first), MariaDB 11+ (sync + server admin)
- Export: excelize (XLSX), encoding/csv
Требования
- Go 1.22 или выше
- MariaDB 11.x (или MySQL 8.x)
- ~50 MB дискового пространства
Установка
1. Клонирование репозитория
git clone https://github.com/your-company/quoteforge.git
cd quoteforge
2. Настройка runtime-конфига (опционально)
config.yaml создаётся автоматически при первом старте в той же user-state папке, где находится qfs.db.
Если найден старый формат, приложение автоматически мигрирует файл в актуальный runtime-формат
(оставляя только используемые секции server и logging).
При необходимости можно создать/отредактировать файл вручную:
server:
host: "0.0.0.0"
port: 8080
mode: "release"
logging:
level: "info"
format: "json"
output: "stdout"
3. Миграции базы данных
go run ./cmd/qfs -migrate
Мигратор OPS -> проекты (preview/apply)
Переносит квоты, чьи названия начинаются с OPS-xxxx (где x — цифра), в проект OPS-xxxx.
Если проекта нет, он будет создан; если архивный — реактивирован.
Сначала всегда смотрите preview:
go run ./cmd/migrate_ops_projects
Применение изменений:
go run ./cmd/migrate_ops_projects -apply
Без интерактивного подтверждения:
go run ./cmd/migrate_ops_projects -apply -yes
Права БД для пользователя приложения
Полный набор прав для обычного пользователя
Чтобы выдать существующему пользователю все необходимые права (без переоздания):
-- Справочные таблицы (только чтение)
GRANT SELECT ON RFQ_LOG.lot TO '<DB_USER>'@'%';
GRANT SELECT ON RFQ_LOG.qt_lot_metadata TO '<DB_USER>'@'%';
GRANT SELECT ON RFQ_LOG.qt_categories TO '<DB_USER>'@'%';
GRANT SELECT ON RFQ_LOG.qt_pricelists TO '<DB_USER>'@'%';
GRANT SELECT ON RFQ_LOG.qt_pricelist_items TO '<DB_USER>'@'%';
-- Таблицы конфигураций и проектов (чтение и запись)
GRANT SELECT, INSERT, UPDATE ON RFQ_LOG.qt_configurations TO '<DB_USER>'@'%';
GRANT SELECT, INSERT, UPDATE ON RFQ_LOG.qt_projects TO '<DB_USER>'@'%';
-- Таблицы синхронизации (только чтение для миграций, чтение+запись для статуса)
GRANT SELECT ON RFQ_LOG.qt_client_local_migrations TO '<DB_USER>'@'%';
GRANT SELECT, INSERT, UPDATE ON RFQ_LOG.qt_client_schema_state TO '<DB_USER>'@'%';
GRANT SELECT, INSERT, UPDATE ON RFQ_LOG.qt_pricelist_sync_status TO '<DB_USER>'@'%';
-- Применить изменения
FLUSH PRIVILEGES;
-- Проверка выданных прав
SHOW GRANTS FOR '<DB_USER>'@'%';
Таблицы и их назначение
| Таблица | Назначение | Права | Примечание |
|---|---|---|---|
lot |
Справочник компонентов | SELECT | Существующая таблица |
qt_lot_metadata |
Расширенные данные компонентов | SELECT | Метаданные компонентов |
qt_categories |
Категории компонентов | SELECT | Справочник |
qt_pricelists |
Прайслисты | SELECT | Управляется сервером |
qt_pricelist_items |
Позиции прайслистов | SELECT | Управляется сервером |
qt_configurations |
Сохранённые конфигурации | SELECT, INSERT, UPDATE | Основная таблица работы |
qt_projects |
Проекты | SELECT, INSERT, UPDATE | Для группировки конфигураций |
qt_client_local_migrations |
Справочник миграций БД | SELECT | Только чтение (управляется админом) |
qt_client_schema_state |
Состояние локальной схемы | SELECT, INSERT, UPDATE | Отслеживание примененных миграций |
qt_pricelist_sync_status |
Статус синхронизации | SELECT, INSERT, UPDATE | Отслеживание активности синхронизации |
При создании нового пользователя
Если нужно создать нового пользователя с нуля:
-- 1) Создать пользователя
CREATE USER IF NOT EXISTS 'quote_user'@'%' IDENTIFIED BY '<DB_PASSWORD>';
-- 2) Выдать все необходимые права
GRANT SELECT ON RFQ_LOG.lot TO 'quote_user'@'%';
GRANT SELECT ON RFQ_LOG.qt_lot_metadata TO 'quote_user'@'%';
GRANT SELECT ON RFQ_LOG.qt_categories TO 'quote_user'@'%';
GRANT SELECT ON RFQ_LOG.qt_pricelists TO 'quote_user'@'%';
GRANT SELECT ON RFQ_LOG.qt_pricelist_items TO 'quote_user'@'%';
GRANT SELECT, INSERT, UPDATE ON RFQ_LOG.qt_configurations TO 'quote_user'@'%';
GRANT SELECT, INSERT, UPDATE ON RFQ_LOG.qt_projects TO 'quote_user'@'%';
GRANT SELECT ON RFQ_LOG.qt_client_local_migrations TO 'quote_user'@'%';
GRANT SELECT, INSERT, UPDATE ON RFQ_LOG.qt_client_schema_state TO 'quote_user'@'%';
GRANT SELECT, INSERT, UPDATE ON RFQ_LOG.qt_pricelist_sync_status TO 'quote_user'@'%';
-- 3) Применить изменения
FLUSH PRIVILEGES;
-- 4) Проверить права
SHOW GRANTS FOR 'quote_user'@'%';
Важные замечания
- Таблицы синхронизации должны быть созданы администратором БД один раз. Приложение не требует прав CREATE TABLE.
- Прайслисты (
qt_pricelists,qt_pricelist_items) — справочные таблицы, управляются сервером, пользователь имеет только SELECT. - Конфигурации и проекты — таблицы, в которые пишет само приложение (INSERT, UPDATE при сохранении изменений).
- Таблицы миграций нужны для синхронизации: приложение читает список миграций и отчитывается о применённых.
- Если видите ошибку
Access denied for user ...@'<ip>', проверьте наличие конфликтующих записей пользователя с разными хостами (user@localhost vs user@'%').
4. Импорт метаданных компонентов
go run ./cmd/importer
5. Запуск
# Development
go run ./cmd/qfs
# Production (with Makefile - recommended)
make build-release # Builds with version info
./bin/qfs -version # Check version
# Production (manual)
VERSION=$(git describe --tags --always --dirty)
CGO_ENABLED=0 go build -ldflags="-s -w -X main.Version=$VERSION" -o bin/qfs ./cmd/qfs
./bin/qfs -version
Makefile команды:
make build-release # Оптимизированная сборка с версией
make build-all # Сборка для всех платформ (Linux, macOS, Windows)
make build-windows # Только для Windows
make run # Запуск dev сервера
make test # Запуск тестов
make install-hooks # Установить git hooks (блокировка коммита с секретами)
make clean # Очистка bin/
make help # Показать все команды
Приложение будет доступно по адресу: http://localhost:8080
Локальная SQLite база (state)
Локальная база приложения хранится в профиле пользователя и не зависит от расположения бинарника.
Имя файла: qfs.db.
- macOS:
~/Library/Application Support/QuoteForge/qfs.db - Linux:
$XDG_STATE_HOME/quoteforge/qfs.db(или~/.local/state/quoteforge/qfs.db) - Windows:
%LOCALAPPDATA%\\QuoteForge\\qfs.db
Можно переопределить путь через -localdb или переменную окружения QFS_DB_PATH.
Sync readiness guard
Перед push/pull выполняется preflight-проверка:
- доступен ли сервер (MariaDB);
- можно ли проверить и применить централизованные миграции локальной БД;
- подходит ли версия приложения под
min_app_versionмиграций.
Если проверка не пройдена:
- локальная работа (CRUD) продолжается;
- sync API возвращает
423 Lockedсreason_codeиreason_text; - в UI показывается красный индикатор и причина блокировки в модалке синхронизации.
Схема потоков данных синхронизации
[ SERVER / MariaDB ]
┌───────────────────────────┐
│ qt_projects │
│ qt_configurations │
│ qt_pricelists │
│ qt_pricelist_items │
│ qt_pricelist_sync_status │
└─────────────┬─────────────┘
│
pull (projects/configs/pricelists)
│
┌──────────────────┴──────────────────┐
│ │
[ CLIENT A / local SQLite ] [ CLIENT B / local SQLite ]
┌───────────────────────────────┐ ┌───────────────────────────────┐
│ local_projects │ │ local_projects │
│ local_configurations │ │ local_configurations │
│ local_pricelists │ │ local_pricelists │
│ local_pricelist_items │ │ local_pricelist_items │
│ pending_changes (proj/config) │ │ pending_changes (proj/config) │
└───────────────┬───────────────┘ └───────────────┬───────────────┘
│ │
push (projects/configurations only) push (projects/configurations only)
│ │
└──────────────────┬────────────────────┘
│
[ SERVER / MariaDB ]
По сущностям:
- Конфигурации:
Client <-> Server <-> Other Clients - Проекты:
Client <-> Server <-> Other Clients - Прайслисты:
Server -> Clients only(локальный push отсутствует) - Локальная очистка прайслистов на клиенте: удаляются записи, которых нет на сервере и которые не используются активными локальными конфигурациями
Версионность конфигураций (local-first)
Для local_configurations используется append-only versioning через полные snapshot-версии:
- таблица:
local_configuration_versions - для каждого изменения создаётся новая версия (
version_no = max + 1) local_configurations.current_version_idуказывает на активную версию- старые версии не изменяются и не удаляются в обычном потоке
- rollback не "перематывает" историю, а создаёт новую версию из выбранного snapshot
При backfill (миграция 006_add_local_configuration_versions.sql) для существующих конфигураций создаётся v1 и проставляется current_version_id.
Rollback
Rollback выполняется API-методом:
POST /api/configs/:uuid/rollback
{
"target_version": 3,
"note": "optional"
}
Результат:
- создаётся новая версия
vNсdataиз целевой версии change_note = "rollback to v{target_version}"(+ note, если передан)current_version_idпереключается на новую версию- конфигурация уходит в
sync_status = pending
Локальный config.yaml
По умолчанию qfs ищет config.yaml в той же user-state папке, где лежит qfs.db (а не рядом с бинарником).
Если файла нет, он создаётся автоматически. Если формат устарел, он автоматически мигрируется в runtime-формат (server + logging).
Можно переопределить путь через -config или QFS_CONFIG_PATH.
Docker
# Сборка образа
docker build -t quoteforge .
# Запуск с docker-compose
docker-compose up -d
Структура проекта
quoteforge/
├── cmd/
│ ├── server/main.go # Main HTTP server
│ └── importer/main.go # Import metadata from lot table
├── internal/
│ ├── config/ # Конфигурация
│ ├── models/ # GORM модели
│ ├── handlers/ # HTTP handlers
│ ├── services/ # Бизнес-логика
│ ├── middleware/ # Auth, CORS, etc.
│ └── repository/ # Работа с БД
├── web/
│ ├── templates/ # HTML шаблоны
│ └── static/ # CSS, JS, изображения
├── migrations/ # SQL миграции
├── config.example.yaml # Пример конфигурации
└── go.mod
Роли пользователей
| Роль | Описание |
|---|---|
viewer |
Просмотр, создание квот, экспорт |
editor |
+ сохранение конфигураций |
pricing_admin |
+ управление ценами и алертами |
admin |
Полный доступ, управление пользователями |
API
Документация API доступна по адресу /api/docs (в разработке).
Основные endpoints:
POST /api/auth/login # Авторизация
GET /api/components # Список компонентов
POST /api/quote/calculate # Расчёт цены
POST /api/export/xlsx # Экспорт в Excel
GET /api/configs # Сохранённые конфигурации
GET /api/configs/:uuid/versions # Список версий конфигурации
GET /api/configs/:uuid/versions/:version # Получить конкретную версию
POST /api/configs/:uuid/rollback # Rollback на указанную версию
POST /api/configs/:uuid/reactivate # Вернуть архивную конфигурацию в активные
GET /api/sync/readiness # Статус readiness guard (ready|blocked|unknown)
GET /api/sync/status # Сводный статус синхронизации
GET /api/sync/info # Данные для модалки синхронизации
POST /api/sync/push # Push pending changes (423, если blocked)
POST /api/sync/all # Full sync push+pull (423, если blocked)
POST /api/sync/components # Pull components (423, если blocked)
POST /api/sync/pricelists # Pull pricelists (423, если blocked)
Краткая карта sync API
| Endpoint | Назначение | Поток |
|---|---|---|
POST /api/sync/push |
Отправить локальные pending-изменения | SQLite -> MariaDB |
POST /api/sync/components |
Подтянуть справочник компонентов | MariaDB -> SQLite |
POST /api/sync/pricelists |
Подтянуть прайслисты и позиции | MariaDB -> SQLite |
POST /api/sync/all |
Полный цикл: push + pull + импорт проектов/конфигураций | двунаправленно |
GET /api/sync/readiness |
Статус preflight/readiness | read-only |
GET /api/sync/status / GET /api/sync/info |
Сводка статуса и данных синхронизации | read-only |
Sync payload для versioning
События в pending_changes для конфигураций содержат:
configuration_uuidoperation(create/update/rollback)current_version_idиcurrent_version_nosnapshot(текущее состояние конфигурации)idempotency_keyиconflict_policy(last_write_wins)
Это позволяет push-слою отправлять на сервер актуальное состояние и готовит основу для будущего conflict resolution.
Разработка
# Запуск в режиме разработки (hot reload)
go run ./cmd/qfs
# Запуск тестов
go test ./...
# Сборка для Linux
CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/qfs ./cmd/qfs
Переменные окружения
| Переменная | Описание | По умолчанию |
|---|---|---|
QF_DB_HOST |
Хост базы данных | localhost |
QF_DB_PORT |
Порт базы данных | 3306 |
QF_DB_NAME |
Имя базы данных | RFQ_LOG |
QF_DB_USER |
Пользователь БД | — |
QF_DB_PASSWORD |
Пароль БД | — |
QF_JWT_SECRET |
Секрет для JWT | — |
QF_SERVER_PORT |
Порт сервера | 8080 |
QFS_DB_PATH |
Полный путь к локальной SQLite БД | OS-specific user state dir |
QFS_STATE_DIR |
Каталог state (если QFS_DB_PATH не задан) |
OS-specific user state dir |
QFS_CONFIG_PATH |
Полный путь к config.yaml |
OS-specific user state dir |
Интеграция с существующей БД
QuoteForge интегрируется с существующей базой RFQ_LOG:
lot— справочник компонентов (только чтение)lot_log— история цен от поставщиков (только чтение)supplier— справочник поставщиков (только чтение)
Новые таблицы QuoteForge имеют префикс qt_:
qt_users— пользователи приложенияqt_lot_metadata— расширенные данные компонентовqt_configurations— сохранённые конфигурацииqt_pricing_alerts— алерты для администраторов
Поддержка
По вопросам работы приложения обращайтесь:
- Email: mike@mchus.pro
- Internal: @mchus
Лицензия
Данное программное обеспечение является собственностью компании и предназначено исключительно для внутреннего использования. Распространение, копирование или модификация без письменного разрешения запрещены.
См. файл LICENSE для подробностей.