Files
QuoteForge/README.md
Mikhail Chusavitin 84dda8cf0a docs: document complete database user permissions for sync support
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>
2026-02-09 11:30:09 +03:00

24 KiB
Raw Blame History

QuoteForge

Server Configuration & Quotation Tool

QuoteForge — корпоративный инструмент для конфигурирования серверов и формирования коммерческих предложений (КП).
Приложение работает в strict local-first режиме: пользовательские операции выполняются через локальную SQLite, MariaDB используется для синхронизации и серверного администрирования прайслистов.

Go Version License Status

Возможности

Для пользователей

  • 📱 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_uuid
  • operation (create / update / rollback)
  • current_version_id и current_version_no
  • snapshot (текущее состояние конфигурации)
  • 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 — алерты для администраторов

Поддержка

По вопросам работы приложения обращайтесь:

Лицензия

Данное программное обеспечение является собственностью компании и предназначено исключительно для внутреннего использования. Распространение, копирование или модификация без письменного разрешения запрещены.

См. файл LICENSE для подробностей.