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>
463 lines
24 KiB
Markdown
463 lines
24 KiB
Markdown
# 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. Клонирование репозитория
|
||
|
||
```bash
|
||
git clone https://github.com/your-company/quoteforge.git
|
||
cd quoteforge
|
||
```
|
||
|
||
### 2. Настройка runtime-конфига (опционально)
|
||
|
||
`config.yaml` создаётся автоматически при первом старте в той же user-state папке, где находится `qfs.db`.
|
||
Если найден старый формат, приложение автоматически мигрирует файл в актуальный runtime-формат
|
||
(оставляя только используемые секции `server` и `logging`).
|
||
|
||
При необходимости можно создать/отредактировать файл вручную:
|
||
|
||
```yaml
|
||
server:
|
||
host: "0.0.0.0"
|
||
port: 8080
|
||
mode: "release"
|
||
|
||
logging:
|
||
level: "info"
|
||
format: "json"
|
||
output: "stdout"
|
||
```
|
||
|
||
### 3. Миграции базы данных
|
||
|
||
```bash
|
||
go run ./cmd/qfs -migrate
|
||
```
|
||
|
||
### Мигратор OPS -> проекты (preview/apply)
|
||
|
||
Переносит квоты, чьи названия начинаются с `OPS-xxxx` (где `x` — цифра), в проект `OPS-xxxx`.
|
||
Если проекта нет, он будет создан; если архивный — реактивирован.
|
||
|
||
Сначала всегда смотрите preview:
|
||
|
||
```bash
|
||
go run ./cmd/migrate_ops_projects
|
||
```
|
||
|
||
Применение изменений:
|
||
|
||
```bash
|
||
go run ./cmd/migrate_ops_projects -apply
|
||
```
|
||
|
||
Без интерактивного подтверждения:
|
||
|
||
```bash
|
||
go run ./cmd/migrate_ops_projects -apply -yes
|
||
```
|
||
|
||
### Права БД для пользователя приложения
|
||
|
||
#### Полный набор прав для обычного пользователя
|
||
|
||
Чтобы выдать существующему пользователю все необходимые права (без переоздания):
|
||
|
||
```sql
|
||
-- Справочные таблицы (только чтение)
|
||
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 | Отслеживание активности синхронизации |
|
||
|
||
#### При создании нового пользователя
|
||
|
||
Если нужно создать нового пользователя с нуля:
|
||
|
||
```sql
|
||
-- 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. Импорт метаданных компонентов
|
||
|
||
```bash
|
||
go run ./cmd/importer
|
||
```
|
||
|
||
### 5. Запуск
|
||
|
||
```bash
|
||
# 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 команды:**
|
||
```bash
|
||
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 показывается красный индикатор и причина блокировки в модалке синхронизации.
|
||
|
||
#### Схема потоков данных синхронизации
|
||
|
||
```text
|
||
[ 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-методом:
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
# Сборка образа
|
||
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.
|
||
|
||
## Разработка
|
||
|
||
```bash
|
||
# Запуск в режиме разработки (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](LICENSE) для подробностей.
|